How to filter any font to basic latin + latin-1 online
- Step 1Pick the font weight you actually serve — Subset the specific TTF/OTF/WOFF/WOFF2 you reference in `@font-face`, not the whole download. Each weight and style (Regular, Bold, Italic) is a separate file and needs its own pass — there is no batch upload in the browser tool. To script all weights at once, see [the multi-weight batch guide](/font-tools/guides/latin-filter-batch-script).
- Step 2Drop the file onto the Latin Filter above — Upload TTF, OTF, WOFF, or WOFF2. WOFF2 is decompressed in-browser (wawoff2) and WOFF is unwrapped to an sfnt before subsetting. There is no options panel — the charset is fixed at `U+0020–U+007E` + `U+00A0–U+00FF`, so there is nothing to configure.
- Step 3Run the filter and read the metrics — Processing happens locally. The result panel reports `Glyphs in source`, `Glyphs kept` (≤192 including `.notdef`), source KB, and subset KB, plus the note `Kerning + OT layout features dropped; outlines preserved`. That note is the headline caveat — see step 6.
- Step 4Download <name>.latin.ttf — Output is **always TTF**, regardless of input format. The filename is `<stem>.latin.ttf`. This is the unhinted, layout-free outline font — small, but not yet web-optimised.
- Step 5Re-compress the TTF to WOFF2 for the web — The TTF is the wrong wire format for production. Run the subset through [ttf-to-woff2](/font-tools/ttf-to-woff2) to get the ~30% smaller compressed file you actually link in `@font-face`. For an old-browser WOFF fallback, also run [ttf-to-woff](/font-tools/ttf-to-woff).
- Step 6Verify kerning loss matters for your use case — Because `GPOS`/`GSUB` are dropped, kerning pairs (`AV`, `To`, `Wa`) and ligatures (`fi`, `fl`) no longer render. For display headlines this is visible; for body text at 16px it is usually invisible. If you need kerning preserved, the browser tool can't do it — use a harfbuzz-based subsetter (pyftsubset / hb-subset) as described in [the batch guide](/font-tools/guides/latin-filter-batch-script), or audit the loss with [kerning-pair-auditor](/font-tools/kerning-pair-auditor).
Exactly what the Latin preset keeps
The hard-coded ranges from the tool's latin charset. Total kept is 191 codepoints plus the always-retained .notdef glyph. Note the gaps: the C0 controls (U+0000–U+001F), DEL (U+007F), and the C1 controls / NBSP-adjacent block (U+0080–U+009F) are NOT in the set.
| Range | Block | Count | Examples |
|---|---|---|---|
U+0020–U+007E | Basic Latin (printable) | 95 | space, A–Z, a–z, 0–9, ! " # $ % & ( ) + , - . / : ; < = > ? @ [ \ ] ^ _ { | } ~ |
U+00A0–U+00FF | Latin-1 Supplement | 96 | NBSP, ¡ ¢ £ © ® ° ± × ÷, À Á Â Ã Ä Å Æ Ç È É, ñ ö ü ÿ ß |
U+0000–U+001F | C0 controls | 0 (excluded) | Tab, LF, CR — control codes, never glyphs |
U+007F | DEL | 0 (excluded) | Control character, no glyph |
U+0080–U+009F | C1 controls | 0 (excluded) | Legacy control block — note NBSP is U+00A0, which IS kept |
Input → output behaviour matrix
How the tool treats each input format. Output is always TTF; layout tables never survive the opentype.js rebuild.
| Input | Decoded by | Output | Kerning / ligatures | Hinting |
|---|---|---|---|---|
| TTF (TrueType outlines) | Passed through as sfnt | .latin.ttf | Dropped (GPOS/GSUB not rebuilt) | Dropped (no fpgm/prep/cvt rebuilt) |
| OTF (CFF / PostScript outlines) | Passed through as sfnt | .latin.ttf — may FAIL in writer | Dropped | n/a |
| WOFF | Unwrapped to sfnt (zlib per table) | .latin.ttf | Dropped | Dropped |
| WOFF2 | Decompressed via wawoff2 | .latin.ttf | Dropped | Dropped |
Where Latin Filter sits among the subsetting tools
Pick the right tool for the job — Latin Filter is the fixed-preset, zero-config option.
| Tool | Charset control | Best for |
|---|---|---|
| latin-filter | Fixed: U+0020–U+00FF (this tool) | English / Western European sites, one click |
| font-subsetter | Dropdown: latin, latin-ext, cyrillic, greek, vietnamese, symbols | Picking a named non-Latin or extended preset |
| character-whitelist-builder | Paste exact characters to keep | Logos, headlines, a known fixed string |
| character-coverage-map | Read-only: scores coverage vs 346 Unicode blocks | Checking what a font covers before subsetting |
Cookbook
Real before/after numbers and the exact production wiring. All processing is local; the only thing that reaches a server is an anonymous run counter for signed-in dashboard stats.
Full-Unicode Noto Sans down to Latin
ExampleNoto Sans ships thousands of glyphs to cover dozens of scripts. An English marketing site renders none of the non-Latin ones. Filter, then compress to WOFF2 for the real win.
Source: NotoSans-Regular.ttf ~580 KB (3,000+ glyphs)
Filter: Latin preset (U+0020-U+00FF)
Result: NotoSans-Regular.latin.ttf ~45 KB (≤192 glyphs)
Next step (web format):
ttf-to-woff2 -> NotoSans-Regular.latin.woff2 ~22 KB
@font-face {
font-family: "Noto Sans";
src: url(/fonts/NotoSans-Regular.latin.woff2) format("woff2");
font-weight: 400;
font-display: swap;
}Confirming the accented set survived
ExampleLatin-1 Supplement carries the Western European accents. After filtering, this string should render fully — every glyph below is inside U+00A0–U+00FF.
Test string (all in the kept range):
café naïve Zürich São Niño Ångström œuvre £5 ©2026
Kept codepoints:
é U+00E9 ï U+00EF ü U+00FC ã U+00E3
ñ U+00F1 Å U+00C5 œ U+0153 ? NO -> U+0153 is Latin
Extended-A, NOT kept
Gotcha: 'œ' (U+0153) and 'Œ' (U+0152) are Extended-A.
French needs them -> use font-subsetter 'latin-ext' instead.WOFF2 in, TTF out — the format flip
ExampleA common surprise: you upload a 22 KB WOFF2 and get a larger TTF back. That's correct — the tool decompresses WOFF2 to work on the outlines, and only ever writes TTF. Re-compress afterwards.
Upload: Inter.woff2 22 KB (already compressed) Download: Inter.latin.ttf 48 KB (uncompressed outlines) This is NOT a regression. TTF is uncompressed; WOFF2 is brotli. Finish the job: ttf-to-woff2 Inter.latin.ttf -> Inter.latin.woff2 ~16 KB (smaller than the original because the subset is smaller)
Reading the result metrics
ExampleThe result panel exposes the numbers you need to sanity-check the subset. 'Glyphs kept' includes .notdef, so an all-Latin font tops out near 192.
Charset: Latin
Glyphs in source: 3,142
Glyphs kept: 189
Source: 581.0 KB
Subset: 44.7 KB
Note: Kerning + OT layout features dropped;
outlines preserved
189 < 192 because this font lacks a few Latin-1 symbols
(e.g. no ð/þ). The filter keeps only glyphs the font has.Self-hosting Google Fonts, Latin-only
ExampleSelf-hosting for privacy or GDPR? Download the TTF from Google Fonts, Latin-filter it, compress, and serve from your own origin. No third-party font CDN sees your visitors.
1. Download Roboto-Regular.ttf from fonts.google.com
2. latin-filter -> Roboto-Regular.latin.ttf
3. ttf-to-woff2 -> Roboto-Regular.latin.woff2 (~12 KB)
4. Host on your origin; reference with unicode-range:
@font-face {
font-family: "Roboto";
src: url(/fonts/Roboto-Regular.latin.woff2) format("woff2");
unicode-range: U+0000-00FF; /* let the browser skip it
for non-Latin pages */
}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.
You uploaded a WOFF2 and got a TTF back
By designThe tool always writes TTF (font/ttf), no matter the input format. It decompresses WOFF/WOFF2 to operate on the raw outlines, then re-serialises as TTF. The output TTF is uncompressed, so it can look larger than the WOFF2 you started with — that's expected. Run ttf-to-woff2 afterwards to get back to a compressed web format; the result will be smaller than the original because the glyph set shrank.
Kerning and ligatures stopped working after filtering
By designThe in-browser engine (opentype.js) rebuilds the font from surviving glyph outlines only — it does not copy the GPOS (kerning) or GSUB (ligatures, stylistic sets) tables. So AV, To, fi, fl lose their special positioning/substitution. The result panel says so: 'Kerning + OT layout features dropped; outlines preserved.' For body text this is rarely noticeable; for display type it can be. If you must keep kerning, use a harfbuzz-based subsetter (see the batch guide).
French œ / Œ went missing
By designœ (U+0153) and Œ (U+0152) live in Latin Extended-A, which is OUTSIDE the Latin preset's U+0020–U+00FF range. So do the Polish, Czech, Turkish, and Romanian letters (ł, č, ş, ğ, ț). The Latin Filter covers 'most' Western European text but not all of French/Iberian and none of Central/Eastern European. Use font-subsetter with the latin-ext preset instead — the Latin-1 vs Extended comparison maps it by language.
Subset would be empty — no Latin glyphs matched
Error — rejectedIf the font has zero glyphs in U+0020–U+00FF (e.g. a CJK-only or icon-only font), the tool throws: 'Subset would be empty — no glyphs in this font match the requested charset (Latin). Run the Character Coverage Map tool first to confirm coverage.' This is a real guard, not a crash. Run character-coverage-map to see what the font actually covers before subsetting.
OTF (CFF outlines) failed in the writer
Error — writer limitationFonts with CFF/PostScript outlines (most .otf files) can fail when opentype.js serialises the new font: 'Subsetting failed in opentype.js's font writer ... This typically affects fonts with CFF (PostScript) outlines or non-standard tables.' The fix is to convert the OTF to TrueType-outline TTF in a desktop tool (FontForge) first, then re-run the filter, or use character-whitelist-builder with a smaller set.
Output is bigger than expected for a tiny charset
Expected191 codepoints sounds tiny, but the surviving glyph outlines still carry their full point data, and the TTF is uncompressed with table padding. A subset can be 30–60 KB as TTF and still drop to 10–20 KB as WOFF2. Judge the win on the compressed WOFF2 size, not the intermediate TTF. The compressionPercent metric compares the TTF to the original file size, so a WOFF2-input job may even show a negative-looking reduction at the TTF stage.
File over 5 MB on the free tier
Error — tier limitThe free tier caps font uploads at 5 MB per file. A large CJK or variable font can exceed that before subsetting. The error reads 'exceeds the free tier per-job limit (5 MB).' Pro raises it to 50 MB and Developer to 1 GB. Alternatively, convert the variable font to a single instance first with variable-font-freezer, or pre-shrink with a desktop tool.
.notdef is always in the output
PreservedGlyph 0 (.notdef, the missing-glyph box) is always retained — the OpenType spec requires it. So 'Glyphs kept' is always at least 1 and an all-Latin font tops out at 192 (191 + .notdef). Characters the font lacks (some fonts omit ð, þ, or ¤) simply aren't kept; the count comes in under 192 and those characters fall through to the CSS fallback chain at render time.
Hinting is gone too
By designBecause the font is rebuilt from outlines, the TrueType hinting tables (fpgm, prep, cvt) are not copied either. On modern macOS, iOS, and Windows 10+/DirectWrite this is invisible (they use outline-only grayscale rendering). On legacy Windows 7/8 GDI + ClearType, small text may render slightly differently. This matches what hinting-stripper does deliberately, so there's no separate hinting step to run.
Variable font lost its weight axis
By designopentype.js subsetting flattens to a static font built from unitsPerEm, ascender, descender, and the kept glyphs — the fvar/gvar variation tables are not carried over, so a variable font collapses to its default instance. To keep a specific weight, freeze it first with variable-font-freezer, then Latin-filter the frozen static font.
Frequently asked questions
Exactly which characters does the Latin Filter keep?
Two fixed ranges: Basic Latin printable U+0020–U+007E (space through ~ — 95 characters: all ASCII letters, digits, and punctuation) and Latin-1 Supplement U+00A0–U+00FF (96 characters: NBSP, common symbols like £ © ° ±, and the Western European accented letters À É Ñ Ü ß æ). That's 191 codepoints, plus the always-retained .notdef glyph. Control codes (U+0000–U+001F, U+007F, U+0080–U+009F) are not glyphs and are excluded.
Which languages does this fully cover?
English, German, Spanish, Italian, Portuguese, Dutch, Danish, Norwegian, Swedish, and Icelandic render cleanly. French is mostly covered but œ/Œ (U+0152/0153) are in Latin Extended-A and missing. Polish, Czech, Turkish, Romanian, Croatian, and Vietnamese need more than Latin-1 — use font-subsetter with latin-ext. The Latin-1 vs Extended guide has a per-language table.
Are accented characters like é, ñ, ü preserved?
Yes — they're in Latin-1 Supplement (U+00A0–U+00FF), which the filter keeps. À, Á, Â, Ã, Ä, Å, Æ, Ç, È, É, ñ, ö, ü, ÿ, and ß all survive, provided the source font actually contains them. If a font omits a given glyph, the filter simply doesn't add it and that character falls back to the next font in your CSS stack.
Why is the output a TTF when I uploaded a WOFF2?
The tool always outputs TTF (font/ttf). It decompresses WOFF2/WOFF to work on the raw outlines, then writes a fresh TTF. The intermediate TTF is uncompressed, so it can be larger on disk than your WOFF2 input — that's normal. Run it through ttf-to-woff2 to get a compressed web font; because the glyph set is smaller, the final WOFF2 will be smaller than your original.
Does it keep kerning and ligatures?
No. The in-browser opentype.js engine rebuilds the font from surviving glyph outlines and does not copy the GPOS (kerning) or GSUB (ligature/substitution) tables — the result panel states 'Kerning + OT layout features dropped; outlines preserved.' For body text this is usually invisible; for display headlines it can matter. If you need layout tables preserved, use a harfbuzz-based subsetter (pyftsubset / hb-subset) — see the batch-script guide.
How much smaller will my font be?
Depends entirely on how many glyphs you're dropping. A full-Unicode font (Noto, Source Han) can fall by 90%+; an already-Latin font (a typical Google Font 'latin' download) may barely change because it's already close to this set. Judge the win on the compressed WOFF2 size after ttf-to-woff2, not the intermediate uncompressed TTF.
How is this different from the Font Subsetter?
Latin Filter is a fixed preset with zero configuration — upload and download. font-subsetter is the same engine with a charset dropdown (latin, latin-ext, cyrillic, greek, vietnamese, symbols). If you want exactly U+0020–U+00FF, both tools produce identical output; Latin Filter just removes the choice so there's nothing to misconfigure.
Can I add a few extra characters to the set?
Not in this tool — the range is hard-coded. To keep a custom set (say, Latin-1 plus a trademark glyph and a few Extended-A letters), use character-whitelist-builder and paste the exact characters, or font-subsetter for a different named preset.
What happens to characters the font doesn't have?
The filter only keeps glyphs that exist in the source for codepoints in the range — it never invents glyphs. So 'Glyphs kept' often comes in below 192. Any in-range character the font lacks simply isn't in the output; at render time the browser falls back to the next font in your font-family stack for that character.
Is my font uploaded to a server?
No. Subsetting runs entirely in your browser via opentype.js (with wawoff2 for WOFF2 input). The font binary never leaves your machine. The only server call is an anonymous run counter for signed-in dashboard stats, which you can opt out of. This matters for licensed or unreleased brand fonts.
What input formats are accepted?
TTF, OTF, WOFF, and WOFF2 (checked by extension and by magic bytes). WOFF2 is decompressed in-browser; WOFF is unwrapped to an sfnt; TTF/OTF pass straight through. Note OTF (CFF/PostScript outlines) can occasionally fail in the opentype.js writer — if so, convert to a TrueType-outline TTF first.
Can I automate this across a whole font family?
The browser tool processes one file at a time. For all weights in CI, see the multi-weight batch guide — it shows a Node + opentype.js script for browser-parity output, or a pyftsubset command if you also need kerning preserved. Programmatically, GET /api/v1/tools/latin-filter returns the (option-free) schema and the @jadapps/runner executes the job locally.
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.