Library / SDK
foliojs/fontkit avatar
foliojs/fontkit

fontkit does shaping, not just glyph lookup, and one API call separates them

An advanced font engine for Node and the browser

1,677 stars249 forksJavaScriptLicense varies

At a glance

What is it?
fontkit is a JavaScript font engine for Node and the browser that reads TrueType, OpenType, WOFF, WOFF2, collections and Datafork fonts, performs GSUB, GPOS and AAT morx shaping, extracts glyph paths and subsets fonts. Its package.json version is 2.0.4, while the listed release is v2.0.0 from 2022.
Who is it for?
Adopt fontkit if you need to lay out text, extract glyph outlines or subset fonts in JavaScript, especially on the browser side where a native shaping library is unavailable. Do not adopt it if you only need to measure a string, since that is a much smaller job, and do not rely on AAT variation support being modern, since the README lists morx and variation glyphs as AAT features.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

layout() versus glyphsForString() is the decision that matters

Fontkit exposes two ways to turn text into glyphs, and choosing wrongly produces wrong output rather than an error.

`glyphsForString(string)` returns an array of Glyph objects in a one to one mapping from characters. That is the naive approach. `font.layout(string, features)` is the real one: it returns a `GlyphRun` containing Glyph objects and GlyphPosition objects, where each position carries `xAdvance`, `yAdvance`, `xOffset` and `yOffset`. The README is direct about this, saying that for most uses you should use `font.layout`, which provides a much more advanced mapping supporting AAT and OpenType shaping.

The difference shows up in ordinary text. Kerning and ligatures are features of the layout engine, not of a character to glyph lookup, so a string typed through `glyphsForString` will place every letter at its own default advance and will never form a ligature. If you are rendering text, `layout` is the only call that matters.

The `features` parameter gives you control over the shaping set. It is either an array of OpenType feature tags applied in addition to the defaults, or an object mapping feature tags to a boolean to enable or disable each one. On an AAT font, the OpenType tags are mapped to AAT features, so the same call works against both font technologies.

For a single character there is a narrower path: `glyphForCodePoint(codePoint)` maps one unicode code point to a Glyph and performs no advanced substitutions, because there is no context to do so. `hasGlyphForCodePoint(codePoint)` answers whether the font has a glyph at all, which is the call to use when validating coverage before rendering.

One font object per format, all inheriting the same API

Fontkit handles six container formats: TrueType (.ttf), OpenType (.otf), WOFF, WOFF2, TrueType Collection (.ttc) and Datafork TrueType (.dfont).

The consequence is that the return type depends on the file. The README says several different types of font objects are returned depending on the format, that they all inherit from the `TTFFont` class, and that they share the same public API. So `font.layout` exists whichever format you opened, but the class you get is format specific.

Three entry points cover the ways you might hold a font. `fontkit.open(filename, postscriptName = null)` opens a file asynchronously and returns a Promise. `fontkit.openSync(filename, postscriptName = null)` does it synchronously. `fontkit.create(buffer, postscriptName = null)` takes an already loaded buffer, which is the browser case where the bytes came from a fetch.

The `postscriptName` parameter is the collection handling, and it is the detail to get right with .ttc files. Pass it and you get that specific font out of the collection; leave it out and you get a collection object, which does not have the `layout` method described above. Same for .dfont.

A synchronous API that parses a binary font file is a blocking call on the Node event loop. The README shows `openSync` as the simple example, and `open` as the Promise returning alternative, so anything in a server should prefer the async form unless the font is small and cached.

What the font object tells you before you render anything

The font object exposes three groups of properties, and the middle group is where most integration bugs live.

The metadata properties are strings, or null when the font does not contain them: `postscriptName`, `fullName`, `familyName`, `subfamilyName`, `copyright` and `version`. They are the values the font creator recorded, which means a font with empty metadata fields returns null rather than a sensible default.

The metrics describe the font's coordinate system and vertical rhythm: `unitsPerEm` as the size of the internal grid, `ascent`, `descent`, `lineGap`, `underlinePosition`, `underlineThickness`, `italicAngle`, `capHeight`, `xHeight` and `bbox`. The README links FreeType's glyph metrics documentation for how they relate, and that link is worth following, because `unitsPerEm` in particular is the scale factor between font units and your chosen point size. A renderer that ignores it gets correctly shaped glyphs at the wrong size.

The remaining properties are `numGlyphs` as the glyph count, `characterSet` as an array of every unicode code point the font supports, and `availableFeatures` as an array of the OpenType feature tags, or mapped AAT tags, that the font actually contains. `availableFeatures` is the cheap way to answer whether a font can do small caps or old style figures before you set a feature that will be silently ignored, since the feature table is per font and not every font implements the full tag set.

Per glyph advance is available through `widthOfGlyph(glyph_id)`, which returns the advance width for a single glyph id. Once you have a GlyphRun, positions come from the run's GlyphPosition objects instead.

Outlines, color tables and the glyph path you can hand to SVG

Beyond layout, fontkit is an outline engine, and that is where PDF and print work comes from.

Both outline formats are supported: TrueType `glyf` outlines and PostScript CFF outlines. A Glyph exposes a path, and the README's example converts it directly to an SVG path string:

