6.0 KiB
Capability Spec Delta: font-decompress (font-decompress)
Package: @suaveplan/font-decompress
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time. No exceptions.
ADDED Requirements
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-decompress/src/runs - THEN the command produces zero matches
Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"tier purity: no node/bun imports"
Requirement: FONT_DECOMPRESS_CODES error registry
The package SHALL declare an error-code registry via defineCodes("FONT_DECOMPRESS", [...]) containing exactly UNPACK_FAILED and INVALID_DATA. Every public error path SHALL throw a FontDecompressError extends SuaveplanError<FontDecompressErrorCode> carrying one of those codes. The class SHALL NOT throw raw Error instances from any public method.
Scenario: every code is reachable from a real call site
- WHEN the test suite exercises malformed wire bytes (truncated header, dictionary length overrun) and a successful unpack call
- THEN each path raises (or returns) values consistent with the documented contract; the
UNPACK_FAILEDandINVALID_DATAcodes are each reachable
Scenario: SuaveplanError contract preserved
- WHEN a
FontDecompressErroris constructed with acause(e.g. wrapping aRangeErrorfromDataView.getUint32) - THEN
error.causeis the original;error instanceof SuaveplanErroris true
Verified by: src/error-codes.test.ts::"every FONT_DECOMPRESS.* code is reachable from a call site" AND src/error-codes.test.ts::"FontDecompressError preserves cause"
Requirement: BinaryFontUnpacker public API
The package SHALL provide class BinaryFontUnpacker with method unpack(data: Uint8Array): TypefaceFont and a createBinaryFontUnpacker(): BinaryFontUnpacker factory function. TypefaceFont is imported from @suaveplan/font-convert. The class SHALL parse the BTF wire format produced by @suaveplan/font-compress::BinaryFontPacker and produce a TypefaceFont object that round-trips that pack-then-unpack pair byte-for-byte to JSON-shape equivalence.
Scenario: round-trip BTF → TypefaceFont
- WHEN a known-vector BTF buffer (produced offline by
BinaryFontPacker.pack(...)) is passed tounpack(...) - THEN the resulting
TypefaceFonthas the expectedfamilyName,cssFontWeight,cssFontStyle,boundingBox,glyphs(per-characterha/x_min/x_max/opath string), and metrics
Scenario: truncated buffer raises UNPACK_FAILED or INVALID_DATA
- WHEN
unpack(...)is called on a buffer truncated mid-dictionary - THEN a
FontDecompressErroris thrown with one of the registry's codes
Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"round-trip pack/unpack preserves font shape" AND src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"truncated buffer raises FontDecompressError"
Requirement: decodeString utility
The package SHALL provide a decodeString(data: Uint8Array, offset: number): { value: string; offset: number } helper used by the unpacker to read length-prefixed UTF-8 strings from the BTF header. The function SHALL return the decoded string plus the offset of the next byte after the string. Malformed length prefixes (negative, beyond buffer end) SHALL raise FontDecompressError with code === "FONT_DECOMPRESS.INVALID_DATA".
Scenario: round-trip a UTF-8 string
- WHEN
decodeStringis called on bytes produced by the matchingencodeString(from@suaveplan/font-compress) for"Inter Variable" - THEN the returned
valueequals"Inter Variable"andoffsetpoints past the encoded bytes
Scenario: rejects length-overrun
- WHEN
decodeStringis called on a buffer whose length prefix exceeds the buffer length - THEN a
FontDecompressErrorwithcode === "FONT_DECOMPRESS.INVALID_DATA"is thrown
Verified by: src/utils/decode-string.test.ts::"round-trip a UTF-8 string" AND src/utils/decode-string.test.ts::"rejects length overrun"
Requirement: Subpath exports tree-shake per feature module
package.json SHALL declare subpath exports for . (root barrel), ./error-codes, ./constants, ./utils/decode-string, ./unpacker/binary-font-unpacker. Each subpath SHALL resolve to a built .js + .d.ts pair under dist/. sideEffects SHALL be false.
Scenario: subpath import resolves to feature module
- WHEN a consumer writes
import { BinaryFontUnpacker } from "@suaveplan/font-decompress/unpacker/binary-font-unpacker" - THEN the import resolves to the dedicated unpacker module, not the root barrel
Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"subpath export resolves to unpacker module"
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-decompress/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