three-text 0.3.2 → 0.3.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * three-text v0.3.2
2
+ * three-text v0.3.3
3
3
  * Copyright (C) 2025 Countertype LLC
4
4
  *
5
5
  * This program is free software: you can redistribute it and/or modify
@@ -5867,8 +5867,10 @@ class Text {
5867
5867
  colors[i + 1] = defaultColor[1];
5868
5868
  colors[i + 2] = defaultColor[2];
5869
5869
  }
5870
- if (color.byText && byTextMatches) {
5871
- const glyphsByTextIndex = new Map();
5870
+ // Build glyph index once for both byText and byCharRange
5871
+ let glyphsByTextIndex;
5872
+ if ((color.byText && byTextMatches) || color.byCharRange) {
5873
+ glyphsByTextIndex = new Map();
5872
5874
  for (const glyph of glyphInfoArray) {
5873
5875
  const existing = glyphsByTextIndex.get(glyph.textIndex);
5874
5876
  if (existing) {
@@ -5878,18 +5880,26 @@ class Text {
5878
5880
  glyphsByTextIndex.set(glyph.textIndex, [glyph]);
5879
5881
  }
5880
5882
  }
5883
+ }
5884
+ if (color.byText && byTextMatches && glyphsByTextIndex) {
5881
5885
  for (const match of byTextMatches) {
5882
5886
  const targetColor = color.byText[match.pattern];
5883
5887
  if (!targetColor)
5884
5888
  continue;
5885
5889
  const matchGlyphs = [];
5886
- const lineIndicesSet = new Set();
5890
+ const lineGroups = new Map();
5887
5891
  for (let i = match.start; i < match.end; i++) {
5888
5892
  const glyphs = glyphsByTextIndex.get(i);
5889
5893
  if (glyphs) {
5890
5894
  for (const glyph of glyphs) {
5891
5895
  matchGlyphs.push(glyph);
5892
- lineIndicesSet.add(glyph.lineIndex);
5896
+ const lineGlyphs = lineGroups.get(glyph.lineIndex);
5897
+ if (lineGlyphs) {
5898
+ lineGlyphs.push(glyph);
5899
+ }
5900
+ else {
5901
+ lineGroups.set(glyph.lineIndex, [glyph]);
5902
+ }
5893
5903
  for (let v = 0; v < glyph.vertexCount; v++) {
5894
5904
  const vertexIndex = (glyph.vertexStart + v) * 3;
5895
5905
  if (vertexIndex >= 0 && vertexIndex < colors.length) {
@@ -5901,48 +5911,91 @@ class Text {
5901
5911
  }
5902
5912
  }
5903
5913
  }
5914
+ // Calculate bounds per line for collision detection
5915
+ const bounds = Array.from(lineGroups.values()).map((lineGlyphs) => this.calculateGlyphBounds(lineGlyphs));
5904
5916
  coloredRanges.push({
5905
5917
  start: match.start,
5906
5918
  end: match.end,
5907
5919
  originalText: match.pattern,
5908
5920
  color: targetColor,
5909
- bounds: [],
5921
+ bounds,
5910
5922
  glyphs: matchGlyphs,
5911
- lineIndices: Array.from(lineIndicesSet).sort((a, b) => a - b)
5923
+ lineIndices: Array.from(lineGroups.keys()).sort((a, b) => a - b)
5912
5924
  });
5913
5925
  }
5914
5926
  }
5915
5927
  // Apply range coloring
5916
- if (color.byCharRange) {
5917
- color.byCharRange.forEach((range) => {
5928
+ if (color.byCharRange && glyphsByTextIndex) {
5929
+ for (const range of color.byCharRange) {
5918
5930
  const rangeGlyphs = [];
5919
- for (const glyph of glyphInfoArray) {
5920
- if (glyph.textIndex >= range.start && glyph.textIndex < range.end) {
5921
- rangeGlyphs.push(glyph);
5922
- for (let i = 0; i < glyph.vertexCount; i++) {
5923
- const vertexIndex = (glyph.vertexStart + i) * 3;
5924
- if (vertexIndex >= 0 && vertexIndex < colors.length) {
5925
- colors[vertexIndex] = range.color[0];
5926
- colors[vertexIndex + 1] = range.color[1];
5927
- colors[vertexIndex + 2] = range.color[2];
5931
+ const lineGroups = new Map();
5932
+ for (let i = range.start; i < range.end; i++) {
5933
+ const glyphs = glyphsByTextIndex.get(i);
5934
+ if (glyphs) {
5935
+ for (const glyph of glyphs) {
5936
+ rangeGlyphs.push(glyph);
5937
+ const lineGlyphs = lineGroups.get(glyph.lineIndex);
5938
+ if (lineGlyphs) {
5939
+ lineGlyphs.push(glyph);
5940
+ }
5941
+ else {
5942
+ lineGroups.set(glyph.lineIndex, [glyph]);
5943
+ }
5944
+ for (let v = 0; v < glyph.vertexCount; v++) {
5945
+ const vertexIndex = (glyph.vertexStart + v) * 3;
5946
+ if (vertexIndex >= 0 && vertexIndex < colors.length) {
5947
+ colors[vertexIndex] = range.color[0];
5948
+ colors[vertexIndex + 1] = range.color[1];
5949
+ colors[vertexIndex + 2] = range.color[2];
5950
+ }
5928
5951
  }
5929
5952
  }
5930
5953
  }
5931
5954
  }
5955
+ // Calculate bounds per line for collision detection
5956
+ const bounds = Array.from(lineGroups.values()).map((lineGlyphs) => this.calculateGlyphBounds(lineGlyphs));
5932
5957
  coloredRanges.push({
5933
5958
  start: range.start,
5934
5959
  end: range.end,
5935
5960
  originalText: originalText.slice(range.start, range.end),
5936
5961
  color: range.color,
5937
- bounds: [], // Would calculate from glyphs if needed
5962
+ bounds,
5938
5963
  glyphs: rangeGlyphs,
5939
- lineIndices: [...new Set(rangeGlyphs.map((g) => g.lineIndex))]
5964
+ lineIndices: Array.from(lineGroups.keys()).sort((a, b) => a - b)
5940
5965
  });
5941
- });
5966
+ }
5942
5967
  }
5943
5968
  }
5944
5969
  return { colors, coloredRanges };
5945
5970
  }
5971
+ calculateGlyphBounds(glyphs) {
5972
+ if (glyphs.length === 0) {
5973
+ return {
5974
+ min: { x: 0, y: 0, z: 0 },
5975
+ max: { x: 0, y: 0, z: 0 }
5976
+ };
5977
+ }
5978
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
5979
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
5980
+ for (const glyph of glyphs) {
5981
+ if (glyph.bounds.min.x < minX)
5982
+ minX = glyph.bounds.min.x;
5983
+ if (glyph.bounds.min.y < minY)
5984
+ minY = glyph.bounds.min.y;
5985
+ if (glyph.bounds.min.z < minZ)
5986
+ minZ = glyph.bounds.min.z;
5987
+ if (glyph.bounds.max.x > maxX)
5988
+ maxX = glyph.bounds.max.x;
5989
+ if (glyph.bounds.max.y > maxY)
5990
+ maxY = glyph.bounds.max.y;
5991
+ if (glyph.bounds.max.z > maxZ)
5992
+ maxZ = glyph.bounds.max.z;
5993
+ }
5994
+ return {
5995
+ min: { x: minX, y: minY, z: minZ },
5996
+ max: { x: maxX, y: maxY, z: maxZ }
5997
+ };
5998
+ }
5946
5999
  finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, originalText, byTextMatches) {
5947
6000
  const { layout = {} } = options;
5948
6001
  const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;
package/dist/index.d.ts CHANGED
@@ -382,6 +382,7 @@ declare class Text {
382
382
  private updateFontVariations;
383
383
  private prepareLayout;
384
384
  private applyColorSystem;
385
+ private calculateGlyphBounds;
385
386
  private finalizeGeometry;
386
387
  getFontMetrics(): FontMetrics;
387
388
  static preloadPatterns(languages: string[], patternsPath?: string): Promise<void>;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * three-text v0.3.2
2
+ * three-text v0.3.3
3
3
  * Copyright (C) 2025 Countertype LLC
4
4
  *
5
5
  * This program is free software: you can redistribute it and/or modify
@@ -5864,8 +5864,10 @@ class Text {
5864
5864
  colors[i + 1] = defaultColor[1];
5865
5865
  colors[i + 2] = defaultColor[2];
5866
5866
  }
5867
- if (color.byText && byTextMatches) {
5868
- const glyphsByTextIndex = new Map();
5867
+ // Build glyph index once for both byText and byCharRange
5868
+ let glyphsByTextIndex;
5869
+ if ((color.byText && byTextMatches) || color.byCharRange) {
5870
+ glyphsByTextIndex = new Map();
5869
5871
  for (const glyph of glyphInfoArray) {
5870
5872
  const existing = glyphsByTextIndex.get(glyph.textIndex);
5871
5873
  if (existing) {
@@ -5875,18 +5877,26 @@ class Text {
5875
5877
  glyphsByTextIndex.set(glyph.textIndex, [glyph]);
5876
5878
  }
5877
5879
  }
5880
+ }
5881
+ if (color.byText && byTextMatches && glyphsByTextIndex) {
5878
5882
  for (const match of byTextMatches) {
5879
5883
  const targetColor = color.byText[match.pattern];
5880
5884
  if (!targetColor)
5881
5885
  continue;
5882
5886
  const matchGlyphs = [];
5883
- const lineIndicesSet = new Set();
5887
+ const lineGroups = new Map();
5884
5888
  for (let i = match.start; i < match.end; i++) {
5885
5889
  const glyphs = glyphsByTextIndex.get(i);
5886
5890
  if (glyphs) {
5887
5891
  for (const glyph of glyphs) {
5888
5892
  matchGlyphs.push(glyph);
5889
- lineIndicesSet.add(glyph.lineIndex);
5893
+ const lineGlyphs = lineGroups.get(glyph.lineIndex);
5894
+ if (lineGlyphs) {
5895
+ lineGlyphs.push(glyph);
5896
+ }
5897
+ else {
5898
+ lineGroups.set(glyph.lineIndex, [glyph]);
5899
+ }
5890
5900
  for (let v = 0; v < glyph.vertexCount; v++) {
5891
5901
  const vertexIndex = (glyph.vertexStart + v) * 3;
5892
5902
  if (vertexIndex >= 0 && vertexIndex < colors.length) {
@@ -5898,48 +5908,91 @@ class Text {
5898
5908
  }
5899
5909
  }
5900
5910
  }
5911
+ // Calculate bounds per line for collision detection
5912
+ const bounds = Array.from(lineGroups.values()).map((lineGlyphs) => this.calculateGlyphBounds(lineGlyphs));
5901
5913
  coloredRanges.push({
5902
5914
  start: match.start,
5903
5915
  end: match.end,
5904
5916
  originalText: match.pattern,
5905
5917
  color: targetColor,
5906
- bounds: [],
5918
+ bounds,
5907
5919
  glyphs: matchGlyphs,
5908
- lineIndices: Array.from(lineIndicesSet).sort((a, b) => a - b)
5920
+ lineIndices: Array.from(lineGroups.keys()).sort((a, b) => a - b)
5909
5921
  });
5910
5922
  }
5911
5923
  }
5912
5924
  // Apply range coloring
5913
- if (color.byCharRange) {
5914
- color.byCharRange.forEach((range) => {
5925
+ if (color.byCharRange && glyphsByTextIndex) {
5926
+ for (const range of color.byCharRange) {
5915
5927
  const rangeGlyphs = [];
5916
- for (const glyph of glyphInfoArray) {
5917
- if (glyph.textIndex >= range.start && glyph.textIndex < range.end) {
5918
- rangeGlyphs.push(glyph);
5919
- for (let i = 0; i < glyph.vertexCount; i++) {
5920
- const vertexIndex = (glyph.vertexStart + i) * 3;
5921
- if (vertexIndex >= 0 && vertexIndex < colors.length) {
5922
- colors[vertexIndex] = range.color[0];
5923
- colors[vertexIndex + 1] = range.color[1];
5924
- colors[vertexIndex + 2] = range.color[2];
5928
+ const lineGroups = new Map();
5929
+ for (let i = range.start; i < range.end; i++) {
5930
+ const glyphs = glyphsByTextIndex.get(i);
5931
+ if (glyphs) {
5932
+ for (const glyph of glyphs) {
5933
+ rangeGlyphs.push(glyph);
5934
+ const lineGlyphs = lineGroups.get(glyph.lineIndex);
5935
+ if (lineGlyphs) {
5936
+ lineGlyphs.push(glyph);
5937
+ }
5938
+ else {
5939
+ lineGroups.set(glyph.lineIndex, [glyph]);
5940
+ }
5941
+ for (let v = 0; v < glyph.vertexCount; v++) {
5942
+ const vertexIndex = (glyph.vertexStart + v) * 3;
5943
+ if (vertexIndex >= 0 && vertexIndex < colors.length) {
5944
+ colors[vertexIndex] = range.color[0];
5945
+ colors[vertexIndex + 1] = range.color[1];
5946
+ colors[vertexIndex + 2] = range.color[2];
5947
+ }
5925
5948
  }
5926
5949
  }
5927
5950
  }
5928
5951
  }
5952
+ // Calculate bounds per line for collision detection
5953
+ const bounds = Array.from(lineGroups.values()).map((lineGlyphs) => this.calculateGlyphBounds(lineGlyphs));
5929
5954
  coloredRanges.push({
5930
5955
  start: range.start,
5931
5956
  end: range.end,
5932
5957
  originalText: originalText.slice(range.start, range.end),
5933
5958
  color: range.color,
5934
- bounds: [], // Would calculate from glyphs if needed
5959
+ bounds,
5935
5960
  glyphs: rangeGlyphs,
5936
- lineIndices: [...new Set(rangeGlyphs.map((g) => g.lineIndex))]
5961
+ lineIndices: Array.from(lineGroups.keys()).sort((a, b) => a - b)
5937
5962
  });
5938
- });
5963
+ }
5939
5964
  }
5940
5965
  }
5941
5966
  return { colors, coloredRanges };
5942
5967
  }
5968
+ calculateGlyphBounds(glyphs) {
5969
+ if (glyphs.length === 0) {
5970
+ return {
5971
+ min: { x: 0, y: 0, z: 0 },
5972
+ max: { x: 0, y: 0, z: 0 }
5973
+ };
5974
+ }
5975
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
5976
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
5977
+ for (const glyph of glyphs) {
5978
+ if (glyph.bounds.min.x < minX)
5979
+ minX = glyph.bounds.min.x;
5980
+ if (glyph.bounds.min.y < minY)
5981
+ minY = glyph.bounds.min.y;
5982
+ if (glyph.bounds.min.z < minZ)
5983
+ minZ = glyph.bounds.min.z;
5984
+ if (glyph.bounds.max.x > maxX)
5985
+ maxX = glyph.bounds.max.x;
5986
+ if (glyph.bounds.max.y > maxY)
5987
+ maxY = glyph.bounds.max.y;
5988
+ if (glyph.bounds.max.z > maxZ)
5989
+ maxZ = glyph.bounds.max.z;
5990
+ }
5991
+ return {
5992
+ min: { x: minX, y: minY, z: minZ },
5993
+ max: { x: maxX, y: maxY, z: maxZ }
5994
+ };
5995
+ }
5943
5996
  finalizeGeometry(vertices, normals, indices, glyphInfoArray, planeBounds, options, originalText, byTextMatches) {
5944
5997
  const { layout = {} } = options;
5945
5998
  const { width, align = layout.direction === 'rtl' ? 'right' : 'left' } = layout;