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.
- package/README.md +166 -156
- package/dist/index.cjs +368 -82
- package/dist/index.d.ts +46 -17
- package/dist/index.js +367 -81
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +372 -84
- package/dist/index.umd.min.js +2 -2
- package/dist/p5/index.cjs +33 -0
- package/dist/p5/index.d.ts +19 -0
- package/dist/p5/index.js +31 -0
- package/dist/three/index.cjs +50 -0
- package/dist/three/index.d.ts +29 -0
- package/dist/three/index.js +48 -0
- package/dist/{react/index.cjs → three/react.cjs} +14 -4
- package/dist/three/react.d.ts +346 -0
- package/dist/{react/index.js → three/react.js} +14 -4
- package/dist/types/core/Text.d.ts +1 -1
- package/dist/types/core/cache/GlyphCache.d.ts +4 -4
- package/dist/types/core/cache/GlyphContourCollector.d.ts +2 -2
- package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +3 -2
- package/dist/types/core/geometry/BoundaryClusterer.d.ts +2 -2
- package/dist/types/core/geometry/Polygonizer.d.ts +3 -3
- package/dist/types/core/layout/TextLayout.d.ts +1 -2
- package/dist/types/core/shaping/TextShaper.d.ts +1 -2
- package/dist/types/core/types.d.ts +13 -16
- package/dist/types/core/vectors.d.ts +75 -0
- package/dist/types/p5/index.d.ts +17 -0
- package/dist/types/{react → three}/ThreeText.d.ts +2 -2
- package/dist/types/three/index.d.ts +21 -0
- package/dist/types/three/react.d.ts +10 -0
- package/dist/types/webgl/index.d.ts +48 -0
- package/dist/types/webgpu/index.d.ts +16 -0
- package/dist/webgl/index.cjs +88 -0
- package/dist/webgl/index.d.ts +51 -0
- package/dist/webgl/index.js +86 -0
- package/dist/webgpu/index.cjs +99 -0
- package/dist/webgpu/index.d.ts +19 -0
- package/dist/webgpu/index.js +97 -0
- package/package.json +22 -6
- package/dist/react/index.d.ts +0 -18
- package/dist/types/react/index.d.ts +0 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.
|
|
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
|
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
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";
|
|
@@ -3907,7 +4170,7 @@ class Extruder {
|
|
|
3907
4170
|
const triangleIndices = triangulatedData.indices;
|
|
3908
4171
|
for (let i = 0; i < points.length; i += 2) {
|
|
3909
4172
|
vertices.push(points[i], points[i + 1], 0);
|
|
3910
|
-
normals.push(0, 0, 1);
|
|
4173
|
+
normals.push(0, 0, -1);
|
|
3911
4174
|
}
|
|
3912
4175
|
// Add triangle indices
|
|
3913
4176
|
for (let i = 0; i < triangleIndices.length; i++) {
|
|
@@ -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
|
|
3947
|
-
const normal = new
|
|
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
|
|
4258
|
-
const v2 = new
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
4579
|
-
max: new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4659
|
-
const end = new
|
|
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
|
|
4681
|
-
const control2 = new
|
|
4682
|
-
const end = new
|
|
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
|
-
//
|
|
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
|
|
4748
|
-
max: new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
5046
|
-
const vertices =
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
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
|
|
5075
|
-
const glyphBox = new Box3(new
|
|
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
|
|
5141
|
-
let cursor = new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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(
|
|
6402
|
-
const vertexCount =
|
|
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
|
-
|
|
6493
|
-
return coloredRanges;
|
|
6767
|
+
return { colors, coloredRanges };
|
|
6494
6768
|
}
|
|
6495
|
-
finalizeGeometry(
|
|
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(
|
|
6775
|
+
const alignmentResult = this.textLayout.applyAlignment(vertices, {
|
|
6502
6776
|
width,
|
|
6503
6777
|
align,
|
|
6504
6778
|
planeBounds
|
|
@@ -6507,7 +6781,10 @@ 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
|
-
|
|
6784
|
+
// Scale vertices only (normals are unit vectors, don't scale)
|
|
6785
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
6786
|
+
vertices[i] *= finalScale;
|
|
6787
|
+
}
|
|
6511
6788
|
planeBounds.min.x *= finalScale;
|
|
6512
6789
|
planeBounds.min.y *= finalScale;
|
|
6513
6790
|
planeBounds.min.z *= finalScale;
|
|
@@ -6527,15 +6804,22 @@ class Text {
|
|
|
6527
6804
|
glyphInfo.bounds.max.y *= finalScale;
|
|
6528
6805
|
glyphInfo.bounds.max.z *= finalScale;
|
|
6529
6806
|
}
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6807
|
+
let colors;
|
|
6808
|
+
let coloredRanges;
|
|
6809
|
+
if (options.color) {
|
|
6810
|
+
const colorResult = this.applyColorSystem(vertices, glyphInfoArray, options.color, options.text);
|
|
6811
|
+
colors = colorResult.colors;
|
|
6812
|
+
coloredRanges = colorResult.coloredRanges;
|
|
6813
|
+
}
|
|
6533
6814
|
// Collect optimization stats for return value
|
|
6534
6815
|
const optimizationStats = this.geometryBuilder.getOptimizationStats();
|
|
6535
|
-
const trianglesGenerated =
|
|
6536
|
-
const verticesGenerated =
|
|
6816
|
+
const trianglesGenerated = indices.length / 3;
|
|
6817
|
+
const verticesGenerated = vertices.length / 3;
|
|
6537
6818
|
return {
|
|
6538
|
-
|
|
6819
|
+
vertices,
|
|
6820
|
+
normals,
|
|
6821
|
+
indices,
|
|
6822
|
+
colors,
|
|
6539
6823
|
glyphs: glyphInfoArray,
|
|
6540
6824
|
planeBounds,
|
|
6541
6825
|
stats: {
|
|
@@ -6553,7 +6837,8 @@ class Text {
|
|
|
6553
6837
|
const queryInstance = new TextRangeQuery(originalText, glyphInfoArray);
|
|
6554
6838
|
return queryInstance.execute(options);
|
|
6555
6839
|
},
|
|
6556
|
-
coloredRanges
|
|
6840
|
+
coloredRanges,
|
|
6841
|
+
glyphAttributes: undefined
|
|
6557
6842
|
};
|
|
6558
6843
|
}
|
|
6559
6844
|
getFontMetrics() {
|
|
@@ -6598,8 +6883,7 @@ class Text {
|
|
|
6598
6883
|
this.geometryBuilder.clearCache();
|
|
6599
6884
|
}
|
|
6600
6885
|
}
|
|
6601
|
-
|
|
6602
|
-
const vertexCount = geometry.attributes.position.count;
|
|
6886
|
+
createGlyphAttributes(vertexCount, glyphs) {
|
|
6603
6887
|
const glyphCenters = new Float32Array(vertexCount * 3);
|
|
6604
6888
|
const glyphIndices = new Float32Array(vertexCount);
|
|
6605
6889
|
const glyphLineIndices = new Float32Array(vertexCount);
|
|
@@ -6618,9 +6902,11 @@ class Text {
|
|
|
6618
6902
|
}
|
|
6619
6903
|
}
|
|
6620
6904
|
});
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6905
|
+
return {
|
|
6906
|
+
glyphCenter: glyphCenters,
|
|
6907
|
+
glyphIndex: glyphIndices,
|
|
6908
|
+
glyphLineIndex: glyphLineIndices
|
|
6909
|
+
};
|
|
6624
6910
|
}
|
|
6625
6911
|
destroy() {
|
|
6626
6912
|
if (!this.loadedFont) {
|