three-text 0.2.19 → 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 +6 -2
- package/dist/index.cjs +185 -332
- package/dist/index.d.ts +10 -38
- package/dist/index.js +185 -332
- package/dist/index.min.cjs +704 -723
- package/dist/index.min.js +705 -724
- package/dist/index.umd.js +185 -332
- package/dist/index.umd.min.js +710 -729
- package/dist/three/index.cjs +3 -1
- package/dist/three/index.d.ts +1 -12
- package/dist/three/index.js +3 -1
- package/dist/three/react.d.ts +5 -13
- package/dist/types/core/Text.d.ts +1 -4
- 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 +5 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/three/index.d.ts +1 -5
- 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 +9 -4
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
|
|
@@ -2386,231 +2386,50 @@
|
|
|
2386
2386
|
}
|
|
2387
2387
|
}
|
|
2388
2388
|
|
|
2389
|
-
//
|
|
2390
|
-
class
|
|
2391
|
-
constructor(
|
|
2389
|
+
// Map-based cache with no eviction policy
|
|
2390
|
+
class Cache {
|
|
2391
|
+
constructor() {
|
|
2392
2392
|
this.cache = new Map();
|
|
2393
|
-
this.head = null;
|
|
2394
|
-
this.tail = null;
|
|
2395
|
-
this.stats = {
|
|
2396
|
-
hits: 0,
|
|
2397
|
-
misses: 0,
|
|
2398
|
-
evictions: 0,
|
|
2399
|
-
size: 0,
|
|
2400
|
-
memoryUsage: 0
|
|
2401
|
-
};
|
|
2402
|
-
this.options = {
|
|
2403
|
-
maxEntries: options.maxEntries ?? Infinity,
|
|
2404
|
-
maxMemoryBytes: options.maxMemoryBytes ?? Infinity,
|
|
2405
|
-
calculateSize: options.calculateSize ?? (() => 0),
|
|
2406
|
-
onEvict: options.onEvict
|
|
2407
|
-
};
|
|
2408
2393
|
}
|
|
2409
2394
|
get(key) {
|
|
2410
|
-
|
|
2411
|
-
if (node) {
|
|
2412
|
-
this.stats.hits++;
|
|
2413
|
-
this.moveToHead(node);
|
|
2414
|
-
return node.value;
|
|
2415
|
-
}
|
|
2416
|
-
else {
|
|
2417
|
-
this.stats.misses++;
|
|
2418
|
-
return undefined;
|
|
2419
|
-
}
|
|
2395
|
+
return this.cache.get(key);
|
|
2420
2396
|
}
|
|
2421
2397
|
has(key) {
|
|
2422
2398
|
return this.cache.has(key);
|
|
2423
2399
|
}
|
|
2424
2400
|
set(key, value) {
|
|
2425
|
-
|
|
2426
|
-
const existingNode = this.cache.get(key);
|
|
2427
|
-
if (existingNode) {
|
|
2428
|
-
const oldSize = this.options.calculateSize(existingNode.value);
|
|
2429
|
-
const newSize = this.options.calculateSize(value);
|
|
2430
|
-
this.stats.memoryUsage = this.stats.memoryUsage - oldSize + newSize;
|
|
2431
|
-
existingNode.value = value;
|
|
2432
|
-
this.moveToHead(existingNode);
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
const size = this.options.calculateSize(value);
|
|
2436
|
-
// Evict entries if we exceed limits
|
|
2437
|
-
this.evictIfNeeded(size);
|
|
2438
|
-
// Create new node
|
|
2439
|
-
const node = {
|
|
2440
|
-
key,
|
|
2441
|
-
value,
|
|
2442
|
-
prev: null,
|
|
2443
|
-
next: null
|
|
2444
|
-
};
|
|
2445
|
-
this.cache.set(key, node);
|
|
2446
|
-
this.addToHead(node);
|
|
2447
|
-
this.stats.size = this.cache.size;
|
|
2448
|
-
this.stats.memoryUsage += size;
|
|
2401
|
+
this.cache.set(key, value);
|
|
2449
2402
|
}
|
|
2450
2403
|
delete(key) {
|
|
2451
|
-
|
|
2452
|
-
if (!node)
|
|
2453
|
-
return false;
|
|
2454
|
-
const size = this.options.calculateSize(node.value);
|
|
2455
|
-
this.removeNode(node);
|
|
2456
|
-
this.cache.delete(key);
|
|
2457
|
-
this.stats.size = this.cache.size;
|
|
2458
|
-
this.stats.memoryUsage -= size;
|
|
2459
|
-
if (this.options.onEvict) {
|
|
2460
|
-
this.options.onEvict(key, node.value);
|
|
2461
|
-
}
|
|
2462
|
-
return true;
|
|
2404
|
+
return this.cache.delete(key);
|
|
2463
2405
|
}
|
|
2464
2406
|
clear() {
|
|
2465
|
-
if (this.options.onEvict) {
|
|
2466
|
-
for (const [key, node] of this.cache) {
|
|
2467
|
-
this.options.onEvict(key, node.value);
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
2407
|
this.cache.clear();
|
|
2471
|
-
this.head = null;
|
|
2472
|
-
this.tail = null;
|
|
2473
|
-
this.stats = {
|
|
2474
|
-
hits: 0,
|
|
2475
|
-
misses: 0,
|
|
2476
|
-
evictions: 0,
|
|
2477
|
-
size: 0,
|
|
2478
|
-
memoryUsage: 0
|
|
2479
|
-
};
|
|
2480
|
-
}
|
|
2481
|
-
getStats() {
|
|
2482
|
-
const total = this.stats.hits + this.stats.misses;
|
|
2483
|
-
const hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0;
|
|
2484
|
-
const memoryUsageMB = this.stats.memoryUsage / (1024 * 1024);
|
|
2485
|
-
return {
|
|
2486
|
-
...this.stats,
|
|
2487
|
-
hitRate,
|
|
2488
|
-
memoryUsageMB
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
keys() {
|
|
2492
|
-
const keys = [];
|
|
2493
|
-
let current = this.head;
|
|
2494
|
-
while (current) {
|
|
2495
|
-
keys.push(current.key);
|
|
2496
|
-
current = current.next;
|
|
2497
|
-
}
|
|
2498
|
-
return keys;
|
|
2499
2408
|
}
|
|
2500
2409
|
get size() {
|
|
2501
2410
|
return this.cache.size;
|
|
2502
2411
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
while (this.cache.size >= this.options.maxEntries && this.tail) {
|
|
2506
|
-
this.evictTail();
|
|
2507
|
-
}
|
|
2508
|
-
// Evict by memory usage
|
|
2509
|
-
if (this.options.maxMemoryBytes < Infinity) {
|
|
2510
|
-
while (this.tail &&
|
|
2511
|
-
this.stats.memoryUsage + requiredSize > this.options.maxMemoryBytes) {
|
|
2512
|
-
this.evictTail();
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
evictTail() {
|
|
2517
|
-
if (!this.tail)
|
|
2518
|
-
return;
|
|
2519
|
-
const nodeToRemove = this.tail;
|
|
2520
|
-
const size = this.options.calculateSize(nodeToRemove.value);
|
|
2521
|
-
this.removeTail();
|
|
2522
|
-
this.cache.delete(nodeToRemove.key);
|
|
2523
|
-
this.stats.size = this.cache.size;
|
|
2524
|
-
this.stats.memoryUsage -= size;
|
|
2525
|
-
this.stats.evictions++;
|
|
2526
|
-
if (this.options.onEvict) {
|
|
2527
|
-
this.options.onEvict(nodeToRemove.key, nodeToRemove.value);
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
addToHead(node) {
|
|
2531
|
-
node.prev = null;
|
|
2532
|
-
node.next = null;
|
|
2533
|
-
if (!this.head) {
|
|
2534
|
-
this.head = this.tail = node;
|
|
2535
|
-
}
|
|
2536
|
-
else {
|
|
2537
|
-
node.next = this.head;
|
|
2538
|
-
this.head.prev = node;
|
|
2539
|
-
this.head = node;
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
removeNode(node) {
|
|
2543
|
-
if (node.prev) {
|
|
2544
|
-
node.prev.next = node.next;
|
|
2545
|
-
}
|
|
2546
|
-
else {
|
|
2547
|
-
this.head = node.next;
|
|
2548
|
-
}
|
|
2549
|
-
if (node.next) {
|
|
2550
|
-
node.next.prev = node.prev;
|
|
2551
|
-
}
|
|
2552
|
-
else {
|
|
2553
|
-
this.tail = node.prev;
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
removeTail() {
|
|
2557
|
-
if (this.tail) {
|
|
2558
|
-
this.removeNode(this.tail);
|
|
2559
|
-
}
|
|
2412
|
+
keys() {
|
|
2413
|
+
return Array.from(this.cache.keys());
|
|
2560
2414
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
this.addToHead(node);
|
|
2415
|
+
getStats() {
|
|
2416
|
+
return {
|
|
2417
|
+
size: this.cache.size
|
|
2418
|
+
};
|
|
2566
2419
|
}
|
|
2567
2420
|
}
|
|
2568
2421
|
|
|
2569
|
-
const DEFAULT_CACHE_SIZE_MB = 250;
|
|
2570
2422
|
function getGlyphCacheKey(fontId, glyphId, depth, removeOverlaps) {
|
|
2571
2423
|
const roundedDepth = Math.round(depth * 1000) / 1000;
|
|
2572
2424
|
return `${fontId}_${glyphId}_${roundedDepth}_${removeOverlaps}`;
|
|
2573
2425
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
size += glyph.normals.length * 4;
|
|
2578
|
-
size += glyph.indices.length * glyph.indices.BYTES_PER_ELEMENT;
|
|
2579
|
-
size += 24; // 2 Vec3s
|
|
2580
|
-
size += 256; // Object overhead
|
|
2581
|
-
return size;
|
|
2426
|
+
const globalGlyphCache = new Cache();
|
|
2427
|
+
function createGlyphCache() {
|
|
2428
|
+
return new Cache();
|
|
2582
2429
|
}
|
|
2583
|
-
const
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2587
|
-
});
|
|
2588
|
-
function createGlyphCache(maxCacheSizeMB = DEFAULT_CACHE_SIZE_MB) {
|
|
2589
|
-
return new LRUCache({
|
|
2590
|
-
maxEntries: Infinity,
|
|
2591
|
-
maxMemoryBytes: maxCacheSizeMB * 1024 * 1024,
|
|
2592
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2593
|
-
});
|
|
2594
|
-
}
|
|
2595
|
-
// Shared across builder instances: contour extraction, word clustering, boundary grouping
|
|
2596
|
-
const globalContourCache = new LRUCache({
|
|
2597
|
-
maxEntries: 1000,
|
|
2598
|
-
calculateSize: (contours) => {
|
|
2599
|
-
let size = 0;
|
|
2600
|
-
for (const path of contours.paths) {
|
|
2601
|
-
size += path.points.length * 16; // Vec2 = 2 floats * 8 bytes
|
|
2602
|
-
}
|
|
2603
|
-
return size + 64; // bounds overhead
|
|
2604
|
-
}
|
|
2605
|
-
});
|
|
2606
|
-
const globalWordCache = new LRUCache({
|
|
2607
|
-
maxEntries: 1000,
|
|
2608
|
-
calculateSize: calculateGlyphMemoryUsage
|
|
2609
|
-
});
|
|
2610
|
-
const globalClusteringCache = new LRUCache({
|
|
2611
|
-
maxEntries: 2000,
|
|
2612
|
-
calculateSize: () => 1
|
|
2613
|
-
});
|
|
2430
|
+
const globalContourCache = new Cache();
|
|
2431
|
+
const globalWordCache = new Cache();
|
|
2432
|
+
const globalClusteringCache = new Cache();
|
|
2614
2433
|
|
|
2615
2434
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
2616
2435
|
|
|
@@ -2945,47 +2764,79 @@
|
|
|
2945
2764
|
|
|
2946
2765
|
class Extruder {
|
|
2947
2766
|
constructor() { }
|
|
2948
|
-
packEdge(a, b) {
|
|
2949
|
-
const lo = a < b ? a : b;
|
|
2950
|
-
const hi = a < b ? b : a;
|
|
2951
|
-
return lo * 0x100000000 + hi;
|
|
2952
|
-
}
|
|
2953
2767
|
extrude(geometry, depth = 0, unitsPerEm) {
|
|
2954
2768
|
const points = geometry.triangles.vertices;
|
|
2955
2769
|
const triangleIndices = geometry.triangles.indices;
|
|
2956
2770
|
const numPoints = points.length / 2;
|
|
2957
|
-
//
|
|
2771
|
+
// Boundary edges are those that appear in exactly one triangle
|
|
2958
2772
|
let boundaryEdges = [];
|
|
2959
2773
|
if (depth !== 0) {
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
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) {
|
|
2963
2779
|
const a = triangleIndices[i];
|
|
2964
2780
|
const b = triangleIndices[i + 1];
|
|
2965
2781
|
const c = triangleIndices[i + 2];
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
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
|
+
}
|
|
2981
2834
|
}
|
|
2982
2835
|
boundaryEdges = [];
|
|
2983
|
-
for (const [
|
|
2984
|
-
if (count
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
if (edge)
|
|
2988
|
-
boundaryEdges.push(edge);
|
|
2836
|
+
for (const [v0, v1, count] of edgeMap.values()) {
|
|
2837
|
+
if (count === 1) {
|
|
2838
|
+
boundaryEdges.push([v0, v1]);
|
|
2839
|
+
}
|
|
2989
2840
|
}
|
|
2990
2841
|
}
|
|
2991
2842
|
const sideEdgeCount = depth === 0 ? 0 : boundaryEdges.length;
|
|
@@ -2999,7 +2850,6 @@
|
|
|
2999
2850
|
: triangleIndices.length * 2 + sideEdgeCount * 6;
|
|
3000
2851
|
const indices = new Uint32Array(indexCount);
|
|
3001
2852
|
if (depth === 0) {
|
|
3002
|
-
// Single-sided flat geometry at z=0
|
|
3003
2853
|
let vPos = 0;
|
|
3004
2854
|
for (let i = 0; i < points.length; i += 2) {
|
|
3005
2855
|
vertices[vPos] = points[i];
|
|
@@ -3010,16 +2860,11 @@
|
|
|
3010
2860
|
normals[vPos + 2] = 1;
|
|
3011
2861
|
vPos += 3;
|
|
3012
2862
|
}
|
|
3013
|
-
|
|
3014
|
-
for (let i = 0; i < triangleIndices.length; i++) {
|
|
3015
|
-
indices[i] = triangleIndices[i];
|
|
3016
|
-
}
|
|
2863
|
+
indices.set(triangleIndices);
|
|
3017
2864
|
return { vertices, normals, indices };
|
|
3018
2865
|
}
|
|
3019
|
-
// Extruded geometry: front at z=0, back at z=depth
|
|
3020
2866
|
const minBackOffset = unitsPerEm * 0.000025;
|
|
3021
2867
|
const backZ = depth <= minBackOffset ? minBackOffset : depth;
|
|
3022
|
-
// Generate both caps in one pass
|
|
3023
2868
|
for (let p = 0, vi = 0; p < points.length; p += 2, vi++) {
|
|
3024
2869
|
const x = points[p];
|
|
3025
2870
|
const y = points[p + 1];
|
|
@@ -3040,41 +2885,39 @@
|
|
|
3040
2885
|
normals[baseD + 1] = 0;
|
|
3041
2886
|
normals[baseD + 2] = 1;
|
|
3042
2887
|
}
|
|
3043
|
-
//
|
|
3044
|
-
|
|
3045
|
-
for (let i = 0; i <
|
|
3046
|
-
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];
|
|
3047
2892
|
}
|
|
3048
|
-
//
|
|
3049
|
-
for (let i = 0; i <
|
|
3050
|
-
indices[
|
|
2893
|
+
// Back cap faces +Z, use original winding
|
|
2894
|
+
for (let i = 0; i < triLen; i++) {
|
|
2895
|
+
indices[triLen + i] = triangleIndices[i] + numPoints;
|
|
3051
2896
|
}
|
|
3052
|
-
// Side walls
|
|
3053
2897
|
let nextVertex = numPoints * 2;
|
|
3054
|
-
let idxPos =
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
const
|
|
3058
|
-
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;
|
|
3059
2906
|
const p0x = points[u2];
|
|
3060
2907
|
const p0y = points[u2 + 1];
|
|
3061
2908
|
const p1x = points[v2];
|
|
3062
2909
|
const p1y = points[v2 + 1];
|
|
3063
|
-
// Perpendicular normal for this wall segment
|
|
3064
|
-
// Uses the edge direction from the cap triangulation so winding does not depend on contour direction
|
|
3065
2910
|
const ex = p1x - p0x;
|
|
3066
2911
|
const ey = p1y - p0y;
|
|
3067
2912
|
const lenSq = ex * ex + ey * ey;
|
|
3068
2913
|
let nx = 0;
|
|
3069
2914
|
let ny = 0;
|
|
3070
|
-
if (lenSq >
|
|
2915
|
+
if (lenSq > 1e-10) {
|
|
3071
2916
|
const invLen = 1 / Math.sqrt(lenSq);
|
|
3072
2917
|
nx = ey * invLen;
|
|
3073
2918
|
ny = -ex * invLen;
|
|
3074
2919
|
}
|
|
3075
|
-
const
|
|
3076
|
-
const base = baseVertex * 3;
|
|
3077
|
-
// Wall quad: front edge at z=0, back edge at z=depth
|
|
2920
|
+
const base = nextVertex * 3;
|
|
3078
2921
|
vertices[base] = p0x;
|
|
3079
2922
|
vertices[base + 1] = p0y;
|
|
3080
2923
|
vertices[base + 2] = 0;
|
|
@@ -3087,7 +2930,6 @@
|
|
|
3087
2930
|
vertices[base + 9] = p1x;
|
|
3088
2931
|
vertices[base + 10] = p1y;
|
|
3089
2932
|
vertices[base + 11] = backZ;
|
|
3090
|
-
// Wall normals point perpendicular to edge
|
|
3091
2933
|
normals[base] = nx;
|
|
3092
2934
|
normals[base + 1] = ny;
|
|
3093
2935
|
normals[base + 2] = 0;
|
|
@@ -3100,13 +2942,14 @@
|
|
|
3100
2942
|
normals[base + 9] = nx;
|
|
3101
2943
|
normals[base + 10] = ny;
|
|
3102
2944
|
normals[base + 11] = 0;
|
|
3103
|
-
|
|
3104
|
-
indices[idxPos
|
|
3105
|
-
indices[idxPos
|
|
3106
|
-
indices[idxPos
|
|
3107
|
-
indices[idxPos
|
|
3108
|
-
indices[idxPos
|
|
3109
|
-
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;
|
|
3110
2953
|
nextVertex += 4;
|
|
3111
2954
|
}
|
|
3112
2955
|
return { vertices, normals, indices };
|
|
@@ -4123,7 +3966,7 @@
|
|
|
4123
3966
|
].join('|');
|
|
4124
3967
|
}
|
|
4125
3968
|
// Build instanced geometry from glyph contours
|
|
4126
|
-
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, separateGlyphs = false, coloredTextIndices) {
|
|
3969
|
+
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, scale, separateGlyphs = false, coloredTextIndices) {
|
|
4127
3970
|
if (isLogEnabled) {
|
|
4128
3971
|
let wordCount = 0;
|
|
4129
3972
|
for (let i = 0; i < clustersByLine.length; i++) {
|
|
@@ -4301,9 +4144,9 @@
|
|
|
4301
4144
|
const py = task.py;
|
|
4302
4145
|
const pz = task.pz;
|
|
4303
4146
|
for (let j = 0; j < v.length; j += 3) {
|
|
4304
|
-
vertexArray[vertexPos++] = v[j] + px;
|
|
4305
|
-
vertexArray[vertexPos++] = v[j + 1] + py;
|
|
4306
|
-
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;
|
|
4307
4150
|
}
|
|
4308
4151
|
normalArray.set(n, normalPos);
|
|
4309
4152
|
normalPos += n.length;
|
|
@@ -4313,6 +4156,20 @@
|
|
|
4313
4156
|
}
|
|
4314
4157
|
}
|
|
4315
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
|
+
}
|
|
4316
4173
|
return {
|
|
4317
4174
|
vertices: vertexArray,
|
|
4318
4175
|
normals: normalArray,
|
|
@@ -4324,7 +4181,6 @@
|
|
|
4324
4181
|
getClusterKey(glyphs, depth, removeOverlaps) {
|
|
4325
4182
|
if (glyphs.length === 0)
|
|
4326
4183
|
return '';
|
|
4327
|
-
// Normalize positions relative to the first glyph in the cluster
|
|
4328
4184
|
const refX = glyphs[0].x ?? 0;
|
|
4329
4185
|
const refY = glyphs[0].y ?? 0;
|
|
4330
4186
|
const parts = glyphs.map((g) => {
|
|
@@ -5379,6 +5235,7 @@
|
|
|
5379
5235
|
}
|
|
5380
5236
|
|
|
5381
5237
|
const DEFAULT_MAX_TEXT_LENGTH = 100000;
|
|
5238
|
+
const DEFAULT_FONT_SIZE = 72;
|
|
5382
5239
|
class Text {
|
|
5383
5240
|
static { this.patternCache = new Map(); }
|
|
5384
5241
|
static { this.hbInitPromise = null; }
|
|
@@ -5452,7 +5309,7 @@
|
|
|
5452
5309
|
return {
|
|
5453
5310
|
...newResult,
|
|
5454
5311
|
getLoadedFont: () => text.getLoadedFont(),
|
|
5455
|
-
|
|
5312
|
+
getCacheSize: () => text.getCacheSize(),
|
|
5456
5313
|
clearCache: () => text.clearCache(),
|
|
5457
5314
|
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5458
5315
|
update
|
|
@@ -5461,7 +5318,7 @@
|
|
|
5461
5318
|
return {
|
|
5462
5319
|
...result,
|
|
5463
5320
|
getLoadedFont: () => text.getLoadedFont(),
|
|
5464
|
-
|
|
5321
|
+
getCacheSize: () => text.getCacheSize(),
|
|
5465
5322
|
clearCache: () => text.clearCache(),
|
|
5466
5323
|
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5467
5324
|
update
|
|
@@ -5593,7 +5450,7 @@
|
|
|
5593
5450
|
async createGeometry(options) {
|
|
5594
5451
|
perfLogger.start('Text.createGeometry', {
|
|
5595
5452
|
textLength: options.text.length,
|
|
5596
|
-
size: options.size ||
|
|
5453
|
+
size: options.size || DEFAULT_FONT_SIZE,
|
|
5597
5454
|
hasLayout: !!options.layout,
|
|
5598
5455
|
mode: 'cached'
|
|
5599
5456
|
});
|
|
@@ -5607,7 +5464,7 @@
|
|
|
5607
5464
|
this.updateFontVariations(options);
|
|
5608
5465
|
if (!this.geometryBuilder) {
|
|
5609
5466
|
const cache = options.maxCacheSizeMB
|
|
5610
|
-
? createGlyphCache(
|
|
5467
|
+
? createGlyphCache()
|
|
5611
5468
|
: globalGlyphCache;
|
|
5612
5469
|
this.geometryBuilder = new GlyphGeometryBuilder(cache, this.loadedFont);
|
|
5613
5470
|
this.geometryBuilder.setFontId(this.currentFontId);
|
|
@@ -5653,10 +5510,9 @@
|
|
|
5653
5510
|
}
|
|
5654
5511
|
}
|
|
5655
5512
|
}
|
|
5656
|
-
const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.
|
|
5657
|
-
const
|
|
5658
|
-
|
|
5659
|
-
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) {
|
|
5660
5516
|
const glyphAttrs = this.createGlyphAttributes(result.vertices.length / 3, result.glyphs);
|
|
5661
5517
|
result.glyphAttributes = glyphAttrs;
|
|
5662
5518
|
}
|
|
@@ -5723,16 +5579,16 @@
|
|
|
5723
5579
|
if (!this.loadedFont) {
|
|
5724
5580
|
throw new Error('Font not loaded. Use Text.create() with a font option');
|
|
5725
5581
|
}
|
|
5726
|
-
const { text, size =
|
|
5582
|
+
const { text, size = DEFAULT_FONT_SIZE, depth = 0, lineHeight = 1.0, letterSpacing = 0, layout = {} } = options;
|
|
5727
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;
|
|
5728
5585
|
let widthInFontUnits;
|
|
5729
5586
|
if (width !== undefined) {
|
|
5730
|
-
widthInFontUnits = width *
|
|
5587
|
+
widthInFontUnits = width * fontUnitsPerPixel;
|
|
5731
5588
|
}
|
|
5732
5589
|
// Keep depth behavior consistent with Extruder: extremely small non-zero depths
|
|
5733
|
-
// are clamped to a minimum back offset
|
|
5734
|
-
const
|
|
5735
|
-
const rawDepthInFontUnits = depth * depthScale;
|
|
5590
|
+
// are clamped to a minimum back offset to prevent Z fighting
|
|
5591
|
+
const rawDepthInFontUnits = depth * fontUnitsPerPixel;
|
|
5736
5592
|
const minExtrudeDepth = this.loadedFont.upem * 0.000025;
|
|
5737
5593
|
const depthInFontUnits = rawDepthInFontUnits <= 0
|
|
5738
5594
|
? 0
|
|
@@ -5775,7 +5631,8 @@
|
|
|
5775
5631
|
align,
|
|
5776
5632
|
direction,
|
|
5777
5633
|
depth: depthInFontUnits,
|
|
5778
|
-
size
|
|
5634
|
+
size,
|
|
5635
|
+
pixelsPerFontUnit: 1 / fontUnitsPerPixel
|
|
5779
5636
|
};
|
|
5780
5637
|
}
|
|
5781
5638
|
applyColorSystem(vertices, glyphInfoArray, color, originalText) {
|
|
@@ -5871,8 +5728,8 @@
|
|
|
5871
5728
|
}
|
|
5872
5729
|
return { colors, coloredRanges };
|
|
5873
5730
|
}
|
|
5874
|
-
finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options,
|
|
5875
|
-
const { layout = {}
|
|
5731
|
+
finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, originalText) {
|
|
5732
|
+
const { layout = {} } = options;
|
|
5876
5733
|
const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
|
|
5877
5734
|
if (!this.textLayout) {
|
|
5878
5735
|
this.textLayout = new TextLayout(this.loadedFont);
|
|
@@ -5885,37 +5742,14 @@
|
|
|
5885
5742
|
const offset = alignmentResult.offset;
|
|
5886
5743
|
planeBounds.min.x = alignmentResult.adjustedBounds.min.x;
|
|
5887
5744
|
planeBounds.max.x = alignmentResult.adjustedBounds.max.x;
|
|
5888
|
-
|
|
5889
|
-
const offsetScaled = offset * finalScale;
|
|
5890
|
-
// Scale vertices only (normals are unit vectors, don't scale)
|
|
5891
|
-
if (offsetScaled === 0) {
|
|
5892
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
5893
|
-
vertices[i] *= finalScale;
|
|
5894
|
-
}
|
|
5895
|
-
}
|
|
5896
|
-
else {
|
|
5745
|
+
if (offset !== 0) {
|
|
5897
5746
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
5898
|
-
vertices[i]
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
planeBounds.min.y *= finalScale;
|
|
5905
|
-
planeBounds.min.z *= finalScale;
|
|
5906
|
-
planeBounds.max.x *= finalScale;
|
|
5907
|
-
planeBounds.max.y *= finalScale;
|
|
5908
|
-
planeBounds.max.z *= finalScale;
|
|
5909
|
-
for (let i = 0; i < glyphInfoArray.length; i++) {
|
|
5910
|
-
const glyphInfo = glyphInfoArray[i];
|
|
5911
|
-
glyphInfo.bounds.min.x =
|
|
5912
|
-
glyphInfo.bounds.min.x * finalScale + offsetScaled;
|
|
5913
|
-
glyphInfo.bounds.min.y *= finalScale;
|
|
5914
|
-
glyphInfo.bounds.min.z *= finalScale;
|
|
5915
|
-
glyphInfo.bounds.max.x =
|
|
5916
|
-
glyphInfo.bounds.max.x * finalScale + offsetScaled;
|
|
5917
|
-
glyphInfo.bounds.max.y *= finalScale;
|
|
5918
|
-
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
|
+
}
|
|
5919
5753
|
}
|
|
5920
5754
|
let colors;
|
|
5921
5755
|
let coloredRanges;
|
|
@@ -5940,8 +5774,7 @@
|
|
|
5940
5774
|
verticesGenerated,
|
|
5941
5775
|
pointsRemovedByVisvalingam: optimizationStats.pointsRemovedByVisvalingam,
|
|
5942
5776
|
pointsRemovedByColinear: optimizationStats.pointsRemovedByColinear,
|
|
5943
|
-
originalPointCount: optimizationStats.originalPointCount
|
|
5944
|
-
...(cacheStats || {})
|
|
5777
|
+
originalPointCount: optimizationStats.originalPointCount
|
|
5945
5778
|
},
|
|
5946
5779
|
query: (options) => {
|
|
5947
5780
|
if (!originalText) {
|
|
@@ -5992,11 +5825,11 @@
|
|
|
5992
5825
|
}
|
|
5993
5826
|
return TextMeasurer.measureTextWidth(this.loadedFont, text, letterSpacing);
|
|
5994
5827
|
}
|
|
5995
|
-
|
|
5828
|
+
getCacheSize() {
|
|
5996
5829
|
if (this.geometryBuilder) {
|
|
5997
|
-
return this.geometryBuilder.getCacheStats();
|
|
5830
|
+
return this.geometryBuilder.getCacheStats().size;
|
|
5998
5831
|
}
|
|
5999
|
-
return
|
|
5832
|
+
return 0;
|
|
6000
5833
|
}
|
|
6001
5834
|
clearCache() {
|
|
6002
5835
|
if (this.geometryBuilder) {
|
|
@@ -6007,25 +5840,45 @@
|
|
|
6007
5840
|
const glyphCenters = new Float32Array(vertexCount * 3);
|
|
6008
5841
|
const glyphIndices = new Float32Array(vertexCount);
|
|
6009
5842
|
const glyphLineIndices = new Float32Array(vertexCount);
|
|
6010
|
-
|
|
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];
|
|
6011
5857
|
const centerX = (glyph.bounds.min.x + glyph.bounds.max.x) / 2;
|
|
6012
5858
|
const centerY = (glyph.bounds.min.y + glyph.bounds.max.y) / 2;
|
|
6013
5859
|
const centerZ = (glyph.bounds.min.z + glyph.bounds.max.z) / 2;
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
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;
|
|
6023
5874
|
}
|
|
6024
|
-
}
|
|
5875
|
+
}
|
|
6025
5876
|
return {
|
|
6026
5877
|
glyphCenter: glyphCenters,
|
|
6027
5878
|
glyphIndex: glyphIndices,
|
|
6028
|
-
glyphLineIndex: glyphLineIndices
|
|
5879
|
+
glyphLineIndex: glyphLineIndices,
|
|
5880
|
+
glyphProgress,
|
|
5881
|
+
glyphBaselineY
|
|
6029
5882
|
};
|
|
6030
5883
|
}
|
|
6031
5884
|
resetHelpers() {
|