toolgarden.xyz
中文
browser tool developmentfont subsettingfonteditor-coreweb fontsfrontend engineering

How to Build an Online Font Subsetter with fonteditor-core

A practical browser-only implementation for extracting used characters from TTF or WOFF files, previewing the result, and exporting a smaller web font.

ToolGarden tools prioritize browser-local processing, so files and text do not need to be uploaded to a server.

Published July 22, 202611 min readBy ToolGarden

Chinese, Japanese, and Korean font files can contain thousands of glyphs, while a landing page may use only a headline and a few labels. Font subsetting removes unused glyphs so the browser downloads only the characters the product actually needs.

A useful online subsetter is more than a file input around a font library. It needs Unicode-safe character collection, format validation, WOFF decompression and compression, missing-glyph reporting, preview isolation, object-URL cleanup, and clear limits. The following architecture is based on a production browser-local implementation using fonteditor-core and fflate.

What font subsetting changes

A font is a collection of glyph outlines plus mapping, metrics, naming, hinting, kerning, and other tables. Subsetting keeps the glyphs reachable from a requested set of Unicode code points and rewrites the related tables. This differs from generic compression: ZIP or Brotli can shrink bytes in transit, but they do not remove thousands of unused glyphs from the decoded font.

The biggest wins usually come from large CJK fonts, icon fonts, campaign fonts, and one-off branded headings. A Latin font that is already small may gain less, especially when kerning and hinting tables are retained.

InputSubset sourceTypical output
Marketing headline fontFinal headline and button textSmall WOFF for one page
CJK UI fontProduct copy and supported punctuationLanguage-specific WOFF files
Icon fontIcons used by the current bundleReduced icon font
Document fontCharacters detected in one documentPortable TTF subset

Use a browser-local processing pipeline

The page should own file selection, options, progress, preview, and errors. A utility module should accept a File and explicit options, then return a discriminated outcome. Keeping font parsing out of the React component makes failure cases testable and prevents UI state from leaking into the conversion layer.

For ordinary subsetting there is no reason to upload the font or the requested text. File.arrayBuffer reads the selected file into the current browser tab, fonteditor-core parses and rewrites it, and the result becomes a Blob for preview and download.

  • Validate byte size and format before reading the entire file.
  • Accept both trustworthy MIME values and file extensions because browsers often leave font MIME types empty.
  • Return typed error codes such as unsupported_input, empty_chars, parse_failed, and no_glyphs.
  • Keep a maximum input size so one accidental font collection cannot exhaust the tab.

Collect Unicode code points, not UTF-16 units

JavaScript string indexing works in UTF-16 code units. Some characters outside the Basic Multilingual Plane use a surrogate pair, so splitting with text.split('') can turn one character into two invalid values. Iterate with for...of or Array.from, read codePointAt(0), and deduplicate the numeric code points with a Set.

Ignore C0 and C1 control characters, but do not discard whitespace blindly: a normal space is a real glyph and should often be kept. Also report characters that the source font does not contain instead of silently suggesting complete coverage.

Create and write the subset with fonteditor-core

fonteditor-core can parse TTF and WOFF inputs, select glyphs by Unicode code point, and write TTF or WOFF output. WOFF uses compressed table data, so fflate adapters provide the inflate and deflate callbacks. The same options should be applied consistently at parse and write time.

import { createFont } from 'fonteditor-core';
import { deflateSync, inflateSync } from 'fflate';

const codePoints = [...new Set(
  Array.from(text, char => char.codePointAt(0)!)
)];

const input = await file.arrayBuffer();
const font = createFont(input, {
  type: 'ttf',
  subset: codePoints,
  hinting: false,
  kerning: false,
  inflate: data => Array.from(inflateSync(new Uint8Array(data))),
});

const output = font.write({
  type: 'woff',
  deflate: data => Array.from(deflateSync(new Uint8Array(data))),
});
  • Disable hinting for smaller web assets unless the target rendering environment needs it.
  • Keep kerning when typographic quality matters, but measure the size difference for the actual font.
  • Name output files predictably, for example brand.subset.woff.
  • Treat WOFF2 as a separate capability; fonteditor-core in this pipeline writes WOFF, not WOFF2.

Verify coverage and show useful metrics

A successful write does not prove that every requested character exists. Inspect the Unicode mappings on the resulting glyphs, compare them with the requested code points, and return included and missing character lists. This turns a vague font preview into an actionable validation result.

Show the original size, output size, saved bytes, saved percentage, requested count, included count, and glyph count. A negative saving is possible for an already tiny font or an output format with different overhead, so the UI should display the actual value rather than promising every subset is smaller.

Preview with a temporary font and clean it up

The output can be previewed without sending it anywhere. Wrap the ArrayBuffer in a Blob, create an object URL, and load it through FontFace or a generated @font-face rule with a unique family name. Render only known-included characters so browser fallback fonts do not hide missing glyphs.

const blob = new Blob([output], { type: 'font/woff' });
const url = URL.createObjectURL(blob);

const face = new FontFace('SubsetPreview', `url(${url})`);
await face.load();
document.fonts.add(face);

// Revoke the previous URL when regenerating or leaving the page.
URL.revokeObjectURL(url);
  • Revoke the previous object URL before replacing the result.
  • Remove the loaded FontFace when the preview is no longer needed.
  • Use a unique family name per result to avoid cached preview confusion.
  • Download from the Blob URL and remove the temporary anchor immediately afterward.

Production limits and font edge cases

Variable fonts, color emoji fonts, OpenType layout rules, ligatures, and complex scripts need more care than a simple Unicode list. A text sample may require shaping-related glyphs that are not directly mapped from individual characters. Test the actual languages, browsers, and font licenses before shipping generated assets.

For a static website, build-time subsetting with a mature font tool may be more reproducible. The online approach is valuable for exploration, one-off assets, internal workflows, and privacy-sensitive files; it should not replace typography QA.

Key takeaways

A dependable online font subsetter combines Unicode-correct input handling, typed validation, real glyph coverage checks, explicit table options, and careful Blob lifecycle management. Keep the parser in a utility layer, make missing characters visible, and test complex fonts rather than assuming every OpenType file behaves like a basic Latin TTF.

Frequently asked questions

Q.Is font subsetting the same as font compression?

No. Subsetting removes unused glyphs and rewrites font tables. Compression encodes the remaining bytes more efficiently. A WOFF subset benefits from both: fewer glyphs first, compressed table data second.

Q.Why does the generated font still contain more glyphs than requested characters?

Fonts normally retain required glyphs such as .notdef, and the parser may keep glyphs needed by mappings or internal relationships. The glyph count therefore does not need to equal the unique character count exactly.

Q.Can this browser approach output WOFF2?

Not with the fonteditor-core plus fflate path shown here. It produces TTF or WOFF. WOFF2 needs a separate encoder, often WebAssembly or a build-time tool such as fonttools, and should be exposed only after browser and license testing.

Q.Does subsetting a font change its license?

No. Technical processing does not grant redistribution rights. Some font licenses allow web embedding or modification and others restrict it. Check the source font license before hosting, modifying, or redistributing a subset.

Q.Why are some emoji or complex-script results incomplete?

Some fonts use color tables, variation tables, ligatures, or shaping rules that go beyond a one-code-point-to-one-glyph mapping. Validate real words and sentences in the target script, and use a shaping-aware production pipeline when those features are required.