# Capability Spec Delta: font-compress (font-compress) **Package**: `@suaveplan/font-compress` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. ## ADDED Requirements ### Requirement: Packer-only public surface The package SHALL expose `BinaryFontPacker` class + `createBinaryFontPacker()` factory + `FONT_COMPRESS_CODES` registry + `FontCompressError` class, and SHALL NOT expose any unpacker functionality. `BinaryFontUnpacker` and `createBinaryFontUnpacker` are NOT exported from this package — they live in the sibling `@suaveplan/font-decompress`. #### Scenario: root barrel exposes only the packer - **WHEN** a consumer imports `@suaveplan/font-compress` - **THEN** `BinaryFontPacker`, `createBinaryFontPacker`, `FONT_COMPRESS_CODES`, and `FontCompressError` are present; `BinaryFontUnpacker` is undefined **Verified by:** `src/packer/binary-font-packer/binary-font-packer.test.ts::"root barrel exposes packer only"` --- ### Requirement: FONT_COMPRESS_CODES trimmed registry The package SHALL declare an error-code registry via `defineCodes("FONT_COMPRESS", [...])` containing exactly `PACK_FAILED` and `INVALID_DATA`. `UNPACK_FAILED` is NOT in this registry — it lives in `@suaveplan/font-decompress` as `FONT_DECOMPRESS.UNPACK_FAILED`. Every pack-side error path SHALL throw a `FontCompressError` carrying one of the two retained codes. #### Scenario: every kept code is reachable - **WHEN** the test suite exercises a successful pack and a malformed-input pack (e.g. missing `familyName`) - **THEN** the malformed path raises `FontCompressError` whose `code` matches `FONT_COMPRESS.INVALID_DATA` or `FONT_COMPRESS.PACK_FAILED` #### Scenario: UNPACK_FAILED removed from registry - **WHEN** the test suite asserts on `Object.keys(FONT_COMPRESS_CODES).sort()` - **THEN** the array is exactly `["INVALID_DATA", "PACK_FAILED"]` (sorted lexicographically) **Verified by:** `src/error-codes.test.ts::"every kept FONT_COMPRESS.* code is reachable"` AND `src/error-codes.test.ts::"UNPACK_FAILED removed from registry post-split"` --- ### Requirement: BinaryFontPacker public API The package SHALL provide `class BinaryFontPacker` with method `pack(font: TypefaceFont): Uint8Array` and a `createBinaryFontPacker(): BinaryFontPacker` factory function. `TypefaceFont` is imported from `@suaveplan/font-convert`. The class SHALL emit the BTF wire format consumed by `@suaveplan/font-decompress::BinaryFontUnpacker`. #### Scenario: round-trip TypefaceFont → BTF → TypefaceFont - **WHEN** a known-vector `TypefaceFont` is passed to `BinaryFontPacker.pack(...)` and the resulting bytes are then unpacked via `BinaryFontUnpacker.unpack(...)` (from `@suaveplan/font-decompress`) - **THEN** the round-trip preserves `familyName`, `cssFontWeight`, `cssFontStyle`, `boundingBox`, metrics, and every glyph entry (within the dictionary-encoding tolerance documented in the implementation) **Verified by:** `src/packer/binary-font-packer/binary-font-packer.test.ts::"round-trip preserves TypefaceFont shape via font-decompress unpacker"` --- ### Requirement: encodeString utility The package SHALL provide an `encodeString(value: string): Uint8Array` helper used by the packer to emit length-prefixed UTF-8 strings to the BTF header. The function SHALL emit the bytes in the format `[uint32 length][utf8 bytes]`. `decodeString` is NOT in this package — it lives in `@suaveplan/font-decompress`. #### Scenario: emits length-prefixed UTF-8 - **WHEN** `encodeString("Inter Variable")` is called - **THEN** the returned `Uint8Array` is `14` (uint32 length, little-endian) followed by the UTF-8 bytes of `"Inter Variable"` #### Scenario: decodeString export removed - **WHEN** `import { decodeString } from "@suaveplan/font-compress"` is attempted at type-check time - **THEN** TypeScript reports `decodeString` is not exported (the test asserts the runtime absence via `import * as ns from ...; expect(ns).not.toHaveProperty("decodeString")`) **Verified by:** `src/utils/encode-string.test.ts::"emits length-prefixed UTF-8"` AND `src/utils/encode-string.test.ts::"decodeString export removed post-split"` --- ### Requirement: Universal-tier purity The package SHALL ship as a universal-tier package — no imports from `node:*`, `bun:*`, or the bare specifiers `fs`/`path`/`stream`/`net`/`http`/`crypto` in any file under `src/`. `package.json` SHALL NOT declare a `"browser"` field, SHALL NOT pin `engines.node` or `engines.bun`, and SHALL declare `"sideEffects": false`. #### Scenario: no platform imports in src/ - **WHEN** the CI step `grep -rnE "from ['\"](node:|bun:|fs|path|stream|net|http)['\"]" packages/fonts/font-compress/src/` runs - **THEN** the command produces zero matches **Verified by:** `src/packer/binary-font-packer/binary-font-packer.test.ts::"tier purity: no node/bun imports"` --- ### Requirement: Subpath exports trimmed `package.json` SHALL declare subpath exports for `.` (root barrel), `./error-codes`, `./constants`, `./utils/encode-string`, `./utils/parse-path`, `./packer/binary-font-packer`. The `./unpacker/binary-font-unpacker` subpath is removed (no longer present in dist). #### Scenario: unpacker subpath removed - **WHEN** a consumer attempts `import { BinaryFontUnpacker } from "@suaveplan/font-compress/unpacker/binary-font-unpacker"` - **THEN** the import fails to resolve (the subpath is no longer in `package.json` exports and the source files are no longer present) **Verified by:** `src/packer/binary-font-packer/binary-font-packer.test.ts::"unpacker subpath export removed"` --- ### Requirement: Banned patterns absent from src/ The package SHALL ship with zero matches of `@deprecated`, `TODO`, `FIXME`, `ts-ignore`, `ts-expect-error`, `it.skip`, `test.skip`, `describe.skip`, `.only`, raw `any`, or `console.log` in any `.ts` or `.tsx` file under `src/`. Zero `// biome-ignore` suppression comments. All test files import runner primitives from `@suaveplan/testing/runner` (rule §7). #### Scenario: clean static-analysis grep - **WHEN** the CI step `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\\.skip\\|\\.only\\|\\bany\\b\\|console\\.log" packages/fonts/font-compress/src/ --include="*.ts"` runs - **THEN** the command produces zero matches **Verified by:** repo-wide CI grep gate + `bun scripts/lint/check-test-runner-imports.ts`