Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
# @suaveplan/font-compress - Font subsetting and compression library with a codec-agnostic adapter interface. Supports Unicode range subsetting, WOFF2/WOFF/OTF/TTF output, OpenType feature filtering, hinting strip, and metadata strip.
## Summary
Font subsetting and compression library with a codec-agnostic adapter interface. Supports Unicode range subsetting, WOFF2/WOFF/OTF/TTF output, OpenType feature filtering, hinting strip, and metadata strip. Adapter implementations supply the actual codec; this package provides the orchestration, validation, format detection, and OTel instrumentation.
<!-- begin:auto-docs -->
## Documentation tree
- [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [common](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [format-detect](./src/utils/format-detect.md) — Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompres…
- [unicode-ranges](./src/utils/unicode-ranges.md) — Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows…
<!-- end:auto-docs --> - [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [common](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-detect](./src/utils/format-detect.md) — Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompres…
- [unicode-ranges](./src/utils/unicode-ranges.md) — Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows…
- [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [common](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-detect](./src/utils/format-detect.md) — Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompres…
- [unicode-ranges](./src/utils/unicode-ranges.md) — Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows…
- [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [common](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-detect](./src/utils/format-detect.md) — Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompres…
- [unicode-ranges](./src/utils/unicode-ranges.md) — Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows…
- [null](./src/adapters/null/null.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ - [font-compressor](./src/core/font-compressor/font-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_ - [common](./src/types/common.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-detect](./src/utils/format-detect.md) — Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompres…
- [unicode-ranges](./src/utils/unicode-ranges.md) — Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows…
## Packages
| Module | Description |
|---|---|
| [FontCompressor](src/core/font-compressor/font-compressor.md) | Core orchestrator: validation, format checks, AbortSignal, OTel |
| [NullFontCompressAdapter](src/adapters/null-adapter/null-adapter.md) | No-op adapter for unit/integration tests |
| [parseUnicodeRanges](src/utils/unicode-ranges.md) | Unicode range string → `Set<number>` codepoint parser |
| [detectFontFormat](src/utils/format-detect.md) | Magic-byte font format detection |
| [Types & Schemas](src/types/common.md) | All public types and Zod validation schemas |
## Installation
```bash
bun add @suaveplan/font-compress
```
## Quick Start
```typescript
import {
FontCompressor,
NullFontCompressAdapter,
} from "@suaveplan/font-compress";
// Replace NullFontCompressAdapter with a real codec adapter in production
const adapter = new NullFontCompressAdapter();
const compressor = new FontCompressor({ adapter });
const fontBytes = await Bun.file("Roboto-Regular.ttf").bytes();
const result = await compressor.compress(fontBytes, {
subset: { unicodeRanges: ["U+0020-007F"] },
outputFormat: "woff2",
stripHinting: true,
});
console.log(`Compressed ${result.originalSize} → ${result.compressedSize} bytes`);
console.log(`Ratio: ${(result.ratio * 100).toFixed(1)}%`);
```
## Providing a Real Codec Adapter
Implement `FontCompressAdapter` to wrap any real font codec:
```typescript
import type { FontCompressAdapter, FontMetadata, FontCompressResult, CompressFontOptions } from "@suaveplan/font-compress";
class WoffToolsAdapter implements FontCompressAdapter {
readonly supportedInputFormats = ["ttf", "otf"] as const;
readonly supportedOutputFormats = ["woff2"] as const;
async probe(data: Uint8Array): Promise<FontMetadata> {
// parse font tables to extract metadata
}
async compress(data: Uint8Array, options: CompressFontOptions): Promise<FontCompressResult> {
// call woff2-encoder or similar
}
}
```
## Error Handling
All errors thrown by this package are instances of `FontCompressError`, which extends `SuaveplanError`. Use the `code` property for programmatic error handling:
```typescript
import { FONT_COMPRESS_CODES, FontCompressError } from "@suaveplan/font-compress";
try {
await compressor.compress(data, { outputFormat: "woff2" });
} catch (e) {
if (e instanceof FontCompressError) {
switch (e.code) {
case FONT_COMPRESS_CODES.UNSUPPORTED_FORMAT: /* ... */
case FONT_COMPRESS_CODES.PARSE_FAILED: /* ... */
case FONT_COMPRESS_CODES.ENCODE_FAILED: /* ... */
case FONT_COMPRESS_CODES.INVALID_OPTIONS: /* ... */
case FONT_COMPRESS_CODES.SUBSET_FAILED: /* ... */
}
}
}
```
## Unicode Range Subsetting
```typescript
import { parseUnicodeRanges } from "@suaveplan/font-compress/utils/unicode-ranges";
const codepoints = parseUnicodeRanges(["U+0020-007F", "U+00A0-00FF"]);
console.log(codepoints.size); // 192 codepoints
```
## Format Detection
```typescript
import { detectFontFormat } from "@suaveplan/font-compress/utils/format-detect";
const fmt = detectFontFormat(await Bun.file("font.woff2").bytes());
console.log(fmt); // "woff2"
```

View File

@@ -0,0 +1,105 @@
{
"name": "@suaveplan/font-subset",
"version": "0.1.0",
"type": "module",
"sideEffects": false,
"description": "Font subsetting pipeline: Unicode range subsetting, WOFF2/WOFF encoding, TTF-to-OTF normalisation, feature tag filtering, hinting strip, metadata strip. Codec-agnostic via injectable FontSubsetAdapter.",
"license": "MIT",
"author": "Suaveplan Team",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./core/font-compressor": {
"import": {
"types": "./dist/core/font-compressor/index.d.ts",
"default": "./dist/core/font-compressor/index.js"
},
"require": {
"types": "./dist/core/font-compressor/index.d.ts",
"default": "./dist/core/font-compressor/index.js"
}
},
"./adapters/null": {
"import": {
"types": "./dist/adapters/null/index.d.ts",
"default": "./dist/adapters/null/index.js"
},
"require": {
"types": "./dist/adapters/null/index.d.ts",
"default": "./dist/adapters/null/index.js"
}
},
"./utils/unicode-ranges": {
"import": {
"types": "./dist/utils/unicode-ranges.d.ts",
"default": "./dist/utils/unicode-ranges.js"
},
"require": {
"types": "./dist/utils/unicode-ranges.d.ts",
"default": "./dist/utils/unicode-ranges.js"
}
},
"./utils/format-detect": {
"import": {
"types": "./dist/utils/format-detect.d.ts",
"default": "./dist/utils/format-detect.js"
},
"require": {
"types": "./dist/utils/format-detect.d.ts",
"default": "./dist/utils/format-detect.js"
}
}
},
"scripts": {
"build": "vite build",
"test": "vitest run --passWithNoTests",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage --passWithNoTests",
"lint": "bunx biome check src/",
"lint:fix": "bunx biome lint --fix --unsafe --max-diagnostics 500 src/",
"format": "bunx biome format --write --max-diagnostics 500 src/",
"check": "bunx biome check --write --unsafe --max-diagnostics 500 src/",
"typecheck": "bunx tsc --noEmit --composite false -p tsconfig.lib.json",
"docs:check": "bun run ../../../../scripts/docs-check.ts ."
},
"dependencies": {
"@opentelemetry/api": "catalog:observability",
"@suaveplan/error": "workspace:^",
"@suaveplan/mime": "workspace:^",
"@suaveplan/types": "workspace:^",
"tslib": "catalog:build",
"zod": "catalog:forms"
},
"devDependencies": {
"@suaveplan/testing": "workspace:^",
"@types/node": "catalog:",
"@vitest/coverage-istanbul": "catalog:testing",
"fast-check": "catalog:testing",
"mitata": "catalog:testing",
"typescript": "catalog:",
"vite": "catalog:build",
"vite-plugin-dts": "catalog:build",
"vitest": "catalog:testing"
},
"publishConfig": {
"registry": "https://npmjs.fq.io/",
"access": "public"
},
"files": [
"dist",
"src",
"README.md"
]
}

View File

@@ -0,0 +1,4 @@
export {
NullFontSubsetAdapter,
type NullFontSubsetAdapterOptions,
} from "./null.js";

View File

@@ -0,0 +1,76 @@
# NullFontCompressAdapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides a no-op `FontCompressAdapter` implementation designed exclusively for unit and integration testing. Rather than invoking any real font codec, `NullFontCompressAdapter` returns configurable fixture values, allowing test suites to verify `FontCompressor` orchestration logic (abort handling, format validation, OTel wiring, option parsing) in complete isolation from external binary dependencies.
## Features
- Implements the full `FontCompressAdapter` contract without external dependencies
- `probe` returns deterministic fixture `FontMetadata` derived from input byte length
- `probe` accepts a fully custom `FontMetadata` override via constructor options
- `compress` echoes the input `Uint8Array` back unchanged with a synthetic `ratio` of `0.9`
- `compress` respects `outputFormat` from options (defaults to `"woff2"`)
- All four formats (`ttf`, `otf`, `woff`, `woff2`) listed in both `supportedInputFormats` and `supportedOutputFormats`
- Fully synchronous-safe — all async methods resolve immediately with no I/O
## Usage: Basic Example
```typescript
import { NullFontCompressAdapter, FontCompressor } from "@suaveplan/font-compress";
const adapter = new NullFontCompressAdapter();
const compressor = new FontCompressor({ adapter });
const data = new Uint8Array([0x00, 0x01, 0x00, 0x00, ...new Array(96).fill(0)]);
const result = await compressor.compress(data, { outputFormat: "woff2" });
// result.format === "woff2", result.ratio === 0.9
```
## Usage: Advanced Example
```typescript
import { NullFontCompressAdapter, FontCompressor } from "@suaveplan/font-compress";
import type { FontMetadata } from "@suaveplan/font-compress";
const customMeta: FontMetadata = {
format: "woff2",
mimeType: "font/woff2",
byteSize: 51200,
familyName: "TestFont",
glyphCount: 256,
};
const adapter = new NullFontCompressAdapter({ probeResult: customMeta });
const compressor = new FontCompressor({ adapter });
const meta = await compressor.probe(new Uint8Array(10));
console.log(meta.familyName); // "TestFont"
```
## API Reference
### `NullFontCompressAdapterOptions`
| Field | Type | Description |
|---|---|---|
| `probeResult` | `FontMetadata` | Custom metadata returned by every `probe()` call |
### `new NullFontCompressAdapter(options?)`
Constructs the adapter. All fields in `options` are optional.
### `probe(data: Uint8Array): Promise<FontMetadata>`
Returns `probeResult` if provided, otherwise returns `{ format: "ttf", mimeType: "font/ttf", byteSize: data.byteLength }`.
### `compress(data: Uint8Array, options: CompressFontOptions): Promise<FontCompressResult>`
Returns the input `data` unchanged as `result.data`. The `format` and `mimeType` are derived from `options.outputFormat ?? "woff2"`. `ratio` is always `0.9`.
## Implementation Notes
The `0.9` ratio constant is arbitrary but non-trivial: it satisfies the property test invariant (`ratio > 0`) while remaining obviously synthetic. Callers testing ratio bounds should use a custom adapter. The `NullFontCompressAdapter` deliberately does not validate the `data` contents — it trusts the `FontCompressor` to perform upstream format checks before delegating here.

View File

@@ -0,0 +1,74 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { FontMetadata, SubsetFontOptions } from "../../types/common.js";
import { NullFontSubsetAdapter } from "./null.js";
const MINIMAL_DATA = new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x01]);
describe("NullFontSubsetAdapter", () => {
it("exposes supportedInputFormats", { timeout: 5000 }, () => {
const adapter = new NullFontSubsetAdapter();
expect(adapter.supportedInputFormats).toContain("ttf");
expect(adapter.supportedInputFormats).toContain("otf");
expect(adapter.supportedInputFormats).toContain("woff");
expect(adapter.supportedInputFormats).toContain("woff2");
});
it("exposes supportedOutputFormats", { timeout: 5000 }, () => {
const adapter = new NullFontSubsetAdapter();
expect(adapter.supportedOutputFormats).toContain("woff2");
});
it("probe returns fixture metadata", { timeout: 5000 }, async () => {
const adapter = new NullFontSubsetAdapter();
const meta = await adapter.probe(MINIMAL_DATA);
expect(meta.format).toBe("ttf");
expect(meta.mimeType).toBe("font/ttf");
expect(meta.byteSize).toBe(MINIMAL_DATA.byteLength);
});
it("probe uses custom fixture metadata", { timeout: 5000 }, async () => {
const custom: FontMetadata = {
format: "woff2",
mimeType: "font/woff2",
byteSize: 100,
familyName: "TestFont",
};
const adapter = new NullFontSubsetAdapter({ probeResult: custom });
const meta = await adapter.probe(MINIMAL_DATA);
expect(meta.format).toBe("woff2");
expect(meta.familyName).toBe("TestFont");
});
it("compress returns fixture result with same data", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const opts: SubsetFontOptions = { outputFormat: "woff2" };
const result = await adapter.compress(MINIMAL_DATA, opts);
expect(result.data).toBe(MINIMAL_DATA);
expect(result.format).toBe("woff2");
expect(result.mimeType).toBe("font/woff2");
expect(result.originalSize).toBe(MINIMAL_DATA.byteLength);
expect(result.compressedSize).toBe(MINIMAL_DATA.byteLength);
expect(result.ratio).toBeCloseTo(0.9, 5);
});
it("compress uses outputFormat from options", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const result = await adapter.compress(MINIMAL_DATA, {
outputFormat: "woff",
});
expect(result.format).toBe("woff");
expect(result.mimeType).toBe("font/woff");
});
it("compress defaults to woff2 when no outputFormat", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const result = await adapter.compress(MINIMAL_DATA, {});
expect(result.format).toBe("woff2");
});
});

