@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
@@ -2,9 +2,32 @@ import isEqual from 'fast-deep-equal';
2
2
  import CFFOperand from './CFFOperand';
3
3
  import { PropertyDescriptor } from 'restructure';
4
4
 
5
+ /** @typedef {import('restructure').BaseType} BaseType */
6
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
7
+ /** @typedef {import('restructure').EncodeStream} EncodeStream */
8
+ /** @typedef {import('restructure').StructValue} StructValue */
9
+ /** @typedef {import('../../types/fontkit').CFFDictField} CFFDictField */
10
+ /** @typedef {import('../../types/fontkit').CFFOperandType} CFFOperandType */
11
+ /** @typedef {import('../../types/fontkit').CFFEncodeContext} CFFEncodeContext */
12
+
13
+ /** @typedef {number | { forceLarge?: boolean, valueOf(): number }} CFFOperandValue */
14
+
15
+ /**
16
+ * @param {CFFOperandType} type
17
+ * @returns {type is BaseType}
18
+ */
19
+ function isCodec(type) {
20
+ return typeof type === 'object' && type != null && !Array.isArray(type);
21
+ }
22
+
5
23
  export default class CFFDict {
24
+ /**
25
+ * @param {CFFDictField[]} [ops]
26
+ */
6
27
  constructor(ops = []) {
28
+ /** @type {CFFDictField[]} */
7
29
  this.ops = ops;
30
+ /** @type {Record<number, CFFDictField>} */
8
31
  this.fields = {};
9
32
  for (let field of ops) {
10
33
  let key = Array.isArray(field[0]) ? field[0][0] << 8 | field[0][1] : field[0];
@@ -12,10 +35,17 @@ export default class CFFDict {
12
35
  }
13
36
  }
14
37
 
38
+ /**
39
+ * @param {CFFOperandType} type
40
+ * @param {DecodeStream} stream
41
+ * @param {StructValue} ret
42
+ * @param {unknown[]} operands
43
+ * @returns {unknown}
44
+ */
15
45
  decodeOperands(type, stream, ret, operands) {
16
46
  if (Array.isArray(type)) {
17
47
  return operands.map((op, i) => this.decodeOperands(type[i], stream, ret, [op]));
18
- } else if (type.decode != null) {
48
+ } else if (isCodec(type) && type.decode != null) {
19
49
  return type.decode(stream, ret, operands);
20
50
  } else {
21
51
  switch (type) {
@@ -31,11 +61,26 @@ export default class CFFDict {
31
61
  }
32
62
  }
33
63
 
64
+ /**
65
+ * @param {CFFOperandType} type
66
+ * @param {EncodeStream | null} stream
67
+ * @param {CFFEncodeContext | StructValue} ctx
68
+ * @param {unknown} operands
69
+ * @returns {unknown[]}
70
+ */
34
71
  encodeOperands(type, stream, ctx, operands) {
35
72
  if (Array.isArray(type)) {
73
+ if (!Array.isArray(operands)) {
74
+ return [operands];
75
+ }
36
76
  return operands.map((op, i) => this.encodeOperands(type[i], stream, ctx, op)[0]);
37
- } else if (type.encode != null) {
38
- return type.encode(stream, operands, ctx);
77
+ } else if (isCodec(type) && type.encode != null) {
78
+ let encoded = type.encode(stream, operands, ctx);
79
+ if (Array.isArray(encoded)) {
80
+ return encoded;
81
+ }
82
+ // Custom ops must return an operand list; coerce scalars (e.g. sid index).
83
+ return [encoded];
39
84
  } else if (typeof operands === 'number') {
40
85
  return [operands];
41
86
  } else if (typeof operands === 'boolean') {
@@ -47,15 +92,23 @@ export default class CFFDict {
47
92
  }
48
93
  }
49
94
 
95
+ /**
96
+ * @param {DecodeStream} stream
97
+ * @param {StructValue} parent
98
+ * @returns {StructValue}
99
+ */
50
100
  decode(stream, parent) {
51
- let end = stream.pos + parent.length;
101
+ let dictLength = typeof parent.length === 'number' ? parent.length : 0;
102
+ let end = stream.pos + dictLength;
103
+ /** @type {StructValue} */
52
104
  let ret = {};
105
+ /** @type {unknown[]} */
53
106
  let operands = [];
54
107
 
55
108
  // define hidden properties
56
109
  Object.defineProperties(ret, {
57
- parent: { value: parent },
58
- _startOffset: { value: stream.pos }
110
+ parent: { value: parent },
111
+ _startOffset: { value: stream.pos }
59
112
  });
60
113
 
61
114
  // fill in defaults
@@ -94,12 +147,20 @@ export default class CFFDict {
94
147
  return ret;
95
148
  }
96
149
 
150
+ /**
151
+ * @param {StructValue} dict
152
+ * @param {StructValue | CFFEncodeContext} parent
153
+ * @param {boolean} [includePointers]
154
+ * @returns {number}
155
+ */
97
156
  size(dict, parent, includePointers = true) {
157
+ /** @type {CFFEncodeContext} */
98
158
  let ctx = {
99
159
  parent,
100
160
  val: dict,
101
161
  pointerSize: 0,
102
- startOffset: parent.startOffset || 0
162
+ startOffset: (typeof parent.startOffset === 'number' ? parent.startOffset : 0),
163
+ pointers: []
103
164
  };
104
165
 
105
166
  let len = 0;
@@ -113,7 +174,7 @@ export default class CFFDict {
113
174
 
114
175
  let operands = this.encodeOperands(field[2], null, ctx, val);
115
176
  for (let op of operands) {
116
- len += CFFOperand.size(op);
177
+ len += CFFOperand.size(/** @type {CFFOperandValue} */ (op));
117
178
  }
118
179
 
119
180
  let key = Array.isArray(field[0]) ? field[0] : [field[0]];
@@ -127,11 +188,18 @@ export default class CFFDict {
127
188
  return len;
128
189
  }
129
190
 
191
+ /**
192
+ * @param {EncodeStream} stream
193
+ * @param {StructValue} dict
194
+ * @param {StructValue | null | undefined} parent
195
+ * @returns {void}
196
+ */
130
197
  encode(stream, dict, parent) {
198
+ /** @type {CFFEncodeContext} */
131
199
  let ctx = {
132
200
  pointers: [],
133
201
  startOffset: stream.pos,
134
- parent,
202
+ parent: parent ?? undefined,
135
203
  val: dict,
136
204
  pointerSize: 0
137
205
  };
@@ -146,7 +214,7 @@ export default class CFFDict {
146
214
 
147
215
  let operands = this.encodeOperands(field[2], stream, ctx, val);
148
216
  for (let op of operands) {
149
- CFFOperand.encode(stream, op);
217
+ CFFOperand.encode(stream, /** @type {CFFOperandValue} */ (op));
150
218
  }
151
219
 
152
220
  let key = Array.isArray(field[0]) ? field[0] : [field[0]];
@@ -158,7 +226,9 @@ export default class CFFDict {
158
226
  let i = 0;
159
227
  while (i < ctx.pointers.length) {
160
228
  let ptr = ctx.pointers[i++];
161
- ptr.type.encode(stream, ptr.val, ptr.parent);
229
+ if (ptr.type.encode) {
230
+ ptr.type.encode(stream, ptr.val, ptr.parent);
231
+ }
162
232
  }
163
233
 
164
234
  return;
@@ -1,3 +1,4 @@
1
+ /** @type {string[]} */
1
2
  export let StandardEncoding = [
2
3
  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
3
4
  '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
@@ -18,6 +19,7 @@ export let StandardEncoding = [
18
19
  'lslash', 'oslash', 'oe', 'germandbls'
19
20
  ];
20
21
 
22
+ /** @type {string[]} */
21
23
  export let ExpertEncoding = [
22
24
  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
23
25
  '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior',
@@ -1,68 +1,131 @@
1
1
  import CFFTop from './CFFTop';
2
2
  import standardStrings from './CFFStandardStrings';
3
3
 
4
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
5
+ /** @typedef {import('restructure').BinaryBuffer} BinaryBuffer */
6
+ /** @typedef {import('restructure').StructValue} StructValue */
7
+ /** @typedef {import('../../types/fontkit').CFFTopDict} CFFTopDict */
8
+ /** @typedef {import('../../types/fontkit').CFFPrivateDict} CFFPrivateDict */
9
+ /** @typedef {import('../../types/fontkit').CFFCharString} CFFCharString */
10
+
11
+ /**
12
+ * Decoded CFF / CFF2 font table.
13
+ * Properties from CFFTop are copied onto the instance in decode().
14
+ */
4
15
  class CFFFont {
16
+ /**
17
+ * @param {DecodeStream} stream
18
+ */
5
19
  constructor(stream) {
20
+ /** @type {DecodeStream} */
6
21
  this.stream = stream;
22
+ /** @type {number} */
23
+ this.version = 0;
24
+ /** @type {number | undefined} */
25
+ this.hdrSize = undefined;
26
+ /** @type {CFFTopDict} */
27
+ this.topDict = /** @type {CFFTopDict} */ ({ CharStrings: [] });
28
+ /** @type {StructValue[] | undefined} */
29
+ this.topDictIndex = undefined;
30
+ /** @type {string[] | undefined} */
31
+ this.nameIndex = undefined;
32
+ /** @type {string[] | undefined} */
33
+ this.stringIndex = undefined;
34
+ /** @type {CFFCharString[] | undefined} */
35
+ this.globalSubrIndex = undefined;
36
+ /** @type {boolean} */
37
+ this.isCIDFont = false;
7
38
  this.decode();
8
39
  }
9
40
 
41
+ /**
42
+ * @param {DecodeStream} stream
43
+ * @returns {CFFFont}
44
+ */
10
45
  static decode(stream) {
11
46
  return new CFFFont(stream);
12
47
  }
13
48
 
49
+ /**
50
+ * @returns {this}
51
+ */
14
52
  decode() {
15
53
  let top = CFFTop.decode(this.stream);
16
54
  for (let key in top) {
17
- let val = top[key];
18
- this[key] = val;
55
+ /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (this))[key] = top[key];
19
56
  }
20
57
 
21
58
  if (this.version < 2) {
22
- if (this.topDictIndex.length !== 1) {
23
- throw new Error("Only a single font is allowed in CFF");
59
+ if (!this.topDictIndex || this.topDictIndex.length !== 1) {
60
+ throw new Error('Only a single font is allowed in CFF');
24
61
  }
25
62
 
26
- this.topDict = this.topDictIndex[0];
63
+ this.topDict = /** @type {CFFTopDict} */ (this.topDictIndex[0]);
27
64
  }
28
65
 
29
66
  this.isCIDFont = this.topDict.ROS != null;
30
67
  return this;
31
68
  }
32
69
 
70
+ /**
71
+ * @param {number | null | undefined} sid
72
+ * @returns {string | null}
73
+ */
33
74
  string(sid) {
34
75
  if (this.version >= 2) {
35
76
  return null;
36
77
  }
37
78
 
79
+ if (sid == null) {
80
+ return null;
81
+ }
82
+
38
83
  if (sid < standardStrings.length) {
39
84
  return standardStrings[sid];
40
85
  }
41
86
 
42
- return this.stringIndex[sid - standardStrings.length];
87
+ return this.stringIndex ? this.stringIndex[sid - standardStrings.length] : null;
43
88
  }
44
89
 
90
+ /**
91
+ * @returns {string | null}
92
+ */
45
93
  get postscriptName() {
46
94
  if (this.version < 2) {
47
- return this.nameIndex[0];
95
+ return this.nameIndex ? this.nameIndex[0] : null;
48
96
  }
49
97
 
50
98
  return null;
51
99
  }
52
100
 
101
+ /**
102
+ * @returns {string | null}
103
+ */
53
104
  get fullName() {
54
105
  return this.string(this.topDict.FullName);
55
106
  }
56
107
 
108
+ /**
109
+ * @returns {string | null}
110
+ */
57
111
  get familyName() {
58
112
  return this.string(this.topDict.FamilyName);
59
113
  }
60
114
 
115
+ /**
116
+ * @param {number} glyph
117
+ * @returns {BinaryBuffer}
118
+ */
61
119
  getCharString(glyph) {
62
- this.stream.pos = this.topDict.CharStrings[glyph].offset;
63
- return this.stream.readBuffer(this.topDict.CharStrings[glyph].length);
120
+ let cs = /** @type {CFFCharString} */ (this.topDict.CharStrings[glyph]);
121
+ this.stream.pos = cs.offset;
122
+ return this.stream.readBuffer(cs.length);
64
123
  }
65
124
 
125
+ /**
126
+ * @param {number} gid
127
+ * @returns {string | null}
128
+ */
66
129
  getGlyphName(gid) {
67
130
  // CFF2 glyph names are in the post table.
68
131
  if (this.version >= 2) {
@@ -76,7 +139,7 @@ class CFFFont {
76
139
 
77
140
  let { charset } = this.topDict;
78
141
  if (Array.isArray(charset)) {
79
- return charset[gid];
142
+ return /** @type {string} */ (charset[gid]);
80
143
  }
81
144
 
82
145
  if (gid === 0) {
@@ -85,14 +148,30 @@ class CFFFont {
85
148
 
86
149
  gid -= 1;
87
150
 
88
- switch (charset.version) {
151
+ if (!charset || typeof charset !== 'object') {
152
+ return null;
153
+ }
154
+
155
+ /**
156
+ * @typedef {{
157
+ * version?: number,
158
+ * glyphs?: number[],
159
+ * ranges?: Array<{ offset: number, nLeft: number, first: number }>
160
+ * }} CharsetTable
161
+ */
162
+ let cs = /** @type {StructValue & CharsetTable} */ (charset);
163
+
164
+ switch (cs.version) {
89
165
  case 0:
90
- return this.string(charset.glyphs[gid]);
166
+ return this.string(cs.glyphs ? cs.glyphs[gid] : undefined);
91
167
 
92
168
  case 1:
93
169
  case 2:
94
- for (let i = 0; i < charset.ranges.length; i++) {
95
- let range = charset.ranges[i];
170
+ if (!cs.ranges) {
171
+ return null;
172
+ }
173
+ for (let i = 0; i < cs.ranges.length; i++) {
174
+ let range = cs.ranges[i];
96
175
  if (range.offset <= gid && gid <= range.offset + range.nLeft) {
97
176
  return this.string(range.first + (gid - range.offset));
98
177
  }
@@ -103,44 +182,59 @@ class CFFFont {
103
182
  return null;
104
183
  }
105
184
 
185
+ /**
186
+ * @param {number} gid
187
+ * @returns {number | null}
188
+ */
106
189
  fdForGlyph(gid) {
107
190
  if (!this.topDict.FDSelect) {
108
191
  return null;
109
192
  }
110
193
 
111
- switch (this.topDict.FDSelect.version) {
194
+ let fdSelect = this.topDict.FDSelect;
195
+
196
+ switch (fdSelect.version) {
112
197
  case 0:
113
- return this.topDict.FDSelect.fds[gid];
198
+ return fdSelect.fds ? fdSelect.fds[gid] : null;
114
199
 
115
200
  case 3:
116
201
  case 4: {
117
- let { ranges } = this.topDict.FDSelect;
202
+ let { ranges } = fdSelect;
203
+ if (!ranges) {
204
+ return null;
205
+ }
118
206
  let low = 0;
119
207
  let high = ranges.length - 1;
120
208
 
121
209
  while (low <= high) {
122
210
  let mid = (low + high) >> 1;
211
+ let midRange = ranges[mid];
212
+ let nextRange = ranges[mid + 1];
123
213
 
124
- if (gid < ranges[mid].first) {
214
+ if (gid < midRange.first) {
125
215
  high = mid - 1;
126
- } else if (mid < high && gid >= ranges[mid + 1].first) {
216
+ } else if (mid < high && nextRange && gid >= nextRange.first) {
127
217
  low = mid + 1;
128
218
  } else {
129
- return ranges[mid].fd;
219
+ return midRange.fd;
130
220
  }
131
221
  }
132
222
 
133
- throw new Error(`Unknown FDSelect version: ${this.topDict.FDSelect.version}`);
223
+ throw new Error(`Unknown FDSelect version: ${fdSelect.version}`);
134
224
  }
135
225
  default:
136
- throw new Error(`Unknown FDSelect version: ${this.topDict.FDSelect.version}`);
226
+ throw new Error(`Unknown FDSelect version: ${fdSelect.version}`);
137
227
  }
138
228
  }
139
229
 
230
+ /**
231
+ * @param {number} gid
232
+ * @returns {CFFPrivateDict | null | undefined}
233
+ */
140
234
  privateDictForGlyph(gid) {
141
235
  if (this.topDict.FDSelect) {
142
236
  let fd = this.fdForGlyph(gid);
143
- if (this.topDict.FDArray[fd]) {
237
+ if (fd != null && this.topDict.FDArray && this.topDict.FDArray[fd]) {
144
238
  return this.topDict.FDArray[fd].Private;
145
239
  }
146
240
 
@@ -151,7 +245,9 @@ class CFFFont {
151
245
  return this.topDict.Private;
152
246
  }
153
247
 
154
- return this.topDict.FDArray[0].Private;
248
+ return this.topDict.FDArray && this.topDict.FDArray[0]
249
+ ? this.topDict.FDArray[0].Private
250
+ : null;
155
251
  }
156
252
  }
157
253
 
@@ -1,18 +1,38 @@
1
1
  import * as r from 'restructure';
2
2
 
3
+ /** @typedef {import('restructure').BaseType} BaseType */
4
+ /** @typedef {import('restructure').DecodeStream} DecodeStream */
5
+ /** @typedef {import('restructure').EncodeStream} EncodeStream */
6
+ /** @typedef {import('restructure').NumberT} NumberT */
7
+ /** @typedef {import('restructure').StructValue} StructValue */
8
+ /** @typedef {import('../../types/fontkit').CFFCharString} CFFCharString */
9
+
3
10
  export default class CFFIndex {
11
+ /**
12
+ * @param {BaseType} [type]
13
+ */
4
14
  constructor(type) {
15
+ /** @type {BaseType | undefined} */
5
16
  this.type = type;
6
17
  }
7
18
 
19
+ /**
20
+ * @param {StructValue | null | undefined} ctx
21
+ * @returns {number}
22
+ */
8
23
  getCFFVersion(ctx) {
9
24
  while (ctx && !ctx.hdrSize) {
10
25
  ctx = ctx.parent;
11
26
  }
12
27
 
13
- return ctx ? ctx.version : -1;
28
+ return ctx && typeof ctx.version === 'number' ? ctx.version : -1;
14
29
  }
15
30
 
31
+ /**
32
+ * @param {DecodeStream} stream
33
+ * @param {StructValue} parent
34
+ * @returns {unknown[]}
35
+ */
16
36
  decode(stream, parent) {
17
37
  let version = this.getCFFVersion(parent);
18
38
  let count = version >= 2
@@ -24,6 +44,7 @@ export default class CFFIndex {
24
44
  }
25
45
 
26
46
  let offSize = stream.readUInt8();
47
+ /** @type {NumberT} */
27
48
  let offsetType;
28
49
  if (offSize === 1) {
29
50
  offsetType = r.uint8;
@@ -37,6 +58,7 @@ export default class CFFIndex {
37
58
  throw new Error(`Bad offset size in CFFIndex: ${offSize} ${stream.pos}`);
38
59
  }
39
60
 
61
+ /** @type {unknown[]} */
40
62
  let ret = [];
41
63
  let startPos = stream.pos + ((count + 1) * offSize) - 1;
42
64
 
@@ -44,7 +66,7 @@ export default class CFFIndex {
44
66
  for (let i = 0; i < count; i++) {
45
67
  let end = offsetType.decode(stream);
46
68
 
47
- if (this.type != null) {
69
+ if (this.type != null && this.type.decode) {
48
70
  let pos = stream.pos;
49
71
  stream.pos = startPos + start;
50
72
 
@@ -52,10 +74,12 @@ export default class CFFIndex {
52
74
  ret.push(this.type.decode(stream, parent));
53
75
  stream.pos = pos;
54
76
  } else {
55
- ret.push({
77
+ /** @type {CFFCharString} */
78
+ let range = {
56
79
  offset: startPos + start,
57
80
  length: end - start
58
- });
81
+ };
82
+ ret.push(range);
59
83
  }
60
84
 
61
85
  start = end;
@@ -65,21 +89,35 @@ export default class CFFIndex {
65
89
  return ret;
66
90
  }
67
91
 
92
+ /**
93
+ * @param {unknown[]} arr
94
+ * @param {StructValue | null | undefined} parent
95
+ * @returns {number}
96
+ */
68
97
  size(arr, parent) {
69
98
  let size = 2;
70
99
  if (arr.length === 0) {
71
100
  return size;
72
101
  }
73
102
 
74
- let type = this.type || new r.Buffer;
103
+ /** @type {BaseType} */
104
+ let type = this.type || new r.Buffer();
75
105
 
76
106
  // find maximum offset to detminine offset type
77
107
  let offset = 1;
78
108
  for (let i = 0; i < arr.length; i++) {
79
109
  let item = arr[i];
80
- offset += type.size(item, parent);
110
+ if (!type.size) {
111
+ throw new Error('CFFIndex element type must implement size()');
112
+ }
113
+ let itemSize = type.size(item, parent);
114
+ if (typeof itemSize !== 'number') {
115
+ throw new Error('CFFIndex element size() must return a number');
116
+ }
117
+ offset += itemSize;
81
118
  }
82
119
 
120
+ /** @type {NumberT} */
83
121
  let offsetType;
84
122
  if (offset <= 0xff) {
85
123
  offsetType = r.uint8;
@@ -90,7 +128,7 @@ export default class CFFIndex {
90
128
  } else if (offset <= 0xffffffff) {
91
129
  offsetType = r.uint32;
92
130
  } else {
93
- throw new Error("Bad offset in CFFIndex");
131
+ throw new Error('Bad offset in CFFIndex');
94
132
  }
95
133
 
96
134
  size += 1 + offsetType.size() * (arr.length + 1);
@@ -99,23 +137,38 @@ export default class CFFIndex {
99
137
  return size;
100
138
  }
101
139
 
140
+ /**
141
+ * @param {EncodeStream} stream
142
+ * @param {unknown[]} arr
143
+ * @param {StructValue | null | undefined} parent
144
+ * @returns {void}
145
+ */
102
146
  encode(stream, arr, parent) {
103
147
  stream.writeUInt16BE(arr.length);
104
148
  if (arr.length === 0) {
105
149
  return;
106
150
  }
107
151
 
108
- let type = this.type || new r.Buffer;
152
+ /** @type {BaseType} */
153
+ let type = this.type || new r.Buffer();
109
154
 
110
155
  // find maximum offset to detminine offset type
156
+ /** @type {number[]} */
111
157
  let sizes = [];
112
158
  let offset = 1;
113
159
  for (let item of arr) {
160
+ if (!type.size) {
161
+ throw new Error('CFFIndex element type must implement size()');
162
+ }
114
163
  let s = type.size(item, parent);
164
+ if (typeof s !== 'number') {
165
+ throw new Error('CFFIndex element size() must return a number');
166
+ }
115
167
  sizes.push(s);
116
168
  offset += s;
117
169
  }
118
170
 
171
+ /** @type {NumberT} */
119
172
  let offsetType;
120
173
  if (offset <= 0xff) {
121
174
  offsetType = r.uint8;
@@ -126,7 +179,7 @@ export default class CFFIndex {
126
179
  } else if (offset <= 0xffffffff) {
127
180
  offsetType = r.uint32;
128
181
  } else {
129
- throw new Error("Bad offset in CFFIndex");
182
+ throw new Error('Bad offset in CFFIndex');
130
183
  }
131
184
 
132
185
  // write offset size
@@ -142,6 +195,10 @@ export default class CFFIndex {
142
195
  }
143
196
 
144
197
  for (let item of arr) {
198
+ if (!type.encode) {
199
+ throw new Error('CFFIndex element type must implement encode()');
200
+ }
201
+ // Element type varies (String, Buffer, CFFDict, …); BaseType.encode accepts unknown.
145
202
  type.encode(stream, item, parent);
146
203
  }
147
204