@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,9 +1,19 @@
1
1
  import Glyph from './Glyph';
2
2
  import BBox from './BBox';
3
3
 
4
+ /** @typedef {import('../../types/fontkit').CPALColor} CPALColor */
5
+ /** @typedef {import('../../types/fontkit').GlyphLike} GlyphLike */
6
+ /** @typedef {import('../../types/fontkit').PathRenderingContext} PathRenderingContext */
7
+
4
8
  class COLRLayer {
9
+ /**
10
+ * @param {GlyphLike | null | undefined} glyph
11
+ * @param {CPALColor} color
12
+ */
5
13
  constructor(glyph, color) {
14
+ /** @type {GlyphLike | null | undefined} */
6
15
  this.glyph = glyph;
16
+ /** @type {CPALColor} */
7
17
  this.color = color;
8
18
  }
9
19
  }
@@ -16,16 +26,22 @@ class COLRLayer {
16
26
  export default class COLRGlyph extends Glyph {
17
27
  type = 'COLR';
18
28
 
29
+ /**
30
+ * @returns {BBox}
31
+ */
19
32
  _getBBox() {
20
33
  let layers = this.layers;
21
34
  if (!layers) {
22
35
  let g = this._font._getBaseGlyph(this.id);
23
- return g ? g.bbox : new BBox(0, 0, 0, 0);
36
+ return g ? new BBox(g.bbox.minX, g.bbox.minY, g.bbox.maxX, g.bbox.maxY) : new BBox(0, 0, 0, 0);
24
37
  }
25
38
 
26
39
  let bbox = new BBox();
27
40
  for (let i = 0; i < layers.length; i++) {
28
41
  let layer = layers[i];
42
+ if (!layer.glyph) {
43
+ continue;
44
+ }
29
45
  let b = layer.glyph.bbox;
30
46
  bbox.addPoint(b.minX, b.minY);
31
47
  bbox.addPoint(b.maxX, b.maxY);
@@ -37,7 +53,7 @@ export default class COLRGlyph extends Glyph {
37
53
  /**
38
54
  * Returns an array of objects containing the glyph and color for
39
55
  * each layer in the composite color glyph.
40
- * @type {object[]}
56
+ * @type {COLRLayer[] | null}
41
57
  */
42
58
  get layers() {
43
59
  let cpal = this._font.CPAL;
@@ -50,6 +66,7 @@ export default class COLRGlyph extends Glyph {
50
66
 
51
67
  let low = 0;
52
68
  let high = colr.baseGlyphRecord.length - 1;
69
+ /** @type {import('../../types/fontkit').COLRBaseGlyphRecord | undefined} */
53
70
  let baseLayer;
54
71
 
55
72
  while (low <= high) {
@@ -70,6 +87,7 @@ export default class COLRGlyph extends Glyph {
70
87
  // default to normal glyph from glyf or CFF
71
88
  if (baseLayer == null) {
72
89
  let g = this._font._getBaseGlyph(this.id);
90
+ /** @type {CPALColor} */
73
91
  let color = {
74
92
  red: 0,
75
93
  green: 0,
@@ -81,10 +99,17 @@ export default class COLRGlyph extends Glyph {
81
99
  }
82
100
 
83
101
  // otherwise, return an array of all the layers
102
+ /** @type {COLRLayer[]} */
84
103
  let layers = [];
85
104
  for (let i = baseLayer.firstLayerIndex; i < baseLayer.firstLayerIndex + baseLayer.numLayers; i++) {
86
105
  let rec = colr.layerRecords[i];
106
+ if (!cpal || !rec) {
107
+ continue;
108
+ }
87
109
  let color = cpal.colorRecords[rec.paletteIndex];
110
+ if (!color) {
111
+ continue;
112
+ }
88
113
  let g = this._font._getBaseGlyph(rec.gid);
89
114
  layers.push(new COLRLayer(g, color));
90
115
  }
@@ -92,6 +117,11 @@ export default class COLRGlyph extends Glyph {
92
117
  return layers;
93
118
  }
94
119
 
120
+ /**
121
+ * @param {PathRenderingContext} ctx
122
+ * @param {number} size
123
+ * @returns {void}
124
+ */
95
125
  render(ctx, size) {
96
126
  let layers = this.layers;
97
127
  if (!layers) {
@@ -99,7 +129,12 @@ export default class COLRGlyph extends Glyph {
99
129
  }
100
130
 
101
131
  for (let { glyph, color } of layers) {
102
- ctx.fillColor([color.red, color.green, color.blue], color.alpha / 255 * 100);
132
+ if (!glyph) {
133
+ continue;
134
+ }
135
+ if (typeof ctx.fillColor === 'function') {
136
+ ctx.fillColor([color.red, color.green, color.blue], color.alpha / 255 * 100);
137
+ }
103
138
  glyph.render(ctx, size);
104
139
  }
105
140
 
@@ -3,6 +3,12 @@ import Path from './Path';
3
3
  import { isMark } from '../packages/unicode-properties/index.js';
4
4
  import StandardNames from './StandardNames';
5
5
 
6
+ /** @typedef {import('../../types/fontkit').FontLike} FontLike */
7
+ /** @typedef {import('../../types/fontkit').GlyphMetrics} GlyphMetrics */
8
+ /** @typedef {import('../../types/fontkit').MetricsTable} MetricsTable */
9
+ /** @typedef {import('../../types/fontkit').PathRenderingContext} PathRenderingContext */
10
+ /** @typedef {import('./BBox').default} BBox */
11
+
6
12
  /**
7
13
  * Glyph objects represent a glyph in the font. They have various properties for accessing metrics and
8
14
  * the actual vector path the glyph represents, and methods for rendering the glyph to a graphics context.
@@ -12,6 +18,11 @@ import StandardNames from './StandardNames';
12
18
  * on the font format, but they all inherit from this class.
13
19
  */
14
20
  export default class Glyph {
21
+ /**
22
+ * @param {number} id
23
+ * @param {number[]} codePoints
24
+ * @param {FontLike} font
25
+ */
15
26
  constructor(id, codePoints, font) {
16
27
  /**
17
28
  * The glyph id in the font
@@ -26,28 +37,50 @@ export default class Glyph {
26
37
  * @type {number[]}
27
38
  */
28
39
  this.codePoints = codePoints;
40
+
41
+ /** @type {FontLike} */
29
42
  this._font = font;
30
43
 
44
+ /** @type {GlyphMetrics | undefined} */
45
+ this._metrics = undefined;
46
+
31
47
  // TODO: get this info from GDEF if available
32
48
  this.isMark = this.codePoints.length > 0 && this.codePoints.every(isMark);
33
49
  this.isLigature = this.codePoints.length > 1;
34
50
  }
35
51
 
52
+ /**
53
+ * @returns {Path}
54
+ */
36
55
  _getPath() {
37
56
  return new Path();
38
57
  }
39
58
 
59
+ /**
60
+ * @returns {BBox}
61
+ */
40
62
  _getCBox() {
41
63
  return this.path.cbox;
42
64
  }
43
65
 
66
+ /**
67
+ * @returns {BBox}
68
+ */
44
69
  _getBBox() {
45
70
  return this.path.bbox;
46
71
  }
47
72
 
73
+ /**
74
+ * @param {MetricsTable} table
75
+ * @returns {{ advance: number, bearing: number }}
76
+ */
48
77
  _getTableMetrics(table) {
49
78
  if (this.id < table.metrics.length) {
50
- return table.metrics.get(this.id);
79
+ let metric = table.metrics.get(this.id);
80
+ return {
81
+ advance: metric ? metric.advance : 0,
82
+ bearing: metric ? metric.bearing : 0
83
+ };
51
84
  }
52
85
 
53
86
  let metric = table.metrics.get(table.metrics.length - 1);
@@ -59,6 +92,10 @@ export default class Glyph {
59
92
  return res;
60
93
  }
61
94
 
95
+ /**
96
+ * @param {BBox} [cbox]
97
+ * @returns {GlyphMetrics}
98
+ */
62
99
  _getMetrics(cbox) {
63
100
  if (this._metrics) { return this._metrics; }
64
101
  if (cbox == null) { ({ cbox } = this); }
@@ -210,10 +247,16 @@ export default class Glyph {
210
247
  return this._getMetrics().bottomBearing;
211
248
  }
212
249
 
250
+ /**
251
+ * @returns {null}
252
+ */
213
253
  get ligatureCaretPositions() {
214
254
  return null;
215
255
  }
216
256
 
257
+ /**
258
+ * @returns {string | null | undefined}
259
+ */
217
260
  _getName() {
218
261
  let { post } = this._font;
219
262
  if (!post) {
@@ -225,25 +268,40 @@ export default class Glyph {
225
268
  return StandardNames[this.id];
226
269
 
227
270
  case 2: {
228
- let id = post.glyphNameIndex[this.id];
271
+ let id = post.glyphNameIndex?.[this.id];
272
+ if (id == null) {
273
+ return null;
274
+ }
229
275
  if (id < StandardNames.length) {
230
276
  return StandardNames[id];
231
277
  }
232
278
 
233
- return post.names[id - StandardNames.length];
279
+ return post.names?.[id - StandardNames.length];
234
280
  }
235
281
 
236
- case 2.5:
237
- return StandardNames[this.id + post.offsets[this.id]];
282
+ case 2.5: {
283
+ let offset = post.offsets?.[this.id];
284
+ if (offset == null) {
285
+ return null;
286
+ }
287
+ return StandardNames[this.id + offset];
288
+ }
238
289
 
239
- case 4:
240
- return String.fromCharCode(post.map[this.id]);
290
+ case 4: {
291
+ let mapped = post.map?.[this.id];
292
+ if (mapped == null) {
293
+ return null;
294
+ }
295
+ return String.fromCharCode(mapped);
296
+ }
241
297
  }
298
+
299
+ return null;
242
300
  }
243
301
 
244
302
  /**
245
303
  * The glyph's name
246
- * @type {string}
304
+ * @type {string | null | undefined}
247
305
  */
248
306
  @cache
249
307
  get name() {
@@ -252,8 +310,9 @@ export default class Glyph {
252
310
 
253
311
  /**
254
312
  * Renders the glyph to the given graphics context, at the specified font size.
255
- * @param {CanvasRenderingContext2d} ctx
313
+ * @param {PathRenderingContext} ctx
256
314
  * @param {number} size
315
+ * @returns {void}
257
316
  */
258
317
  render(ctx, size) {
259
318
  ctx.save();
@@ -1,3 +1,9 @@
1
+ /** @typedef {import('../../types/fontkit').FontLike} FontLike */
2
+ /** @typedef {import('../../types/fontkit').GlyphPoint} GlyphPoint */
3
+ /** @typedef {import('../../types/fontkit').HVARTable} HVARTable */
4
+ /** @typedef {import('../../types/fontkit').ItemVariationStore} ItemVariationStore */
5
+ /** @typedef {import('../../types/fontkit').ItemVariationData} ItemVariationData */
6
+
1
7
  const TUPLES_SHARE_POINT_NUMBERS = 0x8000;
2
8
  const TUPLE_COUNT_MASK = 0x0fff;
3
9
  const EMBEDDED_TUPLE_COORD = 0x8000;
@@ -22,18 +28,35 @@ const DELTA_RUN_COUNT_MASK = 0x3f;
22
28
  * @private
23
29
  */
24
30
  export default class GlyphVariationProcessor {
31
+ /**
32
+ * @param {FontLike} font
33
+ * @param {number[]} coords
34
+ */
25
35
  constructor(font, coords) {
36
+ /** @type {FontLike} */
26
37
  this.font = font;
38
+ /** @type {number[]} */
27
39
  this.normalizedCoords = this.normalizeCoords(coords);
40
+ /** @type {Map<ItemVariationData, number[]>} */
28
41
  this.blendVectors = new Map();
29
42
  }
30
43
 
44
+ /**
45
+ * @param {number[]} coords
46
+ * @returns {number[]}
47
+ */
31
48
  normalizeCoords(coords) {
32
49
  // the default mapping is linear along each axis, in two segments:
33
50
  // from the minValue to defaultValue, and from defaultValue to maxValue.
51
+ /** @type {number[]} */
34
52
  let normalized = [];
35
- for (let i = 0; i < this.font.fvar.axis.length; i++) {
36
- let axis = this.font.fvar.axis[i];
53
+ let fvar = this.font.fvar;
54
+ if (!fvar) {
55
+ return normalized;
56
+ }
57
+
58
+ for (let i = 0; i < fvar.axis.length; i++) {
59
+ let axis = fvar.axis[i];
37
60
  if (coords[i] < axis.defaultValue) {
38
61
  normalized.push((coords[i] - axis.defaultValue + Number.EPSILON) / (axis.defaultValue - axis.minValue + Number.EPSILON));
39
62
  } else {
@@ -63,6 +86,11 @@ export default class GlyphVariationProcessor {
63
86
  return normalized;
64
87
  }
65
88
 
89
+ /**
90
+ * @param {number} gid
91
+ * @param {GlyphPoint[]} glyphPoints
92
+ * @returns {void}
93
+ */
66
94
  transformPoints(gid, glyphPoints) {
67
95
  if (!this.font.fvar || !this.font.gvar) { return; }
68
96
 
@@ -82,10 +110,12 @@ export default class GlyphVariationProcessor {
82
110
  let tupleCount = stream.readUInt16BE();
83
111
  let offsetToData = offset + stream.readUInt16BE();
84
112
 
113
+ /** @type {Uint16Array} */
114
+ let sharedPoints = new Uint16Array(0);
85
115
  if (tupleCount & TUPLES_SHARE_POINT_NUMBERS) {
86
116
  let here = stream.pos;
87
117
  stream.pos = offsetToData;
88
- var sharedPoints = this.decodePoints();
118
+ sharedPoints = this.decodePoints();
89
119
  offsetToData = stream.pos;
90
120
  stream.pos = here;
91
121
  }
@@ -97,6 +127,7 @@ export default class GlyphVariationProcessor {
97
127
  let tupleDataSize = stream.readUInt16BE();
98
128
  let tupleIndex = stream.readUInt16BE();
99
129
 
130
+ /** @type {number[]} */
100
131
  let tupleCoords;
101
132
  if (tupleIndex & EMBEDDED_TUPLE_COORD) {
102
133
  tupleCoords = [];
@@ -111,13 +142,17 @@ export default class GlyphVariationProcessor {
111
142
  tupleCoords = gvar.globalCoords[tupleIndex & TUPLE_INDEX_MASK];
112
143
  }
113
144
 
145
+ /** @type {number[] | undefined} */
146
+ let startCoords;
147
+ /** @type {number[] | undefined} */
148
+ let endCoords;
114
149
  if (tupleIndex & INTERMEDIATE_TUPLE) {
115
- var startCoords = [];
150
+ startCoords = [];
116
151
  for (let a = 0; a < gvar.axisCount; a++) {
117
152
  startCoords.push(stream.readInt16BE() / 16384);
118
153
  }
119
154
 
120
- var endCoords = [];
155
+ endCoords = [];
121
156
  for (let a = 0; a < gvar.axisCount; a++) {
122
157
  endCoords.push(stream.readInt16BE() / 16384);
123
158
  }
@@ -133,6 +168,7 @@ export default class GlyphVariationProcessor {
133
168
  let here = stream.pos;
134
169
  stream.pos = offsetToData;
135
170
 
171
+ /** @type {Uint16Array} */
136
172
  let points;
137
173
  if (tupleIndex & PRIVATE_POINT_NUMBERS) {
138
174
  points = this.decodePoints();
@@ -146,34 +182,34 @@ export default class GlyphVariationProcessor {
146
182
  let yDeltas = this.decodeDeltas(nPoints);
147
183
 
148
184
  if (points.length === 0) { // all points
149
- for (let i = 0; i < glyphPoints.length; i++) {
150
- var point = glyphPoints[i];
151
- point.x += Math.round(xDeltas[i] * factor);
152
- point.y += Math.round(yDeltas[i] * factor);
185
+ for (let pi = 0; pi < glyphPoints.length; pi++) {
186
+ let point = glyphPoints[pi];
187
+ point.x += Math.round(xDeltas[pi] * factor);
188
+ point.y += Math.round(yDeltas[pi] * factor);
153
189
  }
154
190
  } else {
155
191
  let outPoints = origPoints.map(pt => pt.copy());
156
192
  let hasDelta = glyphPoints.map(() => false);
157
193
 
158
- for (let i = 0; i < points.length; i++) {
159
- let idx = points[i];
194
+ for (let pi = 0; pi < points.length; pi++) {
195
+ let idx = points[pi];
160
196
  if (idx < glyphPoints.length) {
161
197
  let point = outPoints[idx];
162
198
  hasDelta[idx] = true;
163
199
 
164
- point.x += xDeltas[i] * factor;
165
- point.y += yDeltas[i] * factor;
200
+ point.x += xDeltas[pi] * factor;
201
+ point.y += yDeltas[pi] * factor;
166
202
  }
167
203
  }
168
204
 
169
205
  this.interpolateMissingDeltas(outPoints, origPoints, hasDelta);
170
206
 
171
- for (let i = 0; i < glyphPoints.length; i++) {
172
- let deltaX = outPoints[i].x - origPoints[i].x;
173
- let deltaY = outPoints[i].y - origPoints[i].y;
207
+ for (let pi = 0; pi < glyphPoints.length; pi++) {
208
+ let deltaX = outPoints[pi].x - origPoints[pi].x;
209
+ let deltaY = outPoints[pi].y - origPoints[pi].y;
174
210
 
175
- glyphPoints[i].x = Math.round(glyphPoints[i].x + deltaX);
176
- glyphPoints[i].y = Math.round(glyphPoints[i].y + deltaY);
211
+ glyphPoints[pi].x = Math.round(glyphPoints[pi].x + deltaX);
212
+ glyphPoints[pi].y = Math.round(glyphPoints[pi].y + deltaY);
177
213
  }
178
214
  }
179
215
 
@@ -182,6 +218,9 @@ export default class GlyphVariationProcessor {
182
218
  }
183
219
  }
184
220
 
221
+ /**
222
+ * @returns {Uint16Array}
223
+ */
185
224
  decodePoints() {
186
225
  let stream = this.font.stream;
187
226
  let count = stream.readUInt8();
@@ -196,7 +235,8 @@ export default class GlyphVariationProcessor {
196
235
  while (i < count) {
197
236
  let run = stream.readUInt8();
198
237
  let runCount = (run & POINT_RUN_COUNT_MASK) + 1;
199
- let fn = run & POINTS_ARE_WORDS ? stream.readUInt16 : stream.readUInt8;
238
+ // gvar point numbers are big-endian (OpenType); was incorrectly readUInt16.
239
+ let fn = run & POINTS_ARE_WORDS ? stream.readUInt16BE : stream.readUInt8;
200
240
 
201
241
  for (let j = 0; j < runCount && i < count; j++) {
202
242
  point += fn.call(stream);
@@ -207,6 +247,10 @@ export default class GlyphVariationProcessor {
207
247
  return points;
208
248
  }
209
249
 
250
+ /**
251
+ * @param {number} count
252
+ * @returns {Int16Array}
253
+ */
210
254
  decodeDeltas(count) {
211
255
  let stream = this.font.stream;
212
256
  let i = 0;
@@ -229,9 +273,19 @@ export default class GlyphVariationProcessor {
229
273
  return deltas;
230
274
  }
231
275
 
276
+ /**
277
+ * @param {number} tupleIndex
278
+ * @param {number[]} tupleCoords
279
+ * @param {number[] | undefined} startCoords
280
+ * @param {number[] | undefined} endCoords
281
+ * @returns {number}
282
+ */
232
283
  tupleFactor(tupleIndex, tupleCoords, startCoords, endCoords) {
233
284
  let normalized = this.normalizedCoords;
234
285
  let { gvar } = this.font;
286
+ if (!gvar) {
287
+ return 0;
288
+ }
235
289
  let factor = 1;
236
290
 
237
291
  for (let i = 0; i < gvar.axisCount; i++) {
@@ -251,6 +305,9 @@ export default class GlyphVariationProcessor {
251
305
 
252
306
  factor = (factor * normalized[i] + Number.EPSILON) / (tupleCoords[i] + Number.EPSILON);
253
307
  } else {
308
+ if (!startCoords || !endCoords) {
309
+ return 0;
310
+ }
254
311
  if ((normalized[i] < startCoords[i])
255
312
  || (normalized[i] > endCoords[i])) {
256
313
  return 0;
@@ -265,9 +322,15 @@ export default class GlyphVariationProcessor {
265
322
  return factor;
266
323
  }
267
324
 
268
- // Interpolates points without delta values.
269
- // Needed for the Ø and Q glyphs in Skia.
270
- // Algorithm from Freetype.
325
+ /**
326
+ * Interpolates points without delta values.
327
+ * Needed for the Ø and Q glyphs in Skia.
328
+ * Algorithm from Freetype.
329
+ * @param {GlyphPoint[]} points
330
+ * @param {GlyphPoint[]} inPoints
331
+ * @param {boolean[]} hasDelta
332
+ * @returns {void}
333
+ */
271
334
  interpolateMissingDeltas(points, inPoints, hasDelta) {
272
335
  if (points.length === 0) {
273
336
  return;
@@ -323,24 +386,36 @@ export default class GlyphVariationProcessor {
323
386
  }
324
387
  }
325
388
 
389
+ /**
390
+ * @param {number} p1
391
+ * @param {number} p2
392
+ * @param {number} ref1
393
+ * @param {number} ref2
394
+ * @param {GlyphPoint[]} inPoints
395
+ * @param {GlyphPoint[]} outPoints
396
+ * @returns {void}
397
+ */
326
398
  deltaInterpolate(p1, p2, ref1, ref2, inPoints, outPoints) {
327
399
  if (p1 > p2) {
328
400
  return;
329
401
  }
330
402
 
403
+ /** @type {Array<'x' | 'y'>} */
331
404
  let iterable = ['x', 'y'];
332
405
  for (let i = 0; i < iterable.length; i++) {
333
406
  let k = iterable[i];
334
- if (inPoints[ref1][k] > inPoints[ref2][k]) {
335
- var p = ref1;
336
- ref1 = ref2;
337
- ref2 = p;
407
+ let r1 = ref1;
408
+ let r2 = ref2;
409
+ if (inPoints[r1][k] > inPoints[r2][k]) {
410
+ let p = r1;
411
+ r1 = r2;
412
+ r2 = p;
338
413
  }
339
414
 
340
- let in1 = inPoints[ref1][k];
341
- let in2 = inPoints[ref2][k];
342
- let out1 = outPoints[ref1][k];
343
- let out2 = outPoints[ref2][k];
415
+ let in1 = inPoints[r1][k];
416
+ let in2 = inPoints[r2][k];
417
+ let out1 = outPoints[r1][k];
418
+ let out2 = outPoints[r2][k];
344
419
 
345
420
  // If the reference points have the same coordinate but different
346
421
  // delta, inferred delta is zero. Otherwise interpolate.
@@ -364,6 +439,14 @@ export default class GlyphVariationProcessor {
364
439
  }
365
440
  }
366
441
 
442
+ /**
443
+ * @param {number} p1
444
+ * @param {number} p2
445
+ * @param {number} ref
446
+ * @param {GlyphPoint[]} inPoints
447
+ * @param {GlyphPoint[]} outPoints
448
+ * @returns {void}
449
+ */
367
450
  deltaShift(p1, p2, ref, inPoints, outPoints) {
368
451
  let deltaX = outPoints[ref].x - inPoints[ref].x;
369
452
  let deltaY = outPoints[ref].y - inPoints[ref].y;
@@ -380,6 +463,11 @@ export default class GlyphVariationProcessor {
380
463
  }
381
464
  }
382
465
 
466
+ /**
467
+ * @param {number} gid
468
+ * @param {HVARTable} table
469
+ * @returns {number}
470
+ */
383
471
  getAdvanceAdjustment(gid, table) {
384
472
  let outerIndex, innerIndex;
385
473
 
@@ -398,8 +486,14 @@ export default class GlyphVariationProcessor {
398
486
  return this.getDelta(table.itemVariationStore, outerIndex, innerIndex);
399
487
  }
400
488
 
401
- // See pseudo code from `Font Variations Overview'
402
- // in the OpenType specification.
489
+ /**
490
+ * See pseudo code from `Font Variations Overview'
491
+ * in the OpenType specification.
492
+ * @param {ItemVariationStore} itemStore
493
+ * @param {number} outerIndex
494
+ * @param {number} innerIndex
495
+ * @returns {number}
496
+ */
403
497
  getDelta(itemStore, outerIndex, innerIndex) {
404
498
  if (outerIndex >= itemStore.itemVariationData.length) {
405
499
  return 0;
@@ -421,13 +515,20 @@ export default class GlyphVariationProcessor {
421
515
  return netAdjustment;
422
516
  }
423
517
 
518
+ /**
519
+ * @param {ItemVariationStore} itemStore
520
+ * @param {number} outerIndex
521
+ * @returns {number[]}
522
+ */
424
523
  getBlendVector(itemStore, outerIndex) {
425
524
  let varData = itemStore.itemVariationData[outerIndex];
426
- if (this.blendVectors.has(varData)) {
427
- return this.blendVectors.get(varData);
525
+ let cached = this.blendVectors.get(varData);
526
+ if (cached) {
527
+ return cached;
428
528
  }
429
529
 
430
530
  let normalizedCoords = this.normalizedCoords;
531
+ /** @type {number[]} */
431
532
  let blendVector = [];
432
533
 
433
534
  // outer loop steps through master designs to be blended