@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
@@ -7,24 +7,59 @@
7
7
  *
8
8
  * @private
9
9
  */
10
+
11
+ /** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
12
+ /** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
13
+ /** @typedef {import('../../types/fontkit').TextDirection} TextDirection */
14
+ /** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
15
+ /** @typedef {import('../../types/fontkit').FeatureInput} FeatureInput */
16
+ /** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
17
+ /** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
18
+ /** @typedef {import('../../types/fontkit').ShapingStage} ShapingStage */
19
+ /** @typedef {import('../../types/fontkit').ShapingStageFn} ShapingStageFn */
20
+ /** @typedef {import('./OTProcessor').default} OTProcessor */
21
+
22
+ /**
23
+ * @typedef {{ global?: string[], local?: string[] }} FeatureStagesArg
24
+ */
25
+
10
26
  export default class ShapingPlan {
27
+ /**
28
+ * @param {LayoutFont} font
29
+ * @param {ScriptTag | string[] | null | undefined} script
30
+ * @param {TextDirection} direction
31
+ */
11
32
  constructor(font, script, direction) {
33
+ /** @type {LayoutFont} */
12
34
  this.font = font;
35
+ /** @type {ScriptTag | string[] | null | undefined} */
13
36
  this.script = script;
37
+ /** @type {TextDirection} */
14
38
  this.direction = direction;
39
+ /** @type {ShapingStage[]} */
15
40
  this.stages = [];
41
+ /** @type {FeatureMap} */
16
42
  this.globalFeatures = {};
43
+ /** @type {Record<string, number>} */
17
44
  this.allFeatures = {};
45
+ /** @type {FeatureMap | null} */
18
46
  this.userFeatures = null; // original user feature values (for Type 3 alternate index)
47
+ /** @type {ScriptTag | string[] | null | undefined} */
48
+ this.bufferScript = undefined;
19
49
  }
20
50
 
21
51
  /**
22
52
  * Adds the given features to the last stage.
23
53
  * Already-planned tags are not duplicated, but a global add still promotes them.
54
+ * @param {string[]} features
55
+ * @param {boolean} global
24
56
  */
25
57
  _addFeatures(features, global) {
26
58
  let stageIndex = this.stages.length - 1;
27
59
  let stage = this.stages[stageIndex];
60
+ if (!Array.isArray(stage)) {
61
+ return;
62
+ }
28
63
  for (let feature of features) {
29
64
  if (this.allFeatures[feature] == null) {
30
65
  stage.push(feature);
@@ -39,6 +74,8 @@ export default class ShapingPlan {
39
74
 
40
75
  /**
41
76
  * Add features to the last stage
77
+ * @param {string | string[] | FeatureStagesArg} arg
78
+ * @param {boolean} [global]
42
79
  */
43
80
  add(arg, global = true) {
44
81
  if (this.stages.length === 0) {
@@ -51,7 +88,7 @@ export default class ShapingPlan {
51
88
 
52
89
  if (Array.isArray(arg)) {
53
90
  this._addFeatures(arg, global);
54
- } else if (typeof arg === 'object') {
91
+ } else if (typeof arg === 'object' && arg !== null) {
55
92
  this._addFeatures(arg.global || [], true);
56
93
  this._addFeatures(arg.local || [], false);
57
94
  } else {
@@ -61,6 +98,8 @@ export default class ShapingPlan {
61
98
 
62
99
  /**
63
100
  * Add a new stage
101
+ * @param {string | string[] | FeatureStagesArg | ShapingStageFn} arg
102
+ * @param {boolean} [global]
64
103
  */
65
104
  addStage(arg, global) {
66
105
  if (typeof arg === 'function') {
@@ -71,17 +110,22 @@ export default class ShapingPlan {
71
110
  }
72
111
  }
73
112
 
113
+ /**
114
+ * @param {FeatureInput | null | undefined} features
115
+ */
74
116
  setFeatureOverrides(features) {
75
117
  if (Array.isArray(features)) {
76
118
  this.add(features);
77
- } else if (typeof features === 'object') {
119
+ } else if (typeof features === 'object' && features !== null) {
78
120
  this.userFeatures = features;
79
121
  for (let tag in features) {
80
122
  if (features[tag]) {
81
123
  this.add(tag);
82
124
  } else if (this.allFeatures[tag] != null) {
83
125
  let stage = this.stages[this.allFeatures[tag]];
84
- stage.splice(stage.indexOf(tag), 1);
126
+ if (Array.isArray(stage)) {
127
+ stage.splice(stage.indexOf(tag), 1);
128
+ }
85
129
  delete this.allFeatures[tag];
86
130
  delete this.globalFeatures[tag];
87
131
  }
@@ -91,6 +135,7 @@ export default class ShapingPlan {
91
135
 
92
136
  /**
93
137
  * Assigns the global features to the given glyphs
138
+ * @param {GlyphInfoLike[]} glyphs
94
139
  */
95
140
  assignGlobalFeatures(glyphs) {
96
141
  for (let glyph of glyphs) {
@@ -102,6 +147,9 @@ export default class ShapingPlan {
102
147
 
103
148
  /**
104
149
  * Executes the planned stages using the given OTProcessor
150
+ * @param {OTProcessor} processor
151
+ * @param {GlyphInfoLike[]} glyphs
152
+ * @param {GlyphPositionLike[] | null | undefined} [positions]
105
153
  */
106
154
  process(processor, glyphs, positions) {
107
155
  // Preserve original user values (e.g. { aalt: 2 }) for GSUB Type 3.
@@ -4,6 +4,9 @@ import UnicodeTrie from '../../packages/unicode-trie/index.js';
4
4
  import { decodeBase64 } from '../../utils';
5
5
  import dataTrie from './data.trie';
6
6
 
7
+ /** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
8
+ /** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
9
+
7
10
  const trie = new UnicodeTrie(decodeBase64(dataTrie));
8
11
  const FEATURES = ['isol', 'fina', 'fin2', 'fin3', 'medi', 'med2', 'init'];
9
12
 
@@ -25,9 +28,13 @@ const FIN3 = 'fin3';
25
28
  const MEDI = 'medi';
26
29
  const MED2 = 'med2';
27
30
  const INIT = 'init';
31
+ /** @type {null} */
28
32
  const NONE = null;
29
33
 
34
+ /** @typedef {[string | null, string | null, number]} ArabicStateTransition */
35
+
30
36
  // Each entry is [prevAction, curAction, nextState]
37
+ /** @type {ArabicStateTransition[][]} */
31
38
  const STATE_TABLE = [
32
39
  // Non_Joining, Left_Joining, Right_Joining, Dual_Joining, ALAPH, DALATH RISH
33
40
  // State 0: prev was U, not willing to join.
@@ -61,6 +68,9 @@ const STATE_TABLE = [
61
68
  * https://github.com/behdad/harfbuzz/blob/master/src/hb-ot-shape-complex-arabic.cc
62
69
  */
63
70
  export default class ArabicShaper extends DefaultShaper {
71
+ /**
72
+ * @param {ShapingPlanLike} plan
73
+ */
64
74
  static planFeatures(plan) {
65
75
  plan.add(['ccmp', 'locl']);
66
76
  for (let i = 0; i < FEATURES.length; i++) {
@@ -71,11 +81,16 @@ export default class ArabicShaper extends DefaultShaper {
71
81
  plan.addStage('mset');
72
82
  }
73
83
 
84
+ /**
85
+ * @param {ShapingPlanLike} plan
86
+ * @param {GlyphInfoLike[]} glyphs
87
+ */
74
88
  static assignFeatures(plan, glyphs) {
75
89
  super.assignFeatures(plan, glyphs);
76
90
 
77
91
  let prev = -1;
78
92
  let state = 0;
93
+ /** @type {Array<string | null | undefined>} */
79
94
  let actions = [];
80
95
 
81
96
  // Apply the state machine to map glyphs to features
@@ -109,6 +124,10 @@ export default class ArabicShaper extends DefaultShaper {
109
124
  }
110
125
  }
111
126
 
127
+ /**
128
+ * @param {number} codePoint
129
+ * @returns {number}
130
+ */
112
131
  function getShapingClass(codePoint) {
113
132
  let res = trie.get(codePoint);
114
133
  if (res) {
@@ -1,11 +1,19 @@
1
1
  import { isDigit, isMark } from '../../packages/unicode-properties/index.js';
2
2
  import GlyphInfo from '../GlyphInfo';
3
3
 
4
+ /** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
5
+ /** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
6
+ /** @typedef {import('../../../types/fontkit').FeatureInput} FeatureInput */
7
+ /** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
8
+ /** @typedef {import('../../../types/fontkit').FeatureMap} FeatureMap */
9
+ /** @typedef {import('../../../types/fontkit').TextDirection} TextDirection */
10
+
4
11
  const VARIATION_FEATURES = ['rvrn'];
5
12
  const COMMON_FEATURES = ['ccmp', 'locl', 'rlig', 'mark', 'mkmk'];
6
13
  const FRACTIONAL_FEATURES = ['frac', 'numr', 'dnom'];
7
14
  const HORIZONTAL_FEATURES = ['calt', 'clig', 'liga', 'rclt', 'curs', 'kern'];
8
15
  const _VERTICAL_FEATURES = ['vert'];
16
+ /** @type {Record<TextDirection, string[]>} */
9
17
  const DIRECTIONAL_FEATURES = {
10
18
  ltr: ['ltra', 'ltrm'],
11
19
  rtl: ['rtla', 'rtlm']
@@ -13,6 +21,12 @@ const DIRECTIONAL_FEATURES = {
13
21
 
14
22
  export default class DefaultShaper {
15
23
  static zeroMarkWidths = 'AFTER_GPOS';
24
+
25
+ /**
26
+ * @param {ShapingPlanLike} plan
27
+ * @param {GlyphInfoLike[]} glyphs
28
+ * @param {FeatureInput | null | undefined} features
29
+ */
16
30
  static plan(plan, glyphs, features) {
17
31
  // Plan the features we want to apply
18
32
  this.planPreprocessing(plan);
@@ -26,6 +40,9 @@ export default class DefaultShaper {
26
40
  this.assignFeatures(plan, glyphs);
27
41
  }
28
42
 
43
+ /**
44
+ * @param {ShapingPlanLike} plan
45
+ */
29
46
  static planPreprocessing(plan) {
30
47
  plan.add({
31
48
  global: [...VARIATION_FEATURES, ...DIRECTIONAL_FEATURES[plan.direction]],
@@ -33,15 +50,26 @@ export default class DefaultShaper {
33
50
  });
34
51
  }
35
52
 
53
+ /**
54
+ * @param {ShapingPlanLike} _plan
55
+ */
36
56
  static planFeatures(_plan) {
37
57
  // Do nothing by default. Let subclasses override this.
38
58
  }
39
59
 
60
+ /**
61
+ * @param {ShapingPlanLike} plan
62
+ * @param {FeatureInput | null | undefined} userFeatures
63
+ */
40
64
  static planPostprocessing(plan, userFeatures) {
41
65
  plan.add([...COMMON_FEATURES, ...HORIZONTAL_FEATURES]);
42
66
  plan.setFeatureOverrides(userFeatures);
43
67
  }
44
68
 
69
+ /**
70
+ * @param {ShapingPlanLike} plan
71
+ * @param {GlyphInfoLike[]} glyphs
72
+ */
45
73
  static assignFeatures(plan, glyphs) {
46
74
  // Font-aware NFC before GSUB (HarfBuzz default-shaper behavior).
47
75
  composeGlyphs(plan.font, glyphs);
@@ -75,9 +103,17 @@ export default class DefaultShaper {
75
103
  }
76
104
  }
77
105
 
78
- // Compose base+mark clusters when the font has the precomposed glyph.
79
- // Skip pure mark reorders (same length) so Arabic calt etc. keep expected order.
106
+ /**
107
+ * Compose base+mark clusters when the font has the precomposed glyph.
108
+ * Skip pure mark reorders (same length) so Arabic calt etc. keep expected order.
109
+ * @param {LayoutFont} font
110
+ * @param {GlyphInfoLike[]} glyphs
111
+ */
80
112
  function composeGlyphs(font, glyphs) {
113
+ /**
114
+ * @param {GlyphInfoLike} g
115
+ * @returns {boolean}
116
+ */
81
117
  let singleMark = g => g.codePoints.length === 1 && isMark(g.codePoints[0]);
82
118
 
83
119
  for (let i = 0; i < glyphs.length;) {
@@ -94,12 +130,15 @@ function composeGlyphs(font, glyphs) {
94
130
  continue;
95
131
  }
96
132
 
97
- let input = glyphs.slice(i, end).map(g => g.codePoints[0]);
133
+ let input = glyphs.slice(i, end).map(/** @param {GlyphInfoLike} g */ g => g.codePoints[0]);
98
134
  let composed = Array.from(String.fromCodePoint(...input).normalize('NFC')).flatMap((char) => {
99
135
  let cp = char.codePointAt(0);
136
+ if (cp == null) {
137
+ return /** @type {number[]} */ ([]);
138
+ }
100
139
  return font.hasGlyphForCodePoint(cp)
101
140
  ? [cp]
102
- : Array.from(char.normalize('NFD'), c => c.codePointAt(0));
141
+ : Array.from(char.normalize('NFD'), c => /** @type {number} */ (c.codePointAt(0)));
103
142
  });
104
143
 
105
144
  if (composed.length === input.length) {
@@ -107,9 +146,14 @@ function composeGlyphs(font, glyphs) {
107
146
  continue;
108
147
  }
109
148
 
110
- let replacement = composed.map(
111
- cp => new GlyphInfo(font, font.glyphForCodePoint(cp).id, [cp], base.features)
112
- );
149
+ /** @type {GlyphInfo[]} */
150
+ let replacement = [];
151
+ for (let cp of composed) {
152
+ let g = font.glyphForCodePoint(cp);
153
+ if (g) {
154
+ replacement.push(new GlyphInfo(font, g.id, [cp], /** @type {FeatureMap} */ (base.features)));
155
+ }
156
+ }
113
157
  glyphs.splice(i, end - i, ...replacement);
114
158
  i += replacement.length;
115
159
  }
@@ -1,6 +1,12 @@
1
1
  import DefaultShaper from './DefaultShaper';
2
2
  import GlyphInfo from '../GlyphInfo';
3
3
 
4
+ /** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
5
+ /** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
6
+ /** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
7
+ /** @typedef {import('../../../types/fontkit').FeatureMap} FeatureMap */
8
+ /** @typedef {import('../../../types/fontkit').FeatureInput} FeatureInput */
9
+
4
10
  /**
5
11
  * This is a shaper for the Hangul script, used by the Korean language.
6
12
  * It does the following:
@@ -25,10 +31,18 @@ import GlyphInfo from '../GlyphInfo';
25
31
  */
26
32
  export default class HangulShaper extends DefaultShaper {
27
33
  static zeroMarkWidths = 'NONE';
34
+
35
+ /**
36
+ * @param {ShapingPlanLike} plan
37
+ */
28
38
  static planFeatures(plan) {
29
39
  plan.add(['ljmo', 'vjmo', 'tjmo'], false);
30
40
  }
31
41
 
42
+ /**
43
+ * @param {ShapingPlanLike} plan
44
+ * @param {GlyphInfoLike[]} glyphs
45
+ */
32
46
  static assignFeatures(plan, glyphs) {
33
47
  let state = 0;
34
48
  let i = 0;
@@ -83,15 +97,24 @@ const V_END = V_BASE + V_COUNT - 1;
83
97
  const T_END = T_BASE + T_COUNT - 1;
84
98
  const DOTTED_CIRCLE = 0x25cc;
85
99
 
100
+ /** @param {number} code @returns {boolean} */
86
101
  const isL = code => 0x1100 <= code && code <= 0x115f || 0xa960 <= code && code <= 0xa97c;
102
+ /** @param {number} code @returns {boolean} */
87
103
  const isV = code => 0x1160 <= code && code <= 0x11a7 || 0xd7b0 <= code && code <= 0xd7c6;
104
+ /** @param {number} code @returns {boolean} */
88
105
  const isT = code => 0x11a8 <= code && code <= 0x11ff || 0xd7cb <= code && code <= 0xd7fb;
106
+ /** @param {number} code @returns {boolean} */
89
107
  const isTone = code => 0x302e <= code && code <= 0x302f;
108
+ /** @param {number} code @returns {boolean} */
90
109
  const isLVT = code => HANGUL_BASE <= code && code <= HANGUL_END;
110
+ /** @param {number} code @returns {boolean} */
91
111
  const isLV = code => (code - HANGUL_BASE) < HANGUL_COUNT && (code - HANGUL_BASE) % T_COUNT === 0;
112
+ /** @param {number} code @returns {boolean} */
92
113
  const isCombiningL = code => L_BASE <= code && code <= L_END;
114
+ /** @param {number} code @returns {boolean} */
93
115
  const isCombiningV = code => V_BASE <= code && code <= V_END;
94
- const isCombiningT = code => T_BASE + 1 && 1 <= code && code <= T_END;
116
+ /** @param {number} code @returns {boolean} */
117
+ const isCombiningT = code => T_BASE + 1 <= code && code <= T_END;
95
118
 
96
119
  // Character categories
97
120
  const X = 0; // Other character
@@ -102,7 +125,11 @@ const LV = 4; // Composed <LV> syllable
102
125
  const LVT = 5; // Composed <LVT> syllable
103
126
  const M = 6; // Tone mark
104
127
 
105
- // This function classifies a character using the above categories.
128
+ /**
129
+ * This function classifies a character using the above categories.
130
+ * @param {number} code
131
+ * @returns {number}
132
+ */
106
133
  function getType(code) {
107
134
  if (isL(code)) { return L; }
108
135
  if (isV(code)) { return V; }
@@ -120,8 +147,11 @@ const COMPOSE = 2;
120
147
  const TONE_MARK = 4;
121
148
  const INVALID = 5;
122
149
 
150
+ /** @typedef {[number, number]} HangulTransition */
151
+
123
152
  // Build a state machine that accepts valid syllables, and applies actions along the way.
124
153
  // The logic this is implementing is documented at the top of the file.
154
+ /** @type {HangulTransition[][]} */
125
155
  const STATE_TABLE = [
126
156
  // X L V T LV LVT M
127
157
  // State 0: start state
@@ -137,10 +167,24 @@ const STATE_TABLE = [
137
167
  [[NO_ACTION, 0], [NO_ACTION, 1], [NO_ACTION, 0], [NO_ACTION, 0], [DECOMPOSE, 2], [DECOMPOSE, 3], [TONE_MARK, 0]]
138
168
  ];
139
169
 
170
+ /**
171
+ * @param {LayoutFont} font
172
+ * @param {number} code
173
+ * @param {FeatureMap | FeatureInput | null | undefined} features
174
+ * @returns {GlyphInfo}
175
+ */
140
176
  function getGlyph(font, code, features) {
141
- return new GlyphInfo(font, font.glyphForCodePoint(code).id, [code], features);
177
+ let g = font.glyphForCodePoint(code);
178
+ // Callers typically check hasGlyphForCodePoint first; .notdef if somehow missing.
179
+ return new GlyphInfo(font, g ? g.id : 0, [code], features);
142
180
  }
143
181
 
182
+ /**
183
+ * @param {GlyphInfoLike[]} glyphs
184
+ * @param {number} i
185
+ * @param {LayoutFont} font
186
+ * @returns {number}
187
+ */
144
188
  function decompose(glyphs, i, font) {
145
189
  let glyph = glyphs[i];
146
190
  let code = glyph.codePoints[0];
@@ -178,6 +222,12 @@ function decompose(glyphs, i, font) {
178
222
  return i + insert.length - 1;
179
223
  }
180
224
 
225
+ /**
226
+ * @param {GlyphInfoLike[]} glyphs
227
+ * @param {number} i
228
+ * @param {LayoutFont} font
229
+ * @returns {number}
230
+ */
181
231
  function compose(glyphs, i, font) {
182
232
  let glyph = glyphs[i];
183
233
  let code = glyphs[i].codePoints[0];
@@ -187,7 +237,14 @@ function compose(glyphs, i, font) {
187
237
  let prevType = getType(prev);
188
238
 
189
239
  // Figure out what type of syllable we're dealing with
190
- let lv, ljmo, vjmo, tjmo;
240
+ /** @type {number | undefined} */
241
+ let lv;
242
+ /** @type {GlyphInfoLike | undefined} */
243
+ let ljmo;
244
+ /** @type {GlyphInfoLike | undefined} */
245
+ let vjmo;
246
+ /** @type {GlyphInfoLike | undefined} */
247
+ let tjmo;
191
248
  if (prevType === LV && type === T) {
192
249
  // <LV,T>
193
250
  lv = prev;
@@ -242,6 +299,12 @@ function compose(glyphs, i, font) {
242
299
  return i;
243
300
  }
244
301
 
302
+ /**
303
+ * Length of the Hangul syllable ending at `code` (for tone-mark reordering).
304
+ * Tone marks only attach to LV/LVT/V/T clusters from the state machine.
305
+ * @param {number} code
306
+ * @returns {number}
307
+ */
245
308
  function getLength(code) {
246
309
  switch (getType(code)) {
247
310
  case LV:
@@ -251,23 +314,37 @@ function getLength(code) {
251
314
  return 2;
252
315
  case T:
253
316
  return 3;
317
+ default:
318
+ return 1;
254
319
  }
255
320
  }
256
321
 
322
+ /**
323
+ * @param {GlyphInfoLike[]} glyphs
324
+ * @param {number} i
325
+ * @param {LayoutFont} font
326
+ */
257
327
  function reorderToneMark(glyphs, i, font) {
258
328
  let glyph = glyphs[i];
259
329
  let code = glyphs[i].codePoints[0];
260
330
 
261
331
  // Move tone mark to the beginning of the previous syllable, unless it is zero width
262
- if (font.glyphForCodePoint(code).advanceWidth === 0) { return; }
332
+ let toneGlyph = font.glyphForCodePoint(code);
333
+ if (!toneGlyph || toneGlyph.advanceWidth === 0) { return; }
263
334
 
264
335
  let prev = glyphs[i - 1].codePoints[0];
265
336
  let len = getLength(prev);
266
337
 
267
338
  glyphs.splice(i, 1);
268
- return glyphs.splice(i - len, 0, glyph);
339
+ glyphs.splice(i - len, 0, glyph);
269
340
  }
270
341
 
342
+ /**
343
+ * @param {GlyphInfoLike[]} glyphs
344
+ * @param {number} i
345
+ * @param {LayoutFont} font
346
+ * @returns {number}
347
+ */
271
348
  function insertDottedCircle(glyphs, i, font) {
272
349
  let glyph = glyphs[i];
273
350
  let code = glyphs[i].codePoints[0];
@@ -276,7 +353,8 @@ function insertDottedCircle(glyphs, i, font) {
276
353
  let dottedCircle = getGlyph(font, DOTTED_CIRCLE, glyph.features);
277
354
 
278
355
  // If the tone mark is zero width, insert the dotted circle before, otherwise after
279
- let idx = font.glyphForCodePoint(code).advanceWidth === 0 ? i : i + 1;
356
+ let toneGlyph = font.glyphForCodePoint(code);
357
+ let idx = (!toneGlyph || toneGlyph.advanceWidth === 0) ? i : i + 1;
280
358
  glyphs.splice(idx, 0, dottedCircle);
281
359
  i++;
282
360
  }
@@ -2,12 +2,23 @@ import { getCombiningClass } from '../../packages/unicode-properties/index.js';
2
2
  import DefaultShaper from './DefaultShaper';
3
3
  import GlyphInfo from '../GlyphInfo';
4
4
 
5
+ /** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
6
+ /** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
7
+ /** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
8
+ /** @typedef {import('../../../types/fontkit').OTScriptRecord} OTScriptRecord */
9
+ /** @typedef {import('../../../types/fontkit').OTLangSys} OTLangSys */
10
+ /** @typedef {import('../../../types/fontkit').OTLangSysRecord} OTLangSysRecord */
11
+
5
12
  /**
6
13
  * Hebrew shaper (HarfBuzz hb-ot-shaper-hebrew.cc):
7
14
  * - Compose presentation forms (FBxx) when the font lacks GPOS mark for `hebr`
8
15
  * - Reorder patah/qamats + sheva/hiriq + meteg/below so meteg sits next to the base
9
16
  */
10
17
  export default class HebrewShaper extends DefaultShaper {
18
+ /**
19
+ * @param {ShapingPlanLike} plan
20
+ * @param {GlyphInfoLike[]} glyphs
21
+ */
11
22
  static assignFeatures(plan, glyphs) {
12
23
  super.assignFeatures(plan, glyphs);
13
24
 
@@ -26,7 +37,12 @@ const DAGESH_FORMS = [
26
37
  0xFB48, 0xFB49, 0xFB4A
27
38
  ];
28
39
 
29
- // Precomposed Hebrew presentation form for `a + b`, or null (HB compose_hebrew).
40
+ /**
41
+ * Precomposed Hebrew presentation form for `a + b`, or null (HB compose_hebrew).
42
+ * @param {number} a
43
+ * @param {number} b
44
+ * @returns {number | null}
45
+ */
30
46
  function composeHebrewPair(a, b) {
31
47
  switch (b) {
32
48
  case 0x05B4: // HIRIQ
@@ -53,8 +69,12 @@ function composeHebrewPair(a, b) {
53
69
  }
54
70
  }
55
71
 
56
- // Greedily compose adjacent pairs when the font has the glyph
57
- // (e.g. SHIN + SHIN_DOT FB2A, then + DAGESH FB2C).
72
+ /**
73
+ * Greedily compose adjacent pairs when the font has the glyph
74
+ * (e.g. SHIN + SHIN_DOT → FB2A, then + DAGESH → FB2C).
75
+ * @param {GlyphInfoLike[]} glyphs
76
+ * @param {LayoutFont} font
77
+ */
58
78
  function composeHebrew(glyphs, font) {
59
79
  for (let i = 0; i + 1 < glyphs.length;) {
60
80
  let composed = glyphs[i].codePoints[0];
@@ -66,17 +86,26 @@ function composeHebrew(glyphs, font) {
66
86
  consumed++;
67
87
  }
68
88
  if (consumed > 1) {
89
+ let composedGlyph = font.glyphForCodePoint(composed);
90
+ if (!composedGlyph) {
91
+ i++;
92
+ continue;
93
+ }
94
+ /** @type {number[]} */
69
95
  let cps = [];
70
96
  for (let j = 0; j < consumed; j++) cps.push(...glyphs[i + j].codePoints);
71
- glyphs[i] = new GlyphInfo(font, font.glyphForCodePoint(composed).id, cps, glyphs[i].features);
97
+ glyphs[i] = new GlyphInfo(font, composedGlyph.id, cps, glyphs[i].features);
72
98
  glyphs.splice(i + 1, consumed - 1);
73
99
  }
74
100
  i++;
75
101
  }
76
102
  }
77
103
 
78
- // HB reorder_marks_hebrew: [patah/qamats, sheva/hiriq, meteg/below] → swap last two.
79
- // unicode-properties already exposes Hebrew modified CCCs (CCC10/14/17/18/22).
104
+ /**
105
+ * HB reorder_marks_hebrew: [patah/qamats, sheva/hiriq, meteg/below] → swap last two.
106
+ * unicode-properties already exposes Hebrew modified CCCs (CCC10/14/17/18/22).
107
+ * @param {GlyphInfoLike[]} glyphs
108
+ */
80
109
  function reorderMarksHebrew(glyphs) {
81
110
  for (let i = 2; i < glyphs.length; i++) {
82
111
  let c0 = getCombiningClass(glyphs[i - 2].codePoints[0]);
@@ -93,13 +122,18 @@ function reorderMarksHebrew(glyphs) {
93
122
  }
94
123
  }
95
124
 
96
- // Gate fallback composition on Hebrew-script GPOS `mark` (not the global featureList).
125
+ /**
126
+ * Gate fallback composition on Hebrew-script GPOS `mark` (not the global featureList).
127
+ * @param {LayoutFont} font
128
+ * @returns {boolean}
129
+ */
97
130
  function hasGposMark(font) {
98
- let hebr = font.GPOS?.scriptList?.find(e => e.tag === 'hebr')?.script;
131
+ let hebr = font.GPOS?.scriptList?.find(/** @param {OTScriptRecord} e */ e => e.tag === 'hebr')?.script;
99
132
  if (!hebr) return false;
100
133
 
101
- let langs = [hebr.defaultLangSys, ...(hebr.langSysRecords || []).map(l => l.langSys)].filter(Boolean);
102
- return langs.some(ls =>
103
- (ls.featureIndexes || []).some(i => font.GPOS.featureList[i]?.tag === 'mark')
134
+ let langs = [hebr.defaultLangSys, ...(hebr.langSysRecords || []).map(/** @param {OTLangSysRecord} l */ l => l.langSys)]
135
+ .filter(/** @returns {value is OTLangSys} */ (/** @type {OTLangSys | null} */ value) => value != null);
136
+ return langs.some(/** @param {OTLangSys} ls */ ls =>
137
+ (ls.featureIndexes || []).some(/** @param {number} i */ i => font.GPOS?.featureList[i]?.tag === 'mark')
104
138
  );
105
139
  }