· explainer
JavaScript font engines compared

A web page can load a font and typeset with it, but it cannot ask the browser what is inside the file: which characters it covers, which OpenType features it carries, what its axes are. For that, JavaScript had to learn font engineering itself, and the libraries it produced split the work into three jobs: parsing the file, shaping text into positioned glyphs, and drawing the result. A fourth job, inspection, refuses to do the other three on principle. Which library you want depends entirely on which jobs you need, so here they are compared job by job, then matched to practical work.

The comparison
| opentype.js | fontkit | Typr.js | LibFont | HarfBuzz (Wasm) | |
|---|---|---|---|---|---|
| Parses | TTF, OTF (glyf and CFF), WOFF. WOFF2 only after separate decompression | TTF, OTF, WOFF, WOFF2, TTC, even dfont | TTF, OTF, TTC, WOFF | TTF, OTF; WOFF and WOFF2 with optional decoder helpers | Whatever you hand it as a blob; it reads tables for shaping, not general inspection |
| Shaping | Basic: kerning, ligatures, some Arabic support | Its own GSUB/GPOS layout engine, plus AAT morx | Basic built-in shaping; delegates hard scripts to HarfBuzz Wasm | None, deliberately | The reference implementation itself, all scripts |
| Color fonts | SVG and COLR/CPAL glyphs | SBIX and COLR layers | SVG color fonts | Reads the tables; draws nothing | Shapes color fonts; painting is your problem |
| Variable fonts | Not a focus; treat as static | Yes, OpenType and AAT variations | Yes | Exposes axes and variation data for inspection | Yes, as part of shaping |
| Draws pixels? | No: emits bézier paths for Canvas, SVG, or Node | No: paths to a graphics context or SVG | No: paths to Canvas or SVG | No, on principle | No: glyph paths out; you paint them |
| Extras | Executes TrueType hinting; tiny footprint (~120 KB) | Font subsetting; powers PDFKit | Claims 2 to 5 times faster parsing than opentype.js at a quarter of the size | Deep table queries: scripts, language systems, features per language | Official HarfBuzz project build (stripped “tiny” configuration) |
| License | MIT | MIT | MIT | MIT | HarfBuzz’s own (MIT-style) |
Two patterns stand out. First, nobody rasterizes. Every library stops at vector paths and hands them to Canvas 2D or SVG, which means the browser’s own native engine does the final painting after all; the JavaScript engine’s job is everything the browser will not tell you or let you control. Second, shaping is the dividing line: three of the five either skip it, simplify it, or borrow it, because real shaping (Arabic joining, Indic reordering, Khmer) is the hardest code in typography, and only one entry in the table simply is the reference implementation, compiled from C to WebAssembly.
The five, briefly
opentype.js is the default choice and the one most tools quietly embed. It parses the common formats, walks glyphs, applies kerning and ligatures, executes TrueType hinting instructions (a party trick none of its rivals bother with), and returns paths you can draw anywhere. Its limits are the honest kind: no WOFF2 without pre-decompression (its README explains that Brotli would multiply the library’s size tenfold, and suggests the Wasm wawoff2 converter), and shaping that covers Latin typography and basic Arabic rather than the world’s scripts.
Or watch it work. The panel below loads the actual opentype.js library and a real font file, parses the font in your browser, and draws the word from the path data the library returns. Nothing is pre-rendered: switch to “Points & handles” and you are looking at the raw material of a glyph, the on-curve points and off-curve control points the file stores and the library decodes.
Loading opentype.js and a real font file…
fontkit is the heavyweight: the broadest format support (it is the only one here that opens a .dfont), its own GSUB/GPOS layout engine plus Apple’s AAT morx substitutions, color glyphs from both the SBIX and COLR tables, OpenType and AAT variations, and subsetting. It is best known as the font engine inside PDFKit, which tells you its center of gravity: documents. Its subsets are PDF-grade (minimal fonts meant for embedding, not standalone files), a limitation its README states plainly.
Typr.js is the engine of the Photopea image editor, built for speed and small size: around 110 KB unminified, with the project claiming two to five times faster parsing than opentype.js across a 3,000-font test corpus. Its built-in shaping is explicitly basic, and its answer for hard scripts is the honest one: load HarfBuzz as a Wasm module alongside (Typr.U.shapeHB), and let each tool do its job. Parsing in JavaScript, shaping in compiled HarfBuzz, drawing in Canvas: that three-layer sandwich is what a production browser editor actually looks like.
LibFont (Pomax’s library, formerly Font.js) is the contrarian: a pure inspection engine. It parses tables deeply (scripts, language systems, the exact features each language uses, variation axes, character support checks) and refuses, in its author’s words, to draw anything or pretend to shape, because browsers already shape better and shaping is too complex to fake responsibly. It is the library you build font tools on top of, rather than renderers.
harfbuzzjs is HarfBuzz itself, the shaping engine from our rendering engines survey, compiled to WebAssembly by the HarfBuzz project. It shapes anything, returns glyph IDs, positions, and SVG paths, and draws nothing. One caveat from its README: it is built in HarfBuzz’s stripped “tiny” configuration, so a few APIs (glyph names, for one) are absent unless you rebuild it. The same Wasm technology, pointed the other way, is what lets a font carry a program into the shaper, the trick behind llama.ttf in our bizarre font uses post. And when the paths these libraries produce need to reach a GPU scene rather than a page, the story continues in Text in WebGL and WebGPU, where there is no text API at all and every letter arrives by texture, distance field, or shader.
Inspection is its own discipline
Two tools show what the inspection job becomes when it grows a user interface. Wakamai Fondue, by Roel Nieskens, answers one question for any font you drop on it: “what can my font do?” Features, variations, language support, character coverage, laid out for humans instead of table dumps. Samsa, by Stephen Nixon, is a variable-font designspace explorer: load a variable font, drag axis sliders, watch outlines interpolate live, inspect the variation data, and export static instances (its README warns the exports are demonstrations, not production fonts). Neither shapes a paragraph or ships a product; both exist because the inside of a font file is otherwise invisible to the people who buy and build them.
What you would actually use them for
Checking fonts before they reach your server. An upload form can parse a file in the visitor’s browser and answer moderation questions instantly: is this really a font, which characters does it cover, what do its name records claim, is the license field sane. For a site that ingests fonts at scale, that is a first line of defense that costs no server time, and inspection libraries (LibFont, or opentype.js’s parser) are exactly the right weight for it.
Specimens and previews without a rendering service. If a page needs a glyph rendered as a path (for an SVG specimen, a logo tool, a cutting or engraving preview), opentype.js or Typr.js plus Canvas is the whole stack, no server round trip and no native code.
Documents. Generating PDFs in Node or the browser with embedded, subsetted fonts is fontkit’s home turf, proven by PDFKit.
Editors and design tools. Photopea is the existence proof: a full image editor doing its own text with Typr.js for parsing and paths, HarfBuzz Wasm for serious shaping.
Learning and QA. Dropping a font into Wakamai Fondue or Samsa before you buy, ship, or debug it is faster than reading a spec, and font developers use the same class of tool to check their own exports.
The choice, reduced: parse and draw simply, take opentype.js; parse everything and lay out documents, take fontkit; go fast inside an editor, take Typr.js and bring HarfBuzz Wasm for the hard scripts; ask questions only, take LibFont; and when only the real shaping will do, skip the imitations and load HarfBuzz itself.