@cantoo/fontkit 2.0.5 → 2.0.7

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.
Files changed (153) hide show
  1. package/dist/browser-module.d.ts +239 -0
  2. package/dist/browser-module.mjs +4423 -1117
  3. package/dist/browser-module.mjs.map +1 -1
  4. package/dist/browser.cjs +4424 -1117
  5. package/dist/browser.cjs.map +1 -1
  6. package/dist/browser.d.ts +239 -0
  7. package/dist/fontkit.umd.d.ts +239 -0
  8. package/dist/fontkit.umd.js +22076 -0
  9. package/dist/fontkit.umd.js.map +7 -0
  10. package/dist/index.d.ts +239 -0
  11. package/dist/main.cjs +4424 -1117
  12. package/dist/main.cjs.map +1 -1
  13. package/dist/main.d.ts +239 -0
  14. package/dist/module.d.ts +239 -0
  15. package/dist/module.mjs +4423 -1117
  16. package/dist/module.mjs.map +1 -1
  17. package/package.json +22 -5
  18. package/src/CmapProcessor.js +59 -20
  19. package/src/DFont.js +114 -14
  20. package/src/TTFFont.js +259 -57
  21. package/src/TrueTypeCollection.js +54 -10
  22. package/src/WOFF2Font.js +128 -16
  23. package/src/WOFFFont.js +45 -8
  24. package/src/aat/AATFeatureMap.js +62 -18
  25. package/src/aat/AATLayoutEngine.js +33 -1
  26. package/src/aat/AATLookupTable.js +22 -4
  27. package/src/aat/AATMorxProcessor.js +189 -52
  28. package/src/aat/AATStateMachine.js +32 -0
  29. package/src/base.js +19 -0
  30. package/src/cff/CFFCharsets.js +3 -0
  31. package/src/cff/CFFDict.js +79 -9
  32. package/src/cff/CFFEncodings.js +2 -0
  33. package/src/cff/CFFFont.js +119 -23
  34. package/src/cff/CFFIndex.js +62 -5
  35. package/src/cff/CFFOperand.js +52 -17
  36. package/src/cff/CFFPointer.js +41 -2
  37. package/src/cff/CFFPrivateDict.js +14 -4
  38. package/src/cff/CFFStandardStrings.js +1 -0
  39. package/src/cff/CFFTop.js +103 -10
  40. package/src/decorators.js +32 -13
  41. package/src/encodings.js +22 -5
  42. package/src/fs.js +10 -0
  43. package/src/glyph/BBox.js +14 -0
  44. package/src/glyph/CFFGlyph.js +290 -148
  45. package/src/glyph/COLRGlyph.js +38 -3
  46. package/src/glyph/Glyph.js +68 -9
  47. package/src/glyph/GlyphVariationProcessor.js +135 -34
  48. package/src/glyph/Path.js +107 -20
  49. package/src/glyph/SBIXGlyph.js +57 -8
  50. package/src/glyph/StandardNames.js +1 -0
  51. package/src/glyph/TTFGlyph.js +176 -29
  52. package/src/glyph/TTFGlyphEncoder.js +32 -0
  53. package/src/glyph/WOFF2Glyph.js +23 -4
  54. package/src/layout/GlyphPosition.js +6 -0
  55. package/src/layout/GlyphRun.js +25 -7
  56. package/src/layout/KernProcessor.js +62 -3
  57. package/src/layout/LayoutEngine.js +86 -12
  58. package/src/layout/Script.js +27 -1
  59. package/src/layout/UnicodeLayoutEngine.js +29 -4
  60. package/src/opentype/GPOSProcessor.js +156 -45
  61. package/src/opentype/GSUBProcessor.js +92 -33
  62. package/src/opentype/GlyphInfo.js +52 -4
  63. package/src/opentype/GlyphIterator.js +51 -2
  64. package/src/opentype/OTLayoutEngine.js +66 -8
  65. package/src/opentype/OTProcessor.js +283 -70
  66. package/src/opentype/ShapingPlan.js +51 -3
  67. package/src/opentype/shapers/ArabicShaper.js +19 -0
  68. package/src/opentype/shapers/DefaultShaper.js +51 -7
  69. package/src/opentype/shapers/HangulShaper.js +85 -7
  70. package/src/opentype/shapers/HebrewShaper.js +45 -11
  71. package/src/opentype/shapers/IndicShaper.js +141 -26
  72. package/src/opentype/shapers/ThaiShaper.js +35 -4
  73. package/src/opentype/shapers/UniversalShaper.js +83 -17
  74. package/src/opentype/shapers/index.js +15 -2
  75. package/src/packages/unicode-properties/data-trie.js +1 -0
  76. package/src/packages/unicode-properties/data.js +12 -0
  77. package/src/packages/unicode-properties/dist/index.cjs +22 -9
  78. package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
  79. package/src/packages/unicode-properties/dist/index.mjs +21 -9
  80. package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
  81. package/src/packages/unicode-properties/index.js +65 -0
  82. package/src/packages/unicode-properties/package.json +1 -1
  83. package/src/packages/unicode-trie/builder.js +128 -7
  84. package/src/packages/unicode-trie/dist/builder.cjs +129 -16
  85. package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
  86. package/src/packages/unicode-trie/dist/builder.mjs +128 -16
  87. package/src/packages/unicode-trie/dist/builder.mjs.map +2 -2
  88. package/src/packages/unicode-trie/dist/index.cjs +22 -9
  89. package/src/packages/unicode-trie/dist/index.cjs.map +2 -2
  90. package/src/packages/unicode-trie/dist/index.mjs +21 -9
  91. package/src/packages/unicode-trie/dist/index.mjs.map +2 -2
  92. package/src/packages/unicode-trie/index.js +62 -10
  93. package/src/packages/unicode-trie/package.json +1 -1
  94. package/src/packages/unicode-trie/swap.js +13 -0
  95. package/src/subset/CFFSubset.js +112 -22
  96. package/src/subset/Subset.js +21 -1
  97. package/src/subset/TTFSubset.js +79 -36
  98. package/src/tables/BASE.js +1 -0
  99. package/src/tables/COLR.js +1 -0
  100. package/src/tables/CPAL.js +1 -0
  101. package/src/tables/DSIG.js +1 -0
  102. package/src/tables/EBDT.js +10 -3
  103. package/src/tables/EBLC.js +67 -7
  104. package/src/tables/GDEF.js +1 -0
  105. package/src/tables/GPOS.js +79 -16
  106. package/src/tables/GSUB.js +14 -3
  107. package/src/tables/HVAR.js +51 -5
  108. package/src/tables/JSTF.js +3 -2
  109. package/src/tables/LTSH.js +1 -0
  110. package/src/tables/OS2.js +1 -0
  111. package/src/tables/PCLT.js +1 -0
  112. package/src/tables/STAT.js +46 -6
  113. package/src/tables/VDMX.js +1 -0
  114. package/src/tables/VORG.js +1 -0
  115. package/src/tables/WOFF2Directory.js +56 -8
  116. package/src/tables/WOFFDirectory.js +14 -2
  117. package/src/tables/aat.js +160 -35
  118. package/src/tables/avar.js +1 -0
  119. package/src/tables/bsln.js +3 -2
  120. package/src/tables/cmap.js +26 -6
  121. package/src/tables/cvt.js +1 -0
  122. package/src/tables/directory.js +44 -16
  123. package/src/tables/feat.js +24 -2
  124. package/src/tables/fpgm.js +1 -0
  125. package/src/tables/fvar.js +32 -4
  126. package/src/tables/gasp.js +1 -0
  127. package/src/tables/glyf.js +1 -0
  128. package/src/tables/gvar.js +29 -2
  129. package/src/tables/hdmx.js +12 -1
  130. package/src/tables/head.js +1 -0
  131. package/src/tables/hhea.js +1 -0
  132. package/src/tables/hmtx.js +18 -2
  133. package/src/tables/index.js +1 -0
  134. package/src/tables/just.js +15 -4
  135. package/src/tables/kern.js +43 -6
  136. package/src/tables/loca.js +21 -8
  137. package/src/tables/maxp.js +1 -0
  138. package/src/tables/morx.js +14 -7
  139. package/src/tables/name.js +104 -23
  140. package/src/tables/opbd.js +2 -1
  141. package/src/tables/opentype.js +39 -4
  142. package/src/tables/post.js +8 -1
  143. package/src/tables/prep.js +1 -0
  144. package/src/tables/sbix.js +12 -1
  145. package/src/tables/variations.js +23 -3
  146. package/src/tables/vhea.js +1 -0
  147. package/src/tables/vmtx.js +18 -2
  148. package/src/utils.js +15 -0
  149. package/types/assets.d.ts +13 -0
  150. package/types/deps.d.ts +83 -0
  151. package/types/fontkit.d.ts +1324 -0
  152. package/types/index.d.ts +239 -0
  153. package/types/restructure.d.ts +354 -0
