@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
@@ -3,46 +3,105 @@ import Path from './Path';
3
3
  import BBox from './BBox';
4
4
  import * as r from 'restructure';
5
5
 
6
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
7
+ /** @typedef {import('restructure').StructValue} StructValue */
8
+ /** @typedef {import('../../types/fontkit').DecodedGlyf} DecodedGlyf */
9
+ /** @typedef {import('../../types/fontkit').GlyphComponent} GlyphComponent */
10
+ /** @typedef {import('../../types/fontkit').GlyphMetrics} GlyphMetrics */
11
+ /** @typedef {import('../../types/fontkit').GlyphPoint} GlyphPoint */
12
+
6
13
  // The header for both simple and composite glyphs
7
14
  let GlyfHeader = new r.Struct({
8
15
  numberOfContours: r.int16, // if negative, this is a composite glyph
9
- xMin: r.int16,
10
- yMin: r.int16,
11
- xMax: r.int16,
12
- yMax: r.int16
16
+ xMin: r.int16,
17
+ yMin: r.int16,
18
+ xMax: r.int16,
19
+ yMax: r.int16
13
20
  });
14
21
 
15
22
  // Flags for simple glyphs
16
- const ON_CURVE = 1 << 0;
17
- const X_SHORT_VECTOR = 1 << 1;
18
- const Y_SHORT_VECTOR = 1 << 2;
19
- const REPEAT = 1 << 3;
20
- const SAME_X = 1 << 4;
21
- const SAME_Y = 1 << 5;
23
+ const ON_CURVE = 1 << 0;
24
+ const X_SHORT_VECTOR = 1 << 1;
25
+ const Y_SHORT_VECTOR = 1 << 2;
26
+ const REPEAT = 1 << 3;
27
+ const SAME_X = 1 << 4;
28
+ const SAME_Y = 1 << 5;
22
29
 
23
30
  // Flags for composite glyphs
24
- const ARG_1_AND_2_ARE_WORDS = 1 << 0;
25
- const _ARGS_ARE_XY_VALUES = 1 << 1;
26
- const _ROUND_XY_TO_GRID = 1 << 2;
27
- const WE_HAVE_A_SCALE = 1 << 3;
28
- const MORE_COMPONENTS = 1 << 5;
29
- const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
30
- const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
31
- const WE_HAVE_INSTRUCTIONS = 1 << 8;
32
- const _USE_MY_METRICS = 1 << 9;
33
- const _OVERLAP_COMPOUND = 1 << 10;
34
- const _SCALED_COMPONENT_OFFSET = 1 << 11;
31
+ const ARG_1_AND_2_ARE_WORDS = 1 << 0;
32
+ const _ARGS_ARE_XY_VALUES = 1 << 1;
33
+ const _ROUND_XY_TO_GRID = 1 << 2;
34
+ const WE_HAVE_A_SCALE = 1 << 3;
35
+ const MORE_COMPONENTS = 1 << 5;
36
+ const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
37
+ const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
38
+ const WE_HAVE_INSTRUCTIONS = 1 << 8;
39
+ const _USE_MY_METRICS = 1 << 9;
40
+ const _OVERLAP_COMPOUND = 1 << 10;
41
+ const _SCALED_COMPONENT_OFFSET = 1 << 11;
35
42
  const _UNSCALED_COMPONENT_OFFSET = 1 << 12;
36
43
 
44
+ /**
45
+ * @param {StructValue} raw
46
+ * @returns {DecodedGlyf}
47
+ */
48
+ function decodeGlyfHeader(raw) {
49
+ let numberOfContours = raw.numberOfContours;
50
+ let xMin = raw.xMin;
51
+ let yMin = raw.yMin;
52
+ let xMax = raw.xMax;
53
+ let yMax = raw.yMax;
54
+ if (
55
+ typeof numberOfContours !== 'number'
56
+ || typeof xMin !== 'number'
57
+ || typeof yMin !== 'number'
58
+ || typeof xMax !== 'number'
59
+ || typeof yMax !== 'number'
60
+ ) {
61
+ throw new Error('Invalid glyf header');
62
+ }
63
+ return { numberOfContours, xMin, yMin, xMax, yMax };
64
+ }
65
+
66
+ /**
67
+ * @param {unknown[]} values
68
+ * @returns {number[]}
69
+ */
70
+ function asNumberArray(values) {
71
+ /** @type {number[]} */
72
+ let out = [];
73
+ for (let i = 0; i < values.length; i++) {
74
+ let v = values[i];
75
+ if (typeof v !== 'number') {
76
+ throw new Error('Expected number array from glyf decode');
77
+ }
78
+ out.push(v);
79
+ }
80
+ return out;
81
+ }
82
+
37
83
  // Represents a point in a simple glyph
