three-text 0.4.2 → 0.4.3
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/dist/index.cjs +376 -150
- package/dist/index.js +376 -150
- package/dist/index.min.cjs +395 -353
- package/dist/index.min.js +392 -350
- package/dist/index.umd.js +376 -150
- package/dist/index.umd.min.js +480 -438
- package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +3 -0
- package/dist/types/core/geometry/Tessellator.d.ts +4 -0
- package/package.json +1 -1
- package/dist/types/core/layout/LineBreakDelta.d.ts +0 -97
- package/dist/types/core/layout/LineBreakGood.d.ts +0 -100
- package/dist/types/core/layout/LineBreakHashMap.d.ts +0 -103
- package/dist/types/core/layout/LineBreakOriginal.d.ts +0 -103
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.4.
|
|
2
|
+
* three-text v0.4.3
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -2502,6 +2502,12 @@ class Tessellator {
|
|
|
2502
2502
|
logger.log(`Tessellator: removeOverlaps=${removeOverlaps}, processing ${valid.length} paths`);
|
|
2503
2503
|
return this.tessellate(valid, removeOverlaps, isCFF, needsExtrusionContours);
|
|
2504
2504
|
}
|
|
2505
|
+
processContours(contours, removeOverlaps = true, isCFF = false, needsExtrusionContours = true) {
|
|
2506
|
+
if (contours.length === 0) {
|
|
2507
|
+
return { triangles: { vertices: [], indices: [] }, contours: [] };
|
|
2508
|
+
}
|
|
2509
|
+
return this.tessellateContours(contours, removeOverlaps, isCFF, needsExtrusionContours);
|
|
2510
|
+
}
|
|
2505
2511
|
tessellate(paths, removeOverlaps, isCFF, needsExtrusionContours) {
|
|
2506
2512
|
// libtess expects CCW winding; TTF outer contours are CW
|
|
2507
2513
|
const needsWindingReversal = !isCFF && !removeOverlaps;
|
|
@@ -2566,6 +2572,68 @@ class Tessellator {
|
|
|
2566
2572
|
contours: extrusionContours
|
|
2567
2573
|
};
|
|
2568
2574
|
}
|
|
2575
|
+
tessellateContours(contours, removeOverlaps, isCFF, needsExtrusionContours) {
|
|
2576
|
+
const needsWindingReversal = !isCFF && !removeOverlaps;
|
|
2577
|
+
let originalContours;
|
|
2578
|
+
let tessContours;
|
|
2579
|
+
if (needsWindingReversal) {
|
|
2580
|
+
tessContours = this.reverseContours(contours);
|
|
2581
|
+
if (removeOverlaps || needsExtrusionContours) {
|
|
2582
|
+
originalContours = contours;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
else {
|
|
2586
|
+
originalContours = contours;
|
|
2587
|
+
tessContours = contours;
|
|
2588
|
+
}
|
|
2589
|
+
let extrusionContours = needsExtrusionContours
|
|
2590
|
+
? needsWindingReversal
|
|
2591
|
+
? tessContours
|
|
2592
|
+
: (originalContours ?? contours)
|
|
2593
|
+
: [];
|
|
2594
|
+
if (removeOverlaps) {
|
|
2595
|
+
logger.log('Two-pass: boundary extraction then triangulation');
|
|
2596
|
+
perfLogger.start('Tessellator.boundaryPass', {
|
|
2597
|
+
contourCount: tessContours.length
|
|
2598
|
+
});
|
|
2599
|
+
const boundaryResult = this.performTessellation(originalContours, 'boundary');
|
|
2600
|
+
perfLogger.end('Tessellator.boundaryPass');
|
|
2601
|
+
if (!boundaryResult) {
|
|
2602
|
+
logger.warn('libtess returned empty result from boundary pass');
|
|
2603
|
+
return { triangles: { vertices: [], indices: [] }, contours: [] };
|
|
2604
|
+
}
|
|
2605
|
+
tessContours = this.boundaryToContours(boundaryResult);
|
|
2606
|
+
if (needsExtrusionContours) {
|
|
2607
|
+
extrusionContours = tessContours;
|
|
2608
|
+
}
|
|
2609
|
+
logger.log(`Boundary pass created ${tessContours.length} contours. Starting triangulation pass.`);
|
|
2610
|
+
}
|
|
2611
|
+
else {
|
|
2612
|
+
logger.log(`Single-pass triangulation for ${isCFF ? 'CFF' : 'TTF'}`);
|
|
2613
|
+
}
|
|
2614
|
+
perfLogger.start('Tessellator.triangulationPass', {
|
|
2615
|
+
contourCount: tessContours.length
|
|
2616
|
+
});
|
|
2617
|
+
const triangleResult = this.performTessellation(tessContours, 'triangles');
|
|
2618
|
+
perfLogger.end('Tessellator.triangulationPass');
|
|
2619
|
+
if (!triangleResult) {
|
|
2620
|
+
const warning = removeOverlaps
|
|
2621
|
+
? 'libtess returned empty result from triangulation pass'
|
|
2622
|
+
: 'libtess returned empty result from single-pass triangulation';
|
|
2623
|
+
logger.warn(warning);
|
|
2624
|
+
return {
|
|
2625
|
+
triangles: { vertices: [], indices: [] },
|
|
2626
|
+
contours: extrusionContours
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
return {
|
|
2630
|
+
triangles: {
|
|
2631
|
+
vertices: triangleResult.vertices,
|
|
2632
|
+
indices: triangleResult.indices || []
|
|
2633
|
+
},
|
|
2634
|
+
contours: extrusionContours
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2569
2637
|
pathsToContours(paths, reversePoints = false) {
|
|
2570
2638
|
const contours = new Array(paths.length);
|
|
2571
2639
|
for (let p = 0; p < paths.length; p++) {
|
|
@@ -2603,6 +2671,35 @@ class Tessellator {
|
|
|
2603
2671
|
}
|
|
2604
2672
|
return contours;
|
|
2605
2673
|
}
|
|
2674
|
+
reverseContours(contours) {
|
|
2675
|
+
const reversed = new Array(contours.length);
|
|
2676
|
+
for (let i = 0; i < contours.length; i++) {
|
|
2677
|
+
reversed[i] = this.reverseContour(contours[i]);
|
|
2678
|
+
}
|
|
2679
|
+
return reversed;
|
|
2680
|
+
}
|
|
2681
|
+
reverseContour(contour) {
|
|
2682
|
+
const len = contour.length;
|
|
2683
|
+
if (len === 0)
|
|
2684
|
+
return [];
|
|
2685
|
+
const isClosed = len >= 4 &&
|
|
2686
|
+
contour[0] === contour[len - 2] &&
|
|
2687
|
+
contour[1] === contour[len - 1];
|
|
2688
|
+
const end = isClosed ? len - 2 : len;
|
|
2689
|
+
if (end === 0)
|
|
2690
|
+
return [];
|
|
2691
|
+
const reversed = new Array(end + 2);
|
|
2692
|
+
let out = 0;
|
|
2693
|
+
for (let i = end - 2; i >= 0; i -= 2) {
|
|
2694
|
+
reversed[out++] = contour[i];
|
|
2695
|
+
reversed[out++] = contour[i + 1];
|
|
2696
|
+
}
|
|
2697
|
+
if (out >= 2) {
|
|
2698
|
+
reversed[out++] = reversed[0];
|
|
2699
|
+
reversed[out++] = reversed[1];
|
|
2700
|
+
}
|
|
2701
|
+
return reversed;
|
|
2702
|
+
}
|
|
2606
2703
|
performTessellation(contours, mode) {
|
|
2607
2704
|
const tess = new libtess_minExports.GluTesselator();
|
|
2608
2705
|
tess.gluTessProperty(libtess_minExports.gluEnum.GLU_TESS_WINDING_RULE, libtess_minExports.windingRule.GLU_TESS_WINDING_NONZERO);
|
|
@@ -2729,80 +2826,86 @@ class Extruder {
|
|
|
2729
2826
|
extrude(geometry, depth = 0, unitsPerEm) {
|
|
2730
2827
|
const points = geometry.triangles.vertices;
|
|
2731
2828
|
const triangleIndices = geometry.triangles.indices;
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2829
|
+
const contours = geometry.contours;
|
|
2830
|
+
const pointLen = points.length;
|
|
2831
|
+
const numPoints = pointLen / 2;
|
|
2832
|
+
// Prefer contours for side walls; fall back to triangle edges
|
|
2734
2833
|
let boundaryEdges = [];
|
|
2834
|
+
let sideEdgeCount = 0;
|
|
2835
|
+
let useContours = false;
|
|
2735
2836
|
if (depth !== 0) {
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
const c = triangleIndices[i + 2];
|
|
2744
|
-
let key, v0, v1;
|
|
2745
|
-
if (a < b) {
|
|
2746
|
-
key = (a << 16) | b;
|
|
2747
|
-
v0 = a;
|
|
2748
|
-
v1 = b;
|
|
2749
|
-
}
|
|
2750
|
-
else {
|
|
2751
|
-
key = (b << 16) | a;
|
|
2752
|
-
v0 = a;
|
|
2753
|
-
v1 = b;
|
|
2754
|
-
}
|
|
2755
|
-
let data = edgeMap.get(key);
|
|
2756
|
-
if (data) {
|
|
2757
|
-
data[2]++;
|
|
2758
|
-
}
|
|
2759
|
-
else {
|
|
2760
|
-
edgeMap.set(key, [v0, v1, 1]);
|
|
2761
|
-
}
|
|
2762
|
-
if (b < c) {
|
|
2763
|
-
key = (b << 16) | c;
|
|
2764
|
-
v0 = b;
|
|
2765
|
-
v1 = c;
|
|
2766
|
-
}
|
|
2767
|
-
else {
|
|
2768
|
-
key = (c << 16) | b;
|
|
2769
|
-
v0 = b;
|
|
2770
|
-
v1 = c;
|
|
2771
|
-
}
|
|
2772
|
-
data = edgeMap.get(key);
|
|
2773
|
-
if (data) {
|
|
2774
|
-
data[2]++;
|
|
2775
|
-
}
|
|
2776
|
-
else {
|
|
2777
|
-
edgeMap.set(key, [v0, v1, 1]);
|
|
2778
|
-
}
|
|
2779
|
-
if (c < a) {
|
|
2780
|
-
key = (c << 16) | a;
|
|
2781
|
-
v0 = c;
|
|
2782
|
-
v1 = a;
|
|
2783
|
-
}
|
|
2784
|
-
else {
|
|
2785
|
-
key = (a << 16) | c;
|
|
2786
|
-
v0 = c;
|
|
2787
|
-
v1 = a;
|
|
2788
|
-
}
|
|
2789
|
-
data = edgeMap.get(key);
|
|
2790
|
-
if (data) {
|
|
2791
|
-
data[2]++;
|
|
2792
|
-
}
|
|
2793
|
-
else {
|
|
2794
|
-
edgeMap.set(key, [v0, v1, 1]);
|
|
2837
|
+
if (contours.length > 0) {
|
|
2838
|
+
useContours = true;
|
|
2839
|
+
for (const contour of contours) {
|
|
2840
|
+
const contourPointCount = contour.length >> 1;
|
|
2841
|
+
if (contourPointCount >= 2) {
|
|
2842
|
+
sideEdgeCount += contourPointCount - 1;
|
|
2843
|
+
}
|
|
2795
2844
|
}
|
|
2796
2845
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2846
|
+
else {
|
|
2847
|
+
// Use a directionless key (min/max) to detect shared edges
|
|
2848
|
+
// Store the directed edge (a->b) and mark as null when seen twice
|
|
2849
|
+
const edgeMap = new Map();
|
|
2850
|
+
const triLen = triangleIndices.length;
|
|
2851
|
+
for (let i = 0; i < triLen; i += 3) {
|
|
2852
|
+
const a = triangleIndices[i];
|
|
2853
|
+
const b = triangleIndices[i + 1];
|
|
2854
|
+
const c = triangleIndices[i + 2];
|
|
2855
|
+
let key, packed;
|
|
2856
|
+
if (a < b) {
|
|
2857
|
+
key = (a << 16) | b;
|
|
2858
|
+
}
|
|
2859
|
+
else {
|
|
2860
|
+
key = (b << 16) | a;
|
|
2861
|
+
}
|
|
2862
|
+
packed = (a << 16) | b;
|
|
2863
|
+
let data = edgeMap.get(key);
|
|
2864
|
+
if (data === undefined) {
|
|
2865
|
+
edgeMap.set(key, packed);
|
|
2866
|
+
}
|
|
2867
|
+
else if (data !== null) {
|
|
2868
|
+
edgeMap.set(key, null);
|
|
2869
|
+
}
|
|
2870
|
+
if (b < c) {
|
|
2871
|
+
key = (b << 16) | c;
|
|
2872
|
+
}
|
|
2873
|
+
else {
|
|
2874
|
+
key = (c << 16) | b;
|
|
2875
|
+
}
|
|
2876
|
+
packed = (b << 16) | c;
|
|
2877
|
+
data = edgeMap.get(key);
|
|
2878
|
+
if (data === undefined) {
|
|
2879
|
+
edgeMap.set(key, packed);
|
|
2880
|
+
}
|
|
2881
|
+
else if (data !== null) {
|
|
2882
|
+
edgeMap.set(key, null);
|
|
2883
|
+
}
|
|
2884
|
+
if (c < a) {
|
|
2885
|
+
key = (c << 16) | a;
|
|
2886
|
+
}
|
|
2887
|
+
else {
|
|
2888
|
+
key = (a << 16) | c;
|
|
2889
|
+
}
|
|
2890
|
+
packed = (c << 16) | a;
|
|
2891
|
+
data = edgeMap.get(key);
|
|
2892
|
+
if (data === undefined) {
|
|
2893
|
+
edgeMap.set(key, packed);
|
|
2894
|
+
}
|
|
2895
|
+
else if (data !== null) {
|
|
2896
|
+
edgeMap.set(key, null);
|
|
2897
|
+
}
|
|
2801
2898
|
}
|
|
2899
|
+
boundaryEdges = [];
|
|
2900
|
+
for (const packedEdge of edgeMap.values()) {
|
|
2901
|
+
if (packedEdge === null)
|
|
2902
|
+
continue;
|
|
2903
|
+
boundaryEdges.push(packedEdge >>> 16, packedEdge & 0xffff);
|
|
2904
|
+
}
|
|
2905
|
+
sideEdgeCount = boundaryEdges.length >> 1;
|
|
2802
2906
|
}
|
|
2803
2907
|
}
|
|
2804
|
-
const
|
|
2805
|
-
const sideVertexCount = depth === 0 ? 0 : sideEdgeCount * 4;
|
|
2908
|
+
const sideVertexCount = sideEdgeCount * 4;
|
|
2806
2909
|
const baseVertexCount = depth === 0 ? numPoints : numPoints * 2;
|
|
2807
2910
|
const vertexCount = baseVertexCount + sideVertexCount;
|
|
2808
2911
|
const vertices = new Float32Array(vertexCount * 3);
|
|
@@ -2812,26 +2915,24 @@ class Extruder {
|
|
|
2812
2915
|
: triangleIndices.length * 2 + sideEdgeCount * 6;
|
|
2813
2916
|
const indices = new Uint32Array(indexCount);
|
|
2814
2917
|
if (depth === 0) {
|
|
2815
|
-
let vPos = 0;
|
|
2816
|
-
|
|
2817
|
-
vertices[vPos] = points[
|
|
2818
|
-
vertices[vPos + 1] = points[i + 1];
|
|
2918
|
+
for (let p = 0, vPos = 0; p < pointLen; p += 2, vPos += 3) {
|
|
2919
|
+
vertices[vPos] = points[p];
|
|
2920
|
+
vertices[vPos + 1] = points[p + 1];
|
|
2819
2921
|
vertices[vPos + 2] = 0;
|
|
2820
2922
|
normals[vPos] = 0;
|
|
2821
2923
|
normals[vPos + 1] = 0;
|
|
2822
2924
|
normals[vPos + 2] = 1;
|
|
2823
|
-
vPos += 3;
|
|
2824
2925
|
}
|
|
2825
2926
|
indices.set(triangleIndices);
|
|
2826
2927
|
return { vertices, normals, indices };
|
|
2827
2928
|
}
|
|
2828
2929
|
const minBackOffset = unitsPerEm * 0.000025;
|
|
2829
2930
|
const backZ = depth <= minBackOffset ? minBackOffset : depth;
|
|
2830
|
-
|
|
2931
|
+
const backOffset = numPoints * 3;
|
|
2932
|
+
for (let p = 0, vi = 0, base0 = 0; p < pointLen; p += 2, vi++, base0 += 3) {
|
|
2831
2933
|
const x = points[p];
|
|
2832
2934
|
const y = points[p + 1];
|
|
2833
2935
|
// Cap at z=0
|
|
2834
|
-
const base0 = vi * 3;
|
|
2835
2936
|
vertices[base0] = x;
|
|
2836
2937
|
vertices[base0 + 1] = y;
|
|
2837
2938
|
vertices[base0 + 2] = 0;
|
|
@@ -2839,7 +2940,7 @@ class Extruder {
|
|
|
2839
2940
|
normals[base0 + 1] = 0;
|
|
2840
2941
|
normals[base0 + 2] = -1;
|
|
2841
2942
|
// Cap at z=depth
|
|
2842
|
-
const baseD =
|
|
2943
|
+
const baseD = base0 + backOffset;
|
|
2843
2944
|
vertices[baseD] = x;
|
|
2844
2945
|
vertices[baseD + 1] = y;
|
|
2845
2946
|
vertices[baseD + 2] = backZ;
|
|
@@ -2858,61 +2959,119 @@ class Extruder {
|
|
|
2858
2959
|
}
|
|
2859
2960
|
let nextVertex = numPoints * 2;
|
|
2860
2961
|
let idxPos = triLen * 2;
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2962
|
+
if (useContours) {
|
|
2963
|
+
for (const contour of contours) {
|
|
2964
|
+
const contourLen = contour.length;
|
|
2965
|
+
if (contourLen < 4)
|
|
2966
|
+
continue;
|
|
2967
|
+
for (let i = 0; i < contourLen - 2; i += 2) {
|
|
2968
|
+
const p0x = contour[i];
|
|
2969
|
+
const p0y = contour[i + 1];
|
|
2970
|
+
const p1x = contour[i + 2];
|
|
2971
|
+
const p1y = contour[i + 3];
|
|
2972
|
+
const ex = p1x - p0x;
|
|
2973
|
+
const ey = p1y - p0y;
|
|
2974
|
+
const lenSq = ex * ex + ey * ey;
|
|
2975
|
+
let nx = 0;
|
|
2976
|
+
let ny = 0;
|
|
2977
|
+
if (lenSq > 1e-10) {
|
|
2978
|
+
const invLen = 1 / Math.sqrt(lenSq);
|
|
2979
|
+
nx = ey * invLen;
|
|
2980
|
+
ny = -ex * invLen;
|
|
2981
|
+
}
|
|
2982
|
+
const base = nextVertex * 3;
|
|
2983
|
+
vertices[base] = p0x;
|
|
2984
|
+
vertices[base + 1] = p0y;
|
|
2985
|
+
vertices[base + 2] = 0;
|
|
2986
|
+
vertices[base + 3] = p1x;
|
|
2987
|
+
vertices[base + 4] = p1y;
|
|
2988
|
+
vertices[base + 5] = 0;
|
|
2989
|
+
vertices[base + 6] = p0x;
|
|
2990
|
+
vertices[base + 7] = p0y;
|
|
2991
|
+
vertices[base + 8] = backZ;
|
|
2992
|
+
vertices[base + 9] = p1x;
|
|
2993
|
+
vertices[base + 10] = p1y;
|
|
2994
|
+
vertices[base + 11] = backZ;
|
|
2995
|
+
normals[base] = nx;
|
|
2996
|
+
normals[base + 1] = ny;
|
|
2997
|
+
normals[base + 2] = 0;
|
|
2998
|
+
normals[base + 3] = nx;
|
|
2999
|
+
normals[base + 4] = ny;
|
|
3000
|
+
normals[base + 5] = 0;
|
|
3001
|
+
normals[base + 6] = nx;
|
|
3002
|
+
normals[base + 7] = ny;
|
|
3003
|
+
normals[base + 8] = 0;
|
|
3004
|
+
normals[base + 9] = nx;
|
|
3005
|
+
normals[base + 10] = ny;
|
|
3006
|
+
normals[base + 11] = 0;
|
|
3007
|
+
const baseVertex = nextVertex;
|
|
3008
|
+
indices[idxPos] = baseVertex;
|
|
3009
|
+
indices[idxPos + 1] = baseVertex + 1;
|
|
3010
|
+
indices[idxPos + 2] = baseVertex + 2;
|
|
3011
|
+
indices[idxPos + 3] = baseVertex + 1;
|
|
3012
|
+
indices[idxPos + 4] = baseVertex + 3;
|
|
3013
|
+
indices[idxPos + 5] = baseVertex + 2;
|
|
3014
|
+
idxPos += 6;
|
|
3015
|
+
nextVertex += 4;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
else {
|
|
3020
|
+
for (let e = 0; e < sideEdgeCount; e++) {
|
|
3021
|
+
const edgeIndex = e << 1;
|
|
3022
|
+
const u = boundaryEdges[edgeIndex];
|
|
3023
|
+
const v = boundaryEdges[edgeIndex + 1];
|
|
3024
|
+
const u2 = u << 1;
|
|
3025
|
+
const v2 = v << 1;
|
|
3026
|
+
const p0x = points[u2];
|
|
3027
|
+
const p0y = points[u2 + 1];
|
|
3028
|
+
const p1x = points[v2];
|
|
3029
|
+
const p1y = points[v2 + 1];
|
|
3030
|
+
const ex = p1x - p0x;
|
|
3031
|
+
const ey = p1y - p0y;
|
|
3032
|
+
const lenSq = ex * ex + ey * ey;
|
|
3033
|
+
let nx = 0;
|
|
3034
|
+
let ny = 0;
|
|
3035
|
+
if (lenSq > 1e-10) {
|
|
3036
|
+
const invLen = 1 / Math.sqrt(lenSq);
|
|
3037
|
+
nx = ey * invLen;
|
|
3038
|
+
ny = -ex * invLen;
|
|
3039
|
+
}
|
|
3040
|
+
const base = nextVertex * 3;
|
|
3041
|
+
vertices[base] = p0x;
|
|
3042
|
+
vertices[base + 1] = p0y;
|
|
3043
|
+
vertices[base + 2] = 0;
|
|
3044
|
+
vertices[base + 3] = p1x;
|
|
3045
|
+
vertices[base + 4] = p1y;
|
|
3046
|
+
vertices[base + 5] = 0;
|
|
3047
|
+
vertices[base + 6] = p0x;
|
|
3048
|
+
vertices[base + 7] = p0y;
|
|
3049
|
+
vertices[base + 8] = backZ;
|
|
3050
|
+
vertices[base + 9] = p1x;
|
|
3051
|
+
vertices[base + 10] = p1y;
|
|
3052
|
+
vertices[base + 11] = backZ;
|
|
3053
|
+
normals[base] = nx;
|
|
3054
|
+
normals[base + 1] = ny;
|
|
3055
|
+
normals[base + 2] = 0;
|
|
3056
|
+
normals[base + 3] = nx;
|
|
3057
|
+
normals[base + 4] = ny;
|
|
3058
|
+
normals[base + 5] = 0;
|
|
3059
|
+
normals[base + 6] = nx;
|
|
3060
|
+
normals[base + 7] = ny;
|
|
3061
|
+
normals[base + 8] = 0;
|
|
3062
|
+
normals[base + 9] = nx;
|
|
3063
|
+
normals[base + 10] = ny;
|
|
3064
|
+
normals[base + 11] = 0;
|
|
3065
|
+
const baseVertex = nextVertex;
|
|
3066
|
+
indices[idxPos] = baseVertex;
|
|
3067
|
+
indices[idxPos + 1] = baseVertex + 1;
|
|
3068
|
+
indices[idxPos + 2] = baseVertex + 2;
|
|
3069
|
+
indices[idxPos + 3] = baseVertex + 1;
|
|
3070
|
+
indices[idxPos + 4] = baseVertex + 3;
|
|
3071
|
+
indices[idxPos + 5] = baseVertex + 2;
|
|
3072
|
+
idxPos += 6;
|
|
3073
|
+
nextVertex += 4;
|
|
3074
|
+
}
|
|
2916
3075
|
}
|
|
2917
3076
|
return { vertices, normals, indices };
|
|
2918
3077
|
}
|
|
@@ -3899,6 +4058,9 @@ class GlyphGeometryBuilder {
|
|
|
3899
4058
|
this.fontId = 'default';
|
|
3900
4059
|
this.cacheKeyPrefix = 'default';
|
|
3901
4060
|
this.emptyGlyphs = new Set();
|
|
4061
|
+
this.clusterPositions = [];
|
|
4062
|
+
this.clusterContoursScratch = [];
|
|
4063
|
+
this.taskScratch = [];
|
|
3902
4064
|
this.cache = cache;
|
|
3903
4065
|
this.loadedFont = loadedFont;
|
|
3904
4066
|
this.tessellator = new Tessellator();
|
|
@@ -3984,14 +4146,28 @@ class GlyphGeometryBuilder {
|
|
|
3984
4146
|
else {
|
|
3985
4147
|
perfLogger.start('GlyphGeometryBuilder.buildInstancedGeometry');
|
|
3986
4148
|
}
|
|
3987
|
-
const tasks =
|
|
4149
|
+
const tasks = this.taskScratch;
|
|
4150
|
+
tasks.length = 0;
|
|
4151
|
+
let taskCount = 0;
|
|
3988
4152
|
let totalVertexFloats = 0;
|
|
3989
4153
|
let totalNormalFloats = 0;
|
|
3990
4154
|
let totalIndexCount = 0;
|
|
3991
4155
|
let vertexCursor = 0; // vertex offset (not float offset)
|
|
3992
4156
|
const pushTask = (data, px, py, pz) => {
|
|
3993
4157
|
const vertexStart = vertexCursor;
|
|
3994
|
-
|
|
4158
|
+
let task = tasks[taskCount];
|
|
4159
|
+
if (task) {
|
|
4160
|
+
task.data = data;
|
|
4161
|
+
task.px = px;
|
|
4162
|
+
task.py = py;
|
|
4163
|
+
task.pz = pz;
|
|
4164
|
+
task.vertexStart = vertexStart;
|
|
4165
|
+
}
|
|
4166
|
+
else {
|
|
4167
|
+
task = { data, px, py, pz, vertexStart };
|
|
4168
|
+
tasks[taskCount] = task;
|
|
4169
|
+
}
|
|
4170
|
+
taskCount++;
|
|
3995
4171
|
totalVertexFloats += data.vertices.length;
|
|
3996
4172
|
totalNormalFloats += data.normals.length;
|
|
3997
4173
|
totalIndexCount += data.indices.length;
|
|
@@ -4041,8 +4217,21 @@ class GlyphGeometryBuilder {
|
|
|
4041
4217
|
boundaryGroups = cached.groups;
|
|
4042
4218
|
}
|
|
4043
4219
|
else {
|
|
4044
|
-
const
|
|
4045
|
-
|
|
4220
|
+
const glyphCount = cluster.glyphs.length;
|
|
4221
|
+
if (this.clusterPositions.length < glyphCount) {
|
|
4222
|
+
for (let i = this.clusterPositions.length; i < glyphCount; i++) {
|
|
4223
|
+
this.clusterPositions.push(new Vec3(0, 0, 0));
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
this.clusterPositions.length = glyphCount;
|
|
4227
|
+
for (let i = 0; i < glyphCount; i++) {
|
|
4228
|
+
const glyph = cluster.glyphs[i];
|
|
4229
|
+
const pos = this.clusterPositions[i];
|
|
4230
|
+
pos.x = glyph.x ?? 0;
|
|
4231
|
+
pos.y = glyph.y ?? 0;
|
|
4232
|
+
pos.z = 0;
|
|
4233
|
+
}
|
|
4234
|
+
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, this.clusterPositions);
|
|
4046
4235
|
this.clusteringCache.set(cacheKey, {
|
|
4047
4236
|
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
4048
4237
|
positions: cluster.glyphs.map((g) => ({
|
|
@@ -4099,7 +4288,8 @@ class GlyphGeometryBuilder {
|
|
|
4099
4288
|
const clusterKey = this.getClusterKey(subClusterGlyphs, depth, removeOverlaps);
|
|
4100
4289
|
let cachedCluster = this.wordCache.get(clusterKey);
|
|
4101
4290
|
if (!cachedCluster) {
|
|
4102
|
-
const
|
|
4291
|
+
const clusterContours = this.clusterContoursScratch;
|
|
4292
|
+
let contourIndex = 0;
|
|
4103
4293
|
const refX = subClusterGlyphs[0].x ?? 0;
|
|
4104
4294
|
const refY = subClusterGlyphs[0].y ?? 0;
|
|
4105
4295
|
for (let i = 0; i < groupIndices.length; i++) {
|
|
@@ -4109,13 +4299,38 @@ class GlyphGeometryBuilder {
|
|
|
4109
4299
|
const relX = (glyph.x ?? 0) - refX;
|
|
4110
4300
|
const relY = (glyph.y ?? 0) - refY;
|
|
4111
4301
|
for (const path of glyphContours.paths) {
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4302
|
+
const points = path.points;
|
|
4303
|
+
const pointCount = points.length;
|
|
4304
|
+
if (pointCount < 3)
|
|
4305
|
+
continue;
|
|
4306
|
+
const isClosed = pointCount > 1 &&
|
|
4307
|
+
points[0].x === points[pointCount - 1].x &&
|
|
4308
|
+
points[0].y === points[pointCount - 1].y;
|
|
4309
|
+
const end = isClosed ? pointCount - 1 : pointCount;
|
|
4310
|
+
const needed = (end + 1) * 2;
|
|
4311
|
+
let contour = clusterContours[contourIndex];
|
|
4312
|
+
if (!contour || contour.length < needed) {
|
|
4313
|
+
contour = new Array(needed);
|
|
4314
|
+
clusterContours[contourIndex] = contour;
|
|
4315
|
+
}
|
|
4316
|
+
else {
|
|
4317
|
+
contour.length = needed;
|
|
4318
|
+
}
|
|
4319
|
+
let out = 0;
|
|
4320
|
+
for (let k = 0; k < end; k++) {
|
|
4321
|
+
const pt = points[k];
|
|
4322
|
+
contour[out++] = pt.x + relX;
|
|
4323
|
+
contour[out++] = pt.y + relY;
|
|
4324
|
+
}
|
|
4325
|
+
if (out >= 2) {
|
|
4326
|
+
contour[out++] = contour[0];
|
|
4327
|
+
contour[out++] = contour[1];
|
|
4328
|
+
}
|
|
4329
|
+
contourIndex++;
|
|
4116
4330
|
}
|
|
4117
4331
|
}
|
|
4118
|
-
|
|
4332
|
+
clusterContours.length = contourIndex;
|
|
4333
|
+
cachedCluster = this.tessellateGlyphCluster(clusterContours, depth, isCFF);
|
|
4119
4334
|
this.wordCache.set(clusterKey, cachedCluster);
|
|
4120
4335
|
}
|
|
4121
4336
|
// Calculate the absolute position of this sub-cluster based on its first glyph
|
|
@@ -4170,6 +4385,7 @@ class GlyphGeometryBuilder {
|
|
|
4170
4385
|
}
|
|
4171
4386
|
}
|
|
4172
4387
|
}
|
|
4388
|
+
tasks.length = taskCount;
|
|
4173
4389
|
// Allocate exact-sized buffers and fill once
|
|
4174
4390
|
const vertexArray = new Float32Array(totalVertexFloats);
|
|
4175
4391
|
const normalArray = new Float32Array(totalNormalFloats);
|
|
@@ -4185,17 +4401,27 @@ class GlyphGeometryBuilder {
|
|
|
4185
4401
|
const px = task.px;
|
|
4186
4402
|
const py = task.py;
|
|
4187
4403
|
const pz = task.pz;
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4404
|
+
const offsetX = px * scale;
|
|
4405
|
+
const offsetY = py * scale;
|
|
4406
|
+
const offsetZ = pz * scale;
|
|
4407
|
+
const vLen = v.length;
|
|
4408
|
+
let outPos = vertexPos;
|
|
4409
|
+
for (let j = 0; j < vLen; j += 3) {
|
|
4410
|
+
vertexArray[outPos] = v[j] * scale + offsetX;
|
|
4411
|
+
vertexArray[outPos + 1] = v[j + 1] * scale + offsetY;
|
|
4412
|
+
vertexArray[outPos + 2] = v[j + 2] * scale + offsetZ;
|
|
4413
|
+
outPos += 3;
|
|
4414
|
+
}
|
|
4415
|
+
vertexPos = outPos;
|
|
4193
4416
|
normalArray.set(n, normalPos);
|
|
4194
4417
|
normalPos += n.length;
|
|
4195
4418
|
const vertexStart = task.vertexStart;
|
|
4196
|
-
|
|
4197
|
-
|
|
4419
|
+
const idxLen = idx.length;
|
|
4420
|
+
let outIndexPos = indexPos;
|
|
4421
|
+
for (let j = 0; j < idxLen; j++) {
|
|
4422
|
+
indexArray[outIndexPos++] = idx[j] + vertexStart;
|
|
4198
4423
|
}
|
|
4424
|
+
indexPos = outIndexPos;
|
|
4199
4425
|
}
|
|
4200
4426
|
perfLogger.end('GlyphGeometryBuilder.buildInstancedGeometry');
|
|
4201
4427
|
planeBounds.min.x *= scale;
|
|
@@ -4293,8 +4519,8 @@ class GlyphGeometryBuilder {
|
|
|
4293
4519
|
this.contourCache.set(key, contours);
|
|
4294
4520
|
return contours;
|
|
4295
4521
|
}
|
|
4296
|
-
tessellateGlyphCluster(
|
|
4297
|
-
const processedGeometry = this.tessellator.
|
|
4522
|
+
tessellateGlyphCluster(contours, depth, isCFF) {
|
|
4523
|
+
const processedGeometry = this.tessellator.processContours(contours, true, isCFF, depth !== 0);
|
|
4298
4524
|
return this.extrudeAndPackage(processedGeometry, depth);
|
|
4299
4525
|
}
|
|
4300
4526
|
extrudeAndPackage(processedGeometry, depth) {
|