@@ -1,49 +1,107 @@
1
1
  import GlyphIterator from './GlyphIterator';
2
2
  import * as Script from '../layout/Script';
3
3
 
4
+ /** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
5
+ /** @typedef {import('../../types/fontkit').OTLayoutTable} OTLayoutTable */
6
+ /** @typedef {import('../../types/fontkit').OTScriptRecord} OTScriptRecord */
7
+ /** @typedef {import('../../types/fontkit').OTScript} OTScript */
8
+ /** @typedef {import('../../types/fontkit').OTLangSys} OTLangSys */
9
+ /** @typedef {import('../../types/fontkit').OTFeature} OTFeature */
10
+ /** @typedef {import('../../types/fontkit').OTLookup} OTLookup */
11
+ /** @typedef {import('../../types/fontkit').FeatureLookup} FeatureLookup */
12
+ /** @typedef {import('../../types/fontkit').CoverageTable} CoverageTable */
13
+ /** @typedef {import('../../types/fontkit').ClassDefTable} ClassDefTable */
14
+ /** @typedef {import('../../types/fontkit').ContextSubtable} ContextSubtable */
15
+ /** @typedef {import('../../types/fontkit').ChainContextSubtable} ChainContextSubtable */
16
+ /** @typedef {import('../../types/fontkit').LookupRecord} LookupRecord */
17
+ /** @typedef {import('../../types/fontkit').FeatureVariationCondition} FeatureVariationCondition */
18
+ /** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
19
+ /** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
20
+ /** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
21
+ /** @typedef {import('../../types/fontkit').LanguageTag} LanguageTag */
22
+ /** @typedef {import('../../types/fontkit').TextDirection} TextDirection */
23
+ /** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
24
+ /** @typedef {import('../../types/fontkit').MarkFilteringSet} MarkFilteringSet */
25
+ /** @typedef {import('restructure').StructValue} StructValue */
26
+
4
27
  const DEFAULT_SCRIPTS = ['DFLT', 'dflt', 'latn'];
