text-shaper 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +413 -38
- package/dist/aat/state-machine.d.ts +18 -6
- package/dist/buffer/glyph-buffer.d.ts +35 -1
- package/dist/buffer/unicode-buffer.d.ts +2 -0
- package/dist/fluent/bitmap-builder.d.ts +146 -0
- package/dist/fluent/index.d.ts +102 -0
- package/dist/fluent/path-builder.d.ts +230 -0
- package/dist/fluent/pipe.d.ts +200 -0
- package/dist/fluent/types.d.ts +93 -0
- package/dist/font/brotli/decode.d.ts +42 -0
- package/dist/font/face.d.ts +2 -0
- package/dist/font/font.d.ts +14 -0
- package/dist/font/tables/cff.d.ts +3 -1
- package/dist/font/tables/cff2.d.ts +63 -0
- package/dist/font/tables/colr.d.ts +4 -1
- package/dist/font/tables/fvar.d.ts +3 -1
- package/dist/font/tables/glyf.d.ts +13 -0
- package/dist/font/tables/gpos.d.ts +24 -1
- package/dist/font/tables/gsub.d.ts +20 -0
- package/dist/font/tables/gvar.d.ts +10 -1
- package/dist/font/tables/head.d.ts +5 -0
- package/dist/font/tables/hhea.d.ts +5 -0
- package/dist/font/tables/loca.d.ts +3 -0
- package/dist/font/tables/maxp.d.ts +5 -0
- package/dist/font/tables/name.d.ts +5 -0
- package/dist/font/tables/os2.d.ts +5 -0
- package/dist/font/tables/post.d.ts +5 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +12 -10
- package/dist/index.js.map +105 -99
- package/dist/layout/justify.d.ts +15 -2
- package/dist/layout/structures/class-def.d.ts +61 -8
- package/dist/layout/structures/coverage.d.ts +57 -10
- package/dist/layout/structures/device.d.ts +26 -5
- package/dist/layout/structures/layout-common.d.ts +18 -3
- package/dist/layout/structures/set-digest.d.ts +59 -0
- package/dist/raster/asymmetric-stroke.d.ts +9 -5
- package/dist/raster/bitmap-utils.d.ts +67 -0
- package/dist/raster/blur.d.ts +11 -0
- package/dist/raster/cascade-blur.d.ts +9 -10
- package/dist/raster/cell.d.ts +24 -0
- package/dist/raster/fixed-point.d.ts +1 -1
- package/dist/raster/gradient.d.ts +15 -0
- package/dist/raster/gray-raster.d.ts +7 -1
- package/dist/raster/lcd-filter.d.ts +15 -0
- package/dist/raster/msdf.d.ts +27 -5
- package/dist/raster/outline-decompose.d.ts +16 -17
- package/dist/raster/rasterize.d.ts +19 -2
- package/dist/raster/sdf.d.ts +3 -0
- package/dist/raster/stroker.d.ts +3 -0
- package/dist/raster/types.d.ts +9 -0
- package/dist/render/path.d.ts +56 -1
- package/dist/shaper/complex/arabic.d.ts +1 -0
- package/dist/shaper/shape-plan.d.ts +11 -8
- package/dist/shaper/shaper.d.ts +189 -4
- package/dist/unicode/bidi/brackets.d.ts +15 -0
- package/dist/unicode/bidi/char-types.d.ts +4 -0
- package/dist/unicode/bidi/embedding-levels.d.ts +3 -0
- package/dist/unicode/bidi/mirroring.d.ts +10 -0
- package/dist/unicode/bidi/reordering.d.ts +15 -0
- package/dist/unicode/normalize.d.ts +23 -0
- package/dist/unicode/script.d.ts +17 -0
- package/dist/unicode/segmentation.d.ts +18 -0
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -5,17 +5,33 @@
|
|
|
5
5
|
|
|
6
6
|
Pure TypeScript text shaping engine with OpenType layout, TrueType hinting, and FreeType-style rasterization. Works in browsers and Bun/Node.js with zero dependencies.
|
|
7
7
|
|
|
8
|
+
## Performance
|
|
9
|
+
|
|
10
|
+
text-shaper outperforms harfbuzzjs (WebAssembly) and opentype.js across all benchmarks:
|
|
11
|
+
|
|
12
|
+
| Category | vs harfbuzzjs | vs opentype.js |
|
|
13
|
+
|----------|---------------|----------------|
|
|
14
|
+
| Path Extraction | 16x faster | 10x faster |
|
|
15
|
+
| Text to SVG | 1.2-1.5x faster | 4-6x faster |
|
|
16
|
+
| Latin Shaping | 1.5x faster | 22x faster |
|
|
17
|
+
| Arabic Shaping | 1.2x faster | 86x faster |
|
|
18
|
+
| Hebrew Shaping | 1.6x faster | 33x faster |
|
|
19
|
+
| Hindi Shaping | 3.6x faster | 11x faster |
|
|
20
|
+
| Myanmar Shaping | 10.5x faster | 17x faster |
|
|
21
|
+
| CJK Shaping | 1.3-1.5x faster | 11-13x faster |
|
|
22
|
+
|
|
8
23
|
## Features
|
|
9
24
|
|
|
10
25
|
- **OpenType Layout**: Full GSUB (substitution) and GPOS (positioning) support
|
|
11
26
|
- **Complex Scripts**: Arabic, Indic, USE (Universal Shaping Engine) shapers
|
|
12
27
|
- **Variable Fonts**: fvar, gvar, avar, HVAR, VVAR, MVAR tables
|
|
13
28
|
- **AAT Support**: morx, kerx, trak tables for Apple fonts
|
|
14
|
-
- **Color Fonts**: SVG, sbix, CBDT/CBLC tables
|
|
29
|
+
- **Color Fonts**: SVG, sbix, CBDT/CBLC, COLR/CPAL tables
|
|
15
30
|
- **BiDi**: UAX #9 bidirectional text algorithm
|
|
16
31
|
- **Rasterization**: FreeType-style grayscale, LCD subpixel, and monochrome rendering
|
|
17
32
|
- **TrueType Hinting**: Full bytecode interpreter (150+ opcodes)
|
|
18
33
|
- **Texture Atlas**: GPU-ready glyph atlas generation with shelf packing
|
|
34
|
+
- **SDF/MSDF**: Signed distance field rendering for scalable text
|
|
19
35
|
- **Zero Dependencies**: Pure TypeScript, works in browser and Node.js
|
|
20
36
|
|
|
21
37
|
## Installation
|
|
@@ -28,16 +44,18 @@ bun add text-shaper
|
|
|
28
44
|
|
|
29
45
|
## Usage
|
|
30
46
|
|
|
47
|
+
### Basic Shaping
|
|
48
|
+
|
|
31
49
|
```typescript
|
|
32
50
|
import { Font, shape, UnicodeBuffer } from "text-shaper";
|
|
33
51
|
|
|
34
52
|
// Load a font
|
|
35
|
-
const fontData = await
|
|
36
|
-
const font =
|
|
53
|
+
const fontData = await fetch("path/to/font.ttf").then(r => r.arrayBuffer());
|
|
54
|
+
const font = Font.load(fontData);
|
|
37
55
|
|
|
38
56
|
// Create a buffer with text
|
|
39
57
|
const buffer = new UnicodeBuffer();
|
|
40
|
-
buffer.
|
|
58
|
+
buffer.addStr("Hello, World!");
|
|
41
59
|
|
|
42
60
|
// Shape the text
|
|
43
61
|
const glyphBuffer = shape(font, buffer);
|
|
@@ -46,21 +64,49 @@ const glyphBuffer = shape(font, buffer);
|
|
|
46
64
|
for (let i = 0; i < glyphBuffer.length; i++) {
|
|
47
65
|
const info = glyphBuffer.info[i];
|
|
48
66
|
const pos = glyphBuffer.pos[i];
|
|
49
|
-
console.log(`Glyph ${info.glyphId}: advance=${pos.xAdvance}
|
|
67
|
+
console.log(`Glyph ${info.glyphId}: advance=${pos.xAdvance}`);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### High-Performance Shaping
|
|
72
|
+
|
|
73
|
+
For best performance, reuse buffers with `shapeInto`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { Font, shapeInto, UnicodeBuffer, GlyphBuffer } from "text-shaper";
|
|
77
|
+
|
|
78
|
+
const font = Font.load(fontData);
|
|
79
|
+
const uBuffer = new UnicodeBuffer();
|
|
80
|
+
const gBuffer = GlyphBuffer.withCapacity(128);
|
|
81
|
+
|
|
82
|
+
// Shape multiple strings efficiently
|
|
83
|
+
for (const text of texts) {
|
|
84
|
+
uBuffer.clear();
|
|
85
|
+
uBuffer.addStr(text);
|
|
86
|
+
gBuffer.reset();
|
|
87
|
+
shapeInto(font, uBuffer, gBuffer);
|
|
88
|
+
// Process gBuffer...
|
|
50
89
|
}
|
|
51
90
|
```
|
|
52
91
|
|
|
53
92
|
### With Features
|
|
54
93
|
|
|
55
94
|
```typescript
|
|
56
|
-
import { Font, shape, UnicodeBuffer, feature
|
|
95
|
+
import { Font, shape, UnicodeBuffer, feature } from "text-shaper";
|
|
96
|
+
|
|
97
|
+
const glyphBuffer = shape(font, buffer, {
|
|
98
|
+
features: [
|
|
99
|
+
feature("smcp"), // Small caps
|
|
100
|
+
feature("liga"), // Ligatures
|
|
101
|
+
feature("kern"), // Kerning
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Or use convenience helpers
|
|
106
|
+
import { smallCaps, standardLigatures, kerning, combineFeatures } from "text-shaper";
|
|
57
107
|
|
|
58
108
|
const glyphBuffer = shape(font, buffer, {
|
|
59
|
-
features:
|
|
60
|
-
feature("smcp", 1), // Small caps
|
|
61
|
-
feature("liga", 1), // Ligatures
|
|
62
|
-
feature("kern", 1), // Kerning
|
|
63
|
-
),
|
|
109
|
+
features: combineFeatures(smallCaps(), standardLigatures(), kerning()),
|
|
64
110
|
});
|
|
65
111
|
```
|
|
66
112
|
|
|
@@ -80,42 +126,26 @@ const glyphBuffer = shape(font, buffer, {
|
|
|
80
126
|
### Rendering to SVG
|
|
81
127
|
|
|
82
128
|
```typescript
|
|
83
|
-
import {
|
|
129
|
+
import {
|
|
130
|
+
Font, shape, UnicodeBuffer,
|
|
131
|
+
glyphBufferToShapedGlyphs, shapedTextToSVG
|
|
132
|
+
} from "text-shaper";
|
|
84
133
|
|
|
85
134
|
const buffer = new UnicodeBuffer();
|
|
86
|
-
buffer.
|
|
135
|
+
buffer.addStr("Hello");
|
|
87
136
|
|
|
88
137
|
const glyphBuffer = shape(font, buffer);
|
|
89
|
-
const
|
|
138
|
+
const shapedGlyphs = glyphBufferToShapedGlyphs(glyphBuffer);
|
|
139
|
+
const svg = shapedTextToSVG(font, shapedGlyphs, { fontSize: 48 });
|
|
90
140
|
```
|
|
91
141
|
|
|
92
|
-
## API
|
|
93
|
-
|
|
94
|
-
### Core Classes
|
|
95
|
-
|
|
96
|
-
- `Font` - Load and parse OpenType/TrueType fonts
|
|
97
|
-
- `Face` - Font face with variation coordinates applied
|
|
98
|
-
- `UnicodeBuffer` - Input buffer for text to shape
|
|
99
|
-
- `GlyphBuffer` - Output buffer containing shaped glyphs
|
|
100
|
-
|
|
101
|
-
### Shaping
|
|
102
|
-
|
|
103
|
-
- `shape(font, buffer, options?)` - Shape text in a buffer
|
|
104
|
-
- `createShapePlan(font, options)` - Create a reusable shape plan
|
|
105
|
-
|
|
106
|
-
### Rendering
|
|
107
|
-
|
|
108
|
-
- `getGlyphPath(font, glyphId)` - Get glyph outline as path commands
|
|
109
|
-
- `shapedTextToSVG(font, buffer, options)` - Render shaped text to SVG
|
|
110
|
-
- `renderShapedText(ctx, font, buffer, options)` - Render to Canvas 2D context
|
|
111
|
-
|
|
112
142
|
### Rasterization
|
|
113
143
|
|
|
114
144
|
```typescript
|
|
115
145
|
import { Font, rasterizeGlyph, buildAtlas, PixelMode } from "text-shaper";
|
|
116
146
|
|
|
117
147
|
// Rasterize a single glyph
|
|
118
|
-
const
|
|
148
|
+
const bitmap = rasterizeGlyph(font, glyphId, 48, {
|
|
119
149
|
pixelMode: PixelMode.Gray, // Gray, Mono, or LCD
|
|
120
150
|
hinting: true, // Enable TrueType hinting
|
|
121
151
|
});
|
|
@@ -129,6 +159,342 @@ const atlas = buildAtlas(font, glyphIds, {
|
|
|
129
159
|
});
|
|
130
160
|
```
|
|
131
161
|
|
|
162
|
+
### SDF/MSDF Rendering
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import {
|
|
166
|
+
Font, getGlyphPath, renderSdf, renderMsdf, buildMsdfAtlas
|
|
167
|
+
} from "text-shaper";
|
|
168
|
+
|
|
169
|
+
// Single glyph SDF
|
|
170
|
+
const path = getGlyphPath(font, glyphId);
|
|
171
|
+
if (path) {
|
|
172
|
+
const sdf = renderSdf(path, {
|
|
173
|
+
width: 64,
|
|
174
|
+
height: 64,
|
|
175
|
+
scale: 1,
|
|
176
|
+
spread: 8,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// MSDF atlas for GPU text rendering (handles font internally)
|
|
181
|
+
const msdfAtlas = buildMsdfAtlas(font, glyphIds, {
|
|
182
|
+
fontSize: 32,
|
|
183
|
+
spread: 4,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Fluent API
|
|
188
|
+
|
|
189
|
+
Two composition styles for glyph manipulation and rendering:
|
|
190
|
+
|
|
191
|
+
#### Builder Pattern (Method Chaining)
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { Font, glyph, char, glyphVar, combine, PixelMode } from "text-shaper";
|
|
195
|
+
|
|
196
|
+
// From glyph ID
|
|
197
|
+
const rgba = glyph(font, glyphId)
|
|
198
|
+
?.scale(2)
|
|
199
|
+
.rotateDeg(15)
|
|
200
|
+
.rasterizeAuto({ padding: 2 })
|
|
201
|
+
.blur(5)
|
|
202
|
+
.toRGBA();
|
|
203
|
+
|
|
204
|
+
// From character
|
|
205
|
+
const svg = char(font, "A")
|
|
206
|
+
?.scale(3)
|
|
207
|
+
.italic(12)
|
|
208
|
+
.toSVG({ width: 100, height: 100 });
|
|
209
|
+
|
|
210
|
+
// Variable fonts
|
|
211
|
+
const bitmap = glyphVar(font, glyphId, [700, 100]) // wght=700, wdth=100
|
|
212
|
+
?.embolden(50)
|
|
213
|
+
.rasterize({ pixelMode: PixelMode.Gray, scale: 2 })
|
|
214
|
+
.toBitmap();
|
|
215
|
+
|
|
216
|
+
// Combine multiple glyphs
|
|
217
|
+
const h = glyph(font, hGlyphId)?.translate(0, 0);
|
|
218
|
+
const i = glyph(font, iGlyphId)?.translate(100, 0);
|
|
219
|
+
if (h && i) {
|
|
220
|
+
const combined = combine(h, i).scale(2).rasterizeAuto().toRGBA();
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### PathBuilder Methods
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Transforms (lazy - accumulated as matrix)
|
|
228
|
+
.scale(sx, sy?) // Scale uniformly or non-uniformly
|
|
229
|
+
.translate(dx, dy) // Translate by offset
|
|
230
|
+
.rotate(radians) // Rotate by angle in radians
|
|
231
|
+
.rotateDeg(degrees) // Rotate by angle in degrees
|
|
232
|
+
.shear(shearX, shearY) // Shear transform
|
|
233
|
+
.italic(degrees) // Italic slant (convenience for shear)
|
|
234
|
+
.matrix(m) // Apply custom 2D matrix
|
|
235
|
+
.perspective(m) // Apply 3D perspective matrix
|
|
236
|
+
.resetTransform() // Reset accumulated transforms
|
|
237
|
+
.apply() // Apply accumulated transforms to path
|
|
238
|
+
|
|
239
|
+
// Path effects (immediate - modifies path)
|
|
240
|
+
.embolden(strength) // Make strokes thicker
|
|
241
|
+
.condense(factor) // Horizontal compression
|
|
242
|
+
.oblique(slant) // Oblique slant effect
|
|
243
|
+
.stroke(width, cap?, join?) // Convert to stroked outline
|
|
244
|
+
.strokeAsymmetric(opts) // Independent x/y stroke widths
|
|
245
|
+
|
|
246
|
+
// Output
|
|
247
|
+
.rasterize(options) // Rasterize to BitmapBuilder
|
|
248
|
+
.rasterizeAuto(options?) // Auto-sized rasterization
|
|
249
|
+
.toSdf(options) // Render to SDF bitmap
|
|
250
|
+
.toMsdf(options) // Render to MSDF bitmap
|
|
251
|
+
.toSVG(options?) // Export as SVG string
|
|
252
|
+
.toSVGElement(options?) // Export as SVG path element
|
|
253
|
+
.toCanvas(ctx, options?) // Draw to Canvas 2D context
|
|
254
|
+
.toPath() // Get raw GlyphPath
|
|
255
|
+
.clone() // Clone the builder
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### BitmapBuilder Methods
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// Blur effects
|
|
262
|
+
.blur(radius) // Gaussian blur
|
|
263
|
+
.boxBlur(radius) // Box blur (faster)
|
|
264
|
+
.fastBlur(radius) // Fast approximated blur
|
|
265
|
+
.cascadeBlur(rx, ry?) // Cascade blur for large radii
|
|
266
|
+
.adaptiveBlur(rx, ry?) // Adaptive quality blur
|
|
267
|
+
|
|
268
|
+
// Modifications
|
|
269
|
+
.embolden(xStrength, yStrength?) // Expand bitmap
|
|
270
|
+
.shift(dx, dy) // Shift bitmap contents
|
|
271
|
+
.resize(width, height) // Resize (nearest neighbor)
|
|
272
|
+
.resizeBilinear(w, h) // Resize with bilinear filtering
|
|
273
|
+
.pad(left, top, right, bottom) // Add padding
|
|
274
|
+
.convert(pixelMode) // Convert pixel format
|
|
275
|
+
|
|
276
|
+
// Output
|
|
277
|
+
.toRGBA() // Export as RGBA Uint8Array
|
|
278
|
+
.toGray() // Export as grayscale Uint8Array
|
|
279
|
+
.toBitmap() // Get Bitmap object
|
|
280
|
+
.toRasterizedGlyph() // Get RasterizedGlyph with metrics
|
|
281
|
+
.clone() // Clone the builder
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### Pipe Pattern (Functional)
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import {
|
|
288
|
+
pipe, glyph,
|
|
289
|
+
$scale, $rotate, $embolden,
|
|
290
|
+
$rasterize, $blur, $toRGBA
|
|
291
|
+
} from "text-shaper";
|
|
292
|
+
|
|
293
|
+
// Compose operations functionally
|
|
294
|
+
const rgba = pipe(
|
|
295
|
+
glyph(font, glyphId),
|
|
296
|
+
$scale(2),
|
|
297
|
+
$rotate(Math.PI / 12),
|
|
298
|
+
$embolden(30),
|
|
299
|
+
$rasterize({ pixelMode: PixelMode.Gray }),
|
|
300
|
+
$blur(3),
|
|
301
|
+
$toRGBA()
|
|
302
|
+
);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Line Breaking & Justification
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { breakIntoLines, justify, JustifyMode } from "text-shaper";
|
|
309
|
+
|
|
310
|
+
// Break text into lines
|
|
311
|
+
const lines = breakIntoLines(glyphBuffer, font, maxWidth);
|
|
312
|
+
|
|
313
|
+
// Justify a line
|
|
314
|
+
const justified = justify(line, targetWidth, {
|
|
315
|
+
mode: JustifyMode.Distribute,
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Text Segmentation
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { countGraphemes, splitGraphemes, splitWords } from "text-shaper";
|
|
323
|
+
|
|
324
|
+
// Count grapheme clusters (visual characters)
|
|
325
|
+
const count = countGraphemes("👨👩👧👦Hello"); // 6 (family emoji = 1)
|
|
326
|
+
|
|
327
|
+
// Split into graphemes
|
|
328
|
+
const graphemes = splitGraphemes("नमस्ते"); // Devanagari clusters
|
|
329
|
+
|
|
330
|
+
// Split into words
|
|
331
|
+
const words = splitWords("Hello World"); // ["Hello", " ", "World"]
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Canvas Rendering
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import {
|
|
338
|
+
Font, shape, UnicodeBuffer,
|
|
339
|
+
glyphBufferToShapedGlyphs, renderShapedText
|
|
340
|
+
} from "text-shaper";
|
|
341
|
+
|
|
342
|
+
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
|
|
343
|
+
const ctx = canvas.getContext("2d")!;
|
|
344
|
+
|
|
345
|
+
const buffer = new UnicodeBuffer();
|
|
346
|
+
buffer.addStr("Hello Canvas!");
|
|
347
|
+
|
|
348
|
+
const glyphBuffer = shape(font, buffer);
|
|
349
|
+
const shapedGlyphs = glyphBufferToShapedGlyphs(glyphBuffer);
|
|
350
|
+
|
|
351
|
+
renderShapedText(ctx, font, shapedGlyphs, {
|
|
352
|
+
x: 50,
|
|
353
|
+
y: 100,
|
|
354
|
+
fontSize: 48,
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### BiDi & RTL Text
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import {
|
|
362
|
+
Font, shape, UnicodeBuffer,
|
|
363
|
+
processBidi, reorderGlyphs, detectDirection
|
|
364
|
+
} from "text-shaper";
|
|
365
|
+
|
|
366
|
+
// Automatic RTL detection and reordering
|
|
367
|
+
const buffer = new UnicodeBuffer();
|
|
368
|
+
buffer.addStr("Hello שלום World"); // Mixed LTR/RTL
|
|
369
|
+
|
|
370
|
+
const glyphBuffer = shape(font, buffer);
|
|
371
|
+
// Glyphs are automatically reordered for visual display
|
|
372
|
+
|
|
373
|
+
// Manual BiDi processing
|
|
374
|
+
const bidiResult = processBidi("مرحبا Hello");
|
|
375
|
+
console.log(bidiResult.direction); // "rtl"
|
|
376
|
+
console.log(bidiResult.levels); // Embedding levels per character
|
|
377
|
+
|
|
378
|
+
// Detect text direction
|
|
379
|
+
const dir = detectDirection("שלום"); // "rtl"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Color Fonts
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import {
|
|
386
|
+
Font,
|
|
387
|
+
hasColorGlyph, getColorPaint, getColorLayers, // COLR
|
|
388
|
+
hasSvgGlyph, getSvgDocument, // SVG
|
|
389
|
+
hasColorBitmap, getBitmapGlyph, // CBDT/sbix
|
|
390
|
+
} from "text-shaper";
|
|
391
|
+
|
|
392
|
+
// Check for color glyph support
|
|
393
|
+
const glyphId = font.glyphId("😀".codePointAt(0)!);
|
|
394
|
+
|
|
395
|
+
// COLR/CPAL (vector color)
|
|
396
|
+
if (hasColorGlyph(font, glyphId)) {
|
|
397
|
+
const paint = getColorPaint(font, glyphId);
|
|
398
|
+
// Render paint tree...
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// SVG color glyphs
|
|
402
|
+
if (hasSvgGlyph(font, glyphId)) {
|
|
403
|
+
const svgDoc = getSvgDocument(font, glyphId);
|
|
404
|
+
// Use SVG document directly
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Bitmap color glyphs (sbix, CBDT)
|
|
408
|
+
if (hasColorBitmap(font, glyphId)) {
|
|
409
|
+
const bitmap = getBitmapGlyph(font, glyphId, 128); // ppem=128
|
|
410
|
+
// bitmap.data contains PNG/JPEG data
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Texture Atlas (WebGL/GPU)
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import {
|
|
418
|
+
Font, buildAtlas, buildStringAtlas, buildMsdfAtlas,
|
|
419
|
+
atlasToRGBA, getGlyphUV, PixelMode
|
|
420
|
+
} from "text-shaper";
|
|
421
|
+
|
|
422
|
+
// Build atlas from glyph IDs
|
|
423
|
+
const atlas = buildAtlas(font, glyphIds, {
|
|
424
|
+
fontSize: 32,
|
|
425
|
+
padding: 2,
|
|
426
|
+
pixelMode: PixelMode.Gray,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Build atlas from string (auto-extracts unique glyphs)
|
|
430
|
+
const textAtlas = buildStringAtlas(font, "Hello World!", {
|
|
431
|
+
fontSize: 48,
|
|
432
|
+
padding: 1,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Convert to RGBA for WebGL texture
|
|
436
|
+
const rgba = atlasToRGBA(atlas);
|
|
437
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, atlas.width, atlas.height,
|
|
438
|
+
0, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
|
|
439
|
+
|
|
440
|
+
// Get UV coordinates for rendering
|
|
441
|
+
const uv = getGlyphUV(atlas, glyphId);
|
|
442
|
+
// uv = { u0, v0, u1, v1, ... }
|
|
443
|
+
|
|
444
|
+
// MSDF atlas for scalable GPU text
|
|
445
|
+
const msdfAtlas = buildMsdfAtlas(font, glyphIds, {
|
|
446
|
+
fontSize: 32,
|
|
447
|
+
spread: 4,
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Browser Usage
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// Fetch font from URL
|
|
455
|
+
const fontData = await fetch("/fonts/MyFont.ttf").then(r => r.arrayBuffer());
|
|
456
|
+
const font = Font.load(fontData);
|
|
457
|
+
|
|
458
|
+
// Or from File input
|
|
459
|
+
const file = input.files[0];
|
|
460
|
+
const buffer = await file.arrayBuffer();
|
|
461
|
+
const font = Font.load(buffer);
|
|
462
|
+
|
|
463
|
+
// Works with any ArrayBuffer source
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## API Reference
|
|
467
|
+
|
|
468
|
+
### Core Classes
|
|
469
|
+
|
|
470
|
+
| Class | Description |
|
|
471
|
+
|-------|-------------|
|
|
472
|
+
| `Font` | Load and parse OpenType/TrueType fonts |
|
|
473
|
+
| `Face` | Font face with variation coordinates applied |
|
|
474
|
+
| `UnicodeBuffer` | Input buffer for text to shape |
|
|
475
|
+
| `GlyphBuffer` | Output buffer containing shaped glyphs |
|
|
476
|
+
|
|
477
|
+
### Shaping Functions
|
|
478
|
+
|
|
479
|
+
| Function | Description |
|
|
480
|
+
|----------|-------------|
|
|
481
|
+
| `shape(font, buffer, options?)` | Shape text, returns new GlyphBuffer |
|
|
482
|
+
| `shapeInto(font, buffer, glyphBuffer, options?)` | Shape into existing buffer (faster) |
|
|
483
|
+
| `createShapePlan(font, options)` | Create reusable shape plan |
|
|
484
|
+
| `getOrCreateShapePlan(font, options)` | Get cached or create shape plan |
|
|
485
|
+
|
|
486
|
+
### Rendering Functions
|
|
487
|
+
|
|
488
|
+
| Function | Description |
|
|
489
|
+
|----------|-------------|
|
|
490
|
+
| `getGlyphPath(font, glyphId)` | Get glyph outline as path commands |
|
|
491
|
+
| `shapedTextToSVG(font, shapedGlyphs, options)` | Render shaped text to SVG string |
|
|
492
|
+
| `renderShapedText(ctx, font, shapedGlyphs, options)` | Render to Canvas 2D context |
|
|
493
|
+
| `glyphBufferToShapedGlyphs(buffer)` | Convert GlyphBuffer to ShapedGlyph[] |
|
|
494
|
+
| `rasterizeGlyph(font, glyphId, size, options)` | Rasterize glyph to bitmap |
|
|
495
|
+
| `rasterizePath(path, options)` | Rasterize path commands to bitmap |
|
|
496
|
+
| `buildAtlas(font, glyphIds, options)` | Build texture atlas |
|
|
497
|
+
|
|
132
498
|
### Feature Helpers
|
|
133
499
|
|
|
134
500
|
```typescript
|
|
@@ -152,10 +518,19 @@ proportionalFigures() // pnum
|
|
|
152
518
|
stylisticSet(n) // ss01-ss20
|
|
153
519
|
characterVariant(n) // cv01-cv99
|
|
154
520
|
swash() // swsh
|
|
155
|
-
|
|
156
|
-
// And many more...
|
|
157
521
|
```
|
|
158
522
|
|
|
523
|
+
### Unicode Utilities
|
|
524
|
+
|
|
525
|
+
| Function | Description |
|
|
526
|
+
|----------|-------------|
|
|
527
|
+
| `processBidi(text)` | Process bidirectional text (UAX #9) |
|
|
528
|
+
| `getScript(codepoint)` | Get Unicode script for codepoint |
|
|
529
|
+
| `getScriptRuns(text)` | Split text into script runs |
|
|
530
|
+
| `countGraphemes(text)` | Count grapheme clusters |
|
|
531
|
+
| `splitGraphemes(text)` | Split into grapheme clusters |
|
|
532
|
+
| `analyzeLineBreaks(text)` | Find line break opportunities (UAX #14) |
|
|
533
|
+
|
|
159
534
|
## Supported Tables
|
|
160
535
|
|
|
161
536
|
### Required
|
|
@@ -171,7 +546,7 @@ CFF, CFF2
|
|
|
171
546
|
fvar, gvar, avar, HVAR, VVAR, MVAR, STAT
|
|
172
547
|
|
|
173
548
|
### AAT (Apple)
|
|
174
|
-
morx, kerx, kern, trak
|
|
549
|
+
morx, kerx, kern, trak, feat
|
|
175
550
|
|
|
176
551
|
### Color
|
|
177
552
|
COLR, CPAL, SVG, sbix, CBDT, CBLC
|
|
@@ -12,23 +12,35 @@ export interface StateMachineContext {
|
|
|
12
12
|
stack: number[];
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Get class for a glyph
|
|
15
|
+
* Get the class value for a glyph from the class table
|
|
16
|
+
* @param classTable - The class lookup table
|
|
17
|
+
* @param glyphId - The glyph ID to look up
|
|
18
|
+
* @returns The class value, or CLASS_OUT_OF_BOUNDS if the glyph is not in the table
|
|
16
19
|
*/
|
|
17
20
|
export declare function getGlyphClass(classTable: ClassTable, glyphId: GlyphId): number;
|
|
18
21
|
/**
|
|
19
|
-
* Process rearrangement subtable
|
|
20
|
-
*
|
|
22
|
+
* Process rearrangement subtable to reorder glyphs based on state machine rules
|
|
23
|
+
* @param subtable - The rearrangement subtable containing state machine and rules
|
|
24
|
+
* @param infos - Array of glyph infos to be reordered in place
|
|
21
25
|
*/
|
|
22
26
|
export declare function processRearrangement(subtable: MorxRearrangementSubtable, infos: GlyphInfo[]): void;
|
|
23
27
|
/**
|
|
24
|
-
* Process contextual substitution subtable
|
|
28
|
+
* Process contextual substitution subtable to replace glyphs based on context
|
|
29
|
+
* @param subtable - The contextual subtable containing state machine and substitution tables
|
|
30
|
+
* @param infos - Array of glyph infos to be modified in place with contextual substitutions
|
|
25
31
|
*/
|
|
26
32
|
export declare function processContextual(subtable: MorxContextualSubtable, infos: GlyphInfo[]): void;
|
|
27
33
|
/**
|
|
28
|
-
* Process ligature subtable
|
|
34
|
+
* Process ligature subtable to combine multiple glyphs into ligatures
|
|
35
|
+
* @param subtable - The ligature subtable containing state machine, actions, and component tables
|
|
36
|
+
* @param infos - Array of glyph infos to process
|
|
37
|
+
* @returns New array of glyph infos with ligatures applied and component glyphs removed
|
|
29
38
|
*/
|
|
30
39
|
export declare function processLigature(subtable: MorxLigatureSubtable, infos: GlyphInfo[]): GlyphInfo[];
|
|
31
40
|
/**
|
|
32
|
-
* Process insertion subtable
|
|
41
|
+
* Process insertion subtable to insert additional glyphs before or after existing glyphs
|
|
42
|
+
* @param subtable - The insertion subtable containing state machine and insertion glyph table
|
|
43
|
+
* @param infos - Array of glyph infos to process
|
|
44
|
+
* @returns New array of glyph infos with inserted glyphs added at specified positions
|
|
33
45
|
*/
|
|
34
46
|
export declare function processInsertion(subtable: MorxInsertionSubtable, infos: GlyphInfo[]): GlyphInfo[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Font } from "../font/font.ts";
|
|
1
2
|
import { Direction, type GlyphId, type GlyphInfo, type GlyphPosition } from "../types.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Output buffer containing shaped glyphs.
|
|
@@ -14,10 +15,29 @@ export declare class GlyphBuffer {
|
|
|
14
15
|
infos: GlyphInfo[];
|
|
15
16
|
/** Glyph position array */
|
|
16
17
|
positions: GlyphPosition[];
|
|
17
|
-
/**
|
|
18
|
+
/** Deleted glyph markers for deferred removal */
|
|
19
|
+
private _deleted;
|
|
20
|
+
/** Count of deleted glyphs pending compaction */
|
|
21
|
+
private _deletedCount;
|
|
22
|
+
/** Pre-allocated info pool for reuse */
|
|
23
|
+
private _infoPool;
|
|
24
|
+
/** Pre-allocated position pool for reuse */
|
|
25
|
+
private _posPool;
|
|
26
|
+
/** Pre-allocated capacity hint */
|
|
27
|
+
private _capacity;
|
|
28
|
+
/** Create buffer with pre-allocated capacity (lazy object creation) */
|
|
18
29
|
static withCapacity(capacity: number): GlyphBuffer;
|
|
19
30
|
/** Number of glyphs */
|
|
20
31
|
get length(): number;
|
|
32
|
+
/** Reset buffer for reuse - reuses existing object pool */
|
|
33
|
+
reset(): void;
|
|
34
|
+
/** Initialize from codepoints, reusing pooled objects when possible */
|
|
35
|
+
initFromCodepoints(codepoints: ArrayLike<number>, clusters: ArrayLike<number>, getGlyphId: (codepoint: number) => number): void;
|
|
36
|
+
/**
|
|
37
|
+
* Initialize from codepoints with direct font access (no closure).
|
|
38
|
+
* This is faster than initFromCodepoints for hot paths.
|
|
39
|
+
*/
|
|
40
|
+
initFromCodepointsWithFont(codepoints: ArrayLike<number>, clusters: ArrayLike<number>, font: Font): void;
|
|
21
41
|
/** Initialize from glyph infos (positions zeroed) */
|
|
22
42
|
initFromInfos(infos: GlyphInfo[]): void;
|
|
23
43
|
/** Set advance width for a glyph */
|
|
@@ -30,6 +50,20 @@ export declare class GlyphBuffer {
|
|
|
30
50
|
insertGlyph(index: number, info: GlyphInfo, position: GlyphPosition): void;
|
|
31
51
|
/** Remove glyphs in range [start, end) */
|
|
32
52
|
removeRange(start: number, end: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* Mark a glyph for deferred deletion. Much faster than removeRange for
|
|
55
|
+
* multiple deletions - call compact() once at end of GSUB phase.
|
|
56
|
+
*/
|
|
57
|
+
markDeleted(index: number): void;
|
|
58
|
+
/** Check if a glyph is marked for deletion */
|
|
59
|
+
isDeleted(index: number): boolean;
|
|
60
|
+
/** Returns true if there are pending deletions */
|
|
61
|
+
hasPendingDeletions(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Compact buffer by removing all marked-for-deletion glyphs in a single O(n) pass.
|
|
64
|
+
* Call this after GSUB phase.
|
|
65
|
+
*/
|
|
66
|
+
compact(): void;
|
|
33
67
|
/** Merge clusters from start to end (inclusive) */
|
|
34
68
|
mergeClusters(start: number, end: number): void;
|
|
35
69
|
/** Reverse glyph order (for RTL) */
|
|
@@ -19,6 +19,8 @@ export declare class UnicodeBuffer {
|
|
|
19
19
|
postContext: number[];
|
|
20
20
|
/** Add a string to the buffer */
|
|
21
21
|
addStr(text: string, startCluster?: number): this;
|
|
22
|
+
/** Append to existing buffer (slower path) */
|
|
23
|
+
private _addStrAppend;
|
|
22
24
|
/** Add codepoints directly */
|
|
23
25
|
addCodepoints(codepoints: number[], startCluster?: number): this;
|
|
24
26
|
/** Add a single codepoint */
|