Real-World Subsetting Benchmarks
The table below shows WOFF2 file sizes for five popular web fonts — both the full distribution file and a Latin-1 subset (U+0000-00FF, covering 256 characters for Western European languages). All measurements were taken using pyftsubset from the Google Fonts repository, then compressed to WOFF2 with Brotli level 11.
| Font Name | Full Size (WOFF2) | Latin Subset | Reduction |
|---|---|---|---|
| Inter Regular | 97 KB | 35 KB | 64% |
| Roboto Regular | 82 KB | 30 KB | 63% |
| Open Sans Regular | 91 KB | 33 KB | 64% |
| Noto Sans | 197 KB | 71 KB | 64% |
| Source Sans Pro | 78 KB | 29 KB | 63% |
Methodology
Subsets generated using pyftsubset font.ttf --unicodes="U+0000-00FF" --layout-features="*" --flavor=woff2. Source files pulled from Google Fonts GitHub repository, commit February 2026. Full font sizes represent the WOFF2 files as distributed by Google Fonts (already Brotli-compressed). Measurements may vary slightly between font versions.
The consistency is striking: across all five fonts, Latin-1 subsetting produces a 63-64% file size reduction. This is not a coincidence — these fonts all contain roughly similar glyph counts for extended scripts, and Latin-1 covers just 256 characters out of the 500-900 glyphs a typical Latin font includes. The remaining glyphs — Greek, Cyrillic, extended Latin diacritics, currency symbols, box-drawing characters — are present in the full file but unused for English content.
For a site loading two font weights (regular and bold), the savings compound. Two subsetted Inter files (35 KB + 32 KB = 67 KB) replace two full Inter files (97 KB + 93 KB = 190 KB) — a saving of 123 KB per page load, before HTTP compression. On a slow 3G connection (1.6 Mbps), that is approximately 600ms of load time reclaimed.
How Font Subsetting Works
A font file is not simply a collection of images. It contains glyph outlines (the vector shapes for each character), hinting instructions (pixel-level rendering adjustments), kerning pairs (spacing adjustments between specific character combinations), and OpenType feature tables (ligatures, contextual alternates, small caps, and more). Every glyph present in the file contributes not only its own outline data, but also its associated kerning entries and feature lookups.
Subsetting removes glyphs you do not need and rebuilds these tables for only the remaining glyphs. The result is a structurally valid font file that contains only the characters your content requires. When done correctly using a standards-compliant tool, the subsetted font is indistinguishable in rendering from the original for the characters it retains.
Subsetting Tools
pyftsubset (Python fonttools)
The reference implementation. Command-line, highly configurable, supports all font formats and OpenType feature preservation.
glyphhanger
Crawls your site to detect which characters are actually used, then subsets automatically. Ideal for build pipelines.
Browser-based, no installation required. Upload a font, specify a unicode range or character set, download a subsetted WOFF2.
The subsetting process follows four steps:
- 1
Identify needed characters
Audit your content for all Unicode codepoints in use. For English sites, U+0000-007F covers 99%+ of characters. Add U+0000-00FF for Western European languages.
- 2
Remove everything else
The subsetting tool strips glyph outlines, kerning pairs, and feature lookups for all excluded codepoints.
- 3
Regenerate font tables
The tool rebuilds all internal font tables (cmap, GSUB, GPOS, etc.) to reference only the remaining glyphs.
- 4
Compress to WOFF2
The subsetted font is compressed using Brotli, the algorithm underlying WOFF2. The smaller glyph count results in significantly better compression ratios.
Watch for ligatures
When subsetting, always include the --layout-features="*" flag (pyftsubset) to preserve OpenType features for retained glyphs. Without it, ligatures like “fi” and “fl” and kerning between retained characters may be stripped, causing subtle rendering regressions.
CJK Fonts: Where Subsetting Matters Most
For Latin fonts, subsetting produces a 60-75% reduction. For CJK (Chinese, Japanese, Korean) fonts, the gains are an order of magnitude more dramatic. A complete Chinese font must include over 20,000 glyphs to cover the full Unicode Han character set — resulting in files that can reach 8-20MB. Even compressed to WOFF2, Noto Sans CJK SC (Simplified Chinese) weighs in at approximately 8MB for the full character set.
| Font | Full Size | 3,000 Char Subset | Per-Page Average |
|---|---|---|---|
| Noto Sans CJK SC | ~8 MB | ~400 KB | 50–100 KB |
| Source Han Sans SC | ~7.5 MB | ~380 KB | 50–100 KB |
| Noto Serif CJK JP | ~10 MB | ~500 KB | 60–120 KB |
The “Per-Page Average” column reflects Google Fonts' approach: rather than serving a monolithic 8MB font file, Google Fonts splits CJK fonts into over 100 small subsets, each containing approximately 100-150 characters. Each subset is a separate WOFF2 file with its own unicode-range declaration. The browser downloads only the subsets whose character ranges match characters present on the current page.
A typical Chinese blog post uses 500-800 unique characters. With 100-character slices, that means downloading 5-8 subset files averaging ~10-15 KB each — a total of 50-100 KB rather than 8 MB. This is a 98-99% reduction in bytes transferred, with no visible difference to the user.
Self-hosted CJK fonts require manual setup
Google Fonts handles CJK subsetting automatically when you embed via their CDN. If you self-host CJK fonts, you must either subset to your target character list or implement a unicode-range splitting strategy. Use our Unicode Range Generator to generate the CSS for multi-slice font loading.
Unicode-Range Implementation
The unicode-range CSS descriptor tells the browser which characters a font file covers. When a browser encounters @font-face with unicode-range, it inspects the characters on the page. If none of those characters fall within the declared range, the font file is never downloaded. This is the mechanism that makes multi-slice CJK font loading practical.
Below is a minimal implementation splitting a font into three subsets: Basic Latin, Latin Extended, and a small CJK slice. The browser will download only the files needed for the current page.
/* Subset 1: Basic Latin (English, punctuation, digits) */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-007F;
}
/* Subset 2: Latin Extended (Western European languages) */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont-latin-ext.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0080-024F, U+1E00-1EFF;
}
/* Subset 3: CJK Unified Ideographs (common 3,500 chars) */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont-cjk-common.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+4E00-9FFF;
}With this setup, a page containing only English text downloads only myfont-latin.woff2. A page with French text (which includes characters like é, ç, à) triggers both the Latin and Latin Extended downloads. A page with Chinese content triggers only the CJK subset. A mixed-language page downloads whichever subsets are needed.
Browser support is universal
unicode-range is supported by Chrome 36+, Firefox 36+, Safari 9+, and Edge 12+ — effectively all browsers in use. No polyfill or fallback is needed. Browsers that do not support unicode-range simply download all declared font files, which is the same behavior as before subsetting.
Use our Unicode Range Generator to produce the correct @font-face blocks for common character sets. The tool generates CSS for Latin, Latin Extended, Cyrillic, Greek, CJK, and custom ranges, ready to paste into your stylesheet.
What to Do Now
Subsetting is not a complex optimization that requires a build pipeline overhaul. For most sites, the workflow is: subset your font files once, update your @font-face CSS to add unicode-range declarations, and deploy. The two tools below handle both steps.
Font Subsetter
Upload any TTF, OTF, or WOFF2 file, specify a unicode range or paste a character list, and download an optimized WOFF2 subset. No installation required — runs entirely in the browser.
Open Font SubsetterUnicode Range Generator
Generate ready-to-paste @font-face CSS with correct unicode-range descriptors for Latin, Cyrillic, Greek, CJK, and custom character sets.
Quick decision guide
- →English-only site: Subset to U+0000-007F. Expect 65%+ reduction. One @font-face entry, no unicode-range needed.
- →Western European languages: Subset to U+0000-00FF. Add U+0100-024F for Polish, Czech, Romanian.
- →Multi-script site: Split into per-script subsets with unicode-range. Browsers download only what each page needs.
- →CJK content: Use Google Fonts CDN (automatic slicing) or implement 100+ slice strategy manually with unicode-range.
Font Subsetting FAQs
Common questions about font subsetting and file size reduction
Written & Verified by
Sarah Mitchell
Product Designer, Font Specialist
Subset Your Fonts Now
Cut your font payload by 60-75% in minutes. Upload a font, choose your character range, download your optimized WOFF2. Free, browser-based, no signup.
