three-text 0.2.8 → 0.2.10
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 +25 -0
- package/dist/index.cjs +110 -46
- package/dist/index.d.ts +7 -1
- package/dist/index.js +110 -46
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +110 -46
- package/dist/index.umd.min.js +2 -2
- package/dist/three/index.cjs +36 -29
- package/dist/three/index.d.ts +1 -0
- package/dist/three/index.js +36 -29
- package/dist/three/react.d.ts +7 -1
- package/dist/types/core/Text.d.ts +7 -1
- package/dist/types/core/layout/constants.d.ts +1 -1
- package/dist/types/three/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -675,11 +675,36 @@ Creates text geometry with automatic font loading and HarfBuzz initialization
|
|
|
675
675
|
- `getCacheStatistics()` - Cache performance data
|
|
676
676
|
- `clearCache()` - Clear the glyph cache
|
|
677
677
|
- `measureTextWidth(text, letterSpacing?)` - Measure text width
|
|
678
|
+
- `update(options)` - Re-render with new options while preserving font/cache state
|
|
678
679
|
|
|
679
680
|
**Three.js adapter (`three-text/three`) returns:**
|
|
680
681
|
- `geometry: BufferGeometry` - Three.js geometry
|
|
681
682
|
- Plus all the above except vertices/normals/indices/colors/glyphAttributes
|
|
682
683
|
|
|
684
|
+
##### `update(options: Partial<TextOptions>): Promise<TextGeometryInfo>`
|
|
685
|
+
|
|
686
|
+
Returns new geometry with updated options. Font and glyph data are cached globally by default, so performance is similar to calling `Text.create()` again; the method is provided for ergonomics when working with the same font configuration across multiple renders
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
const text = await Text.create({
|
|
690
|
+
font: '/fonts/Font.ttf',
|
|
691
|
+
text: 'Hello',
|
|
692
|
+
size: 72
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
const mesh = new THREE.Mesh(text.geometry, material);
|
|
696
|
+
scene.add(mesh);
|
|
697
|
+
|
|
698
|
+
// Later, update the text
|
|
699
|
+
const updated = await text.update({ text: 'World' });
|
|
700
|
+
mesh.geometry.dispose();
|
|
701
|
+
mesh.geometry = updated.geometry;
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The method preserves custom cache instances if `maxCacheSizeMB` was specified. For most use cases, this is primarily an API convenience
|
|
705
|
+
|
|
706
|
+
Options merge at the top level - to remove a nested property like `layout.width`, pass `{ layout: { width: undefined } }`
|
|
707
|
+
|
|
683
708
|
##### `Text.setHarfBuzzPath(path: string): void`
|
|
684
709
|
|
|
685
710
|
**Required.** Sets the path for the HarfBuzz WASM binary. Must be called before `Text.create()`
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.10
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -155,7 +155,7 @@ const perfLogger = new PerformanceLogger();
|
|
|
155
155
|
// TeX defaults
|
|
156
156
|
const FITNESS_TIGHT_THRESHOLD = 12; // if badness > 12 when shrinking -> tight_fit
|
|
157
157
|
const FITNESS_NORMAL_THRESHOLD = 99; // if badness > 99 when stretching -> loose_fit
|
|
158
|
-
const DEFAULT_TOLERANCE =
|
|
158
|
+
const DEFAULT_TOLERANCE = 800;
|
|
159
159
|
const DEFAULT_PRETOLERANCE = 100;
|
|
160
160
|
const DEFAULT_EMERGENCY_STRETCH = 0;
|
|
161
161
|
// In TeX, interword spacing is defined by font parameters (fontdimen):
|
|
@@ -186,11 +186,11 @@ var FitnessClass;
|
|
|
186
186
|
FitnessClass[FitnessClass["VERY_LOOSE"] = 3] = "VERY_LOOSE";
|
|
187
187
|
})(FitnessClass || (FitnessClass = {}));
|
|
188
188
|
// ActiveNodeList maintains all currently viable breakpoints as we scan through the text.
|
|
189
|
-
// Each node represents a potential break with accumulated demerits (total "cost" from start)
|
|
189
|
+
// Each node represents a potential break with accumulated demerits (total "cost" from start)
|
|
190
190
|
//
|
|
191
191
|
// Demerits = cumulative penalty score from text start to this break, calculated as:
|
|
192
|
-
// (line_penalty + badness)² + penalty² + flagged/fitness adjustments (tex.web
|
|
193
|
-
// Lower demerits = better line breaks. TeX minimizes total demerits across the paragraph
|
|
192
|
+
// (line_penalty + badness)² + penalty² + flagged/fitness adjustments (tex.web line 16634)
|
|
193
|
+
// Lower demerits = better line breaks. TeX minimizes total demerits across the paragraph
|
|
194
194
|
//
|
|
195
195
|
// Implementation differs from TeX:
|
|
196
196
|
// - Hash map for O(1) lookups by position+fitness
|
|
@@ -261,7 +261,7 @@ const DEFAULT_EMERGENCY_STRETCH_NO_HYPHEN = 0.1;
|
|
|
261
261
|
const SHORT_LINE_WIDTH_THRESHOLD = 0.5; // Lines < 50% of width are problematic
|
|
262
262
|
const SHORT_LINE_EMERGENCY_STRETCH_INCREMENT = 0.1; // Add 10% per iteration
|
|
263
263
|
class LineBreak {
|
|
264
|
-
// Calculate badness according to TeX's formula (tex.web
|
|
264
|
+
// Calculate badness according to TeX's formula (tex.web line 2337)
|
|
265
265
|
// Given t (desired adjustment) and s (available stretch/shrink)
|
|
266
266
|
// Returns approximation to 100(t/s)³, representing how "bad" a line is
|
|
267
267
|
// Constants are derived from TeX's fixed-point arithmetic:
|
|
@@ -810,19 +810,22 @@ class LineBreak {
|
|
|
810
810
|
// First pass: no hyphenation
|
|
811
811
|
let currentItems = allItems;
|
|
812
812
|
let breaks = LineBreak.findBreakpoints(currentItems, width, pretolerance, looseness, false, 0, context);
|
|
813
|
-
// Second pass:
|
|
813
|
+
// Second pass: with hyphenation if first pass failed
|
|
814
814
|
if (breaks.length === 0 && useHyphenation) {
|
|
815
815
|
const itemsWithHyphenation = LineBreak.itemizeText(text, measureText, true, language, hyphenationPatterns, lefthyphenmin, righthyphenmin, context);
|
|
816
816
|
currentItems = itemsWithHyphenation;
|
|
817
817
|
breaks = LineBreak.findBreakpoints(currentItems, width, tolerance, looseness, false, 0, context);
|
|
818
818
|
}
|
|
819
|
-
// Emergency pass:
|
|
819
|
+
// Emergency pass: add emergency stretch to background stretchability
|
|
820
820
|
if (breaks.length === 0) {
|
|
821
|
-
|
|
821
|
+
// For first emergency attempt, use initialEmergencyStretch
|
|
822
|
+
// For subsequent iterations (short line detection), progressively increase
|
|
823
|
+
currentEmergencyStretch = initialEmergencyStretch + (iteration * width * SHORT_LINE_EMERGENCY_STRETCH_INCREMENT);
|
|
824
|
+
breaks = LineBreak.findBreakpoints(currentItems, width, tolerance, looseness, true, currentEmergencyStretch, context);
|
|
822
825
|
}
|
|
823
|
-
//
|
|
826
|
+
// Last resort: allow higher badness (but not infinite)
|
|
824
827
|
if (breaks.length === 0) {
|
|
825
|
-
breaks = LineBreak.findBreakpoints(currentItems, width,
|
|
828
|
+
breaks = LineBreak.findBreakpoints(currentItems, width, INF_BAD, looseness, true, currentEmergencyStretch, context);
|
|
826
829
|
}
|
|
827
830
|
// Create lines from breaks
|
|
828
831
|
if (breaks.length > 0) {
|
|
@@ -832,9 +835,7 @@ class LineBreak {
|
|
|
832
835
|
if (shortLineDetectionEnabled &&
|
|
833
836
|
breaks.length > 1 &&
|
|
834
837
|
LineBreak.hasShortLines(currentItems, breaks, width, shortLineThreshold)) {
|
|
835
|
-
//
|
|
836
|
-
currentEmergencyStretch +=
|
|
837
|
-
width * SHORT_LINE_EMERGENCY_STRETCH_INCREMENT;
|
|
838
|
+
// Retry with more emergency stretch to push words to next line
|
|
838
839
|
iteration++;
|
|
839
840
|
continue;
|
|
840
841
|
}
|
|
@@ -866,11 +867,12 @@ class LineBreak {
|
|
|
866
867
|
threshold = Infinity, // maximum badness allowed for a break
|
|
867
868
|
looseness = 0, // desired line count adjustment
|
|
868
869
|
isFinalPass = false, // whether this is the final pass
|
|
869
|
-
|
|
870
|
+
backgroundStretch = 0, // additional stretchability for all glue (emergency stretch)
|
|
870
871
|
context) {
|
|
871
872
|
// Pre-compute cumulative widths for fast range queries
|
|
872
873
|
const cumulativeWidths = LineBreak.computeCumulativeWidths(items);
|
|
873
874
|
const activeNodes = new ActiveNodeList();
|
|
875
|
+
const minimumDemerits = { value: Infinity };
|
|
874
876
|
activeNodes.insert({
|
|
875
877
|
position: 0,
|
|
876
878
|
line: 0,
|
|
@@ -884,16 +886,16 @@ class LineBreak {
|
|
|
884
886
|
const item = items[i];
|
|
885
887
|
if (item.type === ItemType.PENALTY &&
|
|
886
888
|
item.penalty < Infinity) {
|
|
887
|
-
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold,
|
|
889
|
+
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold, backgroundStretch, cumulativeWidths, context, isFinalPass, minimumDemerits);
|
|
888
890
|
}
|
|
889
891
|
if (item.type === ItemType.DISCRETIONARY &&
|
|
890
892
|
item.penalty < Infinity) {
|
|
891
|
-
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold,
|
|
893
|
+
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold, backgroundStretch, cumulativeWidths, context, isFinalPass, minimumDemerits);
|
|
892
894
|
}
|
|
893
895
|
if (item.type === ItemType.GLUE &&
|
|
894
896
|
i > 0 &&
|
|
895
897
|
items[i - 1].type === ItemType.BOX) {
|
|
896
|
-
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold,
|
|
898
|
+
LineBreak.considerBreak(items, activeNodes, i, lineWidth, threshold, backgroundStretch, cumulativeWidths, context, isFinalPass, minimumDemerits);
|
|
897
899
|
}
|
|
898
900
|
LineBreak.deactivateNodes(activeNodes, i, lineWidth, cumulativeWidths.minWidths);
|
|
899
901
|
}
|
|
@@ -960,7 +962,7 @@ class LineBreak {
|
|
|
960
962
|
}
|
|
961
963
|
return breakpoints;
|
|
962
964
|
}
|
|
963
|
-
static considerBreak(items, activeNodes, breakpoint, lineWidth, threshold = Infinity,
|
|
965
|
+
static considerBreak(items, activeNodes, breakpoint, lineWidth, threshold = Infinity, backgroundStretch = 0, cumulativeWidths, context, isFinalPass = false, minimumDemerits = { value: Infinity }) {
|
|
964
966
|
const penalty = items[breakpoint].type === ItemType.PENALTY
|
|
965
967
|
? items[breakpoint].penalty
|
|
966
968
|
: 0;
|
|
@@ -972,11 +974,11 @@ class LineBreak {
|
|
|
972
974
|
continue;
|
|
973
975
|
const adjustmentData = LineBreak.computeAdjustmentRatio(items, node.position, breakpoint, node.line, lineWidth, cumulativeWidths, context);
|
|
974
976
|
const { ratio: r, adjustment, stretch, shrink, totalWidth } = adjustmentData;
|
|
975
|
-
// Calculate badness
|
|
977
|
+
// Calculate badness
|
|
976
978
|
let badness;
|
|
977
979
|
if (adjustment > 0) {
|
|
978
|
-
//
|
|
979
|
-
const effectiveStretch = stretch +
|
|
980
|
+
// backgroundStretch includes emergency stretch if in emergency pass
|
|
981
|
+
const effectiveStretch = stretch + backgroundStretch;
|
|
980
982
|
if (effectiveStretch <= 0) {
|
|
981
983
|
// Overfull box - badness is infinite + 1
|
|
982
984
|
badness = INF_BAD + 1;
|
|
@@ -1001,26 +1003,33 @@ class LineBreak {
|
|
|
1001
1003
|
else {
|
|
1002
1004
|
badness = 0;
|
|
1003
1005
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1006
|
+
// Artificial demerits: in final pass with no feasible solution yet
|
|
1007
|
+
// and only one active node left, force this break as a last resort
|
|
1008
|
+
const isLastResort = isFinalPass &&
|
|
1009
|
+
isForcedBreak &&
|
|
1010
|
+
minimumDemerits.value === Infinity &&
|
|
1011
|
+
allActiveNodes.length === 1 &&
|
|
1012
|
+
node.active;
|
|
1013
|
+
if (!isForcedBreak && !isLastResort && r < -1) {
|
|
1014
|
+
continue; // too tight
|
|
1007
1015
|
}
|
|
1008
1016
|
const fitnessClass = LineBreak.computeFitnessClass(badness, adjustment > 0);
|
|
1009
|
-
if (!isForcedBreak && badness > threshold) {
|
|
1017
|
+
if (!isForcedBreak && !isLastResort && badness > threshold) {
|
|
1010
1018
|
continue;
|
|
1011
1019
|
}
|
|
1012
|
-
// Initialize demerits
|
|
1020
|
+
// Initialize demerits with saturation check
|
|
1013
1021
|
let flaggedDemerits = 0;
|
|
1014
1022
|
let fitnessDemerits = 0;
|
|
1015
1023
|
const configuredLinePenalty = context?.linePenalty ?? 0;
|
|
1016
1024
|
let d = configuredLinePenalty + badness;
|
|
1017
1025
|
let demerits = Math.abs(d) >= 10000 ? 100000000 : d * d;
|
|
1026
|
+
const artificialDemerits = isLastResort;
|
|
1018
1027
|
const breakpointPenalty = items[breakpoint].type === ItemType.PENALTY
|
|
1019
1028
|
? items[breakpoint].penalty
|
|
1020
1029
|
: items[breakpoint].type === ItemType.DISCRETIONARY
|
|
1021
1030
|
? items[breakpoint].penalty
|
|
1022
1031
|
: 0;
|
|
1023
|
-
//
|
|
1032
|
+
// Penalty contribution to demerits
|
|
1024
1033
|
if (breakpointPenalty !== 0) {
|
|
1025
1034
|
if (breakpointPenalty > 0) {
|
|
1026
1035
|
demerits += breakpointPenalty * breakpointPenalty;
|
|
@@ -1046,10 +1055,13 @@ class LineBreak {
|
|
|
1046
1055
|
fitnessDemerits = context?.adjDemerits ?? 0;
|
|
1047
1056
|
demerits += fitnessDemerits;
|
|
1048
1057
|
}
|
|
1049
|
-
if (isForcedBreak) {
|
|
1058
|
+
if (isForcedBreak || artificialDemerits) {
|
|
1050
1059
|
demerits = 0;
|
|
1051
1060
|
}
|
|
1052
1061
|
const totalDemerits = node.totalDemerits + demerits;
|
|
1062
|
+
if (totalDemerits < minimumDemerits.value) {
|
|
1063
|
+
minimumDemerits.value = totalDemerits;
|
|
1064
|
+
}
|
|
1053
1065
|
let existingNode = activeNodes.findExisting(breakpoint, fitnessClass);
|
|
1054
1066
|
if (existingNode) {
|
|
1055
1067
|
if (totalDemerits < existingNode.totalDemerits) {
|
|
@@ -1187,7 +1199,6 @@ class LineBreak {
|
|
|
1187
1199
|
return { widths, stretches, shrinks, minWidths };
|
|
1188
1200
|
}
|
|
1189
1201
|
// Deactivate nodes that can't lead to good line breaks
|
|
1190
|
-
// TeX recalculates minWidth each time, we use cumulative arrays for lookup
|
|
1191
1202
|
static deactivateNodes(activeNodeList, currentPosition, lineWidth, minWidths) {
|
|
1192
1203
|
const activeNodes = activeNodeList.getAllActive();
|
|
1193
1204
|
for (let i = activeNodes.length - 1; i >= 0; i--) {
|
|
@@ -1392,8 +1403,8 @@ function convertFontFeaturesToString(features) {
|
|
|
1392
1403
|
|
|
1393
1404
|
class TextMeasurer {
|
|
1394
1405
|
// Measures text width including letter spacing
|
|
1395
|
-
//
|
|
1396
|
-
// so the widths given to the line-breaking algorithm already account for tracking
|
|
1406
|
+
// (letter spacing is added uniformly after each glyph during measurement,
|
|
1407
|
+
// so the widths given to the line-breaking algorithm already account for tracking)
|
|
1397
1408
|
static measureTextWidth(loadedFont, text, letterSpacing = 0) {
|
|
1398
1409
|
const buffer = loadedFont.hb.createBuffer();
|
|
1399
1410
|
buffer.addText(text);
|
|
@@ -3715,10 +3726,11 @@ class LRUCache {
|
|
|
3715
3726
|
}
|
|
3716
3727
|
}
|
|
3717
3728
|
|
|
3729
|
+
const CONTOUR_CACHE_MAX_ENTRIES = 1000;
|
|
3730
|
+
const WORD_CACHE_MAX_ENTRIES = 1000;
|
|
3718
3731
|
class GlyphGeometryBuilder {
|
|
3719
3732
|
constructor(cache, loadedFont) {
|
|
3720
3733
|
this.fontId = 'default';
|
|
3721
|
-
this.wordCache = new Map();
|
|
3722
3734
|
this.cache = cache;
|
|
3723
3735
|
this.loadedFont = loadedFont;
|
|
3724
3736
|
this.tessellator = new Tessellator();
|
|
@@ -3728,7 +3740,7 @@ class GlyphGeometryBuilder {
|
|
|
3728
3740
|
this.drawCallbacks = new DrawCallbackHandler();
|
|
3729
3741
|
this.drawCallbacks.createDrawFuncs(this.loadedFont, this.collector);
|
|
3730
3742
|
this.contourCache = new LRUCache({
|
|
3731
|
-
maxEntries:
|
|
3743
|
+
maxEntries: CONTOUR_CACHE_MAX_ENTRIES,
|
|
3732
3744
|
calculateSize: (contours) => {
|
|
3733
3745
|
let size = 0;
|
|
3734
3746
|
for (const path of contours.paths) {
|
|
@@ -3737,6 +3749,15 @@ class GlyphGeometryBuilder {
|
|
|
3737
3749
|
return size + 64; // bounds overhead
|
|
3738
3750
|
}
|
|
3739
3751
|
});
|
|
3752
|
+
this.wordCache = new LRUCache({
|
|
3753
|
+
maxEntries: WORD_CACHE_MAX_ENTRIES,
|
|
3754
|
+
calculateSize: (data) => {
|
|
3755
|
+
let size = data.vertices.length * 4;
|
|
3756
|
+
size += data.normals.length * 4;
|
|
3757
|
+
size += data.indices.length * data.indices.BYTES_PER_ELEMENT;
|
|
3758
|
+
return size;
|
|
3759
|
+
}
|
|
3760
|
+
});
|
|
3740
3761
|
}
|
|
3741
3762
|
getOptimizationStats() {
|
|
3742
3763
|
return this.collector.getOptimizationStats();
|
|
@@ -4026,7 +4047,7 @@ class TextShaper {
|
|
|
4026
4047
|
let currentClusterText = '';
|
|
4027
4048
|
let clusterStartPosition = new Vec3();
|
|
4028
4049
|
let cursor = new Vec3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
|
|
4029
|
-
// Apply letter spacing
|
|
4050
|
+
// Apply letter spacing after each glyph to match width measurements used during line breaking
|
|
4030
4051
|
const letterSpacingFU = letterSpacing * this.loadedFont.upem;
|
|
4031
4052
|
const spaceAdjustment = this.calculateSpaceAdjustment(lineInfo, align, letterSpacing);
|
|
4032
4053
|
const cjkAdjustment = this.calculateCJKAdjustment(lineInfo, align);
|
|
@@ -5064,6 +5085,54 @@ class Text {
|
|
|
5064
5085
|
if (!Text.hbInitPromise) {
|
|
5065
5086
|
Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
|
|
5066
5087
|
}
|
|
5088
|
+
const loadedFont = await Text.resolveFont(options);
|
|
5089
|
+
const text = new Text({ maxCacheSizeMB: options.maxCacheSizeMB });
|
|
5090
|
+
text.setLoadedFont(loadedFont);
|
|
5091
|
+
// Initial creation
|
|
5092
|
+
const { font, maxCacheSizeMB, ...geometryOptions } = options;
|
|
5093
|
+
const result = await text.createGeometry(geometryOptions);
|
|
5094
|
+
// Recursive update function
|
|
5095
|
+
const update = async (newOptions) => {
|
|
5096
|
+
// Merge options - preserve font from original options if not provided
|
|
5097
|
+
const mergedOptions = { ...options };
|
|
5098
|
+
for (const key in newOptions) {
|
|
5099
|
+
const value = newOptions[key];
|
|
5100
|
+
if (value !== undefined) {
|
|
5101
|
+
mergedOptions[key] = value;
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
// If font definition or configuration changed, reload font and reset helpers
|
|
5105
|
+
if (newOptions.font !== undefined ||
|
|
5106
|
+
newOptions.fontVariations !== undefined ||
|
|
5107
|
+
newOptions.fontFeatures !== undefined) {
|
|
5108
|
+
const newLoadedFont = await Text.resolveFont(mergedOptions);
|
|
5109
|
+
text.setLoadedFont(newLoadedFont);
|
|
5110
|
+
// Reset geometry builder and shaper to use new font
|
|
5111
|
+
text.resetHelpers();
|
|
5112
|
+
}
|
|
5113
|
+
// Update closure options for next time
|
|
5114
|
+
options = mergedOptions;
|
|
5115
|
+
const { font, maxCacheSizeMB, ...currentGeometryOptions } = options;
|
|
5116
|
+
const newResult = await text.createGeometry(currentGeometryOptions);
|
|
5117
|
+
return {
|
|
5118
|
+
...newResult,
|
|
5119
|
+
getLoadedFont: () => text.getLoadedFont(),
|
|
5120
|
+
getCacheStatistics: () => text.getCacheStatistics(),
|
|
5121
|
+
clearCache: () => text.clearCache(),
|
|
5122
|
+
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5123
|
+
update
|
|
5124
|
+
};
|
|
5125
|
+
};
|
|
5126
|
+
return {
|
|
5127
|
+
...result,
|
|
5128
|
+
getLoadedFont: () => text.getLoadedFont(),
|
|
5129
|
+
getCacheStatistics: () => text.getCacheStatistics(),
|
|
5130
|
+
clearCache: () => text.clearCache(),
|
|
5131
|
+
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5132
|
+
update
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5135
|
+
static async resolveFont(options) {
|
|
5067
5136
|
const baseFontKey = typeof options.font === 'string'
|
|
5068
5137
|
? options.font
|
|
5069
5138
|
: `buffer-${Text.generateFontContentHash(options.font)}`;
|
|
@@ -5078,17 +5147,7 @@ class Text {
|
|
|
5078
5147
|
if (!loadedFont) {
|
|
5079
5148
|
loadedFont = await Text.loadAndCacheFont(fontKey, options.font, options.fontVariations, options.fontFeatures);
|
|
5080
5149
|
}
|
|
5081
|
-
|
|
5082
|
-
text.setLoadedFont(loadedFont);
|
|
5083
|
-
const { font, maxCacheSizeMB, ...geometryOptions } = options;
|
|
5084
|
-
const result = await text.createGeometry(geometryOptions);
|
|
5085
|
-
return {
|
|
5086
|
-
...result,
|
|
5087
|
-
getLoadedFont: () => text.getLoadedFont(),
|
|
5088
|
-
getCacheStatistics: () => text.getCacheStatistics(),
|
|
5089
|
-
clearCache: () => text.clearCache(),
|
|
5090
|
-
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing)
|
|
5091
|
-
};
|
|
5150
|
+
return loadedFont;
|
|
5092
5151
|
}
|
|
5093
5152
|
static async loadAndCacheFont(fontKey, font, fontVariations, fontFeatures) {
|
|
5094
5153
|
const tempText = new Text();
|
|
@@ -5546,6 +5605,11 @@ class Text {
|
|
|
5546
5605
|
glyphLineIndex: glyphLineIndices
|
|
5547
5606
|
};
|
|
5548
5607
|
}
|
|
5608
|
+
resetHelpers() {
|
|
5609
|
+
this.geometryBuilder = undefined;
|
|
5610
|
+
this.textShaper = undefined;
|
|
5611
|
+
this.textLayout = undefined;
|
|
5612
|
+
}
|
|
5549
5613
|
destroy() {
|
|
5550
5614
|
if (!this.loadedFont) {
|
|
5551
5615
|
return;
|
package/dist/index.d.ts
CHANGED
|
@@ -382,7 +382,12 @@ declare class Text {
|
|
|
382
382
|
static setHarfBuzzPath(path: string): void;
|
|
383
383
|
static setHarfBuzzBuffer(wasmBuffer: ArrayBuffer): void;
|
|
384
384
|
static init(): Promise<HarfBuzzInstance>;
|
|
385
|
-
static create(options: TextOptions): Promise<TextGeometryInfo & Pick<Text, 'getLoadedFont' | 'getCacheStatistics' | 'clearCache' | 'measureTextWidth'
|
|
385
|
+
static create(options: TextOptions): Promise<TextGeometryInfo & Pick<Text, 'getLoadedFont' | 'getCacheStatistics' | 'clearCache' | 'measureTextWidth'> & {
|
|
386
|
+
update: (options: Partial<TextOptions>) => Promise<TextGeometryInfo & Pick<Text, 'getLoadedFont' | 'getCacheStatistics' | 'clearCache' | 'measureTextWidth'> & {
|
|
387
|
+
update: (options: Partial<TextOptions>) => Promise<any>;
|
|
388
|
+
}>;
|
|
389
|
+
}>;
|
|
390
|
+
private static resolveFont;
|
|
386
391
|
private static loadAndCacheFont;
|
|
387
392
|
private static generateFontContentHash;
|
|
388
393
|
private setLoadedFont;
|
|
@@ -402,6 +407,7 @@ declare class Text {
|
|
|
402
407
|
getCacheStatistics(): GlyphCacheStats | null;
|
|
403
408
|
clearCache(): void;
|
|
404
409
|
private createGlyphAttributes;
|
|
410
|
+
private resetHelpers;
|
|
405
411
|
destroy(): void;
|
|
406
412
|
}
|
|
407
413
|
|