imagescript 3.2.1 → 3.3.0

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 (59) hide show
  1. package/README.md +8 -3
  2. package/THIRD_PARTY_NOTICES.md +131 -0
  3. package/dist/animation.d.ts +7 -0
  4. package/dist/animation.js +21 -11
  5. package/dist/codecs/crc.js +5 -0
  6. package/dist/codecs/gif.js +10 -35
  7. package/dist/codecs/jpeg.js +9 -27
  8. package/dist/codecs/png.js +22 -10
  9. package/dist/codecs/tga.js +3 -1
  10. package/dist/codecs/tiff.js +7 -2
  11. package/dist/codecs/vp8.js +3 -1
  12. package/dist/codecs/vp8l.js +126 -58
  13. package/dist/codecs/webp.js +11 -9
  14. package/dist/codecs/zlib.js +81 -69
  15. package/dist/frame.d.ts +1 -0
  16. package/dist/frame.js +4 -2
  17. package/dist/gif.d.ts +1 -0
  18. package/dist/gif.js +2 -2
  19. package/dist/image.d.ts +42 -7
  20. package/dist/image.js +217 -110
  21. package/dist/index.js +4 -4
  22. package/dist/render/bidi.js +350 -0
  23. package/dist/render/buffer.js +362 -0
  24. package/dist/render/color.js +164 -0
  25. package/dist/render/colr.js +523 -0
  26. package/dist/render/font.js +173 -241
  27. package/dist/render/hinting.js +12 -4
  28. package/dist/render/layout.js +1553 -0
  29. package/dist/render/linebreak.js +210 -0
  30. package/dist/render/segment.js +106 -0
  31. package/dist/render/shape.js +984 -0
  32. package/dist/render/shapers/arabic.js +284 -0
  33. package/dist/render/shapers/hangul.js +142 -0
  34. package/dist/render/shapers/hebrew.js +49 -0
  35. package/dist/render/shapers/index.js +52 -0
  36. package/dist/render/shapers/indic.js +579 -0
  37. package/dist/render/shapers/khmer.js +104 -0
  38. package/dist/render/shapers/machine.js +129 -0
  39. package/dist/render/shapers/myanmar.js +121 -0
  40. package/dist/render/shapers/syllabic.js +100 -0
  41. package/dist/render/shapers/thai.js +125 -0
  42. package/dist/render/shapers/use.js +235 -0
  43. package/dist/render/svg.js +101 -22
  44. package/dist/render/text.js +373 -103
  45. package/dist/render/unicode-data.js +63 -0
  46. package/dist/render/unicode.js +58 -0
  47. package/dist/text-layout.d.ts +4 -1
  48. package/dist/text-layout.js +2 -2
  49. package/dist/util/alpha.js +43 -0
  50. package/dist/util/blur.js +20 -33
  51. package/dist/util/bytes.js +31 -0
  52. package/dist/util/limits.js +34 -0
  53. package/dist/util/options.js +7 -0
  54. package/dist/util/resample.js +122 -5
  55. package/dist/util/rotate.js +43 -0
  56. package/dist/version.js +1 -1
  57. package/licenses/HarfBuzz.txt +42 -0
  58. package/licenses/Unicode-3.0.txt +39 -0
  59. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ const MAX_COMMANDS = 100000;
