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

fontext

v2.0.0

Published

Script for extracting glyphes by ligatures from font and creating new minimized fonts with them

Readme

Extract glyphs from fonts and generate optimized, minimal font files.

Two engines for different use cases:

  • Icon engine — extract glyphs from ligature-based icon fonts (Material Icons, etc.)
  • Subset engine — subset any font by characters or unicode ranges, preserving kerning and OpenType features

Why Fontext?

Icon fonts often contain 1000+ glyphs. Text fonts ship entire alphabets when you only need a subset. Fontext solves both:

  • Extract by ligature — pass ligature names like "home", "search", "menu" (icon engine)
  • Extract by raw unicode — pass the actual unicode character and Fontext resolves the ligature automatically (icon engine)
  • Subset by characters — pass "ABCabc0123" to keep only those characters (subset engine)
  • Subset by unicode range — pass U+0400-U+04FF for Cyrillic block (both engines)
  • Multiple output formats — SVG, TTF, WOFF, WOFF2, EOT
  • Preserves font features — subset engine keeps kerning, hinting, GSUB/GPOS via HarfBuzz; pairs of a legacy kern table are kept only for the glyphs that stay in the font, and warnings tells when some of it can't be kept
  • Glyph metadata — get name, unicode mappings, and SVG path data for each extracted glyph
  • Reproducible output — identical input produces byte-identical fonts, so content-hashed asset names stay stable between builds

Installation

npm install fontext

Requires Node.js 22.13 or later. The package is ESM only; CommonJS code can still load it with require("fontext"), which Node supports for ES modules since 22.13 without warnings.

CLI

npx fontext -i material-icons.woff2 -n my-icons -l home,search,menu -f woff2,ttf -o ./fonts

| Flag | Description | |-------------------------|---------------------------------------------------------| | -i, --input | Path to the font file (required) | | -n, --font-name | Name for the output font (required) | | -l, --ligatures | Comma-separated ligature names | | -r, --raws | Comma-separated raw unicode characters | | -u, --unicode-ranges | Comma-separated unicode ranges (e.g. U+E000-U+E100) | | -f, --formats | Output formats: svg,ttf,woff,woff2,eot (default: all) | | -o, --output | Output directory (default: .) |

Quick Start

import {extract} from 'fontext';
import fs from 'fs';

const font = fs.readFileSync('material-icons.woff2');

const result = await extract(font, {
    fontName: 'my-icons',
    ligatures: ['home', 'search', 'menu'],
    formats: ['woff2', 'ttf'],
});

// result.woff2 — Buffer with optimized WOFF2 font
// result.ttf  — Buffer with optimized TTF font
// result.meta — glyph metadata (name, unicode, svg)

fs.writeFileSync('my-icons.woff2', result.woff2);

API

extract(content, options): Promise<ExtractedResult>

| Parameter | Type | Description | |-----------|----------------|---------------------------------------------------------------------| | content | Buffer \| Uint8Array \| ArrayBuffer | Font file contents: TTF, OTF, WOFF or WOFF2 | | options | MinifyOption | Extraction configuration (see below) |

MinifyOption

| Field | Type | Default | Description | |------------------|-------------|-------------|-----------------------------------------------------------------------------| | fontName | string | — | Required. Name for the output font | | ligatures | string[] | [] | Ligature strings to extract (e.g. ['home', 'search']) | | raws | string[] | [] | Raw unicode characters — Fontext will resolve their ligatures automatically | | unicodeRanges | string[] | [] | Unicode ranges to extract (e.g. ['U+E000-U+E100', 'U+F000']) | | characters | string | — | Characters to keep (e.g. 'ABCabc0123') — subset engine only | | engine | Engine | 'icon' | 'icon' for ligature fonts, 'subset' for text fonts (preserves kerning), 'convert' to change format only | | formats | Formats[] | all formats | Output formats: 'svg', 'ttf', 'woff', 'woff2', 'eot' |

At least one of ligatures, raws, unicodeRanges, or characters must be provided.

Error Handling

