three-text 0.2.18 → 0.2.19
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 +8 -8
- package/dist/index.cjs +35 -23
- package/dist/index.d.ts +48 -43
- package/dist/index.js +35 -23
- package/dist/index.min.cjs +3 -3
- package/dist/index.min.js +3 -3
- package/dist/index.umd.js +35 -23
- package/dist/index.umd.min.js +4 -4
- package/dist/three/index.cjs +1 -0
- package/dist/three/index.d.ts +13 -1
- package/dist/three/index.js +1 -0
- package/dist/three/react.cjs +2 -1
- package/dist/three/react.d.ts +18 -12
- package/dist/three/react.js +2 -1
- package/dist/types/core/Text.d.ts +0 -1
- package/dist/types/core/types.d.ts +10 -3
- package/dist/types/three/index.d.ts +6 -1
- package/dist/types/three/react.d.ts +1 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.typescriptlang.org/)
|
|
5
5
|
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
High fidelity 3D mesh font geometry and text layout engine for the web
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -15,11 +15,7 @@ A high fidelity 3D font renderer and text layout engine for the web
|
|
|
15
15
|
> [!CAUTION]
|
|
16
16
|
> three-text is an alpha release and the API may break rapidly. This warning will last at least through the end of 2025. If API stability is important to you, consider pinning your version. Community feedback is encouraged; please open an issue if you have any suggestions or feedback, thank you
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
text: `three-text is a 3D font geometry and text layout library for the web. Its supports TTF, OTF, and WOFF font files. For layout, it uses Tex-based parameters for breaking text into paragraphs across multiple lines and supports CJK and RTL scripts. three-text caches the geometries it generates for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate, and can be animated by re-drawing each frame with new coordinates. The library has a framework-agnostic core that returns raw vertex data, with lightweight adapters for Three.js, React Three Fiber, p5.js, WebGL and WebGPU. Under the hood, three-text relies on HarfBuzz for text shaping, Knuth-Plass line breaking, Liang hyphenation, libtess by Eric Veach for tessellation, curve polygonization from Maxim Shemanarev's Anti-Grain Geometry, and Visvalingam-Whyatt line simplification`,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**three-text** is a 3D font geometry and text layout library for the web. It supports TTF, OTF, and WOFF font files. For layout, it uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines and supports CJK and RTL scripts. three-text caches the geometries it generates for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate, and can be animated by re-drawing each frame with new coordinates
|
|
18
|
+
**three-text** is a 3D mesh font geometry and text layout library for the web. It supports TTF, OTF, and WOFF font files. For layout, it uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines and supports CJK and RTL scripts. three-text caches the geometries it generates for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate, and can be animated by re-drawing each frame with new coordinates
|
|
23
19
|
|
|
24
20
|
The library has a framework-agnostic core that returns raw vertex data, with lightweight adapters for [Three.js](https://threejs.org), [React Three Fiber](https://docs.pmnd.rs/react-three-fiber), [p5.js](https://p5js.org), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
25
21
|
|
|
@@ -556,7 +552,7 @@ const text = await Text.create({
|
|
|
556
552
|
|
|
557
553
|
Values can be boolean (`true`/`false`) to enable or disable, or numeric for features accepting variant indices. Explicitly disabling a feature overrides the font's defaults
|
|
558
554
|
|
|
559
|
-
Common tags include [`liga`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#liga) (ligatures),
|
|
555
|
+
Common tags include [`liga`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ko#liga) (ligatures), [`calt`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#calt) (contextual alternates), [`tnum`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum) (tabular numbers), sylistic alternates [`ss01`-`ss20`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#ss01--ss20) and character variants [`cv01`-`cv99`](https://learn.microsoft.com/en-us/typography/opentype/spec/features_ae#cv01--cv99). Feature availability depends on the font
|
|
560
556
|
|
|
561
557
|
### Per-glyph attributes
|
|
562
558
|
|
|
@@ -736,6 +732,10 @@ Initializes HarfBuzz WebAssembly. Called automatically by `create()`, but can be
|
|
|
736
732
|
|
|
737
733
|
Preloads hyphenation patterns for specified languages. Useful for avoiding async pattern loading during text rendering
|
|
738
734
|
|
|
735
|
+
##### `Text.setMaxFontCacheMemoryMB(limitMB: number): void`
|
|
736
|
+
|
|
737
|
+
Sets an upper bound for the font cache, measured by the raw font buffer size, eviction is FIFO by insertion order
|
|
738
|
+
|
|
739
739
|
#### Instance Methods
|
|
740
740
|
|
|
741
741
|
The following methods are available on instances created by `Text.create()`:
|
|
@@ -753,7 +753,7 @@ Below are the most important configuration interfaces. For a complete list of al
|
|
|
753
753
|
```typescript
|
|
754
754
|
interface TextOptions {
|
|
755
755
|
text: string; // Text content to render
|
|
756
|
-
font
|
|
756
|
+
font: string | ArrayBuffer; // Font file path or buffer (TTF, OTF, or WOFF)
|
|
757
757
|
size?: number; // Font size in scene units (default: 72)
|
|
758
758
|
depth?: number; // Extrusion depth (default: 0)
|
|
759
759
|
lineHeight?: number; // Line height multiplier (default: 1.0)
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -81,7 +81,9 @@ class PerformanceLogger {
|
|
|
81
81
|
// Find the metric in reverse order (most recent first)
|
|
82
82
|
for (let i = this.metrics.length - 1; i >= 0; i--) {
|
|
83
83
|
const metric = this.metrics[i];
|
|
84
|
-
if (metric.name === name &&
|
|
84
|
+
if (metric.name === name &&
|
|
85
|
+
metric.startTime === startTime &&
|
|
86
|
+
!metric.endTime) {
|
|
85
87
|
metric.endTime = endTime;
|
|
86
88
|
metric.duration = duration;
|
|
87
89
|
break;
|
|
@@ -469,7 +471,9 @@ class LineBreak {
|
|
|
469
471
|
const char = chars[i];
|
|
470
472
|
const nextChar = i < chars.length - 1 ? chars[i + 1] : null;
|
|
471
473
|
if (/\s/.test(char)) {
|
|
472
|
-
const width = widths
|
|
474
|
+
const width = widths
|
|
475
|
+
? (widths[i] ?? measureText(char))
|
|
476
|
+
: measureText(char);
|
|
473
477
|
items.push({
|
|
474
478
|
type: ItemType.GLUE,
|
|
475
479
|
width,
|
|
@@ -842,7 +846,9 @@ class LineBreak {
|
|
|
842
846
|
if (breaks.length === 0) {
|
|
843
847
|
// For first emergency attempt, use initialEmergencyStretch
|
|
844
848
|
// For subsequent iterations (short line detection), progressively increase
|
|
845
|
-
currentEmergencyStretch =
|
|
849
|
+
currentEmergencyStretch =
|
|
850
|
+
initialEmergencyStretch +
|
|
851
|
+
iteration * width * SHORT_LINE_EMERGENCY_STRETCH_INCREMENT;
|
|
846
852
|
breaks = LineBreak.findBreakpoints(currentItems, width, tolerance, looseness, true, currentEmergencyStretch, context);
|
|
847
853
|
}
|
|
848
854
|
// Last resort: allow higher badness (but not infinite)
|
|
@@ -1723,12 +1729,12 @@ class FontMetadataExtractor {
|
|
|
1723
1729
|
try {
|
|
1724
1730
|
if (gsubTableOffset) {
|
|
1725
1731
|
const gsubData = this.extractFeatureDataFromTable(view, gsubTableOffset, nameTableOffset);
|
|
1726
|
-
gsubData.features.forEach(f => features.add(f));
|
|
1732
|
+
gsubData.features.forEach((f) => features.add(f));
|
|
1727
1733
|
Object.assign(featureNames, gsubData.names);
|
|
1728
1734
|
}
|
|
1729
1735
|
if (gposTableOffset) {
|
|
1730
1736
|
const gposData = this.extractFeatureDataFromTable(view, gposTableOffset, nameTableOffset);
|
|
1731
|
-
gposData.features.forEach(f => features.add(f));
|
|
1737
|
+
gposData.features.forEach((f) => features.add(f));
|
|
1732
1738
|
Object.assign(featureNames, gposData.names);
|
|
1733
1739
|
}
|
|
1734
1740
|
}
|
|
@@ -2729,7 +2735,7 @@ class Tessellator {
|
|
|
2729
2735
|
let extrusionContours = needsExtrusionContours
|
|
2730
2736
|
? needsWindingReversal
|
|
2731
2737
|
? tessContours
|
|
2732
|
-
: originalContours ?? this.pathsToContours(paths)
|
|
2738
|
+
: (originalContours ?? this.pathsToContours(paths))
|
|
2733
2739
|
: [];
|
|
2734
2740
|
if (removeOverlaps) {
|
|
2735
2741
|
logger.log('Two-pass: boundary extraction then triangulation');
|
|
@@ -2762,7 +2768,10 @@ class Tessellator {
|
|
|
2762
2768
|
? 'libtess returned empty result from triangulation pass'
|
|
2763
2769
|
: 'libtess returned empty result from single-pass triangulation';
|
|
2764
2770
|
logger.warn(warning);
|
|
2765
|
-
return {
|
|
2771
|
+
return {
|
|
2772
|
+
triangles: { vertices: [], indices: [] },
|
|
2773
|
+
contours: extrusionContours
|
|
2774
|
+
};
|
|
2766
2775
|
}
|
|
2767
2776
|
return {
|
|
2768
2777
|
triangles: {
|
|
@@ -3419,9 +3428,7 @@ class PathOptimizer {
|
|
|
3419
3428
|
const v1LenSq = v1x * v1x + v1y * v1y;
|
|
3420
3429
|
const v2LenSq = v2x * v2x + v2y * v2y;
|
|
3421
3430
|
const minLenSq = this.config.minSegmentLength * this.config.minSegmentLength;
|
|
3422
|
-
if (angle > threshold ||
|
|
3423
|
-
v1LenSq < minLenSq ||
|
|
3424
|
-
v2LenSq < minLenSq) {
|
|
3431
|
+
if (angle > threshold || v1LenSq < minLenSq || v2LenSq < minLenSq) {
|
|
3425
3432
|
result.push(current);
|
|
3426
3433
|
}
|
|
3427
3434
|
else {
|
|
@@ -4183,7 +4190,7 @@ class GlyphGeometryBuilder {
|
|
|
4183
4190
|
const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x ?? 0, g.y ?? 0, 0));
|
|
4184
4191
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
4185
4192
|
this.clusteringCache.set(cacheKey, {
|
|
4186
|
-
glyphIds: cluster.glyphs.map(g => g.g),
|
|
4193
|
+
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
4187
4194
|
groups: boundaryGroups
|
|
4188
4195
|
});
|
|
4189
4196
|
}
|
|
@@ -4573,7 +4580,8 @@ class TextShaper {
|
|
|
4573
4580
|
if (LineBreak.isCJOpeningPunctuation(currentChar)) {
|
|
4574
4581
|
shouldApply = false;
|
|
4575
4582
|
}
|
|
4576
|
-
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4583
|
+
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4584
|
+
LineBreak.isCJPunctuation(nextChar)) {
|
|
4577
4585
|
shouldApply = false;
|
|
4578
4586
|
}
|
|
4579
4587
|
if (shouldApply) {
|
|
@@ -5377,7 +5385,7 @@ class Text {
|
|
|
5377
5385
|
// Stringify with sorted keys for cache stability
|
|
5378
5386
|
static stableStringify(obj) {
|
|
5379
5387
|
const keys = Object.keys(obj).sort();
|
|
5380
|
-
const pairs = keys.map(k => `${k}:${obj[k]}`);
|
|
5388
|
+
const pairs = keys.map((k) => `${k}:${obj[k]}`);
|
|
5381
5389
|
return pairs.join(',');
|
|
5382
5390
|
}
|
|
5383
5391
|
constructor() {
|
|
@@ -5615,7 +5623,9 @@ class Text {
|
|
|
5615
5623
|
// to selectively use glyph-level caching (separate vertices) only for clusters containing
|
|
5616
5624
|
// colored text, while non-colored clusters can still use fast cluster-level merging
|
|
5617
5625
|
let coloredTextIndices;
|
|
5618
|
-
if (options.color &&
|
|
5626
|
+
if (options.color &&
|
|
5627
|
+
typeof options.color === 'object' &&
|
|
5628
|
+
!Array.isArray(options.color)) {
|
|
5619
5629
|
if (options.color.byText || options.color.byCharRange) {
|
|
5620
5630
|
// Build the set manually since glyphs don't exist yet
|
|
5621
5631
|
coloredTextIndices = new Set();
|
|
@@ -5720,7 +5730,9 @@ class Text {
|
|
|
5720
5730
|
const depthScale = this.loadedFont.upem / size;
|
|
5721
5731
|
const rawDepthInFontUnits = depth * depthScale;
|
|
5722
5732
|
const minExtrudeDepth = this.loadedFont.upem * 0.000025;
|
|
5723
|
-
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5733
|
+
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5734
|
+
? 0
|
|
5735
|
+
: Math.max(rawDepthInFontUnits, minExtrudeDepth);
|
|
5724
5736
|
if (!this.textLayout) {
|
|
5725
5737
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
5726
5738
|
}
|
|
@@ -5892,10 +5904,12 @@ class Text {
|
|
|
5892
5904
|
planeBounds.max.z *= finalScale;
|
|
5893
5905
|
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5894
5906
|
const glyphInfo = glyphInfoArray[i];
|
|
5895
|
-
glyphInfo.bounds.min.x =
|
|
5907
|
+
glyphInfo.bounds.min.x =
|
|
5908
|
+
glyphInfo.bounds.min.x * finalScale + offsetScaled;
|
|
5896
5909
|
glyphInfo.bounds.min.y *= finalScale;
|
|
5897
5910
|
glyphInfo.bounds.min.z *= finalScale;
|
|
5898
|
-
glyphInfo.bounds.max.x =
|
|
5911
|
+
glyphInfo.bounds.max.x =
|
|
5912
|
+
glyphInfo.bounds.max.x * finalScale + offsetScaled;
|
|
5899
5913
|
glyphInfo.bounds.max.y *= finalScale;
|
|
5900
5914
|
glyphInfo.bounds.max.z *= finalScale;
|
|
5901
5915
|
}
|
|
@@ -5958,13 +5972,11 @@ class Text {
|
|
|
5958
5972
|
static registerPattern(language, pattern) {
|
|
5959
5973
|
Text.patternCache.set(language, pattern);
|
|
5960
5974
|
}
|
|
5961
|
-
static clearFontCache() {
|
|
5962
|
-
Text.fontCache.clear();
|
|
5963
|
-
Text.fontCacheMemoryBytes = 0;
|
|
5964
|
-
}
|
|
5965
5975
|
static setMaxFontCacheMemoryMB(limitMB) {
|
|
5966
5976
|
Text.maxFontCacheMemoryBytes =
|
|
5967
|
-
limitMB === Infinity
|
|
5977
|
+
limitMB === Infinity
|
|
5978
|
+
? Infinity
|
|
5979
|
+
: Math.max(1, Math.floor(limitMB)) * 1024 * 1024;
|
|
5968
5980
|
Text.enforceFontCacheMemoryLimit();
|
|
5969
5981
|
}
|
|
5970
5982
|
getLoadedFont() {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,45 @@ interface HyphenationTrieNode {
|
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
interface LRUCacheOptions<K, V> {
|
|
9
|
+
maxEntries?: number;
|
|
10
|
+
maxMemoryBytes?: number;
|
|
11
|
+
calculateSize?: (value: V) => number;
|
|
12
|
+
onEvict?: (key: K, value: V) => void;
|
|
13
|
+
}
|
|
14
|
+
interface CacheStats {
|
|
15
|
+
hits: number;
|
|
16
|
+
misses: number;
|
|
17
|
+
evictions: number;
|
|
18
|
+
size: number;
|
|
19
|
+
memoryUsage: number;
|
|
20
|
+
}
|
|
21
|
+
declare class LRUCache<K, V> {
|
|
22
|
+
private cache;
|
|
23
|
+
private head;
|
|
24
|
+
private tail;
|
|
25
|
+
private stats;
|
|
26
|
+
private options;
|
|
27
|
+
constructor(options?: LRUCacheOptions<K, V>);
|
|
28
|
+
get(key: K): V | undefined;
|
|
29
|
+
has(key: K): boolean;
|
|
30
|
+
set(key: K, value: V): void;
|
|
31
|
+
delete(key: K): boolean;
|
|
32
|
+
clear(): void;
|
|
33
|
+
getStats(): CacheStats & {
|
|
34
|
+
hitRate: number;
|
|
35
|
+
memoryUsageMB: number;
|
|
36
|
+
};
|
|
37
|
+
keys(): K[];
|
|
38
|
+
get size(): number;
|
|
39
|
+
private evictIfNeeded;
|
|
40
|
+
private evictTail;
|
|
41
|
+
private addToHead;
|
|
42
|
+
private removeNode;
|
|
43
|
+
private removeTail;
|
|
44
|
+
private moveToHead;
|
|
45
|
+
}
|
|
46
|
+
|
|
8
47
|
interface BoundingBox {
|
|
9
48
|
min: {
|
|
10
49
|
x: number;
|
|
@@ -221,13 +260,19 @@ interface TextGeometryInfo {
|
|
|
221
260
|
pointsRemovedByVisvalingam: number;
|
|
222
261
|
pointsRemovedByColinear: number;
|
|
223
262
|
originalPointCount: number;
|
|
224
|
-
}
|
|
263
|
+
} & Partial<CacheStats & {
|
|
264
|
+
hitRate: number;
|
|
265
|
+
memoryUsageMB: number;
|
|
266
|
+
}>;
|
|
225
267
|
query(options: TextQueryOptions): TextRange[];
|
|
226
268
|
coloredRanges?: ColoredRange[];
|
|
227
269
|
}
|
|
228
270
|
interface TextHandle extends TextGeometryInfo {
|
|
229
271
|
getLoadedFont(): LoadedFont | undefined;
|
|
230
|
-
getCacheStatistics():
|
|
272
|
+
getCacheStatistics(): (CacheStats & {
|
|
273
|
+
hitRate: number;
|
|
274
|
+
memoryUsageMB: number;
|
|
275
|
+
}) | null;
|
|
231
276
|
clearCache(): void;
|
|
232
277
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
233
278
|
update(options: Partial<TextOptions>): Promise<TextHandle>;
|
|
@@ -266,7 +311,7 @@ interface ColoredRange {
|
|
|
266
311
|
}
|
|
267
312
|
interface TextOptions {
|
|
268
313
|
text: string;
|
|
269
|
-
font
|
|
314
|
+
font: string | ArrayBuffer;
|
|
270
315
|
size?: number;
|
|
271
316
|
depth?: number;
|
|
272
317
|
lineHeight?: number;
|
|
@@ -367,7 +412,6 @@ declare class Text {
|
|
|
367
412
|
getFontMetrics(): FontMetrics;
|
|
368
413
|
static preloadPatterns(languages: string[], patternsPath?: string): Promise<void>;
|
|
369
414
|
static registerPattern(language: string, pattern: HyphenationTrieNode): void;
|
|
370
|
-
static clearFontCache(): void;
|
|
371
415
|
static setMaxFontCacheMemoryMB(limitMB: number): void;
|
|
372
416
|
getLoadedFont(): LoadedFont | undefined;
|
|
373
417
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
@@ -436,45 +480,6 @@ declare class FontMetadataExtractor {
|
|
|
436
480
|
static getFontMetrics(metrics: ExtractedMetrics): FontMetrics;
|
|
437
481
|
}
|
|
438
482
|
|
|
439
|
-
interface LRUCacheOptions<K, V> {
|
|
440
|
-
maxEntries?: number;
|
|
441
|
-
maxMemoryBytes?: number;
|
|
442
|
-
calculateSize?: (value: V) => number;
|
|
443
|
-
onEvict?: (key: K, value: V) => void;
|
|
444
|
-
}
|
|
445
|
-
interface CacheStats {
|
|
446
|
-
hits: number;
|
|
447
|
-
misses: number;
|
|
448
|
-
evictions: number;
|
|
449
|
-
size: number;
|
|
450
|
-
memoryUsage: number;
|
|
451
|
-
}
|
|
452
|
-
declare class LRUCache<K, V> {
|
|
453
|
-
private cache;
|
|
454
|
-
private head;
|
|
455
|
-
private tail;
|
|
456
|
-
private stats;
|
|
457
|
-
private options;
|
|
458
|
-
constructor(options?: LRUCacheOptions<K, V>);
|
|
459
|
-
get(key: K): V | undefined;
|
|
460
|
-
has(key: K): boolean;
|
|
461
|
-
set(key: K, value: V): void;
|
|
462
|
-
delete(key: K): boolean;
|
|
463
|
-
clear(): void;
|
|
464
|
-
getStats(): CacheStats & {
|
|
465
|
-
hitRate: number;
|
|
466
|
-
memoryUsageMB: number;
|
|
467
|
-
};
|
|
468
|
-
keys(): K[];
|
|
469
|
-
get size(): number;
|
|
470
|
-
private evictIfNeeded;
|
|
471
|
-
private evictTail;
|
|
472
|
-
private addToHead;
|
|
473
|
-
private removeNode;
|
|
474
|
-
private removeTail;
|
|
475
|
-
private moveToHead;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
483
|
declare const globalGlyphCache: LRUCache<string, GlyphData>;
|
|
479
484
|
declare function createGlyphCache(maxCacheSizeMB?: number): LRUCache<string, GlyphData>;
|
|
480
485
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -78,7 +78,9 @@ class PerformanceLogger {
|
|
|
78
78
|
// Find the metric in reverse order (most recent first)
|
|
79
79
|
for (let i = this.metrics.length - 1; i >= 0; i--) {
|
|
80
80
|
const metric = this.metrics[i];
|
|
81
|
-
if (metric.name === name &&
|
|
81
|
+
if (metric.name === name &&
|
|
82
|
+
metric.startTime === startTime &&
|
|
83
|
+
!metric.endTime) {
|
|
82
84
|
metric.endTime = endTime;
|
|
83
85
|
metric.duration = duration;
|
|
84
86
|
break;
|
|
@@ -466,7 +468,9 @@ class LineBreak {
|
|
|
466
468
|
const char = chars[i];
|
|
467
469
|
const nextChar = i < chars.length - 1 ? chars[i + 1] : null;
|
|
468
470
|
if (/\s/.test(char)) {
|
|
469
|
-
const width = widths
|
|
471
|
+
const width = widths
|
|
472
|
+
? (widths[i] ?? measureText(char))
|
|
473
|
+
: measureText(char);
|
|
470
474
|
items.push({
|
|
471
475
|
type: ItemType.GLUE,
|
|
472
476
|
width,
|
|
@@ -839,7 +843,9 @@ class LineBreak {
|
|
|
839
843
|
if (breaks.length === 0) {
|
|
840
844
|
// For first emergency attempt, use initialEmergencyStretch
|
|
841
845
|
// For subsequent iterations (short line detection), progressively increase
|
|
842
|
-
currentEmergencyStretch =
|
|
846
|
+
currentEmergencyStretch =
|
|
847
|
+
initialEmergencyStretch +
|
|
848
|
+
iteration * width * SHORT_LINE_EMERGENCY_STRETCH_INCREMENT;
|
|
843
849
|
breaks = LineBreak.findBreakpoints(currentItems, width, tolerance, looseness, true, currentEmergencyStretch, context);
|
|
844
850
|
}
|
|
845
851
|
// Last resort: allow higher badness (but not infinite)
|
|
@@ -1720,12 +1726,12 @@ class FontMetadataExtractor {
|
|
|
1720
1726
|
try {
|
|
1721
1727
|
if (gsubTableOffset) {
|
|
1722
1728
|
const gsubData = this.extractFeatureDataFromTable(view, gsubTableOffset, nameTableOffset);
|
|
1723
|
-
gsubData.features.forEach(f => features.add(f));
|
|
1729
|
+
gsubData.features.forEach((f) => features.add(f));
|
|
1724
1730
|
Object.assign(featureNames, gsubData.names);
|
|
1725
1731
|
}
|
|
1726
1732
|
if (gposTableOffset) {
|
|
1727
1733
|
const gposData = this.extractFeatureDataFromTable(view, gposTableOffset, nameTableOffset);
|
|
1728
|
-
gposData.features.forEach(f => features.add(f));
|
|
1734
|
+
gposData.features.forEach((f) => features.add(f));
|
|
1729
1735
|
Object.assign(featureNames, gposData.names);
|
|
1730
1736
|
}
|
|
1731
1737
|
}
|
|
@@ -2726,7 +2732,7 @@ class Tessellator {
|
|
|
2726
2732
|
let extrusionContours = needsExtrusionContours
|
|
2727
2733
|
? needsWindingReversal
|
|
2728
2734
|
? tessContours
|
|
2729
|
-
: originalContours ?? this.pathsToContours(paths)
|
|
2735
|
+
: (originalContours ?? this.pathsToContours(paths))
|
|
2730
2736
|
: [];
|
|
2731
2737
|
if (removeOverlaps) {
|
|
2732
2738
|
logger.log('Two-pass: boundary extraction then triangulation');
|
|
@@ -2759,7 +2765,10 @@ class Tessellator {
|
|
|
2759
2765
|
? 'libtess returned empty result from triangulation pass'
|
|
2760
2766
|
: 'libtess returned empty result from single-pass triangulation';
|
|
2761
2767
|
logger.warn(warning);
|
|
2762
|
-
return {
|
|
2768
|
+
return {
|
|
2769
|
+
triangles: { vertices: [], indices: [] },
|
|
2770
|
+
contours: extrusionContours
|
|
2771
|
+
};
|
|
2763
2772
|
}
|
|
2764
2773
|
return {
|
|
2765
2774
|
triangles: {
|
|
@@ -3416,9 +3425,7 @@ class PathOptimizer {
|
|
|
3416
3425
|
const v1LenSq = v1x * v1x + v1y * v1y;
|
|
3417
3426
|
const v2LenSq = v2x * v2x + v2y * v2y;
|
|
3418
3427
|
const minLenSq = this.config.minSegmentLength * this.config.minSegmentLength;
|
|
3419
|
-
if (angle > threshold ||
|
|
3420
|
-
v1LenSq < minLenSq ||
|
|
3421
|
-
v2LenSq < minLenSq) {
|
|
3428
|
+
if (angle > threshold || v1LenSq < minLenSq || v2LenSq < minLenSq) {
|
|
3422
3429
|
result.push(current);
|
|
3423
3430
|
}
|
|
3424
3431
|
else {
|
|
@@ -4180,7 +4187,7 @@ class GlyphGeometryBuilder {
|
|
|
4180
4187
|
const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x ?? 0, g.y ?? 0, 0));
|
|
4181
4188
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
4182
4189
|
this.clusteringCache.set(cacheKey, {
|
|
4183
|
-
glyphIds: cluster.glyphs.map(g => g.g),
|
|
4190
|
+
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
4184
4191
|
groups: boundaryGroups
|
|
4185
4192
|
});
|
|
4186
4193
|
}
|
|
@@ -4570,7 +4577,8 @@ class TextShaper {
|
|
|
4570
4577
|
if (LineBreak.isCJOpeningPunctuation(currentChar)) {
|
|
4571
4578
|
shouldApply = false;
|
|
4572
4579
|
}
|
|
4573
|
-
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4580
|
+
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4581
|
+
LineBreak.isCJPunctuation(nextChar)) {
|
|
4574
4582
|
shouldApply = false;
|
|
4575
4583
|
}
|
|
4576
4584
|
if (shouldApply) {
|
|
@@ -5374,7 +5382,7 @@ class Text {
|
|
|
5374
5382
|
// Stringify with sorted keys for cache stability
|
|
5375
5383
|
static stableStringify(obj) {
|
|
5376
5384
|
const keys = Object.keys(obj).sort();
|
|
5377
|
-
const pairs = keys.map(k => `${k}:${obj[k]}`);
|
|
5385
|
+
const pairs = keys.map((k) => `${k}:${obj[k]}`);
|
|
5378
5386
|
return pairs.join(',');
|
|
5379
5387
|
}
|
|
5380
5388
|
constructor() {
|
|
@@ -5612,7 +5620,9 @@ class Text {
|
|
|
5612
5620
|
// to selectively use glyph-level caching (separate vertices) only for clusters containing
|
|
5613
5621
|
// colored text, while non-colored clusters can still use fast cluster-level merging
|
|
5614
5622
|
let coloredTextIndices;
|
|
5615
|
-
if (options.color &&
|
|
5623
|
+
if (options.color &&
|
|
5624
|
+
typeof options.color === 'object' &&
|
|
5625
|
+
!Array.isArray(options.color)) {
|
|
5616
5626
|
if (options.color.byText || options.color.byCharRange) {
|
|
5617
5627
|
// Build the set manually since glyphs don't exist yet
|
|
5618
5628
|
coloredTextIndices = new Set();
|
|
@@ -5717,7 +5727,9 @@ class Text {
|
|
|
5717
5727
|
const depthScale = this.loadedFont.upem / size;
|
|
5718
5728
|
const rawDepthInFontUnits = depth * depthScale;
|
|
5719
5729
|
const minExtrudeDepth = this.loadedFont.upem * 0.000025;
|
|
5720
|
-
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5730
|
+
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5731
|
+
? 0
|
|
5732
|
+
: Math.max(rawDepthInFontUnits, minExtrudeDepth);
|
|
5721
5733
|
if (!this.textLayout) {
|
|
5722
5734
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
5723
5735
|
}
|
|
@@ -5889,10 +5901,12 @@ class Text {
|
|
|
5889
5901
|
planeBounds.max.z *= finalScale;
|
|
5890
5902
|
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5891
5903
|
const glyphInfo = glyphInfoArray[i];
|
|
5892
|
-
glyphInfo.bounds.min.x =
|
|
5904
|
+
glyphInfo.bounds.min.x =
|
|
5905
|
+
glyphInfo.bounds.min.x * finalScale + offsetScaled;
|
|
5893
5906
|
glyphInfo.bounds.min.y *= finalScale;
|
|
5894
5907
|
glyphInfo.bounds.min.z *= finalScale;
|
|
5895
|
-
glyphInfo.bounds.max.x =
|
|
5908
|
+
glyphInfo.bounds.max.x =
|
|
5909
|
+
glyphInfo.bounds.max.x * finalScale + offsetScaled;
|
|
5896
5910
|
glyphInfo.bounds.max.y *= finalScale;
|
|
5897
5911
|
glyphInfo.bounds.max.z *= finalScale;
|
|
5898
5912
|
}
|
|
@@ -5955,13 +5969,11 @@ class Text {
|
|
|
5955
5969
|
static registerPattern(language, pattern) {
|
|
5956
5970
|
Text.patternCache.set(language, pattern);
|
|
5957
5971
|
}
|
|
5958
|
-
static clearFontCache() {
|
|
5959
|
-
Text.fontCache.clear();
|
|
5960
|
-
Text.fontCacheMemoryBytes = 0;
|
|
5961
|
-
}
|
|
5962
5972
|
static setMaxFontCacheMemoryMB(limitMB) {
|
|
5963
5973
|
Text.maxFontCacheMemoryBytes =
|
|
5964
|
-
limitMB === Infinity
|
|
5974
|
+
limitMB === Infinity
|
|
5975
|
+
? Infinity
|
|
5976
|
+
: Math.max(1, Math.floor(limitMB)) * 1024 * 1024;
|
|
5965
5977
|
Text.enforceFontCacheMemoryLimit();
|
|
5966
5978
|
}
|
|
5967
5979
|
getLoadedFont() {
|
package/dist/index.min.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -886,10 +886,10 @@ w=e.colors,m=e.fc}const v=this.Ar.bn()
|
|
|
886
886
|
return{vertices:t,normals:e,indices:i,colors:w,glyphs:s,planeBounds:n,stats:{dc:i.length/3,yc:t.length/3,Ls:v.Ls,Ms:v.Ms,Is:v.Is,...o||{}},query(t){if(!h)throw Error("Original text not available for querying")
|
|
887
887
|
return new gt(h,s).xa(t)},fc:m,glyphAttributes:void 0}}Ce(){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
888
888
|
return B.Ce(this.le.p)}static async wc(t,e){await Promise.all(t.map(async t=>{if(!xt.Ma.has(t))try{const s=await i(t,e)
|
|
889
|
-
xt.Ma.set(t,s)}catch(e){l.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static mc(t,e){xt.Ma.set(t,e)}static vc(
|
|
889
|
+
xt.Ma.set(t,s)}catch(e){l.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static mc(t,e){xt.Ma.set(t,e)}static vc(t){xt.Fa=t===1/0?1/0:1048576*Math.max(1,Math.floor(t)),xt.Va()}getLoadedFont(){return this.le}measureTextWidth(t,e=0){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
890
890
|
return G.measureTextWidth(this.le,t,e)}getCacheStatistics(){return this.Ar?this.Ar.Er():null}clearCache(){this.Ar&&this.Ar.clearCache()}hc(t,e){const i=new Float32Array(3*t),s=new Float32Array(t),n=new Float32Array(t)
|
|
891
891
|
return e.forEach((e,r)=>{const o=(e.bounds.min.x+e.bounds.max.x)/2,h=(e.bounds.min.y+e.bounds.max.y)/2,a=(e.bounds.min.z+e.bounds.max.z)/2
|
|
892
892
|
for(let c=0;e.gr>c;c++){const l=e.nr+c
|
|
893
|
-
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.vr)}}),{
|
|
893
|
+
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.vr)}}),{gc:i,dn:s,xc:n}}ja(){this.Ar=void 0,this.tc=void 0,this.lc=void 0}destroy(){if(!this.le)return
|
|
894
894
|
const t=this.le
|
|
895
895
|
try{$.Ze(t)}catch(t){l.warn("Error destroying HarfBuzz objects:",t)}finally{this.le=void 0,this.lc=void 0,this.tc=void 0}}}exports.DEFAULT_CURVE_FIDELITY=it,exports.FontMetadataExtractor=B,exports.Text=xt,exports.createGlyphCache=r,exports.globalGlyphCache=z
|
package/dist/index.min.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -885,10 +885,10 @@ w=e.colors,v=e.fc}const m=this.Ar.bn()
|
|
|
885
885
|
return{vertices:t,normals:e,indices:i,colors:w,glyphs:s,planeBounds:n,stats:{dc:i.length/3,yc:t.length/3,Ls:m.Ls,Ms:m.Ms,Is:m.Is,...o||{}},query(t){if(!h)throw Error("Original text not available for querying")
|
|
886
886
|
return new mt(h,s).xa(t)},fc:v,glyphAttributes:void 0}}Ce(){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
887
887
|
return C.Ce(this.le.p)}static async wc(t,e){await Promise.all(t.map(async t=>{if(!gt.Ma.has(t))try{const s=await i(t,e)
|
|
888
|
-
gt.Ma.set(t,s)}catch(e){c.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static vc(t,e){gt.Ma.set(t,e)}static mc(
|
|
888
|
+
gt.Ma.set(t,s)}catch(e){c.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static vc(t,e){gt.Ma.set(t,e)}static mc(t){gt.Fa=t===1/0?1/0:1048576*Math.max(1,Math.floor(t)),gt.Va()}getLoadedFont(){return this.le}measureTextWidth(t,e=0){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
889
889
|
return I.measureTextWidth(this.le,t,e)}getCacheStatistics(){return this.Ar?this.Ar._r():null}clearCache(){this.Ar&&this.Ar.clearCache()}hc(t,e){const i=new Float32Array(3*t),s=new Float32Array(t),n=new Float32Array(t)
|
|
890
890
|
return e.forEach((e,r)=>{const o=(e.bounds.min.x+e.bounds.max.x)/2,h=(e.bounds.min.y+e.bounds.max.y)/2,a=(e.bounds.min.z+e.bounds.max.z)/2
|
|
891
891
|
for(let c=0;e.gr>c;c++){const l=e.nr+c
|
|
892
|
-
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.mr)}}),{
|
|
892
|
+
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.mr)}}),{gc:i,dn:s,xc:n}}za(){this.Ar=void 0,this.tc=void 0,this.lc=void 0}destroy(){if(!this.le)return
|
|
893
893
|
const t=this.le
|
|
894
894
|
try{$.Ze(t)}catch(t){c.warn("Error destroying HarfBuzz objects:",t)}finally{this.le=void 0,this.lc=void 0,this.tc=void 0}}}export{et as DEFAULT_CURVE_FIDELITY,C as FontMetadataExtractor,gt as Text,r as createGlyphCache,z as globalGlyphCache}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -83,7 +83,9 @@
|
|
|
83
83
|
// Find the metric in reverse order (most recent first)
|
|
84
84
|
for (let i = this.metrics.length - 1; i >= 0; i--) {
|
|
85
85
|
const metric = this.metrics[i];
|
|
86
|
-
if (metric.name === name &&
|
|
86
|
+
if (metric.name === name &&
|
|
87
|
+
metric.startTime === startTime &&
|
|
88
|
+
!metric.endTime) {
|
|
87
89
|
metric.endTime = endTime;
|
|
88
90
|
metric.duration = duration;
|
|
89
91
|
break;
|
|
@@ -471,7 +473,9 @@
|
|
|
471
473
|
const char = chars[i];
|
|
472
474
|
const nextChar = i < chars.length - 1 ? chars[i + 1] : null;
|
|
473
475
|
if (/\s/.test(char)) {
|
|
474
|
-
const width = widths
|
|
476
|
+
const width = widths
|
|
477
|
+
? (widths[i] ?? measureText(char))
|
|
478
|
+
: measureText(char);
|
|
475
479
|
items.push({
|
|
476
480
|
type: ItemType.GLUE,
|
|
477
481
|
width,
|
|
@@ -844,7 +848,9 @@
|
|
|
844
848
|
if (breaks.length === 0) {
|
|
845
849
|
// For first emergency attempt, use initialEmergencyStretch
|
|
846
850
|
// For subsequent iterations (short line detection), progressively increase
|
|
847
|
-
currentEmergencyStretch =
|
|
851
|
+
currentEmergencyStretch =
|
|
852
|
+
initialEmergencyStretch +
|
|
853
|
+
iteration * width * SHORT_LINE_EMERGENCY_STRETCH_INCREMENT;
|
|
848
854
|
breaks = LineBreak.findBreakpoints(currentItems, width, tolerance, looseness, true, currentEmergencyStretch, context);
|
|
849
855
|
}
|
|
850
856
|
// Last resort: allow higher badness (but not infinite)
|
|
@@ -1725,12 +1731,12 @@
|
|
|
1725
1731
|
try {
|
|
1726
1732
|
if (gsubTableOffset) {
|
|
1727
1733
|
const gsubData = this.extractFeatureDataFromTable(view, gsubTableOffset, nameTableOffset);
|
|
1728
|
-
gsubData.features.forEach(f => features.add(f));
|
|
1734
|
+
gsubData.features.forEach((f) => features.add(f));
|
|
1729
1735
|
Object.assign(featureNames, gsubData.names);
|
|
1730
1736
|
}
|
|
1731
1737
|
if (gposTableOffset) {
|
|
1732
1738
|
const gposData = this.extractFeatureDataFromTable(view, gposTableOffset, nameTableOffset);
|
|
1733
|
-
gposData.features.forEach(f => features.add(f));
|
|
1739
|
+
gposData.features.forEach((f) => features.add(f));
|
|
1734
1740
|
Object.assign(featureNames, gposData.names);
|
|
1735
1741
|
}
|
|
1736
1742
|
}
|
|
@@ -2733,7 +2739,7 @@
|
|
|
2733
2739
|
let extrusionContours = needsExtrusionContours
|
|
2734
2740
|
? needsWindingReversal
|
|
2735
2741
|
? tessContours
|
|
2736
|
-
: originalContours ?? this.pathsToContours(paths)
|
|
2742
|
+
: (originalContours ?? this.pathsToContours(paths))
|
|
2737
2743
|
: [];
|
|
2738
2744
|
if (removeOverlaps) {
|
|
2739
2745
|
logger.log('Two-pass: boundary extraction then triangulation');
|
|
@@ -2766,7 +2772,10 @@
|
|
|
2766
2772
|
? 'libtess returned empty result from triangulation pass'
|
|
2767
2773
|
: 'libtess returned empty result from single-pass triangulation';
|
|
2768
2774
|
logger.warn(warning);
|
|
2769
|
-
return {
|
|
2775
|
+
return {
|
|
2776
|
+
triangles: { vertices: [], indices: [] },
|
|
2777
|
+
contours: extrusionContours
|
|
2778
|
+
};
|
|
2770
2779
|
}
|
|
2771
2780
|
return {
|
|
2772
2781
|
triangles: {
|
|
@@ -3423,9 +3432,7 @@
|
|
|
3423
3432
|
const v1LenSq = v1x * v1x + v1y * v1y;
|
|
3424
3433
|
const v2LenSq = v2x * v2x + v2y * v2y;
|
|
3425
3434
|
const minLenSq = this.config.minSegmentLength * this.config.minSegmentLength;
|
|
3426
|
-
if (angle > threshold ||
|
|
3427
|
-
v1LenSq < minLenSq ||
|
|
3428
|
-
v2LenSq < minLenSq) {
|
|
3435
|
+
if (angle > threshold || v1LenSq < minLenSq || v2LenSq < minLenSq) {
|
|
3429
3436
|
result.push(current);
|
|
3430
3437
|
}
|
|
3431
3438
|
else {
|
|
@@ -4187,7 +4194,7 @@
|
|
|
4187
4194
|
const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x ?? 0, g.y ?? 0, 0));
|
|
4188
4195
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
4189
4196
|
this.clusteringCache.set(cacheKey, {
|
|
4190
|
-
glyphIds: cluster.glyphs.map(g => g.g),
|
|
4197
|
+
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
4191
4198
|
groups: boundaryGroups
|
|
4192
4199
|
});
|
|
4193
4200
|
}
|
|
@@ -4577,7 +4584,8 @@
|
|
|
4577
4584
|
if (LineBreak.isCJOpeningPunctuation(currentChar)) {
|
|
4578
4585
|
shouldApply = false;
|
|
4579
4586
|
}
|
|
4580
|
-
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4587
|
+
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4588
|
+
LineBreak.isCJPunctuation(nextChar)) {
|
|
4581
4589
|
shouldApply = false;
|
|
4582
4590
|
}
|
|
4583
4591
|
if (shouldApply) {
|
|
@@ -5381,7 +5389,7 @@
|
|
|
5381
5389
|
// Stringify with sorted keys for cache stability
|
|
5382
5390
|
static stableStringify(obj) {
|
|
5383
5391
|
const keys = Object.keys(obj).sort();
|
|
5384
|
-
const pairs = keys.map(k => `${k}:${obj[k]}`);
|
|
5392
|
+
const pairs = keys.map((k) => `${k}:${obj[k]}`);
|
|
5385
5393
|
return pairs.join(',');
|
|
5386
5394
|
}
|
|
5387
5395
|
constructor() {
|
|
@@ -5619,7 +5627,9 @@
|
|
|
5619
5627
|
// to selectively use glyph-level caching (separate vertices) only for clusters containing
|
|
5620
5628
|
// colored text, while non-colored clusters can still use fast cluster-level merging
|
|
5621
5629
|
let coloredTextIndices;
|
|
5622
|
-
if (options.color &&
|
|
5630
|
+
if (options.color &&
|
|
5631
|
+
typeof options.color === 'object' &&
|
|
5632
|
+
!Array.isArray(options.color)) {
|
|
5623
5633
|
if (options.color.byText || options.color.byCharRange) {
|
|
5624
5634
|
// Build the set manually since glyphs don't exist yet
|
|
5625
5635
|
coloredTextIndices = new Set();
|
|
@@ -5724,7 +5734,9 @@
|
|
|
5724
5734
|
const depthScale = this.loadedFont.upem / size;
|
|
5725
5735
|
const rawDepthInFontUnits = depth * depthScale;
|
|
5726
5736
|
const minExtrudeDepth = this.loadedFont.upem * 0.000025;
|
|
5727
|
-
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5737
|
+
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5738
|
+
? 0
|
|
5739
|
+
: Math.max(rawDepthInFontUnits, minExtrudeDepth);
|
|
5728
5740
|
if (!this.textLayout) {
|
|
5729
5741
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
5730
5742
|
}
|
|
@@ -5896,10 +5908,12 @@
|
|
|
5896
5908
|
planeBounds.max.z *= finalScale;
|
|
5897
5909
|
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5898
5910
|
const glyphInfo = glyphInfoArray[i];
|
|
5899
|
-
glyphInfo.bounds.min.x =
|
|
5911
|
+
glyphInfo.bounds.min.x =
|
|
5912
|
+
glyphInfo.bounds.min.x * finalScale + offsetScaled;
|
|
5900
5913
|
glyphInfo.bounds.min.y *= finalScale;
|
|
5901
5914
|
glyphInfo.bounds.min.z *= finalScale;
|
|
5902
|
-
glyphInfo.bounds.max.x =
|
|
5915
|
+
glyphInfo.bounds.max.x =
|
|
5916
|
+
glyphInfo.bounds.max.x * finalScale + offsetScaled;
|
|
5903
5917
|
glyphInfo.bounds.max.y *= finalScale;
|
|
5904
5918
|
glyphInfo.bounds.max.z *= finalScale;
|
|
5905
5919
|
}
|
|
@@ -5962,13 +5976,11 @@
|
|
|
5962
5976
|
static registerPattern(language, pattern) {
|
|
5963
5977
|
Text.patternCache.set(language, pattern);
|
|
5964
5978
|
}
|
|
5965
|
-
static clearFontCache() {
|
|
5966
|
-
Text.fontCache.clear();
|
|
5967
|
-
Text.fontCacheMemoryBytes = 0;
|
|
5968
|
-
}
|
|
5969
5979
|
static setMaxFontCacheMemoryMB(limitMB) {
|
|
5970
5980
|
Text.maxFontCacheMemoryBytes =
|
|
5971
|
-
limitMB === Infinity
|
|
5981
|
+
limitMB === Infinity
|
|
5982
|
+
? Infinity
|
|
5983
|
+
: Math.max(1, Math.floor(limitMB)) * 1024 * 1024;
|
|
5972
5984
|
Text.enforceFontCacheMemoryLimit();
|
|
5973
5985
|
}
|
|
5974
5986
|
getLoadedFont() {
|
package/dist/index.umd.min.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.19
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -887,10 +887,10 @@ w=e.colors,v=e.fc}const g=this.Ar.Sn()
|
|
|
887
887
|
return{vertices:t,normals:e,indices:i,colors:w,glyphs:s,planeBounds:n,stats:{dc:i.length/3,yc:t.length/3,Ms:g.Ms,Ls:g.Ls,Is:g.Is,...o||{}},query(t){if(!h)throw Error("Original text not available for querying")
|
|
888
888
|
return new mt(h,s).xa(t)},fc:v,glyphAttributes:void 0}}Ce(){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
889
889
|
return B.Ce(this.le.p)}static async wc(t,e){await Promise.all(t.map(async t=>{if(!xt.La.has(t))try{const i=await s(t,e)
|
|
890
|
-
xt.La.set(t,i)}catch(e){l.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static vc(t,e){xt.La.set(t,e)}static gc(
|
|
890
|
+
xt.La.set(t,i)}catch(e){l.warn(`Failed to pre-load patterns for ${t}: ${e}`)}}))}static vc(t,e){xt.La.set(t,e)}static gc(t){xt.Fa=t===1/0?1/0:1048576*Math.max(1,Math.floor(t)),xt.Va()}getLoadedFont(){return this.le}measureTextWidth(t,e=0){if(!this.le)throw Error("Font not loaded. Call loadFont() first")
|
|
891
891
|
return G.measureTextWidth(this.le,t,e)}getCacheStatistics(){return this.Ar?this.Ar.Er():null}clearCache(){this.Ar&&this.Ar.clearCache()}hc(t,e){const i=new Float32Array(3*t),s=new Float32Array(t),n=new Float32Array(t)
|
|
892
892
|
return e.forEach((e,r)=>{const o=(e.bounds.min.x+e.bounds.max.x)/2,h=(e.bounds.min.y+e.bounds.max.y)/2,a=(e.bounds.min.z+e.bounds.max.z)/2
|
|
893
893
|
for(let c=0;e.mr>c;c++){const l=e.nr+c
|
|
894
|
-
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.gr)}}),{
|
|
894
|
+
t>l&&(i[3*l]=o,i[3*l+1]=h,i[3*l+2]=a,s[l]=r,n[l]=e.gr)}}),{mc:i,dn:s,xc:n}}za(){this.Ar=void 0,this.tc=void 0,this.lc=void 0}destroy(){if(!this.le)return
|
|
895
895
|
const t=this.le
|
|
896
|
-
try{$.Ze(t)}catch(t){l.warn("Error destroying HarfBuzz objects:",t)}finally{this.le=void 0,this.lc=void 0,this.tc=void 0}}}t.DEFAULT_CURVE_FIDELITY=it,t.FontMetadataExtractor=B,t.Text=xt,t.
|
|
896
|
+
try{$.Ze(t)}catch(t){l.warn("Error destroying HarfBuzz objects:",t)}finally{this.le=void 0,this.lc=void 0,this.tc=void 0}}}t.DEFAULT_CURVE_FIDELITY=it,t.FontMetadataExtractor=B,t.Text=xt,t.bc=o,t.Sc=W})
|
package/dist/three/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ class Text {
|
|
|
47
47
|
static { this.init = Text$1.Text.init; }
|
|
48
48
|
static { this.registerPattern = Text$1.Text.registerPattern; }
|
|
49
49
|
static { this.preloadPatterns = Text$1.Text.preloadPatterns; }
|
|
50
|
+
static { this.setMaxFontCacheMemoryMB = Text$1.Text.setMaxFontCacheMemoryMB; }
|
|
50
51
|
// Main API - wraps core result in BufferGeometry
|
|
51
52
|
static async create(options) {
|
|
52
53
|
const coreResult = await Text$1.Text.create(options);
|
package/dist/three/index.d.ts
CHANGED
|
@@ -10,10 +10,21 @@ interface HyphenationTrieNode {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
interface CacheStats {
|
|
14
|
+
hits: number;
|
|
15
|
+
misses: number;
|
|
16
|
+
evictions: number;
|
|
17
|
+
size: number;
|
|
18
|
+
memoryUsage: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
interface ThreeTextGeometryInfo extends Omit<TextGeometryInfo, 'vertices' | 'normals' | 'indices' | 'colors' | 'glyphAttributes'> {
|
|
14
22
|
geometry: BufferGeometry;
|
|
15
23
|
getLoadedFont(): LoadedFont | undefined;
|
|
16
|
-
getCacheStatistics():
|
|
24
|
+
getCacheStatistics(): (CacheStats & {
|
|
25
|
+
hitRate: number;
|
|
26
|
+
memoryUsageMB: number;
|
|
27
|
+
}) | null;
|
|
17
28
|
clearCache(): void;
|
|
18
29
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
19
30
|
update(options: Partial<TextOptions>): Promise<ThreeTextGeometryInfo>;
|
|
@@ -24,6 +35,7 @@ declare class Text {
|
|
|
24
35
|
static init: typeof Text$1.init;
|
|
25
36
|
static registerPattern: typeof Text$1.registerPattern;
|
|
26
37
|
static preloadPatterns: typeof Text$1.preloadPatterns;
|
|
38
|
+
static setMaxFontCacheMemoryMB: typeof Text$1.setMaxFontCacheMemoryMB;
|
|
27
39
|
static create(options: TextOptions): Promise<ThreeTextGeometryInfo>;
|
|
28
40
|
}
|
|
29
41
|
|
package/dist/three/index.js
CHANGED
|
@@ -45,6 +45,7 @@ class Text {
|
|
|
45
45
|
static { this.init = Text$1.init; }
|
|
46
46
|
static { this.registerPattern = Text$1.registerPattern; }
|
|
47
47
|
static { this.preloadPatterns = Text$1.preloadPatterns; }
|
|
48
|
+
static { this.setMaxFontCacheMemoryMB = Text$1.setMaxFontCacheMemoryMB; }
|
|
48
49
|
// Main API - wraps core result in BufferGeometry
|
|
49
50
|
static async create(options) {
|
|
50
51
|
const coreResult = await Text$1.create(options);
|
package/dist/three/react.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var THREE = require('three');
|
|
6
|
-
var index = require('./index');
|
|
6
|
+
var index = require('./index.cjs');
|
|
7
7
|
|
|
8
8
|
function _interopNamespaceDefault(e) {
|
|
9
9
|
var n = Object.create(null);
|
|
@@ -142,6 +142,7 @@ const Text = Object.assign(Text$1, {
|
|
|
142
142
|
init: index.Text.init,
|
|
143
143
|
registerPattern: index.Text.registerPattern,
|
|
144
144
|
preloadPatterns: index.Text.preloadPatterns,
|
|
145
|
+
setMaxFontCacheMemoryMB: index.Text.setMaxFontCacheMemoryMB,
|
|
145
146
|
create: index.Text.create
|
|
146
147
|
});
|
|
147
148
|
|
package/dist/three/react.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ interface HyphenationTrieNode {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface CacheStats {
|
|
13
|
+
hits: number;
|
|
14
|
+
misses: number;
|
|
15
|
+
evictions: number;
|
|
16
|
+
size: number;
|
|
17
|
+
memoryUsage: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
interface BoundingBox {
|
|
13
21
|
min: {
|
|
14
22
|
x: number;
|
|
@@ -183,13 +191,19 @@ interface TextGeometryInfo {
|
|
|
183
191
|
pointsRemovedByVisvalingam: number;
|
|
184
192
|
pointsRemovedByColinear: number;
|
|
185
193
|
originalPointCount: number;
|
|
186
|
-
}
|
|
194
|
+
} & Partial<CacheStats & {
|
|
195
|
+
hitRate: number;
|
|
196
|
+
memoryUsageMB: number;
|
|
197
|
+
}>;
|
|
187
198
|
query(options: TextQueryOptions): TextRange[];
|
|
188
199
|
coloredRanges?: ColoredRange[];
|
|
189
200
|
}
|
|
190
201
|
interface TextHandle extends TextGeometryInfo {
|
|
191
202
|
getLoadedFont(): LoadedFont | undefined;
|
|
192
|
-
getCacheStatistics():
|
|
203
|
+
getCacheStatistics(): (CacheStats & {
|
|
204
|
+
hitRate: number;
|
|
205
|
+
memoryUsageMB: number;
|
|
206
|
+
}) | null;
|
|
193
207
|
clearCache(): void;
|
|
194
208
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
195
209
|
update(options: Partial<TextOptions>): Promise<TextHandle>;
|
|
@@ -228,7 +242,7 @@ interface ColoredRange {
|
|
|
228
242
|
}
|
|
229
243
|
interface TextOptions {
|
|
230
244
|
text: string;
|
|
231
|
-
font
|
|
245
|
+
font: string | ArrayBuffer;
|
|
232
246
|
size?: number;
|
|
233
247
|
depth?: number;
|
|
234
248
|
lineHeight?: number;
|
|
@@ -329,7 +343,6 @@ declare class Text$1 {
|
|
|
329
343
|
getFontMetrics(): FontMetrics;
|
|
330
344
|
static preloadPatterns(languages: string[], patternsPath?: string): Promise<void>;
|
|
331
345
|
static registerPattern(language: string, pattern: HyphenationTrieNode): void;
|
|
332
|
-
static clearFontCache(): void;
|
|
333
346
|
static setMaxFontCacheMemoryMB(limitMB: number): void;
|
|
334
347
|
getLoadedFont(): LoadedFont | undefined;
|
|
335
348
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
@@ -343,14 +356,6 @@ declare class Text$1 {
|
|
|
343
356
|
destroy(): void;
|
|
344
357
|
}
|
|
345
358
|
|
|
346
|
-
interface CacheStats {
|
|
347
|
-
hits: number;
|
|
348
|
-
misses: number;
|
|
349
|
-
evictions: number;
|
|
350
|
-
size: number;
|
|
351
|
-
memoryUsage: number;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
359
|
interface ThreeTextProps extends Omit<TextOptions$1, 'text'> {
|
|
355
360
|
children: string;
|
|
356
361
|
font: string | ArrayBuffer;
|
|
@@ -369,6 +374,7 @@ declare const Text: react.ForwardRefExoticComponent<ThreeTextProps & react.RefAt
|
|
|
369
374
|
init: typeof Text$1.init;
|
|
370
375
|
registerPattern: typeof Text$1.registerPattern;
|
|
371
376
|
preloadPatterns: typeof Text$1.preloadPatterns;
|
|
377
|
+
setMaxFontCacheMemoryMB: typeof Text$1.setMaxFontCacheMemoryMB;
|
|
372
378
|
create: typeof Text$2.create;
|
|
373
379
|
};
|
|
374
380
|
|
package/dist/three/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef, useState, useRef, useMemo, useEffect } from 'react';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
|
-
import { Text as Text$2 } from './index';
|
|
4
|
+
import { Text as Text$2 } from './index.js';
|
|
5
5
|
|
|
6
6
|
function deepEqual(a, b) {
|
|
7
7
|
if (a === b)
|
|
@@ -121,6 +121,7 @@ const Text = Object.assign(Text$1, {
|
|
|
121
121
|
init: Text$2.init,
|
|
122
122
|
registerPattern: Text$2.registerPattern,
|
|
123
123
|
preloadPatterns: Text$2.preloadPatterns,
|
|
124
|
+
setMaxFontCacheMemoryMB: Text$2.setMaxFontCacheMemoryMB,
|
|
124
125
|
create: Text$2.create
|
|
125
126
|
});
|
|
126
127
|
|
|
@@ -43,7 +43,6 @@ export declare class Text {
|
|
|
43
43
|
getFontMetrics(): FontMetrics;
|
|
44
44
|
static preloadPatterns(languages: string[], patternsPath?: string): Promise<void>;
|
|
45
45
|
static registerPattern(language: string, pattern: HyphenationTrieNode): void;
|
|
46
|
-
static clearFontCache(): void;
|
|
47
46
|
static setMaxFontCacheMemoryMB(limitMB: number): void;
|
|
48
47
|
getLoadedFont(): LoadedFont | undefined;
|
|
49
48
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HyphenationTrieNode } from '../hyphenation';
|
|
2
|
+
import type { CacheStats } from '../utils/LRUCache';
|
|
2
3
|
import type { Vec2, Vec3, BoundingBox } from './vectors';
|
|
3
4
|
export type { HyphenationTrieNode };
|
|
4
5
|
export interface Path {
|
|
@@ -249,13 +250,19 @@ export interface TextGeometryInfo {
|
|
|
249
250
|
pointsRemovedByVisvalingam: number;
|
|
250
251
|
pointsRemovedByColinear: number;
|
|
251
252
|
originalPointCount: number;
|
|
252
|
-
}
|
|
253
|
+
} & Partial<CacheStats & {
|
|
254
|
+
hitRate: number;
|
|
255
|
+
memoryUsageMB: number;
|
|
256
|
+
}>;
|
|
253
257
|
query(options: TextQueryOptions): TextRange[];
|
|
254
258
|
coloredRanges?: ColoredRange[];
|
|
255
259
|
}
|
|
256
260
|
export interface TextHandle extends TextGeometryInfo {
|
|
257
261
|
getLoadedFont(): LoadedFont | undefined;
|
|
258
|
-
getCacheStatistics():
|
|
262
|
+
getCacheStatistics(): (CacheStats & {
|
|
263
|
+
hitRate: number;
|
|
264
|
+
memoryUsageMB: number;
|
|
265
|
+
}) | null;
|
|
259
266
|
clearCache(): void;
|
|
260
267
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
261
268
|
update(options: Partial<TextOptions>): Promise<TextHandle>;
|
|
@@ -294,7 +301,7 @@ export interface ColoredRange {
|
|
|
294
301
|
}
|
|
295
302
|
export interface TextOptions {
|
|
296
303
|
text: string;
|
|
297
|
-
font
|
|
304
|
+
font: string | ArrayBuffer;
|
|
298
305
|
size?: number;
|
|
299
306
|
depth?: number;
|
|
300
307
|
lineHeight?: number;
|
|
@@ -2,10 +2,14 @@ import { BufferGeometry } from 'three';
|
|
|
2
2
|
import { Text as TextCore } from '../core/Text';
|
|
3
3
|
import type { TextOptions, TextGeometryInfo as CoreTextGeometryInfo, LoadedFont } from '../core/types';
|
|
4
4
|
import type { HyphenationTrieNode } from '../hyphenation';
|
|
5
|
+
import type { CacheStats } from '../utils/LRUCache';
|
|
5
6
|
export interface ThreeTextGeometryInfo extends Omit<CoreTextGeometryInfo, 'vertices' | 'normals' | 'indices' | 'colors' | 'glyphAttributes'> {
|
|
6
7
|
geometry: BufferGeometry;
|
|
7
8
|
getLoadedFont(): LoadedFont | undefined;
|
|
8
|
-
getCacheStatistics():
|
|
9
|
+
getCacheStatistics(): (CacheStats & {
|
|
10
|
+
hitRate: number;
|
|
11
|
+
memoryUsageMB: number;
|
|
12
|
+
}) | null;
|
|
9
13
|
clearCache(): void;
|
|
10
14
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
11
15
|
update(options: Partial<TextOptions>): Promise<ThreeTextGeometryInfo>;
|
|
@@ -16,6 +20,7 @@ export declare class Text {
|
|
|
16
20
|
static init: typeof TextCore.init;
|
|
17
21
|
static registerPattern: typeof TextCore.registerPattern;
|
|
18
22
|
static preloadPatterns: typeof TextCore.preloadPatterns;
|
|
23
|
+
static setMaxFontCacheMemoryMB: typeof TextCore.setMaxFontCacheMemoryMB;
|
|
19
24
|
static create(options: TextOptions): Promise<ThreeTextGeometryInfo>;
|
|
20
25
|
}
|
|
21
26
|
export type { TextOptions, ThreeTextGeometryInfo as TextGeometryInfo, LoadedFont };
|
|
@@ -6,5 +6,6 @@ export declare const Text: import("react").ForwardRefExoticComponent<import("./T
|
|
|
6
6
|
init: typeof import("..").Text.init;
|
|
7
7
|
registerPattern: typeof import("..").Text.registerPattern;
|
|
8
8
|
preloadPatterns: typeof import("..").Text.preloadPatterns;
|
|
9
|
+
setMaxFontCacheMemoryMB: typeof import("..").Text.setMaxFontCacheMemoryMB;
|
|
9
10
|
create: typeof ThreeTextCore.create;
|
|
10
11
|
};
|
package/package.json
CHANGED
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-text",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "3D font
|
|
3
|
+
"version": "0.2.19",
|
|
4
|
+
"description": "3D mesh font geometry and text layout engine for the web",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
11
12
|
"import": "./dist/index.js",
|
|
12
13
|
"require": "./dist/index.cjs"
|
|
13
14
|
},
|
|
14
15
|
"./three": {
|
|
16
|
+
"types": "./dist/three/index.d.ts",
|
|
15
17
|
"import": "./dist/three/index.js",
|
|
16
18
|
"require": "./dist/three/index.cjs"
|
|
17
19
|
},
|
|
18
20
|
"./three/react": {
|
|
21
|
+
"types": "./dist/three/react.d.ts",
|
|
19
22
|
"import": "./dist/three/react.js",
|
|
20
23
|
"require": "./dist/three/react.cjs"
|
|
21
24
|
},
|
|
22
25
|
"./webgl": {
|
|
26
|
+
"types": "./dist/webgl/index.d.ts",
|
|
23
27
|
"import": "./dist/webgl/index.js",
|
|
24
28
|
"require": "./dist/webgl/index.cjs"
|
|
25
29
|
},
|
|
26
30
|
"./webgpu": {
|
|
31
|
+
"types": "./dist/webgpu/index.d.ts",
|
|
27
32
|
"import": "./dist/webgpu/index.js",
|
|
28
33
|
"require": "./dist/webgpu/index.cjs"
|
|
29
34
|
},
|
|
30
35
|
"./p5": {
|
|
36
|
+
"types": "./dist/p5/index.d.ts",
|
|
31
37
|
"import": "./dist/p5/index.js",
|
|
32
38
|
"require": "./dist/p5/index.cjs"
|
|
33
39
|
},
|
|
34
|
-
"./patterns/*":
|
|
40
|
+
"./patterns/*": {
|
|
41
|
+
"types": "./dist/patterns/*.d.ts",
|
|
42
|
+
"import": "./dist/patterns/*.js",
|
|
43
|
+
"require": "./dist/patterns/*.cjs"
|
|
44
|
+
}
|
|
35
45
|
},
|
|
36
46
|
"files": [
|
|
37
47
|
"dist",
|
|
@@ -95,6 +105,7 @@
|
|
|
95
105
|
"libtess": "^1.2.2"
|
|
96
106
|
},
|
|
97
107
|
"devDependencies": {
|
|
108
|
+
"@webgpu/types": "^0.1.64",
|
|
98
109
|
"@react-three/fiber": ">=8.0.0",
|
|
99
110
|
"@rollup/plugin-commonjs": "^25.0.0",
|
|
100
111
|
"@rollup/plugin-node-resolve": "^15.0.0",
|