What a font subsetter actually removes
A webfont is mostly glyphs your pages will never show — alphabets you do not write in, symbols nobody has typed since 1994. Cutting them is the largest single saving available to a text site, and the reader gives up nothing at all for it.
A retail text font carries two to three thousand glyphs. A blog in English uses perhaps two hundred of them; add Vietnamese and it is four hundred. Everything else is Greek, Cyrillic, currency for markets you do not serve, and arrows.
Subsetting removes what a page cannot show. It is not compression and it is not a quality trade: the glyphs that stay are byte-identical to the ones the foundry drew.
Three separate cuts, and they compound
The saving is easy to predict before you run anything. For a face of glyphs of which a site can show , and a per-glyph cost that is roughly constant within a family,
Two hundred glyphs kept out of twenty-eight hundred is a saving of about ninety-three per cent, before a single variation axis has been touched.
Cut one is the codepoints. Keep latin, latin-ext and vietnamese, drop the rest.
Cut two is the variation axes. A variable font stores deltas for every axis it declares across its whole declared range. A family offering weights 400 to 700 in the product is storing 200-to-900 deltas nobody can select.
Cut three is the tables: hinting instructions no modern renderer consults, and layout features a text page never triggers.
pyftsubset Literata.ttf \
--unicodes-file=vietnamese.txt \
--layout-features='kern,liga,onum,tnum' \
--variations='wght=400:700' \
--drop-tables+=DSIG \
--flavor=woff2 --output-file=literata-vietnamese.woff2
It prints back what it kept, and this block is the other kind — a fence with no language on it, which is what most fences are. Nothing here knows a grammar to colour it with, so only the two things true of any notation are marked: what sits inside quotes, and a $NAME.
kept "latin, latin-ext, vietnamese" dropped greek, cyrillic, +19 more
axes "wght 400..700" dropped opsz, ital
wrote $OUT/literata-vietnamese.woff2 46.2 KB (was 97.6 KB)
What the cuts are worth
Measured on this site's own faces, from upstream sources rather than by re-cutting an already-cut file:
| Cut | Family | Before | After |
|---|---|---|---|
| Codepoints | Literata | 187 KB | 41 KB |
| Axis clamp | Source Sans 3 | 35.6 KB | 28.5 KB |
| Both | Inter, 3 subsets | 46.1 KB | 38.5 KB |
| Both | IBM Plex Mono, 6 subsets | 71.2 KB | 65.6 KB |
The part that surprises people
Declaring a face is not downloading it. Split a family into subsets and give each a unicode-range, and the browser fetches only the ranges a page's text actually lands in. An English post never pulls the Vietnamese file even though the CSS names it, and a post with no code never pulls the mono face at all.
Which means the honest number for "how much font does a reader download" is not the size of the family. It is the size of the ranges that page happens to touch — and that is a number you can only get by measuring a real page rather than a directory listing.
@font-face {
font-family: 'Literata';
src: url('/fonts/literata-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
font-display: swap;
}
Tip
Re-subset from the foundry's original every time, never from the file you shipped last. Feeding a subsetter its own output re-rolls the compression, so every run produces a diff of about thirty bytes about nothing.