Generate a FONTSUBSET output from supported files directly in your browser—fast, simple, and no installation required.
.ttf) or OpenType (.otf) file from your machine. Parsing happens in your browser via opentype.js — the font never leaves the page..notdef) and shows the output size and percent saved next to the input size..ttf. If you need .woff2 for web delivery, run the result through a TTF→WOFF2 converter — WOFF2 compression adds another ~30–50% reduction on top of the glyph-count savings.Web fonts ship every glyph the typeface ever drew — kanji you never render, mathematical symbols you don't use, currency marks for languages you don't target. For a Latin-only English site loading a CJK-capable family, that's megabytes of dead weight on every cold visit. Subsetting strips the font down to the glyphs your page actually uses, then unicode-range in @font-face lets the browser skip the file entirely on pages that don't need it. Real wins look like:
0-9, A-Z, a-z, and core punctuation keeps the brand voice in HTML email without a 100 KB attachment.font-display: swap keeps the network cost under 30 KB for a typical Latin page.unicode-range TrickThe standard split Google Fonts uses for Latin scripts (paired with unicode-range so browsers only fetch the file when the page contains matching characters):
| Subset | Unicode range | Covers | Typical Latin WOFF2 size |
|---|---|---|---|
| Basic Latin | U+0020-007F |
ASCII letters, digits, common punctuation | 12–19 KB (median across Google Fonts) |
| Latin-1 Supplement | U+0080-00FF |
Western European: é, ñ, ü, ß, accented vowels | +5–10 KB |
| Latin Extended-A | U+0100-017F |
Central European: ą, č, ł, š, ž | +5–10 KB |
| Latin Extended-B | U+0180-024F |
African / Vietnamese fragments, IPA-adjacent | +5–10 KB |
| Latin Extended Additional | U+1E00-1EFF |
Full Vietnamese tone marks | +8–15 KB |
| Cyrillic | U+0400-04FF |
Russian, Ukrainian, Bulgarian | +10–15 KB |
| Greek | U+0370-03FF |
Modern Greek | +8–12 KB |
unicode-range is supported in Chrome 36+, Edge 17+, Safari 10+, Firefox 44+, and Opera 23+ — effectively every browser in active use today (~96% global support per caniuse). Older browsers gracefully fall back to downloading every declared @font-face source, so there's no breakage cost to adding the descriptor.
xconvert's subsetter is browser-side and intentionally minimal — for production web font pipelines you may want a tool that preserves more font internals.
| Tool | Runs | Preserves kerning & ligatures | Hinting | Variable-font axes | Notes |
|---|---|---|---|---|---|
| xconvert font subsetter (this page) | Browser (opentype.js) | No — rebuilds the font from outlines | Outlines only | No | Best for display fonts, logos, and quick glyph-only subsets where layout features aren't critical |
fontTools pyftsubset |
Python CLI | Yes by default (liga, kern, calt, mark) |
Retained by default; --no-hinting removes for ~30% smaller |
Yes — --instancer or --axis-pinning |
The reference implementation; what Google Fonts itself uses |
| glyphhanger | Node CLI | Yes (wraps pyftsubset) | Yes | Yes | Scrapes your HTML to auto-detect which glyphs you actually use |
| subset-font (npm) | Node | Yes (wraps HarfBuzz) | Yes | Yes | Designed for build pipelines (Vite, webpack, esbuild) |
| Everything Fonts / FontSquirrel webfont generator | Web UI | Yes | Configurable | Limited | Good if you need WOFF2 output without a local toolchain |
The practical takeaway: if your typeface relies on contextual ligatures, complex kerning, OpenType stylistic sets, or variable-font axis ranges, run pyftsubset or glyphhanger as part of your build. If you just need to ship a tiny brand-headline font or scrub unused glyphs from a static .ttf, this page is a one-click answer with no install.
Subsetting takes a font file and produces a smaller one that contains only a chosen list of glyphs (characters). Everything that wasn't requested — unused symbols, alternate scripts, decorative glyphs, sometimes even hinting and layout tables — is dropped. The output is still a valid font, just narrower in scope. It's the single biggest lever for web-font performance after switching to WOFF2.
It depends entirely on the source font's glyph count versus how many glyphs you keep. A Latin-only typeface starting at 50 KB might come down to 20–30 KB if you keep just ASCII. A CJK family like Noto Sans CJK JP (the Variable OTF is ~7.7 MB) can drop to under 1 MB if you keep only kana plus the JIS Level 1 kanji set, and under 100 KB if you keep only the Latin and basic punctuation block. Display fonts subsetted to a brand name often land at 5–15 KB.
In this tool, yes — the xconvert subsetter rebuilds the font from glyph outlines and does not copy the GSUB (substitutions: ligatures, alternates, contextual forms) or GPOS (positioning: kerning, mark attachment) tables into the output. That's fine for logos, monospaced/symbol fonts, and most numeric or all-caps display use. If you need fi/ffi ligatures, pair kerning, small caps, or contextual Arabic/Indic shaping in the subset, run pyftsubset instead — it preserves layout features by default and lets you specify exactly which OpenType features to keep.
Outline data from the original font is copied, but the dedicated TrueType hinting program tables (fpgm, prep, cvt) are not stitched into the rebuilt output. On modern macOS, iOS, and Android the system uses font smoothing instead of hint-based rasterization, so you won't notice. On Windows ClearType at small sizes, unhinted output can look slightly softer than the original at 9–11 px — for body text on Windows users, run pyftsubset with default hinting retained.
You can run a variable .ttf or .otf through this tool and get a smaller file back, but only by character set, not by axis range. The variation tables ride along, so the output remains variable but covers fewer glyphs. To "instance" a variable font to a single weight (e.g. lock Inter-Variable to weight 500 to halve its size), or to clip an axis to a subrange like wght=300:700, use fontTools.varLib.instancer — a sibling CLI to pyftsubset that handles variable-axis editing. A typical pipeline runs varLib.instancer first to pin/clip axes, then pyftsubset to drop unused glyphs.
Every legal OpenType / TrueType font must have a glyph at index 0 called .notdef — the box-with-an-X shown when a character has no glyph. The subsetter always keeps it, which is correct behavior. It's usually under 200 bytes and you'll never see it unless something tries to render a character that wasn't in your subset.
Subset first, then convert to WOFF2. The two compressions are additive: subsetting removes unused outlines, then WOFF2 (Brotli-based) compresses what's left at ~30–50% better than raw .ttf. A full Latin Inter font at ~310 KB TTF subsets to ~30 KB TTF for ASCII, then compresses to ~15 KB WOFF2. Don't try to subset a .woff2 — decompress to TTF first.
Reference it in CSS with @font-face and the matching unicode-range. For a Latin subset:
@font-face {
font-family: 'MyFont';
src: url('myfont-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
Browsers parse the unicode-range and only fetch the file when the page renders matching characters. Pair with a CSS minifier to keep the stylesheet itself lean.
No. The font file is read with FileReader, parsed and rebuilt with opentype.js in your browser tab, and the result is offered as a download via an in-memory Blob. The bytes never touch xconvert's servers. You can verify by opening DevTools → Network and watching no upload request fire when you click Subset.