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 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, caching their geometries for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate
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 tessellated glyphs, so a paragraph of 1000 words might only require 50 tessellations depending on the language. 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
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
- To optimize performance, three-text generates the geometry for each unique glyph or glyph cluster only once. The result is stored in a cache for reuse. This initial geometry creation is a multi-stage pipeline:
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 to avoiding redundant computation
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