three-text 0.1.1 → 0.2.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.
Files changed (42) hide show
  1. package/README.md +166 -156
  2. package/dist/index.cjs +368 -82
  3. package/dist/index.d.ts +46 -17
  4. package/dist/index.js +367 -81
  5. package/dist/index.min.cjs +2 -2
  6. package/dist/index.min.js +2 -2
  7. package/dist/index.umd.js +372 -84
  8. package/dist/index.umd.min.js +2 -2
  9. package/dist/p5/index.cjs +33 -0
  10. package/dist/p5/index.d.ts +19 -0
  11. package/dist/p5/index.js +31 -0
  12. package/dist/three/index.cjs +50 -0
  13. package/dist/three/index.d.ts +29 -0
  14. package/dist/three/index.js +48 -0
  15. package/dist/{react/index.cjs → three/react.cjs} +14 -4
  16. package/dist/three/react.d.ts +346 -0
  17. package/dist/{react/index.js → three/react.js} +14 -4
  18. package/dist/types/core/Text.d.ts +1 -1
  19. package/dist/types/core/cache/GlyphCache.d.ts +4 -4
  20. package/dist/types/core/cache/GlyphContourCollector.d.ts +2 -2
  21. package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +3 -2
  22. package/dist/types/core/geometry/BoundaryClusterer.d.ts +2 -2
  23. package/dist/types/core/geometry/Polygonizer.d.ts +3 -3
  24. package/dist/types/core/layout/TextLayout.d.ts +1 -2
  25. package/dist/types/core/shaping/TextShaper.d.ts +1 -2
  26. package/dist/types/core/types.d.ts +13 -16
  27. package/dist/types/core/vectors.d.ts +75 -0
  28. package/dist/types/p5/index.d.ts +17 -0
  29. package/dist/types/{react → three}/ThreeText.d.ts +2 -2
  30. package/dist/types/three/index.d.ts +21 -0
  31. package/dist/types/three/react.d.ts +10 -0
  32. package/dist/types/webgl/index.d.ts +48 -0
  33. package/dist/types/webgpu/index.d.ts +16 -0
  34. package/dist/webgl/index.cjs +88 -0
  35. package/dist/webgl/index.d.ts +51 -0
  36. package/dist/webgl/index.js +86 -0
  37. package/dist/webgpu/index.cjs +99 -0
  38. package/dist/webgpu/index.d.ts +19 -0
  39. package/dist/webgpu/index.js +97 -0
  40. package/package.json +22 -6
  41. package/dist/react/index.d.ts +0 -18
  42. package/dist/types/react/index.d.ts +0 -2
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * three-text v0.1.1
2
+ * three-text v0.2.1
3
3
  * Copyright (C) 2025 Countertype LLC
4
4
  *
5
5
  * This program is free software: you can redistribute it and/or modify
@@ -12,10 +12,10 @@
12
12
  * This software includes third-party code - see LICENSE_THIRD_PARTY for details.
13
13
  */
