npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@awacloud/fonts

v1.0.0

Published

Browser-safe TrueType / OpenType reader, writer and subsetter — OT v1.9 + TT RM05 hinting, hardened against malformed-font parser attacks. Zero runtime dependency beyond @awacloud/fw.

Readme

@awacloud/fonts

Pure JavaScript library to read, write and subset OpenType and TrueType fonts in the browser. No runtime dependency beyond @awacloud/fw: SFNT container and table directory, table parsing and serialization, quadratic TrueType outlines, composite glyphs, WOFF/WOFF2/TTC containers and a TrueType subsetter for PDF embedding.

The core covers the required OpenType tables, CFF/CFF2, the layout tables (parsed, not applied), variable-font and colour-font tables. An opt-in layer (extra/*) adds the RM05 TrueType hinting VM, Apple AAT parsers, Arabic / Indic / CJK shaping helpers, WOFF2 write and the DSIG / MATH / JSTF tables, grouped into the bundles fonts-large, fonts-full and fonts-apple-aat.

Known limits, so they are not a surprise:

  • GSUB and GPOS lookups are decoded, but there is no shaping engine that applies them.
  • Colour and bitmap glyph tables (COLR / CPAL, SVG , sbix, CBDT / CBLC, EBDT / EBLC / EBSC) are parsed, not rendered: the parsers return the decoded records (paint graphs, palettes, SVG documents, embedded bitmap bytes with their metrics); rasterising and compositing them is left to the caller.
  • WOFF2 decoding returns the table directory and the Brotli body; a transformed glyf / loca table is not reversed.
  • subsetForPdf subsets TrueType (glyf) outlines only; CFF fonts are rejected.
  • Apple AAT state-machine bodies (morx, non-format-0 kerx, prop) are returned as { parsed: false }; ankr and lcar expose raw lookup bytes.

Installation

npm install @awacloud/fonts

In the browser, via import map:

<script type="importmap">
{ "imports": {
    "@awacloud/fw":     "/node_modules/@awacloud/fw/src/main.js",
    "@awacloud/fw/":    "/node_modules/@awacloud/fw/src/",
    "@awacloud/fonts":  "/node_modules/@awacloud/fonts/src/main.js",
    "@awacloud/fonts/": "/node_modules/@awacloud/fonts/src/"
}}
</script>

Quick Start

Read a TTF / OTF

fonts is a plain fw factory descriptor ({ name, dependencies, factory }): its factory is resolved by an @awacloud/fw runtime, never called directly with no arguments.

import fw from '@awacloud/fw';
import { fw_require, modules } from '@awacloud/fonts';

for (const m of [...fw_require, ...modules]) fw.runtime.register(m);
const api  = fw.runtime.resolve('fonts');
const font = api.read(bytes);   // bytes: Uint8Array of a .ttf or .otf file

console.log(font.flavor);          // 'truetype' or 'opentype'
console.log(font.names.family);    // e.g. 'Liberation Sans'
console.log(font.unitsPerEm);      // e.g. 2048
console.log(font.numGlyphs);

const gid   = font.glyphIndexForCodePoint(0x41);  // 'A'
const glyph = font.getGlyphByIndex(gid);
console.log(glyph.advanceWidth);
console.log(glyph.path.toSvgPath());              // "M1167 0 L1006 412 …"

The manifest: four arrays

@awacloud/fonts (src/main.js) exports a declarative manifest, plus every module descriptor by its binding name:

import { fw_require, modules, extras, bundle } from '@awacloud/fonts';
  • fw_require — the @awacloud/fw descriptors consumed through dependency injection, dependency-closed (binaryReader, binaryWriter, bitstream, huffman, lz77, deflate, adler32, zlib, brotliDict, brotliDictWords, brotli).
  • modules — the local core factories (errors, primitives, sfnt, tables, glyph, embed-pdf, encodings, standard14, and the fonts orchestrator).
  • extras — the opt-in modules (RM05 hinting, Apple AAT, shapers, WOFF2 write, DSIG / MATH / JSTF).
  • bundle — the fontsLargeBundle, fontsFullBundle and fontsAppleAatBundle descriptors, resolvable with runtime.resolve(...).

To use an extra, register the extras array as well and resolve it by name:

import fw from '@awacloud/fw';
import { fw_require, modules, extras } from '@awacloud/fonts';

for (const m of [...fw_require, ...modules, ...extras]) fw.runtime.register(m);
const { parseMath } = fw.runtime.resolve('extraMath');

Strict parsing of untrusted input

Resolve the error classes from the same runtime as fonts: every fontErrors.factory() call declares its own class identities, so a class obtained from a separate direct call is not the one read throws.

import fw from '@awacloud/fw';
import { fw_require, modules } from '@awacloud/fonts';

for (const m of [...fw_require, ...modules]) fw.runtime.register(m);
const api = fw.runtime.resolve('fonts');
const { ParseError } = fw.runtime.resolve('fontErrors');

try {
    const font = api.read(untrustedBytes);
    // OK: parser hardened, caps applied, glyphs accessible.
} catch (e) {
    if (e instanceof ParseError) {
        // e.code, e.g. 'fonts/cmap-range-bomb', 'fonts/glyf-too-many-components',
        // 'fonts/inconsistent-tables', 'fonts/loca-non-monotonic'.
        // See docs/api/errors.md.
    } else { throw e; }
}

Security — parsing untrusted fonts

Font files are frequently parsed on content supplied by users (PDFs, Office documents, uploads). This package's parsers are hardened against the following attack classes:

  • cmap range bombs (format 12/13) — strict bounding of ranges to the Unicode domain [0..0x10FFFF] and a cumulative cap of 2 × 0x110000 materializations (fonts/cmap-range-bomb).
  • Composite glyf — component explosion — components per glyph capped at 256 (fonts/glyf-too-many-components); resolution depth capped at 16 (MAX_DEPTH in compositeResolve.js).
  • cmap format 14 (UVS) — independent sub-readers are used instead of peek, so defaultUVS / nonDefaultUVS decode correctly (regression tests in tests/fuzz.test.js).
  • Generic validation caps — numGlyphs ≤ 65535, numTables ≤ 64, name.count ≤ 32768, gsub script / feature / lookup counts ≤ 1024 / 4096 / 4096, loca strictly monotonic, cross-table bounds (fonts/inconsistent-tables).

Error codes: docs/api/errors.md. Parsing failures throw a typed ParseError, never a bare Error.

Committed dist — two build surfaces

For consumers who do not want to hand-assemble the fw_require / modules manifest, dist/ ships a committed, pre-generated build for each of the four assembly roots (fonts, fonts-large, fonts-full, fonts-apple-aat), in two path-discriminated surfaces:

| Surface | Path | Contents | |---|---|---| | dist/build/ | @awacloud/fonts/build/<root> | fw mode: declares the fw modules as dependencies (DI-injected), needs an @awacloud/fw runtime | | dist/standalone/ | @awacloud/fonts/standalone/<root> | framework-free: every reachable fw factory inlined in the body, zero runtime dependency (Worker-serializable) |

Each root emits a .js / .min.js / .meta.json triplet (for example dist/build/fonts-large.js, fonts-large.min.js, fonts-large.meta.json), plus a single dist/build/index.js barrel re-exporting the whole src/main.js namespace (the four registration arrays and every named descriptor). Each .meta.json sidecar records the root's modules, fwDependencies (read back from the generated descriptor) and build byte sizes.

The generator, tools/generate-bundles.mjs (bun run gen:bundles in the source repository), produces byte-identical output across runs — no build stamp, and every .js / .min.js opens with the /*! … */ licence banner at byte 0. It is a thin wrapper around the shared build tool @awacloud/tool-prebuild-generator, which is not published.

Source structure

src/
├── main.js                     — manifest (4 arrays) + named descriptor exports
├── errors.js                   — FontError / ParseError / RenderError / ContractError
├── fonts.js                    — orchestrator (.read / .use) + cross-table validation
├── _shared/                    — container magics, SFNT flavor mapping, search params
├── primitives/                 — fixed, tag, reader, writer, checksum, encoding
├── sfnt/                       — sfnt header, woff1, woff2, ttc
├── table/                      — head, hhea, maxp, hmtx, cmap, name, OS/2, post,
│                                  loca, glyf, cff, cff2, gsub, gpos, gdef,
│                                  fvar/avar/gvar/hvar/mvar/stat, colr/cpal/sbix/svg,
│                                  cbdt/cblc/ebdt/eblc/ebsc, vhea/vmtx/vorg, base, …
├── glyph/                      — Path, Glyph, compositeResolve (cycle + depth caps)
├── layout/                     — coverage + classDef primitives
├── variable/                   — coordsConvert, instance (axisScalar, gvar deltas)
├── cmap/                       — toUnicode (PDF CMap mini-language)
├── encodings/                  — WinAnsi, MacRoman, Symbol, ZapfDingbats, …
├── standard14/                 — Helvetica × 4, Times × 4, Courier × 4, Symbol, ZD
├── embed-pdf/                  — subsetForPdf, FontDescriptor, CidSystemInfo, ToUnicode
├── extra/                      — tt-hinting, apple-aat/*, shaper-{arabic,indic,cjk},
│                                  woff2-write, dsig, math, jstf
└── bundles/                    — fonts-large, fonts-full, fonts-apple-aat

Hook .use(...) (extension)

import fw from '@awacloud/fw';
import { fw_require, modules } from '@awacloud/fonts';

for (const m of [...fw_require, ...modules]) fw.runtime.register(m);
const api = fw.runtime.resolve('fonts');
api.use({
    hydrateFont(font) { /* inspect or annotate */ },
    hydrateGlyph(glyph) { /* hook */ }
});

