three-text 0.5.0 → 0.5.2

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.umd.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @license
3
- * three-text v0.5.0
3
+ * three-text v0.5.2
4
4
  * Copyright © 2025-2026 Jeremy Tribby, Countertype LLC
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
@@ -3267,6 +3267,8 @@
3267
3267
  static { this.patternCache = new Map(); }
3268
3268
  static { this.hbInitPromise = null; }
3269
3269
  static { this.fontCache = new Map(); }
3270
+ static { this.fontLoadPromises = new Map(); }
3271
+ static { this.fontRefCounts = new Map(); }
3270
3272
  static { this.fontCacheMemoryBytes = 0; }
3271
3273
  static { this.maxFontCacheMemoryBytes = Infinity; }
3272
3274
  static { this.fontIdCounter = 0; }
@@ -3311,9 +3313,9 @@
3311
3313
  if (!Text.hbInitPromise) {
3312
3314
  Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
3313
3315
  }
3314
- const loadedFont = await Text.resolveFont(options);
3316
+ const { loadedFont, fontKey } = await Text.resolveFont(options);
3315
3317
  const text = new Text();
3316
- text.setLoadedFont(loadedFont);
3318
+ text.setLoadedFont(loadedFont, fontKey);
3317
3319
  const result = await text.createLayout(options);
3318
3320
  const update = async (newOptions) => {
3319
3321
  const mergedOptions = { ...options };
@@ -3326,8 +3328,8 @@
3326
3328
  if (newOptions.font !== undefined ||
3327
3329
  newOptions.fontVariations !== undefined ||
3328
3330
  newOptions.fontFeatures !== undefined) {
3329
- const newLoadedFont = await Text.resolveFont(mergedOptions);
3330
- text.setLoadedFont(newLoadedFont);
3331
+ const { loadedFont: newLoadedFont, fontKey: newFontKey } = await Text.resolveFont(mergedOptions);
3332
+ text.setLoadedFont(newLoadedFont, newFontKey);
3331
3333
  text.resetHelpers();
3332
3334
  }
3333
3335
  options = mergedOptions;
@@ -3348,6 +3350,22 @@
3348
3350
  dispose: () => text.destroy()
3349
3351
  };
3350
3352
  }
3353
+ static retainFont(fontKey) {
3354
+ Text.fontRefCounts.set(fontKey, (Text.fontRefCounts.get(fontKey) ?? 0) + 1);
3355
+ }
3356
+ static releaseFont(fontKey, loadedFont) {
3357
+ const nextCount = (Text.fontRefCounts.get(fontKey) ?? 0) - 1;
3358
+ if (nextCount > 0) {
3359
+ Text.fontRefCounts.set(fontKey, nextCount);
3360
+ return;
3361
+ }
3362
+ Text.fontRefCounts.delete(fontKey);
3363
+ // Cached fonts stay alive while present in the cache. If a font has been
3364
+ // evicted, destroy it once the last live handle releases it.
3365
+ if (!Text.fontCache.has(fontKey)) {
3366
+ FontLoader.destroyFont(loadedFont);
3367
+ }
3368
+ }
3351
3369
  static async resolveFont(options) {
3352
3370
  const baseFontKey = typeof options.font === 'string'
3353
3371
  ? options.font
@@ -3361,9 +3379,17 @@
3361
3379
  }
3362
3380
  let loadedFont = Text.fontCache.get(fontKey);
3363
3381
  if (!loadedFont) {
3364
- loadedFont = await Text.loadAndCacheFont(fontKey, options.font, options.fontVariations, options.fontFeatures);
3382
+ let loadPromise = Text.fontLoadPromises.get(fontKey);
3383
+ if (!loadPromise) {
3384
+ loadPromise = Text.loadAndCacheFont(fontKey, options.font, options.fontVariations, options.fontFeatures).finally(() => {
3385
+ Text.fontLoadPromises.delete(fontKey);
3386
+ });
3387
+ Text.fontLoadPromises.set(fontKey, loadPromise);
3388
+ }
3389
+ loadedFont = await loadPromise;
3365
3390
  }