14
14
  (function (global, factory) {
15
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) :
16
- typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) :
17
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreeText = {}, global.THREE));
18
- })(this, (function (exports, three) {
15
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
16
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
17
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreeText = {}));
18
+ })(this, (function (exports) {
19
19
  // Cached flag check at module load time for zero-cost logging
20
20
  const isLogEnabled = (() => {
21
21
  if (typeof window !== 'undefined' && window.THREE_TEXT_LOG) {
@@ -205,7 +205,7 @@
205
205
  this.allNodes = new Set();
206
206
  }
207
207
  getKey(position, fitness) {
208
- return `${position}_${fitness}`;
208
+ return (position << 2) | fitness;
209
209
  }
210
210
  insert(node) {
211
211
  const key = this.getKey(node.position, node.fitness);
@@ -1257,7 +1257,7 @@
1257
1257
  }
1258
1258
  return { lines };
1259
1259
  }
1260
- applyAlignment(geometry, options) {
1260
+ applyAlignment(vertices, options) {
1261
1261
  const { width, align, planeBounds } = options;
1262
1262
  let offset = 0;
1263
1263
  const adjustedBounds = {
@@ -1273,7 +1273,10 @@
1273
1273
  offset = width - planeBounds.max.x;
1274
1274
  }
1275
1275
  if (offset !== 0) {
1276
- geometry.translate(offset, 0, 0);
1276
+ // Translate vertices directly
1277
+ for (let i = 0; i < vertices.length; i += 3) {
1278
+ vertices[i] += offset;
1279
+ }
1277
1280
  adjustedBounds.min.x += offset;
1278
1281
  adjustedBounds.max.x += offset;
1279
1282
  }
@@ -1728,6 +1731,268 @@
1728
1731
  }
1729
1732
  }
1730
1733
 
1734
+ // Bector and bounding box types for core
1735
+ // 2D Vector
1736
+ class Vec2 {
1737
+ constructor(x = 0, y = 0) {
1738
+ this.x = x;
1739
+ this.y = y;
1740
+ }
1741
+ set(x, y) {
1742
+ this.x = x;
1743
+ this.y = y;
1744
+ return this;
1745
+ }
1746
+ clone() {
1747
+ return new Vec2(this.x, this.y);
1748
+ }
1749
+ copy(v) {
1750
+ this.x = v.x;
1751
+ this.y = v.y;
1752
+ return this;
1753
+ }
1754
+ add(v) {
1755
+ this.x += v.x;
1756
+ this.y += v.y;
1757
+ return this;
1758
+ }
1759
+ sub(v) {
1760
+ this.x -= v.x;
1761
+ this.y -= v.y;
1762
+ return this;
1763
+ }
1764
+ multiply(scalar) {
1765
+ this.x *= scalar;
1766
+ this.y *= scalar;
1767
+ return this;
1768
+ }
1769
+ divide(scalar) {
1770
+ this.x /= scalar;
1771
+ this.y /= scalar;
1772
+ return this;
1773
+ }
1774
+ length() {
1775
+ return Math.sqrt(this.x * this.x + this.y * this.y);
1776
+ }
1777
+ lengthSq() {
1778
+ return this.x * this.x + this.y * this.y;
1779
+ }
1780
+ normalize() {
1781
+ const len = this.length();
1782
+ if (len > 0) {
1783
+ this.divide(len);
1784
+ }
1785
+ return this;
1786
+ }
1787
+ dot(v) {
1788
+ return this.x * v.x + this.y * v.y;
1789
+ }
1790
+ distanceTo(v) {
1791
+ const dx = this.x - v.x;
1792
+ const dy = this.y - v.y;
1793
+ return Math.sqrt(dx * dx + dy * dy);
1794
+ }
1795
+ distanceToSquared(v) {
1796
+ const dx = this.x - v.x;
1797
+ const dy = this.y - v.y;
1798
+ return dx * dx + dy * dy;
1799
+ }
1800
+ equals(v) {
1801
+ return this.x === v.x && this.y === v.y;
1802
+ }
1803
+ angle() {
1804
+ return Math.atan2(this.y, this.x);
1805
+ }
1806
+ }
1807
+ // 3D Vector
1808
+ class Vec3 {
1809
+ constructor(x = 0, y = 0, z = 0) {
1810
+ this.x = x;
1811
+ this.y = y;
1812
+ this.z = z;
1813
+ }
1814
+ set(x, y, z) {
1815
+ this.x = x;
1816
+ this.y = y;
1817
+ this.z = z;
1818
+ return this;
1819
+ }
1820
+ clone() {
1821
+ return new Vec3(this.x, this.y, this.z);
1822
+ }
1823
+ copy(v) {
1824
+ this.x = v.x;
1825
+ this.y = v.y;
1826
+ this.z = v.z;
1827
+ return this;
1828
+ }
1829
+ add(v) {
1830
+ this.x += v.x;
1831
+ this.y += v.y;
1832
+ this.z += v.z;
1833
+ return this;
1834
+ }
1835
+ sub(v) {
1836
+ this.x -= v.x;
1837
+ this.y -= v.y;
1838
+ this.z -= v.z;
1839
+ return this;
1840
+ }
1841
+ multiply(scalar) {
1842
+ this.x *= scalar;
1843
+ this.y *= scalar;
1844
+ this.z *= scalar;
1845
+ return this;
1846
+ }
1847
+ divide(scalar) {
1848
+ this.x /= scalar;
1849
+ this.y /= scalar;
1850
+ this.z /= scalar;
1851
+ return this;
1852
+ }
1853
+ length() {
1854
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
1855
+ }
1856
+ lengthSq() {
1857
+ return this.x * this.x + this.y * this.y + this.z * this.z;
1858
+ }
1859
+ normalize() {
1860
+ const len = this.length();
1861
+ if (len > 0) {
1862
+ this.divide(len);
1863
+ }
1864
+ return this;
1865
+ }
1866
+ dot(v) {
1867
+ return this.x * v.x + this.y * v.y + this.z * v.z;
1868
+ }
1869
+ cross(v) {
1870
+ const x = this.y * v.z - this.z * v.y;
1871
+ const y = this.z * v.x - this.x * v.z;
1872
+ const z = this.x * v.y - this.y * v.x;
1873
+ this.x = x;
1874
+ this.y = y;
1875
+ this.z = z;
1876
+ return this;
1877
+ }
1878
+ distanceTo(v) {
1879
+ const dx = this.x - v.x;
1880
+ const dy = this.y - v.y;
1881
+ const dz = this.z - v.z;
1882
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
1883
+ }
1884
+ distanceToSquared(v) {
1885
+ const dx = this.x - v.x;
1886
+ const dy = this.y - v.y;
1887
+ const dz = this.z - v.z;
1888
+ return dx * dx + dy * dy + dz * dz;
1889
+ }
1890
+ equals(v) {
1891
+ return this.x === v.x && this.y === v.y && this.z === v.z;
1892
+ }
1893
+ }
1894
+ // 3D Bounding Box
1895
+ class Box3 {
1896
+ constructor(min = new Vec3(Infinity, Infinity, Infinity), max = new Vec3(-Infinity, -Infinity, -Infinity)) {
1897
+ this.min = min;
1898
+ this.max = max;
1899
+ }
1900
+ set(min, max) {
1901
+ this.min.copy(min);
1902
+ this.max.copy(max);
1903
+ return this;
1904
+ }
1905
+ setFromPoints(points) {
1906
+ this.makeEmpty();
1907
+ for (let i = 0; i < points.length; i++) {
1908
+ this.expandByPoint(points[i]);
1909
+ }
1910
+ return this;
1911
+ }
1912
+ makeEmpty() {
1913
+ this.min.x = this.min.y = this.min.z = Infinity;
1914
+ this.max.x = this.max.y = this.max.z = -Infinity;
1915
+ return this;
1916
+ }
1917
+ isEmpty() {
1918
+ return (this.max.x < this.min.x ||
1919
+ this.max.y < this.min.y ||
1920
+ this.max.z < this.min.z);
1921
+ }
1922
+ expandByPoint(point) {
1923
+ this.min.x = Math.min(this.min.x, point.x);
1924
+ this.min.y = Math.min(this.min.y, point.y);
1925
+ this.min.z = Math.min(this.min.z, point.z);
1926
+ this.max.x = Math.max(this.max.x, point.x);
1927
+ this.max.y = Math.max(this.max.y, point.y);
1928
+ this.max.z = Math.max(this.max.z, point.z);
1929
+ return this;
1930
+ }
1931
+ expandByScalar(scalar) {
1932
+ this.min.x -= scalar;
1933
+ this.min.y -= scalar;
1934
+ this.min.z -= scalar;
1935
+ this.max.x += scalar;
1936
+ this.max.y += scalar;
1937
+ this.max.z += scalar;
1938
+ return this;
1939
+ }
1940
+ containsPoint(point) {
1941
+ return (point.x >= this.min.x &&
1942
+ point.x <= this.max.x &&
1943
+ point.y >= this.min.y &&
1944
+ point.y <= this.max.y &&
1945
+ point.z >= this.min.z &&
1946
+ point.z <= this.max.z);
1947
+ }
1948
+ containsBox(box) {
1949
+ return (this.min.x <= box.min.x &&
1950
+ box.max.x <= this.max.x &&
1951
+ this.min.y <= box.min.y &&
1952
+ box.max.y <= this.max.y &&
1953
+ this.min.z <= box.min.z &&
1954
+ box.max.z <= this.max.z);
1955
+ }
1956
+ intersectsBox(box) {
1957
+ return (box.max.x >= this.min.x &&
1958
+ box.min.x <= this.max.x &&
1959
+ box.max.y >= this.min.y &&
1960
+ box.min.y <= this.max.y &&
1961
+ box.max.z >= this.min.z &&
1962
+ box.min.z <= this.max.z);
1963
+ }
1964
+ getCenter(target = new Vec3()) {
1965
+ return this.isEmpty()
1966
+ ? target.set(0, 0, 0)
1967
+ : target.set((this.min.x + this.max.x) * 0.5, (this.min.y + this.max.y) * 0.5, (this.min.z + this.max.z) * 0.5);
1968
+ }
1969
+ getSize(target = new Vec3()) {
1970
+ return this.isEmpty()
1971
+ ? target.set(0, 0, 0)
1972
+ : target.set(this.max.x - this.min.x, this.max.y - this.min.y, this.max.z - this.min.z);
1973
+ }
1974
+ clone() {
1975
+ return new Box3(this.min.clone(), this.max.clone());
1976
+ }
1977
+ copy(box) {
1978
+ this.min.copy(box.min);
1979
+ this.max.copy(box.max);
1980
+ return this;
1981
+ }
1982
+ union(box) {
1983
+ this.min.x = Math.min(this.min.x, box.min.x);
1984
+ this.min.y = Math.min(this.min.y, box.min.y);
1985
+ this.min.z = Math.min(this.min.z, box.min.z);
1986
+ this.max.x = Math.max(this.max.x, box.max.x);
1987
+ this.max.y = Math.max(this.max.y, box.max.y);
1988
+ this.max.z = Math.max(this.max.z, box.max.z);
1989
+ return this;
1990
+ }
1991
+ equals(box) {
1992
+ return box.min.equals(this.min) && box.max.equals(this.max);
1993
+ }
1994
+ }
1995
+
1731
1996
  var WINDING;
1732
1997
  (function (WINDING) {
1733
1998
  WINDING[WINDING["ODD"] = 0] = "ODD";
@@ -3912,7 +4177,7 @@
3912
4177
  const triangleIndices = triangulatedData.indices;
3913
4178
  for (let i = 0; i < points.length; i += 2) {
3914
4179
  vertices.push(points[i], points[i + 1], 0);
3915
- normals.push(0, 0, 1); // Front-facing normal for DoubleSide compatibility
4180
+ normals.push(0, 0, -1);
3916
4181
  }
3917
4182
  // Add triangle indices
3918
4183
  for (let i = 0; i < triangleIndices.length; i++) {
@@ -3948,8 +4213,8 @@
3948
4213
  const p0y = points[i + 1];
3949
4214
  const p1x = points[i + 2];
3950
4215
  const p1y = points[i + 3];
3951
- const edge = new three.Vector2(p1x - p0x, p1y - p0y);
3952
- const normal = new three.Vector2(edge.y, -edge.x).normalize();
4216
+ const edge = new Vec2(p1x - p0x, p1y - p0y);
4217
+ const normal = new Vec2(edge.y, -edge.x).normalize();
3953
4218
  const wallBaseIndex = vertices.length / 3;
3954
4219
  vertices.push(p0x, p0y, 0, p1x, p1y, 0, p0x, p0y, depth, p1x, p1y, depth);
3955
4220
  normals.push(normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0);
@@ -4259,8 +4524,8 @@
4259
4524
  const prev = points[i - 1];
4260
4525
  const current = points[i];
4261
4526
  const next = points[i + 1];
4262
- const v1 = new three.Vector2(current.x - prev.x, current.y - prev.y);
4263
- const v2 = new three.Vector2(next.x - current.x, next.y - current.y);
4527
+ const v1 = new Vec2(current.x - prev.x, current.y - prev.y);
4528
+ const v2 = new Vec2(next.x - current.x, next.y - current.y);
4264
4529
  const angle = Math.abs(v1.angle() - v2.angle());
4265
4530
  const normalizedAngle = Math.min(angle, 2 * Math.PI - angle);
4266
4531
  if (normalizedAngle > threshold ||
@@ -4374,12 +4639,9 @@
4374
4639
  const dx = x3 - x1;
4375
4640
  const dy = y3 - y1;
4376
4641
  const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
4377
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4378
4642
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4379
4643
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4380
- const distanceTolerance = curveLength > 0
4381
- ? baseTolerance * baseTolerance
4382
- : baseTolerance * baseTolerance;
4644
+ const distanceTolerance = baseTolerance * baseTolerance;
4383
4645
  if (d > COLLINEARITY_EPSILON) {
4384
4646
  // Regular case
4385
4647
  // Recursion terminates when the curve is flat enough (deviation from straight line is within tolerance)
@@ -4444,12 +4706,9 @@
4444
4706
  const dy = y4 - y1;
4445
4707
  const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
4446
4708
  const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
4447
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4448
4709
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4449
4710
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4450
- const distanceTolerance = curveLength > 0
4451
- ? baseTolerance * baseTolerance
4452
- : baseTolerance * baseTolerance;
4711
+ const distanceTolerance = baseTolerance * baseTolerance;
4453
4712
  let switchCondition = 0;
4454
4713
  if (d2 > COLLINEARITY_EPSILON)
4455
4714
  switchCondition |= 1;
@@ -4557,7 +4816,7 @@
4557
4816
  this.recursiveCubic(x1234, y1234, x234, y234, x34, y34, x4, y4, points, level + 1);
4558
4817
  }
4559
4818
  addPoint(x, y, points) {
4560
- const newPoint = new three.Vector2(x, y);
4819
+ const newPoint = new Vec2(x, y);
4561
4820
  if (points.length === 0) {
4562
4821
  points.push(newPoint);
4563
4822
  return;
@@ -4580,13 +4839,13 @@
4580
4839
  this.currentPath = null;
4581
4840
  this.currentPoint = null;
4582
4841
  this.currentGlyphBounds = {
4583
- min: new three.Vector2(Infinity, Infinity),
4584
- max: new three.Vector2(-Infinity, -Infinity)
4842
+ min: new Vec2(Infinity, Infinity),
4843
+ max: new Vec2(-Infinity, -Infinity)
4585
4844
  };
4586
4845
  this.collectedGlyphs = [];
4587
4846
  this.glyphPositions = [];
4588
4847
  this.glyphTextIndices = [];
4589
- this.currentPosition = new three.Vector2(0, 0);
4848
+ this.currentPosition = new Vec2(0, 0);
4590
4849
  this.polygonizer = new Polygonizer(curveFidelityConfig);
4591
4850
  this.pathOptimizer = new PathOptimizer({
4592
4851
  ...DEFAULT_OPTIMIZATION_CONFIG,
@@ -4641,7 +4900,7 @@
4641
4900
  if (this.currentPath) {
4642
4901
  this.finishPath();
4643
4902
  }
4644
- this.currentPoint = new three.Vector2(x, y);
4903
+ this.currentPoint = new Vec2(x, y);
4645
4904
  this.updateBounds(this.currentPoint);
4646
4905
  this.currentPath = {
4647
4906
  points: [this.currentPoint],
@@ -4651,7 +4910,7 @@
4651
4910
  onLineTo(x, y) {
4652
4911
  if (!this.currentPath || !this.currentPoint)
4653
4912
  return;
4654
- const point = new three.Vector2(x, y);
4913
+ const point = new Vec2(x, y);
4655
4914
  this.updateBounds(point);
4656
4915
  this.currentPath.points.push(point);
4657
4916
  this.currentPoint = point;
@@ -4660,8 +4919,8 @@
4660
4919
  if (!this.currentPath || !this.currentPoint)
4661
4920
  return;
4662
4921
  const start = this.currentPoint;
4663
- const control = new three.Vector2(cx, cy);
4664
- const end = new three.Vector2(x, y);
4922
+ const control = new Vec2(cx, cy);
4923
+ const end = new Vec2(x, y);
4665
4924
  const dx = end.x - start.x;
4666
4925
  const dy = end.y - start.y;
4667
4926
  const d = Math.abs((control.x - end.x) * dy - (control.y - end.y) * dx);
@@ -4682,9 +4941,9 @@
4682
4941
  if (!this.currentPath || !this.currentPoint)
4683
4942
  return;
4684
4943
  const start = this.currentPoint;
4685
- const control1 = new three.Vector2(c1x, c1y);
4686
- const control2 = new three.Vector2(c2x, c2y);
4687
- const end = new three.Vector2(x, y);
4944
+ const control1 = new Vec2(c1x, c1y);
4945
+ const control2 = new Vec2(c2x, c2y);
4946
+ const end = new Vec2(x, y);
4688
4947
  const dx = end.x - start.x;
4689
4948
  const dy = end.y - start.y;
4690
4949
  const d1 = Math.abs((control1.x - end.x) * dy - (control1.y - end.y) * dx);
@@ -4726,7 +4985,7 @@
4726
4985
  this.currentGlyphBounds.max.y = Math.max(this.currentGlyphBounds.max.y, point.y);
4727
4986
  }
4728
4987
  getCollectedGlyphs() {
4729
- // Make sure to finish any pending glyph
4988
+ // Finish any pending glyph
4730
4989
  if (this.currentGlyphPaths.length > 0) {
4731
4990
  this.finishGlyph();
4732
4991
  }
@@ -4749,8 +5008,8 @@
4749
5008
  this.currentTextIndex = 0;
4750
5009
  this.currentPosition.set(0, 0);
4751
5010
  this.currentGlyphBounds = {
4752
- min: new three.Vector2(Infinity, Infinity),
4753
- max: new three.Vector2(-Infinity, -Infinity)
5011
+ min: new Vec2(Infinity, Infinity),
5012
+ max: new Vec2(-Infinity, -Infinity)
4754
5013
  };
4755
5014
  }
4756
5015
  setCurveFidelityConfig(config) {
@@ -4919,7 +5178,7 @@
4919
5178
  clusterGlyphContours.push(this.getContoursForGlyph(glyph.g));
4920
5179
  }
4921
5180
  // Step 2: Check for overlaps within the cluster
4922
- const relativePositions = cluster.glyphs.map((g) => new three.Vector3(g.x, g.y, 0));
5181
+ const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x, g.y, 0));
4923
5182
  const boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
4924
5183
  const hasOverlaps = separateGlyphs
4925
5184
  ? false
@@ -4936,7 +5195,7 @@
4936
5195
  for (const path of glyphContours.paths) {
4937
5196
  clusterPaths.push({
4938
5197
  ...path,
4939
- points: path.points.map((p) => new three.Vector2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
5198
+ points: path.points.map((p) => new Vec2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
4940
5199
  });
4941
5200
  }
4942
5201
  }
@@ -4949,7 +5208,7 @@
4949
5208
  for (let i = 0; i < cluster.glyphs.length; i++) {
4950
5209
  const glyph = cluster.glyphs[i];
4951
5210
  const glyphContours = clusterGlyphContours[i];
4952
- const absoluteGlyphPosition = new three.Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5211
+ const absoluteGlyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4953
5212
  const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, clusterVertexCount, absoluteGlyphPosition, glyphContours, depth);
4954
5213
  glyphInfos.push(glyphInfo);
4955
5214
  this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
@@ -4960,7 +5219,7 @@
4960
5219
  for (let i = 0; i < cluster.glyphs.length; i++) {
4961
5220
  const glyph = cluster.glyphs[i];
4962
5221
  const glyphContours = clusterGlyphContours[i];
4963
- const glyphPosition = new three.Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5222
+ const glyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4964
5223
  // Skip glyphs with no paths (spaces, zero-width characters, etc.)
4965
5224
  if (glyphContours.paths.length === 0) {
4966
5225
  const glyphInfo = this.createGlyphInfo(glyph, 0, 0, glyphPosition, glyphContours, depth);
@@ -4981,15 +5240,17 @@
4981
5240
  }
4982
5241
  }
4983
5242
  }
4984
- const geometry = new three.BufferGeometry();
4985
5243
  const vertexArray = new Float32Array(vertices);
4986
5244
  const normalArray = new Float32Array(normals);
4987
- geometry.setAttribute('position', new three.Float32BufferAttribute(vertexArray, 3));
4988
- geometry.setAttribute('normal', new three.Float32BufferAttribute(normalArray, 3));
4989
- geometry.setIndex(indices);
4990
- geometry.computeBoundingBox();
5245
+ const indexArray = new Uint32Array(indices);
4991
5246
  perfLogger.end('GlyphGeometryBuilder.buildInstancedGeometry');
4992
- return { geometry, glyphInfos, planeBounds };
5247
+ return {
5248
+ vertices: vertexArray,
5249
+ normals: normalArray,
5250
+ indices: indexArray,
5251
+ glyphInfos,
5252
+ planeBounds
5253
+ };
4993
5254
  }
4994
5255
  appendGeometry(vertices, normals, indices, data, position, offset) {
4995
5256
  for (let j = 0; j < data.vertices.length; j += 3) {
@@ -5047,19 +5308,34 @@
5047
5308
  }
5048
5309
  extrudeAndPackage(processedGeometry, depth) {
5049
5310
  const extrudedResult = this.extruder.extrude(processedGeometry, depth, this.loadedFont.upem);
5050
- const tempGeometry = new three.BufferGeometry();
5051
- const vertices = new Float32Array(extrudedResult.vertices);
5052
- tempGeometry.setAttribute('position', new three.Float32BufferAttribute(vertices, 3));
5053
- tempGeometry.computeBoundingBox();
5054
- const bounds = tempGeometry.boundingBox;
5055
- const boundsMin = new three.Vector3(bounds.min.x, bounds.min.y, bounds.min.z);
5056
- const boundsMax = new three.Vector3(bounds.max.x, bounds.max.y, bounds.max.z);
5057
- tempGeometry.dispose();
5311
+ // Compute bounding box from vertices
5312
+ const vertices = extrudedResult.vertices;
5313
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
5314
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
5315
+ for (let i = 0; i < vertices.length; i += 3) {
5316
+ const x = vertices[i];
5317
+ const y = vertices[i + 1];
5318
+ const z = vertices[i + 2];
5319
+ if (x < minX)
5320
+ minX = x;
5321
+ if (x > maxX)
5322
+ maxX = x;
5323
+ if (y < minY)
5324
+ minY = y;
5325
+ if (y > maxY)
5326
+ maxY = y;
5327
+ if (z < minZ)
5328
+ minZ = z;
5329
+ if (z > maxZ)
5330
+ maxZ = z;
5331
+ }
5332
+ const boundsMin = new Vec3(minX, minY, minZ);
5333
+ const boundsMax = new Vec3(maxX, maxY, maxZ);
5058
5334
  const vertexCount = extrudedResult.vertices.length / 3;
5059
5335
  const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
5060
5336
  return {
5061
5337
  geometry: processedGeometry,
5062
- vertices: vertices,
5338
+ vertices: new Float32Array(extrudedResult.vertices),
5063
5339
  normals: new Float32Array(extrudedResult.normals),
5064
5340
  indices: new IndexArray(extrudedResult.indices),
5065
5341
  bounds: { min: boundsMin, max: boundsMax },
@@ -5076,8 +5352,8 @@
5076
5352
  return this.extrudeAndPackage(processedGeometry, depth);
5077
5353
  }
5078
5354
  updatePlaneBounds(glyphBounds, planeBounds) {
5079
- const planeBox = new three.Box3(new three.Vector3(planeBounds.min.x, planeBounds.min.y, planeBounds.min.z), new three.Vector3(planeBounds.max.x, planeBounds.max.y, planeBounds.max.z));
5080
- const glyphBox = new three.Box3(new three.Vector3(glyphBounds.min.x, glyphBounds.min.y, glyphBounds.min.z), new three.Vector3(glyphBounds.max.x, glyphBounds.max.y, glyphBounds.max.z));
5355
+ const planeBox = new Box3(new Vec3(planeBounds.min.x, planeBounds.min.y, planeBounds.min.z), new Vec3(planeBounds.max.x, planeBounds.max.y, planeBounds.max.z));
5356
+ const glyphBox = new Box3(new Vec3(glyphBounds.min.x, glyphBounds.min.y, glyphBounds.min.z), new Vec3(glyphBounds.max.x, glyphBounds.max.y, glyphBounds.max.z));
5081
5357
  planeBox.union(glyphBox);
5082
5358
  planeBounds.min.x = planeBox.min.x;
5083
5359
  planeBounds.min.y = planeBox.min.y;
@@ -5142,8 +5418,8 @@
5142
5418
  const clusters = [];
5143
5419
  let currentClusterGlyphs = [];
5144
5420
  let currentClusterText = '';
5145
- let clusterStartPosition = new three.Vector3();
5146
- let cursor = new three.Vector3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5421
+ let clusterStartPosition = new Vec3();
5422
+ let cursor = new Vec3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5147
5423
  const letterSpacingFU = letterSpacing * this.loadedFont.upem;
5148
5424
  const spaceAdjustment = this.calculateSpaceAdjustment(lineInfo, align, letterSpacing);
5149
5425
  for (let i = 0; i < glyphInfos.length; i++) {
@@ -5173,7 +5449,7 @@
5173
5449
  }
5174
5450
  const absoluteGlyphPosition = cursor
5175
5451
  .clone()
5176
- .add(new three.Vector3(glyph.dx, glyph.dy, 0));
5452
+ .add(new Vec3(glyph.dx, glyph.dy, 0));
5177
5453
  if (!isWhitespace) {
5178
5454
  if (currentClusterGlyphs.length === 0) {
5179
5455
  clusterStartPosition.copy(absoluteGlyphPosition);
@@ -5306,7 +5582,7 @@
5306
5582
  size += glyph.normals.length * 4;
5307
5583
  // Indices (Uint16Array or Uint32Array)
5308
5584
  size += glyph.indices.length * glyph.indices.BYTES_PER_ELEMENT;
5309
- // Bounds (2 Vector3s = 6 floats * 4 bytes)
5585
+ // Bounds (2 Vec3s = 6 floats * 4 bytes)
5310
5586
  size += 24;
5311
5587
  // Object overhead
5312
5588
  size += 256;
@@ -6122,9 +6398,9 @@
6122
6398
  max: { x: 0, y: 0, z: 0 }
6123
6399
  };
6124
6400
  }
6125
- const box = new three.Box3();
6401
+ const box = new Box3();
6126
6402
  for (const glyph of glyphs) {
6127
- const glyphBox = new three.Box3(new three.Vector3(glyph.bounds.min.x, glyph.bounds.min.y, glyph.bounds.min.z), new three.Vector3(glyph.bounds.max.x, glyph.bounds.max.y, glyph.bounds.max.z));
6403
+ const glyphBox = new Box3(new Vec3(glyph.bounds.min.x, glyph.bounds.min.y, glyph.bounds.min.z), new Vec3(glyph.bounds.max.x, glyph.bounds.max.y, glyph.bounds.max.z));
6128
6404
  box.union(glyphBox);
6129
6405
  }
6130
6406
  return {
@@ -6155,7 +6431,7 @@
6155
6431
  HarfBuzzLoader.setWasmBuffer(wasmBuffer);
6156
6432
  Text.hbInitPromise = null;
6157
6433
  }
6158
- // Initialize HarfBuzz WASM manually (optional - called automatically by create())
6434
+ // Initialize HarfBuzz WASM (optional - create() calls this if needed)
6159
6435
  static init() {
6160
6436
  if (!Text.hbInitPromise) {
6161
6437
  Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
@@ -6289,9 +6565,10 @@
6289
6565
  const clustersByLine = this.textShaper.shapeLines(layoutData.lines, layoutData.scaledLineHeight, layoutData.letterSpacing, layoutData.align, layoutData.direction, options.color, options.text);
6290
6566
  const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.separateGlyphsWithAttributes || false);
6291
6567
  const cacheStats = this.geometryBuilder.getCacheStats();
6292
- const result = this.finalizeGeometry(shapedResult.geometry, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6568
+ const result = this.finalizeGeometry(shapedResult.vertices, shapedResult.normals, shapedResult.indices, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6293
6569
  if (options.separateGlyphsWithAttributes) {
6294
- this.addGlyphAttributes(result.geometry, result.glyphs);
6570
+ const glyphAttrs = this.createGlyphAttributes(result.vertices.length / 3, result.glyphs);
6571
+ result.glyphAttributes = glyphAttrs;
6295
6572
  }
6296
6573
  return result;
6297
6574
  }
@@ -6403,8 +6680,8 @@
6403
6680
  size
6404
6681
  };
6405
6682
  }
6406
- applyColorSystem(geometry, glyphInfoArray, color, originalText) {
6407
- const vertexCount = geometry.attributes.position.count;
6683
+ applyColorSystem(vertices, glyphInfoArray, color, originalText) {
6684
+ const vertexCount = vertices.length / 3;
6408
6685
  const colors = new Float32Array(vertexCount * 3);
6409
6686
  const coloredRanges = [];
6410
6687
  // Simple case: array color for all text
@@ -6494,16 +6771,15 @@
6494
6771
  });
6495
6772
  }
6496
6773
  }
6497
- geometry.setAttribute('color', new three.Float32BufferAttribute(colors, 3));
6498
- return coloredRanges;
6774
+ return { colors, coloredRanges };
6499
6775
  }
6500
- finalizeGeometry(geometry, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6776
+ finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6501
6777
  const { layout = {}, size = 72 } = options;
6502
6778
  const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
6503
6779
  if (!this.textLayout) {
6504
6780
  this.textLayout = new TextLayout(this.loadedFont);
6505
6781
  }
6506
- const alignmentResult = this.textLayout.applyAlignment(geometry, {
6782
+ const alignmentResult = this.textLayout.applyAlignment(vertices, {
6507
6783
  width,
6508
6784
  align,
6509
6785
  planeBounds
@@ -6512,7 +6788,10 @@
6512
6788
  planeBounds.min.x = alignmentResult.adjustedBounds.min.x;
6513
6789
  planeBounds.max.x = alignmentResult.adjustedBounds.max.x;
6514
6790
  const finalScale = size / this.loadedFont.upem;
6515
- geometry.scale(finalScale, finalScale, finalScale);
6791
+ // Scale vertices only (normals are unit vectors, don't scale)
6792
+ for (let i = 0; i < vertices.length; i++) {
6793
+ vertices[i] *= finalScale;
6794
+ }
6516
6795
  planeBounds.min.x *= finalScale;
6517
6796
  planeBounds.min.y *= finalScale;
6518
6797
  planeBounds.min.z *= finalScale;
@@ -6532,15 +6811,22 @@
6532
6811
  glyphInfo.bounds.max.y *= finalScale;
6533
6812
  glyphInfo.bounds.max.z *= finalScale;
6534
6813
  }
6535
- const coloredRanges = options.color
6536
- ? this.applyColorSystem(geometry, glyphInfoArray, options.color, options.text)
6537
- : undefined;
6814
+ let colors;
6815
+ let coloredRanges;
6816
+ if (options.color) {
6817
+ const colorResult = this.applyColorSystem(vertices, glyphInfoArray, options.color, options.text);
6818
+ colors = colorResult.colors;
6819
+ coloredRanges = colorResult.coloredRanges;
6820
+ }
6538
6821
  // Collect optimization stats for return value
6539
6822
  const optimizationStats = this.geometryBuilder.getOptimizationStats();
6540
- const trianglesGenerated = geometry.index ? geometry.index.count / 3 : 0;
6541
- const verticesGenerated = geometry.attributes.position.count;
6823
+ const trianglesGenerated = indices.length / 3;
6824
+ const verticesGenerated = vertices.length / 3;
6542
6825
  return {
6543
- geometry,
6826
+ vertices,
6827
+ normals,
6828
+ indices,
6829
+ colors,
6544
6830
  glyphs: glyphInfoArray,
6545
6831
  planeBounds,
6546
6832
  stats: {
@@ -6558,7 +6844,8 @@
6558
6844
  const queryInstance = new TextRangeQuery(originalText, glyphInfoArray);
6559
6845
  return queryInstance.execute(options);
6560
6846
  },
6561
- coloredRanges
6847
+ coloredRanges,
6848
+ glyphAttributes: undefined
6562
6849
  };
6563
6850
  }
6564
6851
  getFontMetrics() {
@@ -6603,8 +6890,7 @@
6603
6890
  this.geometryBuilder.clearCache();
6604
6891
  }
6605
6892
  }
6606
- addGlyphAttributes(geometry, glyphs) {
6607
- const vertexCount = geometry.attributes.position.count;
6893
+ createGlyphAttributes(vertexCount, glyphs) {
6608
6894
  const glyphCenters = new Float32Array(vertexCount * 3);
6609
6895
  const glyphIndices = new Float32Array(vertexCount);
6610
6896
  const glyphLineIndices = new Float32Array(vertexCount);
@@ -6623,9 +6909,11 @@
6623
6909
  }
6624
6910
  }
6625
6911
  });
6626
- geometry.setAttribute('glyphCenter', new three.Float32BufferAttribute(glyphCenters, 3));
6627
- geometry.setAttribute('glyphIndex', new three.Float32BufferAttribute(glyphIndices, 1));
6628
- geometry.setAttribute('glyphLineIndex', new three.Float32BufferAttribute(glyphLineIndices, 1));
6912
+ return {
6913
+ glyphCenter: glyphCenters,
6914
+ glyphIndex: glyphIndices,
6915
+ glyphLineIndex: glyphLineIndices
6916
+ };
6629
6917
  }
6630
6918
  destroy() {
6631
6919
  if (!this.loadedFont) {