imagescript 3.2.0 → 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 +22 -12
  5. package/dist/codecs/crc.js +5 -0
  6. package/dist/codecs/gif.js +30 -52
  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 +224 -113
  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 +80 -5
  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 +359 -79
  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
@@ -0,0 +1,362 @@
1
+ /*!
2
+ * The shaping buffer, ported from HarfBuzz (hb-buffer.hh, hb-buffer.cc)
3
+ * Copyright © 1998-2004 David Turner and Werner Lemberg, © 2004-2007,2009,2010 Red Hat, Inc., © 2011,2012 Google, Inc.
4
+ * Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
5
+ */
6
+ export const CATEGORY_NAMES = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf',
7
+ 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs'];
8
+ export const isMark = (category) => category === 10 || category === 11 || category === 12;
9
+ export class GlyphInfo {
10
+ codepoint = 0;
11
+ mask = 0;
12
+ cluster = 0;
13
+ unicodeProps = 0;
14
+ glyphProps = 0;
15
+ ligProps = 0;
16
+ syllable = 0;
17
+ category = 0;
18
+ position = 0;
19
+ glyphIndex = 0;
20
+ xAdvance = 0;
21
+ yAdvance = 0;
22
+ xOffset = 0;
23
+ yOffset = 0;
24
+ attachChain = 0;
25
+ attachType = 0;
26
+ clone() {
27
+ return Object.assign(new GlyphInfo(), this);
28
+ }
29
+ }
30
+ const MAX_OPS_FACTOR = 64;
31
+ const MAX_OPS_MIN = 16384;
32
+ const MAX_LEN_FACTOR = 32;
33
+ const MAX_LEN_MIN = 8192;
34
+ export class Buffer {
35
+ info = [];
36
+ outInfo = [];
37
+ idx = 0;
38
+ outLen = 0;
39
+ haveOutput = false;
40
+ successful = true;
41
+ direction = 4;
42
+ script = '';
43
+ serial = 0;
44
+ maxOps = 0;
45
+ maxLen = 0;
46
+ hasIgnorables = false;
47
+ hasContinuations = false;
48
+ hasCGJ = false;
49
+ hasSpaceFallback = false;
50
+ hasAttachment = false;
51
+ beginning = true;
52
+ context = [[], []];
53
+ hasStch = false;
54
+ hasBrokenSyllable = false;
55
+ get len() {
56
+ return this.info.length;
57
+ }
58
+ limit() {
59
+ this.maxOps = Math.max(this.len * MAX_OPS_FACTOR, MAX_OPS_MIN);
60
+ this.maxLen = Math.max(this.len * MAX_LEN_FACTOR, MAX_LEN_MIN);
61
+ }
62
+ cur(offset = 0) {
63
+ return this.info[this.idx + offset];
64
+ }
65
+ prev() {
66
+ return this.outInfo[this.outLen - 1];
67
+ }
68
+ backtrackLen() {
69
+ return this.haveOutput ? this.outLen : this.idx;
70
+ }
71
+ lookaheadLen() {
72
+ return this.len - this.idx;
73
+ }
74
+ nextSerial() {
75
+ this.serial = this.serial + 1 & 255;
76
+ if (!this.serial)
77
+ this.serial = 1;
78
+ return this.serial;
79
+ }
80
+ clearOutput() {
81
+ this.haveOutput = true;
82
+ this.idx = 0;
83
+ this.outLen = 0;
84
+ this.outInfo = [];
85
+ }
86
+ sync() {
87
+ if (this.successful)
88
+ this.nextGlyphs(this.len - this.idx);
89
+ if (this.successful) {
90
+ this.outInfo.length = this.outLen;
91
+ this.info = this.outInfo;
92
+ }
93
+ this.haveOutput = false;
94
+ this.outLen = 0;
95
+ this.outInfo = [];
96
+ this.idx = 0;
97
+ }
98
+ grow(count) {
99
+ if (this.outLen + count > this.maxLen) {
100
+ this.successful = false;
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+ nextGlyph() {
106
+ if (this.haveOutput)
107
+ this.outInfo[this.outLen++] = this.info[this.idx];
108
+ this.idx++;
109
+ }
110
+ nextGlyphs(n) {
111
+ if (this.haveOutput)
112
+ for (let i = 0; i < n; i++)
113
+ this.outInfo[this.outLen++] = this.info[this.idx + i];
114
+ this.idx += n;
115
+ }
116
+ skipGlyph() {
117
+ this.idx++;
118
+ }
119
+ replaceGlyphs(numIn, numOut, glyphs) {
120
+ if (!this.grow(numOut))
121
+ return;
122
+ this.mergeClusters(this.idx, this.idx + numIn);
123
+ const orig = this.idx < this.len ? this.cur() : this.prev();
124
+ for (let i = 0; i < numOut; i++) {
125
+ const info = orig.clone();
126
+ info.codepoint = glyphs[i];
127
+ this.outInfo[this.outLen++] = info;
128
+ }
129
+ this.idx += numIn;
130
+ }
131
+ replaceGlyph(glyph) {
132
+ this.replaceGlyphs(1, 1, [glyph]);
133
+ }
134
+ outputGlyph(glyph) {
135
+ this.replaceGlyphs(0, 1, [glyph]);
136
+ return this.prev();
137
+ }
138
+ outputInfo(info) {
139
+ if (!this.grow(1))
140
+ return;
141
+ this.outInfo[this.outLen++] = info;
142
+ }
143
+ copyGlyph() {
144
+ this.outputInfo(this.cur().clone());
145
+ }
146
+ moveTo(i) {
147
+ if (!this.haveOutput) {
148
+ this.idx = i;
149
+ return true;
150
+ }
151
+ if (!this.successful)
152
+ return false;
153
+ if (this.outLen < i) {
154
+ const count = i - this.outLen;
155
+ if ((this.maxOps -= count) < 0)
156
+ return this.successful = false;
157
+ for (let k = 0; k < count; k++)
158
+ this.outInfo[this.outLen++] = this.info[this.idx++];
159
+ }
160
+ else if (this.outLen > i) {
161
+ const count = this.outLen - i;
162
+ if ((this.maxOps -= count) < 0)
163
+ return this.successful = false;
164
+ if (this.idx < count) {
165
+ const shift = count - this.idx;
166
+ this.info.splice(this.idx, 0, ...Array.from({ length: shift }, () => new GlyphInfo()));
167
+ this.idx += shift;
168
+ }
169
+ this.idx -= count;
170
+ this.outLen -= count;
171
+ for (let k = 0; k < count; k++)
172
+ this.info[this.idx + k] = this.outInfo[this.outLen + k];
173
+ }
174
+ return true;
175
+ }
176
+ mergeClusters(start, end) {
177
+ if (end - start < 2)
178
+ return;
179
+ if ((this.maxOps -= end - start) < 0)
180
+ this.successful = false;
181
+ const info = this.info;
182
+ let cluster = info[start].cluster;
183
+ for (let i = start + 1; i < end; i++)
184
+ cluster = Math.min(cluster, info[i].cluster);
185
+ if (cluster !== info[end - 1].cluster)
186
+ while (end < this.len && info[end - 1].cluster === info[end].cluster)
187
+ end++;
188
+ if (cluster !== info[start].cluster)
189
+ while (this.idx < start && info[start - 1].cluster === info[start].cluster)
190
+ start--;
191
+ if (this.idx === start && info[start].cluster !== cluster)
192
+ for (let i = this.outLen; i && this.outInfo[i - 1].cluster === info[start].cluster; i--)
193
+ this.outInfo[i - 1].cluster = cluster;
194
+ for (let i = start; i < end; i++)
195
+ info[i].cluster = cluster;
196
+ }
197
+ mergeOutClusters(start, end) {
198
+ if (end - start < 2)
199
+ return;
200
+ if ((this.maxOps -= end - start) < 0)
201
+ this.successful = false;
202
+ const out = this.outInfo;
203
+ let cluster = out[start].cluster;
204
+ for (let i = start + 1; i < end; i++)
205
+ cluster = Math.min(cluster, out[i].cluster);
206
+ while (start && out[start - 1].cluster === out[start].cluster)
207
+ start--;
208
+ while (end < this.outLen && out[end - 1].cluster === out[end].cluster)
209
+ end++;
210
+ if (end === this.outLen)
211
+ for (let i = this.idx; i < this.len && this.info[i].cluster === out[end - 1].cluster; i++)
212
+ this.info[i].cluster = cluster;
213
+ for (let i = start; i < end; i++)
214
+ out[i].cluster = cluster;
215
+ }
216
+ deleteGlyph() {
217
+ const cluster = this.info[this.idx].cluster;
218
+ if (!(this.idx + 1 < this.len && cluster === this.info[this.idx + 1].cluster || this.outLen && cluster === this.outInfo[this.outLen - 1].cluster)) {
219
+ if (this.outLen) {
220
+ if (cluster < this.outInfo[this.outLen - 1].cluster) {
221
+ const old = this.outInfo[this.outLen - 1].cluster;
222
+ for (let i = this.outLen; i && this.outInfo[i - 1].cluster === old; i--)
223
+ this.outInfo[i - 1].cluster = cluster;
224
+ }
225
+ }
226
+ else if (this.idx + 1 < this.len)
227
+ this.mergeClusters(this.idx, this.idx + 2);
228
+ }
229
+ this.skipGlyph();
230
+ }
231
+ deleteGlyphsInplace(filter) {
232
+ const info = this.info;
233
+ let j = 0;
234
+ const count = info.length;
235
+ for (let i = 0; i < count; i++) {
236
+ if (filter(info[i])) {
237
+ const cluster = info[i].cluster;
238
+ if (i + 1 < count && cluster === info[i + 1].cluster)
239
+ continue;
240
+ if (j) {
241
+ if (cluster < info[j - 1].cluster) {
242
+ const old = info[j - 1].cluster;
243
+ for (let k = j; k && info[k - 1].cluster === old; k--)
244
+ info[k - 1].cluster = cluster;
245
+ }
246
+ continue;
247
+ }
248
+ if (i + 1 < count)
249
+ this.mergeClusters(i, i + 2);
250
+ continue;
251
+ }
252
+ info[j++] = info[i];
253
+ }
254
+ info.length = j;
255
+ }
256
+ sort(start, end, compare) {
257
+ const info = this.info;
258
+ for (let i = start + 1; i < end; i++) {
259
+ let j = i;
260
+ while (j > start && compare(info[j - 1], info[i]) > 0)
261
+ j--;
262
+ if (i === j)
263
+ continue;
264
+ this.mergeClusters(j, i + 1);
265
+ const t = info[i];
266
+ for (let k = i; k > j; k--)
267
+ info[k] = info[k - 1];
268
+ info[j] = t;
269
+ }
270
+ }
271
+ reverseRange(start, end) {
272
+ const info = this.info;
273
+ for (let i = start, j = end - 1; i < j; i++, j--) {
274
+ const t = info[i];
275
+ info[i] = info[j];
276
+ info[j] = t;
277
+ }
278
+ }
279
+ reverse() {
280
+ this.reverseRange(0, this.len);
281
+ }
282
+ reverseGroups(group) {
283
+ if (!this.len)
284
+ return;
285
+ let start = 0, i = 1;
286
+ for (; i < this.len; i++) {
287
+ if (!group(this.info[i - 1], this.info[i])) {
288
+ this.reverseRange(start, i);
289
+ start = i;
290
+ }
291
+ }
292
+ this.reverseRange(start, i);
293
+ this.reverse();
294
+ }
295
+ groupEnd(start, group) {
296
+ while (++start < this.len && group(this.info[start - 1], this.info[start]))
297
+ ;
298
+ return start;
299
+ }
300
+ setMasks(value, mask, clusterStart, clusterEnd) {
301
+ if (!mask)
302
+ return;
303
+ value &= mask;
304
+ for (const info of this.info)
305
+ if (clusterStart <= info.cluster && info.cluster < clusterEnd)
306
+ info.mask = info.mask & ~mask | value;
307
+ }
308
+ resetMasks(mask) {
309
+ for (const info of this.info)
310
+ info.mask = mask;
311
+ }
312
+ addMasks(mask) {
313
+ for (const info of this.info)
314
+ info.mask |= mask;
315
+ }
316
+ }
317
+ export const generalCategory = (info) => info.unicodeProps & 31;
318
+ export const setGeneralCategory = (info, category) => {
319
+ info.unicodeProps = category | info.unicodeProps & (0xff & ~31);
320
+ };
321
+ export const isUnicodeMark = (info) => isMark(generalCategory(info));
322
+ export const combiningClass = (info) => isUnicodeMark(info) ? info.unicodeProps >> 8 : 0;
323
+ export const setCombiningClass = (info, cc) => {
324
+ if (isUnicodeMark(info))
325
+ info.unicodeProps = cc << 8 | info.unicodeProps & 0xff;
326
+ };
327
+ export const isUnicodeSpace = (info) => generalCategory(info) === 29;
328
+ export const spaceFallback = (info) => isUnicodeSpace(info) ? info.unicodeProps >> 8 : 0;
329
+ export const setSpaceFallback = (info, type) => {
330
+ if (isUnicodeSpace(info))
331
+ info.unicodeProps = type << 8 | info.unicodeProps & 0xff;
332
+ };
333
+ export const isDefaultIgnorable = (info) => (info.unicodeProps & 32) !== 0 && !(info.glyphProps & 16);
334
+ export const isHidden = (info) => (info.unicodeProps & 64) !== 0;
335
+ export const isContinuation = (info) => (info.unicodeProps & 128) !== 0;
336
+ const isFormat = (info) => generalCategory(info) === 1;
337
+ export const isZWNJ = (info) => isFormat(info) && (info.unicodeProps & 512) !== 0;
338
+ export const isZWJ = (info) => isFormat(info) && (info.unicodeProps & 256) !== 0;
339
+ export const IS_LIG_BASE = 0x10;
340
+ export const ligId = (info) => info.ligProps >> 5;
341
+ const ligatedInternal = (info) => (info.ligProps & IS_LIG_BASE) !== 0;
342
+ export const ligComp = (info) => ligatedInternal(info) ? 0 : info.ligProps & 0x0f;
343
+ export const ligNumComps = (info) => info.glyphProps & 4 && ligatedInternal(info) ? info.ligProps & 0x0f : 1;
344
+ export const setLigPropsForLigature = (info, id, count) => {
345
+ info.ligProps = id << 5 | IS_LIG_BASE | count & 0x0f;
346
+ };
347
+ export const setLigPropsForMark = (info, id, comp) => {
348
+ info.ligProps = id << 5 | comp & 0x0f;
349
+ };
350
+ export const isBaseGlyph = (info) => (info.glyphProps & 2) !== 0;
351
+ export const isLigature = (info) => (info.glyphProps & 4) !== 0;
352
+ export const isMarkGlyph = (info) => (info.glyphProps & 8) !== 0;
353
+ export const isSubstituted = (info) => (info.glyphProps & 16) !== 0;
354
+ export const isLigated = (info) => (info.glyphProps & 32) !== 0;
355
+ export const isMultiplied = (info) => (info.glyphProps & 64) !== 0;
356
+ export const ligNumCompsInLigation = (info) => isMultiplied(info) && ligComp(info) ? 0 : ligNumComps(info);
357
+ export const allocateLigId = (buffer) => {
358
+ let id = buffer.nextSerial() & 7;
359
+ while (!id)
360
+ id = buffer.nextSerial() & 7;
361
+ return id;
362
+ };
@@ -0,0 +1,164 @@
1
+ export function parsePalette(font, cpal) {
2
+ const entries = font.u16(cpal + 2), records = cpal + font.u32(cpal + 8), first = font.u16(cpal + 12);
3
+ return index => {
4
+ if (index === 0xffff || index >= entries)
5
+ return null;
6
+ const o = records + (first + index) * 4;
7
+ return (font.u8(o + 2) << 24 | font.u8(o + 1) << 16 | font.u8(o) << 8 | font.u8(o + 3)) >>> 0;
8
+ };
9
+ }
10
+ export function parseColorLayers(font) {
11
+ const colr = font.tables.COLR?.offset, cpal = font.tables.CPAL?.offset;
12
+ if (colr === undefined || cpal === undefined)
13
+ return null;
14
+ const baseCount = font.u16(colr + 2), bases = colr + font.u32(colr + 4), layers = colr + font.u32(colr + 8);
15
+ if (!baseCount)
16
+ return null;
17
+ const color = parsePalette(font, cpal);
18
+ const cache = new Map();
19
+ return glyph => {
20
+ let result = cache.get(glyph);
21
+ if (result !== undefined)
22
+ return result;
23
+ result = null;
24
+ for (let lo = 0, hi = baseCount - 1; lo <= hi;) {
25
+ const mid = lo + hi >> 1, record = bases + mid * 6, id = font.u16(record);
26
+ if (id < glyph)
27
+ lo = mid + 1;
28
+ else if (id > glyph)
29
+ hi = mid - 1;
30
+ else {
31
+ const start = font.u16(record + 2), count = font.u16(record + 4);
32
+ result = [];
33
+ for (let i = 0; i < count; i++) {
34
+ const layer = layers + (start + i) * 4;
35
+ result.push({ glyph: font.u16(layer), color: color(font.u16(layer + 2)) });
36
+ }
37
+ break;
38
+ }
39
+ }
40
+ cache.set(glyph, result);
41
+ return result;
42
+ };
43
+ }
44
+ export function parseBitmaps(font, size) {
45
+ return parseCBDT(font, size) ?? parseSbix(font, size);
46
+ }
47
+ function closestStrike(ppems, size) {
48
+ let best = -1;
49
+ for (const [i, ppem] of ppems.entries()) {
50
+ const current = best < 0 ? 0 : ppems[best];
51
+ if (best < 0 || (current < size ? ppem > current : ppem >= size && ppem < current))
52
+ best = i;
53
+ }
54
+ return best;
55
+ }
56
+ function parseSbix(font, size) {
57
+ const sbix = font.tables.sbix?.offset;
58
+ if (sbix === undefined)
59
+ return null;
60
+ const strikes = Array.from({ length: font.u32(sbix + 4) }, (_, i) => sbix + font.u32(sbix + 8 + i * 4));
61
+ const index = closestStrike(strikes.map(strike => font.u16(strike)), size);
62
+ if (index < 0)
63
+ return null;
64
+ const strike = strikes[index], ppem = font.u16(strike);
65
+ const cache = new Map();
66
+ const lookup = (glyph, depth) => {
67
+ if (glyph >= font.numGlyphs || depth > 4)
68
+ return null;
69
+ const start = strike + font.u32(strike + 4 + glyph * 4), end = strike + font.u32(strike + 8 + glyph * 4);
70
+ if (end - start < 8)
71
+ return null;
72
+ const type = font.u32(start + 4);
73
+ if (type === 0x64757065)
74
+ return lookup(font.u16(start + 8), depth + 1);
75
+ if (type !== 0x706e6720)
76
+ return null;
77
+ const png = font.data.subarray(start + 8, end);
78
+ if (png.length < 24)
79
+ return null;
80
+ const header = new DataView(png.buffer, png.byteOffset + 16, 8), height = header.getUint32(4);
81
+ return { png, ppem, width: header.getUint32(0), height, left: font.i16(start), top: font.i16(start + 2) + height };
82
+ };
83
+ return glyph => {
84
+ let result = cache.get(glyph);
85
+ if (result === undefined)
86
+ cache.set(glyph, result = lookup(glyph, 0));
87
+ return result;
88
+ };
89
+ }
90
+ function parseCBDT(font, size) {
91
+ const cblc = font.tables.CBLC?.offset, cbdt = font.tables.CBDT?.offset;
92
+ if (cblc === undefined || cbdt === undefined)
93
+ return null;
94
+ const ppems = Array.from({ length: font.u32(cblc + 4) }, (_, i) => font.u8(cblc + 8 + i * 48 + 45));
95
+ const index = closestStrike(ppems, size);
96
+ if (index < 0)
97
+ return null;
98
+ const strike = cblc + 8 + index * 48, strikePpem = ppems[index];
99
+ const array = cblc + font.u32(strike), subtables = font.u32(strike + 8);
100
+ const locate = (glyph) => {
101
+ for (let i = 0; i < subtables; i++) {
102
+ const entry = array + i * 8, firstGlyph = font.u16(entry), lastGlyph = font.u16(entry + 2);
103
+ if (glyph < firstGlyph || glyph > lastGlyph)
104
+ continue;
105
+ const header = array + font.u32(entry + 4);
106
+ const indexFormat = font.u16(header), format = font.u16(header + 2), data = cbdt + font.u32(header + 4), k = glyph - firstGlyph;
107
+ switch (indexFormat) {
108
+ case 1: {
109
+ const start = font.u32(header + 8 + k * 4), end = font.u32(header + 12 + k * 4);
110
+ return end > start ? { offset: data + start, format, metrics: -1 } : null;
111
+ }
112
+ case 2: return { offset: data + k * font.u32(header + 8), format, metrics: header + 12 };
113
+ case 3: {
114
+ const start = font.u16(header + 8 + k * 2), end = font.u16(header + 10 + k * 2);
115
+ return end > start ? { offset: data + start, format, metrics: -1 } : null;
116
+ }
117
+ case 4: {
118
+ const count = font.u32(header + 8);
119
+ for (let j = 0; j < count; j++) {
120
+ const pair = header + 12 + j * 4;
121
+ if (font.u16(pair) === glyph)
122
+ return { offset: data + font.u16(pair + 2), format, metrics: -1 };
123
+ }
124
+ return null;
125
+ }
126
+ case 5: {
127
+ const size = font.u32(header + 8), count = font.u32(header + 20);
128
+ for (let j = 0; j < count; j++)
129
+ if (font.u16(header + 24 + j * 2) === glyph)
130
+ return { offset: data + j * size, format, metrics: header + 12 };
131
+ return null;
132
+ }
133
+ default: return null;
134
+ }
135
+ }
136
+ return null;
137
+ };
138
+ const cache = new Map();
139
+ return glyph => {
140
+ let result = cache.get(glyph);
141
+ if (result !== undefined)
142
+ return result;
143
+ result = null;
144
+ const location = locate(glyph);
145
+ if (location) {
146
+ const { format } = location;
147
+ let o = location.offset, metrics = location.metrics;
148
+ if (format === 17) {
149
+ metrics = o;
150
+ o += 5;
151
+ }
152
+ else if (format === 18) {
153
+ metrics = o;
154
+ o += 8;
155
+ }
156
+ if ((format === 17 || format === 18 || format === 19) && metrics >= 0) {
157
+ const length = font.u32(o);
158
+ result = { png: font.data.subarray(o + 4, o + 4 + length), ppem: strikePpem, width: font.u8(metrics + 1), height: font.u8(metrics), left: font.i8(metrics + 2), top: font.i8(metrics + 3) };
159
+ }
160
+ }
161
+ cache.set(glyph, result);
162
+ return result;
163
+ };
164
+ }