@cantoo/fontkit 2.0.5 → 2.0.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.
Files changed (140) hide show
  1. package/dist/browser-module.mjs +4423 -1117
  2. package/dist/browser-module.mjs.map +1 -1
  3. package/dist/browser.cjs +4424 -1117
  4. package/dist/browser.cjs.map +1 -1
  5. package/dist/main.cjs +4424 -1117
  6. package/dist/main.cjs.map +1 -1
  7. package/dist/module.mjs +4423 -1117
  8. package/dist/module.mjs.map +1 -1
  9. package/package.json +3 -1
  10. package/src/CmapProcessor.js +59 -20
  11. package/src/DFont.js +114 -14
  12. package/src/TTFFont.js +259 -57
  13. package/src/TrueTypeCollection.js +54 -10
  14. package/src/WOFF2Font.js +128 -16
  15. package/src/WOFFFont.js +45 -8
  16. package/src/aat/AATFeatureMap.js +62 -18
  17. package/src/aat/AATLayoutEngine.js +33 -1
  18. package/src/aat/AATLookupTable.js +22 -4
  19. package/src/aat/AATMorxProcessor.js +189 -52
  20. package/src/aat/AATStateMachine.js +32 -0
  21. package/src/base.js +19 -0
  22. package/src/cff/CFFCharsets.js +3 -0
  23. package/src/cff/CFFDict.js +79 -9
  24. package/src/cff/CFFEncodings.js +2 -0
  25. package/src/cff/CFFFont.js +119 -23
  26. package/src/cff/CFFIndex.js +62 -5
  27. package/src/cff/CFFOperand.js +52 -17
  28. package/src/cff/CFFPointer.js +41 -2
  29. package/src/cff/CFFPrivateDict.js +14 -4
  30. package/src/cff/CFFStandardStrings.js +1 -0
  31. package/src/cff/CFFTop.js +103 -10
  32. package/src/decorators.js +32 -13
  33. package/src/encodings.js +22 -5
  34. package/src/fs.js +10 -0
  35. package/src/glyph/BBox.js +14 -0
  36. package/src/glyph/CFFGlyph.js +290 -148
  37. package/src/glyph/COLRGlyph.js +38 -3
  38. package/src/glyph/Glyph.js +68 -9
  39. package/src/glyph/GlyphVariationProcessor.js +135 -34
  40. package/src/glyph/Path.js +107 -20
  41. package/src/glyph/SBIXGlyph.js +57 -8
  42. package/src/glyph/StandardNames.js +1 -0
  43. package/src/glyph/TTFGlyph.js +176 -29
  44. package/src/glyph/TTFGlyphEncoder.js +32 -0
  45. package/src/glyph/WOFF2Glyph.js +23 -4
  46. package/src/layout/GlyphPosition.js +6 -0
  47. package/src/layout/GlyphRun.js +25 -7
  48. package/src/layout/KernProcessor.js +62 -3
  49. package/src/layout/LayoutEngine.js +86 -12
  50. package/src/layout/Script.js +27 -1
  51. package/src/layout/UnicodeLayoutEngine.js +29 -4
  52. package/src/opentype/GPOSProcessor.js +156 -45
  53. package/src/opentype/GSUBProcessor.js +92 -33
  54. package/src/opentype/GlyphInfo.js +52 -4
  55. package/src/opentype/GlyphIterator.js +51 -2
  56. package/src/opentype/OTLayoutEngine.js +66 -8
  57. package/src/opentype/OTProcessor.js +283 -70
  58. package/src/opentype/ShapingPlan.js +51 -3
  59. package/src/opentype/shapers/ArabicShaper.js +19 -0
  60. package/src/opentype/shapers/DefaultShaper.js +51 -7
  61. package/src/opentype/shapers/HangulShaper.js +85 -7
  62. package/src/opentype/shapers/HebrewShaper.js +45 -11
  63. package/src/opentype/shapers/IndicShaper.js +141 -26
  64. package/src/opentype/shapers/ThaiShaper.js +35 -4
  65. package/src/opentype/shapers/UniversalShaper.js +83 -17
  66. package/src/opentype/shapers/index.js +15 -2
  67. package/src/packages/unicode-properties/data-trie.js +1 -0
  68. package/src/packages/unicode-properties/data.js +12 -0
  69. package/src/packages/unicode-properties/dist/index.cjs +22 -9
  70. package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
  71. package/src/packages/unicode-properties/dist/index.mjs +21 -9
  72. package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
  73. package/src/packages/unicode-properties/index.js +65 -0
  74. package/src/packages/unicode-properties/package.json +1 -1
  75. package/src/packages/unicode-trie/builder.js +128 -7
  76. package/src/packages/unicode-trie/dist/builder.cjs +129 -16
  77. package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
  78. package/src/packages/unicode-trie/dist/builder.mjs +128 -16
  79. package/src/packages/unicode-trie/dist/builder.mjs.map +2 -2
  80. package/src/packages/unicode-trie/dist/index.cjs +22 -9
  81. package/src/packages/unicode-trie/dist/index.cjs.map +2 -2
  82. package/src/packages/unicode-trie/dist/index.mjs +21 -9
  83. package/src/packages/unicode-trie/dist/index.mjs.map +2 -2
  84. package/src/packages/unicode-trie/index.js +62 -10
  85. package/src/packages/unicode-trie/package.json +1 -1
  86. package/src/packages/unicode-trie/swap.js +13 -0
  87. package/src/subset/CFFSubset.js +112 -22
  88. package/src/subset/Subset.js +21 -1
  89. package/src/subset/TTFSubset.js +79 -36
  90. package/src/tables/BASE.js +1 -0
  91. package/src/tables/COLR.js +1 -0
  92. package/src/tables/CPAL.js +1 -0
  93. package/src/tables/DSIG.js +1 -0
  94. package/src/tables/EBDT.js +10 -3
  95. package/src/tables/EBLC.js +67 -7
  96. package/src/tables/GDEF.js +1 -0
  97. package/src/tables/GPOS.js +79 -16
  98. package/src/tables/GSUB.js +14 -3
  99. package/src/tables/HVAR.js +51 -5
  100. package/src/tables/JSTF.js +3 -2
  101. package/src/tables/LTSH.js +1 -0
  102. package/src/tables/OS2.js +1 -0
  103. package/src/tables/PCLT.js +1 -0
  104. package/src/tables/STAT.js +46 -6
  105. package/src/tables/VDMX.js +1 -0
  106. package/src/tables/VORG.js +1 -0
  107. package/src/tables/WOFF2Directory.js +56 -8
  108. package/src/tables/WOFFDirectory.js +14 -2
  109. package/src/tables/aat.js +160 -35
  110. package/src/tables/avar.js +1 -0
  111. package/src/tables/bsln.js +3 -2
  112. package/src/tables/cmap.js +26 -6
  113. package/src/tables/cvt.js +1 -0
  114. package/src/tables/directory.js +44 -16
  115. package/src/tables/feat.js +24 -2
  116. package/src/tables/fpgm.js +1 -0
  117. package/src/tables/fvar.js +32 -4
  118. package/src/tables/gasp.js +1 -0
  119. package/src/tables/glyf.js +1 -0
  120. package/src/tables/gvar.js +29 -2
  121. package/src/tables/hdmx.js +12 -1
  122. package/src/tables/head.js +1 -0
  123. package/src/tables/hhea.js +1 -0
  124. package/src/tables/hmtx.js +18 -2
  125. package/src/tables/index.js +1 -0
  126. package/src/tables/just.js +15 -4
  127. package/src/tables/kern.js +43 -6
  128. package/src/tables/loca.js +21 -8
  129. package/src/tables/maxp.js +1 -0
  130. package/src/tables/morx.js +14 -7
  131. package/src/tables/name.js +104 -23
  132. package/src/tables/opbd.js +2 -1
  133. package/src/tables/opentype.js +39 -4
  134. package/src/tables/post.js +8 -1
  135. package/src/tables/prep.js +1 -0
  136. package/src/tables/sbix.js +12 -1
  137. package/src/tables/variations.js +23 -3
  138. package/src/tables/vhea.js +1 -0
  139. package/src/tables/vmtx.js +18 -2
  140. package/src/utils.js +15 -0