extract() throws in the following cases:

  • Font input is not a Buffer, Uint8Array or ArrayBuffer — TypeError: "Font input must be a Buffer, Uint8Array or ArrayBuffer"
  • Font data is not TTF, OTF, WOFF or WOFF2 — "Unsupported font format: ..."
  • Font is a collection (TTC, DFONT) — "Font collections (TTC/DFONT) are not supported. Provide a single font file."
  • Font tables are damaged, or the file is cut short so that a table needed to read glyphs runs past its end — "Malformed GSUB table: ...", "Malformed glyf table: ...", "Malformed WOFF file: ..." and similar, naming the table and offset
  • Missing fontName — "fontName is required"
  • No glyph selection for the icon or subset engine — "At least one of ligatures, raws, unicodeRanges, or characters must be provided"
  • Empty or unknown formats — "At least one output format must be specified" / "Invalid format(s): ..."
  • Font lacks a GSUB ligature lookup table (required for raws) — "Font does not contain a GSUB ligature lookup table"
  • A raw unicode character has no matching ligature, or an icon engine ligature doesn't form a single glyph (an unknown name or a typo would otherwise give the glyphs of its letters) — "Font does not contain a ligature for \"...\""
  • The icon engine selection matches no glyph, e.g. unicode ranges the font maps none of — "No glyphs match the selection: the font maps none of unicodeRanges ..."

ExtractedResult

An object with optional keys for each requested format (svg, ttf, woff, woff2, eot), each containing a Buffer. Also includes meta, report and warnings:

interface GlyphMeta {
    name: string;      // ligature name
    unicode: string[];  // unicode mappings
    svg: string;        // SVG markup for the glyph
}

interface OptimizationReport {
    originalSize: number;  // input font size in bytes
    formats: {
        [format: string]: {
            size: number;    // output size in bytes
            saving: number;  // percentage saved, negative when the output is larger
        };
    };
}

interface FontWarning {
    code: "legacy-kern";  // what the output could not keep
    message: string;
}

warnings lists what the subset and convert engines could not keep from the source font; the CLI prints them (and puts them in --json output). legacy-kern: the font kerns only in the legacy kern table, and part of it was left out — an Apple or malformed table, format 2/3, cross-stream or vertical subtables, or pairs of glyphs without a code point that could not be matched in the subset. A version of the font with kerning in GPOS avoids it.

Supported Input Formats

Single TrueType and OpenType fonts (TTF, OTF with CFF outlines), WOFF and WOFF2. Fonts are read with HarfBuzz, which also lays out the ligatures. Font collections (TTC, DFONT) are not supported.

Browser Usage (experimental)

A browser entry point finds glyphs and their SVG outlines without Node.js APIs or format conversion. It reads TTF, OTF and WOFF; WOFF2 is rejected with an error, so decode it first or use the Node entry, which reads WOFF2.

import {createFont, findMetaByLigatures, findLigaturesByRaws} from 'fontext/browser';

const response = await fetch('/fonts/icons.woff');
const data = new Uint8Array(await response.arrayBuffer());

const font = await createFont(data);
const meta = findMetaByLigatures(font, ['home', 'search']);
// meta[0].svg — SVG markup for the glyph
const ligatures = await findLigaturesByRaws(data, ['\uE88A']);

findMetaByLigatures() returns the glyphs the text is laid out to: a text that forms no ligature gives the glyphs of its letters. extract() rejects such ligatures instead.

HarfBuzz runs as WebAssembly (harfbuzz.wasm, about 430 KB, 180 KB gzipped). It is loaded on the first createFont() call from a URL relative to the module (new URL(..., import.meta.url)), and the module uses top-level await, so the bundler has to keep both:

  • Vite works with the default config. In a Web Worker set worker: { format: 'es' }.
  • webpack 5 needs resolve: { fallback: { module: false } }.
  • A Content Security Policy must allow 'wasm-unsafe-eval' in script-src, and .wasm files must be served as application/wasm.

Development

npm test runs the Vitest suite. The browser checks render the output of every engine and format and compare it with the source font pixel by pixel, one browser per script:

  • npm run test:e2e:safari — the installed Safari, through safaridriver. Once per Mac: Safari > Settings > Advanced > "Show features for web developers", then Settings > Developer > "Allow remote automation".
  • npm run test:e2e:chrome — the installed Google Chrome, through Playwright.
  • npm run test:e2e:firefox — Playwright's Firefox; run npx playwright install firefox once.

npm run test:e2e:manual serves the same checks at http://localhost:4178 to open in any browser.

License

MIT