How to audit variable font size, glyph count, and axes before you self-host
- Step 1Grab the exact file you plan to host — Take the precise artifact destined for your server/CDN — ideally the served WOFF2, so the audited byte size matches reality.
- Step 2Upload it to the audit — Drop the single `.ttf`/`.otf`/`.woff`/`.woff2`. The tool decodes WOFF/WOFF2 internally to read glyphs and axes.
- Step 3Check axes first — If `axes` is 0, stop — there's no `fvar` table, so this is a static instance, not the variable family you meant to host. Find the real master.
- Step 4Weigh the byte size — Read `variable_file_bytes`. Decide if that per-request payload is acceptable on your infra and your users' connections.
- Step 5Vet glyph coverage — Compare `glyph_count` against the characters you actually render. A big gap means you're about to host dead weight — subset it down.
- Step 6Sanity-check the strategy and remediate — Read the projection at your weight count; if static wins, host frozen subsets instead. Subset with the [Font Subsetter](/font-tools/font-subsetter), check coverage with [Font Metrics Analyzer](/font-tools/font-metrics-analyzer), and inspect counts with [Glyph Count Analyzer](/font-tools/glyph-count-analyzer).
Pre-self-host audit checklist
What each output field tells you and the action it should trigger.
| Field | Healthy signal | Red flag -> action |
|---|---|---|
axes | >= 1 (real variable font) | 0 -> static file; find the variable master |
variable_file_bytes | Within your per-request budget | Large -> reconsider hosting it at all |
glyph_count | Close to your charset | Far above charset -> subset before hosting |
variable_wins (at your count) | true -> host the variable file | false -> host static subsets instead |
Format handling at audit time
All four input formats are accepted; the byte size audited is always the uploaded file's size.
| Input format | Detection | Decoded for glyph/axis read? | Bytes audited |
|---|---|---|---|
TTF (0x00010000) | Magic bytes | Used directly | Uploaded size |
OTF (OTTO) | Magic bytes | Used directly | Uploaded size |
WOFF (wOFF) | Magic bytes | Decompressed (zlib) to sfnt | Uploaded size |
WOFF2 (wOF2) | Magic bytes | Decompressed (Brotli) to sfnt | Uploaded size |
Cookbook
Audit outputs for fonts about to be self-hosted, with the call each one prompts.
Clean variable font, host it
ExampleA well-subsetted Latin variable WOFF2 with two axes.
Upload: Inter.subset.woff2 variable_file_bytes: 96000 glyph_count: 340 axes: 2 breakeven_static_count: 3 # Reasonable size, tight coverage, real axes -> host the variable file.
Renamed static caught at the gate
ExampleA file named '*-Variable' that is actually a frozen instance.
Upload: Brand-Variable.woff2 glyph_count: 410 axes: 0 <-- NO fvar table # This is a static instance. Do not host it as 'the variable font'.
Oversized coverage flagged
ExampleA full multi-script family far larger than the site's charset.
variable_file_bytes: 1380000 (1.32 MB) glyph_count: 5200 axes: 3 # 1.32 MB / 5,200 glyphs for a Latin-only site = dead weight. # Subset to your charset before self-hosting.
Free-tier gate stops a bloated upload
ExampleTrying to audit a 7 MB CJK variable on the free tier.
Upload: Noto-CJK-Variable.ttf (7 MB) -> Rejected: exceeds free 5 MB font limit # Subset first, or audit on Pro (50 MB). A 7 MB self-hosted font is a red flag anyway.
Strategy check says host subsets
ExampleFor a 2-weight site, the projection prefers static.
2 weights: variable_wins false, delta_bytes -negative # Host two frozen static subsets rather than the full variable file.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
No file uploaded
Error: Upload a variable font.The audit requires the file you intend to host; it throws without one.
axes is 0 — static file mislabelled
axes: 0parseFvar returns null with no fvar table, so axes is 0. This is the single most useful audit catch: a static instance about to be hosted as 'the variable font'. Locate the real master.
glyph_count far exceeds your charset
Subset before hostingA high glyph_count means you'd self-host coverage you never render. The tool reports the count but does not subset — trim with the Font Subsetter first.
Byte size is the uploaded size, not re-encoded
Uses file.sizevariable_file_bytes is exactly the uploaded file's size. Upload the served WOFF2, not a build-time TTF, or the audited payload won't match what you'd actually serve.
Font over the free 5 MB limit
Rejected (tier limit)Free tier caps at 5 MB. A font this big is a self-hosting cost concern in itself; subset or audit on Pro.
Font over the free 1,000-glyph limit
Rejected (glyph limit)Free tier enforces 1,000 glyphs. Hitting it is a signal to subset before you self-host.
Static estimate is heuristic
Heuristic onlyThe projection's static side is 40% of variable per weight, not a measurement. Use it to sanity-check strategy, then verify real bytes for the option you host.
Break-even fixed at 3
By constructionbreakeven_static_count is always 3 due to the 0.4 multiplier; it's not a per-font finding. Judge by byte numbers and coverage.
Options provided
IgnoredThe audit ignores options and always returns the full projection plus the size/glyph/axis facts.
Unsupported or corrupt file
Error: Unsupported font formatAnything failing the TTF/OTF/WOFF/WOFF2 magic-byte check throws before the audit runs.
Frequently asked questions
What should I check before self-hosting a variable font?
Three things this audit reports: axes (must be >= 1 for a real variable font), variable_file_bytes (within your per-request budget), and glyph_count (close to your real charset, not bloated). Plus the static projection to confirm the variable file is the right strategy.
How do I know a file is actually variable?
axes must be at least 1. If it's 0, there's no fvar table and you've got a static instance — do not host it as the variable font.
Why does the byte size differ from what I expected?
variable_file_bytes is the uploaded file's size verbatim. An uncompressed TTF will read much larger than the WOFF2 you'd serve; upload the served file.
Is a high glyph_count a problem?
If it far exceeds the characters you render, yes — you'd self-host dead weight. Subset with the Font Subsetter to your real charset first.
Does the audit subset or optimise the font?
No. It reports size, glyph count, axes, and the static projection. To reduce bytes, subset with the Font Subsetter or freeze a single instance with the Variable Font Freezer.
What formats can I audit?
TTF, OTF, WOFF, and WOFF2. WOFF/WOFF2 are decompressed internally so glyph and axis counts are read correctly.
Why was my font rejected?
On the free tier, files over 5 MB or 1,000 glyphs are rejected. That's also a useful early signal that the font is too heavy to self-host as-is. Pro raises the limits to 50 MB and 65,536 glyphs.
Should I host the variable file or static subsets?
Read variable_wins at your weight count. If true, host the variable file; if false, host frozen static subsets instead. Remember the static side is an estimate.
Can I audit two candidate fonts at once?
No — it's one file per run. Audit each candidate separately and compare the outputs.
How do I check glyph coverage in detail?
Use the Glyph Count Analyzer and Font Metrics Analyzer to go beyond the raw count this audit reports.
Is the font uploaded to a server?
The audit runs in the browser and returns JSON; the font isn't sent to a server for the calculation.
What's in the output file?
<stem>.size-compare.json with variable_file_bytes, glyph_count, axes, estimated_static_per_weight_bytes, breakeven_static_count, and the 1-9 projections array.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.