three-text 0.2.18 → 0.3.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 +14 -10
- package/dist/index.cjs +215 -350
- package/dist/index.d.ts +27 -50
- package/dist/index.js +215 -350
- package/dist/index.min.cjs +704 -723
- package/dist/index.min.js +705 -724
- package/dist/index.umd.js +215 -350
- package/dist/index.umd.min.js +710 -729
- package/dist/three/index.cjs +4 -1
- package/dist/three/index.d.ts +2 -1
- package/dist/three/index.js +4 -1
- package/dist/three/react.cjs +2 -1
- package/dist/three/react.d.ts +15 -17
- package/dist/three/react.js +2 -1
- package/dist/types/core/Text.d.ts +1 -5
- package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +4 -7
- package/dist/types/core/cache/sharedCaches.d.ts +6 -7
- package/dist/types/core/geometry/Extruder.d.ts +0 -1
- package/dist/types/core/shaping/TextShaper.d.ts +1 -4
- package/dist/types/core/types.d.ts +10 -4
- package/dist/types/index.d.ts +1 -1
- package/dist/types/three/index.d.ts +2 -1
- package/dist/types/three/react.d.ts +1 -0
- package/dist/types/utils/Cache.d.ts +14 -0
- package/dist/types/webgl/index.d.ts +12 -0
- package/dist/types/webgpu/index.d.ts +10 -0
- package/dist/webgl/index.cjs +18 -0
- package/dist/webgl/index.d.ts +12 -0
- package/dist/webgl/index.js +18 -0
- package/dist/webgpu/index.cjs +80 -1
- package/dist/webgpu/index.d.ts +10 -0
- package/dist/webgpu/index.js +80 -1
- package/package.json +22 -6
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.
|
|
2
|
+
* three-text v0.3.0
|
|
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
|
}
|
|
@@ -2380,231 +2386,50 @@
|
|
|
2380
2386
|
}
|
|
2381
2387
|
}
|
|
2382
2388
|
|
|
2383
|
-
//
|
|
2384
|
-
class
|
|
2385
|
-
constructor(
|
|
2389
|
+
// Map-based cache with no eviction policy
|
|
2390
|
+
class Cache {
|
|
2391
|
+
constructor() {
|
|
2386
2392
|
this.cache = new Map();
|
|
2387
|
-
this.head = null;
|
|
2388
|
-
this.tail = null;
|
|
2389
|
-
this.stats = {
|
|
2390
|
-
hits: 0,
|
|
2391
|
-
misses: 0,
|
|
2392
|
-
evictions: 0,
|
|
2393
|
-
size: 0,
|
|
2394
|
-
memoryUsage: 0
|
|
2395
|
-
};
|
|
2396
|
-
this.options = {
|
|
2397
|
-
maxEntries: options.maxEntries ?? Infinity,
|
|
2398
|
-
maxMemoryBytes: options.maxMemoryBytes ?? Infinity,
|
|
2399
|
-
calculateSize: options.calculateSize ?? (() => 0),
|
|
2400
|
-
onEvict: options.onEvict
|
|
2401
|
-
};
|
|
2402
2393
|
}
|
|
2403
2394
|
get(key) {
|
|
2404
|
-
|
|
2405
|
-
if (node) {
|
|
2406
|
-
this.stats.hits++;
|
|
2407
|
-
this.moveToHead(node);
|
|
2408
|
-
return node.value;
|
|
2409
|
-
}
|
|
2410
|
-
else {
|
|
2411
|
-
this.stats.misses++;
|
|
2412
|
-
return undefined;
|
|
2413
|
-
}
|
|
2395
|
+
return this.cache.get(key);
|
|
2414
2396
|
}
|
|
2415
2397
|
has(key) {
|
|
2416
2398
|
return this.cache.has(key);
|
|
2417
2399
|
}
|
|
2418
2400
|
set(key, value) {
|
|
2419
|
-
|
|
2420
|
-
const existingNode = this.cache.get(key);
|
|
2421
|
-
if (existingNode) {
|
|
2422
|
-
const oldSize = this.options.calculateSize(existingNode.value);
|
|
2423
|
-
const newSize = this.options.calculateSize(value);
|
|
2424
|
-
this.stats.memoryUsage = this.stats.memoryUsage - oldSize + newSize;
|
|
2425
|
-
existingNode.value = value;
|
|
2426
|
-
this.moveToHead(existingNode);
|
|
2427
|
-
return;
|
|
2428
|
-
}
|
|
2429
|
-
const size = this.options.calculateSize(value);
|
|
2430
|
-
// Evict entries if we exceed limits
|
|
2431
|
-
this.evictIfNeeded(size);
|
|
2432
|
-
// Create new node
|
|
2433
|
-
const node = {
|
|
2434
|
-
key,
|
|
2435
|
-
value,
|
|
2436
|
-
prev: null,
|
|
2437
|
-
next: null
|
|
2438
|
-
};
|
|
2439
|
-
this.cache.set(key, node);
|
|
2440
|
-
this.addToHead(node);
|
|
2441
|
-
this.stats.size = this.cache.size;
|
|
2442
|
-
this.stats.memoryUsage += size;
|
|
2401
|
+
this.cache.set(key, value);
|
|
2443
2402
|
}
|
|
2444
2403
|
delete(key) {
|
|
2445
|
-
|
|
2446
|
-
if (!node)
|
|
2447
|
-
return false;
|
|
2448
|
-
const size = this.options.calculateSize(node.value);
|
|
2449
|
-
this.removeNode(node);
|
|
2450
|
-
this.cache.delete(key);
|
|
2451
|
-
this.stats.size = this.cache.size;
|
|
2452
|
-
this.stats.memoryUsage -= size;
|
|
2453
|
-
if (this.options.onEvict) {
|
|
2454
|
-
this.options.onEvict(key, node.value);
|
|
2455
|
-
}
|
|
2456
|
-
return true;
|
|
2404
|
+
return this.cache.delete(key);
|
|
2457
2405
|
}
|
|
2458
2406
|
clear() {
|
|
2459
|
-
if (this.options.onEvict) {
|
|
2460
|
-
for (const [key, node] of this.cache) {
|
|
2461
|
-
this.options.onEvict(key, node.value);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
2407
|
this.cache.clear();
|
|
2465
|
-
this.head = null;
|
|
2466
|
-
this.tail = null;
|
|
2467
|
-
this.stats = {
|
|
2468
|
-
hits: 0,
|
|
2469
|
-
misses: 0,
|
|
2470
|
-
evictions: 0,
|
|
2471
|
-
size: 0,
|
|
2472
|
-
memoryUsage: 0
|
|
2473
|
-
};
|
|
2474
|
-
}
|
|
2475
|
-
getStats() {
|
|
2476
|
-
const total = this.stats.hits + this.stats.misses;
|
|
2477
|
-
const hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0;
|
|
2478
|
-
const memoryUsageMB = this.stats.memoryUsage / (1024 * 1024);
|
|
2479
|
-
return {
|
|
2480
|
-
...this.stats,
|
|
2481
|
-
hitRate,
|
|
2482
|
-
memoryUsageMB
|
|
2483
|
-
};
|
|
2484
|
-
}
|
|
2485
|
-
keys() {
|
|
2486
|
-
const keys = [];
|
|
2487
|
-
let current = this.head;
|
|
2488
|
-
while (current) {
|
|
2489
|
-
keys.push(current.key);
|
|
2490
|
-
current = current.next;
|
|
2491
|
-
}
|
|
2492
|
-
return keys;
|
|
2493
2408
|
}
|
|
2494
2409
|
get size() {
|
|
2495
2410
|
return this.cache.size;
|
|
2496
2411
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
while (this.cache.size >= this.options.maxEntries && this.tail) {
|
|
2500
|
-
this.evictTail();
|
|
2501
|
-
}
|
|
2502
|
-
// Evict by memory usage
|
|
2503
|
-
if (this.options.maxMemoryBytes < Infinity) {
|
|
2504
|
-
while (this.tail &&
|
|
2505
|
-
this.stats.memoryUsage + requiredSize > this.options.maxMemoryBytes) {
|
|
2506
|
-
this.evictTail();
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
evictTail() {
|
|
2511
|
-
if (!this.tail)
|
|
2512
|
-
return;
|
|
2513
|
-
const nodeToRemove = this.tail;
|
|
2514
|
-
const size = this.options.calculateSize(nodeToRemove.value);
|
|
2515
|
-
this.removeTail();
|
|
2516
|
-
this.cache.delete(nodeToRemove.key);
|
|
2517
|
-
this.stats.size = this.cache.size;
|
|
2518
|
-
this.stats.memoryUsage -= size;
|
|
2519
|
-
this.stats.evictions++;
|
|
2520
|
-
if (this.options.onEvict) {
|
|
2521
|
-
this.options.onEvict(nodeToRemove.key, nodeToRemove.value);
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
addToHead(node) {
|
|
2525
|
-
node.prev = null;
|
|
2526
|
-
node.next = null;
|
|
2527
|
-
if (!this.head) {
|
|
2528
|
-
this.head = this.tail = node;
|
|
2529
|
-
}
|
|
2530
|
-
else {
|
|
2531
|
-
node.next = this.head;
|
|
2532
|
-
this.head.prev = node;
|
|
2533
|
-
this.head = node;
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
removeNode(node) {
|
|
2537
|
-
if (node.prev) {
|
|
2538
|
-
node.prev.next = node.next;
|
|
2539
|
-
}
|
|
2540
|
-
else {
|
|
2541
|
-
this.head = node.next;
|
|
2542
|
-
}
|
|
2543
|
-
if (node.next) {
|
|
2544
|
-
node.next.prev = node.prev;
|
|
2545
|
-
}
|
|
2546
|
-
else {
|
|
2547
|
-
this.tail = node.prev;
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
removeTail() {
|
|
2551
|
-
if (this.tail) {
|
|
2552
|
-
this.removeNode(this.tail);
|
|
2553
|
-
}
|
|
2412
|
+
keys() {
|
|
2413
|
+
return Array.from(this.cache.keys());
|
|
2554
2414
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
this.addToHead(node);
|
|
2415
|
+
getStats() {
|
|
2416
|
+
return {
|
|
2417
|
+
size: this.cache.size
|
|
2418
|
+
};
|
|
2560
2419
|
}
|
|
2561
2420
|
}
|
|
2562
2421
|
|
|
2563
|
-
const DEFAULT_CACHE_SIZE_MB = 250;
|
|
2564
2422
|
function getGlyphCacheKey(fontId, glyphId, depth, removeOverlaps) {
|
|
2565
2423
|
const roundedDepth = Math.round(depth * 1000) / 1000;
|
|
2566
2424
|
return `${fontId}_${glyphId}_${roundedDepth}_${removeOverlaps}`;
|
|
2567
2425
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
size += glyph.normals.length * 4;
|
|
2572
|
-
size += glyph.indices.length * glyph.indices.BYTES_PER_ELEMENT;
|
|
2573
|
-
size += 24; // 2 Vec3s
|
|
2574
|
-
size += 256; // Object overhead
|
|
2575
|
-
return size;
|
|
2576
|
-
}
|
|
2577
|
-
const globalGlyphCache = new LRUCache({
|
|
2578
|
-
maxEntries: Infinity,
|
|
2579
|
-
maxMemoryBytes: DEFAULT_CACHE_SIZE_MB * 1024 * 1024,
|
|
2580
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2581
|
-
});
|
|
2582
|
-
function createGlyphCache(maxCacheSizeMB = DEFAULT_CACHE_SIZE_MB) {
|
|
2583
|
-
return new LRUCache({
|
|
2584
|
-
maxEntries: Infinity,
|
|
2585
|
-
maxMemoryBytes: maxCacheSizeMB * 1024 * 1024,
|
|
2586
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2587
|
-
});
|
|
2426
|
+
const globalGlyphCache = new Cache();
|
|
2427
|
+
function createGlyphCache() {
|
|
2428
|
+
return new Cache();
|
|
2588
2429
|
}
|
|
2589
|
-
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2592
|
-
calculateSize: (contours) => {
|
|
2593
|
-
let size = 0;
|
|
2594
|
-
for (const path of contours.paths) {
|
|
2595
|
-
size += path.points.length * 16; // Vec2 = 2 floats * 8 bytes
|
|
2596
|
-
}
|
|
2597
|
-
return size + 64; // bounds overhead
|
|
2598
|
-
}
|
|
2599
|
-
});
|
|
2600
|
-
const globalWordCache = new LRUCache({
|
|
2601
|
-
maxEntries: 1000,
|
|
2602
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2603
|
-
});
|
|
2604
|
-
const globalClusteringCache = new LRUCache({
|
|
2605
|
-
maxEntries: 2000,
|
|
2606
|
-
calculateSize: () => 1
|
|
2607
|
-
});
|
|
2430
|
+
const globalContourCache = new Cache();
|
|
2431
|
+
const globalWordCache = new Cache();
|
|
2432
|
+
const globalClusteringCache = new Cache();
|
|
2608
2433
|
|
|
2609
2434
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
2610
2435
|
|
|
@@ -2733,7 +2558,7 @@
|
|
|
2733
2558
|
let extrusionContours = needsExtrusionContours
|
|
2734
2559
|
? needsWindingReversal
|
|
2735
2560
|
? tessContours
|
|
2736
|
-
: originalContours ?? this.pathsToContours(paths)
|
|
2561
|
+
: (originalContours ?? this.pathsToContours(paths))
|
|
2737
2562
|
: [];
|
|
2738
2563
|
if (removeOverlaps) {
|
|
2739
2564
|
logger.log('Two-pass: boundary extraction then triangulation');
|
|
@@ -2766,7 +2591,10 @@
|
|
|
2766
2591
|
? 'libtess returned empty result from triangulation pass'
|
|
2767
2592
|
: 'libtess returned empty result from single-pass triangulation';
|
|
2768
2593
|
logger.warn(warning);
|
|
2769
|
-
return {
|
|
2594
|
+
return {
|
|
2595
|
+
triangles: { vertices: [], indices: [] },
|
|
2596
|
+
contours: extrusionContours
|
|
2597
|
+
};
|
|
2770
2598
|
}
|
|
2771
2599
|
return {
|
|
2772
2600
|
triangles: {
|
|
@@ -2936,47 +2764,79 @@
|
|
|
2936
2764
|
|
|
2937
2765
|
class Extruder {
|
|
2938
2766
|
constructor() { }
|
|
2939
|
-
packEdge(a, b) {
|
|
2940
|
-
const lo = a < b ? a : b;
|
|
2941
|
-
const hi = a < b ? b : a;
|
|
2942
|
-
return lo * 0x100000000 + hi;
|
|
2943
|
-
}
|
|
2944
2767
|
extrude(geometry, depth = 0, unitsPerEm) {
|
|
2945
2768
|
const points = geometry.triangles.vertices;
|
|
2946
2769
|
const triangleIndices = geometry.triangles.indices;
|
|
2947
2770
|
const numPoints = points.length / 2;
|
|
2948
|
-
//
|
|
2771
|
+
// Boundary edges are those that appear in exactly one triangle
|
|
2949
2772
|
let boundaryEdges = [];
|
|
2950
2773
|
if (depth !== 0) {
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2774
|
+
// Pack edge pair into integer key: (min << 16) | max
|
|
2775
|
+
// Fits glyph vertex indices comfortably, good hash distribution
|
|
2776
|
+
const edgeMap = new Map();
|
|
2777
|
+
const triLen = triangleIndices.length;
|
|
2778
|
+
for (let i = 0; i < triLen; i += 3) {
|
|
2954
2779
|
const a = triangleIndices[i];
|
|
2955
2780
|
const b = triangleIndices[i + 1];
|
|
2956
2781
|
const c = triangleIndices[i + 2];
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2782
|
+
let key, v0, v1;
|
|
2783
|
+
if (a < b) {
|
|
2784
|
+
key = (a << 16) | b;
|
|
2785
|
+
v0 = a;
|
|
2786
|
+
v1 = b;
|
|
2787
|
+
}
|
|
2788
|
+
else {
|
|
2789
|
+
key = (b << 16) | a;
|
|
2790
|
+
v0 = a;
|
|
2791
|
+
v1 = b;
|
|
2792
|
+
}
|
|
2793
|
+
let data = edgeMap.get(key);
|
|
2794
|
+
if (data) {
|
|
2795
|
+
data[2]++;
|
|
2796
|
+
}
|
|
2797
|
+
else {
|
|
2798
|
+
edgeMap.set(key, [v0, v1, 1]);
|
|
2799
|
+
}
|
|
2800
|
+
if (b < c) {
|
|
2801
|
+
key = (b << 16) | c;
|
|
2802
|
+
v0 = b;
|
|
2803
|
+
v1 = c;
|
|
2804
|
+
}
|
|
2805
|
+
else {
|
|
2806
|
+
key = (c << 16) | b;
|
|
2807
|
+
v0 = b;
|
|
2808
|
+
v1 = c;
|
|
2809
|
+
}
|
|
2810
|
+
data = edgeMap.get(key);
|
|
2811
|
+
if (data) {
|
|
2812
|
+
data[2]++;
|
|
2813
|
+
}
|
|
2814
|
+
else {
|
|
2815
|
+
edgeMap.set(key, [v0, v1, 1]);
|
|
2816
|
+
}
|
|
2817
|
+
if (c < a) {
|
|
2818
|
+
key = (c << 16) | a;
|
|
2819
|
+
v0 = c;
|
|
2820
|
+
v1 = a;
|
|
2821
|
+
}
|
|
2822
|
+
else {
|
|
2823
|
+
key = (a << 16) | c;
|
|
2824
|
+
v0 = c;
|
|
2825
|
+
v1 = a;
|
|
2826
|
+
}
|
|
2827
|
+
data = edgeMap.get(key);
|
|
2828
|
+
if (data) {
|
|
2829
|
+
data[2]++;
|
|
2830
|
+
}
|
|
2831
|
+
else {
|
|
2832
|
+
edgeMap.set(key, [v0, v1, 1]);
|
|
2833
|
+
}
|
|
2972
2834
|
}
|
|
2973
2835
|
boundaryEdges = [];
|
|
2974
|
-
for (const [
|
|
2975
|
-
if (count
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
if (edge)
|
|
2979
|
-
boundaryEdges.push(edge);
|
|
2836
|
+
for (const [v0, v1, count] of edgeMap.values()) {
|
|
2837
|
+
if (count === 1) {
|
|
2838
|
+
boundaryEdges.push([v0, v1]);
|
|
2839
|
+
}
|
|
2980
2840
|
}
|
|
2981
2841
|
}
|
|
2982
2842
|
const sideEdgeCount = depth === 0 ? 0 : boundaryEdges.length;
|
|
@@ -2990,7 +2850,6 @@
|
|
|
2990
2850
|
: triangleIndices.length * 2 + sideEdgeCount * 6;
|
|
2991
2851
|
const indices = new Uint32Array(indexCount);
|
|
2992
2852
|
if (depth === 0) {
|
|
2993
|
-
// Single-sided flat geometry at z=0
|
|
2994
2853
|
let vPos = 0;
|
|
2995
2854
|
for (let i = 0; i < points.length; i += 2) {
|
|
2996
2855
|
vertices[vPos] = points[i];
|
|
@@ -3001,16 +2860,11 @@
|
|
|
3001
2860
|
normals[vPos + 2] = 1;
|
|
3002
2861
|
vPos += 3;
|
|
3003
2862
|
}
|
|
3004
|
-
|
|
3005
|
-
for (let i = 0; i < triangleIndices.length; i++) {
|
|
3006
|
-
indices[i] = triangleIndices[i];
|
|
3007
|
-
}
|
|
2863
|
+
indices.set(triangleIndices);
|
|
3008
2864
|
return { vertices, normals, indices };
|
|
3009
2865
|
}
|
|
3010
|
-
// Extruded geometry: front at z=0, back at z=depth
|
|
3011
2866
|
const minBackOffset = unitsPerEm * 0.000025;
|
|
3012
2867
|
const backZ = depth <= minBackOffset ? minBackOffset : depth;
|
|
3013
|
-
// Generate both caps in one pass
|
|
3014
2868
|
for (let p = 0, vi = 0; p < points.length; p += 2, vi++) {
|
|
3015
2869
|
const x = points[p];
|
|
3016
2870
|
const y = points[p + 1];
|
|
@@ -3031,41 +2885,39 @@
|
|
|
3031
2885
|
normals[baseD + 1] = 0;
|
|
3032
2886
|
normals[baseD + 2] = 1;
|
|
3033
2887
|
}
|
|
3034
|
-
//
|
|
3035
|
-
|
|
3036
|
-
for (let i = 0; i <
|
|
3037
|
-
indices[i] = triangleIndices[
|
|
2888
|
+
// Front cap faces -Z, reverse winding from libtess CCW output
|
|
2889
|
+
const triLen = triangleIndices.length;
|
|
2890
|
+
for (let i = 0; i < triLen; i++) {
|
|
2891
|
+
indices[i] = triangleIndices[triLen - 1 - i];
|
|
3038
2892
|
}
|
|
3039
|
-
//
|
|
3040
|
-
for (let i = 0; i <
|
|
3041
|
-
indices[
|
|
2893
|
+
// Back cap faces +Z, use original winding
|
|
2894
|
+
for (let i = 0; i < triLen; i++) {
|
|
2895
|
+
indices[triLen + i] = triangleIndices[i] + numPoints;
|
|
3042
2896
|
}
|
|
3043
|
-
// Side walls
|
|
3044
2897
|
let nextVertex = numPoints * 2;
|
|
3045
|
-
let idxPos =
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
const
|
|
3049
|
-
const
|
|
2898
|
+
let idxPos = triLen * 2;
|
|
2899
|
+
const numEdges = boundaryEdges.length;
|
|
2900
|
+
for (let e = 0; e < numEdges; e++) {
|
|
2901
|
+
const edge = boundaryEdges[e];
|
|
2902
|
+
const u = edge[0];
|
|
2903
|
+
const v = edge[1];
|
|
2904
|
+
const u2 = u << 1;
|
|
2905
|
+
const v2 = v << 1;
|
|
3050
2906
|
const p0x = points[u2];
|
|
3051
2907
|
const p0y = points[u2 + 1];
|
|
3052
2908
|
const p1x = points[v2];
|
|
3053
2909
|
const p1y = points[v2 + 1];
|
|
3054
|
-
// Perpendicular normal for this wall segment
|
|
3055
|
-
// Uses the edge direction from the cap triangulation so winding does not depend on contour direction
|
|
3056
2910
|
const ex = p1x - p0x;
|
|
3057
2911
|
const ey = p1y - p0y;
|
|
3058
2912
|
const lenSq = ex * ex + ey * ey;
|
|
3059
2913
|
let nx = 0;
|
|
3060
2914
|
let ny = 0;
|
|
3061
|
-
if (lenSq >
|
|
2915
|
+
if (lenSq > 1e-10) {
|
|
3062
2916
|
const invLen = 1 / Math.sqrt(lenSq);
|
|
3063
2917
|
nx = ey * invLen;
|
|
3064
2918
|
ny = -ex * invLen;
|
|
3065
2919
|
}
|
|
3066
|
-
const
|
|
3067
|
-
const base = baseVertex * 3;
|
|
3068
|
-
// Wall quad: front edge at z=0, back edge at z=depth
|
|
2920
|
+
const base = nextVertex * 3;
|
|
3069
2921
|
vertices[base] = p0x;
|
|
3070
2922
|
vertices[base + 1] = p0y;
|
|
3071
2923
|
vertices[base + 2] = 0;
|
|
@@ -3078,7 +2930,6 @@
|
|
|
3078
2930
|
vertices[base + 9] = p1x;
|
|
3079
2931
|
vertices[base + 10] = p1y;
|
|
3080
2932
|
vertices[base + 11] = backZ;
|
|
3081
|
-
// Wall normals point perpendicular to edge
|
|
3082
2933
|
normals[base] = nx;
|
|
3083
2934
|
normals[base + 1] = ny;
|
|
3084
2935
|
normals[base + 2] = 0;
|
|
@@ -3091,13 +2942,14 @@
|
|
|
3091
2942
|
normals[base + 9] = nx;
|
|
3092
2943
|
normals[base + 10] = ny;
|
|
3093
2944
|
normals[base + 11] = 0;
|
|
3094
|
-
|
|
3095
|
-
indices[idxPos
|
|
3096
|
-
indices[idxPos
|
|
3097
|
-
indices[idxPos
|
|
3098
|
-
indices[idxPos
|
|
3099
|
-
indices[idxPos
|
|
3100
|
-
indices[idxPos
|
|
2945
|
+
const baseVertex = nextVertex;
|
|
2946
|
+
indices[idxPos] = baseVertex;
|
|
2947
|
+
indices[idxPos + 1] = baseVertex + 1;
|
|
2948
|
+
indices[idxPos + 2] = baseVertex + 2;
|
|
2949
|
+
indices[idxPos + 3] = baseVertex + 1;
|
|
2950
|
+
indices[idxPos + 4] = baseVertex + 3;
|
|
2951
|
+
indices[idxPos + 5] = baseVertex + 2;
|
|
2952
|
+
idxPos += 6;
|
|
3101
2953
|
nextVertex += 4;
|
|
3102
2954
|
}
|
|
3103
2955
|
return { vertices, normals, indices };
|
|
@@ -3423,9 +3275,7 @@
|
|
|
3423
3275
|
const v1LenSq = v1x * v1x + v1y * v1y;
|
|
3424
3276
|
const v2LenSq = v2x * v2x + v2y * v2y;
|
|
3425
3277
|
const minLenSq = this.config.minSegmentLength * this.config.minSegmentLength;
|
|
3426
|
-
if (angle > threshold ||
|
|
3427
|
-
v1LenSq < minLenSq ||
|
|
3428
|
-
v2LenSq < minLenSq) {
|
|
3278
|
+
if (angle > threshold || v1LenSq < minLenSq || v2LenSq < minLenSq) {
|
|
3429
3279
|
result.push(current);
|
|
3430
3280
|
}
|
|
3431
3281
|
else {
|
|
@@ -4116,7 +3966,7 @@
|
|
|
4116
3966
|
].join('|');
|
|
4117
3967
|
}
|
|
4118
3968
|
// Build instanced geometry from glyph contours
|
|
4119
|
-
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, separateGlyphs = false, coloredTextIndices) {
|
|
3969
|
+
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, scale, separateGlyphs = false, coloredTextIndices) {
|
|
4120
3970
|
if (isLogEnabled) {
|
|
4121
3971
|
let wordCount = 0;
|
|
4122
3972
|
for (let i = 0; i < clustersByLine.length; i++) {
|
|
@@ -4187,7 +4037,7 @@
|
|
|
4187
4037
|
const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x ?? 0, g.y ?? 0, 0));
|
|
4188
4038
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
4189
4039
|
this.clusteringCache.set(cacheKey, {
|
|
4190
|
-
glyphIds: cluster.glyphs.map(g => g.g),
|
|
4040
|
+
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
4191
4041
|
groups: boundaryGroups
|
|
4192
4042
|
});
|
|
4193
4043
|
}
|
|
@@ -4294,9 +4144,9 @@
|
|
|
4294
4144
|
const py = task.py;
|
|
4295
4145
|
const pz = task.pz;
|
|
4296
4146
|
for (let j = 0; j < v.length; j += 3) {
|
|
4297
|
-
vertexArray[vertexPos++] = v[j] + px;
|
|
4298
|
-
vertexArray[vertexPos++] = v[j + 1] + py;
|
|
4299
|
-
vertexArray[vertexPos++] = v[j + 2] + pz;
|
|
4147
|
+
vertexArray[vertexPos++] = (v[j] + px) * scale;
|
|
4148
|
+
vertexArray[vertexPos++] = (v[j + 1] + py) * scale;
|
|
4149
|
+
vertexArray[vertexPos++] = (v[j + 2] + pz) * scale;
|
|
4300
4150
|
}
|
|
4301
4151
|
normalArray.set(n, normalPos);
|
|
4302
4152
|
normalPos += n.length;
|
|
@@ -4306,6 +4156,20 @@
|
|
|
4306
4156
|
}
|
|
4307
4157
|
}
|
|
4308
4158
|
perfLogger.end('GlyphGeometryBuilder.buildInstancedGeometry');
|
|
4159
|
+
planeBounds.min.x *= scale;
|
|
4160
|
+
planeBounds.min.y *= scale;
|
|
4161
|
+
planeBounds.min.z *= scale;
|
|
4162
|
+
planeBounds.max.x *= scale;
|
|
4163
|
+
planeBounds.max.y *= scale;
|
|
4164
|
+
planeBounds.max.z *= scale;
|
|
4165
|
+
for (let i = 0; i < glyphInfos.length; i++) {
|
|
4166
|
+
glyphInfos[i].bounds.min.x *= scale;
|
|
4167
|
+
glyphInfos[i].bounds.min.y *= scale;
|
|
4168
|
+
glyphInfos[i].bounds.min.z *= scale;
|
|
4169
|
+
glyphInfos[i].bounds.max.x *= scale;
|
|
4170
|
+
glyphInfos[i].bounds.max.y *= scale;
|
|
4171
|
+
glyphInfos[i].bounds.max.z *= scale;
|
|
4172
|
+
}
|
|
4309
4173
|
return {
|
|
4310
4174
|
vertices: vertexArray,
|
|
4311
4175
|
normals: normalArray,
|
|
@@ -4317,7 +4181,6 @@
|
|
|
4317
4181
|
getClusterKey(glyphs, depth, removeOverlaps) {
|
|
4318
4182
|
if (glyphs.length === 0)
|
|
4319
4183
|
return '';
|
|
4320
|
-
// Normalize positions relative to the first glyph in the cluster
|
|
4321
4184
|
const refX = glyphs[0].x ?? 0;
|
|
4322
4185
|
const refY = glyphs[0].y ?? 0;
|
|
4323
4186
|
const parts = glyphs.map((g) => {
|
|
@@ -4577,7 +4440,8 @@
|
|
|
4577
4440
|
if (LineBreak.isCJOpeningPunctuation(currentChar)) {
|
|
4578
4441
|
shouldApply = false;
|
|
4579
4442
|
}
|
|
4580
|
-
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4443
|
+
if (LineBreak.isCJPunctuation(currentChar) &&
|
|
4444
|
+
LineBreak.isCJPunctuation(nextChar)) {
|
|
4581
4445
|
shouldApply = false;
|
|
4582
4446
|
}
|
|
4583
4447
|
if (shouldApply) {
|
|
@@ -5371,6 +5235,7 @@
|
|
|
5371
5235
|
}
|
|
5372
5236
|
|
|
5373
5237
|
const DEFAULT_MAX_TEXT_LENGTH = 100000;
|
|
5238
|
+
const DEFAULT_FONT_SIZE = 72;
|
|
5374
5239
|
class Text {
|
|
5375
5240
|
static { this.patternCache = new Map(); }
|
|
5376
5241
|
static { this.hbInitPromise = null; }
|
|
@@ -5381,7 +5246,7 @@
|
|
|
5381
5246
|
// Stringify with sorted keys for cache stability
|
|
5382
5247
|
static stableStringify(obj) {
|
|
5383
5248
|
const keys = Object.keys(obj).sort();
|
|
5384
|
-
const pairs = keys.map(k => `${k}:${obj[k]}`);
|
|
5249
|
+
const pairs = keys.map((k) => `${k}:${obj[k]}`);
|
|
5385
5250
|
return pairs.join(',');
|
|
5386
5251
|
}
|
|
5387
5252
|
constructor() {
|
|
@@ -5444,7 +5309,7 @@
|
|
|
5444
5309
|
return {
|
|
5445
5310
|
...newResult,
|
|
5446
5311
|
getLoadedFont: () => text.getLoadedFont(),
|
|
5447
|
-
|
|
5312
|
+
getCacheSize: () => text.getCacheSize(),
|
|
5448
5313
|
clearCache: () => text.clearCache(),
|
|
5449
5314
|
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5450
5315
|
update
|
|
@@ -5453,7 +5318,7 @@
|
|
|
5453
5318
|
return {
|
|
5454
5319
|
...result,
|
|
5455
5320
|
getLoadedFont: () => text.getLoadedFont(),
|
|
5456
|
-
|
|
5321
|
+
getCacheSize: () => text.getCacheSize(),
|
|
5457
5322
|
clearCache: () => text.clearCache(),
|
|
5458
5323
|
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5459
5324
|
update
|
|
@@ -5585,7 +5450,7 @@
|
|
|
5585
5450
|
async createGeometry(options) {
|
|
5586
5451
|
perfLogger.start('Text.createGeometry', {
|
|
5587
5452
|
textLength: options.text.length,
|
|
5588
|
-
size: options.size ||
|
|
5453
|
+
size: options.size || DEFAULT_FONT_SIZE,
|
|
5589
5454
|
hasLayout: !!options.layout,
|
|
5590
5455
|
mode: 'cached'
|
|
5591
5456
|
});
|
|
@@ -5599,7 +5464,7 @@
|
|
|
5599
5464
|
this.updateFontVariations(options);
|
|
5600
5465
|
if (!this.geometryBuilder) {
|
|
5601
5466
|
const cache = options.maxCacheSizeMB
|
|
5602
|
-
? createGlyphCache(
|
|
5467
|
+
? createGlyphCache()
|
|
5603
5468
|
: globalGlyphCache;
|
|
5604
5469
|
this.geometryBuilder = new GlyphGeometryBuilder(cache, this.loadedFont);
|
|
5605
5470
|
this.geometryBuilder.setFontId(this.currentFontId);
|
|
@@ -5619,7 +5484,9 @@
|
|
|
5619
5484
|
// to selectively use glyph-level caching (separate vertices) only for clusters containing
|
|
5620
5485
|
// colored text, while non-colored clusters can still use fast cluster-level merging
|
|
5621
5486
|
let coloredTextIndices;
|
|
5622
|
-
if (options.color &&
|
|
5487
|
+
if (options.color &&
|
|
5488
|
+
typeof options.color === 'object' &&
|
|
5489
|
+
!Array.isArray(options.color)) {
|
|
5623
5490
|
if (options.color.byText || options.color.byCharRange) {
|
|
5624
5491
|
// Build the set manually since glyphs don't exist yet
|
|
5625
5492
|
coloredTextIndices = new Set();
|
|
@@ -5643,10 +5510,9 @@
|
|
|
5643
5510
|
}
|
|
5644
5511
|
}
|
|
5645
5512
|
}
|
|
5646
|
-
const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.
|
|
5647
|
-
const
|
|
5648
|
-
|
|
5649
|
-
if (options.separateGlyphsWithAttributes) {
|
|
5513
|
+
const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, layoutData.pixelsPerFontUnit, options.perGlyphAttributes ?? false, coloredTextIndices);
|
|
5514
|
+
const result = this.finalizeGeometry(shapedResult.vertices, shapedResult.normals, shapedResult.indices, shapedResult.glyphInfos, shapedResult.planeBounds, options, options.text);
|
|
5515
|
+
if (options.perGlyphAttributes) {
|
|
5650
5516
|
const glyphAttrs = this.createGlyphAttributes(result.vertices.length / 3, result.glyphs);
|
|
5651
5517
|
result.glyphAttributes = glyphAttrs;
|
|
5652
5518
|
}
|
|
@@ -5713,18 +5579,20 @@
|
|
|
5713
5579
|
if (!this.loadedFont) {
|
|
5714
5580
|
throw new Error('Font not loaded. Use Text.create() with a font option');
|
|
5715
5581
|
}
|
|
5716
|
-
const { text, size =
|
|
5582
|
+
const { text, size = DEFAULT_FONT_SIZE, depth = 0, lineHeight = 1.0, letterSpacing = 0, layout = {} } = options;
|
|
5717
5583
|
const { width, direction = 'ltr', align = direction === 'rtl' ? 'right' : 'left', respectExistingBreaks = true, hyphenate = true, language = 'en-us', tolerance = DEFAULT_TOLERANCE, pretolerance = DEFAULT_PRETOLERANCE, emergencyStretch = DEFAULT_EMERGENCY_STRETCH, autoEmergencyStretch, hyphenationPatterns, lefthyphenmin, righthyphenmin, linepenalty, adjdemerits, hyphenpenalty, exhyphenpenalty, doublehyphendemerits, looseness, disableShortLineDetection, shortLineThreshold } = layout;
|
|
5584
|
+
const fontUnitsPerPixel = this.loadedFont.upem / size;
|
|
5718
5585
|
let widthInFontUnits;
|
|
5719
5586
|
if (width !== undefined) {
|
|
5720
|
-
widthInFontUnits = width *
|
|
5587
|
+
widthInFontUnits = width * fontUnitsPerPixel;
|
|
5721
5588
|
}
|
|
5722
5589
|
// Keep depth behavior consistent with Extruder: extremely small non-zero depths
|
|
5723
|
-
// are clamped to a minimum back offset
|
|
5724
|
-
const
|
|
5725
|
-
const rawDepthInFontUnits = depth * depthScale;
|
|
5590
|
+
// are clamped to a minimum back offset to prevent Z fighting
|
|
5591
|
+
const rawDepthInFontUnits = depth * fontUnitsPerPixel;
|
|
5726
5592
|
const minExtrudeDepth = this.loadedFont.upem * 0.000025;
|
|
5727
|
-
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5593
|
+
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5594
|
+
? 0
|
|
5595
|
+
: Math.max(rawDepthInFontUnits, minExtrudeDepth);
|
|
5728
5596
|
if (!this.textLayout) {
|
|
5729
5597
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
5730
5598
|
}
|
|
@@ -5763,7 +5631,8 @@
|
|
|
5763
5631
|
align,
|
|
5764
5632
|
direction,
|
|
5765
5633
|
depth: depthInFontUnits,
|
|
5766
|
-
size
|
|
5634
|
+
size,
|
|
5635
|
+
pixelsPerFontUnit: 1 / fontUnitsPerPixel
|
|
5767
5636
|
};
|
|
5768
5637
|
}
|
|
5769
5638
|
applyColorSystem(vertices, glyphInfoArray, color, originalText) {
|
|
@@ -5859,8 +5728,8 @@
|
|
|
5859
5728
|
}
|
|
5860
5729
|
return { colors, coloredRanges };
|
|
5861
5730
|
}
|
|
5862
|
-
finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options,
|
|
5863
|
-
const { layout = {}
|
|
5731
|
+
finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, originalText) {
|
|
5732
|
+
const { layout = {} } = options;
|
|
5864
5733
|
const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
|
|
5865
5734
|
if (!this.textLayout) {
|
|
5866
5735
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
@@ -5873,35 +5742,14 @@
|
|
|
5873
5742
|
const offset = alignmentResult.offset;
|
|
5874
5743
|
planeBounds.min.x = alignmentResult.adjustedBounds.min.x;
|
|
5875
5744
|
planeBounds.max.x = alignmentResult.adjustedBounds.max.x;
|
|
5876
|
-
|
|
5877
|
-
const offsetScaled = offset * finalScale;
|
|
5878
|
-
// Scale vertices only (normals are unit vectors, don't scale)
|
|
5879
|
-
if (offsetScaled === 0) {
|
|
5880
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
5881
|
-
vertices[i] *= finalScale;
|
|
5882
|
-
}
|
|
5883
|
-
}
|
|
5884
|
-
else {
|
|
5745
|
+
if (offset !== 0) {
|
|
5885
5746
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
5886
|
-
vertices[i]
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
planeBounds.min.y *= finalScale;
|
|
5893
|
-
planeBounds.min.z *= finalScale;
|
|
5894
|
-
planeBounds.max.x *= finalScale;
|
|
5895
|
-
planeBounds.max.y *= finalScale;
|
|
5896
|
-
planeBounds.max.z *= finalScale;
|
|
5897
|
-
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5898
|
-
const glyphInfo = glyphInfoArray[i];
|
|
5899
|
-
glyphInfo.bounds.min.x = glyphInfo.bounds.min.x * finalScale + offsetScaled;
|
|
5900
|
-
glyphInfo.bounds.min.y *= finalScale;
|
|
5901
|
-
glyphInfo.bounds.min.z *= finalScale;
|
|
5902
|
-
glyphInfo.bounds.max.x = glyphInfo.bounds.max.x * finalScale + offsetScaled;
|
|
5903
|
-
glyphInfo.bounds.max.y *= finalScale;
|
|
5904
|
-
glyphInfo.bounds.max.z *= finalScale;
|
|
5747
|
+
vertices[i] += offset;
|
|
5748
|
+
}
|
|
5749
|
+
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5750
|
+
glyphInfoArray[i].bounds.min.x += offset;
|
|
5751
|
+
glyphInfoArray[i].bounds.max.x += offset;
|
|
5752
|
+
}
|
|
5905
5753
|
}
|
|
5906
5754
|
let colors;
|
|
5907
5755
|
let coloredRanges;
|
|
@@ -5926,8 +5774,7 @@
|
|
|
5926
5774
|
verticesGenerated,
|
|
5927
5775
|
pointsRemovedByVisvalingam: optimizationStats.pointsRemovedByVisvalingam,
|
|
5928
5776
|
pointsRemovedByColinear: optimizationStats.pointsRemovedByColinear,
|
|
5929
|
-
originalPointCount: optimizationStats.originalPointCount
|
|
5930
|
-
...(cacheStats || {})
|
|
5777
|
+
originalPointCount: optimizationStats.originalPointCount
|
|
5931
5778
|
},
|
|
5932
5779
|
query: (options) => {
|
|
5933
5780
|
if (!originalText) {
|
|
@@ -5962,13 +5809,11 @@
|
|
|
5962
5809
|
static registerPattern(language, pattern) {
|
|
5963
5810
|
Text.patternCache.set(language, pattern);
|
|
5964
5811
|
}
|
|
5965
|
-
static clearFontCache() {
|
|
5966
|
-
Text.fontCache.clear();
|
|
5967
|
-
Text.fontCacheMemoryBytes = 0;
|
|
5968
|
-
}
|
|
5969
5812
|
static setMaxFontCacheMemoryMB(limitMB) {
|
|
5970
5813
|
Text.maxFontCacheMemoryBytes =
|
|
5971
|
-
limitMB === Infinity
|
|
5814
|
+
limitMB === Infinity
|
|
5815
|
+
? Infinity
|
|
5816
|
+
: Math.max(1, Math.floor(limitMB)) * 1024 * 1024;
|
|
5972
5817
|
Text.enforceFontCacheMemoryLimit();
|
|
5973
5818
|
}
|
|
5974
5819
|
getLoadedFont() {
|
|
@@ -5980,11 +5825,11 @@
|
|
|
5980
5825
|
}
|
|
5981
5826
|
return TextMeasurer.measureTextWidth(this.loadedFont, text, letterSpacing);
|
|
5982
5827
|
}
|
|
5983
|
-
|
|
5828
|
+
getCacheSize() {
|
|
5984
5829
|
if (this.geometryBuilder) {
|
|
5985
|
-
return this.geometryBuilder.getCacheStats();
|
|
5830
|
+
return this.geometryBuilder.getCacheStats().size;
|
|
5986
5831
|
}
|
|
5987
|
-
return
|
|
5832
|
+
return 0;
|
|
5988
5833
|
}
|
|
5989
5834
|
clearCache() {
|
|
5990
5835
|
if (this.geometryBuilder) {
|
|
@@ -5995,25 +5840,45 @@
|
|
|
5995
5840
|
const glyphCenters = new Float32Array(vertexCount * 3);
|
|
5996
5841
|
const glyphIndices = new Float32Array(vertexCount);
|
|
5997
5842
|
const glyphLineIndices = new Float32Array(vertexCount);
|
|
5998
|
-
|
|
5843
|
+
const glyphProgress = new Float32Array(vertexCount);
|
|
5844
|
+
const glyphBaselineY = new Float32Array(vertexCount);
|
|
5845
|
+
let minX = Infinity;
|
|
5846
|
+
let maxX = -Infinity;
|
|
5847
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
5848
|
+
const cx = (glyphs[i].bounds.min.x + glyphs[i].bounds.max.x) / 2;
|
|
5849
|
+
if (cx < minX)
|
|
5850
|
+
minX = cx;
|
|
5851
|
+
if (cx > maxX)
|
|
5852
|
+
maxX = cx;
|
|
5853
|
+
}
|
|
5854
|
+
const range = maxX - minX;
|
|
5855
|
+
for (let index = 0; index < glyphs.length; index++) {
|
|
5856
|
+
const glyph = glyphs[index];
|
|
5999
5857
|
const centerX = (glyph.bounds.min.x + glyph.bounds.max.x) / 2;
|
|
6000
5858
|
const centerY = (glyph.bounds.min.y + glyph.bounds.max.y) / 2;
|
|
6001
5859
|
const centerZ = (glyph.bounds.min.z + glyph.bounds.max.z) / 2;
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
5860
|
+
const baselineY = glyph.bounds.min.y;
|
|
5861
|
+
const progress = range > 0 ? (centerX - minX) / range : 0;
|
|
5862
|
+
const start = glyph.vertexStart;
|
|
5863
|
+
const end = Math.min(start + glyph.vertexCount, vertexCount);
|
|
5864
|
+
if (end <= start)
|
|
5865
|
+
continue;
|
|
5866
|
+
glyphIndices.fill(index, start, end);
|
|
5867
|
+
glyphLineIndices.fill(glyph.lineIndex, start, end);
|
|
5868
|
+
glyphProgress.fill(progress, start, end);
|
|
5869
|
+
glyphBaselineY.fill(baselineY, start, end);
|
|
5870
|
+
for (let v = start * 3; v < end * 3; v += 3) {
|
|
5871
|
+
glyphCenters[v] = centerX;
|
|
5872
|
+
glyphCenters[v + 1] = centerY;
|
|
5873
|
+
glyphCenters[v + 2] = centerZ;
|
|
6011
5874
|
}
|
|
6012
|
-
}
|
|
5875
|
+
}
|
|
6013
5876
|
return {
|
|
6014
5877
|
glyphCenter: glyphCenters,
|
|
6015
5878
|
glyphIndex: glyphIndices,
|
|
6016
|
-
glyphLineIndex: glyphLineIndices
|
|
5879
|
+
glyphLineIndex: glyphLineIndices,
|
|
5880
|
+
glyphProgress,
|
|
5881
|
+
glyphBaselineY
|
|
6017
5882
|
};
|
|
6018
5883
|
}
|
|
6019
5884
|
resetHelpers() {
|