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.
- package/README.md +166 -156
- package/dist/index.cjs +370 -81
- package/dist/index.d.ts +46 -17
- package/dist/index.js +369 -80
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +374 -83
- 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.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
3950
|
-
const normal = new
|
|
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
|
|
4261
|
-
const v2 = new
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
4582
|
-
max: new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4662
|
-
const end = new
|
|
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
|
|
4684
|
-
const control2 = new
|
|
4685
|
-
const end = new
|
|
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
|
-
//
|
|
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
|
|
4751
|
-
max: new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
5049
|
-
const vertices =
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
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
|
|
5078
|
-
const glyphBox = new
|
|
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
|
|
5144
|
-
let cursor = new
|
|
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
|
|
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
|
|
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
|
|
6397
|
+
const box = new Box3();
|
|
6124
6398
|
for (const glyph of glyphs) {
|
|
6125
|
-
const glyphBox = new
|
|
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
|
|
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.
|
|
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.
|
|
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(
|
|
6405
|
-
const vertexCount =
|
|
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
|
-
|
|
6496
|
-
return coloredRanges;
|
|
6770
|
+
return { colors, coloredRanges };
|
|
6497
6771
|
}
|
|
6498
|
-
finalizeGeometry(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
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 =
|
|
6539
|
-
const verticesGenerated =
|
|
6822
|
+
const trianglesGenerated = indices.length / 3;
|
|
6823
|
+
const verticesGenerated = vertices.length / 3;
|
|
6540
6824
|
return {
|
|
6541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6911
|
+
return {
|
|
6912
|
+
glyphCenter: glyphCenters,
|
|
6913
|
+
glyphIndex: glyphIndices,
|
|
6914
|
+
glyphLineIndex: glyphLineIndices
|
|
6915
|
+
};
|
|
6627
6916
|
}
|
|
6628
6917
|
destroy() {
|
|
6629
6918
|
if (!this.loadedFont) {
|