Registration is idempotent: calling use(ext) multiple times with the same object has no additional effect. Recognized hooks are listed in KNOWN_HOOKS (hydrateFont, hydrateGlyph, hydrateTable, hydrateName).

Tests

The suite lives in src/**/*.test.js (unit, beside the module) and tests/ (integration: tests/roundtrip.integration.test.js builds a minimal in-memory TTF, encodes it with packSfnt, reads it back through fonts.read and checks metrics and paths; tests/fuzz.test.js locks the parser-bomb fixes down). Run it from the monorepo root:

bun test packages/front/office/fonts/

Not every source module has a sibling *.test.js; some table sub-parsers (for example table/cff/charstring.js) are covered through their parent modules.

Design choices

  • All big-endian — all reader/writer helpers encode in BE by default (OpenType is BE everywhere).
  • Fixed-point — Fixed 16.16 and F2Dot14 are signed integers viewed as a JS number divided/multiplied by 65536 / 16384. No bigint required.
  • DataView + shared buffer — sub-tables are Uint8Array views over the same buffer (zero copy) except when independent mutable state is needed (readBytesCopy).
  • Exclusively typed errors — throw new ParseError(...) everywhere, never throw new Error(...). Hierarchy FontError → {Parse,Render,Contract}Error.
  • Factory pattern — every module is a factory descriptor resolved by an fw ModuleRuntime; constants are declared inside the factory body, with no mutable module-level state.
  • Browser-only — Uint8Array, DataView, TextEncoder, TextDecoder; no Node module.
  • Hardened parser — explicit caps on all count/length fields, independent sub-readers for sub-tables, cross-table validation (fonts/inconsistent-tables).

