Skip to content

fontdone

Latest release: 2.14.3-alpha.11.

fontdone is the JavaScript npm package for the pure-Rust fontdone engine. It ships a prebuilt wasm32-unknown-unknown module and a zero-dependency ESM wrapper for opening font bytes and rasterizing individual glyphs.

Different alpha releases are not API- or ABI-compatible by promise, and this package is not a text-shaping or layout engine.

Install

npm install fontdone@2.14.3-alpha.11

Node.js

Save as render.mjs. Run node render.mjs /path/to/your-font.ttf with a font you are licensed to use. Requires Node.js 20 or newer.

import { readFile } from "node:fs/promises";
import createFontdone from "fontdone";

const fontBytes = await readFile(process.argv[2]);
const engine = await createFontdone();
try {
  const face = engine.openFace(fontBytes, { pixelSize: 32 });
  try {
    const bitmap = face.render("A");
    console.log(bitmap.width, bitmap.height, bitmap.pixels.length);
  } finally {
    face.close();
  }
} finally {
  engine.close();
}

Browser

Serve your licensed font at /fonts/example.ttf or change the URL to your own asset. The package loads its bundled WASM module automatically.

import createFontdone from "fontdone";

const [engine, fontBytes] = await Promise.all([
  createFontdone(),
  fetch("/fonts/example.ttf").then((response) => response.arrayBuffer()),
]);
const face = engine.openFace(fontBytes, { pixelSize: 32 });

try {
  const bitmap = face.render("A");
  console.log(bitmap.width, bitmap.height, bitmap.pitch, bitmap.pixels);
} finally {
  face.close();
  engine.close();
}

In browsers, the default initializer fetches fontdone.wasm relative to the ESM entry point. In Node.js, the package's conditional node export reads the same bundled asset from disk, so createFontdone() works without a fetch(file://…) call. Pass an explicit URL, Response, ArrayBuffer, typed-array view, WebAssembly.Module, or WebAssembly.Instance when your asset pipeline needs different loading behavior:

const engine = await createFontdone("/assets/fontdone.wasm");

The loader uses streaming instantiation when available and falls back to an ArrayBuffer when the server does not send application/wasm.

Runtime contract

The maintained package requires ESM, WebAssembly, and WebAssembly JavaScript BigInt integration. Browser initialization requires fetch; Node.js uses its conditional entrypoint and the local bundled asset. The wrapper performs no font or telemetry requests; the only implicit asset load is its own Wasm module.

Each initializer call owns a separate WebAssembly instance. Faces and memory offsets are instance-local and cannot be transferred to another instance or Worker. Create one engine per Worker and close every face deterministically.

render maps one Unicode scalar, loads the glyph, renders it in normal grayscale mode, and copies the result out of linear memory. Its result has:

  • glyphIndex: the mapped glyph index;
  • width and height: bitmap dimensions in pixels;
  • pitch: signed bytes per source row;
  • pixels: an owned Uint8Array of abs(pitch) * height bytes.

The package does not shape text, apply bidi ordering, choose fallback fonts, or retain the caller's font buffer. Use a shaping library before loading glyph indices when those behaviors are required.

API

  • createFontdone(source?) / init(source?): create an independent engine.
  • engine.openFace(bytes, { faceIndex?, pixelSize? }): copy bytes, open a face, and select an integer pixels-per-EM size (16 by default).
  • face.setPixelSize(size) or setPixelSizes(width, height): select integer pixels per EM.
  • face.getCharIndex(character): map one number or one Unicode scalar string.
  • face.loadGlyph(index, loadFlags?): load an explicit glyph.
  • face.renderGlyph(renderMode?): render and copy the current glyph bitmap.
  • face.render(character, options?): map, load, and render in one call.
  • face.close() / engine.close(): deterministic, idempotent wrapper cleanup.

Nonzero engine statuses throw FontdoneError, whose code is the preserved FreeType-compatible FT_Error value and whose operation names the failed wrapper call. Programmer errors such as an invalid size or use after close throw standard JavaScript errors.

TypeScript declarations ship with the package. The complete low-level export and record inventory is also distributed as abi.json; only the wrapper above is promoted as the JavaScript application API.

Security and license

Fonts are untrusted binary input and can demand substantial CPU or memory even inside WebAssembly. Apply application-level input-size and execution limits. Report vulnerabilities through the repository's private security-advisory route.

The package is distributed under the FreeType License (FTL). See FTL.TXT and NOTICE.md for terms and attribution.