three-text 0.1.1 → 0.2.0

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 +370 -81
  3. package/dist/index.d.ts +46 -17
  4. package/dist/index.js +369 -80
  5. package/dist/index.min.cjs +2 -2
  6. package/dist/index.min.js +2 -2
  7. package/dist/index.umd.js +374 -83
  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.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * three-text v0.1.1
2
+ * three-text v0.2.0
3
3
  * Copyright (C) 2025 Countertype LLC
4
4
  *
5
5
  * This program is free software: you can redistribute it and/or modify
@@ -13,8 +13,6 @@
13
13
  */
14
14
  'use strict';
15
15
 
16
- var three = require('three');
17
-
18
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
19
17
  // Cached flag check at module load time for zero-cost logging
20
18
  const isLogEnabled = (() => {
@@ -205,7 +203,7 @@ class ActiveNodeList {
205
203
  this.allNodes = new Set();
206
204
  }
207
205
  getKey(position, fitness) {
208
- return `${position}_${fitness}`;
206
+ return (position << 2) | fitness;
209
207
  }
210
208
  insert(node) {
211
209
  const key = this.getKey(node.position, node.fitness);
@@ -1257,7 +1255,7 @@ class TextLayout {
1257
1255
  }
1258
1256
  return { lines };
1259
1257
  }
1260
- applyAlignment(geometry, options) {
1258
+ applyAlignment(vertices, options) {
1261
1259
  const { width, align, planeBounds } = options;
1262
1260
  let offset = 0;
1263
1261
  const adjustedBounds = {
@@ -1273,7 +1271,10 @@ class TextLayout {
1273
1271
  offset = width - planeBounds.max.x;
1274
1272
  }
1275
1273
  if (offset !== 0) {
1276
- geometry.translate(offset, 0, 0);
1274
+ // Translate vertices directly
1275
+ for (let i = 0; i < vertices.length; i += 3) {
1276
+ vertices[i] += offset;
1277
+ }
1277
1278
  adjustedBounds.min.x += offset;
1278
1279
  adjustedBounds.max.x += offset;
1279
1280
  }
@@ -1726,6 +1727,268 @@ async function loadPattern(language, patternsPath) {
1726
1727
  }
1727
1728
  }
1728
1729
 
1730
+ // Bector and bounding box types for core
1731
+ // 2D Vector
1732
+ class Vec2 {
1733
+ constructor(x = 0, y = 0) {
1734
+ this.x = x;
1735
+ this.y = y;
1736
+ }
1737
+ set(x, y) {
1738
+ this.x = x;
1739
+ this.y = y;
1740
+ return this;
1741
+ }
1742
+ clone() {
1743
+ return new Vec2(this.x, this.y);
1744
+ }
1745
+ copy(v) {
1746
+ this.x = v.x;
1747
+ this.y = v.y;
1748
+ return this;
1749
+ }
1750
+ add(v) {
1751
+ this.x += v.x;
1752
+ this.y += v.y;
1753
+ return this;
1754
+ }
1755
+ sub(v) {
1756
+ this.x -= v.x;
1757
+ this.y -= v.y;
1758
+ return this;
1759
+ }
1760
+ multiply(scalar) {
1761
+ this.x *= scalar;
1762
+ this.y *= scalar;
1763
+ return this;
1764
+ }
1765
+ divide(scalar) {
1766
+ this.x /= scalar;
1767
+ this.y /= scalar;
1768
+ return this;
1769
+ }
1770
+ length() {
1771
+ return Math.sqrt(this.x * this.x + this.y * this.y);
1772
+ }
1773
+ lengthSq() {
1774
+ return this.x * this.x + this.y * this.y;
1775
+ }
1776
+ normalize() {
1777
+ const len = this.length();
1778
+ if (len > 0) {
1779
+ this.divide(len);
1780
+ }
1781
+ return this;
1782
+ }
1783
+ dot(v) {
1784
+ return this.x * v.x + this.y * v.y;
1785
+ }
1786
+ distanceTo(v) {
1787
+ const dx = this.x - v.x;
1788
+ const dy = this.y - v.y;
1789
+ return Math.sqrt(dx * dx + dy * dy);
1790
+ }
1791
+ distanceToSquared(v) {
1792
+ const dx = this.x - v.x;
1793
+ const dy = this.y - v.y;
1794
+ return dx * dx + dy * dy;
1795
+ }
1796
+ equals(v) {
1797
+ return this.x === v.x && this.y === v.y;
1798
+ }
1799
+ angle() {
1800
+ return Math.atan2(this.y, this.x);
1801
+ }
1802
+ }
1803
+ // 3D Vector
1804
+ class Vec3 {
1805
+ constructor(x = 0, y = 0, z = 0) {
1806
+ this.x = x;
1807
+ this.y = y;
1808
+ this.z = z;
1809
+ }
1810
+ set(x, y, z) {
1811
+ this.x = x;
1812
+ this.y = y;
1813
+ this.z = z;
1814
+ return this;
1815
+ }
1816
+ clone() {
1817
+ return new Vec3(this.x, this.y, this.z);
1818
+ }
1819
+ copy(v) {
1820
+ this.x = v.x;
1821
+ this.y = v.y;
1822
+ this.z = v.z;
1823
+ return this;
1824
+ }
1825
+ add(v) {
1826
+ this.x += v.x;
1827
+ this.y += v.y;
1828
+ this.z += v.z;
1829
+ return this;
1830
+ }
1831
+ sub(v) {
1832
+ this.x -= v.x;
1833
+ this.y -= v.y;
1834
+ this.z -= v.z;
1835
+ return this;
1836
+ }
1837
+ multiply(scalar) {
1838
+ this.x *= scalar;
1839
+ this.y *= scalar;
1840
+ this.z *= scalar;
1841
+ return this;
1842
+ }
1843
+ divide(scalar) {
1844
+ this.x /= scalar;
1845
+ this.y /= scalar;
1846
+ this.z /= scalar;
1847
+ return this;
1848
+ }
1849
+ length() {
1850
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
1851
+ }
1852
+ lengthSq() {
1853
+ return this.x * this.x + this.y * this.y + this.z * this.z;
1854
+ }
1855
+ normalize() {
1856
+ const len = this.length();
1857
+ if (len > 0) {
1858
+ this.divide(len);
1859
+ }
1860
+ return this;
1861
+ }
1862
+ dot(v) {
1863
+ return this.x * v.x + this.y * v.y + this.z * v.z;
1864
+ }
1865
+ cross(v) {
1866
+ const x = this.y * v.z - this.z * v.y;
1867
+ const y = this.z * v.x - this.x * v.z;
1868
+ const z = this.x * v.y - this.y * v.x;
1869
+ this.x = x;
1870
+ this.y = y;
1871
+ this.z = z;
1872
+ return this;
1873
+ }
1874
+ distanceTo(v) {
1875
+ const dx = this.x - v.x;
1876
+ const dy = this.y - v.y;
1877
+ const dz = this.z - v.z;
1878
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
1879
+ }
1880
+ distanceToSquared(v) {
1881
+ const dx = this.x - v.x;
1882
+ const dy = this.y - v.y;
1883
+ const dz = this.z - v.z;
1884
+ return dx * dx + dy * dy + dz * dz;
1885
+ }
1886
+ equals(v) {
1887
+ return this.x === v.x && this.y === v.y && this.z === v.z;
1888
+ }
1889
+ }
1890
+ // 3D Bounding Box
1891
+ class Box3 {
1892
+ constructor(min = new Vec3(Infinity, Infinity, Infinity), max = new Vec3(-Infinity, -Infinity, -Infinity)) {
1893
+ this.min = min;
1894
+ this.max = max;
1895
+ }
1896
+ set(min, max) {
1897
+ this.min.copy(min);
1898
+ this.max.copy(max);
1899
+ return this;
1900
+ }
1901
+ setFromPoints(points) {
1902
+ this.makeEmpty();
1903
+ for (let i = 0; i < points.length; i++) {
1904
+ this.expandByPoint(points[i]);
1905
+ }
1906
+ return this;
1907
+ }
1908
+ makeEmpty() {
1909
+ this.min.x = this.min.y = this.min.z = Infinity;
1910
+ this.max.x = this.max.y = this.max.z = -Infinity;
1911
+ return this;
1912
+ }
1913
+ isEmpty() {
1914
+ return (this.max.x < this.min.x ||
1915
+ this.max.y < this.min.y ||
1916
+ this.max.z < this.min.z);
1917
+ }
1918
+ expandByPoint(point) {
1919
+ this.min.x = Math.min(this.min.x, point.x);
1920
+ this.min.y = Math.min(this.min.y, point.y);
1921
+ this.min.z = Math.min(this.min.z, point.z);
1922
+ this.max.x = Math.max(this.max.x, point.x);
1923
+ this.max.y = Math.max(this.max.y, point.y);
1924
+ this.max.z = Math.max(this.max.z, point.z);
1925
+ return this;
1926
+ }
1927
+ expandByScalar(scalar) {
1928
+ this.min.x -= scalar;
1929
+ this.min.y -= scalar;
1930
+ this.min.z -= scalar;
1931
+ this.max.x += scalar;
1932
+ this.max.y += scalar;
1933
+ this.max.z += scalar;
1934
+ return this;
1935
+ }
1936
+ containsPoint(point) {
1937
+ return (point.x >= this.min.x &&
1938
+ point.x <= this.max.x &&
1939
+ point.y >= this.min.y &&
1940
+ point.y <= this.max.y &&
1941
+ point.z >= this.min.z &&
1942
+ point.z <= this.max.z);
1943
+ }
1944
+ containsBox(box) {
1945
+ return (this.min.x <= box.min.x &&
1946
+ box.max.x <= this.max.x &&
1947
+ this.min.y <= box.min.y &&
1948
+ box.max.y <= this.max.y &&
1949
+ this.min.z <= box.min.z &&
1950
+ box.max.z <= this.max.z);
1951
+ }
1952
+ intersectsBox(box) {
1953
+ return (box.max.x >= this.min.x &&
1954
+ box.min.x <= this.max.x &&
1955
+ box.max.y >= this.min.y &&
1956
+ box.min.y <= this.max.y &&
1957
+ box.max.z >= this.min.z &&
1958
+ box.min.z <= this.max.z);
1959
+ }
1960
+ getCenter(target = new Vec3()) {
1961
+ return this.isEmpty()
1962
+ ? target.set(0, 0, 0)
1963
+ : 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);
1964
+ }
1965
+ getSize(target = new Vec3()) {
1966
+ return this.isEmpty()
1967
+ ? target.set(0, 0, 0)
1968
+ : target.set(this.max.x - this.min.x, this.max.y - this.min.y, this.max.z - this.min.z);
1969
+ }
1970
+ clone() {
1971
+ return new Box3(this.min.clone(), this.max.clone());
1972
+ }
1973
+ copy(box) {
1974
+ this.min.copy(box.min);
1975
+ this.max.copy(box.max);
1976
+ return this;
1977
+ }
1978
+ union(box) {
1979
+ this.min.x = Math.min(this.min.x, box.min.x);
1980
+ this.min.y = Math.min(this.min.y, box.min.y);
1981
+ this.min.z = Math.min(this.min.z, box.min.z);
1982
+ this.max.x = Math.max(this.max.x, box.max.x);
1983
+ this.max.y = Math.max(this.max.y, box.max.y);
1984
+ this.max.z = Math.max(this.max.z, box.max.z);
1985
+ return this;
1986
+ }
1987
+ equals(box) {
1988
+ return box.min.equals(this.min) && box.max.equals(this.max);
1989
+ }
1990
+ }
1991
+
1729
1992
  var WINDING;
1730
1993
  (function (WINDING) {
1731
1994
  WINDING[WINDING["ODD"] = 0] = "ODD";
@@ -3946,8 +4209,8 @@ class Extruder {
3946
4209
  const p0y = points[i + 1];
3947
4210
  const p1x = points[i + 2];
3948
4211
  const p1y = points[i + 3];
3949
- const edge = new three.Vector2(p1x - p0x, p1y - p0y);
3950
- const normal = new three.Vector2(edge.y, -edge.x).normalize();
4212
+ const edge = new Vec2(p1x - p0x, p1y - p0y);
4213
+ const normal = new Vec2(edge.y, -edge.x).normalize();
3951
4214
  const wallBaseIndex = vertices.length / 3;
3952
4215
  vertices.push(p0x, p0y, 0, p1x, p1y, 0, p0x, p0y, depth, p1x, p1y, depth);
3953
4216
  normals.push(normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0);
@@ -4257,8 +4520,8 @@ class PathOptimizer {
4257
4520
  const prev = points[i - 1];
4258
4521
  const current = points[i];
4259
4522
  const next = points[i + 1];
4260
- const v1 = new three.Vector2(current.x - prev.x, current.y - prev.y);
4261
- const v2 = new three.Vector2(next.x - current.x, next.y - current.y);
4523
+ const v1 = new Vec2(current.x - prev.x, current.y - prev.y);
4524
+ const v2 = new Vec2(next.x - current.x, next.y - current.y);
4262
4525
  const angle = Math.abs(v1.angle() - v2.angle());
4263
4526
  const normalizedAngle = Math.min(angle, 2 * Math.PI - angle);
4264
4527
  if (normalizedAngle > threshold ||
@@ -4372,12 +4635,9 @@ class Polygonizer {
4372
4635
  const dx = x3 - x1;
4373
4636
  const dy = y3 - y1;
4374
4637
  const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
4375
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4376
4638
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4377
4639
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4378
- const distanceTolerance = curveLength > 0
4379
- ? baseTolerance * baseTolerance
4380
- : baseTolerance * baseTolerance;
4640
+ const distanceTolerance = baseTolerance * baseTolerance;
4381
4641
  if (d > COLLINEARITY_EPSILON) {
4382
4642
  // Regular case
4383
4643
  // Recursion terminates when the curve is flat enough (deviation from straight line is within tolerance)
@@ -4442,12 +4702,9 @@ class Polygonizer {
4442
4702
  const dy = y4 - y1;
4443
4703
  const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
4444
4704
  const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
4445
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4446
4705
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4447
4706
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4448
- const distanceTolerance = curveLength > 0
4449
- ? baseTolerance * baseTolerance
4450
- : baseTolerance * baseTolerance;
4707
+ const distanceTolerance = baseTolerance * baseTolerance;
4451
4708
  let switchCondition = 0;
4452
4709
  if (d2 > COLLINEARITY_EPSILON)
4453
4710
  switchCondition |= 1;
@@ -4555,7 +4812,7 @@ class Polygonizer {
4555
4812
  this.recursiveCubic(x1234, y1234, x234, y234, x34, y34, x4, y4, points, level + 1);
4556
4813
  }
4557
4814
  addPoint(x, y, points) {
4558
- const newPoint = new three.Vector2(x, y);
4815
+ const newPoint = new Vec2(x, y);
4559
4816
  if (points.length === 0) {
4560
4817
  points.push(newPoint);
4561
4818
  return;
@@ -4578,13 +4835,13 @@ class GlyphContourCollector {
4578
4835
  this.currentPath = null;
4579
4836
  this.currentPoint = null;
4580
4837
  this.currentGlyphBounds = {
4581
- min: new three.Vector2(Infinity, Infinity),
4582
- max: new three.Vector2(-Infinity, -Infinity)
4838
+ min: new Vec2(Infinity, Infinity),
4839
+ max: new Vec2(-Infinity, -Infinity)
4583
4840
  };
4584
4841
  this.collectedGlyphs = [];
4585
4842
  this.glyphPositions = [];
4586
4843
  this.glyphTextIndices = [];
4587
- this.currentPosition = new three.Vector2(0, 0);
4844
+ this.currentPosition = new Vec2(0, 0);
4588
4845
  this.polygonizer = new Polygonizer(curveFidelityConfig);
4589
4846
  this.pathOptimizer = new PathOptimizer({
4590
4847
  ...DEFAULT_OPTIMIZATION_CONFIG,
@@ -4639,7 +4896,7 @@ class GlyphContourCollector {
4639
4896
  if (this.currentPath) {
4640
4897
  this.finishPath();
4641
4898
  }
4642
- this.currentPoint = new three.Vector2(x, y);
4899
+ this.currentPoint = new Vec2(x, y);
4643
4900
  this.updateBounds(this.currentPoint);
4644
4901
  this.currentPath = {
4645
4902
  points: [this.currentPoint],
@@ -4649,7 +4906,7 @@ class GlyphContourCollector {
4649
4906
  onLineTo(x, y) {
4650
4907
  if (!this.currentPath || !this.currentPoint)
4651
4908
  return;
4652
- const point = new three.Vector2(x, y);
4909
+ const point = new Vec2(x, y);
4653
4910
  this.updateBounds(point);
4654
4911
  this.currentPath.points.push(point);
4655
4912
  this.currentPoint = point;
@@ -4658,8 +4915,8 @@ class GlyphContourCollector {
4658
4915
  if (!this.currentPath || !this.currentPoint)
4659
4916
  return;
4660
4917
  const start = this.currentPoint;
4661
- const control = new three.Vector2(cx, cy);
4662
- const end = new three.Vector2(x, y);
4918
+ const control = new Vec2(cx, cy);
4919
+ const end = new Vec2(x, y);
4663
4920
  const dx = end.x - start.x;
4664
4921
  const dy = end.y - start.y;
4665
4922
  const d = Math.abs((control.x - end.x) * dy - (control.y - end.y) * dx);
@@ -4680,9 +4937,9 @@ class GlyphContourCollector {
4680
4937
  if (!this.currentPath || !this.currentPoint)
4681
4938
  return;
4682
4939
  const start = this.currentPoint;
4683
- const control1 = new three.Vector2(c1x, c1y);
4684
- const control2 = new three.Vector2(c2x, c2y);
4685
- const end = new three.Vector2(x, y);
4940
+ const control1 = new Vec2(c1x, c1y);
4941
+ const control2 = new Vec2(c2x, c2y);
4942
+ const end = new Vec2(x, y);
4686
4943
  const dx = end.x - start.x;
4687
4944
  const dy = end.y - start.y;
4688
4945
  const d1 = Math.abs((control1.x - end.x) * dy - (control1.y - end.y) * dx);
@@ -4724,7 +4981,7 @@ class GlyphContourCollector {
4724
4981
  this.currentGlyphBounds.max.y = Math.max(this.currentGlyphBounds.max.y, point.y);
4725
4982
  }
4726
4983
  getCollectedGlyphs() {
4727
- // Make sure to finish any pending glyph
4984
+ // Finish any pending glyph
4728
4985
  if (this.currentGlyphPaths.length > 0) {
4729
4986
  this.finishGlyph();
4730
4987
  }
@@ -4747,8 +5004,8 @@ class GlyphContourCollector {
4747
5004
  this.currentTextIndex = 0;
4748
5005
  this.currentPosition.set(0, 0);
4749
5006
  this.currentGlyphBounds = {
4750
- min: new three.Vector2(Infinity, Infinity),
4751
- max: new three.Vector2(-Infinity, -Infinity)
5007
+ min: new Vec2(Infinity, Infinity),
5008
+ max: new Vec2(-Infinity, -Infinity)
4752
5009
  };
4753
5010
  }
4754
5011
  setCurveFidelityConfig(config) {
@@ -4917,7 +5174,7 @@ class GlyphGeometryBuilder {
4917
5174
  clusterGlyphContours.push(this.getContoursForGlyph(glyph.g));
4918
5175
  }
4919
5176
  // Step 2: Check for overlaps within the cluster
4920
- const relativePositions = cluster.glyphs.map((g) => new three.Vector3(g.x, g.y, 0));
5177
+ const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x, g.y, 0));
4921
5178
  const boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
4922
5179
  const hasOverlaps = separateGlyphs
4923
5180
  ? false
@@ -4934,7 +5191,7 @@ class GlyphGeometryBuilder {
4934
5191
  for (const path of glyphContours.paths) {
4935
5192
  clusterPaths.push({
4936
5193
  ...path,
4937
- points: path.points.map((p) => new three.Vector2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
5194
+ points: path.points.map((p) => new Vec2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
4938
5195
  });
4939
5196
  }
4940
5197
  }
@@ -4947,7 +5204,7 @@ class GlyphGeometryBuilder {
4947
5204
  for (let i = 0; i < cluster.glyphs.length; i++) {
4948
5205
  const glyph = cluster.glyphs[i];
4949
5206
  const glyphContours = clusterGlyphContours[i];
4950
- const absoluteGlyphPosition = new three.Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5207
+ const absoluteGlyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4951
5208
  const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, clusterVertexCount, absoluteGlyphPosition, glyphContours, depth);
4952
5209
  glyphInfos.push(glyphInfo);
4953
5210
  this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
@@ -4958,7 +5215,7 @@ class GlyphGeometryBuilder {
4958
5215
  for (let i = 0; i < cluster.glyphs.length; i++) {
4959
5216
  const glyph = cluster.glyphs[i];
4960
5217
  const glyphContours = clusterGlyphContours[i];
4961
- const glyphPosition = new three.Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5218
+ const glyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4962
5219
  // Skip glyphs with no paths (spaces, zero-width characters, etc.)
4963
5220
  if (glyphContours.paths.length === 0) {
4964
5221
  const glyphInfo = this.createGlyphInfo(glyph, 0, 0, glyphPosition, glyphContours, depth);
@@ -4979,15 +5236,17 @@ class GlyphGeometryBuilder {
4979
5236
  }
4980
5237
  }
4981
5238
  }
4982
- const geometry = new three.BufferGeometry();
4983
5239
  const vertexArray = new Float32Array(vertices);
4984
5240
  const normalArray = new Float32Array(normals);
4985
- geometry.setAttribute('position', new three.Float32BufferAttribute(vertexArray, 3));
4986
- geometry.setAttribute('normal', new three.Float32BufferAttribute(normalArray, 3));
4987
- geometry.setIndex(indices);
4988
- geometry.computeBoundingBox();
5241
+ const indexArray = new Uint32Array(indices);
4989
5242
  perfLogger.end('GlyphGeometryBuilder.buildInstancedGeometry');
4990
- return { geometry, glyphInfos, planeBounds };
5243
+ return {
5244
+ vertices: vertexArray,
5245
+ normals: normalArray,
5246
+ indices: indexArray,
5247
+ glyphInfos,
5248
+ planeBounds
5249
+ };
4991
5250
  }
4992
5251
  appendGeometry(vertices, normals, indices, data, position, offset) {
4993
5252
  for (let j = 0; j < data.vertices.length; j += 3) {
@@ -5045,19 +5304,34 @@ class GlyphGeometryBuilder {
5045
5304
  }
5046
5305
  extrudeAndPackage(processedGeometry, depth) {
5047
5306
  const extrudedResult = this.extruder.extrude(processedGeometry, depth, this.loadedFont.upem);
5048
- const tempGeometry = new three.BufferGeometry();
5049
- const vertices = new Float32Array(extrudedResult.vertices);
5050
- tempGeometry.setAttribute('position', new three.Float32BufferAttribute(vertices, 3));
5051
- tempGeometry.computeBoundingBox();
5052
- const bounds = tempGeometry.boundingBox;
5053
- const boundsMin = new three.Vector3(bounds.min.x, bounds.min.y, bounds.min.z);
5054
- const boundsMax = new three.Vector3(bounds.max.x, bounds.max.y, bounds.max.z);
5055
- tempGeometry.dispose();
5307
+ // Compute bounding box from vertices
5308
+ const vertices = extrudedResult.vertices;
5309
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
5310
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
5311
+ for (let i = 0; i < vertices.length; i += 3) {
5312
+ const x = vertices[i];
5313
+ const y = vertices[i + 1];
5314
+ const z = vertices[i + 2];
5315
+ if (x < minX)
5316
+ minX = x;
5317
+ if (x > maxX)
5318
+ maxX = x;
5319
+ if (y < minY)
5320
+ minY = y;
5321
+ if (y > maxY)
5322
+ maxY = y;
5323
+ if (z < minZ)
5324
+ minZ = z;
5325
+ if (z > maxZ)
5326
+ maxZ = z;
5327
+ }
5328
+ const boundsMin = new Vec3(minX, minY, minZ);
5329
+ const boundsMax = new Vec3(maxX, maxY, maxZ);
5056
5330
  const vertexCount = extrudedResult.vertices.length / 3;
5057
5331
  const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
5058
5332
  return {
5059
5333
  geometry: processedGeometry,
5060
- vertices: vertices,
5334
+ vertices: new Float32Array(extrudedResult.vertices),
5061
5335
  normals: new Float32Array(extrudedResult.normals),
5062
5336
  indices: new IndexArray(extrudedResult.indices),
5063
5337
  bounds: { min: boundsMin, max: boundsMax },
@@ -5074,8 +5348,8 @@ class GlyphGeometryBuilder {
5074
5348
  return this.extrudeAndPackage(processedGeometry, depth);
5075
5349
  }
5076
5350
  updatePlaneBounds(glyphBounds, planeBounds) {
5077
- 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));
5078
- 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));
5351
+ 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));
5352
+ 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));
5079
5353
  planeBox.union(glyphBox);
5080
5354
  planeBounds.min.x = planeBox.min.x;
5081
5355
  planeBounds.min.y = planeBox.min.y;
@@ -5140,8 +5414,8 @@ class TextShaper {
5140
5414
  const clusters = [];
5141
5415
  let currentClusterGlyphs = [];
5142
5416
  let currentClusterText = '';
5143
- let clusterStartPosition = new three.Vector3();
5144
- let cursor = new three.Vector3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5417
+ let clusterStartPosition = new Vec3();
5418
+ let cursor = new Vec3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5145
5419
  const letterSpacingFU = letterSpacing * this.loadedFont.upem;
5146
5420
  const spaceAdjustment = this.calculateSpaceAdjustment(lineInfo, align, letterSpacing);
5147
5421
  for (let i = 0; i < glyphInfos.length; i++) {
@@ -5171,7 +5445,7 @@ class TextShaper {
5171
5445
  }
5172
5446
  const absoluteGlyphPosition = cursor
5173
5447
  .clone()
5174
- .add(new three.Vector3(glyph.dx, glyph.dy, 0));
5448
+ .add(new Vec3(glyph.dx, glyph.dy, 0));
5175
5449
  if (!isWhitespace) {
5176
5450
  if (currentClusterGlyphs.length === 0) {
5177
5451
  clusterStartPosition.copy(absoluteGlyphPosition);
@@ -5304,7 +5578,7 @@ class GlyphCache {
5304
5578
  size += glyph.normals.length * 4;
5305
5579
  // Indices (Uint16Array or Uint32Array)
5306
5580
  size += glyph.indices.length * glyph.indices.BYTES_PER_ELEMENT;
5307
- // Bounds (2 Vector3s = 6 floats * 4 bytes)
5581
+ // Bounds (2 Vec3s = 6 floats * 4 bytes)
5308
5582
  size += 24;
5309
5583
  // Object overhead
5310
5584
  size += 256;
@@ -6120,9 +6394,9 @@ class TextRangeQuery {
6120
6394
  max: { x: 0, y: 0, z: 0 }
6121
6395
  };
6122
6396
  }
6123
- const box = new three.Box3();
6397
+ const box = new Box3();
6124
6398
  for (const glyph of glyphs) {
6125
- 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));
6399
+ 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));
6126
6400
  box.union(glyphBox);
6127
6401
  }
6128
6402
  return {
@@ -6153,7 +6427,7 @@ class Text {
6153
6427
  HarfBuzzLoader.setWasmBuffer(wasmBuffer);
6154
6428
  Text.hbInitPromise = null;
6155
6429
  }
6156
- // Initialize HarfBuzz WASM manually (optional - called automatically by create())
6430
+ // Initialize HarfBuzz WASM (optional - create() calls this if needed)
6157
6431
  static init() {
6158
6432
  if (!Text.hbInitPromise) {
6159
6433
  Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
@@ -6287,9 +6561,10 @@ class Text {
6287
6561
  const clustersByLine = this.textShaper.shapeLines(layoutData.lines, layoutData.scaledLineHeight, layoutData.letterSpacing, layoutData.align, layoutData.direction, options.color, options.text);
6288
6562
  const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.separateGlyphsWithAttributes || false);
6289
6563
  const cacheStats = this.geometryBuilder.getCacheStats();
6290
- const result = this.finalizeGeometry(shapedResult.geometry, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6564
+ const result = this.finalizeGeometry(shapedResult.vertices, shapedResult.normals, shapedResult.indices, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6291
6565
  if (options.separateGlyphsWithAttributes) {
6292
- this.addGlyphAttributes(result.geometry, result.glyphs);
6566
+ const glyphAttrs = this.createGlyphAttributes(result.vertices.length / 3, result.glyphs);
6567
+ result.glyphAttributes = glyphAttrs;
6293
6568
  }
6294
6569
  return result;
6295
6570
  }
@@ -6401,8 +6676,8 @@ class Text {
6401
6676
  size
6402
6677
  };
6403
6678
  }
6404
- applyColorSystem(geometry, glyphInfoArray, color, originalText) {
6405
- const vertexCount = geometry.attributes.position.count;
6679
+ applyColorSystem(vertices, glyphInfoArray, color, originalText) {
6680
+ const vertexCount = vertices.length / 3;
6406
6681
  const colors = new Float32Array(vertexCount * 3);
6407
6682
  const coloredRanges = [];
6408
6683
  // Simple case: array color for all text
@@ -6492,16 +6767,15 @@ class Text {
6492
6767
  });
6493
6768
  }
6494
6769
  }
6495
- geometry.setAttribute('color', new three.Float32BufferAttribute(colors, 3));
6496
- return coloredRanges;
6770
+ return { colors, coloredRanges };
6497
6771
  }
6498
- finalizeGeometry(geometry, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6772
+ finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6499
6773
  const { layout = {}, size = 72 } = options;
6500
6774
  const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
6501
6775
  if (!this.textLayout) {
6502
6776
  this.textLayout = new TextLayout(this.loadedFont);
6503
6777
  }
6504
- const alignmentResult = this.textLayout.applyAlignment(geometry, {
6778
+ const alignmentResult = this.textLayout.applyAlignment(vertices, {
6505
6779
  width,
6506
6780
  align,
6507
6781
  planeBounds
@@ -6510,7 +6784,13 @@ class Text {
6510
6784
  planeBounds.min.x = alignmentResult.adjustedBounds.min.x;
6511
6785
  planeBounds.max.x = alignmentResult.adjustedBounds.max.x;
6512
6786
  const finalScale = size / this.loadedFont.upem;
6513
- geometry.scale(finalScale, finalScale, finalScale);
6787
+ // Scale vertices and normals directly
6788
+ for (let i = 0; i < vertices.length; i++) {
6789
+ vertices[i] *= finalScale;
6790
+ }
6791
+ for (let i = 0; i < normals.length; i++) {
6792
+ normals[i] *= finalScale;
6793
+ }
6514
6794
  planeBounds.min.x *= finalScale;
6515
6795
  planeBounds.min.y *= finalScale;
6516
6796
  planeBounds.min.z *= finalScale;
@@ -6530,15 +6810,22 @@ class Text {
6530
6810
  glyphInfo.bounds.max.y *= finalScale;
6531
6811
  glyphInfo.bounds.max.z *= finalScale;
6532
6812
  }
6533
- const coloredRanges = options.color
6534
- ? this.applyColorSystem(geometry, glyphInfoArray, options.color, options.text)
6535
- : undefined;
6813
+ let colors;
6814
+ let coloredRanges;
6815
+ if (options.color) {
6816
+ const colorResult = this.applyColorSystem(vertices, glyphInfoArray, options.color, options.text);
6817
+ colors = colorResult.colors;
6818
+ coloredRanges = colorResult.coloredRanges;
6819
+ }
6536
6820
  // Collect optimization stats for return value
6537
6821
  const optimizationStats = this.geometryBuilder.getOptimizationStats();
6538
- const trianglesGenerated = geometry.index ? geometry.index.count / 3 : 0;
6539
- const verticesGenerated = geometry.attributes.position.count;
6822
+ const trianglesGenerated = indices.length / 3;
6823
+ const verticesGenerated = vertices.length / 3;
6540
6824
  return {
6541
- geometry,
6825
+ vertices,
6826
+ normals,
6827
+ indices,
6828
+ colors,
6542
6829
  glyphs: glyphInfoArray,
6543
6830
  planeBounds,
6544
6831
  stats: {
@@ -6556,7 +6843,8 @@ class Text {
6556
6843
  const queryInstance = new TextRangeQuery(originalText, glyphInfoArray);
6557
6844
  return queryInstance.execute(options);
6558
6845
  },
6559
- coloredRanges
6846
+ coloredRanges,
6847
+ glyphAttributes: undefined
6560
6848
  };
6561
6849
  }
6562
6850
  getFontMetrics() {
@@ -6601,8 +6889,7 @@ class Text {
6601
6889
  this.geometryBuilder.clearCache();
6602
6890
  }
6603
6891
  }
6604
- addGlyphAttributes(geometry, glyphs) {
6605
- const vertexCount = geometry.attributes.position.count;
6892
+ createGlyphAttributes(vertexCount, glyphs) {
6606
6893
  const glyphCenters = new Float32Array(vertexCount * 3);
6607
6894
  const glyphIndices = new Float32Array(vertexCount);
6608
6895
  const glyphLineIndices = new Float32Array(vertexCount);
@@ -6621,9 +6908,11 @@ class Text {
6621
6908
  }
6622
6909
  }
6623
6910
  });
6624
- geometry.setAttribute('glyphCenter', new three.Float32BufferAttribute(glyphCenters, 3));
6625
- geometry.setAttribute('glyphIndex', new three.Float32BufferAttribute(glyphIndices, 1));
6626
- geometry.setAttribute('glyphLineIndex', new three.Float32BufferAttribute(glyphLineIndices, 1));
6911
+ return {
6912
+ glyphCenter: glyphCenters,
6913
+ glyphIndex: glyphIndices,
6914
+ glyphLineIndex: glyphLineIndices
6915
+ };
6627
6916
  }
6628
6917
  destroy() {
6629
6918
  if (!this.loadedFont) {