Files
2026-05-27 23:47:53 +01:00

132 lines
5.8 KiB
Markdown

# @suaveplan/font-decompress - BTF (Binary TypeFace) binary unpacking. Reverses @suaveplan/font-compress's BinaryFontPacker output back into typeface.js JSON, ready for Three.js / R3F / game-font rendering pipelines. Universal.
BTF (Binary TypeFace) binary unpacking. Reverses
`@suaveplan/font-compress`'s `BinaryFontPacker` output into a
typeface.js JSON shape ready for Three.js / R3F / game-font
rendering. Universal, store-agnostic, free of `node:` / `bun:`
imports.
<!-- begin:auto-docs -->
## Documentation tree
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/font-decompress` package centralises every failure mode it can produce behind a single registry built w…
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/font-decompress` package centralises every failure mode it can produce behind a single registry built w…
- [binary-font-unpacker](./src/unpacker/binary-font-unpacker/binary-font-unpacker.md) — `BinaryFontUnpacker` reverses the BTF (Binary TypeFace) wire format produced by `BinaryFontPacker` from `@suaveplan/fon…
- [binary-font-unpacker](./src/unpacker/binary-font-unpacker/binary-font-unpacker.md) — `BinaryFontUnpacker` reverses the BTF (Binary TypeFace) wire format produced by `BinaryFontPacker` from `@suaveplan/fon…
- [utils](./src/utils/decode-string.md) — `decodeString` reads a length-prefixed UTF-8 string from a `Uint8Array` at a given offset. The BTF wire format encodes…
- [decode-string](./src/utils/decode-string.md) — `decodeString` reads a length-prefixed UTF-8 string from a `Uint8Array` at a given offset. The BTF wire format encodes…
<!-- end:auto-docs -->
## Purpose
Compressed BTF buffers ship from build-time tooling to the renderer
as compact `Uint8Array` blobs — they are far smaller than the
underlying typeface.js JSON for non-trivial fonts. The renderer needs
the JSON back to hand to Three.js's `FontLoader.parse(...)`. This
package owns that reverse step; the sibling `@suaveplan/font-compress`
owns the forward step.
The split exists because pack and unpack are independent concerns:
build tooling never unpacks, runtime renderers never pack. Shipping a
unified package forces consumers to drag the other half. Splitting
lets each side install only what it actually executes.
## Features
- `BinaryFontUnpacker` class + `createBinaryFontUnpacker()` factory
- Strict boundary validation on every read — raises `FontDecompressError` with `FONT_DECOMPRESS.INVALID_DATA`
- Underlying primitive exceptions (DataView, TextDecoder) wrapped as `FontDecompressError` with `FONT_DECOMPRESS.UNPACK_FAILED` and `cause` preserved
- All five BTF glyph commands (`m`/`l`/`b`/`q`/`z`) round-trip byte-for-byte
- Unicode glyph keys (multi-byte UTF-8) round-trip correctly
- Negative coordinates round-trip correctly
- 100% statement / branch / function / line Istanbul coverage
- 5 tree-shakeable subpath exports
## Usage: Basic
```ts
import { createBinaryFontUnpacker } from "@suaveplan/font-decompress";
const unpacker = createBinaryFontUnpacker();
const font = unpacker.unpack(btfBuffer);
console.log(font.familyName); // "Inter Variable"
console.log(font.glyphs["A"]?.ha); // glyph advance width
```
## Usage: Advanced
```ts
import {
BinaryFontUnpacker,
FONT_DECOMPRESS_CODES,
FontDecompressError,
} from "@suaveplan/font-decompress";
// Reuse a single instance — stateless and safe.
const unpacker = new BinaryFontUnpacker();
async function loadCdnFont(url: string) {
const buf = new Uint8Array(
await (await fetch(url)).arrayBuffer(),
);
try {
return unpacker.unpack(buf);
} catch (err) {
if (
err instanceof FontDecompressError &&
err.code === FONT_DECOMPRESS_CODES.INVALID_DATA
) {
console.warn("Corrupted BTF from CDN:", err.message);
return null;
}
throw err;
}
}
```
## API Reference
### Public exports
- `class BinaryFontUnpacker` — `unpack(data: Uint8Array): TypefaceFont`
- `createBinaryFontUnpacker(): BinaryFontUnpacker`
- `FONT_DECOMPRESS_CODES` — frozen registry with `UNPACK_FAILED`, `INVALID_DATA`
- `class FontDecompressError extends SuaveplanError<FontDecompressErrorCode>`
- `type FontDecompressErrorCode`
The `TypefaceFont` and `PathCommand` types come from
`@suaveplan/font-convert`; consumers import them directly from there
rather than via this package's surface.
### Subpath exports
| Subpath | What |
| -------------------------------------------- | ----------------------------- |
| `.` | Root barrel (above) |
| `./error-codes` | `FONT_DECOMPRESS_CODES`, `FontDecompressError` |
| `./constants` | BTF wire-format constants |
| `./utils/decode-string` | `decodeString` helper |
| `./unpacker/binary-font-unpacker` | `BinaryFontUnpacker`, factory |
## Implementation Notes
The package is **universal**: zero `node:` / `bun:` imports
anywhere in `src/`. The only platform-aware calls are `DataView`,
`TextDecoder`, and `String.fromCharCode` — all available in every
supported runtime (Node ≥ 18, Bun, modern browsers, Web Workers).
The BTF wire-format constants (`CMD_M` … `CMD_Z`, `BYTE_TO_CMD`,
`CMD_COORD_COUNTS`, `METRICS_COUNT`) are duplicated from
`@suaveplan/font-compress`. Duplicating 19 lines of magic numbers
avoids a third "fonts-shared" package and keeps each package
self-contained. If the BTF format ever changes — which is a
coordinated breaking event — both packages get updated in the same
commit.
`@suaveplan/font-compress` is a **devDependency** for round-trip
tests only. The runtime dependency graph is `font-decompress →
font-convert + error`; the test surface adds `font-compress` solely
to generate known-vector fixtures.