Files
suaveplan/openspec/changes/enterprise-font-decompress/proposal.md
2026-05-27 23:29:53 +01:00

5.2 KiB

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

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<FontMetadata>
  decompress(data: Uint8Array): Promise<DecompressResult>
  enumerateGlyphs(data: Uint8Array): Promise<GlyphInfo[]>
  getCmapTable(data: Uint8Array): Promise<CmapEntry[]>
}

interface FontDecompressorOptions {
  adapter: FontDecompressAdapter
  logger?: LoggerLike
  clock?: Clock
}

class FontDecompressor {
  constructor(options: FontDecompressorOptions)
  probe(data: Uint8Array): Promise<FontMetadata>
  decompress(data: Uint8Array, signal?: AbortSignal): Promise<DecompressResult>
  enumerateGlyphs(data: Uint8Array, signal?: AbortSignal): Promise<GlyphInfo[]>
  getCmapTable(data: Uint8Array, signal?: AbortSignal): Promise<CmapEntry[]>
}

function createFontDecompressor(options: FontDecompressorOptions): FontDecompressor

Error codes

type FontDecompressErrorCode =
  | 'FONT_DECOMPRESS_UNSUPPORTED_FORMAT'
  | 'FONT_DECOMPRESS_PARSE_FAILED'
  | 'FONT_DECOMPRESS_DECODE_FAILED'
  | 'FONT_DECOMPRESS_INVALID_INPUT'

const FONT_DECOMPRESS_ERROR_METADATA: Readonly<Record<FontDecompressErrorCode, CodeMetadata>>
function registerFontDecompressErrorCodes(): void

Zod schemas

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

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.