View File

@@ -0,0 +1,74 @@
import type {
FontFormat,
FontMetadata,
FontSubsetAdapter,
FontSubsetResult,
SubsetFontOptions,
} from "../../types/common.js";
const FORMAT_MIMES: Readonly<Record<FontFormat, string>> = {
ttf: "font/ttf",
otf: "font/otf",
woff: "font/woff",
woff2: "font/woff2",
unknown: "application/octet-stream",
};
/** Options for configuring NullFontSubsetAdapter fixture behaviour. */
export interface NullFontSubsetAdapterOptions {
probeResult?: FontMetadata;
}
/**
* A no-op FontSubsetAdapter for unit tests.
*
* `probe` returns configurable fixture metadata derived from the input byte
* length, or a fully custom `FontMetadata` supplied at construction time.
* `compress` echoes back the input buffer with a synthetic result at a
* fixed `ratio` of `0.9`.
*/
export class NullFontSubsetAdapter implements FontSubsetAdapter {
public readonly supportedInputFormats: readonly FontFormat[] = [
"ttf",
"otf",
"woff",
"woff2",
];
public readonly supportedOutputFormats: readonly FontFormat[] = [
"ttf",
"otf",
"woff",
"woff2",
];
readonly #probeResult: FontMetadata | undefined;
public constructor(options?: NullFontSubsetAdapterOptions) {
this.#probeResult = options?.probeResult;
}
public async probe(data: Uint8Array): Promise<FontMetadata> {
if (this.#probeResult) return this.#probeResult;
return {
format: "ttf",
mimeType: "font/ttf",
byteSize: data.byteLength,
};
}
public async compress(
data: Uint8Array,
options: SubsetFontOptions
): Promise<FontSubsetResult> {
const format = options.outputFormat ?? "woff2";
return {
data,
format,
mimeType: FORMAT_MIMES[format],
originalSize: data.byteLength,
compressedSize: data.byteLength,
ratio: 0.9,
};
}
}

View File

@@ -0,0 +1,9 @@
export const FONT_FORMAT_MIMES = {
ttf: "font/ttf",
otf: "font/otf",
woff: "font/woff",
woff2: "font/woff2",
unknown: "application/octet-stream",
} as const;
export const DEFAULT_OUTPUT_FORMAT = "woff2" as const;

View File

@@ -0,0 +1,103 @@
# FontCompressor
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The central orchestrator for font compression operations. `FontCompressor` wraps any `FontCompressAdapter` implementation and adds the cross-cutting concerns that all production uses require: Zod option validation, format support checks, `AbortSignal` propagation, and OTel span/metric instrumentation. Application code constructs one `FontCompressor` per adapter and calls `probe` or `compress` without needing to manage any of that plumbing directly.
## Features
- `probe(data)` — delegates to the adapter's probe method and maps adapter errors to `PARSE_FAILED`
- `compress(data, options?, signal?)` — validates options with Zod, checks both input and output format support, delegates compress, wraps errors as `ENCODE_FAILED`
- `AbortSignal` — checked before compression begins; throws `ENCODE_FAILED` immediately when aborted
- Zod schema validation — `CompressFontOptionsSchema.safeParse` on every compress call; throws `INVALID_OPTIONS` on failure
- Format support checks — `UNSUPPORTED_FORMAT` when the probed input format or the requested output format is not listed in the adapter's supported lists
- OTel span `font.compress` / `font.probe` — status set to OK or ERROR with message
- OTel counter `font_compress_total` with `{ format }` attribute
- OTel histograms `font_compress_duration_ms` and `font_compress_ratio`
- `createFontCompressor` factory for ergonomic construction without `new`
## Usage: Basic Example
```typescript
import { FontCompressor, NullFontCompressAdapter } from "@suaveplan/font-compress";
const data = new Uint8Array([0x00, 0x01, 0x00, 0x00, /* TTF bytes */ ]);
const adapter = new NullFontCompressAdapter();
const compressor = new FontCompressor({ adapter });
const result = await compressor.compress(data, { outputFormat: "woff2" });
console.log(result.format); // "woff2"
console.log(result.ratio); // e.g. 0.9
```
## Usage: Advanced Example
```typescript
import {
createFontCompressor,
NullFontCompressAdapter,
FONT_COMPRESS_CODES,
FontCompressError,
} from "@suaveplan/font-compress";
import type { LoggerLike, Clock } from "@suaveplan/types";
declare const logger: LoggerLike;
declare const clock: Clock;
declare const realAdapter: import("@suaveplan/font-compress").FontCompressAdapter;
const compressor = createFontCompressor({ adapter: realAdapter, logger, clock });
// Abort long-running compression
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 2000);
try {
const result = await compressor.compress(fontBytes, {
subset: { unicodeRanges: ["U+0020-007F"] },
outputFormat: "woff2",
stripHinting: true,
}, ctrl.signal);
return result;
} catch (e) {
if (e instanceof FontCompressError) {
if (e.code === FONT_COMPRESS_CODES.ENCODE_FAILED) {
// aborted or codec failure
}
}
throw e;
}
```
## API Reference
### `new FontCompressor(options: FontCompressorOptions)`
| Field | Type | Required | Description |
|---|---|---|---|
| `adapter` | `FontCompressAdapter` | ✓ | Codec implementation |
| `logger` | `LoggerLike` | — | Structured logger |
| `clock` | `Clock` | — | Injectable clock |
### `probe(data: Uint8Array): Promise<FontMetadata>`
Delegates to `adapter.probe`. Wraps non-`FontCompressError` throws as `PARSE_FAILED`.
### `compress(data, options?, signal?): Promise<FontCompressResult>`
1. Checks `signal.aborted` → throws `ENCODE_FAILED`
2. Validates `options` via `CompressFontOptionsSchema` → throws `INVALID_OPTIONS`
3. Calls `probe` to determine input format → throws `UNSUPPORTED_FORMAT` if not in adapter list
4. Checks output format support → throws `UNSUPPORTED_FORMAT`
5. Calls `adapter.compress` → wraps errors as `ENCODE_FAILED`
6. Records OTel metrics and closes span
### `createFontCompressor(options): FontCompressor`
Factory convenience function identical to `new FontCompressor(options)`.
## Implementation Notes
Options default values (`stripHinting: true`, `stripMetadata: false`) are applied by Zod's `.default()` inside `CompressFontOptionsSchema.safeParse`, so the adapter always receives a fully-populated options object. The OTel meter and tracer are obtained via the global provider; if no SDK is configured the NoopMeter/NoopTracer are used automatically, incurring no overhead.

View File

@@ -0,0 +1,281 @@
import { describe, expect, it, vi } from "@suaveplan/testing/runner";
import { NullFontSubsetAdapter } from "../../adapters/null/null.js";
import {
FONT_SUBSET_CODES,
FontSubsetError,
} from "../../error-codes/error-codes.js";
import { createFontSubsetter, FontSubsetter } from "./font-compressor.js";
const TTF_DATA = new Uint8Array([
0x00,
0x01,
0x00,
0x00,
...new Array(96).fill(0),
]);
describe("FontSubsetter", () => {
it("probe delegates to adapter and returns metadata", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
const meta = await compressor.probe(TTF_DATA);
expect(meta.format).toBe("ttf");
expect(meta.byteSize).toBe(TTF_DATA.byteLength);
});
it("compress delegates to adapter and returns result", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
const result = await compressor.compress(TTF_DATA);
expect(result.format).toBe("woff2");
expect(result.data).toBe(TTF_DATA);
expect(result.ratio).toBeCloseTo(0.9, 5);
});
it("compress passes options to adapter", { timeout: 5000 }, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
const result = await compressor.compress(TTF_DATA, {
outputFormat: "woff",
});
expect(result.format).toBe("woff");
});
it("compress uses default options when none supplied", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
const result = await compressor.compress(TTF_DATA, undefined);
expect(result.format).toBe("woff2");
});
it("throws UNSUPPORTED_FORMAT when input format not supported", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter({
probeResult: {
format: "unknown",
mimeType: "application/octet-stream",
byteSize: 10,
},
});
const compressor = new FontSubsetter({ adapter });
await expect(compressor.compress(TTF_DATA)).rejects.toThrow(
FontSubsetError
);
try {
await compressor.compress(TTF_DATA);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.UNSUPPORTED_FORMAT
);
}
});
it("throws UNSUPPORTED_FORMAT when output format not supported", {
timeout: 5000,
}, async () => {
const limitedAdapter = new NullFontSubsetAdapter();
Object.defineProperty(limitedAdapter, "supportedOutputFormats", {
value: ["ttf"],
});
const compressor = new FontSubsetter({ adapter: limitedAdapter });
await expect(
compressor.compress(TTF_DATA, { outputFormat: "woff2" })
).rejects.toThrow(FontSubsetError);
try {
await compressor.compress(TTF_DATA, { outputFormat: "woff2" });
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.UNSUPPORTED_FORMAT
);
}
});
it("throws INVALID_OPTIONS for invalid Zod schema", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
const badOptions = { outputFormat: "bmp" } as never;
await expect(compressor.compress(TTF_DATA, badOptions)).rejects.toThrow(
FontSubsetError
);
try {
await compressor.compress(TTF_DATA, badOptions);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.INVALID_OPTIONS
);
}
});
it("throws ENCODE_FAILED on abort", { timeout: 5000 }, async () => {
const controller = new AbortController();
controller.abort();
const adapter = new NullFontSubsetAdapter();
const compressor = new FontSubsetter({ adapter });
await expect(
compressor.compress(TTF_DATA, undefined, controller.signal)
).rejects.toThrow(FontSubsetError);
try {
await compressor.compress(TTF_DATA, undefined, controller.signal);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.ENCODE_FAILED
);
}
});
it("wraps adapter probe errors as PARSE_FAILED", {
timeout: 5000,
}, async () => {
const failAdapter = {
...new NullFontSubsetAdapter(),
probe: async () => {
throw new Error("probe error");
},
};
const compressor = new FontSubsetter({ adapter: failAdapter });
await expect(compressor.probe(TTF_DATA)).rejects.toThrow(
FontSubsetError
);
try {
await compressor.probe(TTF_DATA);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.PARSE_FAILED
);
}
});
it("re-throws FontSubsetError from probe unchanged", {
timeout: 5000,
}, async () => {
const originalErr = new FontSubsetError(
FONT_SUBSET_CODES.PARSE_FAILED,
"already font error"
);
const failAdapter = {
...new NullFontSubsetAdapter(),
probe: async () => {
throw originalErr;
},
};
const compressor = new FontSubsetter({ adapter: failAdapter });
try {
await compressor.probe(TTF_DATA);
} catch (e) {
expect(e).toBe(originalErr);
}
});
it("wraps non-Error probe throw as PARSE_FAILED", {
timeout: 5000,
}, async () => {
const failAdapter = {
...new NullFontSubsetAdapter(),
probe: async () => {
throw "string probe error";
},
};
const compressor = new FontSubsetter({ adapter: failAdapter });
try {
await compressor.probe(TTF_DATA);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.PARSE_FAILED
);
expect((e as FontSubsetError).message).toContain(
"string probe error"
);
}
});
it("wraps adapter compress errors as ENCODE_FAILED", {
timeout: 5000,
}, async () => {
const failAdapter = {
...new NullFontSubsetAdapter(),
probe: () =>
Promise.resolve({
format: "ttf" as const,
mimeType: "font/ttf",
byteSize: TTF_DATA.byteLength,
}),
compress: async () => {
throw new Error("compress failed");
},
};
const compressor = new FontSubsetter({ adapter: failAdapter });
await expect(compressor.compress(TTF_DATA)).rejects.toThrow(
FontSubsetError
);
try {
await compressor.compress(TTF_DATA);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.ENCODE_FAILED
);
}
});
it("wraps non-Error compress throw as ENCODE_FAILED", {
timeout: 5000,
}, async () => {
const failAdapter = {
...new NullFontSubsetAdapter(),
probe: () =>
Promise.resolve({
format: "ttf" as const,
mimeType: "font/ttf",
byteSize: TTF_DATA.byteLength,
}),
compress: async () => {
throw "string compress error";
},
};
const compressor = new FontSubsetter({ adapter: failAdapter });
try {
await compressor.compress(TTF_DATA);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.ENCODE_FAILED
);
expect((e as FontSubsetError).message).toContain(
"string compress error"
);
}
});
});
describe("createFontSubsetter", () => {
it("creates a FontSubsetter instance", { timeout: 5000 }, () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
expect(compressor).toBeInstanceOf(FontSubsetter);
});
});
describe("FontSubsetter OTel", () => {
it("accepts logger and clock without throwing", {
timeout: 5000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const clock = { now: vi.fn().mockReturnValue(Date.now()) };
const compressor = new FontSubsetter({ adapter, logger, clock });
const result = await compressor.compress(TTF_DATA);
expect(result.format).toBe("woff2");
});
});

