imagescript 3.2.1 → 3.4.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 (101) hide show
  1. package/README.md +35 -10
  2. package/THIRD_PARTY_NOTICES.md +298 -2
  3. package/dist/animation.d.ts +25 -8
  4. package/dist/animation.js +44 -13
  5. package/dist/codecs/av1/cdef.js +197 -0
  6. package/dist/codecs/av1/data.js +141 -0
  7. package/dist/codecs/av1/decode.js +1769 -0
  8. package/dist/codecs/av1/filmgrain.js +219 -0
  9. package/dist/codecs/av1/frame.js +114 -0
  10. package/dist/codecs/av1/ipred.js +491 -0
  11. package/dist/codecs/av1/itx.js +950 -0
  12. package/dist/codecs/av1/loopfilter.js +149 -0
  13. package/dist/codecs/av1/msac.js +161 -0
  14. package/dist/codecs/av1/obu.js +516 -0
  15. package/dist/codecs/av1/postfilter.js +28 -0
  16. package/dist/codecs/av1/restoration.js +193 -0
  17. package/dist/codecs/av1/tables.js +160 -0
  18. package/dist/codecs/avif.js +581 -0
  19. package/dist/codecs/bmp.js +317 -0
  20. package/dist/codecs/crc.js +5 -0
  21. package/dist/codecs/gif.js +10 -35
  22. package/dist/codecs/hdr.d.ts +25 -0
  23. package/dist/codecs/hdr.js +201 -0
  24. package/dist/codecs/ico.js +48 -0
  25. package/dist/codecs/jpeg.js +11 -28
  26. package/dist/codecs/jxl/bits.js +126 -0
  27. package/dist/codecs/jxl/blend.js +117 -0
  28. package/dist/codecs/jxl/color.js +116 -0
  29. package/dist/codecs/jxl/dct.js +404 -0
  30. package/dist/codecs/jxl/entropy.js +556 -0
  31. package/dist/codecs/jxl/features.js +325 -0
  32. package/dist/codecs/jxl/filters.js +119 -0
  33. package/dist/codecs/jxl/frame-header.js +195 -0
  34. package/dist/codecs/jxl/frame.js +319 -0
  35. package/dist/codecs/jxl/headers.js +249 -0
  36. package/dist/codecs/jxl/idct.js +484 -0
  37. package/dist/codecs/jxl/modular.js +688 -0
  38. package/dist/codecs/jxl/quant.js +219 -0
  39. package/dist/codecs/jxl/upsample.js +52 -0
  40. package/dist/codecs/jxl/vardct.js +472 -0
  41. package/dist/codecs/jxl.js +89 -0
  42. package/dist/codecs/netpbm.js +239 -0
  43. package/dist/codecs/png.js +22 -10
  44. package/dist/codecs/qoi.js +150 -0
  45. package/dist/codecs/tga.js +3 -1
  46. package/dist/codecs/tiff.js +7 -2
  47. package/dist/codecs/vp8-enc-dsp.js +932 -0
  48. package/dist/codecs/vp8-enc-tables.js +78 -0
  49. package/dist/codecs/vp8-enc.js +1930 -0
  50. package/dist/codecs/vp8.js +3 -1
  51. package/dist/codecs/vp8l.js +126 -58
  52. package/dist/codecs/webp.js +47 -11
  53. package/dist/codecs/zlib.js +81 -69
  54. package/dist/frame.d.ts +1 -0
  55. package/dist/frame.js +4 -2
  56. package/dist/gif.d.ts +1 -0
  57. package/dist/gif.js +2 -2
  58. package/dist/image-type.d.ts +15 -1
  59. package/dist/image-type.js +45 -2
  60. package/dist/image.d.ts +89 -13
  61. package/dist/image.js +266 -113
  62. package/dist/index.d.ts +3 -2
  63. package/dist/index.js +4 -4
  64. package/dist/render/bidi.js +350 -0
  65. package/dist/render/buffer.js +362 -0
  66. package/dist/render/color.js +164 -0
  67. package/dist/render/colr.js +523 -0
  68. package/dist/render/font.js +173 -241
  69. package/dist/render/hinting.js +38 -22
  70. package/dist/render/layout.js +1553 -0
  71. package/dist/render/linebreak.js +210 -0
  72. package/dist/render/segment.js +106 -0
  73. package/dist/render/shape.js +984 -0
  74. package/dist/render/shapers/arabic.js +284 -0
  75. package/dist/render/shapers/hangul.js +142 -0
  76. package/dist/render/shapers/hebrew.js +49 -0
  77. package/dist/render/shapers/index.js +52 -0
  78. package/dist/render/shapers/indic.js +579 -0
  79. package/dist/render/shapers/khmer.js +104 -0
  80. package/dist/render/shapers/machine.js +129 -0
  81. package/dist/render/shapers/myanmar.js +121 -0
  82. package/dist/render/shapers/syllabic.js +100 -0
  83. package/dist/render/shapers/thai.js +125 -0
  84. package/dist/render/shapers/use.js +235 -0
  85. package/dist/render/svg.js +102 -23
  86. package/dist/render/text.js +373 -103
  87. package/dist/render/unicode-data.js +63 -0
  88. package/dist/render/unicode.js +50 -0
  89. package/dist/text-layout.d.ts +4 -1
  90. package/dist/text-layout.js +2 -2
  91. package/dist/util/alpha.js +43 -0
  92. package/dist/util/blur.js +20 -33
  93. package/dist/util/bytes.js +40 -0
  94. package/dist/util/limits.js +34 -0
  95. package/dist/util/options.js +7 -0
  96. package/dist/util/resample.js +126 -7
  97. package/dist/util/rotate.js +43 -0
  98. package/dist/version.js +1 -1
  99. package/licenses/HarfBuzz.txt +42 -0
  100. package/licenses/Unicode-3.0.txt +39 -0
  101. package/package.json +25 -2