@@ -1,8 +1,57 @@
1
1
  import OTProcessor from './OTProcessor';
2
2
 
3
+ /** @typedef {import('../../types/fontkit').PositionValue} PositionValue */
4
+ /** @typedef {import('../../types/fontkit').AnchorPoint} AnchorPoint */
5
+ /** @typedef {import('../../types/fontkit').MarkRecord} MarkRecord */
6
+ /** @typedef {import('../../types/fontkit').CoverageTable} CoverageTable */
7
+ /** @typedef {import('../../types/fontkit').ClassDefTable} ClassDefTable */
8
+ /** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
9
+ /** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
10
+ /** @typedef {import('../../types/fontkit').ExtensionSubtable} ExtensionSubtable */
11
+ /** @typedef {import('restructure').StructValue} StructValue */
12
+
13
+ /**
14
+ * @typedef {StructValue & {
15
+ * coverage?: CoverageTable,
16
+ * version?: number,
17
+ * value?: PositionValue,
18
+ * values?: import('../../types/fontkit').LazyList<PositionValue>,
19
+ * pairSets?: import('../../types/fontkit').LazyList<Array<{ secondGlyph: number, value1: PositionValue, value2: PositionValue }>>,
20
+ * classDef1?: ClassDefTable,
21
+ * classDef2?: ClassDefTable,
22
+ * classRecords?: import('../../types/fontkit').LazyList<import('../../types/fontkit').LazyList<{ value1: PositionValue, value2: PositionValue }>>,
23
+ * entryExitRecords?: Array<{ entryAnchor?: AnchorPoint | null, exitAnchor?: AnchorPoint | null }>,
24
+ * markCoverage?: CoverageTable,
25
+ * baseCoverage?: CoverageTable,
26
+ * markArray?: MarkRecord[],
27
+ * baseArray?: AnchorPoint[][],
28
+ * ligatureCoverage?: CoverageTable,
29
+ * ligatureArray?: AnchorPoint[][][],
30
+ * mark1Coverage?: CoverageTable,
31
+ * mark2Coverage?: CoverageTable,
32
+ * mark1Array?: MarkRecord[],
33
+ * mark2Array?: AnchorPoint[][],
34
+ * lookupType?: number,
35
+ * extension?: StructValue,
36
+ * }} GPOSSubtable
37
+ */
38
+
3
39
  export default class GPOSProcessor extends OTProcessor {
40
+ /**
41
+ * @param {number} sequenceIndex
42
+ * @param {PositionValue} value
43
+ */
4
44
  applyPositionValue(sequenceIndex, value) {
5
- let position = this.positions[this.glyphIterator.peekIndex(sequenceIndex)];
45
+ let glyphIterator = this.glyphIterator;
46
+ if (!glyphIterator || !this.positions) {
47
+ return;
48
+ }
49
+
50
+ let position = this.positions[glyphIterator.peekIndex(sequenceIndex)];
51
+ if (!position) {
52
+ return;
53
+ }
54
+
6
55
  if (value.xAdvance != null) position.xAdvance += value.xAdvance;
7
56
  if (value.yAdvance != null) position.yAdvance += value.yAdvance;
8
57
  if (value.xPlacement != null) position.xOffset += value.xPlacement;
@@ -32,19 +81,31 @@ export default class GPOSProcessor extends OTProcessor {
32
81
  // TODO: device tables
33
82
  }
34
83
 
84
+ /**
85
+ * @param {number} lookupType
86
+ * @param {StructValue} table
87
+ * @returns {boolean}
88
+ */
35
89
  applyLookup(lookupType, table) {
90
+ let t = /** @type {GPOSSubtable} */ (table);
91
+ let glyphIterator = this.glyphIterator;
92
+ if (!glyphIterator) {
93
+ return false;
94
+ }
95
+
36
96
  switch (lookupType) {
37
97
  case 1: { // Single positioning value
38
- let index = this.coverageIndex(table.coverage);
98
+ if (!t.coverage) return false;
99
+ let index = this.coverageIndex(t.coverage);
39
100
  if (index === -1) return false;
40
101
 
41
- switch (table.version) {
102
+ switch (t.version) {
42
103
  case 1:
43
- this.applyPositionValue(0, table.value);
104
+ if (t.value) this.applyPositionValue(0, t.value);
44
105
  break;
45
106
 
46
107
  case 2:
47
- this.applyPositionValue(0, table.values.get(index));
108
+ if (t.values) this.applyPositionValue(0, t.values.get(index));
48
109
  break;
49
110
  }
50
111
 
@@ -52,15 +113,17 @@ export default class GPOSProcessor extends OTProcessor {
52
113
  }
53
114
 
54
115
  case 2: { // Pair Adjustment Positioning
55
- let nextGlyph = this.glyphIterator.peek();
116
+ let nextGlyph = glyphIterator.peek();
56
117
  if (!nextGlyph) return false;
57
118
 
58
- let index = this.coverageIndex(table.coverage);
119
+ if (!t.coverage) return false;
120
+ let index = this.coverageIndex(t.coverage);
59
121
  if (index === -1) return false;
60
122
 
61
- switch (table.version) {
123
+ switch (t.version) {
62
124
  case 1: { // Adjustments for glyph pairs
63
- let set = table.pairSets.get(index);
125
+ if (!t.pairSets) return false;
126
+ let set = t.pairSets.get(index);
64
127
 
65
128
  for (let pair of set) {
66
129
  if (pair.secondGlyph === nextGlyph.id) {
@@ -74,11 +137,14 @@ export default class GPOSProcessor extends OTProcessor {
74
137
  }
75
138
 
76
139
  case 2: { // Class pair adjustment
77
- let class1 = this.getClassID(this.glyphIterator.cur.id, table.classDef1);
78
- let class2 = this.getClassID(nextGlyph.id, table.classDef2);
140
+ let cur = glyphIterator.cur;
141
+ if (!cur || !t.classDef1 || !t.classDef2 || !t.classRecords) return false;
142
+
143
+ let class1 = this.getClassID(cur.id, t.classDef1);
144
+ let class2 = this.getClassID(nextGlyph.id, t.classDef2);
79
145
  if (class1 === -1 || class2 === -1) return false;
80
146
 
81
- let pair = table.classRecords.get(class1).get(class2);
147
+ let pair = t.classRecords.get(class1).get(class2);
82
148
  this.applyPositionValue(0, pair.value1);
83
149
  this.applyPositionValue(1, pair.value2);
84
150
  return true;
@@ -89,21 +155,24 @@ export default class GPOSProcessor extends OTProcessor {
89
155
  }
90
156
 
91
157
  case 3: { // Cursive Attachment Positioning
92
- let nextIndex = this.glyphIterator.peekIndex();
158
+ if (!t.coverage || !t.entryExitRecords || !this.positions) return false;
159
+
160
+ let nextIndex = glyphIterator.peekIndex();
93
161
  let nextGlyph = this.glyphs[nextIndex];
94
162
  if (!nextGlyph) return false;
95
163
 
96
- let curRecord = table.entryExitRecords[this.coverageIndex(table.coverage)];
164
+ let curRecord = t.entryExitRecords[this.coverageIndex(t.coverage)];
97
165
  if (!curRecord || !curRecord.exitAnchor) return false;
98
166
 
99
- let nextRecord = table.entryExitRecords[this.coverageIndex(table.coverage, nextGlyph.id)];
167
+ let nextRecord = t.entryExitRecords[this.coverageIndex(t.coverage, nextGlyph.id)];
100
168
  if (!nextRecord || !nextRecord.entryAnchor) return false;
101
169
 
102
170
  let entry = this.getAnchor(nextRecord.entryAnchor);
103
171
  let exit = this.getAnchor(curRecord.exitAnchor);
104
172
 
105
- let cur = this.positions[this.glyphIterator.index];
173
+ let cur = this.positions[glyphIterator.index];
106
174
  let next = this.positions[nextIndex];
175
+ if (!cur || !next) return false;
107
176
  let d;
108
177
 
109
178
  switch (this.direction) {
@@ -123,11 +192,14 @@ export default class GPOSProcessor extends OTProcessor {
123
192
  break;
124
193
  }
125
194
 
126
- if (this.glyphIterator.flags.rightToLeft) {
127
- this.glyphIterator.cur.cursiveAttachment = nextIndex;
195
+ let curGlyph = glyphIterator.cur;
196
+ if (!curGlyph) return false;
197
+
198
+ if (glyphIterator.flags.rightToLeft) {
199
+ curGlyph.cursiveAttachment = nextIndex;
128
200
  cur.yOffset = entry.y - exit.y;
129
201
  } else {
130
- nextGlyph.cursiveAttachment = this.glyphIterator.index;
202
+ nextGlyph.cursiveAttachment = glyphIterator.index;
131
203
  cur.yOffset = exit.y - entry.y;
132
204
  }
133
205
 
@@ -135,58 +207,67 @@ export default class GPOSProcessor extends OTProcessor {
135
207
  }
136
208
 
137
209
  case 4: { // Mark to base positioning
138
- let markIndex = this.coverageIndex(table.markCoverage);
210
+ if (!t.markCoverage || !t.baseCoverage || !t.markArray || !t.baseArray) return false;
211
+
212
+ let markIndex = this.coverageIndex(t.markCoverage);
139
213
  if (markIndex === -1) return false;
140
214
 
141
215
  // search backward for a base glyph
142
- let baseGlyphIndex = this.glyphIterator.index;
143
- while (--baseGlyphIndex >= 0 && (this.glyphs[baseGlyphIndex].isMark || this.glyphs[baseGlyphIndex].ligatureComponent > 0));
216
+ let baseGlyphIndex = glyphIterator.index;
217
+ while (--baseGlyphIndex >= 0 && (this.glyphs[baseGlyphIndex].isMark || (this.glyphs[baseGlyphIndex].ligatureComponent ?? 0) > 0));
144
218
 
145
219
  if (baseGlyphIndex < 0) return false;
146
220
 
147
- let baseIndex = this.coverageIndex(table.baseCoverage, this.glyphs[baseGlyphIndex].id);
221
+ let baseIndex = this.coverageIndex(t.baseCoverage, this.glyphs[baseGlyphIndex].id);
148
222
  if (baseIndex === -1) return false;
149
223
 
150
- let markRecord = table.markArray[markIndex];
151
- let baseAnchor = table.baseArray[baseIndex][markRecord.class];
224
+ let markRecord = t.markArray[markIndex];
225
+ let baseAnchor = t.baseArray[baseIndex][markRecord.class];
152
226
  return this.applyAnchor(markRecord, baseAnchor, baseGlyphIndex);
153
227
  }
154
228
 
155
229
  case 5: { // Mark to ligature positioning
156
- let markIndex = this.coverageIndex(table.markCoverage);
230
+ if (!t.markCoverage || !t.ligatureCoverage || !t.markArray || !t.ligatureArray) return false;
231
+
232
+ let markIndex = this.coverageIndex(t.markCoverage);
157
233
  if (markIndex === -1) return false;
158
234
 
159
235
  // search backward for a base glyph
160
- let baseGlyphIndex = this.glyphIterator.index;
236
+ let baseGlyphIndex = glyphIterator.index;
161
237
  while (--baseGlyphIndex >= 0 && this.glyphs[baseGlyphIndex].isMark);
162
238
 
163
239
  if (baseGlyphIndex < 0) return false;
164
240
 
165
- let ligIndex = this.coverageIndex(table.ligatureCoverage, this.glyphs[baseGlyphIndex].id);
241
+ let ligIndex = this.coverageIndex(t.ligatureCoverage, this.glyphs[baseGlyphIndex].id);
166
242
  if (ligIndex === -1) return false;
167
243
 
168
- let ligAttach = table.ligatureArray[ligIndex];
169
- let markGlyph = this.glyphIterator.cur;
244
+ let ligAttach = t.ligatureArray[ligIndex];
245
+ let markGlyph = glyphIterator.cur;
170
246
  let ligGlyph = this.glyphs[baseGlyphIndex];
171
- let compIndex = ligGlyph.ligatureID && ligGlyph.ligatureID === markGlyph.ligatureID && (markGlyph.ligatureComponent > 0)
172
- ? Math.min(markGlyph.ligatureComponent, ligGlyph.codePoints.length) - 1
247
+ if (!markGlyph) return false;
248
+
249
+ let compIndex = ligGlyph.ligatureID && ligGlyph.ligatureID === markGlyph.ligatureID && (markGlyph.ligatureComponent ?? 0) > 0
250
+ ? Math.min(markGlyph.ligatureComponent ?? 0, ligGlyph.codePoints.length) - 1
173
251
  : ligGlyph.codePoints.length - 1;
174
252
 
175
- let markRecord = table.markArray[markIndex];
253
+ let markRecord = t.markArray[markIndex];
176
254
  let baseAnchor = ligAttach[compIndex][markRecord.class];
177
255
  return this.applyAnchor(markRecord, baseAnchor, baseGlyphIndex);
178
256
  }
179
257
 
180
258
  case 6: { // Mark to mark positioning
181
- let mark1Index = this.coverageIndex(table.mark1Coverage);
259
+ if (!t.mark1Coverage || !t.mark2Coverage || !t.mark1Array || !t.mark2Array) return false;
260
+
261
+ let mark1Index = this.coverageIndex(t.mark1Coverage);
182
262
  if (mark1Index === -1) return false;
183
263
 
184
264
  // get the previous mark to attach to
185
- let prevIndex = this.glyphIterator.peekIndex(-1);
265
+ let prevIndex = glyphIterator.peekIndex(-1);
186
266
  let prev = this.glyphs[prevIndex];
187
267
  if (!prev || !prev.isMark) return false;
188
268
 
189
- let cur = this.glyphIterator.cur;
269
+ let cur = glyphIterator.cur;
270
+ if (!cur) return false;
190
271
 
191
272
  // The following logic was borrowed from Harfbuzz
192
273
  let good = false;
@@ -206,11 +287,11 @@ export default class GPOSProcessor extends OTProcessor {
206
287
 
207
288
  if (!good) return false;
208
289
 
209
- let mark2Index = this.coverageIndex(table.mark2Coverage, prev.id);
290
+ let mark2Index = this.coverageIndex(t.mark2Coverage, prev.id);
210
291
  if (mark2Index === -1) return false;
211
292
 
212
- let markRecord = table.mark1Array[mark1Index];
213
- let baseAnchor = table.mark2Array[mark2Index][markRecord.class];
293
+ let markRecord = t.mark1Array[mark1Index];
294
+ let baseAnchor = t.mark2Array[mark2Index][markRecord.class];
214
295
  return this.applyAnchor(markRecord, baseAnchor, prevIndex);
215
296
  }
216
297
 
@@ -220,28 +301,46 @@ export default class GPOSProcessor extends OTProcessor {
220
301
  case 8: // Chaining contextual positioning
221
302
  return this.applyChainingContext(table);
222
303
 
223
- case 9: // Extension positioning
224
- return this.applyLookup(table.lookupType, table.extension);
304
+ case 9: { // Extension positioning
305
+ let ext = /** @type {ExtensionSubtable} */ (table);
306
+ return this.applyLookup(ext.lookupType, ext.extension);
307
+ }
225
308
 
226
309
  default:
227
310
  throw new Error(`Unsupported GPOS table: ${lookupType}`);
228
311
  }
229
312
  }
230
313
 
314
+ /**
315
+ * @param {MarkRecord} markRecord
316
+ * @param {AnchorPoint | null | undefined} baseAnchor
317
+ * @param {number} baseGlyphIndex
318
+ * @returns {boolean}
319
+ */
231
320
  applyAnchor(markRecord, baseAnchor, baseGlyphIndex) {
232
321
  // NULL offset = no anchor for this class; false so later subtables can try (HarfBuzz).
233
322
  if (!baseAnchor || !markRecord.markAnchor) return false;
234
323
 
324
+ let glyphIterator = this.glyphIterator;
325
+ if (!glyphIterator || !this.positions) return false;
326
+
235
327
  let baseCoords = this.getAnchor(baseAnchor);
236
328
  let markCoords = this.getAnchor(markRecord.markAnchor);
237
- let markPos = this.positions[this.glyphIterator.index];
329
+ let markPos = this.positions[glyphIterator.index];
330
+ if (!markPos) return false;
238
331
 
239
332
  markPos.xOffset = baseCoords.x - markCoords.x;
240
333
  markPos.yOffset = baseCoords.y - markCoords.y;
241
- this.glyphIterator.cur.markAttachment = baseGlyphIndex;
334
+ let cur = glyphIterator.cur;
335
+ if (!cur) return false;
336
+ cur.markAttachment = baseGlyphIndex;
242
337
  return true;
243
338
  }
244
339
 
340
+ /**
341
+ * @param {AnchorPoint} anchor
342
+ * @returns {{ x: number, y: number }}
343
+ */
245
344
  getAnchor(anchor) {
246
345
  // TODO: contour point, device tables
247
346
  let x = anchor.xCoordinate;
@@ -263,19 +362,27 @@ export default class GPOSProcessor extends OTProcessor {
263
362
  return { x, y };
264
363
  }
265
364
 
365
+ /**
366
+ * @param {string[]} userFeatures
367
+ * @param {GlyphInfoLike[]} glyphs
368
+ * @param {GlyphPositionLike[] | null | undefined} [advances]
369
+ */
266
370
  applyFeatures(userFeatures, glyphs, advances) {
267
371
  super.applyFeatures(userFeatures, glyphs, advances);
268
372
 
269
- for (var i = 0; i < this.glyphs.length; i++) {
373
+ for (let i = 0; i < this.glyphs.length; i++) {
270
374
  this.fixCursiveAttachment(i);
271
375
  }
272
376
 
273
377
  this.fixMarkAttachment();
274
378
  }
275
379
 
380
+ /**
381
+ * @param {number} i
382
+ */
276
383
  fixCursiveAttachment(i) {
277
384
  let glyph = this.glyphs[i];
278
- if (glyph.cursiveAttachment != null) {
385
+ if (glyph.cursiveAttachment != null && this.positions) {
279
386
  let j = glyph.cursiveAttachment;
280
387
 
281
388
  glyph.cursiveAttachment = null;
@@ -286,6 +393,10 @@ export default class GPOSProcessor extends OTProcessor {
286
393
  }
287
394
 
288
395
  fixMarkAttachment() {
396
+ if (!this.positions) {
397
+ return;
398
+ }
399
+
289
400
  for (let i = 0; i < this.glyphs.length; i++) {
290
401
  let glyph = this.glyphs[i];
291
402
  if (glyph.markAttachment != null) {
@@ -1,28 +1,73 @@
1
1
  import OTProcessor from './OTProcessor';
2
2
  import GlyphInfo from './GlyphInfo';
3
3
 
4
+ /** @typedef {import('../../types/fontkit').CoverageTable} CoverageTable */
5
+ /** @typedef {import('../../types/fontkit').OTLookup} OTLookup */
6
+ /** @typedef {import('../../types/fontkit').ExtensionSubtable} ExtensionSubtable */
7
+ /** @typedef {import('restructure').StructValue} StructValue */
8
+
9
+ /**
10
+ * @typedef {StructValue & {
11
+ * coverage?: CoverageTable,
12
+ * version?: number,
13
+ * deltaGlyphID?: number,
14
+ * substitute?: import('../../types/fontkit').LazyList<number>,
15
+ * sequences?: import('../../types/fontkit').LazyList<number[]>,
16
+ * alternateSet?: import('../../types/fontkit').LazyList<number[]>,
17
+ * ligatureSets?: import('../../types/fontkit').LazyList<Array<{ glyph: number, components: number[] }>>,
18
+ * lookupType?: number,
19
+ * extension?: StructValue,
20
+ * backtrackGlyphCount?: number,
21
+ * backtrackCoverage?: CoverageTable[],
22
+ * lookaheadCoverage?: CoverageTable[],
23
+ * }} GSUBSubtable
24
+ */
25
+
4
26
  export default class GSUBProcessor extends OTProcessor {
27
+ /**
28
+ * @param {OTLookup} lookup
29
+ * @returns {number}
30
+ */
5
31
  lookupDirection(lookup) {
6
- let type = lookup.lookupType === 7 ? lookup.subTables[0]?.lookupType : lookup.lookupType;
32
+ let first = lookup.subTables[0];
33
+ let type = lookup.lookupType === 7
34
+ ? (first && typeof first === 'object' && 'lookupType' in first
35
+ ? /** @type {number} */ (/** @type {{ lookupType?: number }} */ (first).lookupType)
36
+ : undefined)
37
+ : lookup.lookupType;
7
38
  return type === 8 ? -1 : 1;
8
39
  }
9
40
 
41
+ /**
42
+ * @param {number} lookupType
43
+ * @param {StructValue} table
44
+ * @returns {boolean}
45
+ */
10
46
  applyLookup(lookupType, table) {
47
+ let t = /** @type {GSUBSubtable} */ (table);
48
+ let glyphIterator = this.glyphIterator;
49
+ if (!glyphIterator) {
50
+ return false;
51
+ }
52
+
11
53
  switch (lookupType) {
12
54
  case 1: { // Single Substitution
13
- let index = this.coverageIndex(table.coverage);
55
+ if (!t.coverage) return false;
56
+ let index = this.coverageIndex(t.coverage);
14
57
  if (index === -1) {
15
58
  return false;
16
59
  }
17
60
 
18
- let glyph = this.glyphIterator.cur;
19
- switch (table.version) {
61
+ let glyph = glyphIterator.cur;
62
+ if (!glyph) return false;
63
+
64
+ switch (t.version) {
20
65
  case 1:
21
- glyph.id = (glyph.id + table.deltaGlyphID) & 0xffff;
66
+ glyph.id = (glyph.id + (t.deltaGlyphID ?? 0)) & 0xffff;
22
67
  break;
23
68
 
24
69
  case 2:
25
- glyph.id = table.substitute.get(index);
70
+ if (t.substitute) glyph.id = t.substitute.get(index);
26
71
  break;
27
72
  }
28
73
 
@@ -30,23 +75,26 @@ export default class GSUBProcessor extends OTProcessor {
30
75
  }
31
76
 
32
77
  case 2: { // Multiple Substitution
33
- let index = this.coverageIndex(table.coverage);
78
+ if (!t.coverage || !t.sequences) return false;
79
+ let index = this.coverageIndex(t.coverage);
34
80
  if (index !== -1) {
35
- let sequence = table.sequences.get(index);
81
+ let sequence = t.sequences.get(index);
36
82
 
37
83
  if (sequence.length === 0) {
38
84
  // If the sequence length is zero, delete the glyph.
39
85
  // The OpenType spec disallows this, but seems like Harfbuzz and Uniscribe allow it.
40
- this.glyphs.splice(this.glyphIterator.index, 1);
86
+ this.glyphs.splice(glyphIterator.index, 1);
41
87
  return true;
42
88
  }
43
89
 
44
- this.glyphIterator.cur.id = sequence[0];
45
- this.glyphIterator.cur.ligatureComponent = 0;
90
+ let curGlyph = glyphIterator.cur;
91
+ if (!curGlyph) return false;
46
92
 
47
- let features = this.glyphIterator.cur.features;
48
- let curGlyph = this.glyphIterator.cur;
49
- let replacement = sequence.slice(1).map((gid, i) => {
93
+ curGlyph.id = sequence[0];
94
+ curGlyph.ligatureComponent = 0;
95
+
96
+ let features = curGlyph.features;
97
+ let replacement = sequence.slice(1).map(/** @param {number} gid @param {number} i */ (gid, i) => {
50
98
  let glyph = new GlyphInfo(this.font, gid, undefined, features);
51
99
  glyph.shaperInfo = curGlyph.shaperInfo;
52
100
  glyph.isLigated = curGlyph.isLigated;
@@ -56,7 +104,7 @@ export default class GSUBProcessor extends OTProcessor {
56
104
  return glyph;
57
105
  });
58
106
 
59
- this.glyphs.splice(this.glyphIterator.index + 1, 0, ...replacement);
107
+ this.glyphs.splice(glyphIterator.index + 1, 0, ...replacement);
60
108
  return true;
61
109
  }
62
110
 
@@ -64,31 +112,37 @@ export default class GSUBProcessor extends OTProcessor {
64
112
  }
65
113
 
66
114
  case 3: { // Alternate Substitution
67
- let index = this.coverageIndex(table.coverage);
115
+ if (!t.coverage || !t.alternateSet) return false;
116
+ let index = this.coverageIndex(t.coverage);
68
117
  if (index === -1) return false;
69
118
 
70
119
  // 1-based index from user features (e.g. { aalt: 2 }); boolean / missing → first
71
- let alternates = table.alternateSet.get(index);
72
- let alt = this.userFeatures?.[this.currentFeature];
73
- let i = Number.isInteger(alt) && alt > 0 ? alt - 1 : 0;
74
- this.glyphIterator.cur.id = alternates[i < alternates.length ? i : 0];
120
+ let alternates = t.alternateSet.get(index);
121
+ let cur = glyphIterator.cur;
122
+ if (!cur) return false;
123
+
124
+ let alt = this.currentFeature && this.userFeatures ? this.userFeatures[this.currentFeature] : undefined;
125
+ let i = typeof alt === 'number' && Number.isInteger(alt) && alt > 0 ? alt - 1 : 0;
126
+ cur.id = alternates[i < alternates.length ? i : 0];
75
127
  return true;
76
128
  }
77
129
 
78
130
  case 4: { // Ligature Substitution
79
- let index = this.coverageIndex(table.coverage);
131
+ if (!t.coverage || !t.ligatureSets) return false;
132
+ let index = this.coverageIndex(t.coverage);
80
133
  if (index === -1) return false;
81
134
 
82
- for (let ligature of table.ligatureSets.get(index)) {
135
+ for (let ligature of t.ligatureSets.get(index)) {
83
136
  let matched = this.sequenceMatchIndices(1, ligature.components);
84
137
  if (!matched) continue;
85
138
 
86
- let curGlyph = this.glyphIterator.cur;
139
+ let curGlyph = glyphIterator.cur;
140
+ if (!curGlyph) continue;
87
141
 
88
142
  // Concatenate all of the characters the new ligature will represent
89
143
  let characters = curGlyph.codePoints.slice();
90
- for (let index of matched) {
91
- characters.push(...this.glyphs[index].codePoints);
144
+ for (let matchIdx of matched) {
145
+ characters.push(...this.glyphs[matchIdx].codePoints);
92
146
  }
93
147
 
94
148
  // Create the replacement ligature glyph
@@ -131,7 +185,7 @@ export default class GSUBProcessor extends OTProcessor {
131
185
  let lastLigID = curGlyph.ligatureID;
132
186
  let lastNumComps = curGlyph.codePoints.length;
133
187
  let curComps = lastNumComps;
134
- let idx = this.glyphIterator.index + 1;
188
+ let idx = glyphIterator.index + 1;
135
189
 
136
190
  // Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
137
191
  // This allows GPOS to attach marks to the correct ligature components.
@@ -171,7 +225,7 @@ export default class GSUBProcessor extends OTProcessor {
171
225
  this.glyphs.splice(matched[i], 1);
172
226
  }
173
227
 
174
- this.glyphs[this.glyphIterator.index] = ligatureGlyph;
228
+ this.glyphs[glyphIterator.index] = ligatureGlyph;
175
229
  return true;
176
230
  }
177
231
 
@@ -184,20 +238,25 @@ export default class GSUBProcessor extends OTProcessor {
184
238
  case 6: // Chaining Contextual Substitution
185
239
  return this.applyChainingContext(table);
186
240
 
187
- case 7: // Extension Substitution
188
- return this.applyLookup(table.lookupType, table.extension);
241
+ case 7: { // Extension Substitution
242
+ let ext = /** @type {ExtensionSubtable} */ (table);
243
+ return this.applyLookup(ext.lookupType, ext.extension);
244
+ }
189
245
 
190
246
  case 8: { // Reverse Chaining Contextual Single Substitution
191
- let index = this.coverageIndex(table.coverage);
247
+ if (!t.coverage || !t.substitute || !t.backtrackCoverage || !t.lookaheadCoverage) return false;
248
+ let index = this.coverageIndex(t.coverage);
192
249
  if (index === -1) return false;
193
250
 
194
251
  // Backtrack is stored closest-first; coverageSequenceMatches walks furthest-first.
195
- if (!this.coverageSequenceMatches(-table.backtrackGlyphCount, [...table.backtrackCoverage].reverse())
196
- || !this.coverageSequenceMatches(1, table.lookaheadCoverage)) {
252
+ if (!this.coverageSequenceMatches(-(t.backtrackGlyphCount ?? t.backtrackCoverage.length), [...t.backtrackCoverage].reverse())
253
+ || !this.coverageSequenceMatches(1, t.lookaheadCoverage)) {
197
254
  return false;
198
255
  }
199
256
 
200
- this.glyphIterator.cur.id = table.substitute.get(index);
257
+ let cur = glyphIterator.cur;
258
+ if (!cur) return false;
259
+ cur.id = t.substitute.get(index);
201
260
  return true;
202
261
  }
203
262