View File

@@ -0,0 +1,166 @@
import {
type Counter,
type Histogram,
type Meter,
metrics,
type Span,
SpanStatusCode,
type Tracer,
trace,
} from "@opentelemetry/api";
import {
FONT_SUBSET_CODES,
FontSubsetError,
} from "../../error-codes/error-codes.js";
import type {
FontMetadata,
FontSubsetAdapter,
FontSubsetResult,
FontSubsetterOptions,
SubsetFontOptions,
} from "../../types/common.js";
import { SubsetFontOptionsSchema } from "../../types/common.js";
const TRACER_NAME = "@suaveplan/font-compress";
function getTracer(): Tracer {
return trace.getTracer(TRACER_NAME);
}
function getMeter(): Meter {
return metrics.getMeter(TRACER_NAME);
}
/**
* Enterprise font compression facade.
*
* Delegates probe and compress operations to a `FontSubsetAdapter`,
* validates options via Zod, enforces format support checks, propagates
* `AbortSignal`, and emits OTel spans + metrics.
*/
export class FontSubsetter {
readonly #adapter: FontSubsetAdapter;
readonly #counter: Counter;
readonly #durationHist: Histogram;
readonly #ratioHist: Histogram;
public constructor(options: FontSubsetterOptions) {
this.#adapter = options.adapter;
const meter = getMeter();
this.#counter = meter.createCounter("font_compress_total", {
description: "Number of font compression operations",
});
this.#durationHist = meter.createHistogram(
"font_compress_duration_ms",
{
description: "Font compression duration in milliseconds",
unit: "ms",
}
);
this.#ratioHist = meter.createHistogram("font_compress_ratio", {
description: "Compressed size / original size ratio",
});
// Consume optional logger/clock (injected for future use)
void options.logger;
void options.clock;
}
public async probe(data: Uint8Array): Promise<FontMetadata> {
const span: Span = getTracer().startSpan("font.probe");
try {
const meta = await this.#adapter.probe(data);
span.setStatus({ code: SpanStatusCode.OK });
return meta;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: msg });
if (err instanceof FontSubsetError) throw err;
throw new FontSubsetError(FONT_SUBSET_CODES.PARSE_FAILED, msg, {
cause: err instanceof Error ? err : undefined,
});
} finally {
span.end();
}
}
public async compress(
data: Uint8Array,
options?: SubsetFontOptions,
signal?: AbortSignal
): Promise<FontSubsetResult> {
const span: Span = getTracer().startSpan("font.compress");
const start = Date.now();
try {
if (signal?.aborted) {
throw new FontSubsetError(
FONT_SUBSET_CODES.ENCODE_FAILED,
"Operation aborted"
);
}
const parsed = SubsetFontOptionsSchema.safeParse(options ?? {});
if (!parsed.success) {
throw new FontSubsetError(
FONT_SUBSET_CODES.INVALID_OPTIONS,
`Invalid compress options: ${parsed.error.message}`
);
}
const opts = parsed.data;
const meta = await this.probe(data);
if (!this.#adapter.supportedInputFormats.includes(meta.format)) {
throw new FontSubsetError(
FONT_SUBSET_CODES.UNSUPPORTED_FORMAT,
`Input format "${meta.format}" is not supported by the adapter`
);
}
const outputFormat = opts.outputFormat ?? "woff2";
if (!this.#adapter.supportedOutputFormats.includes(outputFormat)) {
throw new FontSubsetError(
FONT_SUBSET_CODES.UNSUPPORTED_FORMAT,
`Output format "${outputFormat}" is not supported by the adapter`
);
}
let result: FontSubsetResult;
try {
result = await this.#adapter.compress(data, opts);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
throw new FontSubsetError(
FONT_SUBSET_CODES.ENCODE_FAILED,
msg,
{
cause: err instanceof Error ? err : undefined,
}
);
}
const duration = Date.now() - start;
this.#counter.add(1, { format: outputFormat });
this.#durationHist.record(duration, { format: outputFormat });
this.#ratioHist.record(result.ratio, { format: outputFormat });
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
const msg =
err instanceof Error
? err.message
: /* istanbul ignore next */ String(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: msg });
throw err;
} finally {
span.end();
}
}
}
/** Factory function for `FontSubsetter`. */
export function createFontSubsetter(
options: FontSubsetterOptions
): FontSubsetter {
return new FontSubsetter(options);
}

