three-text 0.3.5 → 0.4.1

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
@@ -13,7 +13,7 @@ High fidelity 3D mesh font geometry and text layout engine for the web
13
13
  ## Overview
14
14
 
15
15
  > [!CAUTION]
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
16
+ > three-text is an alpha release and the API may break rapidly. This warning will likely last until the end of March 2026. 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
18
  **three-text** is a 3D mesh font geometry and text layout library for the web. It supports TTF, OTF, and WOFF font files. For layout, it uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines and supports CJK and RTL scripts. three-text caches the geometries it generates for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate, and can be animated by re-drawing each frame with new coordinates
19
19
 
@@ -248,10 +248,10 @@ three-text generates high-fidelity 3D mesh geometry from font files. Unlike text
248
248
  Existing solutions take different approaches:
249
249
 
250
250
  - **Three.js native TextGeometry** uses fonts converted by facetype.js to JSON format. It creates 3D text by extruding flat 2D character outlines. While this produces true 3D geometry with depth, there is no support for real fonts or OpenType features needed for many of the world's scripts
251
- - **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
252
- - **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
251
+ - **three-bmfont-text** renders from pre-generated SDF atlas textures. Atlases are built offline at fixed sizes
252
+ - **troika-three-text** generates SDF glyphs at runtime from font files via HarfBuzz. More flexible than bmfont, but still a 2D image-space technique with artifacts up close
253
253
 
254
- 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
254
+ three-text generates true 3D geometry from font files via HarfBuzz. It is sharper at close distances than bitmap approaches, and produces 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. three-text also provides control over typesetting and paragraph justification via TeX-based parameters
255
255
 
256
256
  ## Library structure
257
257
 
@@ -309,15 +309,14 @@ Hyphenation uses patterns derived from the Tex hyphenation project, converted in
309
309
  The geometry pipeline runs once per unique glyph (or glyph cluster), with intermediate results cached to avoid redundant work:
310
310
 
311
311
  1. **Path collection**: HarfBuzz callbacks provide low level drawing operations
312
- 2. **Curve polygonization**: Uses Anti-Grain Geometry's recursive subdivision to convert bezier curves into polygons, concentrating points where curvature is high
312
+ 2. **Curve polygonization**: Flattens bezier curves into line segments, placing more points where curves are tight
313
313
  3. **Geometry optimization**:
314
- - **Visvalingam-Whyatt simplification**: removes vertices that contribute the least to the overall shape, preserving sharp corners and subtle curves
315
- - **Colinear point removal**: eliminates redundant points that lie on straight lines within angle tolerances
314
+ - **Visvalingam-Whyatt simplification**: removes vertices that form tiny triangles with their neighbors, smoothing out subtle bumps while preserving sharp corners
316
315
  4. **Overlap removal**: removes self-intersections and resolves overlapping paths between glyphs, preserving correct winding rules for triangulation
317
316
  5. **Triangulation**: converts cleaned 2D shapes into triangles using libtess2 with non-zero winding rule
318
317
  6. **Mesh construction**: generates 2D or 3D geometry with front faces and optional depth/extrusion (back faces and side walls)
319
318
 
320
- The multi-stage geometry approach (curve polygonization followed by cleanup, then triangulation) can reduce triangle counts while maintaining high visual fidelity and removing overlaps in variable fonts
319
+ The multi-stage geometry approach (curve polygonization followed by cleanup, then triangulation) reduces triangle counts and removes overlaps in variable fonts
321
320
 
322
321
  #### Glyph caching
323
322
 
@@ -340,54 +339,49 @@ When `depth` is 0, the library generates single-sided geometry, reducing triangl
340
339
 
341
340
  ### Curve fidelity
342
341
 
343
- The library converts bezier curves into line segments by recursively subdividing curves until they meet specified quality thresholds. This is based on the AGG library, attempting to place vertices only where they are needed to maintain the integrity of the curve. You can control curve fidelity with `distanceTolerance` and `angleTolerance`
342
+ Font outlines are bezier curves, but screens render curves flattened into many line segments. The library uses one of two modes:
344
343
 
345
- - `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
346
- - `angleTolerance`: The maximum angle in radians between segments at a join. This helps preserve sharp corners. Default is `0.2`
344
+ **Adaptive (default)** - The algorithm splits each curve at its midpoint, checks if the resulting line segment is close enough to the true curve, and recurses until it is. Tight curves get more segments; gentle curves get fewer. Two tolerances control when to stop subdividing:
347
345
 
348
- In general, this step helps more with time to first render than ongoing interactions in the scene
346
+ - `distanceTolerance`: how far the line segment can stray from the true curve, in font units. Lower values trace the curve more faithfully (default: `0.5`)
347
+ - `angleTolerance`: the maximum angle between adjacent segments, in radians. Smaller values preserve sharp corners better (default: `0.2`)
348
+
349
+ **Fixed-step** - Divides each curve into exactly `curveSteps` segments, regardless of curvature. Simpler and predictable. Overrides adaptive mode when set
349
350
 
350
351
  ```javascript
