---
url: /webfont-generator/browser.md
description: >-
  Browser font generation with precompiled WebAssembly and a worker-owned
  in-memory API.
---

# Browser API

`@atlowchemi/webfont-browser-generator` wraps the Rust engine with wasm-bindgen and runs
generation inside dedicated module workers. The package ships compiled JavaScript, types,
worker code and WASM; applications need no Rust tools or install-time compilation.

::: tip Try it
The [Playground](/playground) runs this package in your browser: drop in SVGs, adjust the options, and download the fonts.
:::

## Installation

::: code-group

```sh [npm]
npm install @atlowchemi/webfont-browser-generator
```

```sh [pnpm]
pnpm add @atlowchemi/webfont-browser-generator
```

```sh [yarn]
yarn add @atlowchemi/webfont-browser-generator
```

```sh [bun]
bun add @atlowchemi/webfont-browser-generator
```

:::

## How to use

Import `generateWebfonts` in your Vite application's client code. Vite automatically bundles
the worker and WASM assets; no additional Vite configuration is needed.

```ts [src/main.ts]
import { generateWebfonts } from '@atlowchemi/webfont-browser-generator';

using result = await generateWebfonts({
    sources: [{ id: 'square', name: 'square', svg: '<svg viewBox="0 0 24 24"><path d="M2 2H22V22H2Z"/></svg>' }],
    types: ['woff2'],
    incremental: true,
    // Pin the font's creation timestamp so the same input always produces the same bytes.
    // Without it, the worker records the current browser time.
    formatOptions: { ttf: { ts: 0 } },
});
const bytes = await result.font('woff2');
const css = await result.generateCss();
```

