@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
@@ -2,6 +2,26 @@ import * as r from 'restructure';
2
2
  import TTFFont from './TTFFont';
3
3
  import { asciiDecoder } from './utils';
4
4
 
5
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
6
+ /** @typedef {import('restructure').StructValue} StructValue */
7
+ /** @typedef {import('restructure').BinaryBuffer} BinaryBuffer */
8
+ /** @typedef {import('../types/fontkit').NameString} NameString */
9
+
10
+ /**
11
+ * @param {NameString | null | undefined} a
12
+ * @param {string | Uint8Array} b
13
+ * @returns {boolean}
14
+ */
15
+ function postscriptNamesEqual(a, b) {
16
+ if (a === b) {
17
+ return true;
18
+ }
19
+ if (a instanceof Uint8Array && b instanceof Uint8Array) {
20
+ return a.length === b.length && a.every((v, i) => b[i] === v);
21
+ }
22
+ return false;
23
+ }
24
+
5
25
  let TTCHeader = new r.VersionedStruct(r.uint32, {
6
26
  0x00010000: {
7
27
  numFonts: r.uint32,
@@ -16,35 +36,55 @@ let TTCHeader = new r.VersionedStruct(r.uint32, {
16
36
  }
17
37
  });
18
38
 
39
+ /**
40
+ * @typedef {StructValue & { offsets: number[] }} TTCHeaderValue
41
+ */
42
+
19
43
  export default class TrueTypeCollection {
44
+ /** @type {string} */
20
45
  type = 'TTC';
21
46
 
47
+ /** @type {DecodeStream} */
48
+ stream;
49
+ /** @type {TTCHeaderValue} */
50
+ header;
51
+
52
+ /**
53
+ * @param {ArrayBufferView} buffer
54
+ * @returns {boolean}
55
+ */
22
56
  static probe(buffer) {
23
- return asciiDecoder.decode(buffer.slice(0, 4)) === 'ttcf';
57
+ let bytes
58
+ = buffer instanceof Uint8Array
59
+ ? buffer
60
+ : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
61
+ return asciiDecoder.decode(bytes.subarray(0, 4)) === 'ttcf';
24
62
  }
25
63
 
64
+ /**
65
+ * @param {DecodeStream} stream
66
+ */
26
67
  constructor(stream) {
27
68
  this.stream = stream;
28
69
  if (stream.readString(4) !== 'ttcf') {
29
70
  throw new Error('Not a TrueType collection');
30
71
  }
31
72
 
32
- this.header = TTCHeader.decode(stream);
73
+ this.header = /** @type {TTCHeaderValue} */ (
74
+ /** @type {unknown} */ (TTCHeader.decode(stream))
75
+ );
33
76
  }
34
77
 
78
+ /**
79
+ * @param {string | Uint8Array} name
80
+ * @returns {TTFFont | null}
81
+ */
35
82
  getFont(name) {
36
83
  for (let offset of this.header.offsets) {
37
84
  let stream = new r.DecodeStream(this.stream.buffer);
38
85
  stream.pos = offset;
39
86
  let font = new TTFFont(stream);
40
- if (
41
- font.postscriptName === name
42
- || (
43
- font.postscriptName instanceof Uint8Array
44
- && name instanceof Uint8Array
45
- && font.postscriptName.every((v, i) => name[i] === v)
46
- )
47
- ) {
87
+ if (postscriptNamesEqual(font.postscriptName, name)) {
48
88
  return font;
49
89
  }
50
90
  }
@@ -52,7 +92,11 @@ export default class TrueTypeCollection {
52
92
  return null;
53
93
  }
54
94
 
95
+ /**
96
+ * @type {TTFFont[]}
97
+ */
55
98
  get fonts() {
99
+ /** @type {TTFFont[]} */
56
100
  let fonts = [];
57
101
  for (let offset of this.header.offsets) {
58
102
  let stream = new r.DecodeStream(this.stream.buffer);
package/src/WOFF2Font.js CHANGED
@@ -6,45 +6,98 @@ import WOFF2Glyph from './glyph/WOFF2Glyph';
6
6
  import WOFF2Directory from './tables/WOFF2Directory';
7
7
  import { asciiDecoder } from './utils';
8
8
 
9
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
10
+ /** @typedef {import('restructure').StructValue} StructValue */
11
+ /** @typedef {import('restructure').BinaryBuffer} BinaryBuffer */
12
+ /** @typedef {import('restructure').BaseType} BaseType */
13
+ /** @typedef {import('../types/fontkit').FontDirectory} FontDirectory */
14
+ /** @typedef {import('../types/fontkit').TableEntry} TableEntry */
15
+ /** @typedef {import('../types/fontkit').TransformedGlyf} TransformedGlyf */
16
+ /** @typedef {import('../types/fontkit').DecodedGlyf} DecodedGlyf */
17
+ /** @typedef {import('./glyph/Glyph').default} Glyph */
18
+
9
19
  /**
10
20
  * Subclass of TTFFont that represents a TTF/OTF font compressed by WOFF2
11
21
  * See spec here: http://www.w3.org/TR/WOFF2/
22
+ *
23
+ * @property {number} [_dataPos] Byte offset of the compressed table data (set in `_decodeDirectory`).
12
24
  */
13
25
  export default class WOFF2Font extends TTFFont {
26
+ /** @type {string} */
14
27
  type = 'WOFF2';
15
28
 
29
+ // NOTE: Do not declare `_dataPos` as a class field. It is assigned in
30
+ // `_decodeDirectory()` during `super()`, and subclass field initializers
31
+ // run *after* `super()` returns — which would wipe the real offset.
32
+
33
+ /** @type {boolean | undefined} */
34
+ _decompressed;
35
+ /** @type {(TransformedGlyf | undefined)[] | undefined} */
36
+ _transformedGlyphs;
37
+
38
+ /**
39
+ * @param {ArrayBufferView} buffer
40
+ * @returns {boolean}
41
+ */
16
42
  static probe(buffer) {
17
- return asciiDecoder.decode(buffer.slice(0, 4)) === 'wOF2';
43
+ let bytes
44
+ = buffer instanceof Uint8Array
45
+ ? buffer
46
+ : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
47
+ return asciiDecoder.decode(bytes.subarray(0, 4)) === 'wOF2';
18
48
  }
19
49
 
50
+ /** @returns {void} */
20
51
  _decodeDirectory() {
21
- this.directory = WOFF2Directory.decode(this.stream);
52
+ this.directory = /** @type {FontDirectory} */ (
53
+ /** @type {unknown} */ (WOFF2Directory.decode(this.stream))
54
+ );
22
55
  this._dataPos = this.stream.pos;
23
56
  }
24
57
 
58
+ /** @returns {void} */
25
59
  _decompress() {
26
60
  // decompress data and setup table offsets if we haven't already
27
61
  if (!this._decompressed) {
28
- this.stream.pos = this._dataPos;
29
- let buffer = this.stream.readBuffer(this.directory.totalCompressedSize);
62
+ let dataPos = this._dataPos;
63
+ if (dataPos == null) {
64
+ throw new Error('Missing WOFF2 data offset; directory was not decoded');
65
+ }
66
+ this.stream.pos = dataPos;
67
+ let totalCompressedSize = this.directory.totalCompressedSize;
68
+ if (totalCompressedSize == null) {
69
+ throw new Error('Missing totalCompressedSize in WOFF2 directory');
70
+ }
71
+ let buffer = this.stream.readBuffer(totalCompressedSize);
30
72
 
31
73
  let decompressedSize = 0;
32
- for (let tag in this.directory.tables) {
33
- let entry = this.directory.tables[tag];
74
+ let tableMap = this.directory.tables;
75
+ for (let tag in tableMap) {
76
+ let entry = tableMap[tag];
77
+ if (!entry) {
78
+ continue;
79
+ }
34
80
  entry.offset = decompressedSize;
35
81
  decompressedSize += (entry.transformLength != null) ? entry.transformLength : entry.length;
36
82
  }
37
83
 
38
- let decompressed = brotli(buffer, decompressedSize);
84
+ let decompressed = brotli(
85
+ buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength),
86
+ decompressedSize
87
+ );
39
88
  if (!decompressed) {
40
89
  throw new Error('Error decoding compressed data in WOFF2');
41
90
  }
42
91
 
43
- this.stream = new r.DecodeStream(decompressed);
92
+ this._setStream(new r.DecodeStream(decompressed));
44
93
  this._decompressed = true;
45
94
  }
46
95
  }
47
96
 
97
+ /**
98
+ * @param {TableEntry} table
99
+ * @returns {unknown}
100
+ */
48
101
  _decodeTable(table) {
49
102
  this._decompress();
50
103
  return super._decodeTable(table);
@@ -52,13 +105,19 @@ export default class WOFF2Font extends TTFFont {
52
105
 
53
106
  // Override this method to get a glyph and return our
54
107
  // custom subclass if there is a glyf table.
108
+ /**
109
+ * @param {number} glyph
110
+ * @param {number[]} [characters]
111
+ * @returns {Glyph | null | undefined}
112
+ */
55
113
  _getBaseGlyph(glyph, characters = []) {
56
114
  let cached = this._glyphs[glyph];
57
- if (cached?._getContours) {
115
+ if (cached && typeof (/** @type {{ _getContours?: unknown }} */ (cached))._getContours === 'function') {
58
116
  return cached;
59
117
  }
60
118
 
61
- if (this.directory.tables.glyf && this.directory.tables.glyf.transformed) {
119
+ let glyfEntry = this.directory.tables.glyf;
120
+ if (glyfEntry && glyfEntry.transformed) {
62
121
  if (!this._transformedGlyphs) { this._transformGlyfTable(); }
63
122
  let outline = new WOFF2Glyph(glyph, characters, this);
64
123
  if (!cached) {
@@ -70,18 +129,27 @@ export default class WOFF2Font extends TTFFont {
70
129
  return super._getBaseGlyph(glyph, characters);
71
130
  }
72
131
 
132
+ /** @returns {void} */
73
133
  _transformGlyfTable() {
74
134
  this._decompress();
75
- this.stream.pos = this.directory.tables.glyf.offset;
76
- let table = GlyfTable.decode(this.stream);
135
+ let glyfEntry = this.directory.tables.glyf;
136
+ if (!glyfEntry) {
137
+ this._transformedGlyphs = [];
138
+ return;
139
+ }
140
+ this.stream.pos = glyfEntry.offset;
141
+ let table = /** @type {GlyfTableDecoded} */ (GlyfTable.decode(this.stream));
142
+ /** @type {TransformedGlyf[]} */
77
143
  let glyphs = [];
78
144
 
79
145
  for (let index = 0; index < table.numGlyphs; index++) {
80
- let glyph = {};
146
+ /** @type {TransformedGlyf} */
147
+ let glyph = { numberOfContours: 0 };
81
148
  let nContours = table.nContours.readInt16BE();
82
149
  glyph.numberOfContours = nContours;
83
150
 
84
151
  if (nContours > 0) { // simple glyph
152
+ /** @type {number[]} */
85
153
  let nPoints = [];
86
154
  let totalPoints = 0;
87
155
 
@@ -93,12 +161,19 @@ export default class WOFF2Font extends TTFFont {
93
161
 
94
162
  glyph.points = decodeTriplet(table.flags, table.glyphs, totalPoints);
95
163
  for (let i = 0; i < nContours; i++) {
96
- glyph.points[nPoints[i] - 1].endContour = true;
164
+ let pts = glyph.points;
165
+ if (pts) {
166
+ pts[nPoints[i] - 1].endContour = true;
167
+ }
97
168
  }
98
169
 
99
170
  read255UInt16(table.glyphs);
100
171
  } else if (nContours < 0) { // composite glyph
101
- let haveInstructions = TTFGlyph.prototype._decodeComposite.call({ _font: this }, glyph, table.composites);
172
+ let haveInstructions = TTFGlyph.prototype._decodeComposite.call(
173
+ /** @type {TTFGlyph} */ (/** @type {unknown} */ ({ _font: this })),
174
+ /** @type {DecodedGlyf} */ (glyph),
175
+ table.composites
176
+ );
102
177
  if (haveInstructions) {
103
178
  read255UInt16(table.glyphs);
104
179
  }
@@ -111,13 +186,33 @@ export default class WOFF2Font extends TTFFont {
111
186
  }
112
187
  }
113
188
 
189
+ /**
190
+ * @typedef {{
191
+ * version: number,
192
+ * numGlyphs: number,
193
+ * nContours: DecodeStream,
194
+ * nPoints: DecodeStream,
195
+ * flags: DecodeStream,
196
+ * glyphs: DecodeStream,
197
+ * composites: DecodeStream
198
+ * }} GlyfTableDecoded
199
+ */
200
+
114
201
  // Special class that accepts a length and returns a sub-stream for that data
115
202
  class Substream {
203
+ /**
204
+ * @param {number | string} length
205
+ */
116
206
  constructor(length) {
117
207
  this.length = length;
118
208
  this._buf = new r.Buffer(length);
119
209
  }
120
210
 
211
+ /**
212
+ * @param {DecodeStream} stream
213
+ * @param {StructValue} parent
214
+ * @returns {DecodeStream}
215
+ */
121
216
  decode(stream, parent) {
122
217
  return new r.DecodeStream(this._buf.decode(stream, parent));
123
218
  }
@@ -149,6 +244,10 @@ const ONE_MORE_BYTE_CODE2 = 254;
149
244
  const ONE_MORE_BYTE_CODE1 = 255;
150
245
  const LOWEST_U_CODE = 253;
151
246
 
247
+ /**
248
+ * @param {DecodeStream} stream
249
+ * @returns {number}
250
+ */
152
251
  function read255UInt16(stream) {
153
252
  let code = stream.readUInt8();
154
253
 
@@ -167,17 +266,30 @@ function read255UInt16(stream) {
167
266
  return code;
168
267
  }
169
268
 
269
+ /**
270
+ * @param {number} flag
271
+ * @param {number} baseval
272
+ * @returns {number}
273
+ */
170
274
  function withSign(flag, baseval) {
171
275
  return flag & 1 ? baseval : -baseval;
172
276
  }
173
277
 
278
+ /**
279
+ * @param {DecodeStream} flags
280
+ * @param {DecodeStream} glyphs
281
+ * @param {number} nPoints
282
+ * @returns {Point[]}
283
+ */
174
284
  function decodeTriplet(flags, glyphs, nPoints) {
175
285
  let y;
176
286
  let x = y = 0;
287
+ /** @type {Point[]} */
177
288
  let res = [];
178
289
 
179
290
  for (let i = 0; i < nPoints; i++) {
180
- let dx, dy;
291
+ let dx = 0;
292
+ let dy = 0;
181
293
  let flag = flags.readUInt8();
182
294
  let onCurve = !(flag >> 7);
183
295
  flag &= 0x7f;
package/src/WOFFFont.js CHANGED
@@ -4,17 +4,41 @@ import { unzlibSync } from 'fflate';
4
4
  import * as r from 'restructure';
5
5
  import { asciiDecoder } from './utils';
6
6
 
7
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
8
+ /** @typedef {import('restructure').BinaryBuffer} BinaryBuffer */
9
+ /** @typedef {import('../types/fontkit').FontDirectory} FontDirectory */
10
+ /** @typedef {import('../types/fontkit').TableEntry} TableEntry */
11
+
7
12
  export default class WOFFFont extends TTFFont {
13
+ /** @type {string} */
8
14
  type = 'WOFF';
9
15
 
16
+ /** @type {boolean | undefined} */
17
+ _decompressed;
18
+
19
+ /**
20
+ * @param {ArrayBufferView} buffer
21
+ * @returns {boolean}
22
+ */
10
23
  static probe(buffer) {
11
- return asciiDecoder.decode(buffer.slice(0, 4)) === 'wOFF';
24
+ let bytes
25
+ = buffer instanceof Uint8Array
26
+ ? buffer
27
+ : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
28
+ return asciiDecoder.decode(bytes.subarray(0, 4)) === 'wOFF';
12
29
  }
13
30
 
31
+ /** @returns {void} */
14
32
  _decodeDirectory() {
15
- this.directory = WOFFDirectory.decode(this.stream, { _startOffset: 0 });
33
+ this.directory = /** @type {FontDirectory} */ (
34
+ /** @type {unknown} */ (WOFFDirectory.decode(this.stream, { _startOffset: 0 }))
35
+ );
16
36
  }
17
37
 
38
+ /**
39
+ * @param {TableEntry} table
40
+ * @returns {unknown}
41
+ */
18
42
  _decodeTable(table) {
19
43
  this._decompress();
20
44
  return super._decodeTable(table);
@@ -22,15 +46,21 @@ export default class WOFFFont extends TTFFont {
22
46
 
23
47
  // Inflate all tables into one stream so internal offsets (e.g. gvar) stay
24
48
  // valid against this.stream — same reason WOFF2Font decompresses up front.
49
+ /** @returns {void} */
25
50
  _decompress() {
26
51
  if (this._decompressed) {
27
52
  return;
28
53
  }
29
54
 
30
55
  let totalSize = 0;
56
+ /** @type {Array<{ entry: TableEntry, newOffset: number }>} */
31
57
  let layout = [];
32
- for (let tag in this.directory.tables) {
33
- let entry = this.directory.tables[tag];
58
+ let tableMap = this.directory.tables;
59
+ for (let tag in tableMap) {
60
+ let entry = tableMap[tag];
61
+ if (!entry) {
62
+ continue;
63
+ }
34
64
  layout.push({ entry, newOffset: totalSize });
35
65
  totalSize = (totalSize + entry.length + 3) & ~3;
36
66
  }
@@ -38,19 +68,26 @@ export default class WOFFFont extends TTFFont {
38
68
  let buffer = new Uint8Array(totalSize);
39
69
  for (let { entry, newOffset } of layout) {
40
70
  this.stream.pos = entry.offset;
71
+ /** @type {BinaryBuffer | Uint8Array} */
41
72
  let data;
42
- if (entry.compLength < entry.length) {
43
- data = unzlibSync(this.stream.readBuffer(entry.compLength), {
73
+ let compLength = entry.compLength != null ? entry.compLength : entry.length;
74
+ if (compLength < entry.length) {
75
+ let compressed = this.stream.readBuffer(compLength);
76
+ let compressedBytes
77
+ = compressed instanceof Uint8Array
78
+ ? compressed
79
+ : new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);
80
+ data = unzlibSync(compressedBytes, {
44
81
  out: new Uint8Array(entry.length)
45
82
  });
46
83
  } else {
47
84
  data = this.stream.readBuffer(entry.length);
48
85
  }
49
- buffer.set(data, newOffset);
86
+ buffer.set(data instanceof Uint8Array ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength), newOffset);
50
87
  entry.offset = newOffset;
51
88
  }
52
89
 
53
- this.stream = new r.DecodeStream(buffer);
90
+ this._setStream(new r.DecodeStream(buffer));
54
91
  this._decompressed = true;
55
92
  }
56
93
  }
@@ -1,5 +1,15 @@
1
1
  // see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html
2
2
  // and /System/Library/Frameworks/CoreText.framework/Versions/A/Headers/SFNTLayoutTypes.h on a Mac
3
+
4
+ /** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
5
+ /** @typedef {import('../../types/fontkit').FeatureValue} FeatureValue */
6
+ /** @typedef {import('../../types/fontkit').AATFeatureSettings} AATFeatureSettings */
7
+
8
+ /**
9
+ * @typedef {{ code: number, exclusive: boolean, [selector: string]: number | boolean }} AATFeatureDef
10
+ */
11
+
12
+ /** @type {Record<string, AATFeatureDef>} */
3
13
  const features = {
4
14
  allTypographicFeatures: {
5
15
  code: 0,
@@ -346,8 +356,17 @@ const features = {
346
356
  }
347
357
  };
348
358
 
349
- const feature = (name, selector) => [features[name].code, features[name][selector]];
359
+ /**
360
+ * @param {string} name
361
+ * @param {string} selector
362
+ * @returns {[number, number]}
363
+ */
364
+ const feature = (name, selector) => {
365
+ let f = features[name];
366
+ return [(f.code), /** @type {number} */ (f[selector])];
367
+ };
350
368
 
369
+ /** @type {Record<string, [number, number]>} */
351
370
  const OTMapping = {
352
371
  rlig: feature('ligatures', 'requiredLigatures'),
353
372
  clig: feature('ligatures', 'contextualLigatures'),
@@ -463,7 +482,7 @@ for (let i = 1; i <= 99; i++) {
463
482
  OTMapping[`cv${`00${i}`.slice(-2)}`] = [features.characterAlternatives.code, i];
464
483
  }
465
484
 
466
- // create inverse mapping
485
+ /** @type {Record<number, Record<number, string>>} */
467
486
  let AATMapping = {};
468
487
  for (let ot in OTMapping) {
469
488
  let aat = OTMapping[ot];
@@ -474,9 +493,14 @@ for (let ot in OTMapping) {
474
493
  AATMapping[aat[0]][aat[1]] = ot;
475
494
  }
476
495
 
477
- // Maps an array of OpenType features to AAT features
478
- // in the form of {featureType:{featureSetting:true}}
496
+ /**
497
+ * Maps an OpenType feature map to AAT features
498
+ * in the form of {featureType:{featureSetting:true}}
499
+ * @param {FeatureMap | Record<string, FeatureValue>} features
500
+ * @returns {AATFeatureSettings}
501
+ */
479
502
  export function mapOTToAAT(features) {
503
+ /** @type {AATFeatureSettings} */
480
504
  let res = {};
481
505
  for (let k in features) {
482
506
  let r;
@@ -492,38 +516,58 @@ export function mapOTToAAT(features) {
492
516
  return res;
493
517
  }
494
518
 
495
- // Maps strings in a [featureType, featureSetting]
496
- // to their equivalent number codes
519
+ /**
520
+ * @param {string | number} value
521
+ * @returns {value is string}
522
+ */
523
+ function isFeatureName(value) {
524
+ return typeof value === 'string' && Number.isNaN(Number(value));
525
+ }
526
+
527
+ /**
528
+ * Maps strings in a [featureType, featureSetting]
529
+ * to their equivalent number codes
530
+ * @param {[string | number, string | number]} f
531
+ * @returns {[number | undefined, number | undefined]}
532
+ */
497
533
  function mapFeatureStrings(f) {
498
534
  let [type, setting] = f;
535
+ /** @type {number | undefined} */
499
536
  let typeCode;
500
- if (isNaN(type)) {
501
- typeCode = features[type] && features[type].code;
537
+ if (isFeatureName(type)) {
538
+ typeCode = features[type] && /** @type {number} */ (features[type].code);
502
539
  } else {
503
- typeCode = type;
540
+ typeCode = Number(type);
504
541
  }
505
542
 
543
+ /** @type {number | undefined} */
506
544
  let settingCode;
507
- if (isNaN(setting)) {
508
- settingCode = features[type] && features[type][setting];
545
+ if (isFeatureName(setting)) {
546
+ let feat = isFeatureName(type) ? features[type] : undefined;
547
+ settingCode = feat ? /** @type {number} */ (feat[setting]) : undefined;
509
548
  } else {
510
- settingCode = setting;
549
+ settingCode = Number(setting);
511
550
  }
512
551
 
513
552
  return [typeCode, settingCode];
514
553
  }
515
554
 
516
- // Maps AAT features to an array of OpenType features
517
- // Supports both arrays in the form of [[featureType, featureSetting]]
518
- // and objects in the form of {featureType:{featureSetting:true}}
519
- // featureTypes and featureSettings can be either strings or number codes
555
+ /**
556
+ * Maps AAT features to an array of OpenType features.
557
+ * Supports both arrays in the form of [[featureType, featureSetting]]
558
+ * and objects in the form of {featureType:{featureSetting:true}}.
559
+ * featureTypes and featureSettings can be either strings or number codes.
560
+ * @param {Array<[string | number, string | number]> | Record<string, Record<string, FeatureValue>>} features
561
+ * @returns {string[]}
562
+ */
520
563
  export function mapAATToOT(features) {
564
+ /** @type {Record<string, true>} */
521
565
  let res = {};
522
566
  if (Array.isArray(features)) {
523
567
  for (let k = 0; k < features.length; k++) {
524
568
  let r;
525
569
  let f = mapFeatureStrings(features[k]);
526
- if ((r = AATMapping[f[0]] && AATMapping[f[0]][f[1]])) {
570
+ if (f[0] != null && f[1] != null && (r = AATMapping[f[0]] && AATMapping[f[0]][f[1]])) {
527
571
  res[r] = true;
528
572
  }
529
573
  }
@@ -533,7 +577,7 @@ export function mapAATToOT(features) {
533
577
  for (let setting in feature) {
534
578
  let r;
535
579
  let f = mapFeatureStrings([type, setting]);
536
- if (feature[setting] && (r = AATMapping[f[0]] && AATMapping[f[0]][f[1]])) {
580
+ if (feature[setting] && f[0] != null && f[1] != null && (r = AATMapping[f[0]] && AATMapping[f[0]][f[1]])) {
537
581
  res[r] = true;
538
582
  }
539
583
  }
@@ -1,13 +1,27 @@
1
1
  import * as AATFeatureMap from './AATFeatureMap';
2
2
  import AATMorxProcessor from './AATMorxProcessor';
3
3
 
4
+ /** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
5
+ /** @typedef {import('../../types/fontkit').GlyphRunLike} GlyphRunLike */
6
+ /** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
7
+ /** @typedef {import('../../types/fontkit').LanguageTag} LanguageTag */
8
+
4
9
  export default class AATLayoutEngine {
10
+ /**
11
+ * @param {LayoutFont} font
12
+ */
5
13
  constructor(font) {
14
+ /** @type {LayoutFont} */
6
15
  this.font = font;
16
+ /** @type {AATMorxProcessor} */
7
17
  this.morxProcessor = new AATMorxProcessor(font);
18
+ /** @type {boolean} */
8
19
  this.fallbackPosition = false;
9
20
  }
10
21
 
22
+ /**
23
+ * @param {GlyphRunLike} glyphRun
24
+ */
11
25
  substitute(glyphRun) {
12
26
  // AAT expects the glyphs to be in visual order prior to morx processing,
13
27
  // so reverse the glyphs if the script is right-to-left.
@@ -15,13 +29,25 @@ export default class AATLayoutEngine {
15
29
  glyphRun.glyphs.reverse();
16
30
  }
17
31
 
18
- this.morxProcessor.process(glyphRun.glyphs, AATFeatureMap.mapOTToAAT(glyphRun.features));
32
+ this.morxProcessor.process(
33
+ /** @type {import('../../types/fontkit').LayoutGlyph[]} */ (glyphRun.glyphs),
34
+ AATFeatureMap.mapOTToAAT(glyphRun.features)
35
+ );
19
36
  }
20
37
 
38
+ /**
39
+ * @param {ScriptTag | string[] | null | undefined} [_script]
40
+ * @param {LanguageTag | null | undefined} [_language]
41
+ * @returns {string[]}
42
+ */
21
43
  getAvailableFeatures(_script, _language) {
22
44
  return AATFeatureMap.mapAATToOT(this.morxProcessor.getSupportedFeatures());
23
45
  }
24
46
 
47
+ /**
48
+ * @param {number} gid
49
+ * @returns {Set<string>}
50
+ */
25
51
  stringsForGlyph(gid) {
26
52
  let glyphStrings = this.morxProcessor.generateInputs(gid);
27
53
  let result = new Set();
@@ -33,6 +59,12 @@ export default class AATLayoutEngine {
33
59
  return result;
34
60
  }
35
61
 
62
+ /**
63
+ * @param {number[]} glyphs
64
+ * @param {number} index
65
+ * @param {Set<string>} strings
66
+ * @param {string} string
67
+ */
36
68
  _addStrings(glyphs, index, strings, string) {
37
69
  let codePoints = this.font._cmapProcessor.codePointsForGlyph(glyphs[index]);
38
70