110 lines
6.3 KiB
Markdown
110 lines
6.3 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
|
|
|
# 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`
|