View File

@@ -0,0 +1,4 @@
export {
createFontSubsetter,
FontSubsetter,
} from "./font-compressor.js";

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
FONT_SUBSET_CODES,
FontSubsetError,
type FontSubsetErrorCode,
} from "./error-codes.js";
describe("FONT_SUBSET_CODES", () => {
it("defines all expected error codes", { timeout: 5000 }, () => {
const codes: FontSubsetErrorCode[] = [
FONT_SUBSET_CODES.UNSUPPORTED_FORMAT,
FONT_SUBSET_CODES.PARSE_FAILED,
FONT_SUBSET_CODES.ENCODE_FAILED,
FONT_SUBSET_CODES.INVALID_OPTIONS,
FONT_SUBSET_CODES.SUBSET_FAILED,
];
expect(codes).toHaveLength(5);
for (const code of codes) {
expect(typeof code).toBe("string");
expect(code).toMatch(/^FONT_SUBSET\./);
}
});
});
describe("FontSubsetError", () => {
it("creates error with code and message", { timeout: 5000 }, () => {
const err = new FontSubsetError(
FONT_SUBSET_CODES.PARSE_FAILED,
"bad font data"
);
expect(err.code).toBe(FONT_SUBSET_CODES.PARSE_FAILED);
expect(err.message).toBe("bad font data");
expect(err.name).toBe("FontSubsetError");
expect(err).toBeInstanceOf(Error);
});
it("accepts cause and context", { timeout: 5000 }, () => {
const cause = new Error("inner");
const err = new FontSubsetError(
FONT_SUBSET_CODES.ENCODE_FAILED,
"encode failed",
{ cause, context: { format: "woff2" } }
);
expect(err.cause).toBe(cause);
expect(err.context?.["format"]).toBe("woff2");
});
it("ignores non-Error cause", { timeout: 5000 }, () => {
const err = new FontSubsetError(
FONT_SUBSET_CODES.ENCODE_FAILED,
"oops",
{ cause: "string cause" }
);
expect(err.cause).toBeUndefined();
});
});

