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
@@ -0,0 +1,1553 @@
1
+ /*!
2
+ * OpenType layout (GDEF, GSUB, GPOS and their feature map), ported from HarfBuzz (hb-ot-layout*, hb-ot-map*,
3
+ * OT/Layout/GSUB/*, OT/Layout/GPOS/*)
4
+ * Copyright © 2007,2008,2009,2010 Red Hat, Inc., © 2010,2012,2013 Google, Inc., Old MIT license (see
5
+ * THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
6
+ */
7
+ import { parseCoverage, parseClassDef } from "./font.js";
8
+ import { GlyphInfo, allocateLigId, generalCategory, setGeneralCategory, isDefaultIgnorable, isHidden, isZWJ, isZWNJ, isMarkGlyph, isBaseGlyph, isLigature, ligId, ligComp, ligNumComps, ligNumCompsInLigation, setLigPropsForLigature, setLigPropsForMark, isMultiplied, } from "./buffer.js";
9
+ const MAX_NESTING_LEVEL = 64;
10
+ const MAX_CONTEXT_LENGTH = 64;
11
+ const tagAt = (font, offset) => String.fromCharCode(font.u8(offset), font.u8(offset + 1), font.u8(offset + 2), font.u8(offset + 3));
12
+ const covered = (font, offset) => {
13
+ const coverage = parseCoverage(font, offset);
14
+ return glyph => coverage(glyph) >= 0;
15
+ };
16
+ export class GDEF {
17
+ hasGlyphClasses;
18
+ glyphClass = () => 0;
19
+ markAttachClass = () => 0;
20
+ markSets = [];
21
+ cache = new Map();
22
+ constructor(font) {
23
+ const gdef = font.tables.GDEF?.offset;
24
+ this.hasGlyphClasses = gdef !== undefined && font.u16(gdef + 4) !== 0;
25
+ if (gdef === undefined)
26
+ return;
27
+ if (font.u16(gdef + 4))
28
+ this.glyphClass = parseClassDef(font, gdef + font.u16(gdef + 4));
29
+ if (font.u16(gdef + 10))
30
+ this.markAttachClass = parseClassDef(font, gdef + font.u16(gdef + 10));
31
+ const version = font.u32(gdef);
32
+ if (version >= 0x00010002 && font.u16(gdef + 12)) {
33
+ const sets = gdef + font.u16(gdef + 12);
34
+ for (let i = 0, count = font.u16(sets + 2); i < count; i++)
35
+ this.markSets.push(covered(font, sets + font.u32(sets + 4 + i * 4)));
36
+ }
37
+ }
38
+ glyphProps(glyph) {
39
+ let props = this.cache.get(glyph);
40
+ if (props !== undefined)
41
+ return props;
42
+ switch (this.glyphClass(glyph)) {
43
+ case 1:
44
+ props = 2;
45
+ break;
46
+ case 2:
47
+ props = 4;
48
+ break;
49
+ case 3:
50
+ props = 8 | this.markAttachClass(glyph) << 8;
51
+ break;
52
+ default: props = 0;
53
+ }
54
+ this.cache.set(glyph, props);
55
+ return props;
56
+ }
57
+ markSetCovers(set, glyph) {
58
+ return this.markSets[set](glyph);
59
+ }
60
+ sanitizeLookupProps(props) {
61
+ if (props & 16 && props >>> 16 >= this.markSets.length)
62
+ props &= ~16;
63
+ return props;
64
+ }
65
+ }
66
+ class SkippingIterator {
67
+ c;
68
+ contextMatch;
69
+ idx = 0;
70
+ end = 0;
71
+ lookupProps = 0;
72
+ mask = -1;
73
+ ignoreZWNJ = false;
74
+ ignoreZWJ = false;
75
+ ignoreHidden = false;
76
+ perSyllable = false;
77
+ syllable = 0;
78
+ matchFunc = null;
79
+ data = null;
80
+ dataIndex = 0;
81
+ constructor(c, contextMatch) {
82
+ this.c = c;
83
+ this.contextMatch = contextMatch;
84
+ }
85
+ init() {
86
+ const c = this.c;
87
+ this.end = c.buffer.len;
88
+ this.matchFunc = null;
89
+ this.data = null;
90
+ this.lookupProps = c.lookupProps;
91
+ this.ignoreZWNJ = c.tableIndex === 1 || this.contextMatch && c.autoZWNJ;
92
+ this.ignoreZWJ = this.contextMatch || c.autoZWJ;
93
+ this.ignoreHidden = c.tableIndex === 1;
94
+ this.mask = this.contextMatch ? -1 : c.lookupMask;
95
+ this.perSyllable = c.tableIndex === 0 && c.perSyllable;
96
+ this.syllable = 0;
97
+ }
98
+ setMatchFunc(func, data) {
99
+ this.matchFunc = func;
100
+ this.data = data;
101
+ this.dataIndex = 0;
102
+ }
103
+ reset(start) {
104
+ this.idx = start;
105
+ this.end = this.c.buffer.len;
106
+ this.syllable = this.c.buffer.cur().syllable;
107
+ }
108
+ resetBack(start) {
109
+ this.idx = start;
110
+ this.syllable = this.c.buffer.cur().syllable;
111
+ }
112
+ resetFast(start) {
113
+ this.idx = start;
114
+ }
115
+ maySkip(info) {
116
+ if (!this.c.checkGlyphProperty(info, this.lookupProps))
117
+ return 1;
118
+ if (isDefaultIgnorable(info) && (this.ignoreZWNJ || !isZWNJ(info)) && (this.ignoreZWJ || !isZWJ(info)) && (this.ignoreHidden || !isHidden(info)))
119
+ return 2;
120
+ return 0;
121
+ }
122
+ mayMatch(info) {
123
+ if (!(info.mask & this.mask) || this.perSyllable && this.syllable && this.syllable !== info.syllable)
124
+ return 0;
125
+ if (this.matchFunc)
126
+ return this.matchFunc(info, this.data ? this.data[this.dataIndex] : 0) ? 1 : 0;
127
+ return 2;
128
+ }
129
+ match(info) {
130
+ const skip = this.maySkip(info);
131
+ if (skip === 1)
132
+ return 2;
133
+ const match = this.mayMatch(info);
134
+ if (match === 1 || match === 2 && skip === 0)
135
+ return 0;
136
+ if (skip === 0)
137
+ return 1;
138
+ return 2;
139
+ }
140
+ next() {
141
+ const info = this.c.buffer.info;
142
+ const stop = this.end - 1;
143
+ while (this.idx < stop) {
144
+ this.idx++;
145
+ const m = this.match(info[this.idx]);
146
+ if (m === 0) {
147
+ this.dataIndex++;
148
+ return true;
149
+ }
150
+ if (m === 1)
151
+ return false;
152
+ }
153
+ return false;
154
+ }
155
+ prev() {
156
+ const buffer = this.c.buffer, info = buffer.haveOutput ? buffer.outInfo : buffer.info;
157
+ while (this.idx > 0) {
158
+ this.idx--;
159
+ const m = this.match(info[this.idx]);
160
+ if (m === 0) {
161
+ this.dataIndex++;
162
+ return true;
163
+ }
164
+ if (m === 1)
165
+ return false;
166
+ }
167
+ return false;
168
+ }
169
+ }
170
+ export class ApplyContext {
171
+ tableIndex;
172
+ table;
173
+ buffer;
174
+ gdef;
175
+ lookupMask = 1;
176
+ lookupIndex = -1;
177
+ lookupProps = 0;
178
+ nestingLevelLeft = MAX_NESTING_LEVEL;
179
+ autoZWNJ = true;
180
+ autoZWJ = true;
181
+ perSyllable = false;
182
+ random = false;
183
+ newSyllables = -1;
184
+ lastBase = -1;
185
+ lastBaseUntil = 0;
186
+ matchPositions = [];
187
+ iterInput;
188
+ iterContext;
189
+ direction;
190
+ randomState = 1;
191
+ constructor(tableIndex, table, buffer, gdef) {
192
+ this.tableIndex = tableIndex;
193
+ this.table = table;
194
+ this.buffer = buffer;
195
+ this.gdef = gdef;
196
+ this.direction = buffer.direction;
197
+ this.iterInput = new SkippingIterator(this, false);
198
+ this.iterContext = new SkippingIterator(this, true);
199
+ this.initIters();
200
+ }
201
+ initIters() {
202
+ this.iterInput.init();
203
+ this.iterContext.init();
204
+ }
205
+ setLookupProps(props) {
206
+ this.lookupProps = this.gdef.sanitizeLookupProps(props);
207
+ this.initIters();
208
+ }
209
+ randomNumber() {
210
+ this.randomState = this.randomState * 48271 % 2147483647;
211
+ return this.randomState;
212
+ }
213
+ recurse(lookupIndex) {
214
+ if (!this.nestingLevelLeft) {
215
+ this.buffer.successful = false;
216
+ return false;
217
+ }
218
+ if (--this.buffer.maxOps < 0) {
219
+ this.buffer.successful = false;
220
+ return false;
221
+ }
222
+ const l = this.table && this.table.lookup(lookupIndex);
223
+ if (!l)
224
+ return false;
225
+ this.nestingLevelLeft--;
226
+ const savedProps = this.lookupProps, savedIndex = this.lookupIndex, savedPositions = this.matchPositions;
227
+ this.lookupIndex = lookupIndex;
228
+ this.setLookupProps(l.props);
229
+ this.matchPositions = [];
230
+ let applied = false;
231
+ for (const subtable of l.subtables)
232
+ if (subtable(this)) {
233
+ applied = true;
234
+ break;
235
+ }
236
+ this.lookupIndex = savedIndex;
237
+ this.setLookupProps(savedProps);
238
+ this.matchPositions = savedPositions;
239
+ this.nestingLevelLeft++;
240
+ return applied;
241
+ }
242
+ matchPropertiesMark(info, glyphProps, matchProps) {
243
+ if (matchProps & 16)
244
+ return this.gdef.markSetCovers(matchProps >>> 16, info.codepoint);
245
+ if (matchProps & 65280)
246
+ return (matchProps & 65280) === (glyphProps & 65280);
247
+ return true;
248
+ }
249
+ checkGlyphProperty(info, matchProps) {
250
+ const glyphProps = info.glyphProps;
251
+ if (glyphProps & matchProps & 14)
252
+ return false;
253
+ if (glyphProps & 8)
254
+ return this.matchPropertiesMark(info, glyphProps, matchProps);
255
+ return true;
256
+ }
257
+ setGlyphClass(glyph, classGuess = 0, ligature = false, component = false) {
258
+ const cur = this.buffer.cur();
259
+ if (this.newSyllables !== -1)
260
+ cur.syllable = this.newSyllables;
261
+ let props = cur.glyphProps | 16;
262
+ if (ligature)
263
+ props = (props | 32) & ~64;
264
+ if (component)
265
+ props |= 64;
266
+ if (this.gdef.hasGlyphClasses)
267
+ cur.glyphProps = props & 112 | this.gdef.glyphProps(glyph);
268
+ else if (classGuess)
269
+ cur.glyphProps = props & 112 | classGuess;
270
+ else
271
+ cur.glyphProps = props;
272
+ }
273
+ replaceGlyph(glyph) {
274
+ this.setGlyphClass(glyph);
275
+ this.buffer.replaceGlyph(glyph);
276
+ }
277
+ replaceGlyphInplace(glyph) {
278
+ this.setGlyphClass(glyph);
279
+ this.buffer.cur().codepoint = glyph;
280
+ }
281
+ replaceGlyphWithLigature(glyph, classGuess) {
282
+ this.setGlyphClass(glyph, classGuess, true);
283
+ this.buffer.replaceGlyph(glyph);
284
+ }
285
+ outputGlyphForComponent(glyph, classGuess) {
286
+ this.setGlyphClass(glyph, classGuess, false, true);
287
+ this.buffer.outputGlyph(glyph);
288
+ }
289
+ }
290
+ function matchInput(c, count, input, match, end, totalComponents) {
291
+ const buffer = c.buffer;
292
+ if (count === 1) {
293
+ end[0] = buffer.idx + 1;
294
+ c.matchPositions[0] = buffer.idx;
295
+ if (totalComponents)
296
+ totalComponents[0] = ligNumComps(buffer.cur());
297
+ return true;
298
+ }
299
+ if (count > MAX_CONTEXT_LENGTH)
300
+ return false;
301
+ const it = c.iterInput;
302
+ it.reset(buffer.idx);
303
+ it.setMatchFunc(match, input);
304
+ let total = 0;
305
+ const firstLigId = ligId(buffer.cur()), firstLigComp = ligComp(buffer.cur());
306
+ let ligbase = 0;
307
+ for (let i = 1; i < count; i++) {
308
+ if (!it.next()) {
309
+ end[0] = it.idx + 1;
310
+ return false;
311
+ }
312
+ c.matchPositions[i] = it.idx;
313
+ const info = buffer.info[it.idx];
314
+ const thisLigId = ligId(info), thisLigComp = ligComp(info);
315
+ if (firstLigId && firstLigComp) {
316
+ if (firstLigId !== thisLigId || firstLigComp !== thisLigComp) {
317
+ if (!ligbase) {
318
+ let found = false, j = buffer.outLen;
319
+ const out = buffer.outInfo;
320
+ while (j && ligId(out[j - 1]) === firstLigId) {
321
+ if (ligComp(out[j - 1]) === 0) {
322
+ j--;
323
+ found = true;
324
+ break;
325
+ }
326
+ j--;
327
+ }
328
+ ligbase = found && it.maySkip(out[j]) === 1 ? 2 : 1;
329
+ }
330
+ if (ligbase === 1)
331
+ return false;
332
+ }
333
+ }
334
+ else if (thisLigId && thisLigComp && thisLigId !== firstLigId)
335
+ return false;
336
+ total += ligNumCompsInLigation(info);
337
+ }
338
+ end[0] = it.idx + 1;
339
+ if (totalComponents)
340
+ totalComponents[0] = total + ligNumComps(buffer.cur());
341
+ c.matchPositions[0] = buffer.idx;
342
+ return true;
343
+ }
344
+ function ligateInput(c, count, matchEnd, ligGlyph, totalComponents) {
345
+ const buffer = c.buffer, positions = c.matchPositions;
346
+ buffer.mergeClusters(buffer.idx, matchEnd);
347
+ let isBaseLigature = isBaseGlyph(buffer.info[positions[0]]), isMarkLigature = isMarkGlyph(buffer.info[positions[0]]);
348
+ for (let i = 1; i < count; i++)
349
+ if (!isMarkGlyph(buffer.info[positions[i]])) {
350
+ isBaseLigature = isMarkLigature = false;
351
+ break;
352
+ }
353
+ const ligature = !isBaseLigature && !isMarkLigature;
354
+ const klass = ligature ? 4 : 0;
355
+ const id = ligature ? allocateLigId(buffer) : 0;
356
+ let lastLigId = ligId(buffer.cur());
357
+ let lastNumComponents = ligNumComps(buffer.cur());
358
+ let componentsSoFar = lastNumComponents;
359
+ if (ligature) {
360
+ setLigPropsForLigature(buffer.cur(), id, totalComponents);
361
+ if (generalCategory(buffer.cur()) === 12)
362
+ setGeneralCategory(buffer.cur(), 7);
363
+ }
364
+ c.replaceGlyphWithLigature(ligGlyph, klass);
365
+ for (let i = 1; i < count; i++) {
366
+ while (buffer.idx < positions[i] && buffer.successful) {
367
+ if (ligature) {
368
+ let thisComp = ligComp(buffer.cur());
369
+ if (!thisComp)
370
+ thisComp = lastNumComponents;
371
+ setLigPropsForMark(buffer.cur(), id, componentsSoFar - lastNumComponents + Math.min(thisComp, lastNumComponents));
372
+ }
373
+ buffer.nextGlyph();
374
+ }
375
+ lastLigId = ligId(buffer.cur());
376
+ lastNumComponents = ligNumCompsInLigation(buffer.cur());
377
+ componentsSoFar += lastNumComponents;
378
+ buffer.skipGlyph();
379
+ }
380
+ if (!isMarkLigature && lastLigId) {
381
+ for (let i = buffer.idx; i < buffer.len; i++) {
382
+ const info = buffer.info[i];
383
+ if (lastLigId !== ligId(info))
384
+ break;
385
+ const thisComp = ligComp(info);
386
+ if (!thisComp)
387
+ break;
388
+ setLigPropsForMark(info, id, componentsSoFar - lastNumComponents + Math.min(thisComp, lastNumComponents));
389
+ }
390
+ }
391
+ }
392
+ function matchBacktrack(c, count, backtrack, match) {
393
+ if (!count)
394
+ return true;
395
+ const it = c.iterContext;
396
+ it.resetBack(c.buffer.backtrackLen());
397
+ it.setMatchFunc(match, backtrack);
398
+ for (let i = 0; i < count; i++)
399
+ if (!it.prev())
400
+ return false;
401
+ return true;
402
+ }
403
+ function matchLookahead(c, count, lookahead, match, start) {
404
+ if (!count)
405
+ return true;
406
+ const it = c.iterContext;
407
+ it.reset(start - 1);
408
+ it.setMatchFunc(match, lookahead);
409
+ for (let i = 0; i < count; i++)
410
+ if (!it.next())
411
+ return false;
412
+ return true;
413
+ }
414
+ function applyLookup(c, count, records, matchEnd) {
415
+ const buffer = c.buffer, positions = c.matchPositions;
416
+ const bl = buffer.backtrackLen();
417
+ let end = bl + matchEnd - buffer.idx;
418
+ const delta0 = bl - buffer.idx;
419
+ for (let j = 0; j < count; j++)
420
+ positions[j] += delta0;
421
+ for (let i = 0; i < records.length && buffer.successful; i += 2) {
422
+ const idx = records[i];
423
+ if (idx >= count)
424
+ continue;
425
+ const origLen = buffer.backtrackLen() + buffer.lookaheadLen();
426
+ if (positions[idx] >= origLen)
427
+ continue;
428
+ if (!buffer.moveTo(positions[idx]))
429
+ break;
430
+ if (buffer.maxOps <= 0)
431
+ break;
432
+ if (!c.recurse(records[i + 1]))
433
+ continue;
434
+ const newLen = buffer.backtrackLen() + buffer.lookaheadLen();
435
+ let delta = newLen - origLen;
436
+ if (!delta)
437
+ continue;
438
+ end += delta;
439
+ if (end < positions[idx]) {
440
+ delta += positions[idx] - end;
441
+ end = positions[idx];
442
+ }
443
+ let next = idx + 1;
444
+ if (delta > 0) {
445
+ if (delta + count > MAX_CONTEXT_LENGTH)
446
+ break;
447
+ }
448
+ else {
449
+ delta = Math.max(delta, next - count);
450
+ next -= delta;
451
+ }
452
+ positions.copyWithin(next + delta, next, count);
453
+ if (positions.length > count + delta)
454
+ positions.length = Math.max(count + delta, 0);
455
+ next += delta;
456
+ count += delta;
457
+ for (let j = idx + 1; j < next; j++)
458
+ positions[j] = positions[j - 1] + 1;
459
+ for (; next < count; next++)
460
+ positions[next] += delta;
461
+ }
462
+ buffer.moveTo(end);
463
+ }
464
+ const matchGlyph = (info, value) => info.codepoint === value;
465
+ export class LayoutTable {
466
+ font;
467
+ tag;
468
+ offset;
469
+ scriptList = 0;
470
+ featureList = 0;
471
+ lookupList = 0;
472
+ lookupCount = 0;
473
+ lookups = new Map();
474
+ substitutions = new Map();
475
+ constructor(font, tag, offset) {
476
+ this.font = font;
477
+ this.tag = tag;
478
+ this.offset = offset;
479
+ const f = font;
480
+ this.scriptList = offset + f.u16(offset + 4);
481
+ this.featureList = offset + f.u16(offset + 6);
482
+ this.lookupList = offset + f.u16(offset + 8);
483
+ this.lookupCount = f.u16(this.lookupList);
484
+ if (f.u16(offset + 2) >= 1 && f.u16(offset) === 1 && f.u32(offset + 10))
485
+ this.featureVariations(offset + f.u32(offset + 10));
486
+ }
487
+ featureVariations(table) {
488
+ const f = this.font;
489
+ for (let i = 0, count = f.u32(table + 4); i < count; i++) {
490
+ const record = table + 8 + i * 8;
491
+ const conditions = f.u32(record), substitution = f.u32(record + 4);
492
+ let matches = true;
493
+ if (conditions) {
494
+ const set = table + conditions;
495
+ for (let j = 0, n = f.u16(set); j < n && matches; j++) {
496
+ const condition = set + f.u32(set + 2 + j * 4);
497
+ if (f.u16(condition) !== 1)
498
+ matches = false;
499
+ else
500
+ matches = f.i16(condition + 4) <= 0 && 0 <= f.i16(condition + 6);
501
+ }
502
+ }
503
+ if (!matches)
504
+ continue;
505
+ if (substitution) {
506
+ const s = table + substitution;
507
+ for (let j = 0, n = f.u16(s + 4); j < n; j++)
508
+ this.substitutions.set(f.u16(s + 6 + j * 6), s + f.u32(s + 8 + j * 6));
509
+ }
510
+ return;
511
+ }
512
+ }
513
+ findScript(tag) {
514
+ const f = this.font;
515
+ for (let i = 0, count = f.u16(this.scriptList); i < count; i++)
516
+ if (tagAt(f, this.scriptList + 2 + i * 6) === tag)
517
+ return i;
518
+ return -1;
519
+ }
520
+ langSys(script) {
521
+ if (script < 0)
522
+ return 0;
523
+ const f = this.font;
524
+ const s = this.scriptList + f.u16(this.scriptList + 2 + script * 6 + 4);
525
+ for (let i = 0, count = f.u16(s + 2); i < count; i++)
526
+ if (tagAt(f, s + 4 + i * 6) === 'dflt')
527
+ return s + f.u16(s + 4 + i * 6 + 4);
528
+ return f.u16(s) ? s + f.u16(s) : 0;
529
+ }
530
+ langSysFeatures(langSys) {
531
+ const features = new Map();
532
+ if (!langSys)
533
+ return { required: -1, features };
534
+ const f = this.font;
535
+ const required = f.u16(langSys + 2);
536
+ const count = f.u16(langSys + 4);
537
+ for (let i = count; i; i--) {
538
+ const index = f.u16(langSys + 6 + (i - 1) * 2);
539
+ features.set(this.featureTag(index), index);
540
+ }
541
+ return { required: required === 0xffff ? -1 : required, features };
542
+ }
543
+ featureTag(index) {
544
+ return tagAt(this.font, this.featureList + 2 + index * 6);
545
+ }
546
+ findFeature(tag) {
547
+ const f = this.font;
548
+ for (let i = 0, count = f.u16(this.featureList); i < count; i++)
549
+ if (this.featureTag(i) === tag)
550
+ return i;
551
+ return -1;
552
+ }
553
+ featureLookups(index) {
554
+ const f = this.font;
555
+ if (index < 0 || index >= f.u16(this.featureList))
556
+ return [];
557
+ const feature = this.substitutions.get(index) ?? this.featureList + f.u16(this.featureList + 2 + index * 6 + 4);
558
+ return Array.from({ length: f.u16(feature + 2) }, (_, j) => f.u16(feature + 4 + j * 2));
559
+ }
560
+ lookup(index) {
561
+ if (index >= this.lookupCount)
562
+ return null;
563
+ let l = this.lookups.get(index);
564
+ if (l !== undefined)
565
+ return l;
566
+ const f = this.font;
567
+ const offset = this.lookupList + f.u16(this.lookupList + 2 + index * 2);
568
+ const lookupType = f.u16(offset), flag = f.u16(offset + 2), count = f.u16(offset + 4);
569
+ const props = flag & 16 ? flag + (f.u16(offset + 6 + count * 2) << 16) : flag;
570
+ const subtables = [], coverages = [], woulds = [];
571
+ const extension = this.tag === 'GSUB' ? 7 : 9;
572
+ let type = lookupType;
573
+ for (let i = 0; i < count; i++) {
574
+ let subtable = offset + f.u16(offset + 6 + i * 2);
575
+ if (lookupType === extension) {
576
+ type = f.u16(subtable + 2);
577
+ subtable += f.u32(subtable + 4);
578
+ }
579
+ const parsed = this.tag === 'GSUB' ? this.gsubSubtable(type, subtable) : this.gposSubtable(type, subtable);
580
+ if (!parsed)
581
+ continue;
582
+ subtables.push(parsed.apply);
583
+ coverages.push(parsed.coverage);
584
+ woulds.push(parsed.would);
585
+ }
586
+ const memo = new Uint8Array(f.numGlyphs);
587
+ const covers = (glyph) => {
588
+ if (glyph >= memo.length)
589
+ return coverages.some(c => c(glyph));
590
+ if (!memo[glyph])
591
+ memo[glyph] = coverages.some(c => c(glyph)) ? 2 : 1;
592
+ return memo[glyph] === 2;
593
+ };
594
+ const would = (glyphs, zeroContext) => glyphs.length > 0 && covers(glyphs[0]) && woulds.some(w => w !== undefined && w(glyphs, zeroContext));
595
+ l = { type, props, subtables, covers, would, reverse: this.tag === 'GSUB' && type === 8 };
596
+ this.lookups.set(index, l);
597
+ return l;
598
+ }
599
+ context(offset, chained) {
600
+ const f = this.font, format = f.u16(offset);
601
+ const records = (o, count) => Array.from({ length: count * 2 }, (_, j) => f.u16(o + j * 2));
602
+ const values = (o, count) => Array.from({ length: count }, (_, j) => f.u16(o + j * 2));
603
+ const rule = (o) => {
604
+ if (!chained) {
605
+ const inputCount = f.u16(o), recordCount = f.u16(o + 2);
606
+ return { backtrack: [], input: values(o + 4, inputCount ? inputCount - 1 : 0), lookahead: [], records: records(o + 4 + Math.max(0, inputCount - 1) * 2, recordCount) };
607
+ }
608
+ const backtrackCount = f.u16(o);
609
+ const backtrack = values(o + 2, backtrackCount);
610
+ let p = o + 2 + backtrackCount * 2;
611
+ const inputCount = f.u16(p);
612
+ const input = values(p + 2, inputCount ? inputCount - 1 : 0);
613
+ p += 2 + Math.max(0, inputCount - 1) * 2;
614
+ const lookaheadCount = f.u16(p);
615
+ const lookahead = values(p + 2, lookaheadCount);
616
+ p += 2 + lookaheadCount * 2;
617
+ return { backtrack, input, lookahead, records: records(p + 2, f.u16(p)) };
618
+ };
619
+ const wouldRule = (glyphs, zeroContext, r, match) => {
620
+ if (zeroContext && (r.backtrack.length || r.lookahead.length))
621
+ return false;
622
+ if (glyphs.length !== r.input.length + 1)
623
+ return false;
624
+ for (let i = 1; i < glyphs.length; i++) {
625
+ const info = new GlyphInfo();
626
+ info.codepoint = glyphs[i];
627
+ if (!match(info, r.input[i - 1]))
628
+ return false;
629
+ }
630
+ return true;
631
+ };
632
+ const applyRule = (c, r, match) => {
633
+ const inputCount = r.input.length + 1, end = [0];
634
+ if (inputCount > MAX_CONTEXT_LENGTH)
635
+ return false;
636
+ if (!chained) {
637
+ if (!matchInput(c, inputCount, r.input, match[1], end))
638
+ return false;
639
+ applyLookup(c, inputCount, r.records, end[0]);
640
+ return true;
641
+ }
642
+ if (!(matchInput(c, inputCount, r.input, match[1], end) && matchLookahead(c, r.lookahead.length, r.lookahead, match[2], end[0])))
643
+ return false;
644
+ if (!matchBacktrack(c, r.backtrack.length, r.backtrack, match[0]))
645
+ return false;
646
+ applyLookup(c, inputCount, r.records, end[0]);
647
+ return true;
648
+ };
649
+ if (format === 1 || format === 2) {
650
+ const coverage = parseCoverage(f, offset + f.u16(offset + 2));
651
+ let classes, setCount, sets;
652
+ if (format === 1) {
653
+ classes = [];
654
+ setCount = f.u16(offset + 4);
655
+ sets = offset + 6;
656
+ }
657
+ else if (!chained) {
658
+ const classDef = parseClassDef(f, offset + f.u16(offset + 4));
659
+ classes = [classDef, classDef, classDef];
660
+ setCount = f.u16(offset + 6);
661
+ sets = offset + 8;
662
+ }
663
+ else {
664
+ classes = [0, 1, 2].map(k => f.u16(offset + 4 + k * 2) ? parseClassDef(f, offset + f.u16(offset + 4 + k * 2)) : () => 0);
665
+ setCount = f.u16(offset + 10);
666
+ sets = offset + 12;
667
+ }
668
+ const match = format === 1 ? [matchGlyph, matchGlyph, matchGlyph]
669
+ : [0, 1, 2].map(k => (info, value) => classes[k](info.codepoint) === value);
670
+ const cache = new Map();
671
+ const rulesOf = (k) => {
672
+ if (k >= setCount || !f.u16(sets + k * 2))
673
+ return [];
674
+ let rules = cache.get(k);
675
+ if (!rules) {
676
+ const set = offset + f.u16(sets + k * 2);
677
+ cache.set(k, rules = Array.from({ length: f.u16(set) }, (_, j) => rule(set + f.u16(set + 2 + j * 2))));
678
+ }
679
+ return rules;
680
+ };
681
+ return {
682
+ coverage: glyph => coverage(glyph) >= 0,
683
+ apply: c => {
684
+ const glyph = c.buffer.cur().codepoint, index = coverage(glyph);
685
+ if (index < 0)
686
+ return false;
687
+ for (const r of rulesOf(format === 1 ? index : classes[1](glyph)))
688
+ if (applyRule(c, r, match))
689
+ return true;
690
+ return false;
691
+ },
692
+ would: (glyphs, zeroContext) => {
693
+ const index = coverage(glyphs[0]);
694
+ if (format === 1 && index < 0)
695
+ return false;
696
+ return rulesOf(format === 1 ? index : classes[1](glyphs[0])).some(r => wouldRule(glyphs, zeroContext, r, match[1]));
697
+ },
698
+ };
699
+ }
700
+ if (format === 3) {
701
+ const { coverages, matchCoverage, tests } = coverageList(f, offset);
702
+ let r, first;
703
+ if (!chained) {
704
+ const inputCount = f.u16(offset + 2), recordCount = f.u16(offset + 4);
705
+ const input = coverages(offset + 6, inputCount);
706
+ if (!input.length)
707
+ return null;
708
+ first = input[0];
709
+ r = { backtrack: [], input: input.slice(1), lookahead: [], records: records(offset + 6 + inputCount * 2, recordCount) };
710
+ }
711
+ else {
712
+ const backtrackCount = f.u16(offset + 2);
713
+ const backtrack = coverages(offset + 4, backtrackCount);
714
+ let p = offset + 4 + backtrackCount * 2;
715
+ const inputCount = f.u16(p);
716
+ const input = coverages(p + 2, inputCount);
717
+ if (!input.length)
718
+ return null;
719
+ p += 2 + inputCount * 2;
720
+ const lookaheadCount = f.u16(p);
721
+ const lookahead = coverages(p + 2, lookaheadCount);
722
+ p += 2 + lookaheadCount * 2;
723
+ first = input[0];
724
+ r = { backtrack, input: input.slice(1), lookahead, records: records(p + 2, f.u16(p)) };
725
+ }
726
+ const firstTest = tests[first];
727
+ return {
728
+ coverage: firstTest,
729
+ apply: c => firstTest(c.buffer.cur().codepoint) && applyRule(c, r, [matchCoverage, matchCoverage, matchCoverage]),
730
+ would: (glyphs, zeroContext) => firstTest(glyphs[0]) && wouldRule(glyphs, zeroContext, r, matchCoverage),
731
+ };
732
+ }
733
+ return null;
734
+ }
735
+ gsubSubtable(type, offset) {
736
+ const f = this.font, format = f.u16(offset);
737
+ if (type >= 1 && type <= 4 || type === 8) {
738
+ const coverage = parseCoverage(f, offset + f.u16(offset + 2));
739
+ const has = (glyph) => coverage(glyph) >= 0;
740
+ const single = (glyphs) => glyphs.length === 1 && has(glyphs[0]);
741
+ switch (type) {
742
+ case 1:
743
+ if (format === 1) {
744
+ const delta = f.u16(offset + 4);
745
+ return { coverage: has, would: single, apply: c => {
746
+ const glyph = c.buffer.cur().codepoint;
747
+ if (coverage(glyph) < 0)
748
+ return false;
749
+ c.replaceGlyph(glyph + delta & 0xffff);
750
+ return true;
751
+ } };
752
+ }
753
+ if (format !== 2)
754
+ return null;
755
+ return { coverage: has, would: single, apply: c => {
756
+ const index = coverage(c.buffer.cur().codepoint);
757
+ if (index < 0 || index >= f.u16(offset + 4))
758
+ return false;
759
+ c.replaceGlyph(f.u16(offset + 6 + index * 2));
760
+ return true;
761
+ } };
762
+ case 2:
763
+ return { coverage: has, would: single, apply: c => {
764
+ const index = coverage(c.buffer.cur().codepoint);
765
+ if (index < 0 || index >= f.u16(offset + 4))
766
+ return false;
767
+ const sequence = offset + f.u16(offset + 6 + index * 2), count = f.u16(sequence);
768
+ const buffer = c.buffer;
769
+ if (count === 1) {
770
+ c.replaceGlyph(f.u16(sequence + 2));
771
+ return true;
772
+ }
773
+ if (count === 0) {
774
+ buffer.deleteGlyph();
775
+ return true;
776
+ }
777
+ const klass = isLigature(buffer.cur()) ? 2 : 0;
778
+ const id = ligId(buffer.cur());
779
+ for (let i = 0; i < count; i++) {
780
+ if (!id)
781
+ setLigPropsForMark(buffer.cur(), 0, i);
782
+ c.outputGlyphForComponent(f.u16(sequence + 2 + i * 2), klass);
783
+ }
784
+ buffer.skipGlyph();
785
+ return true;
786
+ } };
787
+ case 3:
788
+ return { coverage: has, would: single, apply: c => {
789
+ const index = coverage(c.buffer.cur().codepoint);
790
+ if (index < 0 || index >= f.u16(offset + 4))
791
+ return false;
792
+ const set = offset + f.u16(offset + 6 + index * 2), count = f.u16(set);
793
+ if (!count)
794
+ return false;
795
+ const mask = c.lookupMask;
796
+ const shift = 31 - Math.clz32(mask & -mask);
797
+ let alternate = (mask & c.buffer.cur().mask) >>> shift;
798
+ if (alternate === MAX_VALUE && c.random)
799
+ alternate = c.randomNumber() % count + 1;
800
+ if (alternate > count || alternate === 0)
801
+ return false;
802
+ c.replaceGlyph(f.u16(set + 2 + (alternate - 1) * 2));
803
+ return true;
804
+ } };
805
+ case 4: {
806
+ const sets = new Map();
807
+ const ligatures = (index) => {
808
+ let list = sets.get(index);
809
+ if (!list) {
810
+ const set = offset + f.u16(offset + 6 + index * 2);
811
+ list = Array.from({ length: f.u16(set) }, (_, i) => {
812
+ const ligature = set + f.u16(set + 2 + i * 2), components = f.u16(ligature + 2);
813
+ return [f.u16(ligature), components, values16(f, ligature + 4, Math.max(0, components - 1))];
814
+ });
815
+ sets.set(index, list);
816
+ }
817
+ return list;
818
+ };
819
+ return { coverage: has, would: glyphs => {
820
+ const index = coverage(glyphs[0]);
821
+ if (index < 0 || index >= f.u16(offset + 4))
822
+ return false;
823
+ return ligatures(index).some(([, components, input]) => components === glyphs.length && input.every((g, k) => glyphs[k + 1] === g));
824
+ }, apply: c => {
825
+ const index = coverage(c.buffer.cur().codepoint);
826
+ if (index < 0 || index >= f.u16(offset + 4))
827
+ return false;
828
+ for (const [glyph, components, input] of ligatures(index)) {
829
+ if (!components || components > MAX_CONTEXT_LENGTH)
830
+ continue;
831
+ if (components === 1) {
832
+ c.replaceGlyph(glyph);
833
+ return true;
834
+ }
835
+ const end = [0], total = [0];
836
+ if (!matchInput(c, components, input, matchGlyph, end, total))
837
+ continue;
838
+ ligateInput(c, components, end[0], glyph, total[0]);
839
+ return true;
840
+ }
841
+ return false;
842
+ } };
843
+ }
844
+ case 8: {
845
+ const backtrackCount = f.u16(offset + 4);
846
+ const { coverages: list, matchCoverage } = coverageList(f, offset);
847
+ const backtrack = list(offset + 6, backtrackCount);
848
+ let p = offset + 6 + backtrackCount * 2;
849
+ const lookaheadCount = f.u16(p);
850
+ const lookahead = list(p + 2, lookaheadCount);
851
+ p += 2 + lookaheadCount * 2;
852
+ const substituteCount = f.u16(p), substitutes = p + 2;
853
+ return { coverage: has, would: single, apply: c => {
854
+ const index = coverage(c.buffer.cur().codepoint);
855
+ if (index < 0)
856
+ return false;
857
+ if (c.nestingLevelLeft !== MAX_NESTING_LEVEL)
858
+ return false;
859
+ if (index >= substituteCount)
860
+ return false;
861
+ if (matchBacktrack(c, backtrack.length, backtrack, matchCoverage) && matchLookahead(c, lookahead.length, lookahead, matchCoverage, c.buffer.idx + 1)) {
862
+ c.replaceGlyphInplace(f.u16(substitutes + index * 2));
863
+ return true;
864
+ }
865
+ return false;
866
+ } };
867
+ }
868
+ }
869
+ }
870
+ if (type === 5 || type === 6)
871
+ return this.context(offset, type === 6);
872
+ return null;
873
+ }
874
+ gposSubtable(type, offset) {
875
+ const f = this.font, format = f.u16(offset);
876
+ if (type === 7 || type === 8)
877
+ return this.context(offset, type === 8);
878
+ if (type < 1 || type > 6)
879
+ return null;
880
+ const coverage = parseCoverage(f, offset + f.u16(offset + 2));
881
+ const has = (glyph) => coverage(glyph) >= 0;
882
+ switch (type) {
883
+ case 1: {
884
+ const valueFormat = f.u16(offset + 4), size = valueSize(valueFormat);
885
+ if (format === 1)
886
+ return { coverage: has, apply: c => {
887
+ if (coverage(c.buffer.cur().codepoint) < 0)
888
+ return false;
889
+ applyValue(f, valueFormat, offset + 6, c.buffer.cur());
890
+ c.buffer.idx++;
891
+ return true;
892
+ } };
893
+ if (format !== 2)
894
+ return null;
895
+ return { coverage: has, apply: c => {
896
+ const index = coverage(c.buffer.cur().codepoint);
897
+ if (index < 0 || index >= f.u16(offset + 6))
898
+ return false;
899
+ applyValue(f, valueFormat, offset + 8 + index * size, c.buffer.cur());
900
+ c.buffer.idx++;
901
+ return true;
902
+ } };
903
+ }
904
+ case 2: {
905
+ const format1 = f.u16(offset + 4), format2 = f.u16(offset + 6);
906
+ const len1 = valueSize(format1), len2 = valueSize(format2);
907
+ if (format === 1) {
908
+ const recordSize = 2 + len1 + len2;
909
+ return { coverage: has, apply: c => {
910
+ const buffer = c.buffer;
911
+ const index = coverage(buffer.cur().codepoint);
912
+ if (index < 0 || index >= f.u16(offset + 8))
913
+ return false;
914
+ const it = c.iterInput;
915
+ it.resetFast(buffer.idx);
916
+ it.setMatchFunc(null, null);
917
+ if (!it.next())
918
+ return false;
919
+ const pos = it.idx;
920
+ const set = offset + f.u16(offset + 10 + index * 2), count = f.u16(set);
921
+ const second = buffer.info[pos].codepoint;
922
+ let lo = 0, hi = count - 1, record = -1;
923
+ while (lo <= hi) {
924
+ const mid = lo + hi >> 1, g = f.u16(set + 2 + mid * recordSize);
925
+ if (g < second)
926
+ lo = mid + 1;
927
+ else if (g > second)
928
+ hi = mid - 1;
929
+ else {
930
+ record = set + 2 + mid * recordSize;
931
+ break;
932
+ }
933
+ }
934
+ if (record < 0)
935
+ return false;
936
+ if (len1)
937
+ applyValue(f, format1, record + 2, buffer.cur());
938
+ if (len2)
939
+ applyValue(f, format2, record + 2 + len1, buffer.info[pos]);
940
+ buffer.idx = len2 ? pos + 1 : pos;
941
+ return true;
942
+ } };
943
+ }
944
+ if (format !== 2)
945
+ return null;
946
+ const class1 = parseClassDef(f, offset + f.u16(offset + 8)), class2 = parseClassDef(f, offset + f.u16(offset + 10));
947
+ const class1Count = f.u16(offset + 12), class2Count = f.u16(offset + 14), records = offset + 16;
948
+ return { coverage: has, apply: c => {
949
+ const buffer = c.buffer;
950
+ if (coverage(buffer.cur().codepoint) < 0)
951
+ return false;
952
+ const it = c.iterInput;
953
+ it.resetFast(buffer.idx);
954
+ it.setMatchFunc(null, null);
955
+ if (!it.next())
956
+ return false;
957
+ const pos = it.idx;
958
+ const k1 = class1(buffer.cur().codepoint), k2 = class2(buffer.info[pos].codepoint);
959
+ if (k1 >= class1Count || k2 >= class2Count)
960
+ return false;
961
+ const v = records + (k1 * class2Count + k2) * (len1 + len2);
962
+ if (len1)
963
+ applyValue(f, format1, v, buffer.cur());
964
+ if (len2)
965
+ applyValue(f, format2, v + len1, buffer.info[pos]);
966
+ buffer.idx = len2 ? pos + 1 : pos;
967
+ return true;
968
+ } };
969
+ }
970
+ case 3: {
971
+ const count = f.u16(offset + 4);
972
+ const anchorOf = (index, entry) => {
973
+ if (index < 0 || index >= count)
974
+ return 0;
975
+ const o = f.u16(offset + 6 + index * 4 + (entry ? 0 : 2));
976
+ return o ? offset + o : 0;
977
+ };
978
+ return { coverage: has, apply: c => {
979
+ const buffer = c.buffer, info = buffer.info;
980
+ const entry = anchorOf(coverage(buffer.cur().codepoint), true);
981
+ if (!entry)
982
+ return false;
983
+ const it = c.iterInput;
984
+ it.resetFast(buffer.idx);
985
+ it.setMatchFunc(null, null);
986
+ if (!it.prev())
987
+ return false;
988
+ const exit = anchorOf(coverage(info[it.idx].codepoint), false);
989
+ if (!exit)
990
+ return false;
991
+ const i = it.idx, j = buffer.idx;
992
+ const [exitX, exitY] = anchor(f, exit), [entryX, entryY] = anchor(f, entry);
993
+ if (c.direction === 4) {
994
+ info[i].xAdvance = exitX + info[i].xOffset;
995
+ const d = entryX + info[j].xOffset;
996
+ info[j].xAdvance -= d;
997
+ info[j].xOffset -= d;
998
+ }
999
+ else {
1000
+ const d = exitX + info[i].xOffset;
1001
+ info[i].xAdvance -= d;
1002
+ info[i].xOffset -= d;
1003
+ info[j].xAdvance = entryX + info[j].xOffset;
1004
+ }
1005
+ let child = i, parent = j, yOffset = entryY - exitY;
1006
+ if (!(c.lookupProps & 1)) {
1007
+ child = j;
1008
+ parent = i;
1009
+ yOffset = -yOffset;
1010
+ }
1011
+ reverseCursiveMinorOffset(info, child, parent, 0);
1012
+ info[child].attachChain = parent - child;
1013
+ info[child].attachType = 2;
1014
+ buffer.hasAttachment = true;
1015
+ info[child].yOffset = yOffset;
1016
+ if (info[parent].attachChain === -info[child].attachChain) {
1017
+ info[parent].attachChain = 0;
1018
+ info[parent].yOffset = 0;
1019
+ }
1020
+ buffer.idx++;
1021
+ return true;
1022
+ } };
1023
+ }
1024
+ case 4:
1025
+ case 5: {
1026
+ const baseCoverage = parseCoverage(f, offset + f.u16(offset + 4));
1027
+ const classCount = f.u16(offset + 6);
1028
+ const markArray = offset + f.u16(offset + 8), baseArray = offset + f.u16(offset + 10);
1029
+ const ligature = type === 5;
1030
+ const accept = (info, i) => {
1031
+ if (!isMultiplied(info[i]) || ligComp(info[i]) === 0)
1032
+ return true;
1033
+ if (ligature)
1034
+ return false;
1035
+ return i === 0 || isMarkGlyph(info[i - 1]) || !isMultiplied(info[i - 1]) || ligId(info[i]) !== ligId(info[i - 1])
1036
+ || ligComp(info[i]) !== ligComp(info[i - 1]) + 1;
1037
+ };
1038
+ return { coverage: has, apply: c => {
1039
+ const buffer = c.buffer, info = buffer.info;
1040
+ const markIndex = coverage(buffer.cur().codepoint);
1041
+ if (markIndex < 0)
1042
+ return false;
1043
+ const it = c.iterInput;
1044
+ it.lookupProps = 8;
1045
+ it.setMatchFunc(null, null);
1046
+ if (c.lastBaseUntil > buffer.idx) {
1047
+ c.lastBaseUntil = 0;
1048
+ c.lastBase = -1;
1049
+ }
1050
+ for (let j = buffer.idx; j > c.lastBaseUntil; j--) {
1051
+ let m = it.match(info[j - 1]);
1052
+ if (m === 0 && !accept(info, j - 1) && baseCoverage(info[j - 1].codepoint) < 0)
1053
+ m = 2;
1054
+ if (m === 0) {
1055
+ c.lastBase = j - 1;
1056
+ break;
1057
+ }
1058
+ }
1059
+ c.lastBaseUntil = buffer.idx;
1060
+ if (c.lastBase === -1)
1061
+ return false;
1062
+ const idx = c.lastBase;
1063
+ const baseIndex = baseCoverage(info[idx].codepoint);
1064
+ if (baseIndex < 0)
1065
+ return false;
1066
+ if (!ligature)
1067
+ return markAttach(f, c, markArray, markIndex, baseArray, baseIndex, classCount, idx);
1068
+ if (baseIndex >= f.u16(baseArray))
1069
+ return false;
1070
+ const attach = baseArray + f.u16(baseArray + 2 + baseIndex * 2), components = f.u16(attach);
1071
+ if (!components)
1072
+ return false;
1073
+ const id = ligId(info[idx]), markId = ligId(buffer.cur()), markComp = ligComp(buffer.cur());
1074
+ const component = id && id === markId && markComp > 0 ? Math.min(components, markComp) - 1 : components - 1;
1075
+ return markAttach(f, c, markArray, markIndex, attach, component, classCount, idx);
1076
+ } };
1077
+ }
1078
+ case 6: {
1079
+ const mark2Coverage = parseCoverage(f, offset + f.u16(offset + 4));
1080
+ const classCount = f.u16(offset + 6);
1081
+ const mark1Array = offset + f.u16(offset + 8), mark2Array = offset + f.u16(offset + 10);
1082
+ return { coverage: has, apply: c => {
1083
+ const buffer = c.buffer, info = buffer.info;
1084
+ const mark1Index = coverage(buffer.cur().codepoint);
1085
+ if (mark1Index < 0)
1086
+ return false;
1087
+ const it = c.iterInput;
1088
+ it.resetFast(buffer.idx);
1089
+ it.lookupProps = c.lookupProps & ~14;
1090
+ it.setMatchFunc(null, null);
1091
+ if (!it.prev())
1092
+ return false;
1093
+ if (!isMarkGlyph(info[it.idx]))
1094
+ return false;
1095
+ const j = it.idx;
1096
+ const id1 = ligId(buffer.cur()), id2 = ligId(info[j]), comp1 = ligComp(buffer.cur()), comp2 = ligComp(info[j]);
1097
+ let good = false;
1098
+ if (id1 === id2)
1099
+ good = id1 === 0 || comp1 === comp2;
1100
+ else
1101
+ good = id1 > 0 && !comp1 || id2 > 0 && !comp2;
1102
+ if (!good)
1103
+ return false;
1104
+ const mark2Index = mark2Coverage(info[j].codepoint);
1105
+ if (mark2Index < 0)
1106
+ return false;
1107
+ return markAttach(f, c, mark1Array, mark1Index, mark2Array, mark2Index, classCount, j);
1108
+ } };
1109
+ }
1110
+ }
1111
+ return null;
1112
+ }
1113
+ }
1114
+ export function syntheticLookup(props, single, ligatures) {
1115
+ const covers = (glyph) => !!(single && single.has(glyph) || ligatures && ligatures.has(glyph));
1116
+ const apply = c => {
1117
+ const glyph = c.buffer.cur().codepoint;
1118
+ if (single) {
1119
+ const s = single.get(glyph);
1120
+ if (s === undefined)
1121
+ return false;
1122
+ c.replaceGlyph(s);
1123
+ return true;
1124
+ }
1125
+ for (const [components, ligature] of ligatures?.get(glyph) ?? []) {
1126
+ const count = components.length + 1, end = [0], total = [0];
1127
+ if (!matchInput(c, count, components, matchGlyph, end, total))
1128
+ continue;
1129
+ ligateInput(c, count, end[0], ligature, total[0]);
1130
+ return true;
1131
+ }
1132
+ return false;
1133
+ };
1134
+ return { type: single ? 1 : 4, props, subtables: [apply], covers, would: () => false, reverse: false };
1135
+ }
1136
+ export function applySynthetic(buffer, gdef, l, mask) {
1137
+ const c = new ApplyContext(0, null, buffer, gdef);
1138
+ c.lookupMask = mask;
1139
+ c.setLookupProps(l.props);
1140
+ applyString(c, l, 0);
1141
+ }
1142
+ function coverageList(f, subtable) {
1143
+ const tests = [];
1144
+ const coverages = (o, count) => Array.from({ length: count }, (_, j) => {
1145
+ tests.push(covered(f, subtable + f.u16(o + j * 2)));
1146
+ return tests.length - 1;
1147
+ });
1148
+ return { coverages, matchCoverage: (info, value) => tests[value](info.codepoint), tests };
1149
+ }
1150
+ const values16 = (f, o, count) => Array.from({ length: count }, (_, j) => f.u16(o + j * 2));
1151
+ const MAX_BITS = 8;
1152
+ const MAX_VALUE = (1 << MAX_BITS) - 1;
1153
+ const valueSize = (format) => {
1154
+ let n = 0;
1155
+ for (let v = format & 0xff; v; v >>= 1)
1156
+ n += v & 1;
1157
+ return n * 2;
1158
+ };
1159
+ function applyValue(f, format, offset, info) {
1160
+ if (format & 1) {
1161
+ info.xOffset += f.i16(offset);
1162
+ offset += 2;
1163
+ }
1164
+ if (format & 2) {
1165
+ info.yOffset += f.i16(offset);
1166
+ offset += 2;
1167
+ }
1168
+ if (format & 4)
1169
+ info.xAdvance += f.i16(offset);
1170
+ }
1171
+ function anchor(f, offset) {
1172
+ return [f.i16(offset + 2), f.i16(offset + 4)];
1173
+ }
1174
+ function markAttach(f, c, markArray, markIndex, anchors, row, classCount, glyphPos) {
1175
+ const buffer = c.buffer, info = buffer.info;
1176
+ if (markIndex >= f.u16(markArray))
1177
+ return false;
1178
+ const record = markArray + 2 + markIndex * 4;
1179
+ const markClass = f.u16(record), markAnchor = markArray + f.u16(record + 2);
1180
+ if (row >= f.u16(anchors) || markClass >= classCount)
1181
+ return false;
1182
+ const o = f.u16(anchors + 2 + (row * classCount + markClass) * 2);
1183
+ if (!o)
1184
+ return false;
1185
+ const [markX, markY] = anchor(f, markAnchor), [baseX, baseY] = anchor(f, anchors + o);
1186
+ const baseOffset = resolveCrossOffset(info, glyphPos);
1187
+ const mark = buffer.cur();
1188
+ mark.attachChain = glyphPos - buffer.idx;
1189
+ mark.attachType = 1;
1190
+ mark.xOffset = baseX - markX;
1191
+ mark.yOffset = baseY - markY + baseOffset;
1192
+ buffer.hasAttachment = true;
1193
+ buffer.idx++;
1194
+ return true;
1195
+ }
1196
+ function resolveCrossOffset(info, glyphPos) {
1197
+ let offset = info[glyphPos].yOffset;
1198
+ while (info[glyphPos].attachType & 2) {
1199
+ const chain = info[glyphPos].attachChain;
1200
+ if (!chain)
1201
+ break;
1202
+ const parent = glyphPos + chain;
1203
+ if (parent < 0 || parent >= info.length)
1204
+ break;
1205
+ glyphPos = parent;
1206
+ offset += info[glyphPos].yOffset;
1207
+ }
1208
+ return offset;
1209
+ }
1210
+ function reverseCursiveMinorOffset(info, i, newParent, level) {
1211
+ if (level > MAX_NESTING_LEVEL)
1212
+ return;
1213
+ const chain = info[i].attachChain, type = info[i].attachType;
1214
+ if (!chain || !(type & 2))
1215
+ return;
1216
+ info[i].attachChain = 0;
1217
+ const j = i + chain;
1218
+ if (j < 0 || j >= info.length || j === newParent)
1219
+ return;
1220
+ reverseCursiveMinorOffset(info, j, newParent, level + 1);
1221
+ info[j].yOffset = -info[i].yOffset;
1222
+ info[j].attachChain = -chain;
1223
+ info[j].attachType = type;
1224
+ }
1225
+ export function propagateAttachmentOffsets(buffer) {
1226
+ if (!buffer.hasAttachment)
1227
+ return;
1228
+ const info = buffer.info, len = info.length, forward = buffer.direction === 4;
1229
+ const propagate = (i, level) => {
1230
+ const chain = info[i].attachChain, type = info[i].attachType;
1231
+ info[i].attachChain = 0;
1232
+ const j = i + chain;
1233
+ if (j < 0 || j >= len || !level)
1234
+ return;
1235
+ if (info[j].attachChain)
1236
+ propagate(j, level - 1);
1237
+ if (type & 2)
1238
+ info[i].yOffset += info[j].yOffset;
1239
+ else {
1240
+ info[i].xOffset += info[j].xOffset;
1241
+ if (j < i) {
1242
+ if (forward)
1243
+ for (let k = j; k < i; k++) {
1244
+ info[i].xOffset -= info[k].xAdvance;
1245
+ info[i].yOffset -= info[k].yAdvance;
1246
+ }
1247
+ else
1248
+ for (let k = j + 1; k < i + 1; k++) {
1249
+ info[i].xOffset += info[k].xAdvance;
1250
+ info[i].yOffset += info[k].yAdvance;
1251
+ }
1252
+ }
1253
+ else {
1254
+ if (forward)
1255
+ for (let k = i; k < j; k++) {
1256
+ info[i].xOffset += info[k].xAdvance;
1257
+ info[i].yOffset += info[k].yAdvance;
1258
+ }
1259
+ else
1260
+ for (let k = i + 1; k < j + 1; k++) {
1261
+ info[i].xOffset -= info[k].xAdvance;
1262
+ info[i].yOffset -= info[k].yAdvance;
1263
+ }
1264
+ }
1265
+ }
1266
+ };
1267
+ if (forward) {
1268
+ for (let i = 0; i < len; i++)
1269
+ if (info[i].attachChain)
1270
+ propagate(i, MAX_NESTING_LEVEL);
1271
+ }
1272
+ else
1273
+ for (let i = len; i-- > 0;)
1274
+ if (info[i].attachChain)
1275
+ propagate(i, MAX_NESTING_LEVEL);
1276
+ }
1277
+ const GLOBAL_BIT_SHIFT = 31;
1278
+ const GLOBAL_BIT_MASK = 1 << GLOBAL_BIT_SHIFT;
1279
+ const FIRST_BIT = 4;
1280
+ export class OTMap {
1281
+ globalMask = GLOBAL_BIT_MASK;
1282
+ features = [];
1283
+ lookups = [[], []];
1284
+ stages = [[], []];
1285
+ chosenScript = ['', ''];
1286
+ foundScript = [false, false];
1287
+ feature(tag) {
1288
+ return this.features.find(f => f.tag === tag);
1289
+ }
1290
+ getMask(tag) {
1291
+ const f = this.feature(tag);
1292
+ return f ? [f.mask, f.shift] : [0, 0];
1293
+ }
1294
+ get1Mask(tag) {
1295
+ return this.feature(tag)?.oneMask ?? 0;
1296
+ }
1297
+ needsFallback(tag) {
1298
+ return this.feature(tag)?.needsFallback ?? false;
1299
+ }
1300
+ featureIndex(table, tag) {
1301
+ return this.feature(tag)?.index[table] ?? -1;
1302
+ }
1303
+ stageLookups(table, tag) {
1304
+ const f = this.feature(tag);
1305
+ if (!f)
1306
+ return [];
1307
+ const stage = f.stage[table], stages = this.stages[table];
1308
+ if (stage > stages.length)
1309
+ return [];
1310
+ const start = stage ? stages[stage - 1].lastLookup : 0;
1311
+ const end = stage < stages.length ? stages[stage].lastLookup : this.lookups[table].length;
1312
+ return this.lookups[table].slice(start, end);
1313
+ }
1314
+ apply(table, layout, buffer, gdef, plan, font) {
1315
+ const c = layout ? new ApplyContext(table, layout, buffer, gdef) : null;
1316
+ let i = 0;
1317
+ for (const stage of this.stages[table]) {
1318
+ for (; i < stage.lastLookup; i++) {
1319
+ const lookup = this.lookups[table][i];
1320
+ const l = layout && layout.lookup(lookup.index);
1321
+ if (!l || !c || !buffer.len || !lookup.mask)
1322
+ continue;
1323
+ c.lookupIndex = lookup.index;
1324
+ c.lookupMask = lookup.mask;
1325
+ c.lastBase = -1;
1326
+ c.lastBaseUntil = 0;
1327
+ c.autoZWJ = lookup.autoZWJ;
1328
+ c.autoZWNJ = lookup.autoZWNJ;
1329
+ c.random = lookup.random;
1330
+ c.perSyllable = lookup.perSyllable;
1331
+ c.setLookupProps(l.props);
1332
+ applyString(c, l, table);
1333
+ }
1334
+ if (stage.pause)
1335
+ stage.pause(plan, font, buffer);
1336
+ }
1337
+ }
1338
+ }
1339
+ function applyString(c, l, table) {
1340
+ const buffer = c.buffer;
1341
+ if (!l.reverse) {
1342
+ if (table === 0)
1343
+ buffer.clearOutput();
1344
+ buffer.idx = 0;
1345
+ while (buffer.successful) {
1346
+ const info = buffer.info;
1347
+ let j = buffer.idx;
1348
+ while (j < buffer.len && !(l.covers(info[j].codepoint) && info[j].mask & c.lookupMask && c.checkGlyphProperty(info[j], c.lookupProps)))
1349
+ j++;
1350
+ if (j > buffer.idx)
1351
+ buffer.nextGlyphs(j - buffer.idx);
1352
+ if (buffer.idx >= buffer.len)
1353
+ break;
1354
+ let applied = false;
1355
+ for (const subtable of l.subtables)
1356
+ if (subtable(c)) {
1357
+ applied = true;
1358
+ break;
1359
+ }
1360
+ if (!applied)
1361
+ buffer.nextGlyph();
1362
+ }
1363
+ if (table === 0)
1364
+ buffer.sync();
1365
+ }
1366
+ else {
1367
+ buffer.idx = buffer.len - 1;
1368
+ do {
1369
+ const cur = buffer.cur();
1370
+ if (l.covers(cur.codepoint) && cur.mask & c.lookupMask && c.checkGlyphProperty(cur, c.lookupProps))
1371
+ for (const subtable of l.subtables)
1372
+ if (subtable(c))
1373
+ break;
1374
+ buffer.idx--;
1375
+ } while (buffer.idx >= 0);
1376
+ }
1377
+ }
1378
+ export class MapBuilder {
1379
+ tables;
1380
+ featureInfos = [];
1381
+ currentStage = [0, 0];
1382
+ stages = [[], []];
1383
+ scriptIndex = [-1, -1];
1384
+ chosenScript = ['', ''];
1385
+ foundScript = [false, false];
1386
+ langSys = [0, 0];
1387
+ isSimple = true;
1388
+ constructor(tables, scriptTags) {
1389
+ this.tables = tables;
1390
+ for (const t of [0, 1]) {
1391
+ const table = tables[t];
1392
+ if (!table)
1393
+ continue;
1394
+ let index = -1;
1395
+ for (const tag of scriptTags)
1396
+ if ((index = table.findScript(tag)) >= 0) {
1397
+ this.chosenScript[t] = tag;
1398
+ this.foundScript[t] = true;
1399
+ break;
1400
+ }
1401
+ if (index < 0)
1402
+ for (const tag of ['DFLT', 'dflt', 'latn'])
1403
+ if ((index = table.findScript(tag)) >= 0) {
1404
+ this.chosenScript[t] = tag;
1405
+ break;
1406
+ }
1407
+ this.scriptIndex[t] = index;
1408
+ this.langSys[t] = table.langSys(index);
1409
+ }
1410
+ }
1411
+ addFeature(tag, flags = 0, value = 1) {
1412
+ this.featureInfos.push({ tag, seq: this.featureInfos.length + 1, maxValue: value, flags, defaultValue: flags & 1 ? value : 0, stage: [...this.currentStage] });
1413
+ }
1414
+ enableFeature(tag, flags = 0, value = 1) {
1415
+ this.addFeature(tag, flags | 1, value);
1416
+ }
1417
+ disableFeature(tag) {
1418
+ this.addFeature(tag, 1, 0);
1419
+ }
1420
+ addGsubPause(pause) {
1421
+ this.stages[0].push({ index: this.currentStage[0]++, pause });
1422
+ }
1423
+ addGposPause(pause) {
1424
+ this.stages[1].push({ index: this.currentStage[1]++, pause });
1425
+ }
1426
+ hasFeature(tag) {
1427
+ return [0, 1].some(t => this.tables[t]?.langSysFeatures(this.langSys[t]).features.has(tag));
1428
+ }
1429
+ compile() {
1430
+ const m = new OTMap();
1431
+ m.chosenScript = [...this.chosenScript];
1432
+ m.foundScript = [...this.foundScript];
1433
+ const langSys = [0, 1].map(t => this.tables[t]?.langSysFeatures(this.langSys[t]) ?? { required: -1, features: new Map() });
1434
+ const requiredTag = [0, 1].map(t => langSys[t].required >= 0 ? this.tables[t]?.featureTag(langSys[t].required) ?? '' : '');
1435
+ const requiredStage = [0, 0];
1436
+ let infos = this.featureInfos;
1437
+ if (infos.length) {
1438
+ if (!this.isSimple)
1439
+ infos = infos.slice().sort((a, b) => a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : a.seq - b.seq);
1440
+ const merged = [{ ...infos[0], stage: [...infos[0].stage] }];
1441
+ for (let i = 1; i < infos.length; i++) {
1442
+ const f = infos[i], j = merged[merged.length - 1];
1443
+ if (f.tag !== j.tag) {
1444
+ merged.push({ ...f, stage: [...f.stage] });
1445
+ continue;
1446
+ }
1447
+ if (f.flags & 1) {
1448
+ j.flags |= 1;
1449
+ j.maxValue = f.maxValue;
1450
+ j.defaultValue = f.defaultValue;
1451
+ }
1452
+ else {
1453
+ if (j.flags & 1)
1454
+ j.flags ^= 1;
1455
+ j.maxValue = Math.max(j.maxValue, f.maxValue);
1456
+ }
1457
+ j.flags |= f.flags & 2;
1458
+ j.stage[0] = Math.min(j.stage[0], f.stage[0]);
1459
+ j.stage[1] = Math.min(j.stage[1], f.stage[1]);
1460
+ }
1461
+ infos = merged;
1462
+ }
1463
+ let nextBit = FIRST_BIT;
1464
+ for (const info of infos) {
1465
+ const global = info.flags & 1 && info.maxValue === 1;
1466
+ const bitsNeeded = global ? 0 : Math.min(MAX_BITS, 32 - Math.clz32(info.maxValue));
1467
+ if (!info.maxValue || nextBit + bitsNeeded >= GLOBAL_BIT_SHIFT)
1468
+ continue;
1469
+ let found = false;
1470
+ const index = [-1, -1];
1471
+ for (const t of [0, 1]) {
1472
+ if (requiredTag[t] === info.tag)
1473
+ requiredStage[t] = info.stage[t];
1474
+ const i = langSys[t].features.get(info.tag);
1475
+ if (i !== undefined) {
1476
+ index[t] = i;
1477
+ found = true;
1478
+ }
1479
+ }
1480
+ if (!found && info.flags & 16)
1481
+ for (const t of [0, 1]) {
1482
+ const i = this.tables[t]?.findFeature(info.tag) ?? -1;
1483
+ if (i >= 0) {
1484
+ index[t] = i;
1485
+ found = true;
1486
+ }
1487
+ }
1488
+ if (!found && !(info.flags & 2))
1489
+ continue;
1490
+ const map = {
1491
+ tag: info.tag, index, stage: [...info.stage], shift: 0, mask: 0, oneMask: 0, needsFallback: !found,
1492
+ autoZWNJ: !(info.flags & 4), autoZWJ: !(info.flags & 8),
1493
+ random: !!(info.flags & 32), perSyllable: !!(info.flags & 64),
1494
+ };
1495
+ if (global) {
1496
+ map.shift = GLOBAL_BIT_SHIFT;
1497
+ map.mask = GLOBAL_BIT_MASK;
1498
+ }
1499
+ else {
1500
+ map.shift = nextBit;
1501
+ map.mask = (1 << nextBit + bitsNeeded) - (1 << nextBit);
1502
+ nextBit += bitsNeeded;
1503
+ m.globalMask |= info.defaultValue << map.shift & map.mask;
1504
+ }
1505
+ map.oneMask = 1 << map.shift & map.mask;
1506
+ m.features.push(map);
1507
+ }
1508
+ if (this.isSimple)
1509
+ m.features.sort((a, b) => a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0);
1510
+ this.addGsubPause(null);
1511
+ this.addGposPause(null);
1512
+ for (const t of [0, 1]) {
1513
+ const table = this.tables[t];
1514
+ const lookups = m.lookups[t];
1515
+ let stageIndex = 0, lastNumLookups = 0;
1516
+ const add = (featureIndex, mask, autoZWNJ = true, autoZWJ = true, random = false, perSyllable = false, tag = '') => {
1517
+ if (!table || featureIndex < 0)
1518
+ return;
1519
+ for (const index of table.featureLookups(featureIndex)) {
1520
+ if (index >= table.lookupCount)
1521
+ continue;
1522
+ lookups.push({ index, mask, autoZWNJ, autoZWJ, random, perSyllable, tag });
1523
+ }
1524
+ };
1525
+ for (let stage = 0; stage < this.currentStage[t]; stage++) {
1526
+ if (langSys[t].required >= 0 && requiredStage[t] === stage)
1527
+ add(langSys[t].required, GLOBAL_BIT_MASK);
1528
+ for (const feature of m.features)
1529
+ if (feature.stage[t] === stage)
1530
+ add(feature.index[t], feature.mask, feature.autoZWNJ, feature.autoZWJ, feature.random, feature.perSyllable, feature.tag);
1531
+ if (lastNumLookups + 1 < lookups.length) {
1532
+ const stageLookups = lookups.splice(lastNumLookups).sort((a, b) => a.index - b.index);
1533
+ for (const l of stageLookups) {
1534
+ const last = lookups.length > lastNumLookups ? lookups[lookups.length - 1] : null;
1535
+ if (last && last.index === l.index) {
1536
+ last.mask |= l.mask;
1537
+ last.autoZWNJ &&= l.autoZWNJ;
1538
+ last.autoZWJ &&= l.autoZWJ;
1539
+ }
1540
+ else
1541
+ lookups.push({ ...l });
1542
+ }
1543
+ }
1544
+ lastNumLookups = lookups.length;
1545
+ if (stageIndex < this.stages[t].length && this.stages[t][stageIndex].index === stage) {
1546
+ m.stages[t].push({ lastLookup: lastNumLookups, pause: this.stages[t][stageIndex].pause });
1547
+ stageIndex++;
1548
+ }
1549
+ }
1550
+ }
1551
+ return m;
1552
+ }
1553
+ }