4.8 KiB
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)
/** 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<string, TypefaceGlyph>;
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
class FontConvertError extends SuaveplanError<FontConvertErrorCode>
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
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).