3366
- return loadedFont;
3391
+ Text.retainFont(fontKey);
3392
+ return { loadedFont, fontKey };
3367
3393
  }
3368
3394
  static async loadAndCacheFont(fontKey, font, fontVariations, fontFeatures) {
3369
3395
  const tempText = new Text();
@@ -3395,8 +3421,12 @@
3395
3421
  const firstKey = Text.fontCache.keys().next().value;
3396
3422
  if (firstKey === undefined)
3397
3423
  break;
3424
+ const font = Text.fontCache.get(firstKey);
3398
3425
  Text.trackFontCacheRemove(firstKey);
3399
3426
  Text.fontCache.delete(firstKey);
3427
+ if ((Text.fontRefCounts.get(firstKey) ?? 0) <= 0 && font) {
3428
+ FontLoader.destroyFont(font);
3429
+ }
3400
3430
  }
3401
3431
  }
3402
3432
  static generateFontContentHash(buffer) {
@@ -3418,8 +3448,12 @@
3418
3448
  return `c${++Text.fontIdCounter}`;
3419
3449
  }
3420
3450
  }
3421
- setLoadedFont(loadedFont) {
3451
+ setLoadedFont(loadedFont, fontKey) {
3452
+ if (this.loadedFont && this.loadedFont !== loadedFont) {
3453
+ this.releaseCurrentFont();
3454
+ }
3422
3455
  this.loadedFont = loadedFont;
3456
+ this.currentFontCacheKey = fontKey;
3423
3457
  const contentHash = Text.generateFontContentHash(loadedFont._buffer);
3424
3458
  this.currentFontId = `font_${contentHash}`;
3425
3459
  if (loadedFont.fontVariations) {
@@ -3429,6 +3463,29 @@
3429
3463
  this.currentFontId += `_feat_${Text.stableStringify(loadedFont.fontFeatures)}`;
3430
3464
  }
3431
3465
  }
3466
+ releaseCurrentFont() {
3467
+ if (!this.loadedFont)
3468
+ return;
3469
+ const currentFont = this.loadedFont;
3470
+ const currentFontKey = this.currentFontCacheKey;
3471
+ try {
3472
+ if (currentFontKey) {
3473
+ Text.releaseFont(currentFontKey, currentFont);
3474
+ }
3475
+ else {
3476
+ FontLoader.destroyFont(currentFont);
3477
+ }
3478
+ }
3479
+ catch (error) {
3480
+ logger.warn('Error destroying HarfBuzz objects:', error);
3481
+ }
3482
+ finally {
3483
+ this.loadedFont = undefined;
3484
+ this.currentFontCacheKey = undefined;
3485
+ this.textLayout = undefined;
3486
+ this.textShaper = undefined;
3487
+ }
3488
+ }
3432
3489
  async loadFont(fontSrc, fontVariations, fontFeatures) {
3433
3490
  perfLogger.start('Text.loadFont', {
3434
3491
  fontSrc: typeof fontSrc === 'string' ? fontSrc : `buffer(${fontSrc.byteLength})`
@@ -3648,18 +3705,7 @@
3648
3705
  if (!this.loadedFont) {
3649
3706
  return;
3650
3707
  }
3651
- const currentFont = this.loadedFont;
3652
- try {
3653
- FontLoader.destroyFont(currentFont);
3654
- }
3655
- catch (error) {
3656
- logger.warn('Error destroying HarfBuzz objects:', error);
3657
- }
3658
- finally {
3659
- this.loadedFont = undefined;
3660
- this.textLayout = undefined;
3661
- this.textShaper = undefined;
3662
- }
3708
+ this.releaseCurrentFont();
3663
3709
  }
3664
3710
  };
3665
3711
 
@@ -6378,7 +6424,8 @@
6378
6424
  getCacheSize: () => meshPipeline.getCacheSize(),
6379
6425
  clearCache: () => meshPipeline.clearCache(),
6380
6426
  measureTextWidth: (text, letterSpacing) => layoutHandle.measureTextWidth(text, letterSpacing),
6381
- update
6427
+ update,
6428
+ dispose: () => layoutHandle.dispose()
6382
6429
  };
6383
6430
  }
6384
6431
  class Text {