352
+ // Adaptive (default)
351
353
  const text = await Text.create({
352
- text: 'Sample text',
354
+ text: 'Sample',
353
355
  font: '/fonts/Font.ttf',
354
- curveFidelity: {
356
+ curveFidelity: {
355
357
  distanceTolerance: 0.2,
356
- angleTolerance: 0.1,
358
+ angleTolerance: 0.1
357
359
  },
358
360
  });
359
- ```
360
-
361
- ### Geometry optimization
362
-
363
- `three-text` uses a line simplification algorithm after creating lines to reduce the complexity of the shapes as well, which can be combined with `curveFidelity` for different types of control. It is enabled by default:
364
361
 
365
-
366
- ```javascript
367
- // Default optimization (automatic)
362
+ // Fixed-step: 8 segments per curve
368
363
  const text = await Text.create({
369
- text: 'Sample text',
364
+ text: 'Sample',
370
365
  font: '/fonts/Font.ttf',
366
+ curveSteps: 8,
371
367
  });
368
+ ```
369
+
370
+ ### Geometry optimization
371
+
372
+ After curve polygonization, the library applies Visvalingam-Whyatt simplification. Unlike curve flattening, which operates on each bezier independently, V-W sees the complete assembled path. The algorithm looks at each vertex and the triangle it forms with its two neighbors. Vertices that form tiny triangles - nearly collinear with their neighbors - are removed first. The process repeats until no triangle is smaller than `areaThreshold`, measured in square font units. Sharp corners form large triangles, so they survive; subtle bumps form small ones and get smoothed out:
372
373
 
373
- // Custom optimization settings
374
374
  ```javascript
375
375
  const text = await Text.create({
376
376
  text: 'Sample text',
377
377
  font: '/fonts/Font.ttf',
378
378
  geometryOptimization: {
379
- areaThreshold: 1.0, // Default: 1.0 (remove triangles < 1 font unit²)
380
- colinearThreshold: 0.0087, // Default: ~0.5° in radians
381
- minSegmentLength: 10, // Default: 10 font units
379
+ areaThreshold: 1.0, // remove triangles < 1 font unit²
382
380
  },
383
381
  });
384
382
  ```
385
383
 
386
- **The Visvalingam-Whyatt simplification** removes vertices whose removal creates triangles with area below the threshold
387
-
388
- **Colinear point removal** eliminates redundant vertices that lie on straight lines within the specified angle tolerance
389
-
390
- The default settings provide a significant reduction while maintaining high visual quality, but won't be perfect for every font. Adjust thresholds based on your quality requirements, performance constraints, and testing
384
+ Defaults work well for most fonts. Adjust thresholds based on quality requirements and testing
391
385
 
392
386
  ### Line breaking parameters
393
387
 
@@ -740,6 +734,7 @@ interface TextOptions {
740
734
  removeOverlaps?: boolean; // Override default overlap removal (auto-enabled for VF only)
741
735
  perGlyphAttributes?: boolean; // Keep per-glyph identity and add per-glyph shader attributes
742
736
  color?: [number, number, number] | ColorOptions; // Text coloring (simple or complex)
737
+ curveSteps?: number; // Fixed segments per curve; overrides curveFidelity when set
743
738
  curveFidelity?: CurveFidelityConfig;
744
739
  geometryOptimization?: GeometryOptimizationOptions;
745
740
  layout?: LayoutOptions;
@@ -796,12 +791,10 @@ interface CurveFidelityConfig {
796
791
 
797
792
  ```typescript
798
793
  interface GeometryOptimizationOptions {
799
- enabled?: boolean; // Enable geometry optimization (default: true)
800
- areaThreshold?: number; // Min triangle area for Visvalingam-Whyatt (default: 1.0)
801
- colinearThreshold?: number; // Max angle for colinear removal in radians (default: 0.0087)
802
- minSegmentLength?: number; // Min segment length in font units (default: 10)
794
+ enabled?: boolean; // Enable optimization (default: true)
795
+ areaThreshold?: number; // Min triangle area in font units² (default: 1.0)
803
796
  }
804
- ````
797
+ ```
805
798
 
806
799
  #### TextGeometryInfo (Core)
807
800
 
@@ -827,7 +820,6 @@ interface TextGeometryInfo {
827
820
  trianglesGenerated: number;
828
821
  verticesGenerated: number;
829
822
  pointsRemovedByVisvalingam: number;
830
- pointsRemovedByColinear: number;
831
823
  originalPointCount: number;
832
824
  };
833
825
  query(options: TextQueryOptions): TextRange[];