javascript
var fontkit = require('fontkit');

var font = fontkit.openSync('font.ttf');
var run = font.layout('hello world!');
var svg = run.glyphs[0].path.toSVG();

The same path object can also be rendered to a graphics context, which is how you get raster output without going through SVG.

Color glyphs are supported through two vendor tables: Apple's SBIX table and Microsoft's COLR table. That matters for emoji, and it is the kind of feature where a font either has the table or silently renders a monochrome fallback.

Apple Advanced Typography is supported on two fronts, the `morx` table for glyph substitution and AAT variation glyphs, which the README describes as allowing nearly infinite design control over weight, width and other axes. The variation interface is `font.variationAxes`, returning an object keyed by four letter axis tags whose values include `name`, `min`, `default` and `max`, plus `font.namedVariations` for the named instances the designer published. Note what is named here: AAT variation fonts, not OpenType variable fonts, so check which technology your font uses before assuming the axes object will be populated.

The last capability is subsetting, which produces a new font containing only the glyphs you name. The README's example includes the glyphs from a run and encodes the result:

javascript
var subset = font.createSubset();
run.glyphs.forEach(function(glyph) {
  subset.includeGlyph(glyph);
});

let buffer = subset.encode();

For a PDF or web font pipeline, including only used glyphs can reduce the font data that must be shipped.

A 2.0.4 package against a 2.0.0 tag, on an ESM-first build

The version story needs reading carefully, because the two sources disagree in a way that matters if you pin.

The only GitHub release in the visible history is v2.0.0 from 2022-06-18. The repository is not archived and the last push was on 2026-10-13, so work continues, and `package.json` declares `version = "2.0.4"`. So there have been at least four unreleased-in-tag versions. If your dependency resolution accepts `^2.0.0`, npm will give you 2.0.4 from the registry; if you pin to the tag you get 2.0.0. Know which one you have.

The build has also changed. `package.json` sets `"type": "module"` and the build script is `rolldown -c`, with a `rolldown.config.ts` at the root. Tests run through mocha with c8 for coverage. The exports map is the part to check in your own project: Node resolves to `dist/fontkit.node.esm.js` for import and `dist/fontkit.node.cjs` for require, while every other condition resolves to `dist/fontkit.browser.esm.js`. So a bundler that does not set the `node` condition, or a script that imports the package without a bundler, can end up on the browser build. The README's own example uses CommonJS `require('fontkit')`, which the exports map still supports for Node.

The published files are `src` and `dist`, which means the source ships. Both node and browser builds are usable.

There is one more build wrinkle visible in the scripts. `prepublish` runs a clean, then three generation steps, `trie:data`, `trie:use` and `trie:indic`, before the build. Those scripts in `scripts/` generate Unicode trie data, which relates to the `unicode-trie`, `unicode-properties`, `dfa` and `tiny-inflate` dependencies used for text segmentation and script runs. Publishing regenerates that data, so a source install without a build step is not equivalent to an npm install.

The licence is MIT in `package.json`, while the repository's own licence field shows as unclassified; author is Devon Govett, whose PDFKit is the project fontkit is used by. Tests and formatting are mocha, c8 and prettier, and the repository carries an `AGENTS.md` plus an esdoc config.

Editorial conclusion

Adopt fontkit if you need to lay out text, extract glyph outlines or subset fonts in JavaScript, especially on the browser side where a native shaping library is unavailable. Do not adopt it if you only need to measure a string, since that is a much smaller job, and do not rely on AAT variation support being modern, since the README lists morx and variation glyphs as AAT features. Verify first which build your resolver picks, because the exports map sends Node to the node builds and everything else to the browser ESM build, and check that the version you install is what you expect, since the visible tag trail stops at v2.0.0.

Frequently asked questions

How do I install fontkit and get an SVG path for a glyph?

Run npm install fontkit, open a font with fontkit.openSync('font.ttf'), call font.layout('hello world!') to get a GlyphRun, then call run.glyphs[0].path.toSVG() on the glyph you want.

What is the difference between layout and glyphsForString in fontkit?

glyphsForString returns a one to one mapping from characters to glyphs with no substitutions, while layout returns a GlyphRun of glyphs and positions with kerning, ligatures and other OpenType or AAT shaping applied. The README recommends layout for most uses.

How do I open one font from a TrueType collection with fontkit?

Pass the postscriptName as the second argument to fontkit.open, fontkit.openSync or fontkit.create. Without it you get the collection object rather than a single font with a layout method.

Can fontkit create color emoji and subset fonts?

Yes. Color glyphs are supported through Apple's SBIX table and Microsoft's COLR table, and subsetting creates a new font containing only the glyphs you include through font.createSubset() and subset.encode().

Which font formats does fontkit support?

TrueType (.ttf), OpenType (.otf), WOFF, WOFF2, TrueType Collection (.ttc) and Datafork TrueType (.dfont). TrueType glyf and PostScript CFF outlines are both supported for glyph paths.

Official sources

  1. foliojs/fontkit on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/foliojs-fontkit.svg)](https://hysenlabs.com/projects/foliojs-fontkit)

Was this useful?