5
28
 
6
29
  export default class OTProcessor {
30
+ /**
31
+ * @param {LayoutFont} font
32
+ * @param {OTLayoutTable} table
33
+ */
7
34
  constructor(font, table) {
35
+ /** @type {LayoutFont} */
8
36
  this.font = font;
37
+ /** @type {OTLayoutTable} */
9
38
  this.table = table;
10
39
 
40
+ /** @type {OTScript | null} */
11
41
  this.script = null;
42
+ /** @type {string | null} */
12
43
  this.scriptTag = null;
13
44
 
45
+ /** @type {OTLangSys | null} */
14
46
  this.language = null;
47
+ /** @type {string | null} */
15
48
  this.languageTag = null;
16
49
 
50
+ /** @type {TextDirection | undefined} */
51
+ this.direction = undefined;
52
+
53
+ /** @type {Record<string, OTFeature>} */
17
54
  this.features = {};
55
+ /** @type {Record<string, unknown>} */
18
56
  this.lookups = {};
19
57
 
58
+ /** @type {FeatureMap | null} */
59
+ this.userFeatures = null;
60
+
20
61
  // FeatureVariations apply at the current location; default to normalized
21
62
  // origin when no variation is set (HarfBuzz behavior). Without this,
22
63
  // variable fonts skip FeatureVariations until getVariation() is called.
23
64
  let coords = font._variationProcessor?.normalizedCoords
24
65
  ?? (font.fvar ? new Array(font.fvar.axis.length).fill(0) : null);
66
+ /** @type {number} */
25
67
  this.variationsIndex = coords ? this.findVariationsIndex(coords) : -1;
26
68
 
27
69
  // initialize to default script + language
28
70
  this.selectScript();
29
71
 
30
72
  // current context (set by applyFeatures)
73
+ /** @type {GlyphInfoLike[]} */
31
74
  this.glyphs = [];
75
+ /** @type {GlyphPositionLike[] | null | undefined} */
32
76
  this.positions = []; // only used by GPOS
77
+ /** @type {number} */
33
78
  this.ligatureID = 1;
79
+ /** @type {string | null} */
34
80
  this.currentFeature = null;
81
+ /** @type {GlyphIterator | undefined} */
82
+ this.glyphIterator = undefined;
83
+ /** @type {Map<CoverageTable, MarkFilteringSet> | undefined} */
84
+ this._markFilteringCache = undefined;
35
85
  }
36
86
 
87
+ /**
88
+ * @param {ScriptTag | string[] | null | undefined} script
89
+ * @returns {OTScriptRecord | null}
90
+ */
37
91
  findScript(script) {
38
92
  if (this.table.scriptList == null) {
39
93
  return null;
40
94
  }
41
95
 
96
+ /** @type {string[]} */
97
+ let scripts;
42
98
  if (!Array.isArray(script)) {
43
- script = [script];
99
+ scripts = script == null ? [] : [script];
100
+ } else {
101
+ scripts = script;
44
102
  }
45
103
 
46
- for (let s of script) {
104
+ for (let s of scripts) {
47
105
  for (let entry of this.table.scriptList) {
48
106
  if (entry.tag === s) {
49
107
  return entry;
@@ -54,8 +112,15 @@ export default class OTProcessor {
54
112
  return null;
55
113
  }
56
114
 
115
+ /**
116
+ * @param {ScriptTag | string[] | null | undefined} [script]
117
+ * @param {LanguageTag | null | undefined} [language]
118
+ * @param {TextDirection | null | undefined} [direction]
119
+ * @returns {string | null}
120
+ */
57
121
  selectScript(script, language, direction) {
58
122
  let changed = false;
123
+ /** @type {OTScriptRecord | null | undefined} */
59
124
  let entry;
60
125
  if (!this.script || script !== this.scriptTag) {
61
126
  entry = this.findScript(script);
@@ -85,17 +150,19 @@ export default class OTProcessor {
85
150
  if (!language || language !== this.languageTag) {
86
151
  this.language = null;
87
152
 
88
- for (let lang of this.script.langSysRecords) {
89
- if (lang.tag === language) {
90
- this.language = lang.langSys;
91
- this.languageTag = lang.tag;
92
- break;
153
+ if (this.script) {
154
+ for (let lang of this.script.langSysRecords) {
155
+ if (lang.tag === language) {
156
+ this.language = lang.langSys;
157
+ this.languageTag = lang.tag;
158
+ break;
159
+ }
93
160
  }
94
- }
95
161
 
96
- if (!this.language) {
97
- this.language = this.script.defaultLangSys;
98
- this.languageTag = null;
162
+ if (!this.language) {
163
+ this.language = this.script.defaultLangSys;
164
+ this.languageTag = null;
165
+ }
99
166
  }
100
167
 
101
168
  changed = true;
@@ -116,7 +183,13 @@ export default class OTProcessor {
116
183
  return this.scriptTag;
117
184
  }
118
185
 
186
+ /**
187
+ * @param {string[]} [userFeatures]
188
+ * @param {number[] | null | undefined} [exclude]
189
+ * @returns {FeatureLookup[]}
190
+ */
119
191
  lookupsForFeatures(userFeatures = [], exclude) {
192
+ /** @type {FeatureLookup[]} */
120
193
  let lookups = [];
121
194
  for (let tag of userFeatures) {
122
195
  let feature = this.features[tag];
@@ -141,12 +214,21 @@ export default class OTProcessor {
141
214
  return lookups;
142
215
  }
143
216
 
217
+ /**
218
+ * @param {number} featureIndex
219
+ * @returns {OTFeature | null}
220
+ */
144
221
  substituteFeatureForVariations(featureIndex) {
145
222
  if (this.variationsIndex === -1) {
146
223
  return null;
147
224
  }
148
225
 
149
- let record = this.table.featureVariations.featureVariationRecords[this.variationsIndex];
226
+ let variations = this.table.featureVariations;
227
+ if (!variations) {
228
+ return null;
229
+ }
230
+
231
+ let record = variations.featureVariationRecords[this.variationsIndex];
150
232
  let substitutions = record.featureTableSubstitution.substitutions;
151
233
  for (let substitution of substitutions) {
152
234
  if (substitution.featureIndex === featureIndex) {
@@ -157,6 +239,10 @@ export default class OTProcessor {
157
239
  return null;
158
240
  }
159
241
 
242
+ /**
243
+ * @param {number[]} coords
244
+ * @returns {number}
245
+ */
160
246
  findVariationsIndex(coords) {
161
247
  let variations = this.table.featureVariations;
162
248
  if (!variations) {
@@ -174,18 +260,33 @@ export default class OTProcessor {
174
260
  return -1;
175
261
  }
176
262
 
263
+ /**
264
+ * @param {FeatureVariationCondition[]} conditions
265
+ * @param {number[]} coords
266
+ * @returns {boolean}
267
+ */
177
268
  variationConditionsMatch(conditions, coords) {
178
- return conditions.every((condition) => {
269
+ return conditions.every(/** @param {FeatureVariationCondition} condition */ (condition) => {
179
270
  let coord = condition.axisIndex < coords.length ? coords[condition.axisIndex] : 0;
180
271
  return condition.filterRangeMinValue <= coord && coord <= condition.filterRangeMaxValue;
181
272
  });
182
273
  }
183
274
 
275
+ /**
276
+ * @param {string[]} userFeatures
277
+ * @param {GlyphInfoLike[]} glyphs
278
+ * @param {GlyphPositionLike[] | null | undefined} [advances]
279
+ */
184
280
  applyFeatures(userFeatures, glyphs, advances) {
185
281
  let lookups = this.lookupsForFeatures(userFeatures);
186
282
  this.applyLookups(lookups, glyphs, advances);
187
283
  }
188
284
 
285
+ /**
286
+ * @param {FeatureLookup[]} lookups
287
+ * @param {GlyphInfoLike[]} glyphs
288
+ * @param {GlyphPositionLike[] | null | undefined} [positions]
289
+ */
189
290
  applyLookups(lookups, glyphs, positions) {
190
291
  this.glyphs = glyphs;
191
292
  this.positions = positions;
@@ -203,7 +304,8 @@ export default class OTProcessor {
203
304
  );
204
305
 
205
306
  while (this.glyphIterator.index >= 0 && this.glyphIterator.index < glyphs.length) {
206
- if (feature in this.glyphIterator.cur.features) {
307
+ let cur = this.glyphIterator.cur;
308
+ if (cur && feature in cur.features) {
207
309
  for (let table of lookup.subTables) {
208
310
  if (this.applyLookup(lookup.lookupType, table)) {
209
311
  break;
@@ -215,28 +317,46 @@ export default class OTProcessor {
215
317
  }
216
318
  }
217
319
 
218
- // Subclasses may reverse iteration for specific lookup types (e.g. GSUB Type 8).
320
+ /**
321
+ * Subclasses may reverse iteration for specific lookup types (e.g. GSUB Type 8).
322
+ * @param {OTLookup} _lookup
323
+ * @returns {number}
324
+ */
219
325
  lookupDirection(_lookup) {
220
326
  return 1;
221
327
  }
222
328
 
329
+ /**
330
+ * @param {number} _lookup
331
+ * @param {StructValue} _table
332
+ * @returns {boolean}
333
+ */
223
334
  applyLookup(_lookup, _table) {
224
335
  throw new Error('applyLookup must be implemented by subclasses');
225
336
  }
226
337
 
338
+ /**
339
+ * @param {LookupRecord[]} lookupRecords
340
+ * @returns {boolean}
341
+ */
227
342
  applyLookupList(lookupRecords) {
228
- let options = this.glyphIterator.options;
229
- let markFilteringSet = this.glyphIterator.markFilteringSet;
230
- let glyphIndex = this.glyphIterator.index;
343
+ let glyphIterator = this.glyphIterator;
344
+ if (!glyphIterator) {
345
+ return false;
346
+ }
347
+
348
+ let options = glyphIterator.options;
349
+ let markFilteringSet = glyphIterator.markFilteringSet;
350
+ let glyphIndex = glyphIterator.index;
231
351
 
232
352
  for (let lookupRecord of lookupRecords) {
233
353
  // Reset flags and find glyph index for this lookup record
234
- this.glyphIterator.reset(options, glyphIndex, markFilteringSet);
235
- this.glyphIterator.increment(lookupRecord.sequenceIndex);
354
+ glyphIterator.reset(options, glyphIndex, markFilteringSet);
355
+ glyphIterator.increment(lookupRecord.sequenceIndex);
236
356
 
237
357
  // Get the lookup and setup flags for subtables
238
358
  let lookup = this.table.lookupList.get(lookupRecord.lookupListIndex);
239
- this.glyphIterator.reset(lookup.flags, this.glyphIterator.index, this.getMarkFilteringSet(lookup));
359
+ glyphIterator.reset(lookup.flags, glyphIterator.index, this.getMarkFilteringSet(lookup));
240
360
 
241
361
  // Apply lookup subtables until one matches
242
362
  for (let table of lookup.subTables) {
@@ -246,16 +366,20 @@ export default class OTProcessor {
246
366
  }
247
367
  }
248
368
 
249
- this.glyphIterator.reset(options, glyphIndex, markFilteringSet);
369
+ glyphIterator.reset(options, glyphIndex, markFilteringSet);
250
370
  return true;
251
371
  }
252
372
 
373
+ /**
374
+ * @param {OTLookup} lookup
375
+ * @returns {MarkFilteringSet | null}
376
+ */
253
377
  getMarkFilteringSet(lookup) {
254
- if (!lookup.flags.flags.useMarkFilteringSet) {
378
+ if (!lookup.flags.flags?.useMarkFilteringSet) {
255
379
  return null;
256
380
  }
257
381
 
258
- let coverage = this.font.GDEF?.markGlyphSetsDef?.coverage?.[lookup.markFilteringSet];
382
+ let coverage = this.font.GDEF?.markGlyphSetsDef?.coverage?.[lookup.markFilteringSet ?? -1];
259
383
  if (!coverage) {
260
384
  return null;
261
385
  }
@@ -264,25 +388,36 @@ export default class OTProcessor {
264
388
  let cache = (this._markFilteringCache ??= new Map());
265
389
  let filter = cache.get(coverage);
266
390
  if (!filter) {
267
- filter = { has: id => this.coverageIndex(coverage, id) >= 0 };
391
+ filter = { has: /** @param {number} id */ id => this.coverageIndex(coverage, id) >= 0 };
268
392
  cache.set(coverage, filter);
269
393
  }
270
394
  return filter;
271
395
  }
272
396
 
397
+ /**
398
+ * @param {CoverageTable} coverage
399
+ * @param {number | null | undefined} [glyph]
400
+ * @returns {number}
401
+ */
273
402
  coverageIndex(coverage, glyph) {
274
403
  if (glyph == null) {
275
- glyph = this.glyphIterator.cur.id;
404
+ let cur = this.glyphIterator?.cur;
405
+ if (!cur) {
406
+ return -1;
407
+ }
408
+ glyph = cur.id;
276
409
  }
277
410
 
278
411
  switch (coverage.version) {
279
412
  case 1:
280
- return coverage.glyphs.indexOf(glyph);
413
+ return coverage.glyphs ? coverage.glyphs.indexOf(glyph) : -1;
281
414
 
282
415
  case 2:
283
- for (let range of coverage.rangeRecords) {
284
- if (range.start <= glyph && glyph <= range.end) {
285
- return range.startCoverageIndex + glyph - range.start;
416
+ if (coverage.rangeRecords) {
417
+ for (let range of coverage.rangeRecords) {
418
+ if (range.start <= glyph && glyph <= range.end) {
419
+ return range.startCoverageIndex + glyph - range.start;
420
+ }
286
421
  }
287
422
  }
288
423
 
@@ -292,21 +427,35 @@ export default class OTProcessor {
292
427
  return -1;
293
428
  }
294
429
 
430
+ /**
431
+ * @template T
432
+ * @param {number} sequenceIndex
433
+ * @param {T[]} sequence
434
+ * @param {(component: T, glyph: GlyphInfoLike) => boolean} fn
435
+ * @param {number[] | null | undefined} [matched]
436
+ * @returns {boolean | number[]}
437
+ */
295
438
  match(sequenceIndex, sequence, fn, matched) {
296
- let pos = this.glyphIterator.index;
297
- let glyph = this.glyphIterator.increment(sequenceIndex);
439
+ let glyphIterator = this.glyphIterator;
440
+ if (!glyphIterator) {
441
+ return false;
442
+ }
443
+
444
+ let pos = glyphIterator.index;
445
+ /** @type {GlyphInfoLike | null | undefined} */
446
+ let glyph = glyphIterator.increment(sequenceIndex);
298
447
  let idx = 0;
299
448
 
300
449
  while (idx < sequence.length && glyph && fn(sequence[idx], glyph)) {
301
450
  if (matched) {
302
- matched.push(this.glyphIterator.index);
451
+ matched.push(glyphIterator.index);
303
452
  }
304
453
 
305
454
  idx++;
306
- glyph = this.glyphIterator.next();
455
+ glyph = glyphIterator.next();
307
456
  }
308
457
 
309
- this.glyphIterator.index = pos;
458
+ glyphIterator.index = pos;
310
459
  if (idx < sequence.length) {
311
460
  return false;
312
461
  }
@@ -314,32 +463,58 @@ export default class OTProcessor {
314
463
  return matched || true;
315
464
  }
316
465
 
466
+ /**
467
+ * @param {number} sequenceIndex
468
+ * @param {number[]} sequence
469
+ * @returns {boolean | number[]}
470
+ */
317
471
  sequenceMatches(sequenceIndex, sequence) {
318
- return this.match(sequenceIndex, sequence, (component, glyph) => component === glyph.id);
472
+ return this.match(sequenceIndex, sequence, /** @param {number} component @param {GlyphInfoLike} glyph */ (component, glyph) => component === glyph.id);
319
473
  }
320
474
 
475
+ /**
476
+ * @param {number} sequenceIndex
477
+ * @param {number[]} sequence
478
+ * @returns {false | number[]}
479
+ */
321
480
  sequenceMatchIndices(sequenceIndex, sequence) {
322
- return this.match(sequenceIndex, sequence, (component, glyph) => {
481
+ let result = this.match(sequenceIndex, sequence, /** @param {number} component @param {GlyphInfoLike} glyph */ (component, glyph) => {
323
482
  // If the current feature doesn't apply to this glyph,
324
- if (!(this.currentFeature in glyph.features)) {
483
+ if (!(this.currentFeature && this.currentFeature in glyph.features)) {
325
484
  return false;
326
485
  }
327
486
 
328
487
  return component === glyph.id;
329
488
  }, []);
489
+ return result === false ? false : /** @type {number[]} */ (result);
330
490
  }
331
491
 
492
+ /**
493
+ * @param {number} sequenceIndex
494
+ * @param {CoverageTable[]} sequence
495
+ * @returns {boolean | number[]}
496
+ */
332
497
  coverageSequenceMatches(sequenceIndex, sequence) {
333
- return this.match(sequenceIndex, sequence, (coverage, glyph) =>
498
+ return this.match(sequenceIndex, sequence, /** @param {CoverageTable} coverage @param {GlyphInfoLike} glyph */ (coverage, glyph) =>
334
499
  this.coverageIndex(coverage, glyph.id) >= 0
335
500
  );
336
501
  }
337
502
 
503
+ /**
504
+ * @param {number} glyph
505
+ * @param {ClassDefTable | null | undefined} classDef
506
+ * @returns {number}
507
+ */
338
508
  getClassID(glyph, classDef) {
509
+ // Offset 0 ClassDefs decode as null; treat as class 0 (OT default).
510
+ if (!classDef) {
511
+ return 0;
512
+ }
513
+
339
514
  switch (classDef.version) {
340
515
  case 1: { // Class array
341
- let i = glyph - classDef.startGlyph;
342
- if (i >= 0 && i < classDef.classValueArray.length) {
516
+ let i = glyph - (classDef.startGlyph ?? 0);
517
+ if (classDef.classValueArray && i >= 0 && i < classDef.classValueArray.length) {
343
518
  return classDef.classValueArray[i];
344
519
  }
345
520
 
@@ -347,9 +522,11 @@ export default class OTProcessor {
347
522
  }
348
523
 
349
524
  case 2:
350
- for (let range of classDef.classRangeRecord) {
351
- if (range.start <= glyph && glyph <= range.end) {
352
- return range.class;
525
+ if (classDef.classRangeRecord) {
526
+ for (let range of classDef.classRangeRecord) {
527
+ if (range.start <= glyph && glyph <= range.end) {
528
+ return range.class;
529
+ }
353
530
  }
354
531
  }
355
532
 
@@ -359,22 +536,38 @@ export default class OTProcessor {
359
536
  return 0;
360
537
  }
361
538
 
539
+ /**
540
+ * @param {number} sequenceIndex
541
+ * @param {number[]} sequence
542
+ * @param {ClassDefTable | null | undefined} classDef
543
+ * @returns {boolean | number[]}
544
+ */
362
545
  classSequenceMatches(sequenceIndex, sequence, classDef) {
363
- return this.match(sequenceIndex, sequence, (classID, glyph) =>
546
+ return this.match(sequenceIndex, sequence, /** @param {number} classID @param {GlyphInfoLike} glyph */ (classID, glyph) =>
364
547
  classID === this.getClassID(glyph.id, classDef)
365
548
  );
366
549
  }
367
550
 
551
+ /**
552
+ * @param {ContextSubtable | StructValue} table
553
+ * @returns {boolean}
554
+ */
368
555
  applyContext(table) {
369
- let index, set;
370
- switch (table.version) {
556
+ let ctx = /** @type {ContextSubtable} */ (table);
557
+ let index;
558
+ /** @type {import('../../types/fontkit').ContextRule[] | null | undefined} */
559
+ let set;
560
+ switch (ctx.version) {
371
561
  case 1:
372
- index = this.coverageIndex(table.coverage);
562
+ index = this.coverageIndex(ctx.coverage);
373
563
  if (index === -1) {
374
564
  return false;
375
565
  }
376
566
 
377
- set = table.ruleSets[index];
567
+ set = ctx.ruleSets?.[index];
568
+ if (!set) {
569
+ return false;
570
+ }
378
571
  for (let rule of set) {
379
572
  if (this.sequenceMatches(1, rule.input)) {
380
573
  return this.applyLookupList(rule.lookupRecords);
@@ -383,28 +576,33 @@ export default class OTProcessor {
383
576
 
384
577
  break;
385
578
 
386
- case 2:
387
- if (this.coverageIndex(table.coverage) === -1) {
579
+ case 2: {
580
+ if (this.coverageIndex(ctx.coverage) === -1) {
388
581
  return false;
389
582
  }
390
583
 
391
- index = this.getClassID(this.glyphIterator.cur.id, table.classDef);
392
- if (index === -1) {
584
+ let cur = this.glyphIterator?.cur;
585
+ if (!cur || !ctx.classDef) {
393
586
  return false;
394
587
  }
395
588
 
396
- set = table.classSet[index];
589
+ index = this.getClassID(cur.id, ctx.classDef);
590
+ set = ctx.classSet?.[index];
591
+ if (!set) {
592
+ return false;
593
+ }
397
594
  for (let rule of set) {
398
- if (this.classSequenceMatches(1, rule.classes, table.classDef)) {
595
+ if (this.classSequenceMatches(1, rule.classes ?? [], ctx.classDef)) {
399
596
  return this.applyLookupList(rule.lookupRecords);
400
597
  }
401
598
  }
402
599
 
403
600
  break;
601
+ }
404
602
 
405
603
  case 3:
406
- if (this.coverageSequenceMatches(0, table.coverages)) {
407
- return this.applyLookupList(table.lookupRecords);
604
+ if (ctx.coverages && ctx.lookupRecords && this.coverageSequenceMatches(0, ctx.coverages)) {
605
+ return this.applyLookupList(ctx.lookupRecords);
408
606
  }
409
607
 
410
608
  break;
@@ -413,16 +611,24 @@ export default class OTProcessor {
413
611
  return false;
414
612
  }
415
613
 
614
+ /**
615
+ * @param {ChainContextSubtable | StructValue} table
616
+ * @returns {boolean}
617
+ */
416
618
  applyChainingContext(table) {
619
+ let ctx = /** @type {ChainContextSubtable} */ (table);
417
620
  let index;
418
- switch (table.version) {
621
+ switch (ctx.version) {
419
622
  case 1: {
420
- index = this.coverageIndex(table.coverage);
623
+ index = this.coverageIndex(ctx.coverage);
421
624
  if (index === -1) {
422
625
  return false;
423
626
  }
424
627
 
425
- let set = table.chainRuleSets[index];
628
+ let set = ctx.chainRuleSets?.[index];
629
+ if (!set) {
630
+ return false;
631
+ }
426
632
  for (let rule of set) {
427
633
  // Backtrack is stored closest-first; sequenceMatches walks furthest-first.
428
634
  if (this.sequenceMatches(-rule.backtrack.length, [...rule.backtrack].reverse())
@@ -436,20 +642,26 @@ export default class OTProcessor {
436
642
  }
437
643
 
438
644
  case 2: {
439
- if (this.coverageIndex(table.coverage) === -1) {
645
+ if (this.coverageIndex(ctx.coverage) === -1) {
646
+ return false;
647
+ }
648
+
649
+ let cur = this.glyphIterator?.cur;
650
+ if (!cur || !ctx.inputClassDef) {
440
651
  return false;
441
652
  }
442
653
 
443
- index = this.getClassID(this.glyphIterator.cur.id, table.inputClassDef);
444
- let rules = table.chainClassSet[index];
654
+ index = this.getClassID(cur.id, ctx.inputClassDef);
655
+ let rules = ctx.chainClassSet?.[index];
445
656
  if (!rules) {
446
657
  return false;
447
658
  }
448
659
 
449
660
  for (let rule of rules) {
450
- if (this.classSequenceMatches(-rule.backtrack.length, [...rule.backtrack].reverse(), table.backtrackClassDef)
451
- && this.classSequenceMatches(1, rule.input, table.inputClassDef)
452
- && this.classSequenceMatches(1 + rule.input.length, rule.lookahead, table.lookaheadClassDef)) {
661
+ // Backtrack/lookahead ClassDefs may be NULL (offset 0) when unused.
662
+ if (this.classSequenceMatches(-rule.backtrack.length, [...rule.backtrack].reverse(), ctx.backtrackClassDef)
663
+ && this.classSequenceMatches(1, rule.input, ctx.inputClassDef)
664
+ && this.classSequenceMatches(1 + rule.input.length, rule.lookahead, ctx.lookaheadClassDef)) {
453
665
  return this.applyLookupList(rule.lookupRecords);
454
666
  }
455
667
  }
@@ -458,10 +670,11 @@ export default class OTProcessor {
458
670
  }
459
671
 
460
672
  case 3:
461
- if (this.coverageSequenceMatches(-table.backtrackGlyphCount, [...table.backtrackCoverage].reverse())
462
- && this.coverageSequenceMatches(0, table.inputCoverage)
463
- && this.coverageSequenceMatches(table.inputGlyphCount, table.lookaheadCoverage)) {
464
- return this.applyLookupList(table.lookupRecords);
673
+ if (ctx.backtrackCoverage && ctx.inputCoverage && ctx.lookaheadCoverage && ctx.lookupRecords
674
+ && this.coverageSequenceMatches(-(ctx.backtrackGlyphCount ?? ctx.backtrackCoverage.length), [...ctx.backtrackCoverage].reverse())
675
+ && this.coverageSequenceMatches(0, ctx.inputCoverage)
676
+ && this.coverageSequenceMatches(ctx.inputGlyphCount ?? ctx.inputCoverage.length, ctx.lookaheadCoverage)) {
677
+ return this.applyLookupList(ctx.lookupRecords);
465
678
  }
466
679
 
467
680
  break;