38
84
  export class Point {
85
+ /**
86
+ * @param {boolean} onCurve
87
+ * @param {boolean} endContour
88
+ * @param {number} [x]
89
+ * @param {number} [y]
90
+ */
39
91
  constructor(onCurve, endContour, x = 0, y = 0) {
92
+ /** @type {boolean} */
40
93
  this.onCurve = onCurve;
94
+ /** @type {boolean} */
41
95
  this.endContour = endContour;
96
+ /** @type {number} */
42
97
  this.x = x;
98
+ /** @type {number} */
43
99
  this.y = y;
44
100
  }
45
101
 
102
+ /**
103
+ * @returns {Point}
104
+ */
46
105
  copy() {
47
106
  return new Point(this.onCurve, this.endContour, this.x, this.y);
48
107
  }
@@ -50,13 +109,28 @@ export class Point {
50
109
 
51
110
  // Represents a component in a composite glyph
52
111
  class Component {
112
+ /**
113
+ * @param {number} glyphID
114
+ * @param {number} dx
115
+ * @param {number} dy
116
+ */
53
117
  constructor(glyphID, dx, dy) {
118
+ /** @type {number} */
54
119
  this.glyphID = glyphID;
120
+ /** @type {number} */
55
121
  this.dx = dx;
122
+ /** @type {number} */
56
123
  this.dy = dy;
124
+ /** @type {number} */
57
125
  this.pos = 0;
58
- this.scaleX = this.scaleY = 1;
59
- this.scale01 = this.scale10 = 0;
126
+ /** @type {number} */
127
+ this.scaleX = 1;
128
+ /** @type {number} */
129
+ this.scaleY = 1;
130
+ /** @type {number} */
131
+ this.scale01 = 0;
132
+ /** @type {number} */
133
+ this.scale10 = 0;
60
134
  }
61
135
  }
62
136
 
@@ -66,7 +140,11 @@ class Component {
66
140
  export default class TTFGlyph extends Glyph {
67
141
  type = 'TTF';
68
142
 
69
- // Parses just the glyph header and returns the bounding box
143
+ /**
144
+ * Parses just the glyph header and returns the bounding box
145
+ * @param {boolean} [internal]
146
+ * @returns {BBox}
147
+ */
70
148
  _getCBox(internal) {
71
149
  // We need to decode the glyph if variation processing is requested,
72
150
  // so it's easier just to recompute the path's cbox after decoding.
@@ -74,19 +152,34 @@ export default class TTFGlyph extends Glyph {
74
152
  return this.path.cbox;
75
153
  }
76
154
 
77
- let glyfPos = this._font.loca.offsets[this.id];
155
+ let loca = this._font.loca;
156
+ if (!loca) {
157
+ return Object.freeze(new BBox(0, 0, 0, 0));
158
+ }
159
+
160
+ let glyfPos = loca.offsets[this.id];
78
161
  // No outline data (e.g. space): avoid decoding the next glyph's header
79
- if (glyfPos === this._font.loca.offsets[this.id + 1]) {
162
+ if (glyfPos === loca.offsets[this.id + 1]) {
80
163
  return Object.freeze(new BBox(0, 0, 0, 0));
81
164
  }
82
165
 
83
166
  let stream = this._font._getTableStream('glyf');
167
+ if (!stream) {
168
+ return Object.freeze(new BBox(0, 0, 0, 0));
169
+ }
84
170
  stream.pos += glyfPos;
85
- let glyph = GlyfHeader.decode(stream);
171
+ let glyph = decodeGlyfHeader(GlyfHeader.decode(stream));
86
172
  return Object.freeze(new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax));
87
173
  }
88
174
 
89
- // Parses a single glyph coordinate
175
+ /**
176
+ * Parses a single glyph coordinate
177
+ * @param {DecodeStream} stream
178
+ * @param {number} prev
179
+ * @param {number} short
180
+ * @param {number} same
181
+ * @returns {number}
182
+ */
90
183
  _parseGlyphCoord(stream, prev, short, same) {
91
184
  let val;
92
185
  if (short) {
@@ -107,24 +200,34 @@ export default class TTFGlyph extends Glyph {
107
200
  return val;
108
201
  }
109
202
 
110
- // Decodes the glyph data into points for simple glyphs,
111
- // or components for composite glyphs
203
+ /**
204
+ * Decodes the glyph data into points for simple glyphs,
205
+ * or components for composite glyphs
206
+ * @returns {DecodedGlyf | null}
207
+ */
112
208
  _decode() {
113
- let glyfPos = this._font.loca.offsets[this.id];
114
- let nextPos = this._font.loca.offsets[this.id + 1];
209
+ let loca = this._font.loca;
210
+ if (!loca) {
211
+ return null;
212
+ }
213
+
214
+ let glyfPos = loca.offsets[this.id];
215
+ let nextPos = loca.offsets[this.id + 1];
115
216
 
116
217
  // Nothing to do if there is no data for this glyph
117
218
  if (glyfPos === nextPos) { return null; }
118
219
 
119
220
  let stream = this._font._getTableStream('glyf');
221
+ if (!stream) {
222
+ return null;
223
+ }
120
224
  stream.pos += glyfPos;
121
225
  let startPos = stream.pos;
122
226
 
123
- let glyph = GlyfHeader.decode(stream);
227
+ let glyph = decodeGlyfHeader(GlyfHeader.decode(stream));
124
228
 
125
229
  if (glyph.numberOfContours > 0) {
126
230
  this._decodeSimple(glyph, stream);
127
-
128
231
  } else if (glyph.numberOfContours < 0) {
129
232
  this._decodeComposite(glyph, stream, startPos);
130
233
  }
@@ -132,13 +235,19 @@ export default class TTFGlyph extends Glyph {
132
235
  return glyph;
133
236
  }
134
237
 
238
+ /**
239
+ * @param {DecodedGlyf} glyph
240
+ * @param {DecodeStream} stream
241
+ * @returns {void}
242
+ */
135
243
  _decodeSimple(glyph, stream) {
136
244
  // this is a simple glyph
137
245
  glyph.points = [];
138
246
 
139
- let endPtsOfContours = new r.Array(r.uint16, glyph.numberOfContours).decode(stream);
140
- glyph.instructions = new r.Array(r.uint8, r.uint16).decode(stream);
247
+ let endPtsOfContours = asNumberArray(new r.Array(r.uint16, glyph.numberOfContours).decode(stream));
248
+ glyph.instructions = asNumberArray(new r.Array(r.uint8, r.uint16).decode(stream));
141
249
 
250
+ /** @type {number[]} */
142
251
  let flags = [];
143
252
  let numCoords = endPtsOfContours[endPtsOfContours.length - 1] + 1;
144
253
 
@@ -164,13 +273,15 @@ export default class TTFGlyph extends Glyph {
164
273
  let px = 0;
165
274
  for (let i = 0; i < flags.length; i++) {
166
275
  let flag = flags[i];
167
- glyph.points[i].x = px = this._parseGlyphCoord(stream, px, flag & X_SHORT_VECTOR, flag & SAME_X);
276
+ let point = glyph.points[i];
277
+ point.x = px = this._parseGlyphCoord(stream, px, flag & X_SHORT_VECTOR, flag & SAME_X);
168
278
  }
169
279
 
170
280
  let py = 0;
171
281
  for (let i = 0; i < flags.length; i++) {
172
282
  let flag = flags[i];
173
- glyph.points[i].y = py = this._parseGlyphCoord(stream, py, flag & Y_SHORT_VECTOR, flag & SAME_Y);
283
+ let point = glyph.points[i];
284
+ point.y = py = this._parseGlyphCoord(stream, py, flag & Y_SHORT_VECTOR, flag & SAME_Y);
174
285
  }
175
286
 
176
287
  if (this._font._variationProcessor) {
@@ -184,6 +295,12 @@ export default class TTFGlyph extends Glyph {
184
295
  return;
185
296
  }
186
297
 
298
+ /**
299
+ * @param {DecodedGlyf} glyph
300
+ * @param {DecodeStream} stream
301
+ * @param {number} [offset]
302
+ * @returns {boolean}
303
+ */
187
304
  _decodeComposite(glyph, stream, offset = 0) {
188
305
  // this is a composite glyph
189
306
  glyph.components = [];
@@ -212,24 +329,23 @@ export default class TTFGlyph extends Glyph {
212
329
 
213
330
  if (flags & WE_HAVE_A_SCALE) {
214
331
  // fixed number with 14 bits of fraction
215
- component.scaleX =
216
- component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
217
-
332
+ component.scaleX
333
+ = component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
218
334
  } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
219
335
  component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
220
336
  component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
221
-
222
337
  } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
223
- component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
338
+ component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
224
339
  component.scale01 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
225
340
  component.scale10 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
226
- component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
341
+ component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
227
342
  }
228
343
 
229
344
  glyph.components.push(component);
230
345
  }
231
346
 
232
347
  if (this._font._variationProcessor) {
348
+ /** @type {Point[]} */
233
349
  let points = [];
234
350
  for (let j = 0; j < glyph.components.length; j++) {
235
351
  let component = glyph.components[j];
@@ -251,6 +367,10 @@ export default class TTFGlyph extends Glyph {
251
367
  return haveInstructions;
252
368
  }
253
369
 
370
+ /**
371
+ * @param {DecodedGlyf} glyph
372
+ * @returns {Point[]}
373
+ */
254
374
  _getPhantomPoints(glyph) {
255
375
  let cbox = this._getCBox(true);
256
376
  if (this._metrics == null) {
@@ -267,20 +387,25 @@ export default class TTFGlyph extends Glyph {
267
387
  ];
268
388
  }
269
389
 
270
- // Decodes font data, resolves composite glyphs, and returns an array of contours
390
+ /**
391
+ * Decodes font data, resolves composite glyphs, and returns an array of contours
392
+ * @returns {Point[][]}
393
+ */
271
394
  _getContours() {
272
395
  let glyph = this._decode();
273
396
  if (!glyph) {
274
397
  return [];
275
398
  }
276
399
 
400
+ /** @type {Point[]} */
277
401
  let points = [];
278
402
 
279
403
  if (glyph.numberOfContours < 0) {
280
404
  // resolve composite glyphs
281
- for (let component of glyph.components) {
405
+ let components = glyph.components || [];
406
+ for (let component of components) {
282
407
  let componentGlyph = this._font._getBaseGlyph(component.glyphID);
283
- if (!componentGlyph) {
408
+ if (!componentGlyph || typeof componentGlyph._getContours !== 'function') {
284
409
  continue;
285
410
  }
286
411
 
@@ -302,18 +427,22 @@ export default class TTFGlyph extends Glyph {
302
427
  // Recompute and cache metrics if we performed variation processing, and don't have an HVAR table
303
428
  if (glyph.phantomPoints && !this._font.directory.tables.HVAR) {
304
429
  let m = this._metrics;
305
- m.advanceWidth = glyph.phantomPoints[1].x - glyph.phantomPoints[0].x;
306
- m.advanceHeight = glyph.phantomPoints[3].y - glyph.phantomPoints[2].y;
307
- m.leftBearing = glyph.xMin - glyph.phantomPoints[0].x;
308
- m.topBearing = glyph.phantomPoints[2].y - glyph.yMax;
309
- m.rightBearing = m.advanceWidth - m.leftBearing - m.width;
310
- m.bottomBearing = m.advanceHeight - m.topBearing - m.height;
430
+ if (m) {
431
+ m.advanceWidth = glyph.phantomPoints[1].x - glyph.phantomPoints[0].x;
432
+ m.advanceHeight = glyph.phantomPoints[3].y - glyph.phantomPoints[2].y;
433
+ m.leftBearing = glyph.xMin - glyph.phantomPoints[0].x;
434
+ m.topBearing = glyph.phantomPoints[2].y - glyph.yMax;
435
+ m.rightBearing = m.advanceWidth - m.leftBearing - m.width;
436
+ m.bottomBearing = m.advanceHeight - m.topBearing - m.height;
437
+ }
311
438
  }
312
439
 
440
+ /** @type {Point[][]} */
313
441
  let contours = [];
442
+ /** @type {Point[]} */
314
443
  let cur = [];
315
444
  for (let k = 0; k < points.length; k++) {
316
- var point = points[k];
445
+ let point = points[k];
317
446
  cur.push(point);
318
447
  if (point.endContour) {
319
448
  contours.push(cur);
@@ -324,6 +453,9 @@ export default class TTFGlyph extends Glyph {
324
453
  return contours;
325
454
  }
326
455
 
456
+ /**
457
+ * @returns {GlyphMetrics}
458
+ */
327
459
  _getMetrics() {
328
460
  if (this._metrics) {
329
461
  return this._metrics;
@@ -337,20 +469,29 @@ export default class TTFGlyph extends Glyph {
337
469
  this.path;
338
470
  }
339
471
 
340
- return this._metrics;
472
+ // super._getMetrics always assigns this._metrics
473
+ let metrics = this._metrics;
474
+ if (!metrics) {
475
+ throw new Error('Failed to compute glyph metrics');
476
+ }
477
+ return metrics;
341
478
  }
342
479
 
343
- // Converts contours to a Path object that can be rendered
480
+ /**
481
+ * Converts contours to a Path object that can be rendered
482
+ * @returns {Path}
483
+ */
344
484
  _getPath() {
345
485
  let contours = this._getContours();
346
- let path = new Path;
486
+ let path = new Path();
347
487
 
348
488
  for (let i = 0; i < contours.length; i++) {
349
489
  let contour = contours[i];
350
490
  let firstPt = contour[0];
351
491
  let lastPt = contour[contour.length - 1];
352
492
  let start = 0;
353
- let curvePt;
493
+ /** @type {Point | null} */
494
+ let curvePt = null;
354
495
 
355
496
  if (firstPt.onCurve) {
356
497
  // The first point will be consumed by the moveTo command, so skip in the loop
@@ -376,22 +517,21 @@ export default class TTFGlyph extends Glyph {
376
517
 
377
518
  if (prevPt.onCurve && pt.onCurve) {
378
519
  path.lineTo(pt.x, pt.y);
379
-
380
520
  } else if (prevPt.onCurve && !pt.onCurve) {
381
521
  curvePt = pt;
382
-
383
522
  } else if (!prevPt.onCurve && !pt.onCurve) {
384
523
  let midX = (prevPt.x + pt.x) / 2;
385
524
  let midY = (prevPt.y + pt.y) / 2;
386
525
  path.quadraticCurveTo(prevPt.x, prevPt.y, midX, midY);
387
526
  curvePt = pt;
388
-
389
527
  } else if (!prevPt.onCurve && pt.onCurve) {
528
+ if (!curvePt) {
529
+ throw new Error('Unknown TTF path state');
530
+ }
390
531
  path.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y);
391
532
  curvePt = null;
392
-
393
533
  } else {
394
- throw new Error("Unknown TTF path state");
534
+ throw new Error('Unknown TTF path state');
395
535
  }
396
536
  }
397
537
 
@@ -1,18 +1,31 @@
1
1
  import * as r from 'restructure';
2
2
 
3
+ /** @typedef {import('restructure').EncodeStream} EncodeStream */
4
+ /** @typedef {import('restructure').StructValue} StructValue */
5
+ /** @typedef {import('./Path').default} Path */
6
+
3
7
  // Flags for simple glyphs
4
- const ON_CURVE = 1 << 0;
5
- const X_SHORT_VECTOR = 1 << 1;
6
- const Y_SHORT_VECTOR = 1 << 2;
7
- const REPEAT = 1 << 3;
8
- const SAME_X = 1 << 4;
9
- const SAME_Y = 1 << 5;
8
+ const ON_CURVE = 1 << 0;
9
+ const X_SHORT_VECTOR = 1 << 1;
10
+ const Y_SHORT_VECTOR = 1 << 2;
11
+ const REPEAT = 1 << 3;
12
+ const SAME_X = 1 << 4;
13
+ const SAME_Y = 1 << 5;
10
14
 
11
15
  class Point {
16
+ /**
17
+ * @param {number} val
18
+ * @returns {number}
19
+ */
12
20
  static size(val) {
13
21
  return val >= 0 && val <= 255 ? 1 : 2;
14
22
  }
15
-
23
+
24
+ /**
25
+ * @param {EncodeStream} stream
26
+ * @param {number} value
27
+ * @returns {void}
28
+ */
16
29
  static encode(stream, value) {
17
30
  if (value >= 0 && value <= 255) {
18
31
  stream.writeUInt8(value);
@@ -39,23 +52,32 @@ let Glyf = new r.Struct({
39
52
  * Encodes TrueType glyph outlines
40
53
  */
41
54
  export default class TTFGlyphEncoder {
55
+ /**
56
+ * @param {Path} path
57
+ * @param {number[]} [instructions]
58
+ * @returns {import('restructure').BinaryBuffer}
59
+ */
42
60
  encodeSimple(path, instructions = []) {
61
+ /** @type {number[]} */
43
62
  let endPtsOfContours = [];
63
+ /** @type {number[]} */
44
64
  let xPoints = [];
65
+ /** @type {number[]} */
45
66
  let yPoints = [];
67
+ /** @type {number[]} */
46
68
  let flags = [];
47
69
  let same = 0;
48
70
  let lastX = 0, lastY = 0, lastFlag = 0;
49
71
  let pointCount = 0;
50
-
72
+
51
73
  for (let i = 0; i < path.commands.length; i++) {
52
74
  let c = path.commands[i];
53
-
75
+
54
76
  for (let j = 0; j < c.args.length; j += 2) {
55
77
  let x = c.args[j];
56
78
  let y = c.args[j + 1];
57
79
  let flag = 0;
58
-
80
+
59
81
  // If the ending point of a quadratic curve is the midpoint
60
82
  // between the control point and the control point of the next
61
83
  // quadratic curve, we can omit the ending point.
@@ -64,21 +86,21 @@ export default class TTFGlyphEncoder {
64
86
  if (next && next.command === 'quadraticCurveTo') {
65
87
  let midX = (lastX + next.args[0]) / 2;
66
88
  let midY = (lastY + next.args[1]) / 2;
67
-
89
+
68
90
  if (x === midX && y === midY) {
69
91
  continue;
70
92
  }
71
93
  }
72
94
  }
73
-
95
+
74
96
  // All points except control points are on curve.
75
97
  if (!(c.command === 'quadraticCurveTo' && j === 0)) {
76
98
  flag |= ON_CURVE;
77
99
  }
78
-
100
+
79
101
  flag = this._encodePoint(x, lastX, xPoints, flag, X_SHORT_VECTOR, SAME_X);
80
102
  flag = this._encodePoint(y, lastY, yPoints, flag, Y_SHORT_VECTOR, SAME_Y);
81
-
103
+
82
104
  if (flag === lastFlag && same < 255) {
83
105
  flags[flags.length - 1] |= REPEAT;
84
106
  same++;
@@ -87,16 +109,16 @@ export default class TTFGlyphEncoder {
87
109
  flags.push(same);
88
110
  same = 0;
89
111
  }
90
-
112
+
91
113
  flags.push(flag);
92
114
  lastFlag = flag;
93
115
  }
94
-
116
+
95
117
  lastX = x;
96
118
  lastY = y;
97
119
  pointCount++;
98
120
  }
99
-
121
+
100
122
  if (c.command === 'closePath') {
101
123
  endPtsOfContours.push(pointCount - 1);
102
124
  }
@@ -106,8 +128,9 @@ export default class TTFGlyphEncoder {
106
128
  if (path.commands.length > 1 && path.commands[path.commands.length - 1].command !== 'closePath') {
107
129
  endPtsOfContours.push(pointCount - 1);
108
130
  }
109
-
131
+
110
132
  let bbox = path.bbox;
133
+ /** @type {StructValue} */
111
134
  let glyf = {
112
135
  numberOfContours: endPtsOfContours.length,
113
136
  xMin: bbox.minX,
@@ -120,24 +143,33 @@ export default class TTFGlyphEncoder {
120
143
  xPoints: xPoints,
121
144
  yPoints: yPoints
122
145
  };
123
-
146
+
124
147
  let size = Glyf.size(glyf);
125
148
  let tail = 4 - (size % 4);
126
-
149
+
127
150
  let stream = new r.EncodeStream(size + tail);
128
151
  Glyf.encode(stream, glyf);
129
-
152
+
130
153
  // Align to 4-byte length
131
154
  if (tail !== 0) {
132
155
  stream.fill(0, tail);
133
156
  }
134
-
157
+
135
158
  return stream.buffer;
136
159
  }
137
-
160
+
161
+ /**
162
+ * @param {number} value
163
+ * @param {number} last
164
+ * @param {number[]} points
165
+ * @param {number} flag
166
+ * @param {number} shortFlag
167
+ * @param {number} sameFlag
168
+ * @returns {number}
169
+ */
138
170
  _encodePoint(value, last, points, flag, shortFlag, sameFlag) {
139
171
  let diff = value - last;
140
-
172
+
141
173
  if (value === last) {
142
174
  flag |= sameFlag;
143
175
  } else {
@@ -149,10 +181,10 @@ export default class TTFGlyphEncoder {
149
181
  flag |= sameFlag;
150
182
  }
151
183
  }
152
-
184
+
153
185
  points.push(diff);
154
186
  }
155
-
187
+
156
188
  return flag;
157
189
  }
158
190
  }