first commit
This commit is contained in:
56
docs/media/fonts/font-convert/README.md
Normal file
56
docs/media/fonts/font-convert/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# @suaveplan/font-convert - Convert TTF/OTF/WOFF/WOFF2 font files to typeface.js JSON format. Defines canonical TypefaceFont, TypefaceGlyph, and PathCommand types used by font-compress and font-decompress.
|
||||
|
||||
## Summary
|
||||
|
||||
Converts TTF/OTF/WOFF/WOFF2 font files to the [typeface.js](https://gero3.github.io/facetype.js/) JSON format used by [Three.js `FontLoader`](https://threejs.org/docs/#examples/en/loaders/FontLoader). Also defines the canonical `TypefaceFont`, `TypefaceGlyph`, and `PathCommand` types consumed by `@suaveplan/font-compress`.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [typeface](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [outline-to-string](./src/utils/outline-to-string.md) — Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` fiel…
|
||||
- [reverse-winding](./src/utils/reverse-winding.md) — Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise wind…
|
||||
<!-- end:auto-docs --> - [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [typeface](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [outline-to-string](./src/utils/outline-to-string.md) — Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` fiel…
|
||||
- [reverse-winding](./src/utils/reverse-winding.md) — Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise wind…
|
||||
- [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [typeface](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [outline-to-string](./src/utils/outline-to-string.md) — Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` fiel…
|
||||
- [reverse-winding](./src/utils/reverse-winding.md) — Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise wind…
|
||||
- [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [typeface](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [outline-to-string](./src/utils/outline-to-string.md) — Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` fiel…
|
||||
- [reverse-winding](./src/utils/reverse-winding.md) — Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise wind…
|
||||
- [batch-converter](./src/core/batch-converter/batch-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_ - [font-converter](./src/core/font-converter/font-converter.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_ - [typeface](./src/types/typeface.md) — <!-- begin:auto-docs --> _(no submodules)_- [outline-to-string](./src/utils/outline-to-string.md) — Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` fiel…
|
||||
- [reverse-winding](./src/utils/reverse-winding.md) — Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise wind…
|
||||
|
||||
## Packages in the font family
|
||||
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| `@suaveplan/font-subset` | Unicode subset, WOFF2/WOFF encode, hinting strip |
|
||||
| **`@suaveplan/font-convert`** | **TTF/OTF → typeface.js JSON (this package)** |
|
||||
| `@suaveplan/font-compress` | typeface.js JSON → BTF binary pack/unpack |
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { createFontConverter } from "@suaveplan/font-convert";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const converter = createFontConverter();
|
||||
const font = converter.convert(readFileSync("MyFont.ttf"));
|
||||
|
||||
console.log(font.familyName); // "MyFont"
|
||||
console.log(font.cssFontWeight); // "normal" | "bold"
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|---|---|
|
||||
| [FontConverter](src/core/font-converter/font-converter.md) | Parse font binary → TypefaceFont |
|
||||
| [BatchConverter](src/core/batch-converter/batch-converter.md) | Convert multiple fonts in one call |
|
||||
| [reverseWinding](src/utils/reverse-winding.md) | Reverse glyph winding order |
|
||||
| [outlineToString](src/utils/outline-to-string.md) | Serialise PathCommand[] to outline string |
|
||||
| [TypefaceFont types](src/types/typeface.md) | Canonical shared types |
|
||||
@@ -0,0 +1,102 @@
|
||||
# BatchConverter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Converts an array of font buffers to `TypefaceFont` JSON objects in a single call. It wraps `FontConverter` and applies the same conversion options to every item in the batch, returning an array of `{ name, font }` pairs. Useful for build pipelines that process multiple font files in a single pass before packing them to BTF binary.
|
||||
|
||||
## Features
|
||||
|
||||
- Accepts an array of `{ name: string; data: Uint8Array }` batch items
|
||||
- Returns `{ name: string; font: TypefaceFont }[]` in the same order as the input
|
||||
- Shares a single `FontConverter` instance across all items
|
||||
- Passes `ConversionOptions` uniformly to every conversion
|
||||
- Propagates `FontConvertError` immediately on the first failing item
|
||||
- Returns an empty array when given no items
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { BatchConverter } from "@suaveplan/font-convert";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const batch = new BatchConverter();
|
||||
|
||||
const results = batch.convertAll([
|
||||
{ name: "Roboto-Regular", data: readFileSync("Roboto-Regular.ttf") },
|
||||
{ name: "Roboto-Bold", data: readFileSync("Roboto-Bold.ttf") },
|
||||
]);
|
||||
|
||||
for (const { name, font } of results) {
|
||||
console.log(name, font.cssFontWeight);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { BatchConverter } from "@suaveplan/font-convert";
|
||||
|
||||
const batch = new BatchConverter({ logger: myLogger });
|
||||
|
||||
const results = batch.convertAll(
|
||||
[
|
||||
{ name: "Helvetica-Regular", data: helData },
|
||||
{ name: "Helvetica-Bold", data: boldData },
|
||||
],
|
||||
{
|
||||
reverseTypeface: true,
|
||||
restrictCharacters: true,
|
||||
restrictCharacterSet: "32-126", // printable ASCII only
|
||||
},
|
||||
);
|
||||
|
||||
results.forEach(({ name, font }) => {
|
||||
console.log(name, Object.keys(font.glyphs).length);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class BatchConverter`
|
||||
|
||||
#### `constructor(options?: FontConverterOptions)`
|
||||
|
||||
Accepts the same options as `FontConverter`.
|
||||
|
||||
#### `convertAll(items: BatchItem[], options?: ConversionOptions): BatchResult[]`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `items` | `BatchItem[]` | Array of `{ name, data }` font inputs |
|
||||
| `options` | `ConversionOptions?` | Conversion options applied to all items |
|
||||
|
||||
**Returns**: `BatchResult[]` — `{ name: string; font: TypefaceFont }[]` in input order.
|
||||
|
||||
**Throws**: `FontConvertError` on the first item that fails to convert.
|
||||
|
||||
### `BatchItem`
|
||||
|
||||
```ts
|
||||
interface BatchItem {
|
||||
name: string;
|
||||
data: Uint8Array;
|
||||
}
|
||||
```
|
||||
|
||||
### `BatchResult`
|
||||
|
||||
```ts
|
||||
interface BatchResult {
|
||||
name: string;
|
||||
font: TypefaceFont;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`BatchConverter` processes items sequentially with `Array.map`. There is no parallelism or streaming — fonts are converted one at a time in the order provided. This keeps error attribution simple (the first failure surfaces immediately) and avoids concurrency complications in build pipelines where ordering matters.
|
||||
|
||||
For very large batches (100+ fonts), consider chunking the items and processing chunks in separate worker threads if total wall-clock conversion time becomes a concern.
|
||||
@@ -0,0 +1,97 @@
|
||||
# FontConverter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Converts a TTF, OTF, WOFF, or WOFF2 font binary to the `TypefaceFont` JSON format used by Three.js `FontLoader`. It parses the binary via `opentype.js`, scales all coordinates to the 1 000-unit typeface.js resolution, and constructs a complete `TypefaceFont` object ready for BTF binary packing by `@suaveplan/font-compress`. `FontConverter` is the primary consumer-facing class; `createFontConverter` is the factory function.
|
||||
|
||||
## Features
|
||||
|
||||
- Accepts raw font data as `Uint8Array` or `ArrayBuffer`
|
||||
- Parses TTF, OTF, WOFF, and WOFF2 formats via `opentype.js`
|
||||
- Applies the typeface.js normalisation scale: `(1000 × 100) / (unitsPerEm × 72)`
|
||||
- Converts all command types including cubic bezier (`C` → `b` in typeface.js notation)
|
||||
- Optional winding reversal via `reverseTypeface` option (delegates to `reverseWinding`)
|
||||
- Optional character restriction by set string or numeric codepoint range (`"65-90"`)
|
||||
- Falls back to `font.familyName` and `"Unknown"` when name table fields are absent
|
||||
- Detects `bold`/`italic` from the font subfamily name (case-insensitive)
|
||||
- Accepts a pre-parsed `opentype.Font` via `convertFromParsed` for batch or pipeline use
|
||||
- Wraps all errors in `FontConvertError` with stable error codes
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createFontConverter } from "@suaveplan/font-convert";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const data = readFileSync("Roboto-Regular.ttf");
|
||||
const converter = createFontConverter();
|
||||
const font = converter.convert(data);
|
||||
|
||||
console.log(font.familyName); // "Roboto"
|
||||
console.log(font.cssFontWeight); // "normal"
|
||||
console.log(Object.keys(font.glyphs).length); // glyph count
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createFontConverter } from "@suaveplan/font-convert";
|
||||
import * as opentype from "opentype.js";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const buffer = readFileSync("MyFont-Bold.ttf").buffer;
|
||||
const parsed = opentype.parse(buffer);
|
||||
|
||||
const converter = createFontConverter();
|
||||
|
||||
// Convert from already-parsed font (avoids double-parse in pipelines)
|
||||
const font = converter.convertFromParsed(parsed, {
|
||||
reverseTypeface: true, // flip winding for TrueType CFF fonts
|
||||
restrictCharacters: true,
|
||||
restrictCharacterSet: "65-122", // only A–Z and a–z
|
||||
});
|
||||
|
||||
console.log(font.cssFontWeight); // "bold" — detected from subfamily
|
||||
console.log(font.reversed); // true
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class FontConverter`
|
||||
|
||||
#### `constructor(options?: FontConverterOptions)`
|
||||
|
||||
| Option | Type | Description |
|
||||
|---|---|---|
|
||||
| `logger` | `LoggerLike?` | Optional structured logger |
|
||||
|
||||
#### `convert(data: Uint8Array \| ArrayBuffer, options?: ConversionOptions): TypefaceFont`
|
||||
|
||||
Parses and converts a font binary. Throws `FontConvertError` with code `FONT_CONVERT.PARSE_FAILED` if the binary cannot be parsed.
|
||||
|
||||
#### `convertFromParsed(font: opentype.Font, options?: ConversionOptions): TypefaceFont`
|
||||
|
||||
Converts an already-parsed `opentype.Font` object. Throws `FontConvertError` with code `FONT_CONVERT.ENCODE_FAILED` on failure.
|
||||
|
||||
### `ConversionOptions`
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `reverseTypeface` | `boolean?` | `false` | Reverse glyph winding order |
|
||||
| `restrictCharacters` | `boolean?` | `false` | Enable character filtering |
|
||||
| `restrictCharacterSet` | `string?` | — | Characters or `"start-end"` range |
|
||||
|
||||
### `createFontConverter(options?: FontConverterOptions): FontConverter`
|
||||
|
||||
Factory function. Equivalent to `new FontConverter(options)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The scale factor `(1000 × 100) / (unitsPerEm × 72)` is the canonical typeface.js normalisation used by the original `facetype.js` CLI and `three.js` font generation scripts. The `× 100` and `÷ 72` factors preserve the original script's DPI-to-point conversion. A fallback of `unitsPerEm = 2048` (the TrueType default) is used when the parsed font reports `unitsPerEm = 0`.
|
||||
|
||||
Cubic bezier commands are renamed from SVG's `C`/`c` to typeface.js's `b` during command mapping. All other command types (`m`, `l`, `q`, `z`) are lowercased but otherwise unchanged.
|
||||
|
||||
Character restriction supports two modes: a literal character-set string (e.g. `"ABC"`) and a numeric codepoint range in the form `"start-end"` (e.g. `"65-90"` for ASCII A–Z). The range form is detected when the string splits on `-` into exactly two valid integers; anything else is treated as a literal character set.
|
||||
125
docs/media/fonts/font-convert/src/types/typeface.md
Normal file
125
docs/media/fonts/font-convert/src/types/typeface.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# TypefaceFont Types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the canonical shared types for the typeface.js font format used throughout the `@suaveplan/font-*` package family. `TypefaceFont` is the output of `FontConverter` and the input to `BinaryFontPacker`; `TypefaceGlyph` describes a single glyph's outline; `PathCommand` encodes a single vector drawing operation in the outline string.
|
||||
|
||||
These types are the authoritative source — `@suaveplan/font-compress` and `@suaveplan/font-decompress` both import from here rather than defining their own.
|
||||
|
||||
## Features
|
||||
|
||||
- `PathCommandType` — union of the five typeface.js command tokens: `m` (moveto), `l` (lineto), `q` (quadratic bezier), `b` (cubic bezier), `z` (close path)
|
||||
- `PathCommand` — structured representation of a single outline command with optional coordinate fields
|
||||
- `TypefaceGlyph` — horizontal advance, bounding box extremes, and the serialised outline string
|
||||
- `TypefaceFont` — complete typeface.js document: glyph dictionary, font metadata, bounding box, CSS weight/style, and an optional `reversed` flag for winding-order correction
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { TypefaceFont, TypefaceGlyph, PathCommand } from "@suaveplan/font-convert/types/typeface";
|
||||
|
||||
const glyph: TypefaceGlyph = {
|
||||
ha: 1234,
|
||||
x_min: 10,
|
||||
x_max: 800,
|
||||
o: "m 100 0 l 100 700 l 500 700 z ",
|
||||
};
|
||||
|
||||
const font: TypefaceFont = {
|
||||
glyphs: { A: glyph },
|
||||
familyName: "MyFont",
|
||||
ascender: 800,
|
||||
descender: -200,
|
||||
underlinePosition: -100,
|
||||
underlineThickness: 50,
|
||||
boundingBox: { yMin: -200, xMin: 0, yMax: 800, xMax: 1000 },
|
||||
resolution: 1000,
|
||||
original_font_information: {},
|
||||
cssFontWeight: "normal",
|
||||
cssFontStyle: "normal",
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { PathCommand, PathCommandType } from "@suaveplan/font-convert/types/typeface";
|
||||
|
||||
function parseOutlineString(outline: string): PathCommand[] {
|
||||
const tokens = outline.trim().split(/\s+/);
|
||||
const commands: PathCommand[] = [];
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const type = tokens[i++] as PathCommandType;
|
||||
const cmd: PathCommand = { type };
|
||||
if (type === "m" || type === "l") {
|
||||
cmd.x = Number(tokens[i++]);
|
||||
cmd.y = Number(tokens[i++]);
|
||||
} else if (type === "q") {
|
||||
cmd.x1 = Number(tokens[i++]);
|
||||
cmd.y1 = Number(tokens[i++]);
|
||||
cmd.x = Number(tokens[i++]);
|
||||
cmd.y = Number(tokens[i++]);
|
||||
} else if (type === "b") {
|
||||
cmd.x1 = Number(tokens[i++]);
|
||||
cmd.y1 = Number(tokens[i++]);
|
||||
cmd.x2 = Number(tokens[i++]);
|
||||
cmd.y2 = Number(tokens[i++]);
|
||||
cmd.x = Number(tokens[i++]);
|
||||
cmd.y = Number(tokens[i++]);
|
||||
}
|
||||
commands.push(cmd);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `PathCommandType`
|
||||
|
||||
String union: `"m" | "l" | "q" | "b" | "z"`. Maps to typeface.js outline token types (note: cubic bezier uses `"b"`, not SVG's `"c"`).
|
||||
|
||||
### `PathCommand`
|
||||
|
||||
| Field | Type | Present for |
|
||||
|---|---|---|
|
||||
| `type` | `PathCommandType` | all |
|
||||
| `x`, `y` | `number` | `m`, `l`, `q`, `b` |
|
||||
| `x1`, `y1` | `number` | `q`, `b` |
|
||||
| `x2`, `y2` | `number` | `b` only |
|
||||
|
||||
### `TypefaceGlyph`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `ha` | `number` | Horizontal advance (scaled units) |
|
||||
| `x_min` | `number` | Left extent of the glyph bounding box |
|
||||
| `x_max` | `number` | Right extent of the glyph bounding box |
|
||||
| `o` | `string` | Space-separated outline command tokens |
|
||||
|
||||
### `TypefaceFont`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `glyphs` | `Record<string, TypefaceGlyph>` | Map from character to glyph |
|
||||
| `familyName` | `string` | Font family name |
|
||||
| `ascender` | `number` | Ascender height (scaled) |
|
||||
| `descender` | `number` | Descender depth (scaled, negative) |
|
||||
| `underlinePosition` | `number` | Underline Y position |
|
||||
| `underlineThickness` | `number` | Underline stroke width |
|
||||
| `boundingBox` | `{ yMin, xMin, yMax, xMax }` | Overall font bounding box |
|
||||
| `resolution` | `number` | Always `1000` — typeface.js coordinate scale |
|
||||
| `original_font_information` | `unknown` | Raw name table from the source font |
|
||||
| `cssFontWeight` | `string` | `"normal"` or `"bold"` |
|
||||
| `cssFontStyle` | `string` | `"normal"` or `"italic"` |
|
||||
| `reversed` | `boolean?` | `true` when winding was reversed for TrueType CFF compat |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All coordinate values stored in a `TypefaceFont` are pre-scaled by `(1000 × 100) / (unitsPerEm × 72)`. This scale factor normalises any source font's internal coordinate system to the 1000-unit resolution expected by typeface.js and Three.js `FontLoader`. The `resolution: 1000` field documents this contract.
|
||||
|
||||
The `reversed` flag is informational only; it tells downstream consumers (e.g. Three.js path renderers) that the winding correction has already been applied so they should not apply their own.
|
||||
76
docs/media/fonts/font-convert/src/utils/outline-to-string.md
Normal file
76
docs/media/fonts/font-convert/src/utils/outline-to-string.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# outlineToString
|
||||
|
||||
## Purpose
|
||||
|
||||
Serialises an array of `PathCommand` objects to the space-separated token string stored in a `TypefaceGlyph`'s `o` field. The typeface.js format requires each command to be written as `"type [x y] [x1 y1] [x2 y2]"` with a trailing space after each coordinate group. `outlineToString` encapsulates this serialisation contract and applies an optional scale factor so that internal font-unit coordinates are normalised to the 1 000-unit resolution expected by Three.js `FontLoader`.
|
||||
|
||||
## Features
|
||||
|
||||
- Handles all five typeface.js command types: `m`, `l`, `q`, `b`, `z`
|
||||
- Emits only the coordinate fields present on each command
|
||||
- Applies a floating-point scale multiplier and rounds each coordinate to the nearest integer
|
||||
- Returns an empty string for an empty command array
|
||||
- Does not mutate the input array
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { outlineToString } from "@suaveplan/font-convert";
|
||||
import type { PathCommand } from "@suaveplan/font-convert";
|
||||
|
||||
const commands: PathCommand[] = [
|
||||
{ type: "m", x: 0, y: 0 },
|
||||
{ type: "l", x: 100, y: 0 },
|
||||
{ type: "z" },
|
||||
];
|
||||
|
||||
const outline = outlineToString(commands);
|
||||
// → "m 0 0 l 100 0 z "
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { outlineToString } from "@suaveplan/font-convert";
|
||||
import type { PathCommand } from "@suaveplan/font-convert";
|
||||
|
||||
// Apply a scale factor when coordinates are in raw font units
|
||||
const unitsPerEm = 1000;
|
||||
const scale = (1000 * 100) / (unitsPerEm * 72); // ≈ 1.389
|
||||
|
||||
const commands: PathCommand[] = [
|
||||
{ type: "m", x: 0, y: 0 },
|
||||
{ type: "q", x1: 360, y1: 720, x: 720, y: 0 },
|
||||
{ type: "b", x1: 100, y1: 200, x2: 300, y2: 400, x: 1000, y: 0 },
|
||||
];
|
||||
|
||||
const outline = outlineToString(commands, scale);
|
||||
// coordinates are Math.round(value * scale)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `outlineToString(commands: PathCommand[], scale?: number): string`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `commands` | `PathCommand[]` | — | Path commands to serialise |
|
||||
| `scale` | `number` | `1` | Multiplier applied to all coordinate values before rounding |
|
||||
|
||||
**Returns**: The typeface.js outline string. Each command contributes one token group ending with a trailing space. Returns `""` when `commands` is empty.
|
||||
|
||||
**Coordinate order per command type:**
|
||||
|
||||
| Type | Token sequence |
|
||||
|---|---|
|
||||
| `m` | `m x y` |
|
||||
| `l` | `l x y` |
|
||||
| `z` | `z` |
|
||||
| `q` | `q x y x1 y1` |
|
||||
| `b` | `b x y x1 y1 x2 y2` |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The coordinate emission order matches what `FontConverter` and the reference `typeface.js` CLI produce: the endpoint `x/y` is written before control points `x1/y1` and `x2/y2`. This is the order required by the Three.js `FontLoader` parser. Changing this order would silently corrupt all outlines that use bezier curves.
|
||||
|
||||
The scale factor default of `1` makes `outlineToString` directly testable with integer coordinates without requiring the caller to know the actual font resolution.
|
||||
66
docs/media/fonts/font-convert/src/utils/reverse-winding.md
Normal file
66
docs/media/fonts/font-convert/src/utils/reverse-winding.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# reverseWinding
|
||||
|
||||
## Purpose
|
||||
|
||||
Reverses the winding order of a typeface.js path command sequence. Three.js `FontLoader` expects counter-clockwise winding for outer contours, while some TrueType and CFF fonts ship with clockwise winding. Calling `reverseWinding` before serialising glyphs ensures the outline direction is corrected without reprocessing the source binary.
|
||||
|
||||
## Features
|
||||
|
||||
- Splits commands at each `M`/`m` moveto to identify individual subpaths
|
||||
- Drops `Z`/`z` close-path commands (they are implicit in typeface.js rendering)
|
||||
- Reverses command order within each subpath
|
||||
- Swaps cubic bezier control point pairs (`x1/y1` ↔ `x2/y2`) to maintain curve shape
|
||||
- Preserves quadratic bezier single control point after destination flip
|
||||
- Works with zero commands (returns empty array, no error)
|
||||
- Operates entirely on the `PathCommand` type from `@suaveplan/font-convert`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { reverseWinding } from "@suaveplan/font-convert";
|
||||
import type { PathCommand } from "@suaveplan/font-convert";
|
||||
|
||||
const commands: PathCommand[] = [
|
||||
{ type: "m", x: 0, y: 0 },
|
||||
{ type: "l", x: 100, y: 0 },
|
||||
{ type: "l", x: 50, y: 100 },
|
||||
{ type: "z" },
|
||||
];
|
||||
|
||||
const reversed = reverseWinding(commands);
|
||||
// reversed[0] = { type: "m", x: 50, y: 100 } — last point becomes new start
|
||||
// reversed[1] = { type: "l", x: 100, y: 0 }
|
||||
// reversed[2] = { type: "l", x: 0, y: 0 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { reverseWinding } from "@suaveplan/font-convert";
|
||||
import type { PathCommand } from "@suaveplan/font-convert";
|
||||
|
||||
// Cubic bezier: reversal swaps x1/y1 and x2/y2
|
||||
const cubicCommands: PathCommand[] = [
|
||||
{ type: "m", x: 0, y: 0 },
|
||||
{ type: "b", x1: 10, y1: 20, x2: 30, y2: 40, x: 100, y: 0 },
|
||||
];
|
||||
|
||||
const rev = reverseWinding(cubicCommands);
|
||||
// rev[0] = { type: "m", x: 100, y: 0 }
|
||||
// rev[1] = { type: "b", x1: 30, y1: 40, x2: 10, y2: 20, x: 0, y: 0 }
|
||||
// ^^^ x2/y2 and x1/y1 are swapped relative to the original
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `reverseWinding(commands: PathCommand[]): PathCommand[]`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `commands` | `PathCommand[]` | Array of path commands as produced by `FontConverter` |
|
||||
|
||||
**Returns**: A new array of `PathCommand` objects in reversed winding order. The input array is not mutated.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function mirrors the winding-reversal algorithm used in `typeface.js`'s own CLI tooling and the reference `FontConverter.ts`. Subpath splitting is done by scanning for `m`/`M` commands; `z`/`Z` close-path commands are discarded because typeface.js renderers treat each subpath as implicitly closed. The reversed list starts with a new `m` command pointing at the last non-close command of the original path, then walks the original path backwards, placing each point at the previous command's destination.
|
||||
Reference in New Issue
Block a user