# OpenSpec Change: enterprise-font-decompress - **Change ID**: enterprise-font-decompress - **Package**: `@suaveplan/font-decompress` - **Category**: universal - **Target path**: `universal/compression/font-decompress` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/font-decompress.md` --- ## Summary Deliver `@suaveplan/font-decompress` on the genesis branch from scratch at enterprise quality. The package provides font decompression and extraction: WOFF2-to-TTF expansion, WOFF-to-TTF expansion, glyph enumeration, codepoint-to-glyph mapping extraction, and font metadata reading. Counterpart to `@suaveplan/font-compress`. Pure TypeScript; binary font operations delegate to an injectable `FontDecompressAdapter`. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The main-branch package had 95%+ coverage. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `FontDecompressError`; `FONT_DECOMPRESS.*` codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` | | `@suaveplan/mime` (`workspace:^`) | Font MIME-type detection | | `@suaveplan/font-compress` (`workspace:^`) | `FontFormat`, `FontMetadata` shared types | | `zod` (`catalog:forms`) | Zod schemas for all public option types | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`). --- ## Public API ### Core types ```ts class FontDecompressError extends SuaveplanError interface GlyphInfo { id: number unicode: number | null name: string | null } interface CmapEntry { codepoint: number glyphId: number } interface DecompressResult { data: Uint8Array format: FontFormat // re-exported from @suaveplan/font-compress mimeType: string originalSize: number decompressedSize: number } interface FontDecompressAdapter { readonly supportedInputFormats: readonly FontFormat[] probe(data: Uint8Array): Promise decompress(data: Uint8Array): Promise enumerateGlyphs(data: Uint8Array): Promise getCmapTable(data: Uint8Array): Promise } interface FontDecompressorOptions { adapter: FontDecompressAdapter logger?: LoggerLike clock?: Clock } class FontDecompressor { constructor(options: FontDecompressorOptions) probe(data: Uint8Array): Promise decompress(data: Uint8Array, signal?: AbortSignal): Promise enumerateGlyphs(data: Uint8Array, signal?: AbortSignal): Promise getCmapTable(data: Uint8Array, signal?: AbortSignal): Promise } function createFontDecompressor(options: FontDecompressorOptions): FontDecompressor ``` ### Error codes ```ts type FontDecompressErrorCode = | 'FONT_DECOMPRESS_UNSUPPORTED_FORMAT' | 'FONT_DECOMPRESS_PARSE_FAILED' | 'FONT_DECOMPRESS_DECODE_FAILED' | 'FONT_DECOMPRESS_INVALID_INPUT' const FONT_DECOMPRESS_ERROR_METADATA: Readonly> function registerFontDecompressErrorCodes(): void ``` ### Zod schemas ```ts GlyphInfoSchema CmapEntrySchema DecompressResultSchema FontDecompressorOptionsSchema ``` --- ## New additions for genesis 1. **`FontDecompressAdapter` interface** — codec-agnostic; `NullFontDecompressAdapter` ships for unit tests. 2. **`getCmapTable`** — extracts the Unicode cmap (platform 3, encoding 1) as an array of `CmapEntry` — useful for building glyph-to-character lookup tables without full font parse. 3. **Shared types re-export** — `FontFormat`, `FontMetadata` re-used from `@suaveplan/font-compress`; no duplication. 4. **`AbortSignal` propagation** — every async method; throws `FontDecompressError(FONT_DECOMPRESS_DECODE_FAILED)` on abort. 5. **OTel instrumentation** — span `font.decompress`, counter `font_decompress_total`, histogram `font_decompress_duration_ms`. 6. **Property-based tests** — decompressed size ≥ original size for WOFF/WOFF2, glyph count > 0 for non-empty font. 7. **Benchmark suite** — decompress 50 kB WOFF2, enumerate glyphs from 200 kB TTF. --- ## File layout ```text universal/compression/font-decompress/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ font-decompressor/ font-decompressor.ts font-decompressor.md font-decompressor.types.ts font-decompressor.test.ts index.ts adapters/ null-adapter/ null-adapter.ts null-adapter.md null-adapter.test.ts index.ts utils/ format-detect.ts format-detect.md format-detect.test.ts __benchmarks__/ font-decompress.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream**: font inspection tools, `@suaveplan/react-font-decompress` React layer. **Breaking changes**: none — fresh build. **Prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/mime`, `@suaveplan/font-compress` published to Verdaccio.