three-text 0.2.11 → 0.2.13
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 +1 -1
- package/dist/index.cjs +168 -83
- package/dist/index.js +168 -83
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +168 -83
- package/dist/index.umd.min.js +2 -2
- package/dist/types/core/cache/GlyphGeometryBuilder.d.ts +3 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -317,7 +317,7 @@ The library uses a hybrid caching strategy to maximize performance while ensurin
|
|
|
317
317
|
|
|
318
318
|
By default, it operates with glyph-level cache. The geometry for each unique character (`a`, `b`, `c`...) is generated only once and stored for reuse, avoiding redundant computation
|
|
319
319
|
|
|
320
|
-
For text with tight tracking, connected scripts, or complex kerning pairs, individual glyphs can overlap.
|
|
320
|
+
For text with tight tracking, connected scripts, or complex kerning pairs, individual glyphs can overlap. The system detects overlaps within each word and handles them at the sub-cluster level: only the specific glyphs that overlap are tessellated together as a group, while non-overlapping glyphs in the same word continue to use individual glyph caching
|
|
321
321
|
|
|
322
322
|
|
|
323
323
|
#### Flat geometry mode
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.13
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -2586,7 +2586,6 @@ class Extruder {
|
|
|
2586
2586
|
vertices.push(points[i], points[i + 1], 0);
|
|
2587
2587
|
normals.push(0, 0, -1);
|
|
2588
2588
|
}
|
|
2589
|
-
// Add triangle indices
|
|
2590
2589
|
for (let i = 0; i < triangleIndices.length; i++) {
|
|
2591
2590
|
indices.push(baseIndex + triangleIndices[i]);
|
|
2592
2591
|
}
|
|
@@ -2637,18 +2636,21 @@ class BoundaryClusterer {
|
|
|
2637
2636
|
perfLogger.start('BoundaryClusterer.cluster', {
|
|
2638
2637
|
glyphCount: glyphContoursList.length
|
|
2639
2638
|
});
|
|
2640
|
-
|
|
2639
|
+
const n = glyphContoursList.length;
|
|
2640
|
+
if (n === 0) {
|
|
2641
2641
|
perfLogger.end('BoundaryClusterer.cluster');
|
|
2642
2642
|
return [];
|
|
2643
2643
|
}
|
|
2644
|
+
if (n === 1) {
|
|
2645
|
+
perfLogger.end('BoundaryClusterer.cluster');
|
|
2646
|
+
return [[0]];
|
|
2647
|
+
}
|
|
2644
2648
|
const result = this.clusterSweepLine(glyphContoursList, positions);
|
|
2645
2649
|
perfLogger.end('BoundaryClusterer.cluster');
|
|
2646
2650
|
return result;
|
|
2647
2651
|
}
|
|
2648
2652
|
clusterSweepLine(glyphContoursList, positions) {
|
|
2649
2653
|
const n = glyphContoursList.length;
|
|
2650
|
-
if (n <= 1)
|
|
2651
|
-
return n === 0 ? [] : [[0]];
|
|
2652
2654
|
const bounds = new Array(n);
|
|
2653
2655
|
const events = new Array(2 * n);
|
|
2654
2656
|
let eventIndex = 0;
|
|
@@ -2668,7 +2670,6 @@ class BoundaryClusterer {
|
|
|
2668
2670
|
const py = find(y);
|
|
2669
2671
|
if (px === py)
|
|
2670
2672
|
return;
|
|
2671
|
-
// Union by rank, attach smaller tree under larger tree
|
|
2672
2673
|
if (rank[px] < rank[py]) {
|
|
2673
2674
|
parent[px] = py;
|
|
2674
2675
|
}
|
|
@@ -2684,8 +2685,6 @@ class BoundaryClusterer {
|
|
|
2684
2685
|
for (const [, eventType, glyphIndex] of events) {
|
|
2685
2686
|
if (eventType === 0) {
|
|
2686
2687
|
const bounds1 = bounds[glyphIndex];
|
|
2687
|
-
// Check y-overlap with all currently active glyphs
|
|
2688
|
-
// (x-overlap is guaranteed by the sweep line)
|
|
2689
2688
|
for (const activeIndex of active) {
|
|
2690
2689
|
const bounds2 = bounds[activeIndex];
|
|
2691
2690
|
if (bounds1.minY < bounds2.maxY + OVERLAP_EPSILON &&
|
|
@@ -2702,10 +2701,12 @@ class BoundaryClusterer {
|
|
|
2702
2701
|
const clusters = new Map();
|
|
2703
2702
|
for (let i = 0; i < n; i++) {
|
|
2704
2703
|
const root = find(i);
|
|
2705
|
-
|
|
2706
|
-
|
|
2704
|
+
let list = clusters.get(root);
|
|
2705
|
+
if (!list) {
|
|
2706
|
+
list = [];
|
|
2707
|
+
clusters.set(root, list);
|
|
2707
2708
|
}
|
|
2708
|
-
|
|
2709
|
+
list.push(i);
|
|
2709
2710
|
}
|
|
2710
2711
|
return Array.from(clusters.values());
|
|
2711
2712
|
}
|
|
@@ -3758,6 +3759,10 @@ class GlyphGeometryBuilder {
|
|
|
3758
3759
|
return size;
|
|
3759
3760
|
}
|
|
3760
3761
|
});
|
|
3762
|
+
this.clusteringCache = new LRUCache({
|
|
3763
|
+
maxEntries: 2000,
|
|
3764
|
+
calculateSize: () => 1
|
|
3765
|
+
});
|
|
3761
3766
|
}
|
|
3762
3767
|
getOptimizationStats() {
|
|
3763
3768
|
return this.collector.getOptimizationStats();
|
|
@@ -3772,7 +3777,7 @@ class GlyphGeometryBuilder {
|
|
|
3772
3777
|
this.fontId = fontId;
|
|
3773
3778
|
}
|
|
3774
3779
|
// Build instanced geometry from glyph contours
|
|
3775
|
-
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, separateGlyphs = false) {
|
|
3780
|
+
buildInstancedGeometry(clustersByLine, depth, removeOverlaps, isCFF, separateGlyphs = false, coloredTextIndices) {
|
|
3776
3781
|
perfLogger.start('GlyphGeometryBuilder.buildInstancedGeometry', {
|
|
3777
3782
|
lineCount: clustersByLine.length,
|
|
3778
3783
|
wordCount: clustersByLine.flat().length,
|
|
@@ -3796,64 +3801,115 @@ class GlyphGeometryBuilder {
|
|
|
3796
3801
|
clusterGlyphContours.push(this.getContoursForGlyph(glyph.g));
|
|
3797
3802
|
}
|
|
3798
3803
|
// Step 2: Check for overlaps within the cluster
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
const
|
|
3807
|
-
let
|
|
3808
|
-
if (
|
|
3809
|
-
|
|
3810
|
-
for (let i = 0; i <
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
clusterPaths.push({
|
|
3815
|
-
...path,
|
|
3816
|
-
points: path.points.map((p) => new Vec2(p.x + (glyph.x ?? 0), p.y + (glyph.y ?? 0)))
|
|
3817
|
-
});
|
|
3804
|
+
let boundaryGroups;
|
|
3805
|
+
if (cluster.glyphs.length <= 1) {
|
|
3806
|
+
boundaryGroups = [[0]];
|
|
3807
|
+
}
|
|
3808
|
+
else {
|
|
3809
|
+
// Check clustering cache (same text + glyph IDs = same overlap groups)
|
|
3810
|
+
const cacheKey = cluster.text;
|
|
3811
|
+
const cached = this.clusteringCache.get(cacheKey);
|
|
3812
|
+
let isValid = false;
|
|
3813
|
+
if (cached && cached.glyphIds.length === cluster.glyphs.length) {
|
|
3814
|
+
isValid = true;
|
|
3815
|
+
for (let i = 0; i < cluster.glyphs.length; i++) {
|
|
3816
|
+
if (cached.glyphIds[i] !== cluster.glyphs[i].g) {
|
|
3817
|
+
isValid = false;
|
|
3818
|
+
break;
|
|
3818
3819
|
}
|
|
3819
3820
|
}
|
|
3820
|
-
cachedCluster = this.tessellateGlyphCluster(clusterPaths, depth, isCFF);
|
|
3821
|
-
this.wordCache.set(clusterKey, cachedCluster);
|
|
3822
3821
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
const
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3822
|
+
if (isValid && cached) {
|
|
3823
|
+
boundaryGroups = cached.groups;
|
|
3824
|
+
}
|
|
3825
|
+
else {
|
|
3826
|
+
const relativePositions = cluster.glyphs.map((g) => new Vec3(g.x, g.y, 0));
|
|
3827
|
+
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
3828
|
+
this.clusteringCache.set(cacheKey, {
|
|
3829
|
+
glyphIds: cluster.glyphs.map(g => g.g),
|
|
3830
|
+
groups: boundaryGroups
|
|
3831
|
+
});
|
|
3833
3832
|
}
|
|
3834
3833
|
}
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3834
|
+
const clusterHasColoredGlyphs = coloredTextIndices &&
|
|
3835
|
+
cluster.glyphs.some((g) => coloredTextIndices.has(g.absoluteTextIndex));
|
|
3836
|
+
// Force glyph-level caching if:
|
|
3837
|
+
// - separateGlyphs flag is set (for shader attributes), OR
|
|
3838
|
+
// - cluster contains selectively colored text (needs separate vertex ranges per glyph)
|
|
3839
|
+
const forceSeparate = separateGlyphs || clusterHasColoredGlyphs;
|
|
3840
|
+
// Iterate over the geometric groups identified by BoundaryClusterer
|
|
3841
|
+
// logical groups (words) are now split into geometric sub-groups (e.g. "aa", "XX", "bb")
|
|
3842
|
+
for (const groupIndices of boundaryGroups) {
|
|
3843
|
+
const isOverlappingGroup = groupIndices.length > 1;
|
|
3844
|
+
const shouldCluster = isOverlappingGroup && !forceSeparate;
|
|
3845
|
+
if (shouldCluster) {
|
|
3846
|
+
// Cluster-level caching for this specific group of overlapping glyphs
|
|
3847
|
+
const subClusterGlyphs = groupIndices.map((i) => cluster.glyphs[i]);
|
|
3848
|
+
const clusterKey = this.getClusterKey(subClusterGlyphs, depth, removeOverlaps);
|
|
3849
|
+
let cachedCluster = this.wordCache.get(clusterKey);
|
|
3850
|
+
if (!cachedCluster) {
|
|
3851
|
+
const clusterPaths = [];
|
|
3852
|
+
const refX = subClusterGlyphs[0].x ?? 0;
|
|
3853
|
+
const refY = subClusterGlyphs[0].y ?? 0;
|
|
3854
|
+
for (let i = 0; i < groupIndices.length; i++) {
|
|
3855
|
+
const originalIndex = groupIndices[i];
|
|
3856
|
+
const glyphContours = clusterGlyphContours[originalIndex];
|
|
3857
|
+
const glyph = cluster.glyphs[originalIndex];
|
|
3858
|
+
// Position relative to the sub-cluster start
|
|
3859
|
+
const relX = (glyph.x ?? 0) - refX;
|
|
3860
|
+
const relY = (glyph.y ?? 0) - refY;
|
|
3861
|
+
for (const path of glyphContours.paths) {
|
|
3862
|
+
clusterPaths.push({
|
|
3863
|
+
...path,
|
|
3864
|
+
points: path.points.map((p) => new Vec2(p.x + relX, p.y + relY))
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
cachedCluster = this.tessellateGlyphCluster(clusterPaths, depth, isCFF);
|
|
3869
|
+
this.wordCache.set(clusterKey, cachedCluster);
|
|
3870
|
+
}
|
|
3871
|
+
// Calculate the absolute position of this sub-cluster based on its first glyph
|
|
3872
|
+
// (since the cached geometry is relative to that first glyph)
|
|
3873
|
+
const firstGlyphInGroup = subClusterGlyphs[0];
|
|
3874
|
+
const groupPosition = new Vec3(cluster.position.x + (firstGlyphInGroup.x ?? 0), cluster.position.y + (firstGlyphInGroup.y ?? 0), cluster.position.z);
|
|
3875
|
+
const vertexOffset = vertices.length / 3;
|
|
3876
|
+
this.appendGeometry(vertices, normals, indices, cachedCluster, groupPosition, vertexOffset);
|
|
3877
|
+
const clusterVertexCount = cachedCluster.vertices.length / 3;
|
|
3878
|
+
// Register glyph infos for all glyphs in this sub-cluster
|
|
3879
|
+
// They all point to the same merged geometry
|
|
3880
|
+
for (let i = 0; i < groupIndices.length; i++) {
|
|
3881
|
+
const originalIndex = groupIndices[i];
|
|
3882
|
+
const glyph = cluster.glyphs[originalIndex];
|
|
3883
|
+
const glyphContours = clusterGlyphContours[originalIndex];
|
|
3884
|
+
const absoluteGlyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
|
|
3885
|
+
const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, clusterVertexCount, absoluteGlyphPosition, glyphContours, depth);
|
|
3844
3886
|
glyphInfos.push(glyphInfo);
|
|
3845
|
-
|
|
3887
|
+
this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
|
|
3846
3888
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3889
|
+
}
|
|
3890
|
+
else {
|
|
3891
|
+
// Glyph-level caching (standard path for isolated glyphs or when forced separate)
|
|
3892
|
+
for (const i of groupIndices) {
|
|
3893
|
+
const glyph = cluster.glyphs[i];
|
|
3894
|
+
const glyphContours = clusterGlyphContours[i];
|
|
3895
|
+
const glyphPosition = new Vec3(cluster.position.x + (glyph.x ?? 0), cluster.position.y + (glyph.y ?? 0), cluster.position.z);
|
|
3896
|
+
// Skip glyphs with no paths (spaces, zero-width characters, etc.)
|
|
3897
|
+
if (glyphContours.paths.length === 0) {
|
|
3898
|
+
const glyphInfo = this.createGlyphInfo(glyph, 0, 0, glyphPosition, glyphContours, depth);
|
|
3899
|
+
glyphInfos.push(glyphInfo);
|
|
3900
|
+
continue;
|
|
3901
|
+
}
|
|
3902
|
+
let cachedGlyph = this.cache.get(this.fontId, glyph.g, depth, removeOverlaps);
|
|
3903
|
+
if (!cachedGlyph) {
|
|
3904
|
+
cachedGlyph = this.tessellateGlyph(glyphContours, depth, removeOverlaps, isCFF);
|
|
3905
|
+
this.cache.set(this.fontId, glyph.g, depth, removeOverlaps, cachedGlyph);
|
|
3906
|
+
}
|
|
3907
|
+
const vertexOffset = vertices.length / 3;
|
|
3908
|
+
this.appendGeometry(vertices, normals, indices, cachedGlyph, glyphPosition, vertexOffset);
|
|
3909
|
+
const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, cachedGlyph.vertices.length / 3, glyphPosition, glyphContours, depth);
|
|
3910
|
+
glyphInfos.push(glyphInfo);
|
|
3911
|
+
this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
|
|
3851
3912
|
}
|
|
3852
|
-
const vertexOffset = vertices.length / 3;
|
|
3853
|
-
this.appendGeometry(vertices, normals, indices, cachedGlyph, glyphPosition, vertexOffset);
|
|
3854
|
-
const glyphInfo = this.createGlyphInfo(glyph, vertexOffset, cachedGlyph.vertices.length / 3, glyphPosition, glyphContours, depth);
|
|
3855
|
-
glyphInfos.push(glyphInfo);
|
|
3856
|
-
this.updatePlaneBounds(glyphInfo.bounds, planeBounds);
|
|
3857
3913
|
}
|
|
3858
3914
|
}
|
|
3859
3915
|
}
|
|
@@ -3870,6 +3926,21 @@ class GlyphGeometryBuilder {
|
|
|
3870
3926
|
planeBounds
|
|
3871
3927
|
};
|
|
3872
3928
|
}
|
|
3929
|
+
getClusterKey(glyphs, depth, removeOverlaps) {
|
|
3930
|
+
if (glyphs.length === 0)
|
|
3931
|
+
return '';
|
|
3932
|
+
// Normalize positions relative to the first glyph in the cluster
|
|
3933
|
+
const refX = glyphs[0].x ?? 0;
|
|
3934
|
+
const refY = glyphs[0].y ?? 0;
|
|
3935
|
+
const parts = glyphs.map((g) => {
|
|
3936
|
+
const relX = (g.x ?? 0) - refX;
|
|
3937
|
+
const relY = (g.y ?? 0) - refY;
|
|
3938
|
+
return `${g.g}:${relX},${relY}`;
|
|
3939
|
+
});
|
|
3940
|
+
const ids = parts.join('|');
|
|
3941
|
+
const roundedDepth = Math.round(depth * 1000) / 1000;
|
|
3942
|
+
return `${this.fontId}_${ids}_${roundedDepth}_${removeOverlaps}`;
|
|
3943
|
+
}
|
|
3873
3944
|
appendGeometry(vertices, normals, indices, data, position, offset) {
|
|
3874
3945
|
for (let j = 0; j < data.vertices.length; j += 3) {
|
|
3875
3946
|
vertices.push(data.vertices[j] + position.x, data.vertices[j + 1] + position.y, data.vertices[j + 2] + position.z);
|
|
@@ -3994,6 +4065,7 @@ class GlyphGeometryBuilder {
|
|
|
3994
4065
|
clearCache() {
|
|
3995
4066
|
this.cache.clear();
|
|
3996
4067
|
this.wordCache.clear();
|
|
4068
|
+
this.clusteringCache.clear();
|
|
3997
4069
|
}
|
|
3998
4070
|
}
|
|
3999
4071
|
|
|
@@ -4008,30 +4080,14 @@ class TextShaper {
|
|
|
4008
4080
|
perfLogger.start('TextShaper.shapeLines', {
|
|
4009
4081
|
lineCount: lineInfos.length
|
|
4010
4082
|
});
|
|
4011
|
-
// Calculate color boundaries once for the entire text before line processing
|
|
4012
|
-
const colorBoundaries = new Set();
|
|
4013
|
-
if (color &&
|
|
4014
|
-
typeof color === 'object' &&
|
|
4015
|
-
'byText' in color &&
|
|
4016
|
-
color.byText &&
|
|
4017
|
-
originalText) {
|
|
4018
|
-
for (const textToColor of Object.keys(color.byText)) {
|
|
4019
|
-
let index = 0;
|
|
4020
|
-
while ((index = originalText.indexOf(textToColor, index)) !== -1) {
|
|
4021
|
-
colorBoundaries.add(index);
|
|
4022
|
-
colorBoundaries.add(index + textToColor.length);
|
|
4023
|
-
index += textToColor.length;
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
4083
|
const clustersByLine = [];
|
|
4028
4084
|
lineInfos.forEach((lineInfo, lineIndex) => {
|
|
4029
|
-
const clusters = this.shapeLineIntoClusters(lineInfo, lineIndex, scaledLineHeight, letterSpacing, align, direction
|
|
4085
|
+
const clusters = this.shapeLineIntoClusters(lineInfo, lineIndex, scaledLineHeight, letterSpacing, align, direction);
|
|
4030
4086
|
clustersByLine.push(clusters);
|
|
4031
4087
|
});
|
|
4032
4088
|
return clustersByLine;
|
|
4033
4089
|
}
|
|
4034
|
-
shapeLineIntoClusters(lineInfo, lineIndex, scaledLineHeight, letterSpacing, align, direction
|
|
4090
|
+
shapeLineIntoClusters(lineInfo, lineIndex, scaledLineHeight, letterSpacing, align, direction) {
|
|
4035
4091
|
const buffer = this.loadedFont.hb.createBuffer();
|
|
4036
4092
|
if (direction === 'rtl') {
|
|
4037
4093
|
buffer.setDirection('rtl');
|
|
@@ -4064,8 +4120,9 @@ class TextShaper {
|
|
|
4064
4120
|
glyph.absoluteTextIndex = lineInfo.originalStart + glyph.cl;
|
|
4065
4121
|
}
|
|
4066
4122
|
glyph.lineIndex = lineIndex;
|
|
4067
|
-
|
|
4068
|
-
|
|
4123
|
+
// Cluster boundaries are based on whitespace only.
|
|
4124
|
+
// Coloring is applied later via vertex colors and must never affect shaping/kerning.
|
|
4125
|
+
if (isWhitespace) {
|
|
4069
4126
|
if (currentClusterGlyphs.length > 0) {
|
|
4070
4127
|
clusters.push({
|
|
4071
4128
|
text: currentClusterText,
|
|
@@ -5252,7 +5309,35 @@ class Text {
|
|
|
5252
5309
|
// Allow manual override via options.removeOverlaps
|
|
5253
5310
|
const shouldRemoveOverlaps = options.removeOverlaps ?? this.loadedFont.isVariable ?? false;
|
|
5254
5311
|
const clustersByLine = this.textShaper.shapeLines(layoutData.lines, layoutData.scaledLineHeight, layoutData.letterSpacing, layoutData.align, layoutData.direction, options.color, options.text);
|
|
5255
|
-
|
|
5312
|
+
// Pre-compute which character indices will be colored. This allows geometry building
|
|
5313
|
+
// to selectively use glyph-level caching (separate vertices) only for clusters containing
|
|
5314
|
+
// colored text, while non-colored clusters can still use fast cluster-level merging
|
|
5315
|
+
let coloredTextIndices;
|
|
5316
|
+
if (options.color && typeof options.color === 'object' && !Array.isArray(options.color)) {
|
|
5317
|
+
if (options.color.byText || options.color.byCharRange) {
|
|
5318
|
+
// Build the set manually since glyphs don't exist yet
|
|
5319
|
+
coloredTextIndices = new Set();
|
|
5320
|
+
if (options.color.byText) {
|
|
5321
|
+
for (const pattern of Object.keys(options.color.byText)) {
|
|
5322
|
+
let index = 0;
|
|
5323
|
+
while ((index = options.text.indexOf(pattern, index)) !== -1) {
|
|
5324
|
+
for (let i = index; i < index + pattern.length; i++) {
|
|
5325
|
+
coloredTextIndices.add(i);
|
|
5326
|
+
}
|
|
5327
|
+
index += pattern.length;
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
if (options.color.byCharRange) {
|
|
5332
|
+
for (const range of options.color.byCharRange) {
|
|
5333
|
+
for (let i = range.start; i < range.end; i++) {
|
|
5334
|
+
coloredTextIndices.add(i);
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
const shapedResult = this.geometryBuilder.buildInstancedGeometry(clustersByLine, layoutData.depth, shouldRemoveOverlaps, this.loadedFont.metrics.isCFF, options.separateGlyphsWithAttributes || false, coloredTextIndices);
|
|
5256
5341
|
const cacheStats = this.geometryBuilder.getCacheStats();
|
|
5257
5342
|
const result = this.finalizeGeometry(shapedResult.vertices, shapedResult.normals, shapedResult.indices, shapedResult.glyphInfos, shapedResult.planeBounds, options, cacheStats, options.text);
|
|
5258
5343
|
if (options.separateGlyphsWithAttributes) {
|