font-flux-js
v1.3.0
Published
Convert fonts to JSON, make edits, then convert them back!
Maintainers
Readme
Font Flux JS
Convert fonts to JSON, make edits, then convert them back!
Font Flux JS is a JavaScript library for parsing OpenType/TrueType font binaries into structured JSON, then exporting that JSON back into a valid font binary. Every table is fully parsed into human-readable fields! If you're ambitious, you can also create a font JSON from scratch and turn it into a font.
Font Flux JS is part of the Glyphr Studio family. Any questions or feedback? We'd love to hear from you: [email protected]
Demo
Try out the demo app! You can load a font, edit it's metadata, subset glyphs, and even save as different font file formats.
Font Flux JS Demo App
Installation
npm (recommended)
npm install font-flux-jsStandalone
You can also use dist/font-flux-js.js directly as a single-file ES module — no bundler or npm required. Everything is self-contained except for WOFF2 support, which requires the brotli-wasm package in browser environments (Node.js uses its built-in zlib). If you don't need WOFF2, the single file works with no other dependencies.
Quick start
Browser
<script type="module">
import { importFont, exportFont } from 'font-flux-js';
const response = await fetch('./fonts/MyFont.ttf');
const buffer = await response.arrayBuffer();
const fontData = importFont(buffer);
// Modify anything — font metadata, glyphs, kerning, tables...
fontData.font.familyName = 'My Custom Font';
const outputBuffer = exportFont(fontData);
// Download the modified font
const blob = new Blob([outputBuffer], { type: 'font/ttf' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'MyFont-modified.ttf';
a.click();
</script>Node.js
import { readFile, writeFile } from 'node:fs/promises';
import { importFont, exportFont, validateJSON } from 'font-flux-js';
// Import
const buffer = (await readFile('MyFont.ttf')).buffer;
const fontData = importFont(buffer);
// Inspect the simplified structure
console.log(fontData.font.familyName); // "Helvetica"
console.log(fontData.glyphs.length); // 756
console.log(fontData.kerning?.length); // 1200
// Modify
fontData.font.familyName = 'My Custom Font';
fontData.font.version = 'Version 2.0';
// Validate before export
const report = validateJSON(fontData);
if (!report.valid) {
console.error(report.issues);
}
// Export
const output = exportFont(fontData);
await writeFile('MyFont-modified.ttf', Buffer.from(output));What importFont returns
importFont returns a simplified structure that consolidates data from many tables into a single coherent view:
{
font: { // Metadata from head, name, OS/2, hhea, post
familyName, styleName, unitsPerEm, ascender, descender, ...
},
glyphs: [ // Per-glyph data from glyf/CFF + hmtx + cmap + post
{ name, unicode, advanceWidth, contours, components, ... }
],
kerning: [ // Pair adjustments from kern / GPOS
{ left, right, value }
],
axes: [...], // Variable font axes (fvar)
instances: [...], // Named instances (fvar)
features: { GPOS, GSUB, GDEF }, // OpenType layout features
tables: { ... }, // ALL original parsed tables (for lossless round-trip)
_header: { ... }, // SFNT header
}The top-level fields (font, glyphs, kerning) are the human-friendly editing interface. The tables object preserves every parsed table for lossless binary round-trip.
API
| Function | Description |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| importFont(buffer) | Parse an ArrayBuffer into a simplified font object. Handles TTF, OTF, TTC, OTC, WOFF, and WOFF2. |
| exportFont(fontData, options?) | Convert a font object back to binary. Returns an ArrayBuffer. |
| initWoff2() | Initialize WOFF2 support (async). Must be awaited once before importing/exporting WOFF2 files. |
| validateJSON(fontData) | Check a font object for structural issues. Returns { valid, errors, warnings, infos, summary }. |
| fontToJSON(fontData, indent?) | Serialize a font object to a JSON string. Handles BigInt, TypedArrays, and strips transient fields. |
| fontFromJSON(jsonString) | Deserialize a JSON string back into a font object. |
| contoursToSVGPath(contours) | Convert font contours (TrueType or CFF) to an SVG path d string. |
| svgPathToContours(d) | Parse an SVG path d string into font contour data. |
| interpretCharString(bytes, ...) | Interpret CFF Type 2 charstring bytecode into cubic Bézier contours. |
| disassembleCharString(bytes, ...) | Disassemble CFF charstring bytecode into a human-readable instruction listing. |
| buildSimplified(raw) | Convert raw { header, tables } into the simplified structure above. |
| buildRawFromSimplified(simplified) | Convert a simplified object back to { header, tables }. |
| importFontTables(buffer) | Low-level import returning raw { header, tables } without simplification. |
Supported formats
- TTF (
.ttf) and OTF (.otf) — single fonts - TTC (
.ttc) and OTC (.otc) — font collections - WOFF (
.woff) — Web Open Font Format 1.0 (zlib compression) - WOFF2 (
.woff2) — Web Open Font Format 2.0 (Brotli compression)
WOFF2 initialization
WOFF2 support requires a one-time async initialization before use. WOFF1 and SFNT formats work without it.
import { initWoff2, importFont, exportFont } from 'font-flux-js';
await initWoff2(); // Call once at startup
// Now WOFF2 import and export work
const fontData = importFont(woff2Buffer);
const woff2Output = exportFont(fontData, { format: 'woff2' });Supported tables
Shared SFNT tables
BASE, CBDT, CBLC, COLR, CPAL, DSIG, EBDT, EBLC, EBSC, GDEF, GPOS, GSUB, HVAR, JSTF, LTSH, MATH, MERG, MVAR, OS/2, PCLT, STAT, SVG , VDMX, VVAR, avar, cmap, fvar, hdmx, head, hhea, hmtx, kern, maxp, meta, name, post, sbix, vhea, vmtx
OTF-specific tables
CFF , CFF2, VORG
TTF-specific tables
cvar, cvt , fpgm, gasp, glyf, gvar, loca, prep
Apple AAT tables
bloc, bdat, ltag
Tables not in this list (e.g. vendor-specific tables like
FFTM) are preserved as raw bytes for lossless round-trip.
