@cantoo/fontkit 2.0.4 → 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 (142) hide show
  1. package/dist/browser-module.mjs +4448 -1142
  2. package/dist/browser-module.mjs.map +1 -1
  3. package/dist/browser.cjs +4449 -1142
  4. package/dist/browser.cjs.map +1 -1
  5. package/dist/main.cjs +4449 -1142
  6. package/dist/main.cjs.map +1 -1
  7. package/dist/module.mjs +4448 -1142
  8. package/dist/module.mjs.map +1 -1
  9. package/package.json +4 -1
  10. package/src/CmapProcessor.js +60 -21
  11. package/src/DFont.js +114 -14
  12. package/src/TTFFont.js +260 -62
  13. package/src/TrueTypeCollection.js +59 -15
  14. package/src/WOFF2Font.js +128 -22
  15. package/src/WOFFFont.js +45 -8
  16. package/src/aat/AATFeatureMap.js +83 -40
  17. package/src/aat/AATLayoutEngine.js +34 -2
  18. package/src/aat/AATLookupTable.js +24 -6
  19. package/src/aat/AATMorxProcessor.js +202 -65
  20. package/src/aat/AATStateMachine.js +35 -3
  21. package/src/base.js +20 -1
  22. package/src/cff/CFFCharsets.js +3 -0
  23. package/src/cff/CFFDict.js +81 -11
  24. package/src/cff/CFFEncodings.js +2 -0
  25. package/src/cff/CFFFont.js +120 -24
  26. package/src/cff/CFFIndex.js +66 -9
  27. package/src/cff/CFFOperand.js +53 -28
  28. package/src/cff/CFFPointer.js +41 -2
  29. package/src/cff/CFFPrivateDict.js +34 -24
  30. package/src/cff/CFFStandardStrings.js +68 -67
  31. package/src/cff/CFFTop.js +161 -68
  32. package/src/decorators.js +34 -15
  33. package/src/encodings.js +133 -116
  34. package/src/fs.js +10 -0
  35. package/src/glyph/BBox.js +14 -0
  36. package/src/glyph/CFFGlyph.js +293 -152
  37. package/src/glyph/COLRGlyph.js +40 -5
  38. package/src/glyph/Glyph.js +72 -13
  39. package/src/glyph/GlyphVariationProcessor.js +158 -62
  40. package/src/glyph/Path.js +119 -32
  41. package/src/glyph/SBIXGlyph.js +57 -8
  42. package/src/glyph/StandardNames.js +1 -0
  43. package/src/glyph/TTFGlyph.js +203 -63
  44. package/src/glyph/TTFGlyphEncoder.js +58 -26
  45. package/src/glyph/WOFF2Glyph.js +25 -6
  46. package/src/layout/GlyphPosition.js +6 -0
  47. package/src/layout/GlyphRun.js +28 -10
  48. package/src/layout/KernProcessor.js +63 -4
  49. package/src/layout/LayoutEngine.js +99 -26
  50. package/src/layout/Script.js +52 -26
  51. package/src/layout/UnicodeLayoutEngine.js +34 -10
  52. package/src/opentype/GPOSProcessor.js +156 -45
  53. package/src/opentype/GSUBProcessor.js +92 -33
  54. package/src/opentype/GlyphInfo.js +53 -5
  55. package/src/opentype/GlyphIterator.js +51 -2
  56. package/src/opentype/OTLayoutEngine.js +66 -8
  57. package/src/opentype/OTProcessor.js +284 -71
  58. package/src/opentype/ShapingPlan.js +52 -5
  59. package/src/opentype/shapers/ArabicShaper.js +27 -8
  60. package/src/opentype/shapers/DefaultShaper.js +54 -10
  61. package/src/opentype/shapers/HangulShaper.js +120 -42
  62. package/src/opentype/shapers/HebrewShaper.js +50 -16
  63. package/src/opentype/shapers/IndicShaper.js +159 -44
  64. package/src/opentype/shapers/ThaiShaper.js +50 -19
  65. package/src/opentype/shapers/UniversalShaper.js +83 -17
  66. package/src/opentype/shapers/index.js +48 -35
  67. package/src/opentype/shapers/indic-data.js +2 -2
  68. package/src/packages/unicode-properties/data-trie.js +2 -1
  69. package/src/packages/unicode-properties/data.js +13 -1
  70. package/src/packages/unicode-properties/dist/index.cjs +23 -10
  71. package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
  72. package/src/packages/unicode-properties/dist/index.mjs +22 -10
  73. package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
  74. package/src/packages/unicode-properties/generate.js +3 -4
  75. package/src/packages/unicode-properties/index.js +95 -30
  76. package/src/packages/unicode-properties/package.json +1 -1
  77. package/src/packages/unicode-trie/builder.js +146 -29
  78. package/src/packages/unicode-trie/dist/builder.cjs +135 -25
  79. package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
  80. package/src/packages/unicode-trie/dist/builder.mjs +134 -25
  81. package/src/packages/unicode-trie/dist/builder.mjs.map +2 -2
  82. package/src/packages/unicode-trie/dist/index.cjs +22 -9
  83. package/src/packages/unicode-trie/dist/index.cjs.map +2 -2
  84. package/src/packages/unicode-trie/dist/index.mjs +21 -9
  85. package/src/packages/unicode-trie/dist/index.mjs.map +2 -2
  86. package/src/packages/unicode-trie/index.js +63 -12
  87. package/src/packages/unicode-trie/package.json +1 -1
  88. package/src/packages/unicode-trie/swap.js +15 -2
  89. package/src/subset/CFFSubset.js +112 -22
  90. package/src/subset/Subset.js +23 -3
  91. package/src/subset/TTFSubset.js +84 -41
  92. package/src/tables/BASE.js +28 -27
  93. package/src/tables/COLR.js +10 -9
  94. package/src/tables/CPAL.js +2 -1
  95. package/src/tables/DSIG.js +8 -7
  96. package/src/tables/EBDT.js +10 -4
  97. package/src/tables/EBLC.js +70 -10
  98. package/src/tables/GDEF.js +18 -17
  99. package/src/tables/GPOS.js +153 -90
  100. package/src/tables/GSUB.js +50 -39
  101. package/src/tables/HVAR.js +51 -5
  102. package/src/tables/JSTF.js +17 -16
  103. package/src/tables/LTSH.js +4 -3
  104. package/src/tables/OS2.js +50 -49
  105. package/src/tables/PCLT.js +16 -15
  106. package/src/tables/STAT.js +46 -6
  107. package/src/tables/VDMX.js +18 -17
  108. package/src/tables/VORG.js +8 -7
  109. package/src/tables/WOFF2Directory.js +57 -9
  110. package/src/tables/WOFFDirectory.js +32 -20
  111. package/src/tables/aat.js +160 -35
  112. package/src/tables/avar.js +1 -0
  113. package/src/tables/bsln.js +3 -2
  114. package/src/tables/cmap.js +83 -63
  115. package/src/tables/cvt.js +1 -0
  116. package/src/tables/directory.js +56 -28
  117. package/src/tables/feat.js +24 -2
  118. package/src/tables/fpgm.js +1 -0
  119. package/src/tables/fvar.js +32 -4
  120. package/src/tables/gasp.js +6 -5
  121. package/src/tables/glyf.js +2 -1
  122. package/src/tables/gvar.js +29 -2
  123. package/src/tables/hdmx.js +18 -7
  124. package/src/tables/head.js +17 -16
  125. package/src/tables/hhea.js +15 -14
  126. package/src/tables/hmtx.js +18 -2
  127. package/src/tables/index.js +1 -5
  128. package/src/tables/just.js +15 -4
  129. package/src/tables/kern.js +84 -47
  130. package/src/tables/loca.js +23 -10
  131. package/src/tables/maxp.js +16 -15
  132. package/src/tables/morx.js +27 -20
  133. package/src/tables/name.js +120 -39
  134. package/src/tables/opbd.js +2 -1
  135. package/src/tables/opentype.js +126 -91
  136. package/src/tables/post.js +18 -11
  137. package/src/tables/prep.js +1 -0
  138. package/src/tables/sbix.js +12 -1
  139. package/src/tables/variations.js +29 -9
  140. package/src/tables/vhea.js +15 -14
  141. package/src/tables/vmtx.js +20 -4
  142. package/src/utils.js +15 -0
