three-text 0.2.5 → 0.2.7
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 +67 -27
- package/dist/index.cjs +431 -70
- package/dist/index.d.ts +18 -1
- package/dist/index.js +431 -70
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +431 -70
- package/dist/index.umd.min.js +2 -2
- package/dist/p5/index.cjs +4 -8
- package/dist/p5/index.js +4 -8
- package/dist/three/react.d.ts +11 -1
- package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +1 -0
- package/dist/types/core/font/FontMetadata.d.ts +7 -0
- package/dist/types/core/font/constants.d.ts +10 -0
- package/dist/types/core/shaping/fontFeatures.d.ts +3 -0
- package/dist/types/core/types.d.ts +11 -1
- package/dist/types/utils/LRUCache.d.ts +38 -0
- package/dist/types/utils/{DebugLogger.d.ts → Logger.d.ts} +2 -2
- package/dist/types/utils/PerformanceLogger.d.ts +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A high fidelity 3D font renderer and text layout engine for the web
|
|
|
15
15
|
> [!CAUTION]
|
|
16
16
|
> three-text is an alpha release and the API may break rapidly. This warning will last at least through the end of 2025. If API stability is important to you, consider pinning your version. Community feedback is encouraged; please open an issue if you have any suggestions or feedback, thank you
|
|
17
17
|
|
|
18
|
-
**three-text** renders and formats text from TTF, OTF, and WOFF font files as 3D geometry. It uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines, and turns font outlines into 3D shapes on the fly
|
|
18
|
+
**three-text** renders and formats text from TTF, OTF, and WOFF font files as 3D geometry. It uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines, and turns font outlines into 3D shapes on the fly. Glyph geometry is cached for low CPU overhead, especially in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate
|
|
19
19
|
|
|
20
20
|
The library has a framework-agnostic core that returns raw vertex data, with lightweight adapters for [Three.js](https://threejs.org), [React Three Fiber](https://docs.pmnd.rs/react-three-fiber), [p5.js](https://p5js.org), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
21
21
|
|
|
@@ -235,7 +235,7 @@ Then navigate to `http://localhost:3000`
|
|
|
235
235
|
|
|
236
236
|
## Why three-text?
|
|
237
237
|
|
|
238
|
-
three-text generates high-fidelity 3D mesh geometry from font files. Unlike texture-based approaches, it produces true geometry that can be lit, shaded, and manipulated like any 3D model
|
|
238
|
+
three-text generates high-fidelity 3D mesh geometry from font files. Unlike texture-based approaches, it produces true geometry that can be lit, shaded, and manipulated like any 3D model
|
|
239
239
|
|
|
240
240
|
Existing solutions take different approaches:
|
|
241
241
|
|
|
@@ -243,7 +243,7 @@ Existing solutions take different approaches:
|
|
|
243
243
|
- **three-bmfont-text** is a 2D approach for Three.js, using pre-rendered bitmap fonts with SDF support. Texture atlases are generated at specific sizes, and artifacts are apparent up close
|
|
244
244
|
- **troika-three-text** uses MSDF, which improves quality, and like three-text, it is built on HarfBuzz, which provides substantial language coverage, but is ultimately a 2D technique in image space. For flat text that does not need formatting or extrusion, and where artifacts are acceptable up close, troika works well
|
|
245
245
|
|
|
246
|
-
three-text generates true 3D geometry from font files via HarfBuzz. It is sharper at close distances than bitmap approaches when flat, and produces real mesh data that can be used with any rendering system. The library caches
|
|
246
|
+
three-text generates true 3D geometry from font files via HarfBuzz. It is sharper at close distances than bitmap approaches when flat, and produces real mesh data that can be used with any rendering system. The library caches glyph geometry, so a paragraph of 1000 words might only require 50 unique glyphs to be processed. This makes it well-suited to longer texts. In addition to performance considerations, three-text provides control over typesetting and paragraph justification via TeX-based parameters
|
|
247
247
|
|
|
248
248
|
## Library structure
|
|
249
249
|
|
|
@@ -298,14 +298,14 @@ Hyphenation uses patterns derived from the Tex hyphenation project, converted in
|
|
|
298
298
|
|
|
299
299
|
### Geometry generation and optimization
|
|
300
300
|
|
|
301
|
-
|
|
301
|
+
The geometry pipeline runs once per unique glyph (or glyph cluster), with intermediate results cached to avoid redundant work:
|
|
302
302
|
|
|
303
303
|
1. **Path collection**: HarfBuzz callbacks provide low level drawing operations
|
|
304
304
|
2. **Curve polygonization**: Uses Anti-Grain Geometry's recursive subdivision to convert bezier curves into polygons, concentrating points where curvature is high
|
|
305
305
|
3. **Geometry optimization**:
|
|
306
306
|
- **Visvalingam-Whyatt simplification**: removes vertices that contribute the least to the overall shape, preserving sharp corners and subtle curves
|
|
307
307
|
- **Colinear point removal**: eliminates redundant points that lie on straight lines within angle tolerances
|
|
308
|
-
4. **Overlap removal**: removes self-intersections and resolves overlapping paths between glyphs, preserving correct winding rules for triangulation
|
|
308
|
+
4. **Overlap removal**: removes self-intersections and resolves overlapping paths between glyphs, preserving correct winding rules for triangulation
|
|
309
309
|
5. **Triangulation**: converts cleaned 2D shapes into triangles using libtess2 with non-zero winding rule
|
|
310
310
|
6. **Mesh construction**: generates 2D or 3D geometry with front faces and optional depth/extrusion (back faces and side walls)
|
|
311
311
|
|
|
@@ -315,7 +315,7 @@ The multi-stage geometry approach (curve polygonization followed by cleanup, the
|
|
|
315
315
|
|
|
316
316
|
The library uses a hybrid caching strategy to maximize performance while ensuring visual correctness
|
|
317
317
|
|
|
318
|
-
By default, it operates with glyph-level cache. The geometry for each unique character (`a`, `b`, `c`...) is generated only once and stored for reuse
|
|
318
|
+
By default, it operates with glyph-level cache. The geometry for each unique character (`a`, `b`, `c`...) is generated only once and stored for reuse, avoiding redundant computation
|
|
319
319
|
|
|
320
320
|
For text with tight tracking, connected scripts, or complex kerning pairs, individual glyphs can overlap. When an overlap within a word is found, the entire word is treated as a single unit and escalated to a word-level cache. All of its glyphs are tessellated together to correctly resolve the overlaps, and the resulting geometry for the word is cached
|
|
321
321
|
|
|
@@ -334,7 +334,7 @@ The library converts bezier curves into line segments by recursively subdividing
|
|
|
334
334
|
- `distanceTolerance`: The maximum allowed deviation of the curve from a straight line segment, measured in font units. Lower values produce higher fidelity and more vertices. Default is `0.5`, which is nearly imperceptable without extrusion
|
|
335
335
|
- `angleTolerance`: The maximum angle in radians between segments at a join. This helps preserve sharp corners. Default is `0.2`
|
|
336
336
|
|
|
337
|
-
In general, this step helps more with time to first render than ongoing interactions in the scene
|
|
337
|
+
In general, this step helps more with time to first render than ongoing interactions in the scene
|
|
338
338
|
|
|
339
339
|
```javascript
|
|
340
340
|
// Using the default configuration
|
|
@@ -466,25 +466,6 @@ const text = await Text.create({
|
|
|
466
466
|
});
|
|
467
467
|
```
|
|
468
468
|
|
|
469
|
-
### Per-glyph animation attributes
|
|
470
|
-
|
|
471
|
-
For shader-based animations and interactive effects, the library can generate per-vertex attributes that identify which glyph each vertex belongs to:
|
|
472
|
-
|
|
473
|
-
```javascript
|
|
474
|
-
const text = await Text.create({
|
|
475
|
-
text: 'Sample text',
|
|
476
|
-
font: '/fonts/Font.ttf',
|
|
477
|
-
separateGlyphsWithAttributes: true,
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// Geometry includes these vertex attributes:
|
|
481
|
-
// - glyphCenter (vec3): center point of each glyph
|
|
482
|
-
// - glyphIndex (float): sequential glyph index
|
|
483
|
-
// - glyphLineIndex (float): line number
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
This option bypasses overlap-based clustering and adds vertex attributes suitable for per-character manipulation in vertex shaders. Each unique glyph is still tessellated only once and cached for reuse. The tradeoff is potential visual artifacts where glyphs actually overlap (tight kerning, cursive scripts)
|
|
487
|
-
|
|
488
469
|
### Variable fonts
|
|
489
470
|
|
|
490
471
|
Variable fonts allow dynamic adjustment of typographic characteristics through variation axes:
|
|
@@ -537,6 +518,47 @@ const text = await Text.create({
|
|
|
537
518
|
});
|
|
538
519
|
```
|
|
539
520
|
|
|
521
|
+
### OpenType features
|
|
522
|
+
|
|
523
|
+
The `fontFeatures` option controls OpenType layout features using 4-character tags from the [feature registry](https://learn.microsoft.com/en-us/typography/opentype/spec/featuretags):
|
|
524
|
+
|
|
525
|
+
```javascript
|
|
526
|
+
const text = await Text.create({
|
|
527
|
+
text: 'Difficult ffi ffl',
|
|
528
|
+
font: '/fonts/Font.ttf',
|
|
529
|
+
fontFeatures: {
|
|
530
|
+
liga: true,
|
|
531
|
+
dlig: true,
|
|
532
|
+
kern: false,
|
|
533
|
+
ss01: 1,
|
|
534
|
+
cv01: 3,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Values can be boolean (`true`/`false`) to enable or disable, or numeric for features accepting variant indices. Explicitly disabling a feature overrides the font's defaults
|
|
540
|
+
|
|
541
|
+
Common tags include [`liga`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#liga) (ligatures), [`kern`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#kern) (kerning), [`calt`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#calt) (contextual alternates), and [`smcp`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#smcp) (small capitals). Number styling uses [`lnum`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#lnum)/[`onum`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#onum)/[`tnum`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum). Stylistic alternates are [`ss01`-`ss20`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#ss01--ss20) and [`cv01`-`cv99`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#cv01--cv99). Feature availability depends on the font
|
|
542
|
+
|
|
543
|
+
### Per-glyph attributes
|
|
544
|
+
|
|
545
|
+
For shader-based animations and interactive effects, the library can generate per-vertex attributes that identify which glyph each vertex belongs to:
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
const text = await Text.create({
|
|
549
|
+
text: 'Sample text',
|
|
550
|
+
font: '/fonts/Font.ttf',
|
|
551
|
+
separateGlyphsWithAttributes: true,
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Geometry includes these vertex attributes:
|
|
555
|
+
// - glyphCenter (vec3): center point of each glyph
|
|
556
|
+
// - glyphIndex (float): sequential glyph index
|
|
557
|
+
// - glyphLineIndex (float): line number
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
This option bypasses overlap-based clustering and adds vertex attributes suitable for per-character manipulation in vertex shaders. Each unique glyph is still tessellated only once and cached for reuse. The tradeoff is potential visual artifacts where glyphs actually overlap (tight kerning, cursive scripts)
|
|
561
|
+
|
|
540
562
|
## Querying text content
|
|
541
563
|
|
|
542
564
|
After creating text geometry, use the `query()` method to find text ranges:
|
|
@@ -634,7 +656,7 @@ The library's full TypeScript definitions are the most complete source of truth
|
|
|
634
656
|
|
|
635
657
|
#### `Text.create(options: TextOptions): Promise<TextGeometryInfo>`
|
|
636
658
|
|
|
637
|
-
Creates text geometry with automatic font loading and HarfBuzz initialization
|
|
659
|
+
Creates text geometry with automatic font loading and HarfBuzz initialization
|
|
638
660
|
|
|
639
661
|
**Core (`three-text`) returns:**
|
|
640
662
|
- `vertices: Float32Array` - Vertex positions
|
|
@@ -694,6 +716,7 @@ interface TextOptions {
|
|
|
694
716
|
lineHeight?: number; // Line height multiplier (default: 1.0)
|
|
695
717
|
letterSpacing?: number; // Letter spacing as a fraction of em (e.g., 0.05)
|
|
696
718
|
fontVariations?: { [key: string]: number }; // Variable font axis settings
|
|
719
|
+
fontFeatures?: { [tag: string]: boolean | number }; // OpenType feature settings
|
|
697
720
|
removeOverlaps?: boolean; // Override default overlap removal (auto-enabled for VF only)
|
|
698
721
|
separateGlyphsWithAttributes?: boolean; // Force individual glyph tessellation and add shader attributes
|
|
699
722
|
color?: [number, number, number] | ColorOptions; // Text coloring (simple or complex)
|
|
@@ -935,6 +958,23 @@ npm test -- --coverage # Coverage report
|
|
|
935
958
|
|
|
936
959
|
Tests use mocked HarfBuzz and tessellation libraries for fast execution without requiring WASM files
|
|
937
960
|
|
|
961
|
+
### Benchmarking
|
|
962
|
+
|
|
963
|
+
For performance of the real pipeline using HarfBuzz, including shaping, layout, tessellation, extrusion, there is a dedicated benchmark:
|
|
964
|
+
|
|
965
|
+
```bash
|
|
966
|
+
npm run benchmark
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
This runs a Node/Vitest scenario that:
|
|
970
|
+
|
|
971
|
+
- initializes HarfBuzz from `hb.wasm` via `Text.setHarfBuzzBuffer`
|
|
972
|
+
- loads Nimbus Sans and tests the example paragraph from the demos
|
|
973
|
+
- performs a small number of cold runs followed by warm runs of `Text.create()` with justification and hyphenation enabled
|
|
974
|
+
- prints a per-stage timing table (font load, line breaking, polygonization, tessellation, extrusion, and overall geometry creation)
|
|
975
|
+
|
|
976
|
+
Use this to compare changes locally; it is meant as a sanity check on real work rather than a reliable micro-benchmark
|
|
977
|
+
|
|
938
978
|
## Build system
|
|
939
979
|
|
|
940
980
|
### Development
|