three-text 0.2.5 → 0.2.6

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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * three-text v0.2.5
2
+ * three-text v0.2.6
3
3
  * Copyright (C) 2025 Countertype LLC
4
4
  *
5
5
  * This program is free software: you can redistribute it and/or modify
@@ -22,7 +22,7 @@ const isLogEnabled = (() => {
22
22
  }
23
23
  return false;
24
24
  })();
25
- class DebugLogger {
25
+ class Logger {
26
26
  warn(message, ...args) {
27
27
  console.warn(message, ...args);
28
28
  }
@@ -33,7 +33,7 @@ class DebugLogger {
33
33
  isLogEnabled && console.log(message, ...args);
34
34
  }
35
35
  }
36
- const debugLogger = new DebugLogger();
36
+ const logger = new Logger();
37
37
 
38
38
  class PerformanceLogger {
39
39
  constructor() {
@@ -59,7 +59,7 @@ class PerformanceLogger {
59
59
  const endTime = performance.now();
60
60
  const startTime = this.activeTimers.get(name);
61
61
  if (startTime === undefined) {
62
- debugLogger.warn(`Performance timer "${name}" was not started`);
62
+ logger.warn(`Performance timer "${name}" was not started`);
63
63
  return null;
64
64
  }
65
65
  const duration = endTime - startTime;
@@ -562,7 +562,7 @@ class LineBreak {
562
562
  let useHyphenation = hyphenate;
563
563
  if (useHyphenation &&
564
564
  (!hyphenationPatterns || !hyphenationPatterns[language])) {
565
- debugLogger.warn(`Hyphenation patterns for ${language} not available`);
565
+ logger.warn(`Hyphenation patterns for ${language} not available`);
566
566
  useHyphenation = false;
567
567
  }
568
568
  // Calculate initial emergency stretch (TeX default: 0)
@@ -1177,12 +1177,40 @@ class LineBreak {
1177
1177
  }
1178
1178
  }
1179
1179
 
1180
+ // Convert feature objects to HarfBuzz comma-separated format
1181
+ function convertFontFeaturesToString(features) {
1182
+ if (!features || Object.keys(features).length === 0) {
1183
+ return undefined;
1184
+ }
1185
+ const featureStrings = [];
1186
+ for (const [tag, value] of Object.entries(features)) {
1187
+ if (!/^[a-zA-Z0-9]{4}$/.test(tag)) {
1188
+ logger.warn(`Invalid OpenType feature tag: "${tag}". Tags must be exactly 4 alphanumeric characters.`);
1189
+ continue;
1190
+ }
1191
+ if (value === false || value === 0) {
1192
+ featureStrings.push(`${tag}=0`);
1193
+ }
1194
+ else if (value === true || value === 1) {
1195
+ featureStrings.push(tag);
1196
+ }
1197
+ else if (typeof value === 'number' && value > 1) {
1198
+ featureStrings.push(`${tag}=${Math.floor(value)}`);
1199
+ }
1200
+ else {
1201
+ logger.warn(`Invalid value for feature "${tag}": ${value}. Expected boolean or positive number.`);
1202
+ }
1203
+ }
1204
+ return featureStrings.length > 0 ? featureStrings.join(',') : undefined;
1205
+ }
1206
+
1180
1207
  class TextMeasurer {
1181
1208
  static measureTextWidth(loadedFont, text, letterSpacing = 0) {
1182
1209
  const buffer = loadedFont.hb.createBuffer();
1183
1210
  buffer.addText(text);
1184
1211
  buffer.guessSegmentProperties();
1185
- loadedFont.hb.shape(loadedFont.font, buffer);
1212
+ const featuresString = convertFontFeaturesToString(loadedFont.fontFeatures);
1213
+ loadedFont.hb.shape(loadedFont.font, buffer, featuresString);
1186
1214
  const glyphInfos = buffer.json(loadedFont.font);
1187
1215
  const letterSpacingInFontUnits = letterSpacing * loadedFont.upem;
1188
1216
  // Calculate total advance width with letter spacing
@@ -1283,6 +1311,17 @@ const FONT_SIGNATURE_OPEN_TYPE_CFF = 0x4f54544f; // 'OTTO'
1283
1311
  const FONT_SIGNATURE_TRUE_TYPE_COLLECTION = 0x74746366; // 'ttcf'
1284
1312
  const FONT_SIGNATURE_WOFF = 0x774f4646; // 'wOFF'
1285
1313
  const FONT_SIGNATURE_WOFF2 = 0x774f4632; // 'wOF2'
1314
+ // Table Tags
1315
+ const TABLE_TAG_HEAD = 0x68656164; // 'head'
1316
+ const TABLE_TAG_HHEA = 0x68686561; // 'hhea'
1317
+ const TABLE_TAG_OS2 = 0x4f532f32; // 'OS/2'
1318
+ const TABLE_TAG_FVAR = 0x66766172; // 'fvar'
1319
+ const TABLE_TAG_STAT = 0x53544154; // 'STAT'
1320
+ const TABLE_TAG_NAME = 0x6e616d65; // 'name'
1321
+ const TABLE_TAG_CFF = 0x43464620; // 'CFF '
1322
+ const TABLE_TAG_CFF2 = 0x43464632; // 'CFF2'
1323
+ const TABLE_TAG_GSUB = 0x47535542; // 'GSUB'
1324
+ const TABLE_TAG_GPOS = 0x47504f53; // 'GPOS'
1286
1325
 
1287
1326
  class FontMetadataExtractor {
1288
1327
  static extractMetadata(fontBuffer) {
@@ -1299,7 +1338,6 @@ class FontMetadataExtractor {
1299
1338
  if (!validSignatures.includes(sfntVersion)) {
1300
1339
  throw new Error(`Invalid font format. Expected TrueType or OpenType, got signature: 0x${sfntVersion.toString(16)}`);
1301
1340
  }
1302
- const buffer = new Uint8Array(fontBuffer);
1303
1341
  const numTables = view.getUint16(4); // OpenType header - number of tables is at offset 4
1304
1342
  let isCFF = false;
1305
1343
  let headTableOffset = 0;
@@ -1309,30 +1347,28 @@ class FontMetadataExtractor {
1309
1347
  let nameTableOffset = 0;
1310
1348
  let fvarTableOffset = 0;
1311
1349
  for (let i = 0; i < numTables; i++) {
1312
- const tag = new TextDecoder().decode(buffer.slice(12 + i * 16, 12 + i * 16 + 4));
1313
- if (tag === 'CFF ') {
1350
+ const offset = 12 + i * 16;
1351
+ const tag = view.getUint32(offset);
1352
+ if (tag === TABLE_TAG_CFF || tag === TABLE_TAG_CFF2) {
1314
1353
  isCFF = true;
1315
1354
  }
1316
- else if (tag === 'CFF2') {
1317
- isCFF = true;
1355
+ else if (tag === TABLE_TAG_HEAD) {
1356
+ headTableOffset = view.getUint32(offset + 8);
1318
1357
  }
1319
- if (tag === 'head') {
1320
- headTableOffset = view.getUint32(12 + i * 16 + 8);
1358
+ else if (tag === TABLE_TAG_HHEA) {
1359
+ hheaTableOffset = view.getUint32(offset + 8);
1321
1360
  }
1322
- if (tag === 'hhea') {
1323
- hheaTableOffset = view.getUint32(12 + i * 16 + 8);
1361
+ else if (tag === TABLE_TAG_OS2) {
1362
+ os2TableOffset = view.getUint32(offset + 8);
1324
1363
  }
1325
- if (tag === 'OS/2') {
1326
- os2TableOffset = view.getUint32(12 + i * 16 + 8);
1364
+ else if (tag === TABLE_TAG_FVAR) {
1365
+ fvarTableOffset = view.getUint32(offset + 8);
1327
1366
  }
1328
- if (tag === 'fvar') {
1329
- fvarTableOffset = view.getUint32(12 + i * 16 + 8);
1367
+ else if (tag === TABLE_TAG_STAT) {
1368
+ statTableOffset = view.getUint32(offset + 8);
1330
1369
  }
1331
- if (tag === 'STAT') {
1332
- statTableOffset = view.getUint32(12 + i * 16 + 8);
1333
- }
1334
- if (tag === 'name') {
1335
- nameTableOffset = view.getUint32(12 + i * 16 + 8);
1370
+ else if (tag === TABLE_TAG_NAME) {
1371
+ nameTableOffset = view.getUint32(offset + 8);
1336
1372
  }
1337
1373
  }
1338
1374
  const unitsPerEm = headTableOffset
@@ -1375,6 +1411,88 @@ class FontMetadataExtractor {
1375
1411
  axisNames
1376
1412
  };
1377
1413
  }
1414
+ static extractFeatureTags(fontBuffer) {
1415
+ const view = new DataView(fontBuffer);
1416
+ const numTables = view.getUint16(4);
1417
+ let gsubTableOffset = 0;
1418
+ let gposTableOffset = 0;
1419
+ let nameTableOffset = 0;
1420
+ for (let i = 0; i < numTables; i++) {
1421
+ const offset = 12 + i * 16;
1422
+ const tag = view.getUint32(offset);
1423
+ if (tag === TABLE_TAG_GSUB) {
1424
+ gsubTableOffset = view.getUint32(offset + 8);
1425
+ }
1426
+ else if (tag === TABLE_TAG_GPOS) {
1427
+ gposTableOffset = view.getUint32(offset + 8);
1428
+ }
1429
+ else if (tag === TABLE_TAG_NAME) {
1430
+ nameTableOffset = view.getUint32(offset + 8);
1431
+ }
1432
+ }
1433
+ const features = new Set();
1434
+ const featureNames = {};
1435
+ try {
1436
+ if (gsubTableOffset) {
1437
+ const gsubData = this.extractFeatureDataFromTable(view, gsubTableOffset, nameTableOffset);
1438
+ gsubData.features.forEach(f => features.add(f));
1439
+ Object.assign(featureNames, gsubData.names);
1440
+ }
1441
+ if (gposTableOffset) {
1442
+ const gposData = this.extractFeatureDataFromTable(view, gposTableOffset, nameTableOffset);
1443
+ gposData.features.forEach(f => features.add(f));
1444
+ Object.assign(featureNames, gposData.names);
1445
+ }
1446
+ }
1447
+ catch (e) {
1448
+ return undefined;
1449
+ }
1450
+ const featureArray = Array.from(features).sort();
1451
+ if (featureArray.length === 0)
1452
+ return undefined;
1453
+ return {
1454
+ tags: featureArray,
1455
+ names: Object.keys(featureNames).length > 0 ? featureNames : {}
1456
+ };
1457
+ }
1458
+ static extractFeatureDataFromTable(view, tableOffset, nameTableOffset) {
1459
+ const featureListOffset = view.getUint16(tableOffset + 6);
1460
+ const featureListStart = tableOffset + featureListOffset;
1461
+ const featureCount = view.getUint16(featureListStart);
1462
+ const features = [];
1463
+ const names = {};
1464
+ for (let i = 0; i < featureCount; i++) {
1465
+ const recordOffset = featureListStart + 2 + i * 6;
1466
+ // Decode feature tag
1467
+ const tag = String.fromCharCode(view.getUint8(recordOffset), view.getUint8(recordOffset + 1), view.getUint8(recordOffset + 2), view.getUint8(recordOffset + 3));
1468
+ features.push(tag);
1469
+ // Extract feature name for stylistic sets and character variants
1470
+ if (/^(ss\d{2}|cv\d{2})$/.test(tag) && nameTableOffset) {
1471
+ const featureOffset = view.getUint16(recordOffset + 4);
1472
+ const featureTableStart = featureListStart + featureOffset;
1473
+ // Feature table structure:
1474
+ // uint16 FeatureParams offset
1475
+ // uint16 LookupCount
1476
+ // uint16[LookupCount] LookupListIndex
1477
+ const featureParamsOffset = view.getUint16(featureTableStart);
1478
+ // FeatureParams for ss features:
1479
+ // uint16 Version (should be 0)
1480
+ // uint16 UINameID
1481
+ if (featureParamsOffset !== 0) {
1482
+ const paramsStart = featureTableStart + featureParamsOffset;
1483
+ const version = view.getUint16(paramsStart);
1484
+ if (version === 0) {
1485
+ const nameID = view.getUint16(paramsStart + 2);
1486
+ const name = this.getNameFromNameTable(view, nameTableOffset, nameID);
1487
+ if (name) {
1488
+ names[tag] = name;
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+ return { features, names };
1495
+ }
1378
1496
  static extractAxisNames(view, statOffset, nameOffset) {
1379
1497
  try {
1380
1498
  // STAT table structure
@@ -1585,7 +1703,7 @@ class WoffConverter {
1585
1703
  const padding = (4 - (table.origLength % 4)) % 4;
1586
1704
  sfntOffset += padding;
1587
1705
  }
1588
- debugLogger.log('WOFF font decompressed successfully');
1706
+ logger.log('WOFF font decompressed successfully');
1589
1707
  return sfntData.buffer.slice(0, sfntOffset);
1590
1708
  }
1591
1709
  static async decompressZlib(compressedData) {
@@ -1614,7 +1732,7 @@ class FontLoader {
1614
1732
  // Check if this is a WOFF font and decompress if needed
1615
1733
  const format = WoffConverter.detectFormat(fontBuffer);
1616
1734
  if (format === 'woff') {
1617
- debugLogger.log('WOFF font detected, decompressing...');
1735
+ logger.log('WOFF font detected, decompressing...');
1618
1736
  fontBuffer = await WoffConverter.decompressWoff(fontBuffer);
1619
1737
  }
1620
1738
  else if (format === 'woff2') {
@@ -1652,6 +1770,7 @@ class FontLoader {
1652
1770
  };
1653
1771
  }
1654
1772
  }
1773
+ const featureData = FontMetadataExtractor.extractFeatureTags(fontBuffer);
1655
1774
  return {
1656
1775
  hb,
1657
1776
  fontBlob,
@@ -1662,11 +1781,13 @@ class FontLoader {
1662
1781
  metrics,
1663
1782
  fontVariations,
1664
1783
  isVariable,
1665
- variationAxes
1784
+ variationAxes,
1785
+ availableFeatures: featureData?.tags,
1786
+ featureNames: featureData?.names
1666
1787
  };
1667
1788
  }
1668
1789
  catch (error) {
1669
- debugLogger.error('Failed to load font:', error);
1790
+ logger.error('Failed to load font:', error);
1670
1791
  throw error;
1671
1792
  }
1672
1793
  finally {
@@ -1687,7 +1808,7 @@ class FontLoader {
1687
1808
  }
1688
1809
  }
1689
1810
  catch (error) {
1690
- debugLogger.error('Error destroying font resources:', error);
1811
+ logger.error('Error destroying font resources:', error);
1691
1812
  }
1692
1813
  }
1693
1814
  }
@@ -2089,7 +2210,7 @@ class Tessellator {
2089
2210
  if (valid.length === 0) {
2090
2211
  return { triangles: { vertices: [], indices: [] }, contours: [] };
2091
2212
  }
2092
- debugLogger.log(`Tessellator: removeOverlaps=${removeOverlaps}, processing ${valid.length} paths`);
2213
+ logger.log(`Tessellator: removeOverlaps=${removeOverlaps}, processing ${valid.length} paths`);
2093
2214
  return this.tessellate(valid, removeOverlaps, isCFF);
2094
2215
  }
2095
2216
  tessellate(paths, removeOverlaps, isCFF) {
@@ -2099,19 +2220,19 @@ class Tessellator {
2099
2220
  : paths;
2100
2221
  let contours = this.pathsToContours(normalizedPaths);
2101
2222
  if (removeOverlaps) {
2102
- debugLogger.log('Two-pass: boundary extraction then triangulation');
2223
+ logger.log('Two-pass: boundary extraction then triangulation');
2103
2224
  // Extract boundaries to remove overlaps
2104
2225
  const boundaryResult = this.performTessellation(contours, 'boundary');
2105
2226
  if (!boundaryResult) {
2106
- debugLogger.warn('libtess returned empty result from boundary pass');
2227
+ logger.warn('libtess returned empty result from boundary pass');
2107
2228
  return { triangles: { vertices: [], indices: [] }, contours: [] };
2108
2229
  }
2109
2230
  // Convert boundary elements back to contours
2110
2231
  contours = this.boundaryToContours(boundaryResult);
2111
- debugLogger.log(`Boundary pass created ${contours.length} contours. Starting triangulation pass.`);
2232
+ logger.log(`Boundary pass created ${contours.length} contours. Starting triangulation pass.`);
2112
2233
  }
2113
2234
  else {
2114
- debugLogger.log(`Single-pass triangulation for ${isCFF ? 'CFF' : 'TTF'}`);
2235
+ logger.log(`Single-pass triangulation for ${isCFF ? 'CFF' : 'TTF'}`);
2115
2236
  }
2116
2237
  // Triangulate the contours
2117
2238
  const triangleResult = this.performTessellation(contours, 'triangles');
@@ -2119,7 +2240,7 @@ class Tessellator {
2119
2240
  const warning = removeOverlaps
2120
2241
  ? 'libtess returned empty result from triangulation pass'
2121
2242
  : 'libtess returned empty result from single-pass triangulation';
2122
- debugLogger.warn(warning);
2243
+ logger.warn(warning);
2123
2244
  return { triangles: { vertices: [], indices: [] }, contours };
2124
2245
  }
2125
2246
  return {
@@ -2174,7 +2295,7 @@ class Tessellator {
2174
2295
  return idx;
2175
2296
  });
2176
2297
  tess.gluTessCallback(libtess_minExports.gluEnum.GLU_TESS_ERROR, (errno) => {
2177
- debugLogger.warn(`libtess error: ${errno}`);
2298
+ logger.warn(`libtess error: ${errno}`);
2178
2299
  });
2179
2300
  tess.gluTessNormal(0, 0, 1);
2180
2301
  tess.gluTessBeginPolygon(null);
@@ -3196,7 +3317,7 @@ class DrawCallbackHandler {
3196
3317
  }
3197
3318
  }
3198
3319
  catch (error) {
3199
- debugLogger.warn('Error destroying draw callbacks:', error);
3320
+ logger.warn('Error destroying draw callbacks:', error);
3200
3321
  }
3201
3322
  this.collector = undefined;
3202
3323
  }
@@ -3486,7 +3607,8 @@ class TextShaper {
3486
3607
  }
3487
3608
  buffer.addText(lineInfo.text);
3488
3609
  buffer.guessSegmentProperties();
3489
- this.loadedFont.hb.shape(this.loadedFont.font, buffer);
3610
+ const featuresString = convertFontFeaturesToString(this.loadedFont.fontFeatures);
3611
+ this.loadedFont.hb.shape(this.loadedFont.font, buffer, featuresString);
3490
3612
  const glyphInfos = buffer.json(this.loadedFont.font);
3491
3613
  buffer.destroy();
3492
3614
  const clusters = [];
@@ -3565,12 +3687,10 @@ class TextShaper {
3565
3687
  const stretchFactor = SPACE_STRETCH_RATIO;
3566
3688
  const shrinkFactor = SPACE_SHRINK_RATIO;
3567
3689
  if (lineInfo.adjustmentRatio > 0) {
3568
- spaceAdjustment =
3569
- lineInfo.adjustmentRatio * width * stretchFactor;
3690
+ spaceAdjustment = lineInfo.adjustmentRatio * width * stretchFactor;
3570
3691
  }
3571
3692
  else if (lineInfo.adjustmentRatio < 0) {
3572
- spaceAdjustment =
3573
- lineInfo.adjustmentRatio * width * shrinkFactor;
3693
+ spaceAdjustment = lineInfo.adjustmentRatio * width * shrinkFactor;
3574
3694
  }
3575
3695
  }
3576
3696
  return spaceAdjustment;
@@ -4495,12 +4615,16 @@ class Text {
4495
4615
  const baseFontKey = typeof options.font === 'string'
4496
4616
  ? options.font
4497
4617
  : `buffer-${Text.generateFontContentHash(options.font)}`;
4498
- const fontKey = options.fontVariations
4499
- ? `${baseFontKey}_${JSON.stringify(options.fontVariations)}`
4500
- : baseFontKey;
4618
+ let fontKey = baseFontKey;
4619
+ if (options.fontVariations) {
4620
+ fontKey += `_var_${JSON.stringify(options.fontVariations)}`;
4621
+ }
4622
+ if (options.fontFeatures) {
4623
+ fontKey += `_feat_${JSON.stringify(options.fontFeatures)}`;
4624
+ }
4501
4625
  let loadedFont = Text.fontCache.get(fontKey);
4502
4626
  if (!loadedFont) {
4503
- loadedFont = await Text.loadAndCacheFont(fontKey, options.font, options.fontVariations);
4627
+ loadedFont = await Text.loadAndCacheFont(fontKey, options.font, options.fontVariations, options.fontFeatures);
4504
4628
  }
4505
4629
  const text = new Text({ maxCacheSizeMB: options.maxCacheSizeMB });
4506
4630
  text.setLoadedFont(loadedFont);
@@ -4514,12 +4638,11 @@ class Text {
4514
4638
  measureTextWidth: (textString, letterSpacing) => text.measureTextWidth(textString, letterSpacing)
4515
4639
  };
4516
4640
  }
4517
- static async loadAndCacheFont(fontKey, font, fontVariations) {
4641
+ static async loadAndCacheFont(fontKey, font, fontVariations, fontFeatures) {
4518
4642
  const tempText = new Text();
4519
- await tempText.loadFont(font, fontVariations);
4643
+ await tempText.loadFont(font, fontVariations, fontFeatures);
4520
4644
  const loadedFont = tempText.getLoadedFont();
4521
4645
  Text.fontCache.set(fontKey, loadedFont);
4522
- // Don't destroy tempText - the cached font references its HarfBuzz objects
4523
4646
  return loadedFont;
4524
4647
  }
4525
4648
  static generateFontContentHash(buffer) {
@@ -4538,10 +4661,13 @@ class Text {
4538
4661
  const contentHash = Text.generateFontContentHash(loadedFont._buffer);
4539
4662
  this.currentFontId = `font_${contentHash}`;
4540
4663
  if (loadedFont.fontVariations) {
4541
- this.currentFontId += `_${JSON.stringify(loadedFont.fontVariations)}`;
4664
+ this.currentFontId += `_var_${JSON.stringify(loadedFont.fontVariations)}`;
4665
+ }
4666
+ if (loadedFont.fontFeatures) {
4667
+ this.currentFontId += `_feat_${JSON.stringify(loadedFont.fontFeatures)}`;
4542
4668
  }
4543
4669
  }
4544
- async loadFont(fontSrc, fontVariations) {
4670
+ async loadFont(fontSrc, fontVariations, fontFeatures) {
4545
4671
  perfLogger.start('Text.loadFont', {
4546
4672
  fontSrc: typeof fontSrc === 'string' ? fontSrc : `buffer(${fontSrc.byteLength})`
4547
4673
  });
@@ -4562,14 +4688,20 @@ class Text {
4562
4688
  this.destroy();
4563
4689
  }
4564
4690
  this.loadedFont = await this.fontLoader.loadFont(fontBuffer, fontVariations);
4691
+ if (fontFeatures) {
4692
+ this.loadedFont.fontFeatures = fontFeatures;
4693
+ }
4565
4694
  const contentHash = Text.generateFontContentHash(fontBuffer);
4566
4695
  this.currentFontId = `font_${contentHash}`;
4567
4696
  if (fontVariations) {
4568
- this.currentFontId += `_${JSON.stringify(fontVariations)}`;
4697
+ this.currentFontId += `_var_${JSON.stringify(fontVariations)}`;
4698
+ }
4699
+ if (fontFeatures) {
4700
+ this.currentFontId += `_feat_${JSON.stringify(fontFeatures)}`;
4569
4701
  }
4570
4702
  }
4571
4703
  catch (error) {
4572
- debugLogger.error('Failed to load font:', error);
4704
+ logger.error('Failed to load font:', error);
4573
4705
  throw error;
4574
4706
  }
4575
4707
  finally {
@@ -4644,7 +4776,7 @@ class Text {
4644
4776
  };
4645
4777
  }
4646
4778
  catch (error) {
4647
- debugLogger.warn(`Failed to load patterns for ${language}: ${error}`);
4779
+ logger.warn(`Failed to load patterns for ${language}: ${error}`);
4648
4780
  return {
4649
4781
  ...options,
4650
4782
  layout: {
@@ -4908,7 +5040,7 @@ class Text {
4908
5040
  Text.patternCache.set(language, pattern);
4909
5041
  }
4910
5042
  catch (error) {
4911
- debugLogger.warn(`Failed to pre-load patterns for ${language}: ${error}`);
5043
+ logger.warn(`Failed to pre-load patterns for ${language}: ${error}`);
4912
5044
  }
4913
5045
  }
4914
5046
  }));
@@ -4970,7 +5102,7 @@ class Text {
4970
5102
  FontLoader.destroyFont(currentFont);
4971
5103
  }
4972
5104
  catch (error) {
4973
- debugLogger.warn('Error destroying HarfBuzz objects:', error);
5105
+ logger.warn('Error destroying HarfBuzz objects:', error);
4974
5106
  }
4975
5107
  finally {
4976
5108
  this.loadedFont = undefined;