@@ -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
 
@@ -1,36 +1,79 @@
1
- import {isMark} from '../packages/unicode-properties/index.js';
1
+ import { isMark } from '../packages/unicode-properties/index.js';
2
2
  import OTProcessor from './OTProcessor';
3
3
 
4
+ /** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
5
+ /** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
6
+ /** @typedef {import('../../types/fontkit').FeatureInput} FeatureInput */
7
+ /** @typedef {import('../../types/fontkit').ClassDefTable} ClassDefTable */
8
+
4
9
  export default class GlyphInfo {
10
+ /**
11
+ * @param {LayoutFont} font
12
+ * @param {number} id
13
+ * @param {number[]} [codePoints]
14
+ * @param {FeatureInput | null | undefined} [features]
15
+ */
5
16
  constructor(font, id, codePoints = [], features) {
17
+ /** @type {LayoutFont} */
6
18
  this._font = font;
19
+ /** @type {number[]} */
7
20
  this.codePoints = codePoints;
8
- this.id = id;
21
+ /** @type {number} */
22
+ this._id = 0;
9
23
 
24
+ /** @type {FeatureMap} */
10
25
  this.features = {};
11
26
  if (Array.isArray(features)) {
12
27
  for (let i = 0; i < features.length; i++) {
13
28
  let feature = features[i];
14
29
  this.features[feature] = true;
15
30
  }
16
- } else if (typeof features === 'object') {
31
+ } else if (typeof features === 'object' && features !== null) {
17
32
  Object.assign(this.features, features);
18
33
  }
19
34
 
35
+ /** @type {number | null} */
20
36
  this.ligatureID = null;
37
+ /** @type {number | null} */
21
38
  this.ligatureComponent = null;
39
+ /** @type {boolean} */
22
40
  this.isLigated = false;
41
+ /** @type {number | null} */
23
42
  this.cursiveAttachment = null;
43
+ /** @type {number | null} */
24
44
  this.markAttachment = null;
45
+ /** @type {import('../../types/fontkit').IndicShaperInfo | import('../../types/fontkit').USEShaperInfo | null} */
25
46
  this.shaperInfo = null;
47
+ /** @type {boolean} */
26
48
  this.substituted = false;
49
+ /** @type {boolean} */
27
50
  this.isMultiplied = false;
51
+
52
+ /** @type {boolean} */
53
+ this.isBase = false;
54
+ /** @type {boolean} */
55
+ this.isLigature = false;
56
+ /** @type {boolean} */
57
+ this.isMark = false;
58
+ /** @type {number} */
59
+ this.markAttachmentType = 0;
60
+
61
+ // Apply GDEF / mark classification via the id setter, then clear
62
+ // `substituted` — construction is not a GSUB substitution.
63
+ this.id = id;
64
+ this.substituted = false;
28
65
  }
29
66
 
67
+ /**
68
+ * @returns {number}
69
+ */
30
70
  get id() {
31
71
  return this._id;
32
72
  }
33
73
 
74
+ /**
75
+ * @param {number} id
76
+ */
34
77
  set id(id) {
35
78
  this._id = id;
36
79
  this.substituted = true;
@@ -38,11 +81,13 @@ export default class GlyphInfo {
38
81
  let GDEF = this._font.GDEF;
39
82
  if (GDEF && GDEF.glyphClassDef) {
40
83
  // TODO: clean this up
41
- let classID = OTProcessor.prototype.getClassID(id, GDEF.glyphClassDef);
84
+ let classID = OTProcessor.prototype.getClassID(id, /** @type {ClassDefTable} */ (GDEF.glyphClassDef));
42
85
  this.isBase = classID === 1;
43
86
  this.isLigature = classID === 2;
44
87
  this.isMark = classID === 3;
45
- this.markAttachmentType = GDEF.markAttachClassDef ? OTProcessor.prototype.getClassID(id, GDEF.markAttachClassDef) : 0;
88
+ this.markAttachmentType = GDEF.markAttachClassDef
89
+ ? OTProcessor.prototype.getClassID(id, /** @type {ClassDefTable} */ (GDEF.markAttachClassDef))
90
+ : 0;
46
91
  } else {
47
92
  this.isMark = this.codePoints.length > 0 && this.codePoints.every(isMark);
48
93
  this.isBase = !this.isMark;
@@ -51,6 +96,9 @@ export default class GlyphInfo {
51
96
  }
52
97
  }
53
98
 
99
+ /**
100
+ * @returns {GlyphInfo}
101
+ */
54
102
  copy() {
55
103
  return new GlyphInfo(this._font, this.id, this.codePoints, this.features);
56
104
  }
@@ -1,21 +1,48 @@
1
+ /** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
2
+ /** @typedef {import('../../types/fontkit').GlyphIteratorOptions} GlyphIteratorOptions */
3
+ /** @typedef {import('../../types/fontkit').LookupFlags} LookupFlags */
4
+ /** @typedef {import('../../types/fontkit').MarkFilteringSet} MarkFilteringSet */
5
+
1
6
  export default class GlyphIterator {
7
+ /**
8
+ * @param {GlyphInfoLike[]} glyphs
9
+ * @param {GlyphIteratorOptions} [options]
10
+ */
2
11
  constructor(glyphs, options) {
12
+ /** @type {GlyphInfoLike[]} */
3
13
  this.glyphs = glyphs;
4
14
  this.reset(options);
5
15
  }
6
16
 
17
+ /**
18
+ * @param {GlyphIteratorOptions} [options]
19
+ * @param {number} [index]
20
+ * @param {MarkFilteringSet | null} [markFilteringSet]
21
+ */
7
22
  reset(options = {}, index = 0, markFilteringSet = null) {
23
+ /** @type {GlyphIteratorOptions} */
8
24
  this.options = options;
25
+ /** @type {LookupFlags} */
9
26
  this.flags = options.flags || {};
27
+ /** @type {number} */
10
28
  this.markAttachmentType = options.markAttachmentType || 0;
29
+ /** @type {MarkFilteringSet | null} */
11
30
  this.markFilteringSet = markFilteringSet;
31
+ /** @type {number} */
12
32
  this.index = index;
13
33
  }
14
34
 
35
+ /**
36
+ * @returns {GlyphInfoLike | null}
37
+ */
15
38
  get cur() {
16
39
  return this.glyphs[this.index] || null;
17
40
  }
18
41
 
42
+ /**
43
+ * @param {GlyphInfoLike} glyph
44
+ * @returns {boolean}
45
+ */
19
46
  shouldIgnore(glyph) {
20
47
  // Mark skip hierarchy (OT chapter 2): ignoreMarks > mark filtering set > markAttachmentType.
21
48
  if (glyph.isMark) {
@@ -30,10 +57,14 @@ export default class GlyphIterator {
30
57
  }
31
58
  }
32
59
 
33
- return (this.flags.ignoreBaseGlyphs && glyph.isBase) ||
34
- (this.flags.ignoreLigatures && glyph.isLigature);
60
+ return !!(this.flags.ignoreBaseGlyphs && glyph.isBase)
61
+ || !!(this.flags.ignoreLigatures && glyph.isLigature);
35
62
  }
36
63
 
64
+ /**
65
+ * @param {number} dir
66
+ * @returns {GlyphInfoLike | null}
67
+ */
37
68
  move(dir) {
38
69
  this.index += dir;
39
70
  while (0 <= this.index && this.index < this.glyphs.length && this.shouldIgnore(this.glyphs[this.index])) {
@@ -47,14 +78,24 @@ export default class GlyphIterator {
47
78
  return this.glyphs[this.index];
48
79
  }
49
80
 
81
+ /**
82
+ * @returns {GlyphInfoLike | null}
83
+ */
50
84
  next() {
51
85
  return this.move(+1);
52
86
  }
53
87
 
88
+ /**
89
+ * @returns {GlyphInfoLike | null}
90
+ */
54
91
  prev() {
55
92
  return this.move(-1);
56
93
  }
57
94
 
95
+ /**
96
+ * @param {number} [count]
97
+ * @returns {GlyphInfoLike | undefined}
98
+ */
58
99
  peek(count = 1) {
59
100
  let idx = this.index;
60
101
  let res = this.increment(count);
@@ -62,6 +103,10 @@ export default class GlyphIterator {
62
103
  return res;
63
104
  }
64
105
 
106
+ /**
107
+ * @param {number} [count]
108
+ * @returns {number}
109
+ */
65
110
  peekIndex(count = 1) {
66
111
  let idx = this.index;
67
112
  this.increment(count);
@@ -70,6 +115,10 @@ export default class GlyphIterator {
70
115
  return res;
71
116
  }
72
117
 
118
+ /**
119
+ * @param {number} [count]
120
+ * @returns {GlyphInfoLike | undefined}
121
+ */
73
122
  increment(count = 1) {
74
123
  let dir = count < 0 ? -1 : 1;
75
124
  count = Math.abs(count);
@@ -4,14 +4,35 @@ import GlyphInfo from './GlyphInfo';
4
4
  import GSUBProcessor from './GSUBProcessor';
5
5
  import GPOSProcessor from './GPOSProcessor';
6
6
 
7
+ /** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
8
+ /** @typedef {import('../../types/fontkit').GlyphRunLike} GlyphRunLike */
9
+ /** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
10
+ /** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
11
+ /** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
12
+ /** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
13
+ /** @typedef {import('../../types/fontkit').LanguageTag} LanguageTag */
14
+ /** @typedef {import('../../types/fontkit').LayoutGlyph} LayoutGlyph */
15
+ /** @typedef {import('../../types/fontkit').ShaperLike} ShaperLike */
16
+
7
17
  export default class OTLayoutEngine {
18
+ /**
19
+ * @param {LayoutFont} font
20
+ */
8
21
  constructor(font) {
22
+ /** @type {LayoutFont} */
9
23
  this.font = font;
24
+ /** @type {GlyphInfo[] | null} */
10
25
  this.glyphInfos = null;
26
+ /** @type {ShapingPlan | null} */
11
27
  this.plan = null;
28
+ /** @type {GSUBProcessor | null} */
12
29
  this.GSUBProcessor = null;
30
+ /** @type {GPOSProcessor | null} */
13
31
  this.GPOSProcessor = null;
32
+ /** @type {boolean} */
14
33
  this.fallbackPosition = true;
34
+ /** @type {ShaperLike | null} */
35
+ this.shaper = null;
15
36
 
16
37
  if (font.GSUB) {
17
38
  this.GSUBProcessor = new GSUBProcessor(font, font.GSUB);
@@ -22,12 +43,18 @@ export default class OTLayoutEngine {
22
43
  }
23
44
  }
24
45
 
46
+ /**
47
+ * @param {GlyphRunLike} glyphRun
48
+ */
25
49
  setup(glyphRun) {
26
50
  // Map glyphs to GlyphInfo objects so data can be passed between
27
51
  // GSUB and GPOS without mutating the real (shared) Glyph objects.
28
- this.glyphInfos = glyphRun.glyphs.map(glyph => new GlyphInfo(this.font, glyph.id, [...glyph.codePoints]));
52
+ this.glyphInfos = /** @type {LayoutGlyph[]} */ (glyphRun.glyphs).map(
53
+ /** @param {LayoutGlyph} glyph */ glyph => new GlyphInfo(this.font, glyph.id, [...glyph.codePoints])
54
+ );
29
55
 
30
56
  // Select an OpenType script for GSUB/GPOS features.
57
+ /** @type {string | null} */
31
58
  let script = null;
32
59
  if (this.GPOSProcessor) {
33
60
  script = this.GPOSProcessor.selectScript(glyphRun.script, glyphRun.language, glyphRun.direction);
@@ -40,12 +67,13 @@ export default class OTLayoutEngine {
40
67
  // Choose the complex shaper from the buffer Unicode script (HB model). The OT
41
68
  // script may fall back to DFLT/latn even for Thai text; that must not select
42
69
  // DefaultShaper or Thai SARA AM / PUA preprocessing is skipped.
43
- this.shaper = Shapers.choose(glyphRun.script || script);
70
+ this.shaper = /** @type {ShaperLike} */ (Shapers.choose(glyphRun.script || script));
44
71
  this.plan = new ShapingPlan(this.font, script || glyphRun.script, glyphRun.direction);
45
72
  this.plan.bufferScript = glyphRun.script;
46
73
  this.shaper.plan(this.plan, this.glyphInfos, glyphRun.features);
47
74
 
48
75
  // Enabled features as true, then overlay user values (e.g. aalt: 2) without mutating input.
76
+ /** @type {FeatureMap} */
49
77
  let features = {};
50
78
  for (let key in this.plan.allFeatures) {
51
79
  features[key] = true;
@@ -53,38 +81,62 @@ export default class OTLayoutEngine {
53
81
  glyphRun.features = Object.assign(features, glyphRun.features);
54
82
  }
55
83
 
84
+ /**
85
+ * @param {GlyphRunLike} glyphRun
86
+ */
56
87
  substitute(glyphRun) {
57
- if (this.GSUBProcessor) {
88
+ if (this.GSUBProcessor && this.plan && this.glyphInfos) {
58
89
  this.plan.process(this.GSUBProcessor, this.glyphInfos);
59
90
 
60
91
  // Map glyph infos back to normal Glyph objects
61
- glyphRun.glyphs = this.glyphInfos.map(glyphInfo => this.font.getGlyph(glyphInfo.id, glyphInfo.codePoints));
92
+ /** @type {LayoutGlyph[]} */
93
+ let glyphs = [];
94
+ for (let glyphInfo of this.glyphInfos) {
95
+ let g = this.font.getGlyph(glyphInfo.id, glyphInfo.codePoints);
96
+ if (g) {
97
+ glyphs.push(g);
98
+ }
99
+ }
100
+ glyphRun.glyphs = glyphs;
62
101
  }
63
102
  }
64
103
 
104
+ /**
105
+ * @param {GlyphRunLike} glyphRun
106
+ * @returns {FeatureMap | Record<string, import('../../types/fontkit').OTFeature> | null | undefined | false}
107
+ */
65
108
  position(glyphRun) {
66
- if (this.shaper.zeroMarkWidths === 'BEFORE_GPOS') {
109
+ if (this.shaper && this.shaper.zeroMarkWidths === 'BEFORE_GPOS' && glyphRun.positions) {
67
110
  this.zeroMarkAdvances(glyphRun.positions);
68
111
  }
69
112
 
70
- if (this.GPOSProcessor) {
113
+ if (this.GPOSProcessor && this.plan && this.glyphInfos && glyphRun.positions) {
71
114
  this.plan.process(this.GPOSProcessor, this.glyphInfos, glyphRun.positions);
72
115
  }
73
116
 
74
- if (this.shaper.zeroMarkWidths === 'AFTER_GPOS') {
117
+ if (this.shaper && this.shaper.zeroMarkWidths === 'AFTER_GPOS' && glyphRun.positions) {
75
118
  this.zeroMarkAdvances(glyphRun.positions);
76
119
  }
77
120
 
78
121
  // Reverse the glyphs and positions if the script is right-to-left
79
122
  if (glyphRun.direction === 'rtl') {
80
123
  glyphRun.glyphs.reverse();
81
- glyphRun.positions.reverse();
124
+ if (glyphRun.positions) {
125
+ glyphRun.positions.reverse();
126
+ }
82
127
  }
83
128
 
84
129
  return this.GPOSProcessor && this.GPOSProcessor.features;
85
130
  }
86
131
 
132
+ /**
133
+ * @param {GlyphPositionLike[]} positions
134
+ */
87
135
  zeroMarkAdvances(positions) {
136
+ if (!this.glyphInfos) {
137
+ return;
138
+ }
139
+
88
140
  for (let i = 0; i < this.glyphInfos.length; i++) {
89
141
  if (this.glyphInfos[i].isMark) {
90
142
  positions[i].xAdvance = 0;
@@ -99,7 +151,13 @@ export default class OTLayoutEngine {
99
151
  this.shaper = null;
100
152
  }
101
153
 
154
+ /**
155
+ * @param {ScriptTag | string[] | null | undefined} script
156
+ * @param {LanguageTag | null | undefined} language
157
+ * @returns {string[]}
158
+ */
102
159
  getAvailableFeatures(script, language) {
160
+ /** @type {string[]} */
103
161
  let features = [];
104
162
 
105
163
  if (this.GSUBProcessor) {