Options follow the [Rust SVG text API](./rust#svg-text-api) in camelCase, including variants,
color selection, missing-glyph policies, format options and inline templates. Unknown fields,
including file input/output options, are rejected. There are no JavaScript callbacks.
Variant families support TTF, WOFF and WOFF2; ordinary fonts additionally support SVG and EOT.

Without `formatOptions.ttf.ts`, generation captures the browser clock once and retains that
timestamp during updates; fonts reused from a [persistent cache](#persistent-cache) keep the
timestamp of the session that created them. A fixed Unix timestamp produces reproducible font
metadata.
WOFF uses the pure-Rust zlib-rs compression backend on both native and wasm32 targets.
WOFF2 uses single-threaded Brotli at every supported compression quality (0–11).

### Using the output

The result holds plain bytes and text, so you can use the font on the current page, offer it as a download, or upload it.

::: code-group

```ts [Use on the page]
const fontUrl = URL.createObjectURL(new Blob([bytes!], { type: 'font/woff2' }));
// Point the generated CSS at the in-memory font, then apply it.
const style = document.createElement('style');
style.textContent = await result.generateCss();
style.textContent = style.textContent.replace(/url\("?[^)"]*\.woff2[^)"]*"?\)/g, `url("${fontUrl}")`);
document.head.append(style);

document.body.insertAdjacentHTML('beforeend', '<i class="icon icon-square"></i>');
```

```ts [Download]
function download(name: string, data: BlobPart, type: string) {
    const link = document.createElement('a');
    link.href = URL.createObjectURL(new Blob([data], { type }));
    link.download = name;
    link.click();
    URL.revokeObjectURL(link.href);
}

download('iconfont.woff2', bytes!, 'font/woff2');
download('iconfont.css', await result.generateCss(), 'text/css');
```

```ts [Upload]
const outputs = await result.collectOutputs();
const form = new FormData();
for (const [kind, data] of Object.entries(outputs)) {
    form.append(kind, new Blob([data]), `iconfont.${kind}`);
}
await fetch('/api/icon-fonts', { method: 'POST', body: form });
```

:::

To pass explicit font URLs instead of rewriting the CSS, set `cssFontsUrl` to the URL prefix where you will serve the files.

## Result methods

::: tip
An explicit `dispose()` method is available for manually managed lifetimes instead of using Explicit Resource Management. It is idempotent: calling it more than once has no additional effect and does not throw.
:::

| Method                            | Result                             | Behavior                                                                                                              |
| --------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `font(format)`                    | `Promise<Uint8Array \| undefined>` | Copy a requested font; does not render templates                                                                      |
| `generateCss()`                   | `Promise<string>`                  | Lazy CSS render, even if CSS collection is disabled                                                                   |
| `generateHtml()`                  | `Promise<string>`                  | Lazy HTML render, reusing CSS                                                                                         |
| `generateTs()`                    | `Promise<string>`                  | Fixed TypeScript metadata, even if TS or CSS collection is disabled                                                   |
| `collectOutputs()`                | `Promise<CollectedOutputs>`        | Object keyed by output kind, with independently owned bytes; unrequested outputs are omitted                          |
| `regenerate(membership, changes)` | `Promise<void>`                    | Apply source updates; requires `incremental: true`                                                                    |
| `add(source, variant?)`           | `Promise<void>`                    | Append a new source; families require a target variant. Requires `incremental: true`.                                 |
| `update(id, svg, name?)`          | `Promise<void>`                    | Replace an existing source, preserving its position. Requires `incremental: true`.                                    |
| `remove(id)`                      | `Promise<void>`                    | Remove an ID from all designs without emptying a design. Requires `incremental: true`.                                |
| `exportCache()`                   | `Promise<Uint8Array \| undefined>` | Persistent cache bytes to store; `undefined` when caching is off or nothing changed. See [caching](#persistent-cache) |
| `dispose()`                       | `void`                             | Terminate the worker and reject pending calls. Idempotent: repeated calls have no additional effect and do not throw  |

`CollectedOutputs` is `Partial<Record<FontType | 'css' | 'html' | 'ts', Uint8Array<ArrayBuffer>>>`.
Access outputs directly by kind, for example `(await result.collectOutputs()).woff2`.

Calls on one result execute in submission order. Returned byte arrays remain valid after updates
and disposal and cannot mutate the engine's retained buffers. CSS and HTML are compiled lazily;
an invalid HTML template does not prevent accessing a font or rendering CSS.

## TypeScript metadata

`await result.generateTs()` returns fixed TypeScript source, even when `ts` or `css` is
false. Set `ts: true` in generation options to also include UTF-8 bytes under `ts` in
`collectOutputs()`; collection defaults to off.

```ts
export const iconBaseSelector = ".icon" as const;
export const iconClassPrefix = "icon-" as const;
export const icons = [
  "square"
] as const;

export type IconName = (typeof icons)[number];
```

The readonly tuple follows the engine's canonical icon order and variant-family names.
Selectors use the effective `templateOptions.baseSelector` and `templateOptions.classPrefix`.
Custom CSS may emit different classes: this metadata describes the context, not parsed CSS.
There is no TS template, override, callback or browser filesystem destination.

Source additions, removals, renames and batch regeneration refresh the metadata. To change
options, create a new result.

## Persistent cache

Pass `cache` to reuse work from an earlier session. Start with an empty array, store what
`exportCache()` returns, and pass it back next time. Unchanged icons skip parsing and path
processing, and unchanged formats are reused; output is byte-identical.

The package only accepts and returns the bytes (a `Uint8Array`); storing them is up to your app.
Keep them anywhere that holds binary data: IndexedDB (stores `Uint8Array` natively), the Origin
Private File System, Cache Storage, or your own server. Expect several MB for hundreds of icons.
In the example, `loadCache` and `saveCache` stand for your own storage code:

```ts
import { generateWebfonts } from '@atlowchemi/webfont-browser-generator';
// Your own module, not part of the package: for example an IndexedDB wrapper.
import { loadCache, saveCache } from './icon-cache';

const stored = await loadCache('icons'); // Uint8Array | undefined
using result = await generateWebfonts({
    sources,
    types: ['woff2'],
    cache: stored ?? new Uint8Array(),
});
const updated = await result.exportCache();
if (updated) await saveCache('icons', updated);
```

* Without a fixed `formatOptions.ttf.ts`, reused fonts keep the timestamp of the session that
  created them, so output matches that session rather than the current clock. Set `formatOptions.ttf.ts` for
  reproducible output.
* `exportCache()` reflects incremental updates and resolves `undefined` when nothing changed since
  the cache was loaded or last exported, so you can skip writes.
* Bytes from another engine version or other settings are ignored, and corrupted or truncated
  bytes fall back to a correct uncached run. Saved fonts and saved per-icon work carry separate
  checksums; sessions whose fonts all come from the cache don't read the per-icon work, so damage
  limited to it is detected (and a fresh cache exported) by the next session that needs it.

::: warning Only pass trusted cache bytes
Cached fonts are reused as-is, so only pass bytes your own app stored. Don't accept cache bytes
from users or other origins, and clear the stored copy if in doubt.
:::

## Incremental updates

With `incremental: true`, single actions derive membership from the worker's current result:

```ts
await result.add({ id: 'search', name: 'search', svg: searchSvg });
await result.update('search', replacementSvg, 'renamed');
await result.remove('search');
// For a family, additions require a target variant:
await family.add({ id: 'bold:search', name: 'search', svg: searchSvg }, 'bold');
```

`add(source, variant?)` appends a new ID. `update(id, svg, name?)` preserves its position and,
when omitted, its name. `remove(id)` preserves remaining order. Updates and removals apply
to every design consuming the ID. Duplicate additions, unknown IDs, incorrect target variants,
and removals that empty a design reject without replacing the result. Calls execute in submission
order, including after recoverable failures. None of these methods requires resending unchanged SVGs.

For batches, explicit ordering, or membership reassignment, use `regenerate`:

```ts
await result.regenerate({ sources: ['square'] }, [{ type: 'changed', id: 'square', svg: replacementSvg }]);
```

Supply complete final IDs in order, with only changed source content. For variants, membership
is `{ variants: [{ name: 'light', ids: [...] }, { name: 'bold', ids: [...] }] }` in original design order.
Changes use `type: 'added'` with id/name/svg, `type: 'changed'` with id/svg and optional name,
or `type: 'removed'` with id. Options and variant configuration stay fixed.

Ordinary validation/build failures reject the call and preserve the existing result for retry.
Worker failures or WASM traps invalidate the session and reject pending calls. Create a new
result after a fatal failure. `dispose()` is idempotent; all subsequent calls reject.

## Worker lifetime and hosting

`generateWebfonts(options, { workerUrl })` accepts an optional string or URL for a separately
hosted worker. By default, bundlers resolve the worker relative to the package entry.

For separately hosted workers, serve `worker.js` and `webfont_browser_bg.wasm` together from the same origin.

Each result has its own worker and WASM instance. Keep a result for incremental work, and
dispose it when finished. The package needs neither SharedArrayBuffer nor cross-origin isolation.
Vite emits the worker and WASM assets. Serve WASM with `application/wasm`, and permit worker
execution and WASM compilation in your Content Security Policy.