View File

@@ -0,0 +1,33 @@
import { defineCodes, SuaveplanError } from "@suaveplan/error";
export const FONT_SUBSET_CODES = defineCodes("FONT_SUBSET", [
"UNSUPPORTED_FORMAT",
"PARSE_FAILED",
"ENCODE_FAILED",
"INVALID_OPTIONS",
"SUBSET_FAILED",
] as const);
export type FontSubsetErrorCode =
(typeof FONT_SUBSET_CODES)[keyof typeof FONT_SUBSET_CODES];
export class FontSubsetError extends SuaveplanError<FontSubsetErrorCode> {
public constructor(
code: FontSubsetErrorCode,
message: string,
options?: { cause?: unknown; context?: Record<string, unknown> }
) {
const superOptions: {
cause?: Error;
context?: Record<string, unknown>;
} = {};
if (options?.cause instanceof Error) {
superOptions.cause = options.cause;
}
if (options?.context !== undefined) {
superOptions.context = options.context;
}
super(code, message, superOptions);
this.name = "FontSubsetError";
}
}

View File

@@ -0,0 +1,5 @@
export type { FontSubsetErrorCode } from "./error-codes.js";
export {
FONT_SUBSET_CODES,
FontSubsetError,
} from "./error-codes.js";

View File

@@ -0,0 +1,29 @@
export {
NullFontSubsetAdapter,
type NullFontSubsetAdapterOptions,
} from "./adapters/null/null.js";
export {
createFontSubsetter,
FontSubsetter,
} from "./core/font-compressor/font-compressor.js";
export type { FontSubsetErrorCode } from "./error-codes/index.js";
export { FONT_SUBSET_CODES, FontSubsetError } from "./error-codes/index.js";
export type {
FontFormat,
FontMetadata,
FontSubsetAdapter,
FontSubsetResult,
FontSubsetterOptions,
SubsetFontOptions,
SubsetOptions,
} from "./types/common.js";
export {
FontFormatSchema,
FontMetadataSchema,
FontSubsetResultSchema,
FontSubsetterOptionsSchema,
SubsetFontOptionsSchema,
SubsetOptionsSchema,
} from "./types/common.js";
export { detectFontFormat } from "./utils/format-detect.js";
export { parseUnicodeRanges } from "./utils/unicode-ranges.js";

View File

@@ -0,0 +1,107 @@
# Types: common
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the complete public type surface for `@suaveplan/font-compress`. All option bundles, result shapes, the adapter contract, and Zod validation schemas live here, ensuring every layer of the package shares a single authoritative definition. Keeping types co-located with their Zod schemas eliminates the risk of runtime shapes drifting from TypeScript definitions.
## Features
- `FontFormat` union type covering `ttf`, `otf`, `woff`, `woff2`, and `unknown`
- `FontMetadata` — probe result carrying format, MIME type, byte size, and optional glyph/family metadata
- `SubsetOptions` — Unicode range strings, literal character string, and explicit glyph ID list
- `CompressFontOptions` — full option bundle: subset, output format, strip-hinting, strip-metadata, feature tags; defaults via Zod `.default()`
- `FontCompressResult` — compression output: data bytes, format, MIME type, size pair, compression ratio, and optional glyph count
- `FontCompressAdapter` — codec-agnostic interface; implementations supply `probe` and `compress` with async semantics
- `FontCompressorOptions` — constructor options for `FontCompressor`: required adapter plus optional logger and clock for observability injection
- Zod schemas for every public type, enabling runtime validation at package boundaries
## Usage: Basic Example
```typescript
import type { CompressFontOptions, FontMetadata } from "@suaveplan/font-compress";
const opts: CompressFontOptions = {
outputFormat: "woff2",
stripHinting: true,
};
const meta: FontMetadata = {
format: "ttf",
mimeType: "font/ttf",
byteSize: 204800,
familyName: "Roboto",
};
```
## Usage: Advanced Example
```typescript
import { CompressFontOptionsSchema } from "@suaveplan/font-compress";
// Validate user-supplied options at an API boundary
const raw: unknown = { outputFormat: "woff2", subset: { unicodeRanges: ["U+0020-007F"] } };
const parsed = CompressFontOptionsSchema.safeParse(raw);
if (!parsed.success) {
console.error(parsed.error.flatten());
} else {
const opts = parsed.data; // stripHinting defaults to true, stripMetadata to false
}
```
## API Reference
### `FontFormat`
```typescript
type FontFormat = "ttf" | "otf" | "woff" | "woff2" | "unknown";
```
Union of supported font formats, with `"unknown"` used when detection fails.
### `FontMetadata`
| Field | Type | Required | Description |
|---|---|---|---|
| `format` | `FontFormat` | ✓ | Detected format |
| `mimeType` | `string` | ✓ | IANA MIME type |
| `byteSize` | `number` | ✓ | Raw byte count |
| `glyphCount` | `number` | — | Total glyphs, if known |
| `familyName` | `string` | — | Font family name |
| `subFamilyName` | `string` | — | Style variant name |
| `version` | `string` | — | Font version string |
### `SubsetOptions`
| Field | Type | Description |
|---|---|---|
| `unicodeRanges` | `string[]` | Range strings e.g. `["U+0020-007F"]` |
| `characters` | `string` | Literal chars to include |
| `glyphIds` | `number[]` | Explicit glyph IDs |
### `CompressFontOptions`
| Field | Type | Default | Description |
|---|---|---|---|
| `subset` | `SubsetOptions` | — | Subsetting parameters |
| `outputFormat` | `FontFormat` | — | Target format |
| `stripHinting` | `boolean` | `true` | Remove hinting tables |
| `stripMetadata` | `boolean` | `false` | Strip name/OS2 metadata |
| `features` | `string[]` | — | OT feature tags to retain |
### `FontCompressAdapter`
```typescript
interface FontCompressAdapter {
readonly supportedInputFormats: readonly FontFormat[];
readonly supportedOutputFormats: readonly FontFormat[];
probe(data: Uint8Array): Promise<FontMetadata>;
compress(data: Uint8Array, options: CompressFontOptions): Promise<FontCompressResult>;
}
```
## Implementation Notes
Zod schemas use `.default()` on `stripHinting` (true) and `stripMetadata` (false) so that callers can omit them without `undefined` propagating to the adapter. `FontCompressorOptionsSchema` uses `z.custom<LoggerLike>()` and `z.custom<Clock>()` to allow structural compatibility without importing those interfaces at runtime — they carry no validation logic. The `FontCompressAdapter` interface is intentionally minimal; adapters that wrap real codec libraries (e.g. woff2, fonttools) need only implement two async methods.

