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.js 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
@@ -11,8 +11,6 @@
11
11
  *
12
12
  * This software includes third-party code - see LICENSE_THIRD_PARTY for details.
13
13
  */
14
- import { Vector2, Vector3, BufferGeometry, Float32BufferAttribute, Box3 } from 'three';
15
-
16
14
  // Cached flag check at module load time for zero-cost logging
17
15
  const isLogEnabled = (() => {
18
16
  if (typeof window !== 'undefined' && window.THREE_TEXT_LOG) {
@@ -202,7 +200,7 @@ class ActiveNodeList {
202
200
  this.allNodes = new Set();
203
201
  }
204
202
  getKey(position, fitness) {
205
- return `${position}_${fitness}`;
203
+ return (position << 2) | fitness;
206
204
  }
207
205
  insert(node) {
208
206
  const key = this.getKey(node.position, node.fitness);
@@ -1254,7 +1252,7 @@ class TextLayout {
1254
1252
  }
1255
1253
  return { lines };
1256
1254
  }
1257
- applyAlignment(geometry, options) {
1255
+ applyAlignment(vertices, options) {
1258
1256
  const { width, align, planeBounds } = options;
1259
1257
  let offset = 0;
1260
1258
  const adjustedBounds = {
@@ -1270,7 +1268,10 @@ class TextLayout {
1270
1268
  offset = width - planeBounds.max.x;
1271
1269
  }
1272
1270
  if (offset !== 0) {
1273
- geometry.translate(offset, 0, 0);
1271
+ // Translate vertices directly
1272
+ for (let i = 0; i < vertices.length; i += 3) {
1273
+ vertices[i] += offset;
1274
+ }
1274
1275
  adjustedBounds.min.x += offset;
1275
1276
  adjustedBounds.max.x += offset;
1276
1277
  }
@@ -1723,6 +1724,268 @@ async function loadPattern(language, patternsPath) {
1723
1724
  }
1724
1725
  }
1725
1726
 
1727
+ // Bector and bounding box types for core
1728
+ // 2D Vector
1729
+ class Vec2 {
1730
+ constructor(x = 0, y = 0) {
1731
+ this.x = x;
1732
+ this.y = y;
1733
+ }
1734
+ set(x, y) {
1735
+ this.x = x;
1736
+ this.y = y;
1737
+ return this;
1738
+ }
1739
+ clone() {
1740
+ return new Vec2(this.x, this.y);
1741
+ }
1742
+ copy(v) {
1743
+ this.x = v.x;
1744
+ this.y = v.y;
1745
+ return this;
1746
+ }
1747
+ add(v) {
1748
+ this.x += v.x;
1749
+ this.y += v.y;
1750
+ return this;
1751
+ }
1752
+ sub(v) {
1753
+ this.x -= v.x;
1754
+ this.y -= v.y;
1755
+ return this;
1756
+ }
1757
+ multiply(scalar) {
1758
+ this.x *= scalar;
1759
+ this.y *= scalar;
1760
+ return this;
1761
+ }
1762
+ divide(scalar) {
1763
+ this.x /= scalar;
1764
+ this.y /= scalar;
1765
+ return this;
1766
+ }
1767
+ length() {
1768
+ return Math.sqrt(this.x * this.x + this.y * this.y);
1769
+ }
1770
+ lengthSq() {
1771
+ return this.x * this.x + this.y * this.y;
1772
+ }
1773
+ normalize() {
1774
+ const len = this.length();
1775
+ if (len > 0) {
1776
+ this.divide(len);
1777
+ }
1778
+ return this;
1779
+ }
1780
+ dot(v) {
1781
+ return this.x * v.x + this.y * v.y;
1782
+ }
1783
+ distanceTo(v) {
1784
+ const dx = this.x - v.x;
1785
+ const dy = this.y - v.y;
1786
+ return Math.sqrt(dx * dx + dy * dy);
1787
+ }
1788
+ distanceToSquared(v) {
1789
+ const dx = this.x - v.x;
1790
+ const dy = this.y - v.y;
1791
+ return dx * dx + dy * dy;
1792
+ }
1793
+ equals(v) {
1794
+ return this.x === v.x && this.y === v.y;
1795
+ }
1796
+ angle() {
1797
+ return Math.atan2(this.y, this.x);
1798
+ }
1799
+ }
1800
+ // 3D Vector
1801
+ class Vec3 {
1802
+ constructor(x = 0, y = 0, z = 0) {
1803
+ this.x = x;
1804
+ this.y = y;
1805
+ this.z = z;
1806
+ }
1807
+ set(x, y, z) {
1808
+ this.x = x;
1809
+ this.y = y;
1810
+ this.z = z;
1811
+ return this;
1812
+ }
1813
+ clone() {
1814
+ return new Vec3(this.x, this.y, this.z);
1815
+ }
1816
+ copy(v) {
1817
+ this.x = v.x;
1818
+ this.y = v.y;
1819
+ this.z = v.z;
1820
+ return this;
1821
+ }
1822
+ add(v) {
1823
+ this.x += v.x;
1824
+ this.y += v.y;
1825
+ this.z += v.z;
1826
+ return this;
1827
+ }
1828
+ sub(v) {
1829
+ this.x -= v.x;
1830
+ this.y -= v.y;
1831
+ this.z -= v.z;
1832
+ return this;
1833
+ }
1834
+ multiply(scalar) {
1835
+ this.x *= scalar;
1836
+ this.y *= scalar;
1837
+ this.z *= scalar;
1838
+ return this;
1839
+ }
1840
+ divide(scalar) {
1841
+ this.x /= scalar;
1842
+ this.y /= scalar;
1843
+ this.z /= scalar;
1844
+ return this;
1845
+ }
1846
+ length() {
1847
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
1848
+ }
1849
+ lengthSq() {
1850
+ return this.x * this.x + this.y * this.y + this.z * this.z;
1851
+ }
1852
+ normalize() {
1853
+ const len = this.length();
1854
+ if (len > 0) {
1855
+ this.divide(len);
1856
+ }
1857
+ return this;
1858
+ }
1859
+ dot(v) {
1860
+ return this.x * v.x + this.y * v.y + this.z * v.z;
1861
+ }
1862
+ cross(v) {
1863
+ const x = this.y * v.z - this.z * v.y;
1864
+ const y = this.z * v.x - this.x * v.z;
1865
+ const z = this.x * v.y - this.y * v.x;
1866
+ this.x = x;
1867
+ this.y = y;
1868
+ this.z = z;
1869
+ return this;
1870
+ }
1871
+ distanceTo(v) {
1872
+ const dx = this.x - v.x;
1873
+ const dy = this.y - v.y;
1874
+ const dz = this.z - v.z;
1875
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
1876
+ }
1877
+ distanceToSquared(v) {
1878
+ const dx = this.x - v.x;
1879
+ const dy = this.y - v.y;
1880
+ const dz = this.z - v.z;
1881
+ return dx * dx + dy * dy + dz * dz;
1882
+ }
1883
+ equals(v) {
1884
+ return this.x === v.x && this.y === v.y && this.z === v.z;
1885
+ }
1886
+ }
1887
+ // 3D Bounding Box
1888
+ class Box3 {
1889
+ constructor(min = new Vec3(Infinity, Infinity, Infinity), max = new Vec3(-Infinity, -Infinity, -Infinity)) {
1890
+ this.min = min;
1891
+ this.max = max;
1892
+ }
1893
+ set(min, max) {
1894
+ this.min.copy(min);
1895
+ this.max.copy(max);
1896
+ return this;
1897
+ }
1898
+ setFromPoints(points) {
1899
+ this.makeEmpty();
1900
+ for (let i = 0; i < points.length; i++) {
1901
+ this.expandByPoint(points[i]);
1902
+ }
1903
+ return this;
1904
+ }
1905
+ makeEmpty() {
1906
+ this.min.x = this.min.y = this.min.z = Infinity;
1907
+ this.max.x = this.max.y = this.max.z = -Infinity;
1908
+ return this;
1909
+ }
1910
+ isEmpty() {
1911
+ return (this.max.x < this.min.x ||
1912
+ this.max.y < this.min.y ||
1913
+ this.max.z < this.min.z);
1914
+ }
1915
+ expandByPoint(point) {
1916
+ this.min.x = Math.min(this.min.x, point.x);
1917
+ this.min.y = Math.min(this.min.y, point.y);
1918
+ this.min.z = Math.min(this.min.z, point.z);
1919
+ this.max.x = Math.max(this.max.x, point.x);
1920
+ this.max.y = Math.max(this.max.y, point.y);
1921
+ this.max.z = Math.max(this.max.z, point.z);
1922
+ return this;
1923
+ }
1924
+ expandByScalar(scalar) {
1925
+ this.min.x -= scalar;
1926
+ this.min.y -= scalar;
1927
+ this.min.z -= scalar;
1928
+ this.max.x += scalar;
1929
+ this.max.y += scalar;
1930
+ this.max.z += scalar;
1931
+ return this;
1932
+ }
1933
+ containsPoint(point) {
1934
+ return (point.x >= this.min.x &&
1935
+ point.x <= this.max.x &&
1936
+ point.y >= this.min.y &&
1937
+ point.y <= this.max.y &&
1938
+ point.z >= this.min.z &&
1939
+ point.z <= this.max.z);
1940
+ }
1941
+ containsBox(box) {
1942
+ return (this.min.x <= box.min.x &&
1943
+ box.max.x <= this.max.x &&
1944
+ this.min.y <= box.min.y &&
1945
+ box.max.y <= this.max.y &&
1946
+ this.min.z <= box.min.z &&
1947
+ box.max.z <= this.max.z);
1948
+ }
1949
+ intersectsBox(box) {
1950
+ return (box.max.x >= this.min.x &&
1951
+ box.min.x <= this.max.x &&
1952
+ box.max.y >= this.min.y &&
1953
+ box.min.y <= this.max.y &&
1954
+ box.max.z >= this.min.z &&
1955
+ box.min.z <= this.max.z);
1956
+ }
1957
+ getCenter(target = new Vec3()) {
1958
+ return this.isEmpty()
1959
+ ? target.set(0, 0, 0)
1960
+ : 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);
1961
+ }
1962
+ getSize(target = new Vec3()) {
1963
+ return this.isEmpty()
1964
+ ? target.set(0, 0, 0)
1965
+ : target.set(this.max.x - this.min.x, this.max.y - this.min.y, this.max.z - this.min.z);
1966
+ }
1967
+ clone() {
1968
+ return new Box3(this.min.clone(), this.max.clone());
1969
+ }
1970
+ copy(box) {
1971
+ this.min.copy(box.min);
1972
+ this.max.copy(box.max);
1973
+ return this;
1974
+ }
1975
+ union(box) {
1976
+ this.min.x = Math.min(this.min.x, box.min.x);
1977
+ this.min.y = Math.min(this.min.y, box.min.y);
1978
+ this.min.z = Math.min(this.min.z, box.min.z);
1979
+ this.max.x = Math.max(this.max.x, box.max.x);
1980
+ this.max.y = Math.max(this.max.y, box.max.y);
1981
+ this.max.z = Math.max(this.max.z, box.max.z);
1982
+ return this;
1983
+ }
1984
+ equals(box) {
1985
+ return box.min.equals(this.min) && box.max.equals(this.max);
1986
+ }
1987
+ }
1988
+
1726
1989
  var WINDING;
1727
1990
  (function (WINDING) {
1728
1991
  WINDING[WINDING["ODD"] = 0] = "ODD";
@@ -3943,8 +4206,8 @@ class Extruder {
3943
4206
  const p0y = points[i + 1];
3944
4207
  const p1x = points[i + 2];
3945
4208
  const p1y = points[i + 3];
3946
- const edge = new Vector2(p1x - p0x, p1y - p0y);
3947
- const normal = new Vector2(edge.y, -edge.x).normalize();
4209
+ const edge = new Vec2(p1x - p0x, p1y - p0y);
4210
+ const normal = new Vec2(edge.y, -edge.x).normalize();
3948
4211
  const wallBaseIndex = vertices.length / 3;
3949
4212
  vertices.push(p0x, p0y, 0, p1x, p1y, 0, p0x, p0y, depth, p1x, p1y, depth);
3950
4213
  normals.push(normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0, normal.x, normal.y, 0);
@@ -4254,8 +4517,8 @@ class PathOptimizer {
4254
4517
  const prev = points[i - 1];
4255
4518
  const current = points[i];
4256
4519
  const next = points[i + 1];
4257
- const v1 = new Vector2(current.x - prev.x, current.y - prev.y);
4258
- const v2 = new Vector2(next.x - current.x, next.y - current.y);
4520
+ const v1 = new Vec2(current.x - prev.x, current.y - prev.y);
4521
+ const v2 = new Vec2(next.x - current.x, next.y - current.y);
4259
4522
  const angle = Math.abs(v1.angle() - v2.angle());
4260
4523
  const normalizedAngle = Math.min(angle, 2 * Math.PI - angle);
4261
4524
  if (normalizedAngle > threshold ||
@@ -4369,12 +4632,9 @@ class Polygonizer {
4369
4632
  const dx = x3 - x1;
4370
4633
  const dy = y3 - y1;
4371
4634
  const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
4372
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4373
4635
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4374
4636
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4375
- const distanceTolerance = curveLength > 0
4376
- ? baseTolerance * baseTolerance
4377
- : baseTolerance * baseTolerance;
4637
+ const distanceTolerance = baseTolerance * baseTolerance;
4378
4638
  if (d > COLLINEARITY_EPSILON) {
4379
4639
  // Regular case
4380
4640
  // Recursion terminates when the curve is flat enough (deviation from straight line is within tolerance)
@@ -4439,12 +4699,9 @@ class Polygonizer {
4439
4699
  const dy = y4 - y1;
4440
4700
  const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
4441
4701
  const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
4442
- const curveLength = Math.sqrt(dx * dx + dy * dy);
4443
4702
  const baseTolerance = this.curveFidelityConfig.distanceTolerance ??
4444
4703
  DEFAULT_CURVE_FIDELITY.distanceTolerance;
4445
- const distanceTolerance = curveLength > 0
4446
- ? baseTolerance * baseTolerance
4447
- : baseTolerance * baseTolerance;
4704
+ const distanceTolerance = baseTolerance * baseTolerance;
4448
4705
  let switchCondition = 0;
4449
4706
  if (d2 > COLLINEARITY_EPSILON)
4450
4707
  switchCondition |= 1;
@@ -4552,7 +4809,7 @@ class Polygonizer {
4552
4809
  this.recursiveCubic(x1234, y1234, x234, y234, x34, y34, x4, y4, points, level + 1);
4553
4810
  }
4554
4811
  addPoint(x, y, points) {
4555
- const newPoint = new Vector2(x, y);
4812
+ const newPoint = new Vec2(x, y);
4556
4813
  if (points.length === 0) {
4557
4814
  points.push(newPoint);
4558
4815
  return;
@@ -4575,13 +4832,13 @@ class GlyphContourCollector {
4575
4832
  this.currentPath = null;
4576
4833
  this.currentPoint = null;
4577
4834
  this.currentGlyphBounds = {
4578
- min: new Vector2(Infinity, Infinity),
4579
- max: new Vector2(-Infinity, -Infinity)
4835
+ min: new Vec2(Infinity, Infinity),
4836
+ max: new Vec2(-Infinity, -Infinity)
4580
4837
  };
4581
4838
  this.collectedGlyphs = [];
4582
4839
  this.glyphPositions = [];
4583
4840
  this.glyphTextIndices = [];
4584
- this.currentPosition = new Vector2(0, 0);
4841
+ this.currentPosition = new Vec2(0, 0);
4585
4842
  this.polygonizer = new Polygonizer(curveFidelityConfig);
4586
4843
  this.pathOptimizer = new PathOptimizer({
4587
4844
  ...DEFAULT_OPTIMIZATION_CONFIG,
@@ -4636,7 +4893,7 @@ class GlyphContourCollector {
4636
4893
  if (this.currentPath) {
4637
4894
  this.finishPath();
4638
4895
  }
4639
- this.currentPoint = new Vector2(x, y);
4896
+ this.currentPoint = new Vec2(x, y);
4640
4897
  this.updateBounds(this.currentPoint);
4641
4898
  this.currentPath = {
4642
4899
  points: [this.currentPoint],
@@ -4646,7 +4903,7 @@ class GlyphContourCollector {
4646
4903
  onLineTo(x, y) {
4647
4904
  if (!this.currentPath || !this.currentPoint)
4648
4905
  return;
4649
- const point = new Vector2(x, y);
4906
+ const point = new Vec2(x, y);
4650
4907
  this.updateBounds(point);
4651
4908
  this.currentPath.points.push(point);
4652
4909
  this.currentPoint = point;
@@ -4655,8 +4912,8 @@ class GlyphContourCollector {
4655
4912
  if (!this.currentPath || !this.currentPoint)
4656
4913
  return;
4657
4914
  const start = this.currentPoint;
4658
- const control = new Vector2(cx, cy);
4659
- const end = new Vector2(x, y);
4915
+ const control = new Vec2(cx, cy);
4916
+ const end = new Vec2(x, y);
4660
4917
  const dx = end.x - start.x;
4661
4918
  const dy = end.y - start.y;
4662
4919
  const d = Math.abs((control.x - end.x) * dy - (control.y - end.y) * dx);
@@ -4677,9 +4934,9 @@ class GlyphContourCollector {
4677
4934
  if (!this.currentPath || !this.currentPoint)
4678
4935
  return;
4679
4936
  const start = this.currentPoint;
4680
- const control1 = new Vector2(c1x, c1y);
4681
- const control2 = new Vector2(c2x, c2y);
4682
- const end = new Vector2(x, y);
4937
+ const control1 = new Vec2(c1x, c1y);
4938
+ const control2 = new Vec2(c2x, c2y);
4939
+ const end = new Vec2(x, y);
4683
4940
  const dx = end.x - start.x;
4684
4941
  const dy = end.y - start.y;
4685
4942
  const d1 = Math.abs((control1.x - end.x) * dy - (control1.y - end.y) * dx);
@@ -4721,7 +4978,7 @@ class GlyphContourCollector {
4721
4978
  this.currentGlyphBounds.max.y = Math.max(this.currentGlyphBounds.max.y, point.y);
4722
4979
  }
4723
4980
  getCollectedGlyphs() {
4724
- // Make sure to finish any pending glyph
4981
+ // Finish any pending glyph
4725
4982
  if (this.currentGlyphPaths.length > 0) {
4726
4983
  this.finishGlyph();
4727
4984
  }
@@ -4744,8 +5001,8 @@ class GlyphContourCollector {
4744
5001
  this.currentTextIndex = 0;
4745
5002
  this.currentPosition.set(0, 0);
4746
5003
  this.currentGlyphBounds = {
4747
- min: new Vector2(Infinity, Infinity),
4748
- max: new Vector2(-Infinity, -Infinity)
5004
+ min: new Vec2(Infinity, Infinity),
5005
+ max: new Vec2(-Infinity, -Infinity)
4749
5006
  };
4750
5007
  }
4751
5008
  setCurveFidelityConfig(config) {
@@ -4914,7 +5171,7 @@ class GlyphGeometryBuilder {
4914
5171
  clusterGlyphContours.push(this.getContoursForGlyph(glyph.g));
4915
5172
  }
4916
5173
  // Step 2: Check for overlaps within the cluster
4917
- const relativePositions = cluster.glyphs.map((g) => new Vector3(g.x, g.y, 0));
5174
+ const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x, g.y, 0));
4918
5175
  const boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
4919
5176
  const hasOverlaps = separateGlyphs
4920
5177
  ? false
@@ -4931,7 +5188,7 @@ class GlyphGeometryBuilder {
4931
5188
  for (const path of glyphContours.paths) {
4932
5189
  clusterPaths.push({
4933
5190
  ...path,
4934
- points: path.points.map((p) => new Vector2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
5191
+ points: path.points.map((p) => new Vec2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
4935
5192
  });
4936
5193
  }
4937
5194
  }
@@ -4944,7 +5201,7 @@ class GlyphGeometryBuilder {
4944
5201
  for (let i = 0; i < cluster.glyphs.length; i++) {
4945
5202
  const glyph = cluster.glyphs[i];
4946
5203
  const glyphContours = clusterGlyphContours[i];
4947
- const absoluteGlyphPosition = new Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5204
+ const absoluteGlyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4948
5205
  const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, clusterVertexCount, absoluteGlyphPosition, glyphContours, depth);
4949
5206
  glyphInfos.push(glyphInfo);
4950
5207
  this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
@@ -4955,7 +5212,7 @@ class GlyphGeometryBuilder {
4955
5212
  for (let i = 0; i < cluster.glyphs.length; i++) {
4956
5213
  const glyph = cluster.glyphs[i];
4957
5214
  const glyphContours = clusterGlyphContours[i];
4958
- const glyphPosition = new Vector3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
5215
+ const glyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
4959
5216
  // Skip glyphs with no paths (spaces, zero-width characters, etc.)
4960
5217
  if (glyphContours.paths.length === 0) {
4961
5218
  const glyphInfo = this.createGlyphInfo(glyph, 0, 0, glyphPosition, glyphContours, depth);
@@ -4976,15 +5233,17 @@ class GlyphGeometryBuilder {
4976
5233
  }
4977
5234
  }
4978
5235
  }
4979
- const geometry = new BufferGeometry();
4980
5236
  const vertexArray = new Float32Array(vertices);
4981
5237
  const normalArray = new Float32Array(normals);
4982
- geometry.setAttribute('position', new Float32BufferAttribute(vertexArray, 3));
4983
- geometry.setAttribute('normal', new Float32BufferAttribute(normalArray, 3));
4984
- geometry.setIndex(indices);
4985
- geometry.computeBoundingBox();
5238
+ const indexArray = new Uint32Array(indices);
4986
5239
  perfLogger.end('GlyphGeometryBuilder.buildInstancedGeometry');
4987
- return { geometry, glyphInfos, planeBounds };
5240
+ return {
5241
+ vertices: vertexArray,
5242
+ normals: normalArray,
5243
+ indices: indexArray,
5244
+ glyphInfos,
5245
+ planeBounds
5246
+ };
4988
5247
  }
4989
5248
  appendGeometry(vertices, normals, indices, data, position, offset) {
4990
5249
  for (let j = 0; j < data.vertices.length; j += 3) {
@@ -5042,19 +5301,34 @@ class GlyphGeometryBuilder {
5042
5301
  }
5043
5302
  extrudeAndPackage(processedGeometry, depth) {
5044
5303
  const extrudedResult = this.extruder.extrude(processedGeometry, depth, this.loadedFont.upem);
5045
- const tempGeometry = new BufferGeometry();
5046
- const vertices = new Float32Array(extrudedResult.vertices);
5047
- tempGeometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
5048
- tempGeometry.computeBoundingBox();
5049
- const bounds = tempGeometry.boundingBox;
5050
- const boundsMin = new Vector3(bounds.min.x, bounds.min.y, bounds.min.z);
5051
- const boundsMax = new Vector3(bounds.max.x, bounds.max.y, bounds.max.z);
5052
- tempGeometry.dispose();
5304
+ // Compute bounding box from vertices
5305
+ const vertices = extrudedResult.vertices;
5306
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
5307
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
5308
+ for (let i = 0; i < vertices.length; i += 3) {
5309
+ const x = vertices[i];
5310
+ const y = vertices[i + 1];
5311
+ const z = vertices[i + 2];
5312
+ if (x < minX)
5313
+ minX = x;
5314
+ if (x > maxX)
5315
+ maxX = x;
5316
+ if (y < minY)
5317
+ minY = y;
5318
+ if (y > maxY)
5319
+ maxY = y;
5320
+ if (z < minZ)
5321
+ minZ = z;
5322
+ if (z > maxZ)
5323
+ maxZ = z;
5324
+ }
5325
+ const boundsMin = new Vec3(minX, minY, minZ);
5326
+ const boundsMax = new Vec3(maxX, maxY, maxZ);
5053
5327
  const vertexCount = extrudedResult.vertices.length / 3;
5054
5328
  const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
5055
5329
  return {
5056
5330
  geometry: processedGeometry,
5057
- vertices: vertices,
5331
+ vertices: new Float32Array(extrudedResult.vertices),
5058
5332
  normals: new Float32Array(extrudedResult.normals),
5059
5333
  indices: new IndexArray(extrudedResult.indices),
5060
5334
  bounds: { min: boundsMin, max: boundsMax },
@@ -5071,8 +5345,8 @@ class GlyphGeometryBuilder {
5071
5345
  return this.extrudeAndPackage(processedGeometry, depth);
5072
5346
  }
5073
5347
  updatePlaneBounds(glyphBounds, planeBounds) {
5074
- const planeBox = new Box3(new Vector3(planeBounds.min.x, planeBounds.min.y, planeBounds.min.z), new Vector3(planeBounds.max.x, planeBounds.max.y, planeBounds.max.z));
5075
- const glyphBox = new Box3(new Vector3(glyphBounds.min.x, glyphBounds.min.y, glyphBounds.min.z), new Vector3(glyphBounds.max.x, glyphBounds.max.y, glyphBounds.max.z));
5348
+ 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));
5349
+ 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));
5076
5350
  planeBox.union(glyphBox);
5077
5351
  planeBounds.min.x = planeBox.min.x;
5078
5352
  planeBounds.min.y = planeBox.min.y;
@@ -5137,8 +5411,8 @@ class TextShaper {
5137
5411
  const clusters = [];
5138
5412
  let currentClusterGlyphs = [];
5139
5413
  let currentClusterText = '';
5140
- let clusterStartPosition = new Vector3();
5141
- let cursor = new Vector3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5414
+ let clusterStartPosition = new Vec3();
5415
+ let cursor = new Vec3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
5142
5416
  const letterSpacingFU = letterSpacing * this.loadedFont.upem;
5143
5417
  const spaceAdjustment = this.calculateSpaceAdjustment(lineInfo, align, letterSpacing);
5144
5418
  for (let i = 0; i < glyphInfos.length; i++) {
@@ -5168,7 +5442,7 @@ class TextShaper {
5168
5442
  }
5169
5443
  const absoluteGlyphPosition = cursor
5170
5444
  .clone()
5171
- .add(new Vector3(glyph.dx, glyph.dy, 0));
5445
+ .add(new Vec3(glyph.dx, glyph.dy, 0));
5172
5446
  if (!isWhitespace) {
5173
5447
  if (currentClusterGlyphs.length === 0) {
5174
5448
  clusterStartPosition.copy(absoluteGlyphPosition);
@@ -5301,7 +5575,7 @@ class GlyphCache {
5301
5575
  size += glyph.normals.length * 4;
5302
5576
  // Indices (Uint16Array or Uint32Array)
5303
5577
  size += glyph.indices.length * glyph.indices.BYTES_PER_ELEMENT;
5304
- // Bounds (2 Vector3s = 6 floats * 4 bytes)
5578
+ // Bounds (2 Vec3s = 6 floats * 4 bytes)
5305
5579
  size += 24;
5306
5580
  // Object overhead
5307
5581
  size += 256;
@@ -6119,7 +6393,7 @@ class TextRangeQuery {
6119
6393
  }
6120
6394
  const box = new Box3();
6121
6395
  for (const glyph of glyphs) {
6122
- const glyphBox = new Box3(new Vector3(glyph.bounds.min.x, glyph.bounds.min.y, glyph.bounds.min.z), new Vector3(glyph.bounds.max.x, glyph.bounds.max.y, glyph.bounds.max.z));
6396
+ 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));
6123
6397
  box.union(glyphBox);
6124
6398
  }
6125
6399
  return {
@@ -6150,7 +6424,7 @@ class Text {
6150
6424
  HarfBuzzLoader.setWasmBuffer(wasmBuffer);
6151
6425
  Text.hbInitPromise = null;
6152
6426
  }
6153
- // Initialize HarfBuzz WASM manually (optional - called automatically by create())
6427
+ // Initialize HarfBuzz WASM (optional - create() calls this if needed)
6154
6428
  static init() {
6155
6429
  if (!Text.hbInitPromise) {
6156
6430
  Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
@@ -6284,9 +6558,10 @@ class Text {
6284
6558
  const clustersByLine = this.textShaper.shapeLines(layoutData.lines, layoutData.scaledLineHeight, layoutData.letterSpacing, layoutData.align, layoutData.direction, options.color, options.text);
6285
6559
  const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.separateGlyphsWithAttributes || false);
6286
6560
  const cacheStats = this.geometryBuilder.getCacheStats();
6287
- const result = this.finalizeGeometry(shapedResult.geometry, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6561
+ const result = this.finalizeGeometry(shapedResult.vertices, shapedResult.normals, shapedResult.indices, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
6288
6562
  if (options.separateGlyphsWithAttributes) {
6289
- this.addGlyphAttributes(result.geometry, result.glyphs);
6563
+ const glyphAttrs = this.createGlyphAttributes(result.vertices.length / 3, result.glyphs);
6564
+ result.glyphAttributes = glyphAttrs;
6290
6565
  }
6291
6566
  return result;
6292
6567
  }
@@ -6398,8 +6673,8 @@ class Text {
6398
6673
  size
6399
6674
  };
6400
6675
  }
6401
- applyColorSystem(geometry, glyphInfoArray, color, originalText) {
6402
- const vertexCount = geometry.attributes.position.count;
6676
+ applyColorSystem(vertices, glyphInfoArray, color, originalText) {
6677
+ const vertexCount = vertices.length / 3;
6403
6678
  const colors = new Float32Array(vertexCount * 3);
6404
6679
  const coloredRanges = [];
6405
6680
  // Simple case: array color for all text
@@ -6489,16 +6764,15 @@ class Text {
6489
6764
  });
6490
6765
  }
6491
6766
  }
6492
- geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));
6493
- return coloredRanges;
6767
+ return { colors, coloredRanges };
6494
6768
  }
6495
- finalizeGeometry(geometry, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6769
+ finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, cacheStats, originalText) {
6496
6770
  const { layout = {}, size = 72 } = options;
6497
6771
  const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
6498
6772
  if (!this.textLayout) {
6499
6773
  this.textLayout = new TextLayout(this.loadedFont);
6500
6774
  }
6501
- const alignmentResult = this.textLayout.applyAlignment(geometry, {
6775
+ const alignmentResult = this.textLayout.applyAlignment(vertices, {
6502
6776
  width,
6503
6777
  align,
6504
6778
  planeBounds
@@ -6507,7 +6781,13 @@ class Text {
6507
6781
  planeBounds.min.x = alignmentResult.adjustedBounds.min.x;
6508
6782
  planeBounds.max.x = alignmentResult.adjustedBounds.max.x;
6509
6783
  const finalScale = size / this.loadedFont.upem;
6510
- geometry.scale(finalScale, finalScale, finalScale);
6784
+ // Scale vertices and normals directly
6785
+ for (let i = 0; i < vertices.length; i++) {
6786
+ vertices[i] *= finalScale;
6787
+ }
6788
+ for (let i = 0; i < normals.length; i++) {
6789
+ normals[i] *= finalScale;
6790
+ }
6511
6791
  planeBounds.min.x *= finalScale;
6512
6792
  planeBounds.min.y *= finalScale;
6513
6793
  planeBounds.min.z *= finalScale;
@@ -6527,15 +6807,22 @@ class Text {
6527
6807
  glyphInfo.bounds.max.y *= finalScale;
6528
6808
  glyphInfo.bounds.max.z *= finalScale;
6529
6809
  }
6530
- const coloredRanges = options.color
6531
- ? this.applyColorSystem(geometry, glyphInfoArray, options.color, options.text)
6532
- : undefined;
6810
+ let colors;
6811
+ let coloredRanges;
6812
+ if (options.color) {
6813
+ const colorResult = this.applyColorSystem(vertices, glyphInfoArray, options.color, options.text);
6814
+ colors = colorResult.colors;
6815
+ coloredRanges = colorResult.coloredRanges;
6816
+ }
6533
6817
  // Collect optimization stats for return value
6534
6818
  const optimizationStats = this.geometryBuilder.getOptimizationStats();
6535
- const trianglesGenerated = geometry.index ? geometry.index.count / 3 : 0;
6536
- const verticesGenerated = geometry.attributes.position.count;
6819
+ const trianglesGenerated = indices.length / 3;
6820
+ const verticesGenerated = vertices.length / 3;
6537
6821
  return {
6538
- geometry,
6822
+ vertices,
6823
+ normals,
6824
+ indices,
6825
+ colors,
6539
6826
  glyphs: glyphInfoArray,
6540
6827
  planeBounds,
6541
6828
  stats: {
@@ -6553,7 +6840,8 @@ class Text {
6553
6840
  const queryInstance = new TextRangeQuery(originalText, glyphInfoArray);
6554
6841
  return queryInstance.execute(options);
6555
6842
  },
6556
- coloredRanges
6843
+ coloredRanges,
6844
+ glyphAttributes: undefined
6557
6845
  };
6558
6846
  }
6559
6847
  getFontMetrics() {
@@ -6598,8 +6886,7 @@ class Text {
6598
6886
  this.geometryBuilder.clearCache();
6599
6887
  }
6600
6888
  }
6601
- addGlyphAttributes(geometry, glyphs) {
6602
- const vertexCount = geometry.attributes.position.count;
6889
+ createGlyphAttributes(vertexCount, glyphs) {
6603
6890
  const glyphCenters = new Float32Array(vertexCount * 3);
6604
6891
  const glyphIndices = new Float32Array(vertexCount);
6605
6892
  const glyphLineIndices = new Float32Array(vertexCount);
@@ -6618,9 +6905,11 @@ class Text {
6618
6905
  }
6619
6906
  }
6620
6907
  });
6621
- geometry.setAttribute('glyphCenter', new Float32BufferAttribute(glyphCenters, 3));
6622
- geometry.setAttribute('glyphIndex', new Float32BufferAttribute(glyphIndices, 1));
6623
- geometry.setAttribute('glyphLineIndex', new Float32BufferAttribute(glyphLineIndices, 1));
6908
+ return {
6909
+ glyphCenter: glyphCenters,
6910
+ glyphIndex: glyphIndices,
6911
+ glyphLineIndex: glyphLineIndices
6912
+ };
6624
6913
  }
6625
6914
  destroy() {
6626
6915
  if (!this.loadedFont) {