Documentation

Exposed sub-paths

| Sub-path | Target | Usage | |---|---|---| | @awacloud/fonts | src/main.js | Manifest (fw_require, modules, extras, bundle) and every module descriptor by binding name | | @awacloud/fonts/fonts | src/fonts.js | Top-level fonts orchestrator descriptor (read / .use()) | | @awacloud/fonts/embed-pdf/subsetForPdf | src/embed-pdf/subsetForPdf.js | TrueType subsetter for PDF embedding | | @awacloud/fonts/embed-pdf/subsetForPdf/closure | src/embed-pdf/subsetForPdf/closure.js | Glyph closure (internal sub-module of the subsetter) | | @awacloud/fonts/embed-pdf/subsetForPdf/cmap-builder | src/embed-pdf/subsetForPdf/cmap-builder.js | Subset cmap builder (internal sub-module of the subsetter) | | @awacloud/fonts/embed-pdf/subsetForPdf/glyph-rewriter | src/embed-pdf/subsetForPdf/glyph-rewriter.js | Glyph renumbering (internal sub-module of the subsetter) | | @awacloud/fonts/embed-pdf/subsetForPdf/hash | src/embed-pdf/subsetForPdf/hash.js | Subset-tag hash (internal sub-module of the subsetter) | | @awacloud/fonts/embed-pdf/fontDescriptor | src/embed-pdf/fontDescriptor.js | PDF FontDescriptor dictionary builder | | @awacloud/fonts/embed-pdf/cidSystemInfo | src/embed-pdf/cidSystemInfo.js | PDF CIDSystemInfo dictionary builder | | @awacloud/fonts/embed-pdf/toUnicodeBuilder | src/embed-pdf/toUnicodeBuilder.js | PDF ToUnicode CMap stream builder | | @awacloud/fonts/embed-pdf/*.js | src/embed-pdf/*.js | Any other file of src/embed-pdf/, addressed with its .js suffix | | @awacloud/fonts/encodings | src/encodings/lookup.js | WinAnsi / MacRoman / MacExpert / Standard / Symbol / ZapfDingbats lookup | | @awacloud/fonts/standard14 | src/standard14/lookup.js | Standard 14 AFM metrics (Helvetica, Times, Courier, Symbol, ZapfDingbats) | | @awacloud/fonts/cmap-to-unicode | src/cmap/toUnicode.js | PDF ToUnicode CMap parser / builder | | @awacloud/fonts/extra/* | src/extra/*.js | Opt-in modules (RM05 hinting, AAT, shapers, WOFF2 write, DSIG / MATH / JSTF) | | @awacloud/fonts/bundles/* | src/bundles/*.js | fonts-large / fonts-full / fonts-apple-aat (fw factory descriptors) | | @awacloud/fonts/build/* | dist/build/* | Committed fw-mode build (fonts, fonts-large, fonts-full, fonts-apple-aat, index) | | @awacloud/fonts/standalone/* | dist/standalone/* | Committed framework-free build, same roots |

Maturity

L4 (awa.maturity in package.json): the package covers its declared surface, within the limits listed above, and ships a test suite and per-module reference pages.

Licence

AGPL-3.0-only — see LICENSE. A commercial licence and the third-party notices (Adobe Core 14 AFM metrics, Adobe Glyph List) are in NOTICE.

Copyright (c) 2026 AwaCloud SAS

Project