Files
suaveplan/openspec/archive/2026-04-25-enterprise-font-subset/proposal.md
2026-05-27 23:29:53 +01:00

5.6 KiB

OpenSpec Change: enterprise-font-subset

  • Change ID: enterprise-font-subset
  • Package: @suaveplan/font-subset
  • Category: universal
  • Target path: universal/compression/font-subset
  • Status: PROPOSED
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/font-compress.md

Summary

Deliver @suaveplan/font-subset on the genesis branch from scratch at enterprise quality. The package provides font subsetting and compression: Unicode range subsetting (remove glyphs not in a given character set), WOFF2 encoding, WOFF encoding, TTF-to-OTF normalisation, feature tag filtering (OpenType layout features), hinting strip, and metadata strip. Pure TypeScript; binary font operations delegate to an injectable FontCompressAdapter.


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 FontCompressError; FONT_COMPRESS.* codes
@suaveplan/types (workspace:^) LoggerLike, Clock
@suaveplan/mime (workspace:^) Font MIME-type detection
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 FontCompressError extends SuaveplanError

type FontFormat = 'ttf' | 'otf' | 'woff' | 'woff2' | 'unknown'

interface FontMetadata {
  format: FontFormat
  mimeType: string
  byteSize: number
  glyphCount?: number
  familyName?: string
  subFamilyName?: string
  version?: string
}

interface SubsetOptions {
  unicodeRanges?: string[]    // e.g. ['U+0020-007F', 'U+00A0-00FF']
  characters?: string         // literal character string to include
  glyphIds?: number[]         // explicit glyph IDs to include
}

interface CompressFontOptions {
  subset?: SubsetOptions
  outputFormat?: FontFormat   // default: 'woff2'
  stripHinting?: boolean      // default: true
  stripMetadata?: boolean     // default: false
  features?: string[]         // OpenType feature tags to retain; empty = all
}

interface FontCompressResult {
  data: Uint8Array
  format: FontFormat
  mimeType: string
  originalSize: number
  compressedSize: number
  ratio: number
  glyphCount?: number
}

interface FontCompressAdapter {
  readonly supportedInputFormats: readonly FontFormat[]
  readonly supportedOutputFormats: readonly FontFormat[]
  probe(data: Uint8Array): Promise<FontMetadata>
  compress(data: Uint8Array, options: CompressFontOptions): Promise<FontCompressResult>
}

interface FontCompressorOptions {
  adapter: FontCompressAdapter
  logger?: LoggerLike
  clock?: Clock
}

class FontCompressor {
  constructor(options: FontCompressorOptions)
  probe(data: Uint8Array): Promise<FontMetadata>
  compress(data: Uint8Array, options?: CompressFontOptions, signal?: AbortSignal): Promise<FontCompressResult>
}

function createFontCompressor(options: FontCompressorOptions): FontCompressor

Error codes

type FontCompressErrorCode =
  | 'FONT_COMPRESS_UNSUPPORTED_FORMAT'
  | 'FONT_COMPRESS_PARSE_FAILED'
  | 'FONT_COMPRESS_ENCODE_FAILED'
  | 'FONT_COMPRESS_INVALID_OPTIONS'
  | 'FONT_COMPRESS_SUBSET_FAILED'

const FONT_COMPRESS_ERROR_METADATA: Readonly<Record<FontCompressErrorCode, CodeMetadata>>
function registerFontCompressErrorCodes(): void

Zod schemas

SubsetOptionsSchema
CompressFontOptionsSchema
FontMetadataSchema
FontCompressResultSchema
FontCompressorOptionsSchema

New additions for genesis

  1. FontCompressAdapter interface — codec-agnostic; NullFontCompressAdapter ships for unit tests.
  2. Unicode range parser — parseUnicodeRanges(ranges: string[]): Set<number> utility; parses U+XXXX and U+XXXX-XXXX notation into a codepoint set.
  3. Format detection — delegates to @suaveplan/mime for initial sniff of TTF/OTF/WOFF/WOFF2 magic bytes.
  4. AbortSignal propagation — passed to adapter; throws FontCompressError(FONT_COMPRESS_ENCODE_FAILED) on abort.
  5. OTel instrumentation — span font.compress, counter font_compress_total, histogram font_compress_duration_ms + font_compress_ratio.
  6. Property-based tests — compressed size ≤ original size for any valid options, ratio in (0, 1].
  7. Benchmark suite — probe 200 kB TTF, WOFF2 encode 200 kB TTF, subset to ASCII range.

File layout

universal/compression/font-subset/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/
      index.ts
    types/
      common.ts
      common.md
    core/
      font-compressor/
        font-compressor.ts
        font-compressor.md
        font-compressor.types.ts
        font-compressor.test.ts
        index.ts
    adapters/
      null-adapter/
        null-adapter.ts
        null-adapter.md
        null-adapter.test.ts
        index.ts
    utils/
      unicode-ranges.ts
      unicode-ranges.md
      unicode-ranges.test.ts
      format-detect.ts
      format-detect.md
      format-detect.test.ts
    __benchmarks__/
      font-compress.bench.ts
  tests/
    integration.test.ts
    property.test.ts

Impact

Downstream: font-loading pipelines, @suaveplan/font-decompress. Breaking changes: none — fresh build. Prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/mime published to Verdaccio.