@@ -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);
@@ -12,6 +12,12 @@ class SkipInstruction extends Error {
12
12
  }
13
13
  const roundHalfAway = (v) => v < 0 ? -Math.round(-v) : Math.round(v);
14
14
  const mulFix = (a, b) => roundHalfAway(a * b / 65536);
15
+ const mulDiv = (a, b, c) => {
16
+ if (!c)
17
+ return a * b < 0 ? -0x7fffffff : 0x7fffffff;
18
+ const d = Math.floor((Math.abs(a * b) + Math.abs(c) / 2) / Math.abs(c));
19
+ return a * b * c < 0 ? -d : d;
20
+ };
15
21
  const divFix = (a, b) => b === 0 ? 0 : roundHalfAway(a * 65536 / b);
16
22
  const ROUND_GRID = { period: 64, phase: 0, threshold: 32 };
17
23
  const defaultState = () => ({
@@ -28,6 +34,7 @@ function makeZone(count) {
28
34
  };
29
35
  }
30
36
  const MAX_INSTRUCTIONS = 1_000_000;
37
+ const MAX_POINTS = 65536;
31
38
  export class Hinter {
32
39
  font;
33
40
  fpgm;
@@ -38,6 +45,8 @@ export class Hinter {
38
45
  maxTwilight;
39
46
  sizes = new Map();
40
47
  scaleFixed = 0;
48
+ loading = new Set();
49
+ points = 0;
41
50
  constructor(font) {
42
51
  this.font = font;
43
52
  const table = (tag) => {
@@ -98,6 +107,8 @@ export class Hinter {
98
107
  if (state.broken)
99
108
  return null;
100
109
  try {
110
+ this.loading.clear();
111
+ this.points = 0;
101
112
  const outline = this.load(glyph, state, 0);
102
113
  if (!outline)
103
114
  return { path: [], advance: Math.round(this.font.advance(glyph) * state.scale / 64) };
@@ -125,19 +136,23 @@ export class Hinter {
125
136
  load(glyph, size, depth) {
126
137
  if (depth > 8)
127
138
  throw new HintError('composite glyphs nested too deep');
139
+ if (this.loading.has(glyph))
140
+ throw new HintError('composite glyph containing itself');
128
141
  const font = this.font;
129
142
  const data = font.glyfPoints(glyph);
130
143
  if (!data)
131
144
  return null;
145
+ this.points += data.xs.length;
146
+ if (this.points > MAX_POINTS)
147
+ throw new HintError('too many points');
132
148
  const scale = size.scale, scaleFixed = this.scaleFixed = size.scaleFixed;
133
149
  const lsb = font.leftSideBearing(glyph), advance = font.advance(glyph);
134
150
  let count, zone;
135
151
  if (data.components) {
136
- const parts = [];
137
- const placed = [];
138
152
  let total = 0;
139
153
  const xsAll = [], ysAll = [], on = [], ends = [];
140
154
  let useMetrics = null;
155
+ this.loading.add(glyph);
141
156
  for (const component of data.components) {
142
157
  const part = this.load(component.glyph, size, depth + 1);
143
158
  if (!part)
@@ -161,7 +176,6 @@ export class Hinter {
161
176
  dy = Math.round(dy / 64) * 64;
162
177
  }
163
178
  }
164
- placed.push({ dx, dy });
165
179
  for (let i = 0; i < part.count; i++) {
166
180
  const x = part.zone.curX[i], y = part.zone.curY[i];
167
181
  xsAll.push(transformed ? a * x + c * y + dx : x + dx);
@@ -171,10 +185,10 @@ export class Hinter {
171
185
  for (const end of part.zone.ends)
172
186
  ends.push(end + total);
173
187
  total += part.count;
174
- parts.push(part);
175
188
  if (component.useMetrics)
176
189
  useMetrics = part;
177
190
  }
191
+ this.loading.delete(glyph);
178
192
  count = total;
179
193
  zone = makeZone(count + 4);
180
194
  zone.ends = ends;
@@ -339,10 +353,10 @@ class Execution {
339
353
  }
340
354
  lineVector(p1, p2, perpendicular, original) {
341
355
  const z1 = this.zone(this.gs.zp1), z2 = this.zone(this.gs.zp2);
342
- this.point(z1, p2);
343
- this.point(z2, p1);
344
- let x = (original ? z1.orgX[p2] - z2.orgX[p1] : z1.curX[p2] - z2.curX[p1]);
345
- let y = (original ? z1.orgY[p2] - z2.orgY[p1] : z1.curY[p2] - z2.curY[p1]);
356
+ this.point(z1, p1);
357
+ this.point(z2, p2);
358
+ let x = (original ? z1.orgX[p1] - z2.orgX[p2] : z1.curX[p1] - z2.curX[p2]);
359
+ let y = (original ? z1.orgY[p1] - z2.orgY[p2] : z1.curY[p1] - z2.curY[p2]);
346
360
  if (x === 0 && y === 0) {
347
361
  x = 1;
348
362
  y = 0;
@@ -467,18 +481,19 @@ class Execution {
467
481
  [a0, a1].forEach(i => this.point(za, i));
468
482
  [b0, b1].forEach(i => this.point(zb, i));
469
483
  this.point(z, p);
470
- const ax0 = za.curX[a0], ay0 = za.curY[a0], ax1 = za.curX[a1], ay1 = za.curY[a1];
471
- const bx0 = zb.curX[b0], by0 = zb.curY[b0], bx1 = zb.curX[b1], by1 = zb.curY[b1];
484
+ const ax0 = Math.round(za.curX[a0]), ay0 = Math.round(za.curY[a0]), ax1 = Math.round(za.curX[a1]), ay1 = Math.round(za.curY[a1]);
485
+ const bx0 = Math.round(zb.curX[b0]), by0 = Math.round(zb.curY[b0]), bx1 = Math.round(zb.curX[b1]), by1 = Math.round(zb.curY[b1]);
472
486
  const dax = ax1 - ax0, day = ay1 - ay0, dbx = bx1 - bx0, dby = by1 - by0;
473
- const det = dax * dby - day * dbx;
474
- if (Math.abs(det) < 1e-9) {
475
- z.curX[p] = (ax0 + ax1 + bx0 + bx1) / 4;
476
- z.curY[p] = (ay0 + ay1 + by0 + by1) / 4;
487
+ const discriminant = mulDiv(dax, -dby, 64) + mulDiv(day, dbx, 64);
488
+ const dotProduct = mulDiv(dax, dbx, 64) + mulDiv(day, dby, 64);
489
+ if (19 * Math.abs(discriminant) > Math.abs(dotProduct)) {
490
+ const v = mulDiv(bx0 - ax0, -dby, 64) + mulDiv(by0 - ay0, dbx, 64);
491
+ z.curX[p] = ax0 + mulDiv(v, dax, discriminant);
492
+ z.curY[p] = ay0 + mulDiv(v, day, discriminant);
477
493
  }
478
494
  else {
479
- const t = ((bx0 - ax0) * dby - (by0 - ay0) * dbx) / det;
480
- z.curX[p] = ax0 + t * dax;
481
- z.curY[p] = ay0 + t * day;
495
+ z.curX[p] = Math.trunc((ax0 + ax1 + bx0 + bx1) / 4);
496
+ z.curY[p] = Math.trunc((ay0 + ay1 + by0 + by1) / 4);
482
497
  }
483
498
  z.touchX[p] = z.touchY[p] = 1;
484
499
  break;
@@ -532,11 +547,12 @@ class Execution {
532
547
  case 0x76:
533
548
  case 0x77: {
534
549
  const n = this.pop();
535
- const grid = op === 0x76 ? 64 : 64 / Math.SQRT2;
536
- const period = [grid / 2, grid, grid * 2, grid][n >> 6 & 3];
537
- const phase = [0, period / 4, period / 2, period * 3 / 4][n >> 4 & 3];
550
+ const grid = op === 0x76 ? 0x4000 : 0x2d41;
551
+ const period = [grid >> 1, grid, grid * 2, grid][n >> 6 & 3];
552
+ const phase = [0, period >> 2, period >> 1, Math.trunc(period * 3 / 4)][n >> 4 & 3];
538
553
  const t = n & 15;
539
- gs.round = { period, phase, threshold: t === 0 ? period - 1 : (t - 4) * period / 8 };
554
+ const threshold = t === 0 ? period - 1 : Math.trunc((t - 4) * period / 8);
555
+ gs.round = { period: period >> 8, phase: phase >> 8, threshold: threshold >> 8 };
540
556
  break;
541
557
  }
542
558
  case 0x1a:
@@ -1016,7 +1032,7 @@ class Execution {
1016
1032
  const z0 = this.zone(gs.zp0), z1 = this.zone(gs.zp1);
1017
1033
  this.point(z0, p2);
1018
1034
  this.point(z1, p1);
1019
- const d = this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1]) / 2;
1035
+ const d = Math.trunc(Math.round(this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1])) / 2);
1020
1036
  this.move(z1, p1, d);
1021
1037
  this.move(z0, p2, -d);
1022
1038
  break;