three-text 0.3.4 → 0.3.5
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 +2 -0
- package/dist/index.cjs +50 -8
- package/dist/index.js +50 -8
- package/dist/index.min.cjs +157 -152
- package/dist/index.min.js +157 -152
- package/dist/index.umd.js +50 -8
- package/dist/index.umd.min.js +157 -152
- package/dist/types/core/cache/sharedCaches.d.ts +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -637,6 +637,8 @@ const text = await Text.create({
|
|
|
637
637
|
|
|
638
638
|
Text matching occurs after layout processing, so patterns like "connection" will be found even if hyphenation splits them across lines. The `coloredRanges` property on the returned object contains the resolved color assignments for programmatic access to the colored parts of the geometry
|
|
639
639
|
|
|
640
|
+
When using selective coloring with `byText` or `byCharRange`, colored glyphs are kept geometrically separate from adjacent non-colored glyphs. This ensures accurate vertex coloring while still allowing overlap removal between glyphs of the same color status, e.g. two adjacent colored letters that overlap will still be properly merged
|
|
641
|
+
|
|
640
642
|
## API reference
|
|
641
643
|
|
|
642
644
|
The library's full TypeScript definitions are the most complete source of truth for the API. The core data structures and configuration options can be found in `src/core/types.ts`
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.3.
|
|
2
|
+
* three-text v0.3.5
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -3902,15 +3902,20 @@ class GlyphGeometryBuilder {
|
|
|
3902
3902
|
boundaryGroups = [[0]];
|
|
3903
3903
|
}
|
|
3904
3904
|
else {
|
|
3905
|
-
// Check clustering cache (same text + glyph IDs = same overlap groups)
|
|
3905
|
+
// Check clustering cache (same text + glyph IDs + positions = same overlap groups)
|
|
3906
3906
|
// Key must be font-specific; glyph ids/bounds differ between fonts
|
|
3907
|
+
// Positions must match since overlap detection depends on relative glyph placement
|
|
3907
3908
|
const cacheKey = `${this.cacheKeyPrefix}_${cluster.text}`;
|
|
3908
3909
|
const cached = this.clusteringCache.get(cacheKey);
|
|
3909
3910
|
let isValid = false;
|
|
3910
3911
|
if (cached && cached.glyphIds.length === cluster.glyphs.length) {
|
|
3911
3912
|
isValid = true;
|
|
3912
3913
|
for (let i = 0; i < cluster.glyphs.length; i++) {
|
|
3913
|
-
|
|
3914
|
+
const glyph = cluster.glyphs[i];
|
|
3915
|
+
const cachedPos = cached.positions[i];
|
|
3916
|
+
if (cached.glyphIds[i] !== glyph.g ||
|
|
3917
|
+
cachedPos.x !== (glyph.x ?? 0) ||
|
|
3918
|
+
cachedPos.y !== (glyph.y ?? 0)) {
|
|
3914
3919
|
isValid = false;
|
|
3915
3920
|
break;
|
|
3916
3921
|
}
|
|
@@ -3924,17 +3929,52 @@ class GlyphGeometryBuilder {
|
|
|
3924
3929
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
3925
3930
|
this.clusteringCache.set(cacheKey, {
|
|
3926
3931
|
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
3932
|
+
positions: cluster.glyphs.map((g) => ({
|
|
3933
|
+
x: g.x ?? 0,
|
|
3934
|
+
y: g.y ?? 0
|
|
3935
|
+
})),
|
|
3927
3936
|
groups: boundaryGroups
|
|
3928
3937
|
});
|
|
3929
3938
|
}
|
|
3930
3939
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
//
|
|
3934
|
-
|
|
3940
|
+
// Only force separate tessellation when explicitly requested via separateGlyphs
|
|
3941
|
+
const forceSeparate = separateGlyphs;
|
|
3942
|
+
// Split boundary groups so colored and non-colored glyphs don't merge together
|
|
3943
|
+
// This preserves overlap removal within each color class while keeping
|
|
3944
|
+
// geometry separate for accurate vertex coloring
|
|
3945
|
+
let finalGroups = boundaryGroups;
|
|
3946
|
+
if (coloredTextIndices && coloredTextIndices.size > 0) {
|
|
3947
|
+
finalGroups = [];
|
|
3948
|
+
for (const group of boundaryGroups) {
|
|
3949
|
+
if (group.length <= 1) {
|
|
3950
|
+
finalGroups.push(group);
|
|
3951
|
+
}
|
|
3952
|
+
else {
|
|
3953
|
+
// Split group into colored and non-colored sub-groups
|
|
3954
|
+
const coloredIndices = [];
|
|
3955
|
+
const nonColoredIndices = [];
|
|
3956
|
+
for (const idx of group) {
|
|
3957
|
+
const glyph = cluster.glyphs[idx];
|
|
3958
|
+
if (coloredTextIndices.has(glyph.absoluteTextIndex)) {
|
|
3959
|
+
coloredIndices.push(idx);
|
|
3960
|
+
}
|
|
3961
|
+
else {
|
|
3962
|
+
nonColoredIndices.push(idx);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
// Add non-empty sub-groups
|
|
3966
|
+
if (coloredIndices.length > 0) {
|
|
3967
|
+
finalGroups.push(coloredIndices);
|
|
3968
|
+
}
|
|
3969
|
+
if (nonColoredIndices.length > 0) {
|
|
3970
|
+
finalGroups.push(nonColoredIndices);
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3935
3975
|
// Iterate over the geometric groups identified by BoundaryClusterer
|
|
3936
3976
|
// logical groups (words) split into geometric sub-groups
|
|
3937
|
-
for (const groupIndices of
|
|
3977
|
+
for (const groupIndices of finalGroups) {
|
|
3938
3978
|
const isOverlappingGroup = groupIndices.length > 1;
|
|
3939
3979
|
const shouldCluster = isOverlappingGroup && !forceSeparate;
|
|
3940
3980
|
if (shouldCluster) {
|
|
@@ -5595,6 +5635,7 @@ class Text {
|
|
|
5595
5635
|
else {
|
|
5596
5636
|
lineGroups.set(glyph.lineIndex, [glyph]);
|
|
5597
5637
|
}
|
|
5638
|
+
// Color vertices owned by this glyph
|
|
5598
5639
|
for (let v = 0; v < glyph.vertexCount; v++) {
|
|
5599
5640
|
const vertexIndex = (glyph.vertexStart + v) * 3;
|
|
5600
5641
|
if (vertexIndex >= 0 && vertexIndex < colors.length) {
|
|
@@ -5636,6 +5677,7 @@ class Text {
|
|
|
5636
5677
|
else {
|
|
5637
5678
|
lineGroups.set(glyph.lineIndex, [glyph]);
|
|
5638
5679
|
}
|
|
5680
|
+
// Color vertices owned by this glyph
|
|
5639
5681
|
for (let v = 0; v < glyph.vertexCount; v++) {
|
|
5640
5682
|
const vertexIndex = (glyph.vertexStart + v) * 3;
|
|
5641
5683
|
if (vertexIndex >= 0 && vertexIndex < colors.length) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.3.
|
|
2
|
+
* three-text v0.3.5
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -3899,15 +3899,20 @@ class GlyphGeometryBuilder {
|
|
|
3899
3899
|
boundaryGroups = [[0]];
|
|
3900
3900
|
}
|
|
3901
3901
|
else {
|
|
3902
|
-
// Check clustering cache (same text + glyph IDs = same overlap groups)
|
|
3902
|
+
// Check clustering cache (same text + glyph IDs + positions = same overlap groups)
|
|
3903
3903
|
// Key must be font-specific; glyph ids/bounds differ between fonts
|
|
3904
|
+
// Positions must match since overlap detection depends on relative glyph placement
|
|
3904
3905
|
const cacheKey = `${this.cacheKeyPrefix}_${cluster.text}`;
|
|
3905
3906
|
const cached = this.clusteringCache.get(cacheKey);
|
|
3906
3907
|
let isValid = false;
|
|
3907
3908
|
if (cached && cached.glyphIds.length === cluster.glyphs.length) {
|
|
3908
3909
|
isValid = true;
|
|
3909
3910
|
for (let i = 0; i < cluster.glyphs.length; i++) {
|
|
3910
|
-
|
|
3911
|
+
const glyph = cluster.glyphs[i];
|
|
3912
|
+
const cachedPos = cached.positions[i];
|
|
3913
|
+
if (cached.glyphIds[i] !== glyph.g ||
|
|
3914
|
+
cachedPos.x !== (glyph.x ?? 0) ||
|
|
3915
|
+
cachedPos.y !== (glyph.y ?? 0)) {
|
|
3911
3916
|
isValid = false;
|
|
3912
3917
|
break;
|
|
3913
3918
|
}
|
|
@@ -3921,17 +3926,52 @@ class GlyphGeometryBuilder {
|
|
|
3921
3926
|
boundaryGroups = this.clusterer.cluster(clusterGlyphContours, relativePositions);
|
|
3922
3927
|
this.clusteringCache.set(cacheKey, {
|
|
3923
3928
|
glyphIds: cluster.glyphs.map((g) => g.g),
|
|
3929
|
+
positions: cluster.glyphs.map((g) => ({
|
|
3930
|
+
x: g.x ?? 0,
|
|
3931
|
+
y: g.y ?? 0
|
|
3932
|
+
})),
|
|
3924
3933
|
groups: boundaryGroups
|
|
3925
3934
|
});
|
|
3926
3935
|
}
|
|
3927
3936
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
//
|
|
3931
|
-
|
|
3937
|
+
// Only force separate tessellation when explicitly requested via separateGlyphs
|
|
3938
|
+
const forceSeparate = separateGlyphs;
|
|
3939
|
+
// Split boundary groups so colored and non-colored glyphs don't merge together
|
|
3940
|
+
// This preserves overlap removal within each color class while keeping
|
|
3941
|
+
// geometry separate for accurate vertex coloring
|
|
3942
|
+
let finalGroups = boundaryGroups;
|
|
3943
|
+
if (coloredTextIndices && coloredTextIndices.size > 0) {
|
|
3944
|
+
finalGroups = [];
|
|
3945
|
+
for (const group of boundaryGroups) {
|
|
3946
|
+
if (group.length <= 1) {
|
|
3947
|
+
finalGroups.push(group);
|
|
3948
|
+
}
|
|
3949
|
+
else {
|
|
3950
|
+
// Split group into colored and non-colored sub-groups
|
|
3951
|
+
const coloredIndices = [];
|
|
3952
|
+
const nonColoredIndices = [];
|
|
3953
|
+
for (const idx of group) {
|
|
3954
|
+
const glyph = cluster.glyphs[idx];
|
|
3955
|
+
if (coloredTextIndices.has(glyph.absoluteTextIndex)) {
|
|
3956
|
+
coloredIndices.push(idx);
|
|
3957
|
+
}
|
|
3958
|
+
else {
|
|
3959
|
+
nonColoredIndices.push(idx);
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
// Add non-empty sub-groups
|
|
3963
|
+
if (coloredIndices.length > 0) {
|
|
3964
|
+
finalGroups.push(coloredIndices);
|
|
3965
|
+
}
|
|
3966
|
+
if (nonColoredIndices.length > 0) {
|
|
3967
|
+
finalGroups.push(nonColoredIndices);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3932
3972
|
// Iterate over the geometric groups identified by BoundaryClusterer
|
|
3933
3973
|
// logical groups (words) split into geometric sub-groups
|
|
3934
|
-
for (const groupIndices of
|
|
3974
|
+
for (const groupIndices of finalGroups) {
|
|
3935
3975
|
const isOverlappingGroup = groupIndices.length > 1;
|
|
3936
3976
|
const shouldCluster = isOverlappingGroup && !forceSeparate;
|
|
3937
3977
|
if (shouldCluster) {
|
|
@@ -5592,6 +5632,7 @@ class Text {
|
|
|
5592
5632
|
else {
|
|
5593
5633
|
lineGroups.set(glyph.lineIndex, [glyph]);
|
|
5594
5634
|
}
|
|
5635
|
+
// Color vertices owned by this glyph
|
|
5595
5636
|
for (let v = 0; v < glyph.vertexCount; v++) {
|
|
5596
5637
|
const vertexIndex = (glyph.vertexStart + v) * 3;
|
|
5597
5638
|
if (vertexIndex >= 0 && vertexIndex < colors.length) {
|
|
@@ -5633,6 +5674,7 @@ class Text {
|
|
|
5633
5674
|
else {
|
|
5634
5675
|
lineGroups.set(glyph.lineIndex, [glyph]);
|
|
5635
5676
|
}
|
|
5677
|
+
// Color vertices owned by this glyph
|
|
5636
5678
|
for (let v = 0; v < glyph.vertexCount; v++) {
|
|
5637
5679
|
const vertexIndex = (glyph.vertexStart + v) * 3;
|
|
5638
5680
|
if (vertexIndex >= 0 && vertexIndex < colors.length) {
|