1
2
  export class Font {
2
3
  data;
3
4
  view;
@@ -13,7 +14,8 @@ export class Font {
13
14
  cff = null;
14
15
  cmap;
15
16
  outlineCache;
16
- kerning;
17
+ kernTable;
18
+ variation;
17
19
  constructor(data) {
18
20
  this.data = data;
19
21
  this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
@@ -53,11 +55,14 @@ export class Font {
53
55
  }
54
56
  else if (this.tables.CFF2)
55
57
  throw new Error('Font: CFF2 outlines are not supported');
58
+ else if (this.tables.CBDT && this.tables.CBLC || this.tables.sbix)
59
+ this.outlines = 'none';
56
60
  else
57
61
  throw new Error('Font: no supported glyph outlines');
58
62
  this.cmap = parseCmap(this, this.tables.cmap.offset);
59
63
  this.outlineCache = new Map();
60
- this.kerning = (this.tables.GPOS && parseGPOSKerning(this, this.tables.GPOS.offset)) || (this.tables.kern && parseKern(this, this.tables.kern.offset)) || null;
64
+ this.kernTable = this.tables.kern && parseKern(this, this.tables.kern.offset) || null;
65
+ this.variation = parseVariations(this, this.tables.cmap.offset);
61
66
  }
62
67
  u8(o) { return this.data[o]; }
63
68
  i8(o) { return this.view.getInt8(o); }
@@ -73,6 +78,28 @@ export class Font {
73
78
  const index = Math.min(glyph, this.numberOfHMetrics - 1);
74
79
  return this.u16(hmtx + index * 4);
75
80
  }
81
+ variationGlyph(codepoint, selector) {
82
+ const glyph = this.variation ? this.variation(codepoint, selector) : 0;
83
+ return glyph < 0 ? this.glyphIndex(codepoint) : glyph;
84
+ }
85
+ extents(glyph) {
86
+ if (this.outlines === 'glyf' && glyph < this.numGlyphs) {
87
+ const o = this.glyfOffset(glyph);
88
+ if (o < 0)
89
+ return { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
90
+ return { xMin: this.i16(o + 2), yMin: this.i16(o + 4), xMax: this.i16(o + 6), yMax: this.i16(o + 8) };
91
+ }
92
+ let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
93
+ for (const command of this.outline(glyph))
94
+ for (let i = 1; i < command.length; i += 2) {
95
+ const x = command[i], y = command[i + 1];
96
+ xMin = Math.min(xMin, x);
97
+ xMax = Math.max(xMax, x);
98
+ yMin = Math.min(yMin, y);
99
+ yMax = Math.max(yMax, y);
100
+ }
101
+ return xMax >= xMin ? { xMin, yMin, xMax, yMax } : { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
102
+ }
76
103
  leftSideBearing(glyph) {
77
104
  const hmtx = this.tables.hmtx.offset;
78
105
  if (glyph < this.numberOfHMetrics)
@@ -82,63 +109,42 @@ export class Font {
82
109
  glyfPoints(glyph) {
83
110
  if (this.outlines !== 'glyf' || glyph >= this.numGlyphs)
84
111
  return null;
85
- const loca = this.tables.loca.offset;
86
- const start = this.indexToLocFormat ? this.u32(loca + glyph * 4) : this.u16(loca + glyph * 2) * 2;
87
- const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
88
- if (end <= start)
112
+ const offset = this.glyfOffset(glyph);
113
+ if (offset < 0)
89
114
  return null;
90
- const offset = this.tables.glyf.offset + start;
91
115
  const contourCount = this.i16(offset);
92
116
  const xMin = this.i16(offset + 2);
93
117
  if (contourCount < 0) {
94
- const components = [];
95
- let p = offset + 10, flags = 0;
96
- do {
97
- flags = this.u16(p);
98
- const component = this.u16(p + 2);
99
- p += 4;
100
- let arg1, arg2;
101
- if (flags & 1) {
102
- arg1 = flags & 2 ? this.i16(p) : this.u16(p);
103
- arg2 = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
104
- p += 4;
105
- }
106
- else {
107
- arg1 = flags & 2 ? this.i8(p) : this.u8(p);
108
- arg2 = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
109
- p += 2;
110
- }
111
- let matrix = [1, 0, 0, 1];
112
- const f2dot14 = (o) => this.i16(o) / 16384;
113
- if (flags & 8) {
114
- const s = f2dot14(p);
115
- matrix = [s, 0, 0, s];
116
- p += 2;
117
- }
118
- else if (flags & 0x40) {
119
- matrix = [f2dot14(p), 0, 0, f2dot14(p + 2)];
120
- p += 4;
121
- }
122
- else if (flags & 0x80) {
123
- matrix = [f2dot14(p), f2dot14(p + 2), f2dot14(p + 4), f2dot14(p + 6)];
124
- p += 8;
125
- }
126
- components.push({
127
- glyph: component,
128
- dx: flags & 2 ? arg1 : 0,
129
- dy: flags & 2 ? arg2 : 0,
130
- matrix,
131
- round: (flags & 4) !== 0,
132
- match: flags & 2 ? null : [arg1, arg2],
133
- useMetrics: (flags & 0x200) !== 0
134
- });
135
- } while (flags & 0x20);
136
- const instructions = flags & 0x100 ? this.data.subarray(p + 2, p + 2 + this.u16(p)) : new Uint8Array(0);
137
- return { xs: [], ys: [], on: [], ends: [], instructions, xMin, components };
118
+ const { components, end } = this.components(offset);
119
+ const instructions = components[components.length - 1].flags & 0x100 ? this.data.subarray(end + 2, end + 2 + this.u16(end)) : new Uint8Array(0);
120
+ return {
121
+ xs: [], ys: [], on: [], ends: [], instructions, xMin,
122
+ components: components.map(({ glyph, flags, arg1, arg2, matrix }) => ({
123
+ glyph, dx: flags & 2 ? arg1 : 0, dy: flags & 2 ? arg2 : 0, matrix,
124
+ round: (flags & 4) !== 0, match: flags & 2 ? null : [arg1, arg2], useMetrics: (flags & 0x200) !== 0,
125
+ })),
126
+ };
138
127
  }
128
+ const points = this.simplePoints(offset, contourCount);
129
+ if (!points)
130
+ return null;
131
+ const { xs, ys, flags, ends, instructions } = points;
132
+ return { xs: Array.from(xs), ys: Array.from(ys), on: Array.from(flags, f => (f & 1) === 1), ends, instructions, xMin, components: null };
133
+ }
134
+ glyfOffset(glyph) {
135
+ const loca = this.tables.loca.offset;
136
+ const start = this.indexToLocFormat ? this.u32(loca + glyph * 4) : this.u16(loca + glyph * 2) * 2;
137
+ const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
138
+ return end > start ? this.tables.glyf.offset + start : -1;
139
+ }
140
+ simplePoints(offset, contourCount) {
139
141
  const ends = [];
140
- for (let i = 0; i < contourCount; i++)
141
- ends.push(this.u16(offset + 10 + i * 2));
142
+ for (let i = 0; i < contourCount; i++) {
143
+ const end = this.u16(offset + 10 + i * 2);
144
+ if (i && end <= ends[i - 1])
145
+ return null;
146
+ ends.push(end);
147
+ }
142
148
  const pointCount = contourCount ? ends[contourCount - 1] + 1 : 0;
143
149
  let p = offset + 10 + contourCount * 2;
144
150
  const instructionLength = this.u16(p);
@@ -152,7 +158,7 @@ export class Font {
152
158
  for (let repeat = this.u8(p++); repeat > 0 && i < pointCount; repeat--)
153
159
  flags[i++] = flag;
154
160
  }
155
- const xs = [], ys = [];
161
+ const xs = new Int32Array(pointCount), ys = new Int32Array(pointCount);
156
162
  for (const [coords, short, same] of [[xs, 2, 16], [ys, 4, 32]]) {
157
163
  let value = 0;
158
164
  for (let i = 0; i < pointCount; i++) {
@@ -165,113 +171,92 @@ export class Font {
165
171
  value += this.i16(p);
166
172
  p += 2;
167
173
  }
168
- coords.push(value);
174
+ coords[i] = value;
169
175
  }
170
176
  }
171
- return { xs, ys, on: Array.from(flags, f => (f & 1) === 1), ends, instructions, xMin, components: null };
177
+ return { xs, ys, flags, ends, instructions };
178
+ }
179
+ components(offset) {
180
+ const components = [];
181
+ const f2dot14 = (o) => this.i16(o) / 16384;
182
+ let p = offset + 10, flags = 0;
183
+ do {
184
+ flags = this.u16(p);
185
+ const glyph = this.u16(p + 2);
186
+ p += 4;
187
+ let arg1, arg2;
188
+ if (flags & 1) {
189
+ arg1 = flags & 2 ? this.i16(p) : this.u16(p);
190
+ arg2 = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
191
+ p += 4;
192
+ }
193
+ else {
194
+ arg1 = flags & 2 ? this.i8(p) : this.u8(p);
195
+ arg2 = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
196
+ p += 2;
197
+ }
198
+ let matrix = [1, 0, 0, 1];
199
+ if (flags & 8) {
200
+ const scale = f2dot14(p);
201
+ matrix = [scale, 0, 0, scale];
202
+ p += 2;
203
+ }
204
+ else if (flags & 0x40) {
205
+ matrix = [f2dot14(p), 0, 0, f2dot14(p + 2)];
206
+ p += 4;
207
+ }
208
+ else if (flags & 0x80) {
209
+ matrix = [f2dot14(p), f2dot14(p + 2), f2dot14(p + 4), f2dot14(p + 6)];
210
+ p += 8;
211
+ }
212
+ components.push({ glyph, flags, arg1, arg2, matrix });
213
+ } while (flags & 0x20);
214
+ return { components, end: p };
172
215
  }
173
216
  outline(glyph) {
174
217
  if (glyph >= this.numGlyphs)
175
218
  glyph = 0;
176
219
  let path = this.outlineCache.get(glyph);
177
220
  if (!path) {
178
- path = this.outlines === 'glyf' ? this.glyfOutline(glyph, 0) : this.cffOutline(glyph);
221
+ path = this.outlines === 'glyf' ? this.glyfOutline(glyph, { expanding: new Set(), budget: MAX_COMMANDS }) : this.outlines === 'cff' ? this.cffOutline(glyph) : [];
179
222
  this.outlineCache.set(glyph, path);
180
223
  }
181
224
  return path;
182
225
  }
183
- glyfOutline(glyph, depth) {
184
- if (depth > 8)
226
+ glyfOutline(glyph, expanding) {
227
+ if (expanding.expanding.has(glyph) || expanding.expanding.size > 8 || expanding.budget <= 0)
185
228
  return [];
186
- const loca = this.tables.loca.offset;
187
- const start = this.indexToLocFormat ? this.u32(loca + glyph * 4) : this.u16(loca + glyph * 2) * 2;
188
- const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
189
- if (end <= start)
229
+ const offset = this.glyfOffset(glyph);
230
+ if (offset < 0)
190
231
  return [];
191
- const offset = this.tables.glyf.offset + start;
192
232
  const contours = this.i16(offset);
193
- return contours >= 0 ? this.simpleGlyph(offset, contours) : this.compositeGlyph(offset, depth);
233
+ if (contours >= 0) {
234
+ const path = this.simpleGlyph(offset, contours);
235
+ expanding.budget -= path.length;
236
+ return expanding.budget >= 0 ? path : [];
237
+ }
238
+ expanding.expanding.add(glyph);
239
+ const path = this.compositeGlyph(offset, expanding);
240
+ expanding.expanding.delete(glyph);
241
+ return path;
194
242
  }
195
243
  simpleGlyph(offset, contourCount) {
196
- const endPoints = [];
197
- for (let i = 0; i < contourCount; i++)
198
- endPoints.push(this.u16(offset + 10 + i * 2));
199
- const pointCount = contourCount ? endPoints[contourCount - 1] + 1 : 0;
200
- let p = offset + 10 + contourCount * 2;
201
- p += 2 + this.u16(p);
202
- const flags = new Uint8Array(pointCount);
203
- for (let i = 0; i < pointCount;) {
204
- const flag = this.u8(p++);
205
- flags[i++] = flag;
206
- if (flag & 8) {
207
- for (let repeat = this.u8(p++); repeat > 0 && i < pointCount; repeat--)
208
- flags[i++] = flag;
209
- }
210
- }
211
- const xs = new Int32Array(pointCount);
212
- const ys = new Int32Array(pointCount);
213
- for (const [coords, short, same] of [[xs, 2, 16], [ys, 4, 32]]) {
214
- let value = 0;
215
- for (let i = 0; i < pointCount; i++) {
216
- const flag = flags[i];
217
- if (flag & short) {
218
- const delta = this.u8(p++);
219
- value += flag & same ? delta : -delta;
220
- }
221
- else if (!(flag & same)) {
222
- value += this.i16(p);
223
- p += 2;
224
- }
225
- coords[i] = value;
226
- }
227
- }
244
+ const points = this.simplePoints(offset, contourCount);
245
+ if (!points)
246
+ return [];
228
247
  const path = [];
229
248
  let first = 0;
230
- for (const last of endPoints) {
231
- contourToPath(path, xs, ys, flags, first, last);
249
+ for (const last of points.ends) {
250
+ contourToPath(path, points.xs, points.ys, points.flags, first, last);
232
251
  first = last + 1;
233
252
  }
234
253
  return path;
235
254
  }
236
- compositeGlyph(offset, depth) {
255
+ compositeGlyph(offset, expanding) {
237
256
  const path = [];
238
- let p = offset + 10;
239
- for (let more = true; more;) {
240
- const flags = this.u16(p);
241
- const glyph = this.u16(p + 2);
242
- p += 4;
243
- let dx, dy;
244
- if (flags & 1) {
245
- dx = flags & 2 ? this.i16(p) : this.u16(p);
246
- dy = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
247
- p += 4;
248
- }
249
- else {
250
- dx = flags & 2 ? this.i8(p) : this.u8(p);
251
- dy = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
252
- p += 2;
253
- }
254
- if (!(flags & 2))
255
- dx = dy = 0;
256
- let a = 1, b = 0, c = 0, d = 1;
257
- const f2dot14 = (o) => this.i16(o) / 16384;
258
- if (flags & 8) {
259
- a = d = f2dot14(p);
260
- p += 2;
261
- }
262
- else if (flags & 0x40) {
263
- a = f2dot14(p);
264
- d = f2dot14(p + 2);
265
- p += 4;
266
- }
267
- else if (flags & 0x80) {
268
- a = f2dot14(p);
269
- b = f2dot14(p + 2);
270
- c = f2dot14(p + 4);
271
- d = f2dot14(p + 6);
272
- p += 8;
273
- }
274
- for (const command of this.glyfOutline(glyph, depth + 1)) {
257
+ for (const { glyph, flags, arg1, arg2, matrix: [a, b, c, d] } of this.components(offset).components) {
258
+ const dx = flags & 2 ? arg1 : 0, dy = flags & 2 ? arg2 : 0;
259
+ for (const command of this.glyfOutline(glyph, expanding)) {
275
260
  const transformed = [command[0]];
276
261
  for (let i = 1; i < command.length; i += 2) {
277
262
  const x = command[i], y = command[i + 1];
@@ -279,7 +264,6 @@ export class Font {
279
264
  }
280
265
  path.push(transformed);
281
266
  }
282
- more = !!(flags & 0x20);
283
267
  }
284
268
  return path;
285
269
  }
@@ -424,115 +408,63 @@ function parseCmap(font, offset) {
424
408
  }
425
409
  return symbol ? (c) => lookup(c) || (c < 256 ? lookup(0xf000 + c) : 0) : lookup;
426
410
  }
427
- function parseGPOSKerning(font, gpos) {
428
- const featureList = gpos + font.u16(gpos + 6);
429
- const lookupList = gpos + font.u16(gpos + 8);
430
- const lookups = new Set();
431
- for (let i = 0, count = font.u16(featureList); i < count; i++) {
432
- const record = featureList + 2 + i * 6;
433
- if (font.u32(record) !== 0x6b65726e)
434
- continue;
435
- const feature = featureList + font.u16(record + 4);
436
- for (let j = 0, n = font.u16(feature + 2); j < n; j++)
437
- lookups.add(font.u16(feature + 4 + j * 2));
438
- }
439
- const pairLookups = [];
440
- for (const index of [...lookups].sort((a, b) => a - b)) {
441
- if (index >= font.u16(lookupList))
442
- continue;
443
- const lookup = lookupList + font.u16(lookupList + 2 + index * 2);
444
- const type = font.u16(lookup);
445
- const subtables = [];
446
- for (let i = 0, count = font.u16(lookup + 4); i < count; i++) {
447
- let subtable = lookup + font.u16(lookup + 6 + i * 2);
448
- if (type === 9) {
449
- if (font.u16(subtable + 2) !== 2)
450
- continue;
451
- subtable += font.u32(subtable + 4);
452
- }
453
- else if (type !== 2)
454
- continue;
455
- const pair = parsePairPos(font, subtable);
456
- if (pair)
457
- subtables.push(pair);
411
+ function parseVariations(font, offset) {
412
+ let table = -1;
413
+ for (let i = 0, count = font.u16(offset + 2); i < count; i++) {
414
+ const record = offset + 4 + i * 8;
415
+ if (font.u16(record) === 0 && font.u16(record + 2) === 5) {
416
+ const t = offset + font.u32(record + 4);
417
+ if (font.u16(t) === 14)
418
+ table = t;
458
419
  }
459
- if (subtables.length)
460
- pairLookups.push(subtables);
461
420
  }
462
- if (!pairLookups.length)
421
+ if (table < 0)
463
422
  return null;
464
- return (left, right) => {
465
- let total = 0;
466
- for (const subtables of pairLookups) {
467
- for (const subtable of subtables) {
468
- const value = subtable(left, right);
469
- if (value === undefined)
470
- continue;
471
- total += value;
472
- break;
423
+ const u24 = (o) => font.u8(o) << 16 | font.u16(o + 1);
424
+ const records = font.u32(table + 6);
425
+ return (codepoint, selector) => {
426
+ let lo = 0, hi = records - 1;
427
+ while (lo <= hi) {
428
+ const mid = lo + hi >> 1, record = table + 10 + mid * 11, vs = u24(record);
429
+ if (vs < selector)
430
+ lo = mid + 1;
431
+ else if (vs > selector)
432
+ hi = mid - 1;
433
+ else {
434
+ const defaults = font.u32(record + 3), others = font.u32(record + 7);
435
+ if (others) {
436
+ const o = table + others;
437
+ let l = 0, h = font.u32(o) - 1;
438
+ while (l <= h) {
439
+ const m = l + h >> 1, u = u24(o + 4 + m * 5);
440
+ if (u < codepoint)
441
+ l = m + 1;
442
+ else if (u > codepoint)
443
+ h = m - 1;
444
+ else
445
+ return font.u16(o + 4 + m * 5 + 3);
446
+ }
447
+ }
448
+ if (defaults) {
449
+ const o = table + defaults;
450
+ let l = 0, h = font.u32(o) - 1;
451
+ while (l <= h) {
452
+ const m = l + h >> 1, start = u24(o + 4 + m * 4);
453
+ if (codepoint < start)
454
+ h = m - 1;
455
+ else if (codepoint > start + font.u8(o + 4 + m * 4 + 3))
456
+ l = m + 1;
457
+ else
458
+ return -1;
459
+ }
460
+ }
461
+ return 0;
473
462
  }
474
463
  }
475
- return total;
464
+ return 0;
476
465
  };
477
466
  }
478
- function valueRecord(format) {
479
- let size = 0, xAdvance = -1;
480
- for (let bit = 0; bit < 8; bit++) {
481
- if (!(format & 1 << bit))
482
- continue;
483
- if (bit === 2)
484
- xAdvance = size;
485
- size += 2;
486
- }
487
- return { size, xAdvance };
488
- }
489
- function parsePairPos(font, table) {
490
- const format = font.u16(table);
491
- const coverage = parseCoverage(font, table + font.u16(table + 2));
492
- const first = valueRecord(font.u16(table + 4));
493
- const second = valueRecord(font.u16(table + 6));
494
- if (first.xAdvance === -1)
495
- return null;
496
- if (format === 1) {
497
- const sets = font.u16(table + 8);
498
- const recordSize = 2 + first.size + second.size;
499
- return (left, right) => {
500
- const index = coverage(left);
501
- if (index === -1 || index >= sets)
502
- return undefined;
503
- const set = table + font.u16(table + 10 + index * 2);
504
- let lo = 0, hi = font.u16(set) - 1;
505
- while (lo <= hi) {
506
- const mid = lo + hi >> 1;
507
- const record = set + 2 + mid * recordSize;
508
- const glyph = font.u16(record);
509
- if (glyph < right)
510
- lo = mid + 1;
511
- else if (glyph > right)
512
- hi = mid - 1;
513
- else
514
- return font.i16(record + 2 + first.xAdvance);
515
- }
516
- return undefined;
517
- };
518
- }
519
- if (format === 2) {
520
- const classes1 = parseClassDef(font, table + font.u16(table + 8));
521
- const classes2 = parseClassDef(font, table + font.u16(table + 10));
522
- const class1Count = font.u16(table + 12), class2Count = font.u16(table + 14);
523
- const recordSize = first.size + second.size;
524
- return (left, right) => {
525
- if (coverage(left) === -1)
526
- return undefined;
527
- const c1 = classes1(left), c2 = classes2(right);
528
- if (c1 >= class1Count || c2 >= class2Count)
529
- return undefined;
530
- return font.i16(table + 16 + (c1 * class2Count + c2) * recordSize + first.xAdvance);
531
- };
532
- }
533
- return null;
534
- }
535
- function parseCoverage(font, table) {
467
+ export function parseCoverage(font, table) {
536
468
  const format = font.u16(table), count = font.u16(table + 2);
537
469
  return glyph => {
538
470
  let lo = 0, hi = count - 1;
@@ -560,7 +492,7 @@ function parseCoverage(font, table) {
560
492
  return -1;
561
493
  };
562
494
  }
563
- function parseClassDef(font, table) {
495
+ export function parseClassDef(font, table) {
564
496
  const format = font.u16(table);
565
497
  if (format === 1) {
566
498
  const start = font.u16(table + 2), count = font.u16(table + 4);
@@ -28,6 +28,7 @@ function makeZone(count) {
28
28
  };
29
29
  }
30
30
  const MAX_INSTRUCTIONS = 1_000_000;
31
+ const MAX_POINTS = 65536;
31
32
  export class Hinter {
32
33
  font;
33
34
  fpgm;
@@ -38,6 +39,8 @@ export class Hinter {
38
39
  maxTwilight;
39
40
  sizes = new Map();
40
41
  scaleFixed = 0;
42
+ loading = new Set();
43
+ points = 0;
41
44
  constructor(font) {
42
45
  this.font = font;
43
46
  const table = (tag) => {
@@ -98,6 +101,8 @@ export class Hinter {
98
101
  if (state.broken)
99
102
  return null;
100
103
  try {
104
+ this.loading.clear();
105
+ this.points = 0;
101
106
  const outline = this.load(glyph, state, 0);
102
107
  if (!outline)
103
108
  return { path: [], advance: Math.round(this.font.advance(glyph) * state.scale / 64) };
@@ -125,19 +130,23 @@ export class Hinter {
125
130
  load(glyph, size, depth) {
126
131
  if (depth > 8)
127
132
  throw new HintError('composite glyphs nested too deep');
133
+ if (this.loading.has(glyph))
134
+ throw new HintError('composite glyph containing itself');
128
135
  const font = this.font;
129
136
  const data = font.glyfPoints(glyph);
130
137
  if (!data)
131
138
  return null;
139
+ this.points += data.xs.length;
140
+ if (this.points > MAX_POINTS)
141
+ throw new HintError('too many points');
132
142
  const scale = size.scale, scaleFixed = this.scaleFixed = size.scaleFixed;
133
143
  const lsb = font.leftSideBearing(glyph), advance = font.advance(glyph);
134
144
  let count, zone;
135
145
  if (data.components) {
136
- const parts = [];
137
- const placed = [];
138
146
  let total = 0;
139
147
  const xsAll = [], ysAll = [], on = [], ends = [];
140
148
  let useMetrics = null;
149
+ this.loading.add(glyph);
141
150
  for (const component of data.components) {
142
151
  const part = this.load(component.glyph, size, depth + 1);
143
152
  if (!part)
@@ -161,7 +170,6 @@ export class Hinter {
161
170
  dy = Math.round(dy / 64) * 64;
162
171
  }
163
172
  }
164
- placed.push({ dx, dy });
165
173
  for (let i = 0; i < part.count; i++) {
166
174
  const x = part.zone.curX[i], y = part.zone.curY[i];
167
175
  xsAll.push(transformed ? a * x + c * y + dx : x + dx);
@@ -171,10 +179,10 @@ export class Hinter {
171
179
  for (const end of part.zone.ends)
172
180
  ends.push(end + total);
173
181
  total += part.count;
174
- parts.push(part);
175
182
  if (component.useMetrics)
176
183
  useMetrics = part;
177
184
  }
185
+ this.loading.delete(glyph);
178
186
  count = total;
179
187
  zone = makeZone(count + 4);
180
188
  zone.ends = ends;