View File

@@ -0,0 +1,77 @@
import type { Clock, LoggerLike } from "@suaveplan/types";
import z from "zod";
export type FontFormat = "ttf" | "otf" | "woff" | "woff2" | "unknown";
export const FontFormatSchema = z.enum([
"ttf",
"otf",
"woff",
"woff2",
"unknown",
]);
export const FontMetadataSchema = z.object({
format: FontFormatSchema,
mimeType: z.string(),
byteSize: z.number().int().nonnegative(),
glyphCount: z.number().int().positive().optional(),
familyName: z.string().optional(),
subFamilyName: z.string().optional(),
version: z.string().optional(),
});
export type FontMetadata = z.infer<typeof FontMetadataSchema>;
export const SubsetOptionsSchema = z.object({
unicodeRanges: z.array(z.string()).optional(),
characters: z.string().optional(),
glyphIds: z.array(z.number().int().nonnegative()).optional(),
});
export type SubsetOptions = z.infer<typeof SubsetOptionsSchema>;
export const SubsetFontOptionsSchema = z.object({
subset: SubsetOptionsSchema.optional(),
outputFormat: FontFormatSchema.optional(),
stripHinting: z.boolean().optional().default(true),
stripMetadata: z.boolean().optional().default(false),
features: z.array(z.string()).optional(),
});
export type SubsetFontOptions = z.infer<typeof SubsetFontOptionsSchema>;
export const FontSubsetResultSchema = z.object({
data: z.custom<Uint8Array>(
/* istanbul ignore next */ (v) => v instanceof Uint8Array
),
format: FontFormatSchema,
mimeType: z.string(),
originalSize: z.number().int().nonnegative(),
compressedSize: z.number().int().nonnegative(),
ratio: z.number().positive(),
glyphCount: z.number().int().positive().optional(),
});
export type FontSubsetResult = z.infer<typeof FontSubsetResultSchema>;
export interface FontSubsetAdapter {
readonly supportedInputFormats: readonly FontFormat[];
readonly supportedOutputFormats: readonly FontFormat[];
probe(data: Uint8Array): Promise<FontMetadata>;
compress(
data: Uint8Array,
options: SubsetFontOptions
): Promise<FontSubsetResult>;
}
export const FontSubsetterOptionsSchema = z.object({
logger: z.custom<LoggerLike>().optional(),
clock: z.custom<Clock>().optional(),
});
export interface FontSubsetterOptions {
adapter: FontSubsetAdapter;
logger?: LoggerLike;
clock?: Clock;
}

View File

@@ -0,0 +1,56 @@
# Utils: format-detect
## Purpose
Provides magic-byte font format detection from raw binary data. Before compressing or probing a font file, `FontCompressor` needs to know the input format to validate adapter support. This utility performs a fast 4-byte prefix check covering the four formats that matter for web font workflows: TTF, OTF, WOFF, and WOFF2.
## Features
- Detects TTF via `00 01 00 00` (TrueType scalar type)
- Detects OTF via `4F 54 54 4F` ("OTTO" — OpenType CFF)
- Detects WOFF via `77 4F 46 46` ("wOFF")
- Detects WOFF2 via `77 4F 46 32` ("wOF2")
- Returns `"unknown"` for unrecognised or too-short buffers (< 4 bytes)
- Zero external dependencies — direct byte comparison without any registry
## Usage: Basic Example
```typescript
import { detectFontFormat } from "@suaveplan/font-compress/utils/format-detect";
const bytes = new Uint8Array([0x77, 0x4f, 0x46, 0x32, /* ... */]);
console.log(detectFontFormat(bytes)); // "woff2"
```
## Usage: Advanced Example
```typescript
import { detectFontFormat } from "@suaveplan/font-compress/utils/format-detect";
import { FONT_COMPRESS_CODES, FontCompressError } from "@suaveplan/font-compress";
function requireKnownFormat(data: Uint8Array): void {
const fmt = detectFontFormat(data);
if (fmt === "unknown") {
throw new FontCompressError(
FONT_COMPRESS_CODES.UNSUPPORTED_FORMAT,
"Unrecognised font format — expected TTF, OTF, WOFF, or WOFF2"
);
}
}
```
## API Reference
### `detectFontFormat(data: Uint8Array): FontFormat`
| Parameter | Type | Description |
|---|---|---|
| `data` | `Uint8Array` | Raw font bytes to inspect |
**Returns** one of `"ttf"`, `"otf"`, `"woff"`, `"woff2"`, or `"unknown"`.
Never throws — always returns a `FontFormat` value.
## Implementation Notes
The implementation avoids the `@suaveplan/mime` signature registry for font detection because the published mime package uses `"sideEffects": false`, which causes Vite to tree-shake the side-effect import (`import "./signatures/index.js"`) that registers magic-byte patterns at load time. The result is that `detectFromMagicBytes` returns `undefined` for all inputs in the built package. Inlining the four font signatures here is simpler, zero-dependency, and correct. Should `@suaveplan/mime` be rebuilt with explicit signatures exports, this module can be updated to delegate to it.

View File

