@atlowchemi/webfont-generator
A native Rust NAPI addon that generates webfonts (SVG, TTF, EOT, WOFF, WOFF2) and their companion CSS/HTML from a set of SVG icon files.
The addon selects the Rust engine's fs capability (including its required async
support) with engine defaults disabled. Cargo feature selection is internal to the
addon; npm consumers use the file-based JavaScript API documented here.
Native WOFF compression uses the engine's static zlib backend.
Incremental results also support single-file add, update, and remove helpers without
resending membership, plus addAsync/updateAsync/removeAsync returning replacement results.
See single-file actions.
For browser applications, use @atlowchemi/webfont-browser-generator, which ships precompiled
WASM and runs SVG-text generation in workers.
This is a ground-up rewrite of @vusion/webfonts-generator in Rust — the original package and its authors deserve credit for the API design and template system that this project builds on. The JS implementation is unmaintained, so this package reimplements the same pipeline natively for better performance and long-term maintainability.
The API is largely compatible with @vusion/webfonts-generator, with a few differences:
- The
cssContextandhtmlContextcallbacks receive only the context object. The original also passedoptionsand thehandlebarsinstance as additional arguments — those are no longer available. formatOptionsis now strictly typed as{ svg?: SvgFormatOptions; ttf?: TtfFormatOptions; woff?: WoffFormatOptions; woff2?: Woff2FormatOptions }. The original accepted arbitrary{ [format]: unknown }; theeotkey is no longer accepted (EOT is derived from the TTF output).- A new
optimizeOutputoption runs an SVG path optimizer over each glyph before assembling the font. Defaults tofalse; optimization may reduce SVG path data, but does not guarantee smaller binary font output, so compare the generated sizes for your input set. Also available asformatOptions.svg.optimizeOutput. formatOptions.woff2.compressionQualitysets the Brotli compression quality (0–11) for WOFF2 output. Defaults to11(smallest output); lower it (e.g. to10) for faster encoding at a marginal size cost.- A new
incrementaloption (defaultfalse) retains parsed glyph data on the result soresult.regenerateAsync(files, changes?)can rebuild after file changes without re-parsing the glyphs that didn't change or blocking the Node.js event loop. It refreshes the outputs in memory and, when the result was generated withwriteFiles, writes refreshed fonts to disk too while skipping unchanged CSS/HTML companion files. You pass the full file set (in the order a fresh build would use) plus what changed, or omitchangesto re-read/hash the full set and infer changes; the result is byte-identical to a freshgenerateWebfonts()of that set, additions included. - Generated font binaries (TTF, WOFF, etc.) may differ at the byte level because a different encoder is used, but the fonts are equally valid.
- CSS, HTML, and template output is identical.
- A new
variantsoption supports multi-weight families with discrete designs. - A new
colorGlyphsoption preserves solid SVG colors for selected glyphs in TTF, WOFF, and WOFF2, disabled by default. colorSlots, for example{ secondary: '#ff9900' }, reserves named palette entries for CSS light/dark themes. Requires activecolorGlyphs; slots use distinct#RRGGBBcolors and ASCII alphanumeric/hyphen/underscore names. Indices follow ASCII name order and are exposed in the CSS context'scolorSlotsmap. See the live example.
Performance scales better with glyph count — for larger icon sets the native pipeline is significantly faster.
Installation and usage
npm install @atlowchemi/webfont-generator
Consumers on the following platforms do not need Rust or Cargo. Keep optional dependencies enabled so your package manager installs the matching pre-built native binary.
| Platform | Architecture |
|---|---|
| macOS | x64, arm64 |
| Linux (glibc) | x64, arm64, armv7 |
| Linux (musl) | x64, arm64 |
| Windows (MSVC) | x64, arm64 |
import { generateWebfonts } from '@atlowchemi/webfont-generator';
const result = await generateWebfonts({
files: ['./icons/home.svg', './icons/search.svg'],
dest: './dist/fonts',
fontName: 'my-icons',
types: ['woff2', 'woff'],
});
const css = result.generateCss();
const html = result.generateHtml();
Incremental regeneration
Multi-weight families also support incremental builds. Set incremental: true, then use
regenerateAsync({ variants: [{ variant: 'bold', files: [...] }, ...] }, changes)
with every configured design's complete file list. Omit changes to re-diff every design. See the
variant regeneration reference
for input shapes, synchronous alternatives, and failure semantics.
let files = ['./icons/home.svg', './icons/search.svg'];
let result = await generateWebfonts({ files, dest, fontName: 'my-icons', incremental: true });
// On a watch event, rebuild reusing cached geometry for unchanged glyphs. Pass the full file set
// (in fresh-build order) so additions land in the right position, plus what changed:
result = await result.regenerateAsync({ files }, [{ path: './icons/home.svg', changeType: 'changed' }]);
// Or omit changes when watcher hints are unavailable/untrusted:
result = await result.regenerateAsync({ files });
result.woff2; // refreshed bytes
The first argument is the complete file set after the change, in the order a fresh build would use (e.g. your glob result) — any file omitted from it is dropped. Each change is { path, changeType: 'added' | 'changed' | 'removed', name? }, where name is the resolved glyph name if you apply a custom rename; otherwise added files derive their name from the file stem, changed files keep their current name, and removed files ignore it. When changes is omitted or null, every current file is re-read and hashed, and changes are inferred automatically. regenerateAsync() returns a replacement result; the receiver stays readable and unchanged while work runs and after failure. Assign the replacement before starting another rebuild because overlapping calls from the same result lineage are rejected. Disk writes are not transactional. The synchronous, mutating regenerate() method remains available when blocking the event loop is acceptable. Results generated with cssContext or htmlContext callbacks cannot be regenerated because those JavaScript callbacks cannot be re-run during a rebuild.
Templates
Default CSS, SCSS, and HTML templates are available via the /templates export:
import * as templates from '@atlowchemi/webfont-generator/templates';
console.log(templates.css); // path to default CSS template
console.log(templates.scss); // path to default SCSS template
console.log(templates.html); // path to default HTML template
Documentation
See the Node.js reference for the complete API. For direct Rust or command-line use, see the separate engine crate.