# OpenSpec Change: enterprise-font-convert - **Change ID**: enterprise-font-convert - **Package**: `@suaveplan/font-convert` - **Category**: universal - **Target path**: `universal/compression/font-convert` - **Status**: PROPOSED - **Date**: 2026-04-25 - **Reference source**: `/mnt/s/web/suaveplan/packages/universal/font-compress/src/converter/` --- ## Summary Deliver `@suaveplan/font-convert` on the genesis branch from scratch at enterprise quality. The package converts TTF/OTF font files to the typeface.js JSON format used by Three.js. It parses binary fonts via `opentype.js`, extracts glyph outline commands, corrects path winding order for TrueType fonts, and emits a `TypefaceFont` JSON structure. Also defines the shared `TypefaceFont` and `PathCommand` types consumed by `@suaveplan/font-compress` and `@suaveplan/font-decompress`. --- ## Current-state audit (genesis) The package does not exist on genesis yet. The original logic lived inside `@suaveplan/font-compress` on main; it is extracted here as a standalone package. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `FontConvertError`; `FONT_CONVERT.*` codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` | | `opentype.js` (`catalog:fonts`) | Font binary parsing (TTF/OTF/WOFF/WOFF2) | | `@types/opentype.js` (`catalog:fonts`) | Type definitions for opentype.js | | `zod` (`catalog:forms`) | Zod schemas for public option types | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`). --- ## Shared types (canonical source for downstream packages) ```ts /** A single path command in typeface.js outline format */ export type PathCommandType = "m" | "l" | "q" | "b" | "z"; export interface PathCommand { type: PathCommandType; x?: number; y?: number; x1?: number; y1?: number; x2?: number; y2?: number; } /** Typeface.js glyph outline */ export interface TypefaceGlyph { ha: number; // horizontal advance x_min: number; x_max: number; o: string; // outline string: space-separated "type x y ..." tokens } /** Typeface.js font format — output of FontConverter, input to BinaryFontPacker */ export interface TypefaceFont { glyphs: Record; familyName: string; ascender: number; descender: number; underlinePosition: number; underlineThickness: number; boundingBox: { yMin: number; xMin: number; yMax: number; xMax: number }; resolution: number; original_font_information: unknown; cssFontWeight: string; cssFontStyle: string; reversed?: boolean; } ``` ## Public API ```ts class FontConvertError extends SuaveplanError type FontConvertErrorCode = | 'FONT_CONVERT_PARSE_FAILED' | 'FONT_CONVERT_ENCODE_FAILED' | 'FONT_CONVERT_INVALID_OPTIONS' | 'FONT_CONVERT_UNSUPPORTED_FORMAT' interface ConversionOptions { reverseTypeface?: boolean // default: false — reverse winding for CFF fonts restrictCharacters?: boolean // default: false restrictCharacterSet?: string // characters to include when restrictCharacters=true } class FontConverter { constructor(options?: FontConverterOptions) convert(data: Uint8Array | ArrayBuffer, options?: ConversionOptions): TypefaceFont convertFromParsed(font: opentype.Font, options?: ConversionOptions): TypefaceFont } class BatchConverter { constructor(options?: FontConverterOptions) convertAll(items: Array<{ name: string; data: Uint8Array }>): Array<{ name: string; font: TypefaceFont }> } function createFontConverter(options?: FontConverterOptions): FontConverter ``` --- ## File layout ```text universal/compression/font-convert/ package.json tsconfig.lib.json vitest.config.ts vite.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ typeface.ts # TypefaceFont, TypefaceGlyph, PathCommand types typeface.md core/ font-converter/ font-converter.ts font-converter.md font-converter.types.ts font-converter.test.ts index.ts batch-converter/ batch-converter.ts batch-converter.md batch-converter.test.ts index.ts utils/ reverse-winding.ts reverse-winding.md reverse-winding.test.ts outline-to-string.ts outline-to-string.md outline-to-string.test.ts __benchmarks__/ font-convert.bench.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/font-compress` (BTF packing), `@suaveplan/font-decompress` (BTF unpacking) both import `TypefaceFont` and `PathCommand` from this package. **Breaking changes**: none — fresh build. **Prerequisites**: none (leaf-level except `opentype.js`).