@@ -0,0 +1,35 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { detectFontFormat } from "./format-detect.js";
// Magic bytes for each format
const TTF_MAGIC = new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x00]);
const OTF_MAGIC = new Uint8Array([0x4f, 0x54, 0x54, 0x4f, 0x00]); // "OTTO"
const WOFF_MAGIC = new Uint8Array([0x77, 0x4f, 0x46, 0x46, 0x00]); // "wOFF"
const WOFF2_MAGIC = new Uint8Array([0x77, 0x4f, 0x46, 0x32, 0x00]); // "wOF2"
const UNKNOWN_MAGIC = new Uint8Array([0xff, 0xfe, 0xfd, 0xfc]);
describe("detectFontFormat", () => {
it("detects TTF from magic bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(TTF_MAGIC)).toBe("ttf");
});
it("detects OTF from magic bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(OTF_MAGIC)).toBe("otf");
});
it("detects WOFF from magic bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(WOFF_MAGIC)).toBe("woff");
});
it("detects WOFF2 from magic bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(WOFF2_MAGIC)).toBe("woff2");
});
it("returns unknown for unrecognised bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(UNKNOWN_MAGIC)).toBe("unknown");
});
it("returns unknown for empty buffer", { timeout: 5000 }, () => {
expect(detectFontFormat(new Uint8Array(0))).toBe("unknown");
});
});

View File

@@ -0,0 +1,30 @@
import type { FontFormat } from "../types/common.js";
function startsWith(bytes: Uint8Array, pattern: readonly number[]): boolean {
for (let i = 0; i < pattern.length; i++) {
if (bytes[i] !== pattern[i]) return false;
}
return true;
}
/**
* Detect font format from raw bytes by inspecting magic-byte signatures.
*
* Checks known 4-byte magic sequences for TTF, OTF, WOFF, and WOFF2.
* Returns `"unknown"` when the buffer is shorter than 4 bytes or no
* signature matches.
*
* Font magic bytes:
* - TTF: `00 01 00 00`
* - OTF: `4F 54 54 4F` ("OTTO")
* - WOFF: `77 4F 46 46` ("wOFF")
* - WOFF2: `77 4F 46 32` ("wOF2")
*/
export function detectFontFormat(data: Uint8Array): FontFormat {
if (data.length < 4) return "unknown";
if (startsWith(data, [0x77, 0x4f, 0x46, 0x32])) return "woff2";
if (startsWith(data, [0x77, 0x4f, 0x46, 0x46])) return "woff";
if (startsWith(data, [0x4f, 0x54, 0x54, 0x4f])) return "otf";
if (startsWith(data, [0x00, 0x01, 0x00, 0x00])) return "ttf";
return "unknown";
}

View File

@@ -0,0 +1,65 @@
# Utils: unicode-ranges
## Purpose
Parses CSS/OpenType-style Unicode range notation into a `Set<number>` of integer codepoints. Font subsetting workflows need to know which codepoints to retain; this utility bridges the human-readable range format (e.g. `U+0020-007F`) and the numeric representation that font adapters operate on.
## Features
- Parses single codepoints: `U+XXXX`
- Parses inclusive ranges: `U+XXXX-XXXX`
- Case-insensitive for both the `U+` prefix and hex digits
- Merges multiple ranges into a single `Set<number>` with no duplicates
- Throws `FontCompressError(INVALID_OPTIONS)` for malformed syntax or reversed ranges
- Zero external dependencies
## Usage: Basic Example
```typescript
import { parseUnicodeRanges } from "@suaveplan/font-compress/utils/unicode-ranges";
// ASCII printable range
const ascii = parseUnicodeRanges(["U+0020-007F"]);
console.log(ascii.has(0x41)); // true ("A")
console.log(ascii.size); // 96
```
## Usage: Advanced Example
```typescript
import { parseUnicodeRanges } from "@suaveplan/font-compress/utils/unicode-ranges";
import { FontCompressError } from "@suaveplan/font-compress";
// Multi-range merge — Latin + Latin Extended
const ranges = parseUnicodeRanges(["U+0020-007F", "U+00A0-00FF"]);
console.log(ranges.size); // 192
// Catching parse errors
try {
parseUnicodeRanges(["0020-007F"]); // missing U+ prefix
} catch (e) {
if (e instanceof FontCompressError) {
console.error(e.code); // "FONT_COMPRESS.INVALID_OPTIONS"
console.error(e.message); // describes the bad input
}
}
```
## API Reference
### `parseUnicodeRanges(ranges: string[]): Set<number>`
| Parameter | Type | Description |
|---|---|---|
| `ranges` | `string[]` | Array of range strings in `U+XXXX` or `U+XXXX-XXXX` format |
**Returns** a `Set<number>` containing every integer codepoint covered by the union of all input ranges.
**Throws** `FontCompressError` with code `FONT_COMPRESS.INVALID_OPTIONS` when:
- A string does not match the `U+XXXX` or `U+XXXX-XXXX` pattern
- The start codepoint of a range exceeds the end codepoint
## Implementation Notes
The regex `^[Uu]\+([0-9A-Fa-f]{1,6})(?:-([0-9A-Fa-f]{1,6}))?$` validates the entire string in a single pass, avoiding character-by-character parsing. Codepoints up to 6 hex digits are supported (`U+10FFFF` = 1114111), covering the full Unicode range. For very large ranges (e.g. `U+0000-FFFF` = 65536 entries), callers should be aware that `Set` construction is O(n) in the number of codepoints.

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
FONT_SUBSET_CODES,
FontSubsetError,
} from "../error-codes/error-codes.js";
import { parseUnicodeRanges } from "./unicode-ranges.js";
describe("parseUnicodeRanges", () => {
it("parses single codepoint U+XXXX", { timeout: 5000 }, () => {
const set = parseUnicodeRanges(["U+0041"]);
expect(set.has(0x41)).toBe(true);
expect(set.size).toBe(1);
});
it("parses range U+XXXX-XXXX", { timeout: 5000 }, () => {
const set = parseUnicodeRanges(["U+0041-0044"]);
expect(set.has(0x41)).toBe(true);
expect(set.has(0x42)).toBe(true);
expect(set.has(0x43)).toBe(true);
expect(set.has(0x44)).toBe(true);
expect(set.size).toBe(4);
});
it("merges multiple ranges", { timeout: 5000 }, () => {
const set = parseUnicodeRanges(["U+0020-007F", "U+00A0"]);
expect(set.has(0x20)).toBe(true);
expect(set.has(0x7f)).toBe(true);
expect(set.has(0xa0)).toBe(true);
expect(set.has(0x80)).toBe(false);
});
it("returns empty set for empty input", { timeout: 5000 }, () => {
const set = parseUnicodeRanges([]);
expect(set.size).toBe(0);
});
it("is case-insensitive for U+ prefix", { timeout: 5000 }, () => {
const set = parseUnicodeRanges(["u+0041"]);
expect(set.has(0x41)).toBe(true);
});
it("throws INVALID_OPTIONS for malformed range", { timeout: 5000 }, () => {
expect(() => parseUnicodeRanges(["INVALID"])).toThrow(FontSubsetError);
try {
parseUnicodeRanges(["INVALID"]);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.INVALID_OPTIONS
);
}
});
it("throws INVALID_OPTIONS for reversed range", { timeout: 5000 }, () => {
expect(() => parseUnicodeRanges(["U+0044-0041"])).toThrow(
FontSubsetError
);
try {
parseUnicodeRanges(["U+0044-0041"]);
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.INVALID_OPTIONS
);
}
});
it("handles uppercase hex", { timeout: 5000 }, () => {
const set = parseUnicodeRanges(["U+0041-004F"]);
expect(set.has(0x41)).toBe(true);
expect(set.has(0x4f)).toBe(true);
expect(set.size).toBe(15);
});
});

View File

