three-text 0.2.8 → 0.2.9
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 +23 -0
- package/dist/index.cjs +108 -45
- package/dist/index.d.ts +7 -1
- package/dist/index.js +108 -45
- package/dist/index.min.cjs +2 -2
- package/dist/index.min.js +2 -2
- package/dist/index.umd.js +108 -45
- 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/three/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -675,11 +675,34 @@ 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
|
+
|
|
683
706
|
##### `Text.setHarfBuzzPath(path: string): void`
|
|
684
707
|
|
|
685
708
|
**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.9
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -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,32 @@ 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
|
+
minimumDemerits.value === Infinity &&
|
|
1010
|
+
allActiveNodes.length === 1 &&
|
|
1011
|
+
node.active;
|
|
1012
|
+
if (!isForcedBreak && !isLastResort && r < -1) {
|
|
1013
|
+
continue; // too tight
|
|
1007
1014
|
}
|
|
1008
1015
|
const fitnessClass = LineBreak.computeFitnessClass(badness, adjustment > 0);
|
|
1009
|
-
if (!isForcedBreak && badness > threshold) {
|
|
1016
|
+
if (!isForcedBreak && !isLastResort && badness > threshold) {
|
|
1010
1017
|
continue;
|
|
1011
1018
|
}
|
|
1012
|
-
// Initialize demerits
|
|
1019
|
+
// Initialize demerits with saturation check
|
|
1013
1020
|
let flaggedDemerits = 0;
|
|
1014
1021
|
let fitnessDemerits = 0;
|
|
1015
1022
|
const configuredLinePenalty = context?.linePenalty ?? 0;
|
|
1016
1023
|
let d = configuredLinePenalty + badness;
|
|
1017
1024
|
let demerits = Math.abs(d) >= 10000 ? 100000000 : d * d;
|
|
1025
|
+
const artificialDemerits = isLastResort;
|
|
1018
1026
|
const breakpointPenalty = items[breakpoint].type === ItemType.PENALTY
|
|
1019
1027
|
? items[breakpoint].penalty
|
|
1020
1028
|
: items[breakpoint].type === ItemType.DISCRETIONARY
|
|
1021
1029
|
? items[breakpoint].penalty
|
|
1022
1030
|
: 0;
|
|
1023
|
-
//
|
|
1031
|
+
// Penalty contribution to demerits
|
|
1024
1032
|
if (breakpointPenalty !== 0) {
|
|
1025
1033
|
if (breakpointPenalty > 0) {
|
|
1026
1034
|
demerits += breakpointPenalty * breakpointPenalty;
|
|
@@ -1046,10 +1054,13 @@ class LineBreak {
|
|
|
1046
1054
|
fitnessDemerits = context?.adjDemerits ?? 0;
|
|
1047
1055
|
demerits += fitnessDemerits;
|
|
1048
1056
|
}
|
|
1049
|
-
if (isForcedBreak) {
|
|
1057
|
+
if (isForcedBreak || artificialDemerits) {
|
|
1050
1058
|
demerits = 0;
|
|
1051
1059
|
}
|
|
1052
1060
|
const totalDemerits = node.totalDemerits + demerits;
|
|
1061
|
+
if (totalDemerits < minimumDemerits.value) {
|
|
1062
|
+
minimumDemerits.value = totalDemerits;
|
|
1063
|
+
}
|
|
1053
1064
|
let existingNode = activeNodes.findExisting(breakpoint, fitnessClass);
|
|
1054
1065
|
if (existingNode) {
|
|
1055
1066
|
if (totalDemerits < existingNode.totalDemerits) {
|
|
@@ -1187,7 +1198,6 @@ class LineBreak {
|
|
|
1187
1198
|
return { widths, stretches, shrinks, minWidths };
|
|
1188
1199
|
}
|
|
1189
1200
|
// Deactivate nodes that can't lead to good line breaks
|
|
1190
|
-
// TeX recalculates minWidth each time, we use cumulative arrays for lookup
|
|
1191
1201
|
static deactivateNodes(activeNodeList, currentPosition, lineWidth, minWidths) {
|
|
1192
1202
|
const activeNodes = activeNodeList.getAllActive();
|
|
1193
1203
|
for (let i = activeNodes.length - 1; i >= 0; i--) {
|
|
@@ -1392,8 +1402,8 @@ function convertFontFeaturesToString(features) {
|
|
|
1392
1402
|
|
|
1393
1403
|
class TextMeasurer {
|
|
1394
1404
|
// Measures text width including letter spacing
|
|
1395
|
-
//
|
|
1396
|
-
// so the widths given to the line-breaking algorithm already account for tracking
|
|
1405
|
+
// (letter spacing is added uniformly after each glyph during measurement,
|
|
1406
|
+
// so the widths given to the line-breaking algorithm already account for tracking)
|
|
1397
1407
|
static measureTextWidth(loadedFont, text, letterSpacing = 0) {
|
|
1398
1408
|
const buffer = loadedFont.hb.createBuffer();
|
|
1399
1409
|
buffer.addText(text);
|
|
@@ -3715,10 +3725,11 @@ class LRUCache {
|
|
|
3715
3725
|
}
|
|
3716
3726
|
}
|
|
3717
3727
|
|
|
3728
|
+
const CONTOUR_CACHE_MAX_ENTRIES = 1000;
|
|
3729
|
+
const WORD_CACHE_MAX_ENTRIES = 1000;
|
|
3718
3730
|
class GlyphGeometryBuilder {
|
|
3719
3731
|
constructor(cache, loadedFont) {
|
|
3720
3732
|
this.fontId = 'default';
|
|
3721
|
-
this.wordCache = new Map();
|
|
3722
3733
|
this.cache = cache;
|
|
3723
3734
|
this.loadedFont = loadedFont;
|
|
3724
3735
|
this.tessellator = new Tessellator();
|
|
@@ -3728,7 +3739,7 @@ class GlyphGeometryBuilder {
|
|
|
3728
3739
|
this.drawCallbacks = new DrawCallbackHandler();
|
|
3729
3740
|
this.drawCallbacks.createDrawFuncs(this.loadedFont, this.collector);
|
|
3730
3741
|
this.contourCache = new LRUCache({
|
|
3731
|
-
maxEntries:
|
|
3742
|
+
maxEntries: CONTOUR_CACHE_MAX_ENTRIES,
|
|
3732
3743
|
calculateSize: (contours) => {
|
|
3733
3744
|
let size = 0;
|
|
3734
3745
|
for (const path of contours.paths) {
|
|
@@ -3737,6 +3748,15 @@ class GlyphGeometryBuilder {
|
|
|
3737
3748
|
return size + 64; // bounds overhead
|
|
3738
3749
|
}
|
|
3739
3750
|
});
|
|
3751
|
+
this.wordCache = new LRUCache({
|
|
3752
|
+
maxEntries: WORD_CACHE_MAX_ENTRIES,
|
|
3753
|
+
calculateSize: (data) => {
|
|
3754
|
+
let size = data.vertices.length * 4;
|
|
3755
|
+
size += data.normals.length * 4;
|
|
3756
|
+
size += data.indices.length * data.indices.BYTES_PER_ELEMENT;
|
|
3757
|
+
return size;
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3740
3760
|
}
|
|
3741
3761
|
getOptimizationStats() {
|
|
3742
3762
|
return this.collector.getOptimizationStats();
|
|
@@ -4026,7 +4046,7 @@ class TextShaper {
|
|
|
4026
4046
|
let currentClusterText = '';
|
|
4027
4047
|
let clusterStartPosition = new Vec3();
|
|
4028
4048
|
let cursor = new Vec3(lineInfo.xOffset, -lineIndex * scaledLineHeight, 0);
|
|
4029
|
-
// Apply letter spacing
|
|
4049
|
+
// Apply letter spacing after each glyph to match width measurements used during line breaking
|
|
4030
4050
|
const letterSpacingFU = letterSpacing * this.loadedFont.upem;
|
|
4031
4051
|
const spaceAdjustment = this.calculateSpaceAdjustment(lineInfo, align, letterSpacing);
|
|
4032
4052
|
const cjkAdjustment = this.calculateCJKAdjustment(lineInfo, align);
|
|
@@ -5064,6 +5084,54 @@ class Text {
|
|
|
5064
5084
|
if (!Text.hbInitPromise) {
|
|
5065
5085
|
Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
|
|
5066
5086
|
}
|
|
5087
|
+
const loadedFont = await Text.resolveFont(options);
|
|
5088
|
+
const text = new Text({ maxCacheSizeMB: options.maxCacheSizeMB });
|
|
5089
|
+
text.setLoadedFont(loadedFont);
|
|
5090
|
+
// Initial creation
|
|
5091
|
+
const { font, maxCacheSizeMB, ...geometryOptions } = options;
|
|
5092
|
+
const result = await text.createGeometry(geometryOptions);
|
|
5093
|
+
// Recursive update function
|
|
5094
|
+
const update = async (newOptions) => {
|
|
5095
|
+
// Merge options - preserve font from original options if not provided
|
|
5096
|
+
const mergedOptions = { ...options };
|
|
5097
|
+
for (const key in newOptions) {
|
|
5098
|
+
const value = newOptions[key];
|
|
5099
|
+
if (value !== undefined) {
|
|
5100
|
+
mergedOptions[key] = value;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
// If font definition or configuration changed, reload font and reset helpers
|
|
5104
|
+
if (newOptions.font !== undefined ||
|
|
5105
|
+
newOptions.fontVariations !== undefined ||
|
|
5106
|
+
newOptions.fontFeatures !== undefined) {
|
|
5107
|
+
const newLoadedFont = await Text.resolveFont(mergedOptions);
|
|
5108
|
+
text.setLoadedFont(newLoadedFont);
|
|
5109
|
+
// Reset geometry builder and shaper to use new font
|
|
5110
|
+
text.resetHelpers();
|
|
5111
|
+
}
|
|
5112
|
+
// Update closure options for next time
|
|
5113
|
+
options = mergedOptions;
|
|
5114
|
+
const { font, maxCacheSizeMB, ...currentGeometryOptions } = options;
|
|
5115
|
+
const newResult = await text.createGeometry(currentGeometryOptions);
|
|
5116
|
+
return {
|
|
5117
|
+
...newResult,
|
|
5118
|
+
getLoadedFont: () => text.getLoadedFont(),
|
|
5119
|
+
getCacheStatistics: () => text.getCacheStatistics(),
|
|
5120
|
+
clearCache: () => text.clearCache(),
|
|
5121
|
+
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5122
|
+
update
|
|
5123
|
+
};
|
|
5124
|
+
};
|
|
5125
|
+
return {
|
|
5126
|
+
...result,
|
|
5127
|
+
getLoadedFont: () => text.getLoadedFont(),
|
|
5128
|
+
getCacheStatistics: () => text.getCacheStatistics(),
|
|
5129
|
+
clearCache: () => text.clearCache(),
|
|
5130
|
+
measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing),
|
|
5131
|
+
update
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
static async resolveFont(options) {
|
|
5067
5135
|
const baseFontKey = typeof options.font === 'string'
|
|
5068
5136
|
? options.font
|
|
5069
5137
|
: `buffer-${Text.generateFontContentHash(options.font)}`;
|
|
@@ -5078,17 +5146,7 @@ class Text {
|
|
|
5078
5146
|
if (!loadedFont) {
|
|
5079
5147
|
loadedFont = await Text.loadAndCacheFont(fontKey, options.font, options.fontVariations, options.fontFeatures);
|
|
5080
5148
|
}
|
|
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
|
-
};
|
|
5149
|
+
return loadedFont;
|
|
5092
5150
|
}
|
|
5093
5151
|
static async loadAndCacheFont(fontKey, font, fontVariations, fontFeatures) {
|
|
5094
5152
|
const tempText = new Text();
|
|
@@ -5546,6 +5604,11 @@ class Text {
|
|
|
5546
5604
|
glyphLineIndex: glyphLineIndices
|
|
5547
5605
|
};
|
|
5548
5606
|
}
|
|
5607
|
+
resetHelpers() {
|
|
5608
|
+
this.geometryBuilder = undefined;
|
|
5609
|
+
this.textShaper = undefined;
|
|
5610
|
+
this.textLayout = undefined;
|
|
5611
|
+
}
|
|
5549
5612
|
destroy() {
|
|
5550
5613
|
if (!this.loadedFont) {
|
|
5551
5614
|
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
|
|