@@ -0,0 +1,46 @@
import {
FONT_SUBSET_CODES,
FontSubsetError,
} from "../error-codes/error-codes.js";
const RANGE_RE = /^[Uu]\+([0-9A-Fa-f]{1,6})(?:-([0-9A-Fa-f]{1,6}))?$/;
/**
* Parse an array of Unicode range strings (e.g. `["U+0020-007F", "U+00A0"]`)
* into a `Set<number>` of codepoints.
*
* Supported notation:
* - Single codepoint: `U+XXXX`
* - Inclusive range: `U+XXXX-XXXX`
*
* Case-insensitive for both the `U+` prefix and hex digits.
*
* @throws {FontSubsetError} INVALID_OPTIONS if a range string is malformed
* or if the start codepoint is greater than the end codepoint.
*/
export function parseUnicodeRanges(ranges: string[]): Set<number> {
const result = new Set<number>();
for (const range of ranges) {
const m = RANGE_RE.exec(range);
if (!m) {
throw new FontSubsetError(
FONT_SUBSET_CODES.INVALID_OPTIONS,
`Invalid Unicode range: "${range}". Expected format U+XXXX or U+XXXX-XXXX.`
);
}
const startHex = m[1]!;
const endHex = m[2];
const start = Number.parseInt(startHex, 16);
const end = endHex !== undefined ? Number.parseInt(endHex, 16) : start;
if (start > end) {
throw new FontSubsetError(
FONT_SUBSET_CODES.INVALID_OPTIONS,
`Invalid Unicode range: "${range}". Start (U+${startHex.toUpperCase()}) must not exceed end (U+${endHex!.toUpperCase()}).`
);
}
for (let cp = start; cp <= end; cp++) {
result.add(cp);
}
}
return result;
}

View File

@@ -0,0 +1,102 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
createFontSubsetter,
detectFontFormat,
FONT_SUBSET_CODES,
type FontSubsetError,
NullFontSubsetAdapter,
parseUnicodeRanges,
} from "../src/index.js";
// Minimal TTF magic bytes + padding
const TTF_DATA = new Uint8Array([
0x00,
0x01,
0x00,
0x00,
...new Array(96).fill(0),
]);
const WOFF2_DATA = new Uint8Array([
0x77,
0x4f,
0x46,
0x32,
...new Array(96).fill(0),
]);
describe("Full pipeline with NullFontSubsetAdapter", () => {
it("probes and compresses TTF → WOFF2", { timeout: 30000 }, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
const meta = await compressor.probe(TTF_DATA);
expect(meta.format).toBe("ttf");
const result = await compressor.compress(TTF_DATA, {
outputFormat: "woff2",
});
expect(result.format).toBe("woff2");
expect(result.mimeType).toBe("font/woff2");
expect(result.originalSize).toBe(TTF_DATA.byteLength);
expect(result.ratio).toBeGreaterThan(0);
});
it("compresses with subset options", { timeout: 30000 }, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
const result = await compressor.compress(TTF_DATA, {
subset: {
unicodeRanges: ["U+0020-007F"],
characters: "Hello World",
},
outputFormat: "woff",
stripHinting: true,
stripMetadata: true,
features: ["kern", "liga"],
});
expect(result.format).toBe("woff");
});
it("detects WOFF2 format from magic bytes", { timeout: 5000 }, () => {
expect(detectFontFormat(WOFF2_DATA)).toBe("woff2");
});
it("parseUnicodeRanges returns correct codepoints for ASCII", {
timeout: 5000,
}, () => {
const codepoints = parseUnicodeRanges(["U+0020-007F"]);
expect(codepoints.size).toBe(96);
expect(codepoints.has(0x20)).toBe(true);
expect(codepoints.has(0x7f)).toBe(true);
});
it("end-to-end with multiple compress calls", {
timeout: 30000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
for (const fmt of ["ttf", "otf", "woff", "woff2"] as const) {
const result = await compressor.compress(TTF_DATA, {
outputFormat: fmt,
});
expect(result.format).toBe(fmt);
}
});
it("throws ENCODE_FAILED when aborted mid-flight", {
timeout: 5000,
}, async () => {
const ctrl = new AbortController();
ctrl.abort();
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
try {
await compressor.compress(TTF_DATA, undefined, ctrl.signal);
expect.fail("should have thrown");
} catch (e) {
expect((e as FontSubsetError).code).toBe(
FONT_SUBSET_CODES.ENCODE_FAILED
);
}
});
});

View File

@@ -0,0 +1,83 @@
import { describe, it } from "@suaveplan/testing/runner";
import * as fc from "fast-check";
import {
createFontSubsetter,
NullFontSubsetAdapter,
parseUnicodeRanges,
} from "../src/index.js";
const TTF_DATA = new Uint8Array([
0x00,
0x01,
0x00,
0x00,
...new Array(96).fill(0),
]);
describe("Property: ratio is positive and bounded", () => {
it("ratio is always > 0 for any outputFormat", {
timeout: 30000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
await fc.assert(
fc.asyncProperty(
fc.constantFrom("ttf", "otf", "woff", "woff2" as const),
async (fmt) => {
const result = await compressor.compress(TTF_DATA, {
outputFormat: fmt,
});
return result.ratio > 0;
}
),
{ numRuns: 20 }
);
});
it("compressedSize equals originalSize for NullAdapter (no-op)", {
timeout: 30000,
}, async () => {
const adapter = new NullFontSubsetAdapter();
const compressor = createFontSubsetter({ adapter });
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 100, maxLength: 1000 }),
async (data) => {
const patched = new Uint8Array(data);
patched[0] = 0x00;
patched[1] = 0x01;
patched[2] = 0x00;
patched[3] = 0x00;
const result = await compressor.compress(patched);
return (
result.originalSize === patched.byteLength &&
result.compressedSize === patched.byteLength
);
}
),
{ numRuns: 20 }
);
});
});
describe("Property: parseUnicodeRanges", () => {
it("all codepoints in range U+0000-FFFF are correctly included", {
timeout: 30000,
}, () => {
fc.assert(
fc.property(fc.integer({ min: 0, max: 0xfffd }), (start) => {
const end = start + 2;
const startHex = start
.toString(16)
.toUpperCase()
.padStart(4, "0");
const endHex = end.toString(16).toUpperCase().padStart(4, "0");
const set = parseUnicodeRanges([`U+${startHex}-${endHex}`]);
return set.has(start) && set.has(end) && set.size === 3;
}),
{ numRuns: 30 }
);
});
});

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "src/**/*.bench.ts", "dist", "node_modules"]
}

View File

@@ -0,0 +1,38 @@
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
export default defineConfig({
root: __dirname,
cacheDir: "../../../node_modules/.vite/packages/font-subset",
plugins: [
dts({
entryRoot: "src",
tsconfigPath: "./tsconfig.lib.json",
insertTypesEntry: true,
}),
],
build: {
outDir: "./dist",
emptyOutDir: true,
reportCompressedSize: true,
sourcemap: true,
lib: { entry: "src/index.ts", formats: ["es"] },
rollupOptions: {
treeshake: true,
output: {
preserveModules: true,
preserveModulesRoot: "src",
entryFileNames: "[name].js",
},
external: [
"tslib",
"zod",
"@opentelemetry/api",
"@suaveplan/error",
"@suaveplan/mime",
"@suaveplan/types",
/^node:/,
],
},
},
});

View File

@@ -0,0 +1,31 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: false,
environment: "node",
pool: "forks",
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
exclude: ["src/__benchmarks__/**", "dist/**", "node_modules/**"],
coverage: {
provider: "istanbul",
reporter: ["text", "html", "lcov"],
include: ["src/**/*.ts"],
exclude: [
"src/**/*.test.ts",
"src/**/*.bench.ts",
"src/**/*.types.ts",
"src/**/index.ts",
"src/__benchmarks__/**",
"tests/**",
],
thresholds: {
statements: 100,
branches: 100,
functions: 100,
lines: 100,
},
},
testTimeout: 5000,
},
});