@mark1russell7/typeset 0.1.1 → 0.1.2

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 (50) hide show
  1. package/README.md +21 -1
  2. package/build/chunks/engines.d.ts +32 -0
  3. package/build/chunks/engines.d.ts.map +1 -0
  4. package/build/chunks/engines.js +35 -0
  5. package/build/chunks/engines.js.map +1 -0
  6. package/build/chunks/font.d.ts +203 -0
  7. package/build/chunks/font.d.ts.map +1 -0
  8. package/build/chunks/font.js +3550 -0
  9. package/build/chunks/font.js.map +1 -0
  10. package/build/chunks/fonts.d.ts +8 -0
  11. package/build/chunks/fonts.d.ts.map +1 -0
  12. package/build/chunks/fonts.js +13 -0
  13. package/build/chunks/fonts.js.map +1 -0
  14. package/build/chunks/measurer.d.ts +67 -0
  15. package/build/chunks/measurer.d.ts.map +1 -0
  16. package/build/chunks/measurer.js +277 -0
  17. package/build/chunks/measurer.js.map +1 -0
  18. package/build/chunks/sfnt.d.ts +22 -0
  19. package/build/chunks/sfnt.d.ts.map +1 -0
  20. package/build/chunks/text.js.map +1 -1
  21. package/build/fonts-inspect.d.ts +10 -0
  22. package/build/fonts-inspect.d.ts.map +1 -0
  23. package/build/fonts-inspect.js +1 -0
  24. package/build/fonts.d.ts +5 -0
  25. package/build/fonts.js +4 -0
  26. package/build/index.d.ts +3 -1
  27. package/build/index.js +3 -1
  28. package/build/measurer.d.ts +2 -0
  29. package/build/measurer.js +1 -0
  30. package/package.json +17 -3
  31. package/src/fonts/bidi.ts +125 -0
  32. package/src/fonts/binary.ts +41 -0
  33. package/src/fonts/engines.ts +38 -0
  34. package/src/fonts/font.ts +442 -0
  35. package/src/fonts/gvar.ts +220 -0
  36. package/src/fonts/index.ts +10 -0
  37. package/src/fonts/indic.ts +585 -0
  38. package/src/fonts/joining.ts +141 -0
  39. package/src/fonts/kerning.ts +217 -0
  40. package/src/fonts/sfnt.ts +102 -0
  41. package/src/fonts/substitution.ts +586 -0
  42. package/src/fonts/tables.ts +140 -0
  43. package/src/fonts/variations.ts +175 -0
  44. package/src/fonts/words.ts +119 -0
  45. package/src/fonts-inspect.ts +8 -0
  46. package/src/fonts.ts +8 -0
  47. package/src/index.ts +2 -0
  48. package/src/measurer/advance.ts +307 -0
  49. package/src/measurer.ts +5 -0
  50. package/src/text/rope.ts +1 -1
@@ -0,0 +1,3550 @@
1
+ import { fnv1a32 } from "@mark1russell7/rigor-generators";
2
+ // src/fonts/tables.ts
3
+ /** This function reads the header fields from `head`, `hhea` and `maxp`. */
4
+ function readHeader(head, hhea, maxp) {
5
+ if (head.u32(12) !== 1594834165) throw new Error("the head table has a bad magic number");
6
+ return {
7
+ revision: head.u32(4),
8
+ checksumAdjustment: head.u32(8),
9
+ unitsPerEm: head.u16(18),
10
+ numberOfHMetrics: hhea.u16(34),
11
+ numGlyphs: maxp.u16(4)
12
+ };
13
+ }
14
+ /** This function gives the advance of each glyph in font units, from `hmtx`. */
15
+ function readAdvances(hmtx, header) {
16
+ const advances = new Uint16Array(header.numGlyphs);
17
+ let last = 0;
18
+ for (let glyph = 0; glyph < header.numGlyphs; glyph++) {
19
+ if (glyph < header.numberOfHMetrics) last = hmtx.u16(glyph * 4);
20
+ advances[glyph] = last;
21
+ }
22
+ return advances;
23
+ }
24
+ /** This function gives the map from code points to glyphs of `cmap`. It uses a format 12 subtable if there is one, else a format 4 subtable. */
25
+ function readCharacterMap(cmap) {
26
+ const count = cmap.u16(2);
27
+ let best;
28
+ for (let i = 0; i < count; i++) {
29
+ const record = 4 + i * 8;
30
+ const platform = cmap.u16(record);
31
+ const encoding = cmap.u16(record + 2);
32
+ const offset = cmap.u32(record + 4);
33
+ const format = cmap.u16(offset);
34
+ if (!(platform === 0 || platform === 3 && (encoding === 1 || encoding === 10))) continue;
35
+ const rank = format === 12 ? 2 : format === 4 ? 1 : 0;
36
+ if (rank > 0 && (best === void 0 || rank > best.rank)) best = {
37
+ rank,
38
+ offset
39
+ };
40
+ }
41
+ if (best === void 0) throw new Error("the cmap table has no Unicode subtable of format 4 or 12");
42
+ const table = cmap.part(best.offset);
43
+ const map = /* @__PURE__ */ new Map();
44
+ if (best.rank === 2) {
45
+ const groups = table.u32(12);
46
+ for (let g = 0; g < groups; g++) {
47
+ const at = 16 + g * 12;
48
+ const start = table.u32(at);
49
+ const end = table.u32(at + 4);
50
+ const glyph = table.u32(at + 8);
51
+ for (let c = start; c <= end; c++) map.set(c, glyph + c - start);
52
+ }
53
+ return map;
54
+ }
55
+ const segments = table.u16(6) / 2;
56
+ const ends = 14;
57
+ const starts = ends + segments * 2 + 2;
58
+ const deltas = starts + segments * 2;
59
+ const ranges = deltas + segments * 2;
60
+ for (let s = 0; s < segments; s++) {
61
+ const start = table.u16(starts + s * 2);
62
+ const end = table.u16(ends + s * 2);
63
+ const delta = table.u16(deltas + s * 2);
64
+ const range = table.u16(ranges + s * 2);
65
+ for (let c = start; c <= end && c !== 65535; c++) {
66
+ let glyph;
67
+ if (range === 0) glyph = c + delta & 65535;
68
+ else {
69
+ glyph = table.u16(ranges + s * 2 + range + (c - start) * 2);
70
+ if (glyph !== 0) glyph = glyph + delta & 65535;
71
+ }
72
+ if (glyph !== 0) map.set(c, glyph);
73
+ }
74
+ }
75
+ return map;
76
+ }
77
+ /** This function gives the class of a glyph in a ClassDef table. A glyph that the table does not list has the class 0. */
78
+ function classOf(table, glyph) {
79
+ const format = table.u16(0);
80
+ if (format === 1) {
81
+ const start = table.u16(2);
82
+ const count = table.u16(4);
83
+ return glyph >= start && glyph < start + count ? table.u16(6 + (glyph - start) * 2) : 0;
84
+ }
85
+ if (format === 2) {
86
+ let low = 0;
87
+ let high = table.u16(2) - 1;
88
+ while (low <= high) {
89
+ const middle = low + high >> 1;
90
+ const at = 4 + middle * 6;
91
+ if (glyph < table.u16(at)) high = middle - 1;
92
+ else if (glyph > table.u16(at + 2)) low = middle + 1;
93
+ else return table.u16(at + 4);
94
+ }
95
+ return 0;
96
+ }
97
+ throw new Error(`the ClassDef format ${format} is unknown`);
98
+ }
99
+ /** This function gives the index of a glyph in a Coverage table, or -1 when the table does not cover the glyph. */
100
+ function coverageIndex(table, glyph) {
101
+ const format = table.u16(0);
102
+ let low = 0;
103
+ let high = table.u16(2) - 1;
104
+ if (format === 1) {
105
+ while (low <= high) {
106
+ const middle = low + high >> 1;
107
+ const value = table.u16(4 + middle * 2);
108
+ if (glyph < value) high = middle - 1;
109
+ else if (glyph > value) low = middle + 1;
110
+ else return middle;
111
+ }
112
+ return -1;
113
+ }
114
+ if (format === 2) {
115
+ while (low <= high) {
116
+ const middle = low + high >> 1;
117
+ const at = 4 + middle * 6;
118
+ if (glyph < table.u16(at)) high = middle - 1;
119
+ else if (glyph > table.u16(at + 2)) low = middle + 1;
120
+ else return table.u16(at + 4) + glyph - table.u16(at);
121
+ }
122
+ return -1;
123
+ }
124
+ throw new Error(`the Coverage format ${format} is unknown`);
125
+ }
126
+ /** The GDEF glyph classes: 1 base, 2 ligature, 3 mark, 4 component. */
127
+ function readGlyphClasses(gdef) {
128
+ if (gdef === void 0) return () => 0;
129
+ const offset = gdef.u16(4);
130
+ if (offset === 0) return () => 0;
131
+ const table = gdef.part(offset);
132
+ return (glyph) => classOf(table, glyph);
133
+ }
134
+ /** This function gives the size in bytes of a ValueRecord of the format `format`. */
135
+ function valueSize(format) {
136
+ let bits = format & 255;
137
+ let size = 0;
138
+ while (bits !== 0) {
139
+ size += bits & 1;
140
+ bits >>= 1;
141
+ }
142
+ return size * 2;
143
+ }
144
+ /**
145
+ * This function gives the XAdvance of a ValueRecord at `offset`, or 0 when the format has none. The offset of a device table
146
+ * is relative to `parent`. A VariationIndex table adds its delta from `store`, not rounded, as HarfBuzz adds it. A device
147
+ * table of hinting deltas adds nothing, because the shaper of the browser does not set a ppem.
148
+ */
149
+ function xAdvance(table, offset, format, parent, store) {
150
+ const value = (format & 4) === 0 ? 0 : table.i16(offset + valueSize(format & 3));
151
+ let delta = 0;
152
+ if ((format & 64) !== 0 && store !== void 0) {
153
+ const device = table.u16(offset + valueSize(format & 63));
154
+ if (device !== 0 && parent.u16(device + 4) === 32768) delta = store.delta(parent.u16(device), parent.u16(device + 2));
155
+ }
156
+ return {
157
+ value: value + delta,
158
+ delta
159
+ };
160
+ }
161
+ /** This function makes the value of a pair from the XAdvance of its two glyphs. */
162
+ function pairValue(first, second, skipSecond) {
163
+ return {
164
+ first: first.value,
165
+ second: second.value,
166
+ firstDelta: first.delta,
167
+ secondDelta: second.delta,
168
+ skipSecond
169
+ };
170
+ }
171
+ /** This function makes the pair function of one PairPos subtable. */
172
+ function pairSubtable(table, store) {
173
+ const format = table.u16(0);
174
+ const coverage = table.part(table.u16(2));
175
+ const format1 = table.u16(4);
176
+ const format2 = table.u16(6);
177
+ const size1 = valueSize(format1);
178
+ const size2 = valueSize(format2);
179
+ const skipSecond = format2 !== 0;
180
+ if (format === 1) return (first, second) => {
181
+ const index = coverageIndex(coverage, first);
182
+ if (index < 0) return void 0;
183
+ const set = table.part(table.u16(10 + index * 2));
184
+ const recordSize = 2 + size1 + size2;
185
+ let low = 0;
186
+ let high = set.u16(0) - 1;
187
+ while (low <= high) {
188
+ const middle = low + high >> 1;
189
+ const at = 2 + middle * recordSize;
190
+ const glyph = set.u16(at);
191
+ if (second < glyph) high = middle - 1;
192
+ else if (second > glyph) low = middle + 1;
193
+ else return pairValue(xAdvance(set, at + 2, format1, set, store), xAdvance(set, at + 2 + size1, format2, set, store), skipSecond);
194
+ }
195
+ };
196
+ if (format === 2) {
197
+ const classes1 = table.part(table.u16(8));
198
+ const classes2 = table.part(table.u16(10));
199
+ const count1 = table.u16(12);
200
+ const count2 = table.u16(14);
201
+ return (first, second) => {
202
+ if (coverageIndex(coverage, first) < 0) return void 0;
203
+ const class1 = classOf(classes1, first);
204
+ const class2 = classOf(classes2, second);
205
+ if (class1 >= count1 || class2 >= count2) return void 0;
206
+ const at = 16 + (class1 * count2 + class2) * (size1 + size2);
207
+ return pairValue(xAdvance(table, at, format1, table, store), xAdvance(table, at + size1, format2, table, store), skipSecond);
208
+ };
209
+ }
210
+ throw new Error(`the PairPos format ${format} is unknown`);
211
+ }
212
+ /** This function gives the tags of the scripts to try, in order, for a script tag, as HarfBuzz tries them. */
213
+ function scriptTags(script) {
214
+ return [
215
+ ...script === "deva" ? ["dev2", "deva"] : [script],
216
+ "DFLT",
217
+ "dflt",
218
+ "latn"
219
+ ];
220
+ }
221
+ /** The GPOS features with pair lookups that HarfBuzz applies to horizontal text. */
222
+ const pairFeatures = /* @__PURE__ */ new Set(["kern", "dist"]);
223
+ /** The lookup flag that selects a mark filtering set of GDEF. */
224
+ const USE_MARK_FILTERING_SET$1 = 16;
225
+ /**
226
+ * This function gives the pair lookups of the features `kern` and `dist` of GPOS for a script, in the order of the lookup
227
+ * list. The store gives the deltas of a variable font at its instance. The filter gives the mark filtering sets of GDEF.
228
+ */
229
+ function readGposKerning(gpos, script, store, filter) {
230
+ const scripts = gpos.part(gpos.u16(4));
231
+ const features = gpos.part(gpos.u16(6));
232
+ const lookups = gpos.part(gpos.u16(8));
233
+ const scriptCount = scripts.u16(0);
234
+ let langSys;
235
+ for (const wanted of scriptTags(script)) {
236
+ for (let i = 0; i < scriptCount && langSys === void 0; i++) {
237
+ if (scripts.tag(2 + i * 6) !== wanted) continue;
238
+ const table = scripts.part(scripts.u16(2 + i * 6 + 4));
239
+ const fallback = table.u16(0);
240
+ if (fallback !== 0) langSys = table.part(fallback);
241
+ }
242
+ if (langSys !== void 0) break;
243
+ }
244
+ if (langSys === void 0) return [];
245
+ const indices = /* @__PURE__ */ new Set();
246
+ const featureCount = langSys.u16(4);
247
+ for (let i = 0; i < featureCount; i++) {
248
+ const record = 2 + langSys.u16(6 + i * 2) * 6;
249
+ if (!pairFeatures.has(features.tag(record))) continue;
250
+ const feature = features.part(features.u16(record + 4));
251
+ for (let k = 0; k < feature.u16(2); k++) indices.add(feature.u16(4 + k * 2));
252
+ }
253
+ return [...indices].sort((a, b) => a - b).flatMap((index) => {
254
+ const lookup = lookups.part(lookups.u16(2 + index * 2));
255
+ const subtables = [];
256
+ const type = lookup.u16(0);
257
+ for (let s = 0; s < lookup.u16(4); s++) {
258
+ let subtable = lookup.part(lookup.u16(6 + s * 2));
259
+ let subtype = type;
260
+ if (type === 9) {
261
+ subtype = subtable.u16(2);
262
+ subtable = subtable.part(subtable.u32(4));
263
+ }
264
+ if (subtype === 2) subtables.push(pairSubtable(subtable, store));
265
+ }
266
+ if (subtables.length === 0) return [];
267
+ const flags = lookup.u16(2);
268
+ const markSet = (flags & USE_MARK_FILTERING_SET$1) !== 0 && filter !== void 0 ? lookup.u16(6 + lookup.u16(4) * 2) : -1;
269
+ const skip = markSet < 0 ? void 0 : (glyph) => {
270
+ const kind = filter.glyphClass(glyph);
271
+ if ((flags & 2) !== 0 && kind === 1) return true;
272
+ if ((flags & 4) !== 0 && kind === 2) return true;
273
+ if (kind !== 3) return false;
274
+ return (flags & 8) !== 0 || !filter.inMarkSet(markSet, glyph);
275
+ };
276
+ return [{
277
+ flags,
278
+ ...skip === void 0 ? {} : { skip },
279
+ pair(first, second) {
280
+ for (const subtable of subtables) {
281
+ const value = subtable(first, second);
282
+ if (value !== void 0) return value;
283
+ }
284
+ }
285
+ }];
286
+ });
287
+ }
288
+ /** This function gives the pairs of format 0 of the `kern` table, as one lookup. */
289
+ function readKernTable(kern) {
290
+ if (kern.u16(0) !== 0) return [];
291
+ const pairs = /* @__PURE__ */ new Map();
292
+ const count = kern.u16(2);
293
+ let offset = 4;
294
+ for (let t = 0; t < count; t++) {
295
+ const length = kern.u16(offset + 2);
296
+ if ((kern.u16(offset + 4) & 65287) === 1) {
297
+ const n = kern.u16(offset + 6);
298
+ for (let i = 0; i < n; i++) {
299
+ const at = offset + 14 + i * 6;
300
+ const key = kern.u16(at) * 65536 + kern.u16(at + 2);
301
+ pairs.set(key, (pairs.get(key) ?? 0) + kern.i16(at + 4));
302
+ }
303
+ }
304
+ offset += length;
305
+ }
306
+ if (pairs.size === 0) return [];
307
+ return [{
308
+ flags: 0,
309
+ pair(first, second) {
310
+ const value = pairs.get(first * 65536 + second);
311
+ return value === void 0 ? void 0 : {
312
+ first: value,
313
+ second: 0,
314
+ skipSecond: false
315
+ };
316
+ }
317
+ }];
318
+ }
319
+ // src/fonts/binary.ts
320
+ /** A big-endian reader over a part of a font file. The offsets are relative to the start of the part. */
321
+ var Reader = class Reader {
322
+ bytes;
323
+ view;
324
+ constructor(bytes) {
325
+ this.bytes = bytes;
326
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
327
+ }
328
+ get length() {
329
+ return this.bytes.byteLength;
330
+ }
331
+ u8(offset) {
332
+ return this.view.getUint8(offset);
333
+ }
334
+ u16(offset) {
335
+ return this.view.getUint16(offset);
336
+ }
337
+ i16(offset) {
338
+ return this.view.getInt16(offset);
339
+ }
340
+ u32(offset) {
341
+ return this.view.getUint32(offset);
342
+ }
343
+ tag(offset) {
344
+ return String.fromCharCode(this.u8(offset), this.u8(offset + 1), this.u8(offset + 2), this.u8(offset + 3));
345
+ }
346
+ /** This method gives a reader over the bytes from `offset`, with the length `length` or to the end. */
347
+ part(offset, length) {
348
+ const end = length === void 0 ? this.bytes.byteLength : offset + length;
349
+ if (offset < 0 || end > this.bytes.byteLength || offset > end) throw new RangeError(`the part ${offset}..${end} is outside ${this.bytes.byteLength} bytes`);
350
+ return new Reader(this.bytes.subarray(offset, end));
351
+ }
352
+ };
353
+ // src/fonts/sfnt.ts
354
+ /** The tags of the WOFF2 table directory, in the order of the WOFF2 specification. */
355
+ const knownTags = [
356
+ "cmap",
357
+ "head",
358
+ "hhea",
359
+ "hmtx",
360
+ "maxp",
361
+ "name",
362
+ "OS/2",
363
+ "post",
364
+ "cvt ",
365
+ "fpgm",
366
+ "glyf",
367
+ "loca",
368
+ "prep",
369
+ "CFF ",
370
+ "VORG",
371
+ "EBDT",
372
+ "EBLC",
373
+ "gasp",
374
+ "hdmx",
375
+ "kern",
376
+ "LTSH",
377
+ "PCLT",
378
+ "VDMX",
379
+ "vhea",
380
+ "vmtx",
381
+ "BASE",
382
+ "GDEF",
383
+ "GPOS",
384
+ "GSUB",
385
+ "EBSC",
386
+ "JSTF",
387
+ "MATH",
388
+ "CBDT",
389
+ "CBLC",
390
+ "COLR",
391
+ "CPAL",
392
+ "SVG ",
393
+ "sbix",
394
+ "acnt",
395
+ "avar",
396
+ "bdat",
397
+ "bloc",
398
+ "bsln",
399
+ "cvar",
400
+ "fdsc",
401
+ "feat",
402
+ "fmtx",
403
+ "fvar",
404
+ "gvar",
405
+ "hsty",
406
+ "just",
407
+ "lcar",
408
+ "mort",
409
+ "morx",
410
+ "opbd",
411
+ "prop",
412
+ "trak",
413
+ "Zapf",
414
+ "Silf",
415
+ "Glat",
416
+ "Gloc",
417
+ "Feat",
418
+ "Sill"
419
+ ];
420
+ /** This function gives the tables of a TrueType, OpenType or WOFF2 file. */
421
+ function readTables(bytes, decompress) {
422
+ const file = new Reader(bytes);
423
+ if (file.length < 12) throw new RangeError("the file is too short for a font");
424
+ const signature = file.tag(0);
425
+ if (signature === "wOF2") {
426
+ if (decompress === void 0) throw new Error("a WOFF2 file needs a Brotli decompress function");
427
+ return readWoff2(file, decompress);
428
+ }
429
+ if (signature === "wOFF") throw new Error("WOFF 1 files are not supported");
430
+ if (signature === "ttcf") throw new Error("font collections are not supported");
431
+ if (file.u32(0) !== 65536 && signature !== "OTTO" && signature !== "true") throw new Error(`the signature ${JSON.stringify(signature)} is not a font`);
432
+ const tables = /* @__PURE__ */ new Map();
433
+ const count = file.u16(4);
434
+ for (let i = 0; i < count; i++) {
435
+ const record = 12 + i * 16;
436
+ tables.set(file.tag(record), file.part(file.u32(record + 8), file.u32(record + 12)));
437
+ }
438
+ return tables;
439
+ }
440
+ /** This function reads a UIntBase128 number of WOFF2 and gives the number and the next offset. */
441
+ function base128(file, offset) {
442
+ let value = 0;
443
+ for (let i = 0; i < 5; i++) {
444
+ const byte = file.u8(offset + i);
445
+ if (i === 0 && byte === 128) throw new RangeError("a UIntBase128 has a leading zero");
446
+ value = value * 128 + (byte & 127);
447
+ if ((byte & 128) === 0) return [value, offset + i + 1];
448
+ }
449
+ throw new RangeError("a UIntBase128 is longer than 5 bytes");
450
+ }
451
+ /**
452
+ * This function gives the tables of a WOFF2 file. The `glyf` and `loca` tables stay in their transformed form,
453
+ * because the width does not use them. A transformed `hmtx` table becomes a plain table again.
454
+ */
455
+ function readWoff2(file, decompress) {
456
+ if (file.u32(4) === 1953784678) throw new Error("WOFF2 font collections are not supported");
457
+ const count = file.u16(12);
458
+ const compressedLength = file.u32(20);
459
+ let offset = 48;
460
+ const entries = [];
461
+ for (let i = 0; i < count; i++) {
462
+ const flags = file.u8(offset++);
463
+ let tag;
464
+ if ((flags & 63) === 63) {
465
+ tag = file.tag(offset);
466
+ offset += 4;
467
+ } else {
468
+ const known = knownTags[flags & 63];
469
+ if (known === void 0) throw new RangeError(`the WOFF2 tag index ${flags & 63} is unknown`);
470
+ tag = known;
471
+ }
472
+ const version = flags >> 6 & 3;
473
+ let length;
474
+ [length, offset] = base128(file, offset);
475
+ const transformed = tag === "glyf" || tag === "loca" ? version === 0 : version !== 0;
476
+ if (transformed) [length, offset] = base128(file, offset);
477
+ entries.push({
478
+ tag,
479
+ length,
480
+ transformed
481
+ });
482
+ }
483
+ const data = new Reader(decompress(file.bytes.subarray(offset, offset + compressedLength)));
484
+ const tables = /* @__PURE__ */ new Map();
485
+ let at = 0;
486
+ for (const entry of entries) {
487
+ tables.set(entry.tag, data.part(at, entry.length));
488
+ at += entry.length;
489
+ }
490
+ if (entries.find((entry) => entry.tag === "hmtx")?.transformed === true) tables.set("hmtx", untransformHmtx(tables));
491
+ return tables;
492
+ }
493
+ /** This function gives a plain `hmtx` table from the transform of WOFF2. Only the advances matter, so each side bearing is 0. */
494
+ function untransformHmtx(tables) {
495
+ const source = tables.get("hmtx");
496
+ const hhea = tables.get("hhea");
497
+ if (hhea === void 0) throw new Error("a transformed hmtx needs hhea");
498
+ const metrics = hhea.u16(34);
499
+ const plain = new Uint8Array(metrics * 4);
500
+ const view = new DataView(plain.buffer);
501
+ for (let i = 0; i < metrics; i++) view.setUint16(i * 4, source.u16(1 + i * 2));
502
+ return new Reader(plain);
503
+ }
504
+ // src/fonts/substitution.ts
505
+ /**
506
+ * The GSUB features that the default model applies: the features that HarfBuzz and Chromium apply to horizontal text
507
+ * when the page does not change them.
508
+ */
509
+ const defaultFeatures = [
510
+ "ccmp",
511
+ "locl",
512
+ "rlig",
513
+ "liga",
514
+ "clig",
515
+ "calt",
516
+ "rclt"
517
+ ];
518
+ /** The lookup flag that selects a mark filtering set of GDEF. */
519
+ const USE_MARK_FILTERING_SET = 16;
520
+ /** The largest depth of nested lookups, as in HarfBuzz. */
521
+ const MAX_NESTING = 64;
522
+ /** This function reads the glyph classes, the mark attachment classes and the mark filtering sets of GDEF. */
523
+ function readGlyphFilter(gdef, glyphClass) {
524
+ const attachOffset = gdef === void 0 ? 0 : gdef.u16(10);
525
+ const attach = attachOffset === 0 ? void 0 : gdef.part(attachOffset);
526
+ const setsOffset = gdef === void 0 || gdef.u16(2) < 2 ? 0 : gdef.u16(12);
527
+ const sets = setsOffset === 0 ? void 0 : gdef.part(setsOffset);
528
+ return {
529
+ glyphClass,
530
+ markAttachClass: (glyph) => attach === void 0 ? 0 : classOf(attach, glyph),
531
+ inMarkSet(set, glyph) {
532
+ if (sets === void 0 || set >= sets.u16(2)) return false;
533
+ return coverageIndex(sets.part(sets.u32(4 + set * 4)), glyph) >= 0;
534
+ }
535
+ };
536
+ }
537
+ function records(table, at, count) {
538
+ return Array.from({ length: count }, (_, k) => ({
539
+ sequenceIndex: table.u16(at + k * 4),
540
+ lookupIndex: table.u16(at + k * 4 + 2)
541
+ }));
542
+ }
543
+ /** The lookups of one GSUB table. It reads each lookup at its first use. */
544
+ var Gsub = class {
545
+ lookups;
546
+ filter;
547
+ cache = /* @__PURE__ */ new Map();
548
+ constructor(table, filter) {
549
+ this.lookups = table.part(table.u16(8));
550
+ this.filter = filter;
551
+ }
552
+ lookup(index) {
553
+ if (index >= this.lookups.u16(0)) return void 0;
554
+ let found = this.cache.get(index);
555
+ if (found === void 0) {
556
+ const table = this.lookups.part(this.lookups.u16(2 + index * 2));
557
+ const type = table.u16(0);
558
+ const flags = table.u16(2);
559
+ const count = table.u16(4);
560
+ const markSet = (flags & USE_MARK_FILTERING_SET) !== 0 ? table.u16(6 + count * 2) : -1;
561
+ const subtables = [];
562
+ let subtype = type;
563
+ for (let s = 0; s < count; s++) {
564
+ const subtable = table.part(table.u16(6 + s * 2));
565
+ if (type === 7) {
566
+ subtype = subtable.u16(2);
567
+ subtables.push(subtable.part(subtable.u32(4)));
568
+ } else subtables.push(subtable);
569
+ }
570
+ const { glyphClass, markAttachClass, inMarkSet } = this.filter;
571
+ const skip = (glyph) => {
572
+ const kind = glyphClass(glyph);
573
+ if ((flags & 2) !== 0 && kind === 1) return true;
574
+ if ((flags & 4) !== 0 && kind === 2) return true;
575
+ if (kind !== 3) return false;
576
+ if ((flags & 8) !== 0) return true;
577
+ if (markSet >= 0) return !inMarkSet(markSet, glyph);
578
+ const attach = flags >> 8;
579
+ return attach !== 0 && markAttachClass(glyph) !== attach;
580
+ };
581
+ found = {
582
+ type: subtype,
583
+ flags,
584
+ subtables,
585
+ skip
586
+ };
587
+ this.cache.set(index, found);
588
+ }
589
+ return found;
590
+ }
591
+ /**
592
+ * The masks of the glyphs of the current call of `applyAll`, or `undefined`. A substitution that adds or removes glyphs
593
+ * changes this array in the same way.
594
+ */
595
+ masks;
596
+ /** The data of the glyphs of the current call of `applyRun`, or `undefined`. */
597
+ infos;
598
+ /** The mask of the current call of `applyRun`. Each input glyph needs a bit of it. */
599
+ lookupMask = -1;
600
+ /** It is true when the matched glyphs of the current call of `applyRun` need the syllable of the first glyph. */
601
+ perSyllable = false;
602
+ /**
603
+ * This function replaces `count` glyphs at `at` with `replacement`. Each new glyph gets the mask and the data of the
604
+ * glyph at `at`.
605
+ */
606
+ splice(glyphs, at, count, replacement) {
607
+ glyphs.splice(at, count, ...replacement);
608
+ const masks = this.masks;
609
+ if (masks !== void 0) masks.splice(at, count, ...replacement.map(() => masks[at] ?? 0));
610
+ const infos = this.infos;
611
+ if (infos !== void 0) {
612
+ const source = infos[at];
613
+ infos.splice(at, count, ...replacement.map(() => ({ ...source })));
614
+ }
615
+ }
616
+ /** This function adds the glyph properties `set` to the glyph at `i`, and then removes the properties `clear`. */
617
+ setProps(i, set, clear = 0) {
618
+ const info = this.infos?.[i];
619
+ if (info !== void 0) info.props = (info.props | set) & ~clear;
620
+ }
621
+ /**
622
+ * This function tells if the glyph at `j` can match for the glyph at `first`. An input glyph needs a bit of the mask of
623
+ * the lookup. With the per-syllable matching, each glyph needs the syllable of `first`.
624
+ */
625
+ admits(j, first, input) {
626
+ const infos = this.infos;
627
+ if (infos === void 0) return true;
628
+ if (input && (infos[j].mask & this.lookupMask) === 0) return false;
629
+ const syllable = infos[first]?.syllable ?? 0;
630
+ return !this.perSyllable || syllable === 0 || infos[j].syllable === syllable;
631
+ }
632
+ /** This function applies a lookup to all the glyphs with their data `infos`, as `applyInfos` of `SubstitutionLookup` tells. */
633
+ applyRun(index, glyphs, infos, mask, perSyllable) {
634
+ const lookup = this.lookup(index);
635
+ if (lookup === void 0) return;
636
+ this.infos = infos;
637
+ this.lookupMask = mask;
638
+ this.perSyllable = perSyllable;
639
+ try {
640
+ this.applyMasked(lookup, glyphs, (i) => (infos[i].mask & mask) !== 0);
641
+ } finally {
642
+ this.infos = void 0;
643
+ this.lookupMask = -1;
644
+ this.perSyllable = false;
645
+ }
646
+ }
647
+ /**
648
+ * This function applies a lookup to all the glyphs, as the main loop of a shaper does. With `masks`, the lookup starts
649
+ * only at a glyph whose mask has a bit of `mask`.
650
+ */
651
+ applyAll(index, glyphs, masks, mask = -1) {
652
+ const lookup = this.lookup(index);
653
+ if (lookup === void 0) return;
654
+ this.masks = masks;
655
+ try {
656
+ this.applyMasked(lookup, glyphs, (i) => masks === void 0 || ((masks[i] ?? 0) & mask) !== 0);
657
+ } finally {
658
+ this.masks = void 0;
659
+ }
660
+ }
661
+ applyMasked(lookup, glyphs, selected) {
662
+ if (lookup.type === 8) {
663
+ for (let i = glyphs.length - 1; i >= 0; i--) if (selected(i) && !lookup.skip(glyphs[i])) this.reverse(lookup, glyphs, i);
664
+ return;
665
+ }
666
+ let i = 0;
667
+ while (i < glyphs.length) {
668
+ if (!selected(i) || lookup.skip(glyphs[i])) {
669
+ i++;
670
+ continue;
671
+ }
672
+ const before = glyphs.length;
673
+ const next = this.applyAt(lookup, glyphs, i, 0);
674
+ i = next < 0 ? i + 1 : next > i || glyphs.length < before ? next : i + 1;
675
+ }
676
+ }
677
+ /** This function applies the first subtable of a lookup that matches at `i`. It gives the next position, or -1. */
678
+ applyAt(lookup, glyphs, i, depth) {
679
+ for (const subtable of lookup.subtables) {
680
+ const next = this.subtable(lookup, subtable, glyphs, i, depth);
681
+ if (next >= 0) return next;
682
+ }
683
+ return -1;
684
+ }
685
+ nextOf(lookup, glyphs, from) {
686
+ for (let j = from; j < glyphs.length; j++) if (!lookup.skip(glyphs[j])) return j;
687
+ return -1;
688
+ }
689
+ previousOf(lookup, glyphs, from) {
690
+ for (let j = from; j >= 0; j--) if (!lookup.skip(glyphs[j])) return j;
691
+ return -1;
692
+ }
693
+ /**
694
+ * This function matches `count` glyphs after the position `i` with a test for each. It gives the positions of the
695
+ * matched glyphs, the first glyph at `i` included, or `undefined`.
696
+ */
697
+ matchInput(lookup, glyphs, i, count, test) {
698
+ const positions = [i];
699
+ let at = i;
700
+ for (let k = 0; k < count; k++) {
701
+ at = this.nextOf(lookup, glyphs, at + 1);
702
+ if (at < 0 || !this.admits(at, i, true) || !test(k, glyphs[at])) return void 0;
703
+ positions.push(at);
704
+ }
705
+ return positions;
706
+ }
707
+ matchBacktrack(lookup, glyphs, i, count, test) {
708
+ let at = i;
709
+ for (let k = 0; k < count; k++) {
710
+ at = this.previousOf(lookup, glyphs, at - 1);
711
+ if (at < 0 || !this.admits(at, i, false) || !test(k, glyphs[at])) return false;
712
+ }
713
+ return true;
714
+ }
715
+ matchLookahead(lookup, glyphs, first, last, count, test) {
716
+ let at = last;
717
+ for (let k = 0; k < count; k++) {
718
+ at = this.nextOf(lookup, glyphs, at + 1);
719
+ if (at < 0 || !this.admits(at, first, false) || !test(k, glyphs[at])) return false;
720
+ }
721
+ return true;
722
+ }
723
+ /** This function applies the lookups of the records of a context rule at the matched positions. It gives the next position. */
724
+ applyRecords(glyphs, positions, list, depth) {
725
+ if (depth < MAX_NESTING) for (const record of list) {
726
+ const at = positions[record.sequenceIndex];
727
+ const nested = this.lookup(record.lookupIndex);
728
+ if (at === void 0 || at >= glyphs.length || nested === void 0 || nested.skip(glyphs[at])) continue;
729
+ const before = glyphs.length;
730
+ if (nested.type === 8) this.reverse(nested, glyphs, at);
731
+ else this.applyAt(nested, glyphs, at, depth + 1);
732
+ const delta = glyphs.length - before;
733
+ if (delta > 0) {
734
+ for (let k = record.sequenceIndex + 1; k < positions.length; k++) positions[k] = positions[k] + delta;
735
+ positions.splice(record.sequenceIndex + 1, 0, ...Array.from({ length: delta }, (_, k) => at + 1 + k));
736
+ } else if (delta < 0) {
737
+ positions.splice(record.sequenceIndex + 1, Math.min(-delta, positions.length - record.sequenceIndex - 1));
738
+ for (let k = record.sequenceIndex + 1; k < positions.length; k++) positions[k] = positions[k] + delta;
739
+ }
740
+ }
741
+ return Math.min(glyphs.length, positions[positions.length - 1] + 1);
742
+ }
743
+ reverse(lookup, glyphs, i) {
744
+ for (const t of lookup.subtables) {
745
+ const index = coverageIndex(t.part(t.u16(2)), glyphs[i]);
746
+ if (index < 0) continue;
747
+ const backCount = t.u16(4);
748
+ const aheadAt = 6 + backCount * 2;
749
+ const aheadCount = t.u16(aheadAt);
750
+ const substituteAt = aheadAt + 2 + aheadCount * 2;
751
+ if (!this.matchBacktrack(lookup, glyphs, i, backCount, (k, g) => coverageIndex(t.part(t.u16(6 + k * 2)), g) >= 0)) continue;
752
+ if (!this.matchLookahead(lookup, glyphs, i, i, aheadCount, (k, g) => coverageIndex(t.part(t.u16(aheadAt + 2 + k * 2)), g) >= 0)) continue;
753
+ glyphs[i] = t.u16(substituteAt + 2 + index * 2);
754
+ this.setProps(i, 16);
755
+ return true;
756
+ }
757
+ return false;
758
+ }
759
+ /** This function tells if a lookup changes the sequence `glyphs` with no context, as `wouldApply` of `SubstitutionLookup` tells. */
760
+ wouldApply(index, glyphs) {
761
+ const lookup = this.lookup(index);
762
+ if (lookup === void 0 || glyphs.length === 0) return false;
763
+ const [first] = glyphs;
764
+ return lookup.subtables.some((t) => {
765
+ const format = t.u16(0);
766
+ if (lookup.type === 1 || lookup.type === 2 || lookup.type === 3 || lookup.type === 8) return glyphs.length === 1 && coverageIndex(t.part(t.u16(2)), first) >= 0;
767
+ if (lookup.type === 4) {
768
+ const index = coverageIndex(t.part(t.u16(2)), first);
769
+ if (index < 0) return false;
770
+ const set = t.part(t.u16(6 + index * 2));
771
+ return Array.from({ length: set.u16(0) }, (_, l) => set.part(set.u16(2 + l * 2))).some((ligature) => ligature.u16(2) === glyphs.length && glyphs.every((g, k) => k === 0 || ligature.u16(4 + (k - 1) * 2) === g));
772
+ }
773
+ if (lookup.type !== 5 && lookup.type !== 6) return false;
774
+ const chain = lookup.type === 6;
775
+ if (format === 3) {
776
+ const backCount = chain ? t.u16(2) : 0;
777
+ const inputAt = chain ? 4 + backCount * 2 : 2;
778
+ const count = t.u16(inputAt);
779
+ const coverageAt = chain ? inputAt + 2 : 6;
780
+ if (chain && (backCount !== 0 || t.u16(inputAt + 2 + count * 2) !== 0)) return false;
781
+ return count === glyphs.length && glyphs.every((g, k) => coverageIndex(t.part(t.u16(coverageAt + k * 2)), g) >= 0);
782
+ }
783
+ const index = coverageIndex(t.part(t.u16(2)), first);
784
+ if (index < 0) return false;
785
+ const classes = format === 2 ? t.part(t.u16(chain ? 6 : 4)) : void 0;
786
+ const value = (g) => classes === void 0 ? g : classOf(classes, g);
787
+ const setIndex = classes === void 0 ? index : value(first);
788
+ const setCountAt = chain ? format === 2 ? 10 : 4 : format === 2 ? 6 : 4;
789
+ if (setIndex >= t.u16(setCountAt) || t.u16(setCountAt + 2 + setIndex * 2) === 0) return false;
790
+ const set = t.part(t.u16(setCountAt + 2 + setIndex * 2));
791
+ for (let r = 0; r < set.u16(0); r++) {
792
+ const rule = set.part(set.u16(2 + r * 2));
793
+ const backCount = chain ? rule.u16(0) : 0;
794
+ const inputAt = chain ? 2 + backCount * 2 : 0;
795
+ const count = rule.u16(inputAt);
796
+ const inputStart = chain ? inputAt + 2 : 4;
797
+ if (chain && (backCount !== 0 || rule.u16(inputStart + Math.max(0, count - 1) * 2) !== 0)) continue;
798
+ if (count === glyphs.length && glyphs.every((g, k) => k === 0 || value(g) === rule.u16(inputStart + (k - 1) * 2))) return true;
799
+ }
800
+ return false;
801
+ });
802
+ }
803
+ /** This function applies one subtable at `i`. It gives the next position, or -1 when the subtable does not match. */
804
+ subtable(lookup, t, glyphs, i, depth) {
805
+ const glyph = glyphs[i];
806
+ const format = t.u16(0);
807
+ switch (lookup.type) {
808
+ case 1: {
809
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
810
+ if (index < 0) return -1;
811
+ glyphs[i] = format === 1 ? glyph + t.i16(4) & 65535 : t.u16(6 + index * 2);
812
+ this.setProps(i, 16);
813
+ return i + 1;
814
+ }
815
+ case 2: {
816
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
817
+ if (index < 0) return -1;
818
+ const sequence = t.part(t.u16(6 + index * 2));
819
+ const replacement = Array.from({ length: sequence.u16(0) }, (_, k) => sequence.u16(2 + k * 2));
820
+ this.splice(glyphs, i, 1, replacement);
821
+ for (let k = 0; k < replacement.length; k++) this.setProps(i + k, replacement.length > 1 ? 80 : 16);
822
+ return i + replacement.length;
823
+ }
824
+ case 3: {
825
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
826
+ if (index < 0) return -1;
827
+ const set = t.part(t.u16(6 + index * 2));
828
+ if (set.u16(0) > 0) glyphs[i] = set.u16(2);
829
+ this.setProps(i, 16);
830
+ return i + 1;
831
+ }
832
+ case 4: {
833
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
834
+ if (index < 0) return -1;
835
+ const set = t.part(t.u16(6 + index * 2));
836
+ for (let l = 0; l < set.u16(0); l++) {
837
+ const ligature = set.part(set.u16(2 + l * 2));
838
+ const components = ligature.u16(2);
839
+ const positions = this.matchInput(lookup, glyphs, i, components - 1, (k, g) => g === ligature.u16(4 + k * 2));
840
+ if (positions === void 0) continue;
841
+ glyphs[i] = ligature.u16(0);
842
+ this.setProps(i, 48, 64);
843
+ for (let k = positions.length - 1; k >= 1; k--) this.splice(glyphs, positions[k], 1, []);
844
+ return i + 1;
845
+ }
846
+ return -1;
847
+ }
848
+ case 5: return this.context(lookup, t, glyphs, i, depth);
849
+ case 6: return this.chainContext(lookup, t, glyphs, i, depth);
850
+ case 8: return this.reverse(lookup, glyphs, i) ? i + 1 : -1;
851
+ default: return -1;
852
+ }
853
+ }
854
+ context(lookup, t, glyphs, i, depth) {
855
+ const glyph = glyphs[i];
856
+ const format = t.u16(0);
857
+ if (format === 3) {
858
+ const count = t.u16(2);
859
+ const recordCount = t.u16(4);
860
+ if (count === 0 || coverageIndex(t.part(t.u16(6)), glyph) < 0) return -1;
861
+ const positions = this.matchInput(lookup, glyphs, i, count - 1, (k, g) => coverageIndex(t.part(t.u16(8 + k * 2)), g) >= 0);
862
+ if (positions === void 0) return -1;
863
+ return this.applyRecords(glyphs, positions, records(t, 6 + count * 2, recordCount), depth);
864
+ }
865
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
866
+ if (index < 0) return -1;
867
+ const classes = format === 2 ? t.part(t.u16(4)) : void 0;
868
+ const setIndex = classes === void 0 ? index : classOf(classes, glyph);
869
+ if (setIndex >= t.u16(classes === void 0 ? 4 : 6)) return -1;
870
+ const setOffset = t.u16((classes === void 0 ? 6 : 8) + setIndex * 2);
871
+ if (setOffset === 0) return -1;
872
+ const set = t.part(setOffset);
873
+ const value = (g) => classes === void 0 ? g : classOf(classes, g);
874
+ for (let r = 0; r < set.u16(0); r++) {
875
+ const rule = set.part(set.u16(2 + r * 2));
876
+ const count = rule.u16(0);
877
+ const recordCount = rule.u16(2);
878
+ const positions = this.matchInput(lookup, glyphs, i, count - 1, (k, g) => value(g) === rule.u16(4 + k * 2));
879
+ if (positions === void 0) continue;
880
+ return this.applyRecords(glyphs, positions, records(rule, 4 + (count - 1) * 2, recordCount), depth);
881
+ }
882
+ return -1;
883
+ }
884
+ chainContext(lookup, t, glyphs, i, depth) {
885
+ const glyph = glyphs[i];
886
+ const format = t.u16(0);
887
+ if (format === 3) {
888
+ const backCount = t.u16(2);
889
+ const inputAt = 4 + backCount * 2;
890
+ const inputCount = t.u16(inputAt);
891
+ const aheadAt = inputAt + 2 + inputCount * 2;
892
+ const aheadCount = t.u16(aheadAt);
893
+ const recordsAt = aheadAt + 2 + aheadCount * 2;
894
+ const covers = (offset, g) => coverageIndex(t.part(t.u16(offset)), g) >= 0;
895
+ if (inputCount === 0 || !covers(inputAt + 2, glyph)) return -1;
896
+ if (!this.matchBacktrack(lookup, glyphs, i, backCount, (k, g) => covers(4 + k * 2, g))) return -1;
897
+ const positions = this.matchInput(lookup, glyphs, i, inputCount - 1, (k, g) => covers(inputAt + 4 + k * 2, g));
898
+ if (positions === void 0) return -1;
899
+ if (!this.matchLookahead(lookup, glyphs, i, positions[positions.length - 1], aheadCount, (k, g) => covers(aheadAt + 2 + k * 2, g))) return -1;
900
+ return this.applyRecords(glyphs, positions, records(t, recordsAt + 2, t.u16(recordsAt)), depth);
901
+ }
902
+ const index = coverageIndex(t.part(t.u16(2)), glyph);
903
+ if (index < 0) return -1;
904
+ const byClass = format === 2;
905
+ const backClasses = byClass ? t.part(t.u16(4)) : void 0;
906
+ const inputClasses = byClass ? t.part(t.u16(6)) : void 0;
907
+ const aheadClasses = byClass ? t.part(t.u16(8)) : void 0;
908
+ const setIndex = inputClasses === void 0 ? index : classOf(inputClasses, glyph);
909
+ if (setIndex >= t.u16(byClass ? 10 : 4)) return -1;
910
+ const setOffset = t.u16((byClass ? 12 : 6) + setIndex * 2);
911
+ if (setOffset === 0) return -1;
912
+ const set = t.part(setOffset);
913
+ const valueIn = (classes, g) => classes === void 0 ? g : classOf(classes, g);
914
+ for (let r = 0; r < set.u16(0); r++) {
915
+ const rule = set.part(set.u16(2 + r * 2));
916
+ const backCount = rule.u16(0);
917
+ const inputAt = 2 + backCount * 2;
918
+ const inputCount = rule.u16(inputAt);
919
+ const aheadAt = inputAt + 2 + Math.max(0, inputCount - 1) * 2;
920
+ const aheadCount = rule.u16(aheadAt);
921
+ const recordsAt = aheadAt + 2 + aheadCount * 2;
922
+ if (!this.matchBacktrack(lookup, glyphs, i, backCount, (k, g) => valueIn(backClasses, g) === rule.u16(2 + k * 2))) continue;
923
+ const positions = this.matchInput(lookup, glyphs, i, inputCount - 1, (k, g) => valueIn(inputClasses, g) === rule.u16(inputAt + 2 + k * 2));
924
+ if (positions === void 0) continue;
925
+ if (!this.matchLookahead(lookup, glyphs, i, positions[positions.length - 1], aheadCount, (k, g) => valueIn(aheadClasses, g) === rule.u16(aheadAt + 2 + k * 2))) continue;
926
+ return this.applyRecords(glyphs, positions, records(rule, recordsAt + 2, rule.u16(recordsAt)), depth);
927
+ }
928
+ return -1;
929
+ }
930
+ };
931
+ /**
932
+ * This function gives the lookups of the features `features` of GSUB for a script, in the order of the lookup list, as
933
+ * HarfBuzz applies them. The lookups include the lookups of the necessary feature of the language system.
934
+ */
935
+ function readSubstitutions(gsub, script, features, filter) {
936
+ const scripts = gsub.part(gsub.u16(4));
937
+ const featureList = gsub.part(gsub.u16(6));
938
+ const scriptCount = scripts.u16(0);
939
+ let langSys;
940
+ for (const wanted of scriptTags(script)) {
941
+ for (let i = 0; i < scriptCount && langSys === void 0; i++) {
942
+ if (scripts.tag(2 + i * 6) !== wanted) continue;
943
+ const table = scripts.part(scripts.u16(2 + i * 6 + 4));
944
+ const fallback = table.u16(0);
945
+ if (fallback !== 0) langSys = table.part(fallback);
946
+ }
947
+ if (langSys !== void 0) break;
948
+ }
949
+ if (langSys === void 0 || features.length === 0) return [];
950
+ const wanted = new Set(features);
951
+ const indices = /* @__PURE__ */ new Map();
952
+ const add = (featureIndex, required) => {
953
+ const record = 2 + featureIndex * 6;
954
+ if (featureIndex >= featureList.u16(0)) return;
955
+ const tag = featureList.tag(record);
956
+ if (!required && !wanted.has(tag)) return;
957
+ const feature = featureList.part(featureList.u16(record + 4));
958
+ for (let k = 0; k < feature.u16(2); k++) {
959
+ const index = feature.u16(4 + k * 2);
960
+ const tags = indices.get(index) ?? [];
961
+ if (!tags.includes(tag)) tags.push(tag);
962
+ indices.set(index, tags);
963
+ }
964
+ };
965
+ const required = langSys.u16(2);
966
+ if (required !== 65535) add(required, true);
967
+ for (let i = 0; i < langSys.u16(4); i++) add(langSys.u16(6 + i * 2), false);
968
+ const table = new Gsub(gsub, filter);
969
+ const result = [];
970
+ for (const [index, tags] of [...indices].sort(([a], [b]) => a - b)) {
971
+ const lookup = table.lookup(index);
972
+ if (lookup === void 0) continue;
973
+ result.push({
974
+ index,
975
+ type: lookup.type,
976
+ flags: lookup.flags,
977
+ features: tags,
978
+ apply: (glyphs, masks, mask) => table.applyAll(index, glyphs, masks, mask),
979
+ applyInfos: (glyphs, infos, mask, perSyllable) => table.applyRun(index, glyphs, infos, mask, perSyllable),
980
+ wouldApply: (glyphs) => table.wouldApply(index, glyphs)
981
+ });
982
+ }
983
+ return result;
984
+ }
985
+ // src/fonts/variations.ts
986
+ /** This function reads a 16.16 fixed number. */
987
+ function fixed(table, offset) {
988
+ return (table.u16(offset) << 16 >> 16) + table.u16(offset + 2) / 65536;
989
+ }
990
+ /** This function reads a 2.14 fixed number. */
991
+ function f2dot14(table, offset) {
992
+ return table.i16(offset) / 16384;
993
+ }
994
+ /** This function rounds a number to the 2.14 grid, as HarfBuzz and FreeType do with each normalized coordinate. */
995
+ function toF2dot14(value) {
996
+ return Math.round(value * 16384) / 16384;
997
+ }
998
+ /** This function gives the axes of `fvar`. */
999
+ function readAxes(fvar) {
1000
+ const offset = fvar.u16(4);
1001
+ const count = fvar.u16(8);
1002
+ const size = fvar.u16(10);
1003
+ const axes = [];
1004
+ for (let i = 0; i < count; i++) {
1005
+ const at = offset + i * size;
1006
+ axes.push({
1007
+ tag: fvar.tag(at),
1008
+ min: fixed(fvar, at + 4),
1009
+ default: fixed(fvar, at + 8),
1010
+ max: fixed(fvar, at + 12)
1011
+ });
1012
+ }
1013
+ return axes;
1014
+ }
1015
+ /**
1016
+ * This function gives the normalized coordinates of an instance. Each coordinate is in -1 to 1, on the 2.14 grid. The
1017
+ * segment maps of `avar` change it when the font has them. A value outside the range of an axis is clamped to the range.
1018
+ */
1019
+ function normalize$1(axes, avar, instance) {
1020
+ const coordinates = axes.map((axis) => {
1021
+ const value = Math.min(axis.max, Math.max(axis.min, instance[axis.tag] ?? axis.default));
1022
+ if (value < axis.default) return toF2dot14(axis.default === axis.min ? 0 : (value - axis.default) / (axis.default - axis.min));
1023
+ if (value > axis.default) return toF2dot14(axis.max === axis.default ? 0 : (value - axis.default) / (axis.max - axis.default));
1024
+ return 0;
1025
+ });
1026
+ if (avar === void 0 || avar.u16(0) !== 1) return coordinates;
1027
+ let at = 8;
1028
+ const count = Math.min(avar.u16(6), axes.length);
1029
+ for (let a = 0; a < count; a++) {
1030
+ const pairs = avar.u16(at);
1031
+ const from = [];
1032
+ const to = [];
1033
+ for (let p = 0; p < pairs; p++) {
1034
+ from.push(f2dot14(avar, at + 2 + p * 4));
1035
+ to.push(f2dot14(avar, at + 4 + p * 4));
1036
+ }
1037
+ at += 2 + pairs * 4;
1038
+ coordinates[a] = toF2dot14(mapSegment(from, to, coordinates[a]));
1039
+ }
1040
+ return coordinates;
1041
+ }
1042
+ /** This function maps a coordinate through one segment map of `avar`, by linear interpolation between its points. */
1043
+ function mapSegment(from, to, value) {
1044
+ if (from.length === 0) return value;
1045
+ if (value <= from[0]) return to[0] + value - from[0];
1046
+ for (let i = 1; i < from.length; i++) {
1047
+ if (value === from[i]) return to[i];
1048
+ if (value < from[i]) return to[i - 1] + (value - from[i - 1]) * (to[i] - to[i - 1]) / (from[i] - from[i - 1]);
1049
+ }
1050
+ return to[to.length - 1] + value - from[from.length - 1];
1051
+ }
1052
+ /** This function reads an ItemVariationStore and computes the scalar of each region at the coordinates. */
1053
+ function readVariationStore(store, coordinates) {
1054
+ const regions = store.part(store.u32(2));
1055
+ const axisCount = regions.u16(0);
1056
+ const regionCount = regions.u16(2);
1057
+ const scalars = [];
1058
+ for (let r = 0; r < regionCount; r++) {
1059
+ let scalar = 1;
1060
+ for (let a = 0; a < axisCount && scalar !== 0; a++) {
1061
+ const at = 4 + (r * axisCount + a) * 6;
1062
+ scalar *= axisScalar(f2dot14(regions, at), f2dot14(regions, at + 2), f2dot14(regions, at + 4), coordinates[a] ?? 0);
1063
+ }
1064
+ scalars.push(scalar);
1065
+ }
1066
+ const dataCount = store.u16(6);
1067
+ const data = Array.from({ length: dataCount }, (_, i) => store.part(store.u32(8 + i * 4)));
1068
+ return { delta(outer, inner) {
1069
+ const table = data[outer];
1070
+ if (table === void 0 || inner >= table.u16(0)) return 0;
1071
+ const wordField = table.u16(2);
1072
+ const longWords = (wordField & 32768) !== 0;
1073
+ const words = wordField & 32767;
1074
+ const count = table.u16(4);
1075
+ const wordSize = longWords ? 4 : 2;
1076
+ const smallSize = longWords ? 2 : 1;
1077
+ const rowSize = words * wordSize + (count - words) * smallSize;
1078
+ let at = 6 + count * 2 + inner * rowSize;
1079
+ let sum = 0;
1080
+ for (let i = 0; i < count; i++) {
1081
+ let value;
1082
+ if (i < words) {
1083
+ value = longWords ? table.u32(at) | 0 : table.i16(at);
1084
+ at += wordSize;
1085
+ } else {
1086
+ value = longWords ? table.i16(at) : table.u8(at) << 24 >> 24;
1087
+ at += smallSize;
1088
+ }
1089
+ const scalar = scalars[table.u16(6 + i * 2)] ?? 0;
1090
+ if (scalar !== 0) sum += scalar * value;
1091
+ }
1092
+ return sum;
1093
+ } };
1094
+ }
1095
+ /** This function gives the scalar of one axis of a region, as the OpenType specification defines it. */
1096
+ function axisScalar(start, peak, end, value) {
1097
+ if (start > peak || peak > end) return 1;
1098
+ if (start < 0 && end > 0 && peak !== 0) return 1;
1099
+ if (peak === 0 || value === peak) return 1;
1100
+ if (value <= start || value >= end) return 0;
1101
+ return value < peak ? (value - start) / (peak - start) : (end - value) / (end - peak);
1102
+ }
1103
+ /** This function gives the outer and inner index of a glyph in a DeltaSetIndexMap. A glyph after the end uses the last entry. */
1104
+ function mapIndex(map, index) {
1105
+ const format = map.u8(0);
1106
+ const entryFormat = map.u8(1);
1107
+ const count = format === 0 ? map.u16(2) : map.u32(2);
1108
+ const start = format === 0 ? 4 : 6;
1109
+ if (count === 0) return [0, index];
1110
+ const size = (entryFormat >> 4 & 3) + 1;
1111
+ const innerBits = (entryFormat & 15) + 1;
1112
+ const at = start + Math.min(index, count - 1) * size;
1113
+ let entry = 0;
1114
+ for (let i = 0; i < size; i++) entry = entry * 256 + map.u8(at + i);
1115
+ const innerCount = 2 ** innerBits;
1116
+ return [Math.floor(entry / innerCount), entry % innerCount];
1117
+ }
1118
+ /**
1119
+ * This function gives the advances of an instance. With `round`, each delta of `HVAR` is rounded to whole font units, as
1120
+ * FreeType rounds it: a half goes up. With `exact`, the delta keeps its fraction. The advances stay as they are when the
1121
+ * font has no `HVAR` table.
1122
+ */
1123
+ function instanceAdvances(advances, hvar, coordinates, deltas = "round") {
1124
+ const result = Float64Array.from(advances);
1125
+ if (hvar === void 0 || coordinates.every((value) => value === 0)) return result;
1126
+ const store = readVariationStore(hvar.part(hvar.u32(4)), coordinates);
1127
+ const mapOffset = hvar.u32(8);
1128
+ const map = mapOffset === 0 ? void 0 : hvar.part(mapOffset);
1129
+ for (let glyph = 0; glyph < result.length; glyph++) {
1130
+ const [outer, inner] = map === void 0 ? [0, glyph] : mapIndex(map, glyph);
1131
+ const delta = store.delta(outer, inner);
1132
+ result[glyph] = advances[glyph] + (deltas === "round" ? Math.round(delta) : delta);
1133
+ }
1134
+ return result;
1135
+ }
1136
+ // src/fonts/gvar.ts
1137
+ /** The composite flags of `glyf` that change the length of a component record. */
1138
+ const ARG_1_AND_2_ARE_WORDS = 1;
1139
+ const WE_HAVE_A_SCALE = 8;
1140
+ const MORE_COMPONENTS = 32;
1141
+ const WE_HAVE_AN_X_AND_Y_SCALE = 64;
1142
+ const WE_HAVE_A_TWO_BY_TWO = 128;
1143
+ /** The composite flag that gives the metrics of the component to the composite glyph. */
1144
+ const USE_MY_METRICS = 512;
1145
+ /**
1146
+ * This function reads the composite records that start at `at`. It gives their length in bytes and the count of
1147
+ * components. It also gives the first component with the flag USE_MY_METRICS, or -1.
1148
+ */
1149
+ function components(glyph, at) {
1150
+ let count = 0;
1151
+ let offset = at;
1152
+ let metrics = -1;
1153
+ for (;;) {
1154
+ const flags = glyph.u16(offset);
1155
+ if ((flags & USE_MY_METRICS) !== 0 && metrics < 0) metrics = glyph.u16(offset + 2);
1156
+ offset += 4 + ((flags & ARG_1_AND_2_ARE_WORDS) !== 0 ? 4 : 2);
1157
+ if ((flags & WE_HAVE_A_SCALE) !== 0) offset += 2;
1158
+ else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) !== 0) offset += 4;
1159
+ else if ((flags & WE_HAVE_A_TWO_BY_TWO) !== 0) offset += 8;
1160
+ count++;
1161
+ if ((flags & MORE_COMPONENTS) === 0) return {
1162
+ count,
1163
+ length: offset - at,
1164
+ metrics
1165
+ };
1166
+ }
1167
+ }
1168
+ /**
1169
+ * This function gives the points of each glyph. A glyph of contours has the points of its contours. A composite glyph
1170
+ * has one point for each component.
1171
+ */
1172
+ function readPointCounts(glyf, loca, indexToLocFormat, numGlyphs) {
1173
+ const counts = new Uint32Array(numGlyphs);
1174
+ const metrics = new Int32Array(numGlyphs).fill(-1);
1175
+ const offsetOf = (glyph) => indexToLocFormat === 0 ? loca.u16(glyph * 2) * 2 : loca.u32(glyph * 4);
1176
+ for (let glyph = 0; glyph < numGlyphs; glyph++) {
1177
+ const start = offsetOf(glyph);
1178
+ if (offsetOf(glyph + 1) <= start) continue;
1179
+ const contours = glyf.i16(start);
1180
+ if (contours > 0) counts[glyph] = glyf.u16(start + 10 + (contours - 1) * 2) + 1;
1181
+ else if (contours < 0) {
1182
+ const found = components(glyf, start + 10);
1183
+ counts[glyph] = found.count;
1184
+ metrics[glyph] = found.metrics;
1185
+ }
1186
+ }
1187
+ return {
1188
+ counts,
1189
+ metrics
1190
+ };
1191
+ }
1192
+ /** This function reads a 255UInt16 of WOFF2. It gives the value and the next offset. */
1193
+ function read255(stream, at) {
1194
+ const code = stream.u8(at);
1195
+ if (code === 253) return [stream.u16(at + 1), at + 3];
1196
+ if (code === 254) return [stream.u8(at + 1) + 506, at + 2];
1197
+ if (code === 255) return [stream.u8(at + 1) + 253, at + 2];
1198
+ return [code, at + 1];
1199
+ }
1200
+ /** This function gives the points of each glyph from the transformed `glyf` table of WOFF2. */
1201
+ function readTransformedPointCounts(glyf, numGlyphs) {
1202
+ const counts = new Uint32Array(numGlyphs);
1203
+ const metrics = new Int32Array(numGlyphs).fill(-1);
1204
+ const sizes = Array.from({ length: 7 }, (_, i) => glyf.u32(8 + i * 4));
1205
+ const contourStream = glyf.part(36, sizes[0]);
1206
+ const pointStream = glyf.part(36 + sizes[0], sizes[1]);
1207
+ const compositeStream = glyf.part(36 + sizes[0] + sizes[1] + sizes[2] + sizes[3], sizes[4]);
1208
+ let pointAt = 0;
1209
+ let compositeAt = 0;
1210
+ const glyphs = Math.min(numGlyphs, glyf.u16(4));
1211
+ for (let glyph = 0; glyph < glyphs; glyph++) {
1212
+ const contours = contourStream.i16(glyph * 2);
1213
+ if (contours > 0) {
1214
+ let total = 0;
1215
+ for (let c = 0; c < contours; c++) {
1216
+ const [points, next] = read255(pointStream, pointAt);
1217
+ total += points;
1218
+ pointAt = next;
1219
+ }
1220
+ counts[glyph] = total;
1221
+ } else if (contours < 0) {
1222
+ const found = components(compositeStream, compositeAt);
1223
+ counts[glyph] = found.count;
1224
+ metrics[glyph] = found.metrics;
1225
+ compositeAt += found.length;
1226
+ }
1227
+ }
1228
+ return {
1229
+ counts,
1230
+ metrics
1231
+ };
1232
+ }
1233
+ /** This function reads packed point numbers. It gives the point numbers, or `undefined` for all the points, and the next offset. */
1234
+ function readPoints(data, at) {
1235
+ let count = data.u8(at++);
1236
+ if ((count & 128) !== 0) count = (count & 127) << 8 | data.u8(at++);
1237
+ if (count === 0) return [void 0, at];
1238
+ const points = [];
1239
+ let last = 0;
1240
+ while (points.length < count) {
1241
+ const control = data.u8(at++);
1242
+ const words = (control & 128) !== 0;
1243
+ const run = (control & 127) + 1;
1244
+ for (let k = 0; k < run && points.length < count; k++) {
1245
+ last += words ? data.u16(at) : data.u8(at);
1246
+ at += words ? 2 : 1;
1247
+ points.push(last);
1248
+ }
1249
+ }
1250
+ return [points, at];
1251
+ }
1252
+ /** This function reads `count` packed deltas. It gives the deltas and the next offset. */
1253
+ function readDeltas(data, at, count) {
1254
+ const deltas = [];
1255
+ while (deltas.length < count) {
1256
+ const control = data.u8(at++);
1257
+ const run = (control & 63) + 1;
1258
+ const kind = control & 192;
1259
+ for (let k = 0; k < run && deltas.length < count; k++) if (kind === 128) deltas.push(0);
1260
+ else if (kind === 64) {
1261
+ deltas.push(data.i16(at));
1262
+ at += 2;
1263
+ } else if (kind === 192) {
1264
+ deltas.push(data.u32(at) | 0);
1265
+ at += 4;
1266
+ } else deltas.push(data.u8(at++) << 24 >> 24);
1267
+ }
1268
+ return [deltas, at];
1269
+ }
1270
+ /**
1271
+ * This function gives the advance delta of each glyph at the coordinates, from the phantom points of `gvar`. The delta
1272
+ * is the x delta of the second phantom point minus the x delta of the first. With `round`, the difference is rounded to
1273
+ * whole font units, and a half goes away from 0, as HarfBuzz rounds it.
1274
+ */
1275
+ function gvarAdvanceDeltas(gvar, pointCounts, coordinates, deltas) {
1276
+ const axisCount = gvar.u16(4);
1277
+ const sharedCount = gvar.u16(6);
1278
+ const sharedAt = gvar.u32(8);
1279
+ const glyphCount = Math.min(gvar.u16(12), pointCounts.length);
1280
+ const longOffsets = (gvar.u16(14) & 1) !== 0;
1281
+ const dataAt = gvar.u32(16);
1282
+ const offsetOf = (glyph) => longOffsets ? gvar.u32(20 + glyph * 4) : gvar.u16(20 + glyph * 2) * 2;
1283
+ const peakOf = (at) => Array.from({ length: axisCount }, (_, a) => gvar.i16(at + a * 2) / 16384);
1284
+ const shared = Array.from({ length: sharedCount }, (_, t) => peakOf(sharedAt + t * axisCount * 2));
1285
+ const result = new Float64Array(pointCounts.length);
1286
+ for (let glyph = 0; glyph < glyphCount; glyph++) {
1287
+ const start = offsetOf(glyph);
1288
+ const end = offsetOf(glyph + 1);
1289
+ if (end <= start) continue;
1290
+ const data = gvar.part(dataAt + start, end - start);
1291
+ const tupleCount = data.u16(0) & 4095;
1292
+ const sharedPoints = (data.u16(0) & 32768) !== 0;
1293
+ let serialized = data.u16(2);
1294
+ let header = 4;
1295
+ const allPoints = pointCounts[glyph] + 4;
1296
+ let sharedList;
1297
+ if (sharedPoints) [sharedList, serialized] = readPoints(data, serialized);
1298
+ const first = pointCounts[glyph];
1299
+ let left = 0;
1300
+ let right = 0;
1301
+ for (let t = 0; t < tupleCount; t++) {
1302
+ const size = data.u16(header);
1303
+ const index = data.u16(header + 2);
1304
+ header += 4;
1305
+ let peak;
1306
+ if ((index & 32768) !== 0) {
1307
+ peak = Array.from({ length: axisCount }, (_, a) => data.i16(header + a * 2) / 16384);
1308
+ header += axisCount * 2;
1309
+ } else peak = shared[index & 4095] ?? [];
1310
+ let startTuple;
1311
+ let endTuple;
1312
+ if ((index & 16384) !== 0) {
1313
+ startTuple = Array.from({ length: axisCount }, (_, a) => data.i16(header + a * 2) / 16384);
1314
+ endTuple = Array.from({ length: axisCount }, (_, a) => data.i16(header + axisCount * 2 + a * 2) / 16384);
1315
+ header += axisCount * 4;
1316
+ }
1317
+ let scalar = 1;
1318
+ for (let a = 0; a < axisCount && scalar !== 0; a++) {
1319
+ const p = peak[a] ?? 0;
1320
+ const s = startTuple?.[a] ?? Math.min(p, 0);
1321
+ const e = endTuple?.[a] ?? Math.max(p, 0);
1322
+ scalar *= axisScalar(s, p, e, coordinates[a] ?? 0);
1323
+ }
1324
+ const tupleEnd = serialized + size;
1325
+ if (scalar !== 0) {
1326
+ let at = serialized;
1327
+ let points = sharedList;
1328
+ if ((index & 8192) !== 0) [points, at] = readPoints(data, at);
1329
+ const count = points === void 0 ? allPoints : points.length;
1330
+ const [xs] = readDeltas(data, at, count);
1331
+ const deltaOf = (point) => {
1332
+ if (points === void 0) return xs[point] ?? 0;
1333
+ const k = points.indexOf(point);
1334
+ return k < 0 ? 0 : xs[k];
1335
+ };
1336
+ left += scalar * deltaOf(first);
1337
+ right += scalar * deltaOf(first + 1);
1338
+ }
1339
+ serialized = tupleEnd;
1340
+ }
1341
+ const delta = right - left;
1342
+ result[glyph] = deltas === "round" ? Math.sign(delta) * Math.round(Math.abs(delta)) : delta;
1343
+ }
1344
+ return result;
1345
+ }
1346
+ // src/fonts/indic.ts
1347
+ /** The OpenType script tags that use the Indic shaper in this model. */
1348
+ const indicScripts = ["deva"];
1349
+ const X = 0;
1350
+ const C = 1;
1351
+ const V = 2;
1352
+ const N = 3;
1353
+ const H = 4;
1354
+ const ZWNJ = 5;
1355
+ const ZWJ = 6;
1356
+ const M = 7;
1357
+ const SM = 8;
1358
+ const A = 9;
1359
+ const PLACEHOLDER = 10;
1360
+ const DOTTEDCIRCLE = 11;
1361
+ const RS = 12;
1362
+ const MPST = 13;
1363
+ const REPHA = 14;
1364
+ const RA = 15;
1365
+ const CM = 16;
1366
+ const SYMBOL = 17;
1367
+ const CS = 18;
1368
+ const SMPST = 57;
1369
+ const START = 0;
1370
+ const RA_TO_BECOME_REPH = 1;
1371
+ const PRE_M = 2;
1372
+ const PRE_C = 3;
1373
+ const BASE_C = 4;
1374
+ const BELOW_C = 8;
1375
+ const AFTER_SUB = 9;
1376
+ const POST_C = 11;
1377
+ const SMVD = 13;
1378
+ const END = 14;
1379
+ const CONSONANT_SYLLABLE = 0;
1380
+ const VOWEL_SYLLABLE = 1;
1381
+ const STANDALONE_CLUSTER = 2;
1382
+ const SYMBOL_CLUSTER = 3;
1383
+ const BROKEN_CLUSTER = 4;
1384
+ const NON_INDIC_CLUSTER = 5;
1385
+ /**
1386
+ * The categories and positions of the Indic table of HarfBuzz for the characters that Devanagari text uses. Each group of
1387
+ * four has the first code point, the last code point, the category and the position. Another code point has the category
1388
+ * `X` and the position `END`.
1389
+ */
1390
+ const indicTable = [
1391
+ 45,
1392
+ 45,
1393
+ PLACEHOLDER,
1394
+ BASE_C,
1395
+ 48,
1396
+ 57,
1397
+ PLACEHOLDER,
1398
+ BASE_C,
1399
+ 160,
1400
+ 160,
1401
+ PLACEHOLDER,
1402
+ BASE_C,
1403
+ 178,
1404
+ 179,
1405
+ SMPST,
1406
+ SMVD,
1407
+ 215,
1408
+ 215,
1409
+ PLACEHOLDER,
1410
+ BASE_C,
1411
+ 2304,
1412
+ 2307,
1413
+ SM,
1414
+ SMVD,
1415
+ 2308,
1416
+ 2324,
1417
+ V,
1418
+ BASE_C,
1419
+ 2325,
1420
+ 2351,
1421
+ C,
1422
+ BASE_C,
1423
+ 2352,
1424
+ 2352,
1425
+ RA,
1426
+ BASE_C,
1427
+ 2353,
1428
+ 2361,
1429
+ C,
1430
+ BASE_C,
1431
+ 2362,
1432
+ 2363,
1433
+ M,
1434
+ AFTER_SUB,
1435
+ 2364,
1436
+ 2364,
1437
+ N,
1438
+ END,
1439
+ 2365,
1440
+ 2365,
1441
+ SYMBOL,
1442
+ SMVD,
1443
+ 2366,
1444
+ 2366,
1445
+ M,
1446
+ AFTER_SUB,
1447
+ 2367,
1448
+ 2367,
1449
+ M,
1450
+ PRE_M,
1451
+ 2368,
1452
+ 2380,
1453
+ M,
1454
+ AFTER_SUB,
1455
+ 2381,
1456
+ 2381,
1457
+ H,
1458
+ BELOW_C,
1459
+ 2382,
1460
+ 2382,
1461
+ M,
1462
+ PRE_M,
1463
+ 2383,
1464
+ 2383,
1465
+ M,
1466
+ AFTER_SUB,
1467
+ 2385,
1468
+ 2386,
1469
+ A,
1470
+ SMVD,
1471
+ 2387,
1472
+ 2388,
1473
+ SM,
1474
+ SMVD,
1475
+ 2389,
1476
+ 2391,
1477
+ M,
1478
+ AFTER_SUB,
1479
+ 2392,
1480
+ 2399,
1481
+ C,
1482
+ BASE_C,
1483
+ 2400,
1484
+ 2401,
1485
+ V,
1486
+ BASE_C,
1487
+ 2402,
1488
+ 2403,
1489
+ M,
1490
+ AFTER_SUB,
1491
+ 2406,
1492
+ 2415,
1493
+ PLACEHOLDER,
1494
+ BASE_C,
1495
+ 2418,
1496
+ 2423,
1497
+ V,
1498
+ BASE_C,
1499
+ 2424,
1500
+ 2431,
1501
+ C,
1502
+ BASE_C,
1503
+ 7376,
1504
+ 7378,
1505
+ A,
1506
+ SMVD,
1507
+ 7380,
1508
+ 7400,
1509
+ A,
1510
+ SMVD,
1511
+ 7401,
1512
+ 7404,
1513
+ SYMBOL,
1514
+ SMVD,
1515
+ 7405,
1516
+ 7405,
1517
+ A,
1518
+ SMVD,
1519
+ 7406,
1520
+ 7409,
1521
+ SYMBOL,
1522
+ SMVD,
1523
+ 7410,
1524
+ 7411,
1525
+ C,
1526
+ BASE_C,
1527
+ 7412,
1528
+ 7412,
1529
+ A,
1530
+ SMVD,
1531
+ 7413,
1532
+ 7414,
1533
+ CS,
1534
+ BASE_C,
1535
+ 7415,
1536
+ 7417,
1537
+ A,
1538
+ SMVD,
1539
+ 7418,
1540
+ 7418,
1541
+ PLACEHOLDER,
1542
+ BASE_C,
1543
+ 8204,
1544
+ 8204,
1545
+ ZWNJ,
1546
+ END,
1547
+ 8205,
1548
+ 8205,
1549
+ ZWJ,
1550
+ END,
1551
+ 8208,
1552
+ 8213,
1553
+ PLACEHOLDER,
1554
+ BASE_C,
1555
+ 8226,
1556
+ 8226,
1557
+ PLACEHOLDER,
1558
+ BASE_C,
1559
+ 8308,
1560
+ 8308,
1561
+ SMPST,
1562
+ SMVD,
1563
+ 8322,
1564
+ 8324,
1565
+ SMPST,
1566
+ SMVD,
1567
+ 9676,
1568
+ 9676,
1569
+ DOTTEDCIRCLE,
1570
+ BASE_C,
1571
+ 43232,
1572
+ 43249,
1573
+ A,
1574
+ SMVD,
1575
+ 43250,
1576
+ 43255,
1577
+ SYMBOL,
1578
+ SMVD,
1579
+ 43262,
1580
+ 43262,
1581
+ V,
1582
+ BASE_C,
1583
+ 43263,
1584
+ 43263,
1585
+ M,
1586
+ AFTER_SUB
1587
+ ];
1588
+ /** This function gives the category and the position of a code point. */
1589
+ function indicProperties(codePoint) {
1590
+ let low = 0;
1591
+ let high = indicTable.length / 4 - 1;
1592
+ while (low <= high) {
1593
+ const middle = low + high >> 1;
1594
+ if (codePoint < indicTable[middle * 4]) high = middle - 1;
1595
+ else if (codePoint > indicTable[middle * 4 + 1]) low = middle + 1;
1596
+ else return [indicTable[middle * 4 + 2], indicTable[middle * 4 + 3]];
1597
+ }
1598
+ return [X, END];
1599
+ }
1600
+ /**
1601
+ * The canonical combining classes of the marks of the Devanagari blocks that are not 0. The normalization uses them to
1602
+ * sort the marks and to block a composition. A mark of another block has the class 0 in this model.
1603
+ */
1604
+ const combiningClasses = [
1605
+ 2364,
1606
+ 2364,
1607
+ 7,
1608
+ 2381,
1609
+ 2381,
1610
+ 9,
1611
+ 2385,
1612
+ 2385,
1613
+ 230,
1614
+ 2386,
1615
+ 2386,
1616
+ 220,
1617
+ 2387,
1618
+ 2388,
1619
+ 230,
1620
+ 7376,
1621
+ 7378,
1622
+ 230,
1623
+ 7380,
1624
+ 7380,
1625
+ 1,
1626
+ 7381,
1627
+ 7385,
1628
+ 220,
1629
+ 7386,
1630
+ 7387,
1631
+ 230,
1632
+ 7388,
1633
+ 7391,
1634
+ 220,
1635
+ 7392,
1636
+ 7392,
1637
+ 230,
1638
+ 7394,
1639
+ 7400,
1640
+ 1,
1641
+ 7405,
1642
+ 7405,
1643
+ 220,
1644
+ 7412,
1645
+ 7412,
1646
+ 230,
1647
+ 7416,
1648
+ 7417,
1649
+ 230,
1650
+ 43232,
1651
+ 43249,
1652
+ 230
1653
+ ];
1654
+ function combiningClass(codePoint) {
1655
+ for (let i = 0; i < combiningClasses.length; i += 3) if (codePoint >= combiningClasses[i] && codePoint <= combiningClasses[i + 1]) return combiningClasses[i + 2];
1656
+ return 0;
1657
+ }
1658
+ const unicodeMark = /\p{M}/u;
1659
+ /** The general categories from `FORMAT` to `NON_SPACING_MARK` of HarfBuzz: a letter, a mark, or a format or other code point. */
1660
+ const letterOrMark = /[\p{L}\p{M}\p{Cf}\p{Cn}\p{Co}\p{Cs}]/u;
1661
+ function isMark(codePoint) {
1662
+ return unicodeMark.test(String.fromCodePoint(codePoint));
1663
+ }
1664
+ /**
1665
+ * This function inserts a dotted circle in each vowel sequence of Devanagari that looks like another vowel, as
1666
+ * `_hb_preprocess_text_vowel_constraints` does.
1667
+ */
1668
+ function vowelConstraints(codePoints) {
1669
+ const out = [];
1670
+ let i = 0;
1671
+ while (i + 1 < codePoints.length) {
1672
+ const first = codePoints[i];
1673
+ const second = codePoints[i + 1];
1674
+ let matched = false;
1675
+ if (first === 2309) matched = [
1676
+ 2362,
1677
+ 2363,
1678
+ 2366,
1679
+ 2373,
1680
+ 2374,
1681
+ 2377,
1682
+ 2378,
1683
+ 2379,
1684
+ 2380,
1685
+ 2383,
1686
+ 2390,
1687
+ 2391
1688
+ ].includes(second);
1689
+ else if (first === 2310) matched = [
1690
+ 2362,
1691
+ 2373,
1692
+ 2374,
1693
+ 2375,
1694
+ 2376
1695
+ ].includes(second);
1696
+ else if (first === 2313) matched = second === 2369;
1697
+ else if (first === 2319) matched = [
1698
+ 2373,
1699
+ 2374,
1700
+ 2375
1701
+ ].includes(second);
1702
+ else if (first === 2352 && second === 2381 && codePoints[i + 2] === 2311) {
1703
+ out.push(codePoints[i++]);
1704
+ matched = true;
1705
+ }
1706
+ out.push(codePoints[i++]);
1707
+ if (matched) out.push(9676, codePoints[i++]);
1708
+ }
1709
+ while (i < codePoints.length) out.push(codePoints[i++]);
1710
+ return out;
1711
+ }
1712
+ /**
1713
+ * This function normalizes the code points as HarfBuzz does in the mode `COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT`. It
1714
+ * decomposes each character that has a canonical decomposition when the font has the glyphs of its parts. The letter
1715
+ * U+0931 does not decompose. A text with a mark after its first character then gets more steps. The function sorts the
1716
+ * marks, and composes each mark with its starter again when the font has the glyph of the composite.
1717
+ */
1718
+ function normalize(codePoints, hasGlyph) {
1719
+ const allSimple = !codePoints.some((codePoint, i) => i > 0 && isMark(codePoint));
1720
+ const decomposed = [];
1721
+ for (const codePoint of codePoints) {
1722
+ const parts = codePoint === 2353 ? [codePoint] : [...String.fromCodePoint(codePoint).normalize("NFD")].map((part) => part.codePointAt(0));
1723
+ if (parts.length > 1 || parts[0] !== codePoint) {
1724
+ if (parts.every(hasGlyph)) {
1725
+ decomposed.push(...parts);
1726
+ continue;
1727
+ }
1728
+ }
1729
+ decomposed.push(codePoint);
1730
+ }
1731
+ if (allSimple) return decomposed;
1732
+ for (let i = 1; i < decomposed.length; i++) for (let j = i; j > 0; j--) {
1733
+ const before = combiningClass(decomposed[j - 1]);
1734
+ const after = combiningClass(decomposed[j]);
1735
+ if (after === 0 || before <= after) break;
1736
+ [decomposed[j - 1], decomposed[j]] = [decomposed[j], decomposed[j - 1]];
1737
+ }
1738
+ const out = decomposed.slice(0, 1);
1739
+ let starter = 0;
1740
+ for (let i = 1; i < decomposed.length; i++) {
1741
+ const codePoint = decomposed[i];
1742
+ if (isMark(codePoint) && (starter === out.length - 1 || combiningClass(out[out.length - 1]) < combiningClass(codePoint))) {
1743
+ const composed = compose(out[starter], codePoint);
1744
+ if (composed !== void 0 && hasGlyph(composed)) {
1745
+ out[starter] = composed;
1746
+ continue;
1747
+ }
1748
+ }
1749
+ out.push(codePoint);
1750
+ if (combiningClass(codePoint) === 0) starter = out.length - 1;
1751
+ }
1752
+ return out;
1753
+ }
1754
+ /** This function gives the canonical composite of two code points, or `undefined`. A mark does not compose with a mark. */
1755
+ function compose(first, second) {
1756
+ if (isMark(first)) return void 0;
1757
+ const [single, ...rest] = [...[(String.fromCodePoint(first) + String.fromCodePoint(second)).normalize("NFC")][0]];
1758
+ return rest.length === 0 && single !== void 0 ? single.codePointAt(0) : void 0;
1759
+ }
1760
+ const letters = /* @__PURE__ */ new Map([
1761
+ [X, "x"],
1762
+ [C, "c"],
1763
+ [V, "v"],
1764
+ [N, "n"],
1765
+ [H, "h"],
1766
+ [ZWNJ, "w"],
1767
+ [ZWJ, "j"],
1768
+ [M, "m"],
1769
+ [SM, "s"],
1770
+ [A, "a"],
1771
+ [PLACEHOLDER, "p"],
1772
+ [DOTTEDCIRCLE, "d"],
1773
+ [RS, "r"],
1774
+ [MPST, "q"],
1775
+ [REPHA, "f"],
1776
+ [RA, "R"],
1777
+ [CM, "k"],
1778
+ [SYMBOL, "y"],
1779
+ [CS, "S"],
1780
+ [SMPST, "t"]
1781
+ ]);
1782
+ const c = "[cR]";
1783
+ const n = "(?:w?r)?(?:nn?)?";
1784
+ const z = "[wj]";
1785
+ const reph = "(?:Rh|f)";
1786
+ const sm = "[st]";
1787
+ const cn = `${c}j?${n}`;
1788
+ const matraGroup = `${z}*(?:m|${sm}?q)n?h?`;
1789
+ const syllableTail = `(?:${z}?${sm}${sm}?w?)?a*`;
1790
+ const halantGroup = `(?:${z}?h(?:jn?)?)`;
1791
+ const complexSyllableTail = `(?:${halantGroup}${cn})*k?(?:${`(?:${halantGroup}|hw)`}|(?:${matraGroup})*)${syllableTail}`;
1792
+ /** The rules of the machine in the order of HarfBuzz. A tie of two lengths goes to the first rule. */
1793
+ const machine = [
1794
+ [`[fS]?${cn}${complexSyllableTail}`, CONSONANT_SYLLABLE],
1795
+ [`${reph}?v${n}(?:j|${complexSyllableTail})`, VOWEL_SYLLABLE],
1796
+ [`(?:[fS]?p|${reph}?d)${n}${complexSyllableTail}`, STANDALONE_CLUSTER],
1797
+ [`yn?${syllableTail}`, SYMBOL_CLUSTER],
1798
+ ["t", NON_INDIC_CLUSTER],
1799
+ [`${reph}?${n}${complexSyllableTail}`, BROKEN_CLUSTER],
1800
+ ["[^]", NON_INDIC_CLUSTER]
1801
+ ].map(([rule, type]) => [new RegExp(`^(?:${rule})$`), type]);
1802
+ /**
1803
+ * This function divides the glyphs into syllables, as the machine of HarfBuzz does. At each position, it takes the
1804
+ * longest match of the rules. Each glyph gets the syllable byte. Its high 4 bits are a serial number from 1 to 15, and
1805
+ * its low 4 bits are the type. The function tells if a syllable is a broken cluster.
1806
+ */
1807
+ function findSyllables(infos) {
1808
+ const text = infos.map((info) => letters.get(info.category) ?? "x").join("");
1809
+ let serial = 1;
1810
+ let broken = false;
1811
+ let at = 0;
1812
+ while (at < text.length) {
1813
+ const next = text.indexOf("x", at + 1);
1814
+ let length = 1;
1815
+ let type = NON_INDIC_CLUSTER;
1816
+ search: for (let end = next < 0 ? text.length : next; end > at; end--) {
1817
+ const piece = text.slice(at, end);
1818
+ for (const [rule, kind] of machine) if (rule.test(piece)) {
1819
+ length = end - at;
1820
+ type = kind;
1821
+ break search;
1822
+ }
1823
+ }
1824
+ if (type === BROKEN_CLUSTER) broken = true;
1825
+ for (let i = at; i < at + length; i++) infos[i].syllable = serial << 4 | type;
1826
+ serial = serial === 15 ? 1 : serial + 1;
1827
+ at += length;
1828
+ }
1829
+ return broken;
1830
+ }
1831
+ /** It is true when the glyph made no ligature and its category is one of `categories`. A ligature has no category. */
1832
+ function isOneOf(info, categories) {
1833
+ return (info.props & 32) === 0 && categories.includes(info.category);
1834
+ }
1835
+ const consonants = [
1836
+ C,
1837
+ CS,
1838
+ RA,
1839
+ CM,
1840
+ V,
1841
+ PLACEHOLDER,
1842
+ DOTTEDCIRCLE
1843
+ ];
1844
+ const joiners = [ZWJ, ZWNJ];
1845
+ const isConsonant = (info) => isOneOf(info, consonants);
1846
+ const isJoiner = (info) => isOneOf(info, joiners);
1847
+ const isHalant = (info) => isOneOf(info, [H]);
1848
+ const ligatedAndDidntMultiply = (info) => (info.props & 96) === 32;
1849
+ /** The basic features, in the order of their stages before the final reordering. The value is true for a global feature. */
1850
+ const basicFeatures = [
1851
+ ["nukt", true],
1852
+ ["akhn", true],
1853
+ ["rphf", false],
1854
+ ["rkrf", true],
1855
+ ["pref", false],
1856
+ ["blwf", false],
1857
+ ["abvf", false],
1858
+ ["half", false],
1859
+ ["pstf", false],
1860
+ ["vatu", true],
1861
+ ["cjct", true]
1862
+ ];
1863
+ /** The features of the last stage, after the final reordering. They match only in a syllable. */
1864
+ const finalFeatures = [
1865
+ "init",
1866
+ "pres",
1867
+ "abvs",
1868
+ "blws",
1869
+ "psts",
1870
+ "haln"
1871
+ ];
1872
+ /** The mask bit of the global features. Each glyph has it. */
1873
+ const GLOBAL = 1;
1874
+ /** The mask bits of the features that apply only to some glyphs of a syllable. */
1875
+ const featureBits = /* @__PURE__ */ new Map([
1876
+ ["rphf", 2],
1877
+ ["pref", 4],
1878
+ ["blwf", 8],
1879
+ ["abvf", 16],
1880
+ ["half", 32],
1881
+ ["pstf", 64],
1882
+ ["init", 128]
1883
+ ]);
1884
+ /** This function moves the glyph at `from` to `to`. The glyphs between them move by one position. */
1885
+ function move(run, from, to) {
1886
+ const [glyph] = run.glyphs.splice(from, 1);
1887
+ const [info] = run.infos.splice(from, 1);
1888
+ run.glyphs.splice(to, 0, glyph);
1889
+ run.infos.splice(to, 0, info);
1890
+ }
1891
+ function reverse(run, start, end) {
1892
+ run.glyphs.splice(start, end - start, ...run.glyphs.slice(start, end).reverse());
1893
+ run.infos.splice(start, end - start, ...run.infos.slice(start, end).reverse());
1894
+ }
1895
+ /** This function gives the ranges of the syllables, as `foreach_syllable` of HarfBuzz. */
1896
+ function syllables(infos) {
1897
+ const ranges = [];
1898
+ let start = 0;
1899
+ for (let i = 1; i <= infos.length; i++) if (i === infos.length || infos[i].syllable !== infos[start].syllable) {
1900
+ ranges.push([start, i]);
1901
+ start = i;
1902
+ }
1903
+ return infos.length === 0 ? [] : ranges;
1904
+ }
1905
+ /**
1906
+ * This function makes the function that gives the glyphs of a word with the Indic shaper. The features `features` are the
1907
+ * features of the page. The shaper applies `locl` and `ccmp` before it reorders the syllables, and the other features of
1908
+ * the page after its own features. It does not apply `liga`.
1909
+ */
1910
+ function indicShaper(font, script, features) {
1911
+ const lookupsOf = (tags) => font.substitutions(script, tags);
1912
+ const maskOf = (tag) => lookupsOf([tag]).length === 0 ? 0 : featureBits.get(tag) ?? GLOBAL;
1913
+ const [rphfMask, prefMask, blwfMask, abvfMask, halfMask, pstfMask, initMask] = [
1914
+ "rphf",
1915
+ "pref",
1916
+ "blwf",
1917
+ "abvf",
1918
+ "half",
1919
+ "pstf",
1920
+ "init"
1921
+ ].map(maskOf);
1922
+ const would = (tag, glyphs) => lookupsOf([tag]).some((lookup) => lookup.wouldApply(glyphs));
1923
+ const virama = font.glyph(2381);
1924
+ const dottedCircle = font.glyph(9676);
1925
+ const positions = /* @__PURE__ */ new Map();
1926
+ /** This function gives the position of a consonant from the below-base, post-base and pre-base forms of the font. */
1927
+ const consonantPosition = (consonant) => {
1928
+ let position = positions.get(consonant);
1929
+ if (position === void 0) {
1930
+ const before = [virama, consonant];
1931
+ const after = [consonant, virama];
1932
+ const either = (tag) => would(tag, before) || would(tag, after);
1933
+ position = either("blwf") || either("vatu") ? BELOW_C : either("pstf") || either("pref") ? POST_C : BASE_C;
1934
+ positions.set(consonant, position);
1935
+ }
1936
+ return position;
1937
+ };
1938
+ const first = lookupsOf(["locl", "ccmp"]);
1939
+ const basic = basicFeatures.map(([tag, global]) => ({
1940
+ lookups: lookupsOf([tag]),
1941
+ mask: global ? GLOBAL : featureBits.get(tag)
1942
+ }));
1943
+ const extra = features.filter((tag) => ![
1944
+ "ccmp",
1945
+ "locl",
1946
+ "liga"
1947
+ ].includes(tag) && !finalFeatures.includes(tag));
1948
+ const last = lookupsOf([...finalFeatures, ...extra]).map((lookup) => ({
1949
+ lookup,
1950
+ mask: lookup.features.reduce((mask, tag) => mask | (tag === "init" ? initMask : GLOBAL), 0),
1951
+ perSyllable: lookup.features.some((tag) => finalFeatures.includes(tag))
1952
+ }));
1953
+ /** This function reorders one syllable before the basic features, and gives the masks of its glyphs. */
1954
+ const initialReordering = (run, start, end) => {
1955
+ const { glyphs, infos } = run;
1956
+ let base = end;
1957
+ let hasReph = false;
1958
+ let limit = start;
1959
+ if (rphfMask !== 0 && start + 3 <= end && !isJoiner(infos[start + 2]) && would("rphf", [glyphs[start], glyphs[start + 1]])) {
1960
+ limit += 2;
1961
+ while (limit < end && isJoiner(infos[limit])) limit++;
1962
+ base = start;
1963
+ hasReph = true;
1964
+ }
1965
+ let seenBelow = false;
1966
+ for (let i = end - 1; i >= limit; i--) {
1967
+ const info = infos[i];
1968
+ if (isConsonant(info)) {
1969
+ base = i;
1970
+ if (info.position !== BELOW_C && (info.position !== POST_C || seenBelow)) break;
1971
+ if (info.position === BELOW_C) seenBelow = true;
1972
+ } else if (start < i && info.category === ZWJ && infos[i - 1].category === H) break;
1973
+ }
1974
+ if (hasReph && base === start && limit - base <= 2) hasReph = false;
1975
+ for (let i = start; i < base; i++) infos[i].position = Math.min(PRE_C, infos[i].position);
1976
+ if (base < end) infos[base].position = BASE_C;
1977
+ if (hasReph) infos[start].position = RA_TO_BECOME_REPH;
1978
+ let lastPosition = START;
1979
+ for (let i = start; i < end; i++) {
1980
+ const info = infos[i];
1981
+ if ([
1982
+ ZWJ,
1983
+ ZWNJ,
1984
+ N,
1985
+ RS,
1986
+ CM,
1987
+ H
1988
+ ].includes(info.category)) {
1989
+ info.position = lastPosition;
1990
+ if (info.category === H && info.position === PRE_M) {
1991
+ for (let j = i; j > start; j--) if (infos[j - 1].position !== PRE_M) {
1992
+ info.position = infos[j - 1].position;
1993
+ break;
1994
+ }
1995
+ }
1996
+ } else if (info.position !== SMVD) {
1997
+ if (info.category === MPST && i > start && infos[i - 1].category === SM) infos[i - 1].position = info.position;
1998
+ lastPosition = info.position;
1999
+ }
2000
+ }
2001
+ let previous = base;
2002
+ for (let i = base + 1; i < end; i++) if (isConsonant(infos[i])) {
2003
+ for (let j = previous + 1; j < i; j++) if (infos[j].position < SMVD) infos[j].position = infos[i].position;
2004
+ previous = i;
2005
+ } else if (infos[i].category === M || infos[i].category === MPST) previous = i;
2006
+ glyphs.slice(start, end).map((glyph, k) => ({
2007
+ glyph,
2008
+ info: infos[start + k]
2009
+ })).sort((a, b) => a.info.position - b.info.position).forEach(({ glyph, info }, k) => {
2010
+ glyphs[start + k] = glyph;
2011
+ infos[start + k] = info;
2012
+ });
2013
+ base = end;
2014
+ let firstLeftMatra = end;
2015
+ let lastLeftMatra = end;
2016
+ for (let i = start; i < end; i++) {
2017
+ if (infos[i].position === BASE_C) {
2018
+ base = i;
2019
+ break;
2020
+ }
2021
+ if (infos[i].position === PRE_M) {
2022
+ if (firstLeftMatra === end) firstLeftMatra = i;
2023
+ lastLeftMatra = i;
2024
+ }
2025
+ }
2026
+ if (firstLeftMatra < lastLeftMatra) {
2027
+ reverse(run, firstLeftMatra, lastLeftMatra + 1);
2028
+ let i = firstLeftMatra;
2029
+ for (let j = i; j <= lastLeftMatra; j++) if (infos[j].category === M || infos[j].category === MPST) {
2030
+ reverse(run, i, j + 1);
2031
+ i = j + 1;
2032
+ }
2033
+ }
2034
+ for (let i = start; i < end && infos[i].position === RA_TO_BECOME_REPH; i++) infos[i].mask |= rphfMask;
2035
+ for (let i = start; i < base; i++) infos[i].mask |= halfMask | blwfMask;
2036
+ for (let i = base + 1; i < end; i++) infos[i].mask |= blwfMask | abvfMask | pstfMask;
2037
+ if (prefMask !== 0 && base + 2 < end) {
2038
+ for (let i = base + 1; i + 1 < end; i++) if (would("pref", [glyphs[i], glyphs[i + 1]])) {
2039
+ infos[i].mask |= prefMask;
2040
+ infos[i + 1].mask |= prefMask;
2041
+ break;
2042
+ }
2043
+ }
2044
+ for (let i = start + 1; i < end; i++) {
2045
+ if (!isJoiner(infos[i]) || infos[i].category !== ZWNJ) continue;
2046
+ let j = i;
2047
+ do {
2048
+ j--;
2049
+ infos[j].mask &= ~halfMask;
2050
+ } while (j > start && !isConsonant(infos[j]));
2051
+ }
2052
+ };
2053
+ /** This function moves the pre-base matra and the reph of one syllable after the basic features. */
2054
+ const finalReordering = (run, start, end) => {
2055
+ const { glyphs, infos } = run;
2056
+ for (let i = start; i < end; i++) if (virama !== 0 && glyphs[i] === virama && (infos[i].props & 96) === 96) {
2057
+ infos[i].category = H;
2058
+ infos[i].props &= -97;
2059
+ }
2060
+ let tryPref = prefMask !== 0;
2061
+ let base = start;
2062
+ for (; base < end; base++) {
2063
+ if (infos[base].position < BASE_C) continue;
2064
+ if (tryPref && base + 1 < end) {
2065
+ for (let i = base + 1; i < end; i++) {
2066
+ if ((infos[i].mask & prefMask) === 0) continue;
2067
+ if (!((infos[i].props & 16) !== 0 && ligatedAndDidntMultiply(infos[i]))) {
2068
+ base = i;
2069
+ while (base < end && isHalant(infos[base])) base++;
2070
+ if (base < end) infos[base].position = BASE_C;
2071
+ tryPref = false;
2072
+ }
2073
+ break;
2074
+ }
2075
+ if (base === end) break;
2076
+ }
2077
+ if (start < base && infos[base].position > BASE_C) base--;
2078
+ break;
2079
+ }
2080
+ if (base === end && start < base && isOneOf(infos[base - 1], [ZWJ])) base--;
2081
+ if (base < end) while (start < base && isOneOf(infos[base], [N, H])) base--;
2082
+ if (start + 1 < end && start < base) {
2083
+ let newPosition = base === end ? base - 2 : base - 1;
2084
+ for (;;) {
2085
+ while (newPosition > start && !isOneOf(infos[newPosition], [
2086
+ M,
2087
+ MPST,
2088
+ H
2089
+ ])) newPosition--;
2090
+ if (isHalant(infos[newPosition]) && infos[newPosition].position !== PRE_M) {
2091
+ if (newPosition + 1 < end && infos[newPosition + 1].category === ZWJ && newPosition > start) {
2092
+ newPosition--;
2093
+ continue;
2094
+ }
2095
+ } else newPosition = start;
2096
+ break;
2097
+ }
2098
+ if (start < newPosition && infos[newPosition].position !== PRE_M) for (let i = newPosition; i > start; i--) {
2099
+ if (infos[i - 1].position !== PRE_M) continue;
2100
+ const oldPosition = i - 1;
2101
+ if (oldPosition < base && base <= newPosition) base--;
2102
+ move(run, oldPosition, newPosition);
2103
+ newPosition--;
2104
+ }
2105
+ }
2106
+ if (start + 1 < end && infos[start].position === RA_TO_BECOME_REPH && infos[start].category === REPHA !== ligatedAndDidntMultiply(infos[start])) {
2107
+ let newPosition = start + 1;
2108
+ while (newPosition < base && !isHalant(infos[newPosition])) newPosition++;
2109
+ if (newPosition < base && isHalant(infos[newPosition])) {
2110
+ if (newPosition + 1 < base && isJoiner(infos[newPosition + 1])) newPosition++;
2111
+ } else {
2112
+ newPosition = end - 1;
2113
+ while (newPosition > start && infos[newPosition].position === SMVD) newPosition--;
2114
+ if (isHalant(infos[newPosition])) {
2115
+ for (let i = base + 1; i < newPosition; i++) if (infos[i].category === M || infos[i].category === MPST) newPosition--;
2116
+ }
2117
+ }
2118
+ move(run, start, newPosition);
2119
+ if (start < base && base <= newPosition) base--;
2120
+ }
2121
+ if (tryPref && base + 1 < end) for (let i = base + 1; i < end; i++) {
2122
+ if ((infos[i].mask & prefMask) === 0) continue;
2123
+ if (ligatedAndDidntMultiply(infos[i])) {
2124
+ let newPosition = base;
2125
+ while (newPosition > start && !isOneOf(infos[newPosition - 1], [
2126
+ M,
2127
+ MPST,
2128
+ H
2129
+ ])) newPosition--;
2130
+ if (newPosition > start && isHalant(infos[newPosition - 1]) && newPosition < end && isJoiner(infos[newPosition])) newPosition++;
2131
+ move(run, i, newPosition);
2132
+ if (newPosition <= base && base < i) base++;
2133
+ }
2134
+ break;
2135
+ }
2136
+ if (infos[start].position === PRE_M && (start === 0 || !letterOrMark.test(String.fromCodePoint(infos[start - 1].codePoint)))) infos[start].mask |= initMask;
2137
+ };
2138
+ return (word) => {
2139
+ const codePoints = normalize(vowelConstraints([...word].map((character) => character.codePointAt(0))), (codePoint) => font.glyph(codePoint) !== 0);
2140
+ const run = {
2141
+ glyphs: codePoints.map((codePoint) => font.glyph(codePoint)),
2142
+ infos: []
2143
+ };
2144
+ for (const codePoint of codePoints) {
2145
+ const [category, position] = indicProperties(codePoint);
2146
+ run.infos.push({
2147
+ mask: GLOBAL,
2148
+ syllable: 0,
2149
+ props: 0,
2150
+ category,
2151
+ position,
2152
+ codePoint
2153
+ });
2154
+ }
2155
+ const broken = findSyllables(run.infos);
2156
+ for (const lookup of first) lookup.applyInfos(run.glyphs, run.infos, GLOBAL, true);
2157
+ if (virama !== 0) {
2158
+ for (let i = 0; i < run.infos.length; i++) if (run.infos[i].position === BASE_C) run.infos[i].position = consonantPosition(run.glyphs[i]);
2159
+ }
2160
+ if (broken && dottedCircle !== 0) for (let i = run.infos.length - 1; i >= 0; i--) {
2161
+ const info = run.infos[i];
2162
+ if ((info.syllable & 15) !== BROKEN_CLUSTER || i > 0 && run.infos[i - 1].syllable === info.syllable) continue;
2163
+ run.glyphs.splice(i, 0, dottedCircle);
2164
+ run.infos.splice(i, 0, {
2165
+ mask: info.mask,
2166
+ syllable: info.syllable,
2167
+ props: 0,
2168
+ category: DOTTEDCIRCLE,
2169
+ position: END,
2170
+ codePoint: 9676
2171
+ });
2172
+ }
2173
+ for (const [start, end] of syllables(run.infos)) {
2174
+ const type = run.infos[start].syllable & 15;
2175
+ if (type !== SYMBOL_CLUSTER && type !== NON_INDIC_CLUSTER) initialReordering(run, start, end);
2176
+ }
2177
+ for (const { lookups, mask } of basic) for (const lookup of lookups) lookup.applyInfos(run.glyphs, run.infos, mask, true);
2178
+ for (const [start, end] of syllables(run.infos)) finalReordering(run, start, end);
2179
+ for (const { lookup, mask, perSyllable } of last) lookup.applyInfos(run.glyphs, run.infos, mask, perSyllable);
2180
+ return run.glyphs;
2181
+ };
2182
+ }
2183
+ // src/fonts/joining.ts
2184
+ /** The forms in the order of the features of the shaper. The index of a form gives its bit in a feature mask. */
2185
+ const joiningFeatures = [
2186
+ "isol",
2187
+ "fina",
2188
+ "fin2",
2189
+ "fin3",
2190
+ "medi",
2191
+ "med2",
2192
+ "init"
2193
+ ];
2194
+ /** The OpenType script tags that use the joining of the Arabic shaper in this model. */
2195
+ const joiningScripts = ["arab", "syrc"];
2196
+ const U = 0;
2197
+ const R = 2;
2198
+ const D = 3;
2199
+ const ALAPH = 4;
2200
+ const DALATH_RISH = 5;
2201
+ /** The joining type T: a transparent character, for example a mark. The state machine does not see it. */
2202
+ const T = 6;
2203
+ /**
2204
+ * The joining types of `ArabicShaping.txt`. Each triple has the first code point, the last code point and the type.
2205
+ * The type `C` (join causing) is `D` here, as in HarfBuzz. A code point that is not in the table has the type `U`.
2206
+ * When its general category is `Mn`, `Me` or `Cf`, it has the type `T`.
2207
+ */
2208
+ const joiningTable = [
2209
+ 1536,
2210
+ 1541,
2211
+ U,
2212
+ 1544,
2213
+ 1544,
2214
+ U,
2215
+ 1547,
2216
+ 1547,
2217
+ U,
2218
+ 1568,
2219
+ 1568,
2220
+ D,
2221
+ 1569,
2222
+ 1569,
2223
+ U,
2224
+ 1570,
2225
+ 1573,
2226
+ R,
2227
+ 1574,
2228
+ 1574,
2229
+ D,
2230
+ 1575,
2231
+ 1575,
2232
+ R,
2233
+ 1576,
2234
+ 1576,
2235
+ D,
2236
+ 1577,
2237
+ 1577,
2238
+ R,
2239
+ 1578,
2240
+ 1582,
2241
+ D,
2242
+ 1583,
2243
+ 1586,
2244
+ R,
2245
+ 1587,
2246
+ 1599,
2247
+ D,
2248
+ 1600,
2249
+ 1600,
2250
+ D,
2251
+ 1601,
2252
+ 1607,
2253
+ D,
2254
+ 1608,
2255
+ 1608,
2256
+ R,
2257
+ 1609,
2258
+ 1610,
2259
+ D,
2260
+ 1646,
2261
+ 1647,
2262
+ D,
2263
+ 1649,
2264
+ 1651,
2265
+ R,
2266
+ 1652,
2267
+ 1652,
2268
+ U,
2269
+ 1653,
2270
+ 1655,
2271
+ R,
2272
+ 1656,
2273
+ 1671,
2274
+ D,
2275
+ 1672,
2276
+ 1689,
2277
+ R,
2278
+ 1690,
2279
+ 1727,
2280
+ D,
2281
+ 1728,
2282
+ 1728,
2283
+ R,
2284
+ 1729,
2285
+ 1730,
2286
+ D,
2287
+ 1731,
2288
+ 1739,
2289
+ R,
2290
+ 1740,
2291
+ 1740,
2292
+ D,
2293
+ 1741,
2294
+ 1741,
2295
+ R,
2296
+ 1742,
2297
+ 1742,
2298
+ D,
2299
+ 1743,
2300
+ 1743,
2301
+ R,
2302
+ 1744,
2303
+ 1745,
2304
+ D,
2305
+ 1746,
2306
+ 1747,
2307
+ R,
2308
+ 1749,
2309
+ 1749,
2310
+ R,
2311
+ 1757,
2312
+ 1757,
2313
+ U,
2314
+ 1774,
2315
+ 1775,
2316
+ R,
2317
+ 1786,
2318
+ 1788,
2319
+ D,
2320
+ 1791,
2321
+ 1791,
2322
+ D,
2323
+ 1807,
2324
+ 1807,
2325
+ T,
2326
+ 1808,
2327
+ 1808,
2328
+ ALAPH,
2329
+ 1810,
2330
+ 1812,
2331
+ D,
2332
+ 1813,
2333
+ 1814,
2334
+ DALATH_RISH,
2335
+ 1815,
2336
+ 1817,
2337
+ R,
2338
+ 1818,
2339
+ 1821,
2340
+ D,
2341
+ 1822,
2342
+ 1822,
2343
+ R,
2344
+ 1823,
2345
+ 1831,
2346
+ D,
2347
+ 1832,
2348
+ 1832,
2349
+ R,
2350
+ 1833,
2351
+ 1833,
2352
+ D,
2353
+ 1834,
2354
+ 1834,
2355
+ DALATH_RISH,
2356
+ 1835,
2357
+ 1835,
2358
+ D,
2359
+ 1836,
2360
+ 1836,
2361
+ R,
2362
+ 1837,
2363
+ 1838,
2364
+ D,
2365
+ 1839,
2366
+ 1839,
2367
+ DALATH_RISH,
2368
+ 1869,
2369
+ 1869,
2370
+ R,
2371
+ 1870,
2372
+ 1880,
2373
+ D,
2374
+ 1881,
2375
+ 1883,
2376
+ R,
2377
+ 1884,
2378
+ 1898,
2379
+ D,
2380
+ 1899,
2381
+ 1900,
2382
+ R,
2383
+ 1901,
2384
+ 1904,
2385
+ D,
2386
+ 1905,
2387
+ 1905,
2388
+ R,
2389
+ 1906,
2390
+ 1906,
2391
+ D,
2392
+ 1907,
2393
+ 1908,
2394
+ R,
2395
+ 1909,
2396
+ 1911,
2397
+ D,
2398
+ 1912,
2399
+ 1913,
2400
+ R,
2401
+ 1914,
2402
+ 1919,
2403
+ D,
2404
+ 1994,
2405
+ 2026,
2406
+ D,
2407
+ 2042,
2408
+ 2042,
2409
+ D,
2410
+ 2112,
2411
+ 2112,
2412
+ R,
2413
+ 2113,
2414
+ 2117,
2415
+ D,
2416
+ 2118,
2417
+ 2119,
2418
+ R,
2419
+ 2120,
2420
+ 2120,
2421
+ D,
2422
+ 2121,
2423
+ 2121,
2424
+ R,
2425
+ 2122,
2426
+ 2131,
2427
+ D,
2428
+ 2132,
2429
+ 2132,
2430
+ R,
2431
+ 2133,
2432
+ 2133,
2433
+ D,
2434
+ 2134,
2435
+ 2136,
2436
+ R,
2437
+ 2144,
2438
+ 2144,
2439
+ D,
2440
+ 2145,
2441
+ 2145,
2442
+ U,
2443
+ 2146,
2444
+ 2149,
2445
+ D,
2446
+ 2150,
2447
+ 2150,
2448
+ U,
2449
+ 2151,
2450
+ 2151,
2451
+ R,
2452
+ 2152,
2453
+ 2152,
2454
+ D,
2455
+ 2153,
2456
+ 2154,
2457
+ R,
2458
+ 2160,
2459
+ 2178,
2460
+ R,
2461
+ 2179,
2462
+ 2181,
2463
+ D,
2464
+ 2182,
2465
+ 2182,
2466
+ D,
2467
+ 2183,
2468
+ 2184,
2469
+ U,
2470
+ 2185,
2471
+ 2189,
2472
+ D,
2473
+ 2190,
2474
+ 2190,
2475
+ R,
2476
+ 2191,
2477
+ 2191,
2478
+ D,
2479
+ 2192,
2480
+ 2193,
2481
+ U,
2482
+ 2208,
2483
+ 2217,
2484
+ D,
2485
+ 2218,
2486
+ 2220,
2487
+ R,
2488
+ 2221,
2489
+ 2221,
2490
+ U,
2491
+ 2222,
2492
+ 2222,
2493
+ R,
2494
+ 2223,
2495
+ 2224,
2496
+ D,
2497
+ 2225,
2498
+ 2226,
2499
+ R,
2500
+ 2227,
2501
+ 2232,
2502
+ D,
2503
+ 2233,
2504
+ 2233,
2505
+ R,
2506
+ 2234,
2507
+ 2248,
2508
+ D,
2509
+ 2274,
2510
+ 2274,
2511
+ U,
2512
+ 8204,
2513
+ 8204,
2514
+ U,
2515
+ 8205,
2516
+ 8205,
2517
+ D
2518
+ ];
2519
+ const transparent = /[\p{Mn}\p{Me}\p{Cf}]/u;
2520
+ /** This function gives the joining type of a code point: a column of the state machine, or `T`. */
2521
+ function joiningType(codePoint) {
2522
+ let low = 0;
2523
+ let high = joiningTable.length / 3 - 1;
2524
+ while (low <= high) {
2525
+ const middle = low + high >> 1;
2526
+ if (codePoint < joiningTable[middle * 3]) high = middle - 1;
2527
+ else if (codePoint > joiningTable[middle * 3 + 1]) low = middle + 1;
2528
+ else return joiningTable[middle * 3 + 2];
2529
+ }
2530
+ return transparent.test(String.fromCodePoint(codePoint)) ? T : U;
2531
+ }
2532
+ const ISOL = 0;
2533
+ const FINA = 1;
2534
+ const FIN2 = 2;
2535
+ const FIN3 = 3;
2536
+ const MEDI = 4;
2537
+ const MED2 = 5;
2538
+ const INIT = 6;
2539
+ const NONE = 7;
2540
+ /**
2541
+ * The state machine of `arabic_state_table` of HarfBuzz. Each row is a state, with one entry for each column. An entry
2542
+ * has the action for the previous letter, the action for the current letter and the next state.
2543
+ */
2544
+ const states = [
2545
+ [
2546
+ [
2547
+ NONE,
2548
+ NONE,
2549
+ 0
2550
+ ],
2551
+ [
2552
+ NONE,
2553
+ ISOL,
2554
+ 2
2555
+ ],
2556
+ [
2557
+ NONE,
2558
+ ISOL,
2559
+ 1
2560
+ ],
2561
+ [
2562
+ NONE,
2563
+ ISOL,
2564
+ 2
2565
+ ],
2566
+ [
2567
+ NONE,
2568
+ ISOL,
2569
+ 1
2570
+ ],
2571
+ [
2572
+ NONE,
2573
+ ISOL,
2574
+ 6
2575
+ ]
2576
+ ],
2577
+ [
2578
+ [
2579
+ NONE,
2580
+ NONE,
2581
+ 0
2582
+ ],
2583
+ [
2584
+ NONE,
2585
+ ISOL,
2586
+ 2
2587
+ ],
2588
+ [
2589
+ NONE,
2590
+ ISOL,
2591
+ 1
2592
+ ],
2593
+ [
2594
+ NONE,
2595
+ ISOL,
2596
+ 2
2597
+ ],
2598
+ [
2599
+ NONE,
2600
+ FIN2,
2601
+ 5
2602
+ ],
2603
+ [
2604
+ NONE,
2605
+ ISOL,
2606
+ 6
2607
+ ]
2608
+ ],
2609
+ [
2610
+ [
2611
+ NONE,
2612
+ NONE,
2613
+ 0
2614
+ ],
2615
+ [
2616
+ NONE,
2617
+ ISOL,
2618
+ 2
2619
+ ],
2620
+ [
2621
+ INIT,
2622
+ FINA,
2623
+ 1
2624
+ ],
2625
+ [
2626
+ INIT,
2627
+ FINA,
2628
+ 3
2629
+ ],
2630
+ [
2631
+ INIT,
2632
+ FINA,
2633
+ 4
2634
+ ],
2635
+ [
2636
+ INIT,
2637
+ FINA,
2638
+ 6
2639
+ ]
2640
+ ],
2641
+ [
2642
+ [
2643
+ NONE,
2644
+ NONE,
2645
+ 0
2646
+ ],
2647
+ [
2648
+ NONE,
2649
+ ISOL,
2650
+ 2
2651
+ ],
2652
+ [
2653
+ MEDI,
2654
+ FINA,
2655
+ 1
2656
+ ],
2657
+ [
2658
+ MEDI,
2659
+ FINA,
2660
+ 3
2661
+ ],
2662
+ [
2663
+ MEDI,
2664
+ FINA,
2665
+ 4
2666
+ ],
2667
+ [
2668
+ MEDI,
2669
+ FINA,
2670
+ 6
2671
+ ]
2672
+ ],
2673
+ [
2674
+ [
2675
+ NONE,
2676
+ NONE,
2677
+ 0
2678
+ ],
2679
+ [
2680
+ NONE,
2681
+ ISOL,
2682
+ 2
2683
+ ],
2684
+ [
2685
+ MED2,
2686
+ ISOL,
2687
+ 1
2688
+ ],
2689
+ [
2690
+ MED2,
2691
+ ISOL,
2692
+ 2
2693
+ ],
2694
+ [
2695
+ MED2,
2696
+ FIN2,
2697
+ 5
2698
+ ],
2699
+ [
2700
+ MED2,
2701
+ ISOL,
2702
+ 6
2703
+ ]
2704
+ ],
2705
+ [
2706
+ [
2707
+ NONE,
2708
+ NONE,
2709
+ 0
2710
+ ],
2711
+ [
2712
+ NONE,
2713
+ ISOL,
2714
+ 2
2715
+ ],
2716
+ [
2717
+ ISOL,
2718
+ ISOL,
2719
+ 1
2720
+ ],
2721
+ [
2722
+ ISOL,
2723
+ ISOL,
2724
+ 2
2725
+ ],
2726
+ [
2727
+ ISOL,
2728
+ FIN2,
2729
+ 5
2730
+ ],
2731
+ [
2732
+ ISOL,
2733
+ ISOL,
2734
+ 6
2735
+ ]
2736
+ ],
2737
+ [
2738
+ [
2739
+ NONE,
2740
+ NONE,
2741
+ 0
2742
+ ],
2743
+ [
2744
+ NONE,
2745
+ ISOL,
2746
+ 2
2747
+ ],
2748
+ [
2749
+ NONE,
2750
+ ISOL,
2751
+ 1
2752
+ ],
2753
+ [
2754
+ NONE,
2755
+ ISOL,
2756
+ 2
2757
+ ],
2758
+ [
2759
+ NONE,
2760
+ FIN3,
2761
+ 5
2762
+ ],
2763
+ [
2764
+ NONE,
2765
+ ISOL,
2766
+ 6
2767
+ ]
2768
+ ]
2769
+ ];
2770
+ /**
2771
+ * This function gives the joining form of each code point of a text, in logical order. A transparent character, for
2772
+ * example a mark, and a character that does not join get `undefined`. The text before and after the code points is not
2773
+ * known, so the function sees it as a character of the type U.
2774
+ */
2775
+ function joiningForms(codePoints) {
2776
+ const actions = codePoints.map(() => NONE);
2777
+ let previous = -1;
2778
+ let state = 0;
2779
+ codePoints.forEach((codePoint, i) => {
2780
+ const type = joiningType(codePoint);
2781
+ if (type === T) return;
2782
+ const [previousAction, currentAction, next] = states[state][type];
2783
+ if (previousAction !== NONE && previous >= 0) actions[previous] = previousAction;
2784
+ actions[i] = currentAction;
2785
+ previous = i;
2786
+ state = next;
2787
+ });
2788
+ return actions.map((action) => joiningFeatures[action]);
2789
+ }
2790
+ /**
2791
+ * This function gives the stages of the Arabic shaper of HarfBuzz for a list of features. The stages are `ccmp` and
2792
+ * `locl`, then each joining form alone, then `rlig`, then `calt`, then the other features. When the font has `rclt`,
2793
+ * `calt` and the other features are one stage, as in HarfBuzz. The list `features` gives the features apart from the
2794
+ * joining forms, so a feature that is not in the list is not in a stage.
2795
+ */
2796
+ function joiningStages(features, hasRclt) {
2797
+ const take = (tags) => features.filter((tag) => tags.includes(tag));
2798
+ const first = ["ccmp", "locl"];
2799
+ const rest = features.filter((tag) => !first.includes(tag) && tag !== "rlig" && (hasRclt || tag !== "calt"));
2800
+ const stages = [
2801
+ { features: take(first) },
2802
+ ...joiningFeatures.map((form) => ({
2803
+ features: [form],
2804
+ form
2805
+ })),
2806
+ { features: take(["rlig"]) }
2807
+ ];
2808
+ if (!hasRclt) stages.push({ features: take(["calt"]) });
2809
+ stages.push({ features: rest });
2810
+ return stages.filter((stage) => stage.features.length > 0);
2811
+ }
2812
+ // src/fonts/bidi.ts
2813
+ const nonSpacingMark = /[\p{Mn}\p{Me}]/u;
2814
+ const currency = /\p{Sc}/u;
2815
+ const format = /\p{Cf}/u;
2816
+ const hebrew = /\p{Script_Extensions=Hebrew}/u;
2817
+ const arabicLetter = /[\p{Script_Extensions=Arabic}\p{Script_Extensions=Syriac}\p{Script_Extensions=Thaana}]/u;
2818
+ const letter = /[\p{L}\p{Mc}]/u;
2819
+ const commonSeparators = /* @__PURE__ */ new Set([
2820
+ 44,
2821
+ 46,
2822
+ 47,
2823
+ 58,
2824
+ 160,
2825
+ 1548,
2826
+ 8239,
2827
+ 8260
2828
+ ]);
2829
+ /** This function gives the bidi class of a character, from its code point, its script and its general category. */
2830
+ function bidiClass(character) {
2831
+ const c = character.codePointAt(0);
2832
+ if (nonSpacingMark.test(character)) return "NSM";
2833
+ if (c >= 48 && c <= 57 || c >= 1776 && c <= 1785 || c === 178 || c === 179 || c === 185 || c >= 65296 && c <= 65305) return "EN";
2834
+ if (c >= 1632 && c <= 1641 || c === 1643 || c === 1644) return "AN";
2835
+ if (c === 43 || c === 45 || c === 8722) return "ES";
2836
+ if (c === 35 || c === 37 || c === 176 || c === 8240 || currency.test(character)) return "ET";
2837
+ if (commonSeparators.has(c)) return "CS";
2838
+ if (c === 9 || c === 11 || c === 31) return "S";
2839
+ if (c === 10 || c === 13 || c >= 28 && c <= 30 || c === 133 || c === 8233) return "B";
2840
+ if (c === 32 || c === 12 || c >= 8192 && c <= 8202 || c === 8232 || c === 12288) return "WS";
2841
+ if (format.test(character)) return "BN";
2842
+ if (hebrew.test(character)) return "R";
2843
+ if (arabicLetter.test(character)) return "AL";
2844
+ return letter.test(character) ? "L" : "ON";
2845
+ }
2846
+ /** This function gives the direction of a strong class for the rule N1: `EN` and `AN` count as `R`. */
2847
+ function strongDirection(type) {
2848
+ if (type === "L") return "L";
2849
+ return type === "R" || type === "EN" || type === "AN" ? "R" : void 0;
2850
+ }
2851
+ /**
2852
+ * This function gives the bidi level of each code point of a text, in a paragraph with the base level 0. It applies the
2853
+ * rules W1 to W7, N1, N2, I1 and L1 of UAX #9.
2854
+ */
2855
+ function bidiLevels(text) {
2856
+ const original = [...text].map(bidiClass);
2857
+ const types = [...original];
2858
+ const n = types.length;
2859
+ for (let i = 0; i < n; i++) if (types[i] === "NSM") types[i] = i === 0 || types[i - 1] === "BN" ? "L" : types[i - 1];
2860
+ let strong = "L";
2861
+ for (let i = 0; i < n; i++) {
2862
+ const type = types[i];
2863
+ if (type === "L" || type === "R" || type === "AL") strong = type;
2864
+ else if (type === "EN" && strong === "AL") types[i] = "AN";
2865
+ }
2866
+ for (let i = 0; i < n; i++) if (types[i] === "AL") types[i] = "R";
2867
+ for (let i = 1; i + 1 < n; i++) {
2868
+ const before = types[i - 1];
2869
+ if (types[i] === "ES" && before === "EN" && types[i + 1] === "EN") types[i] = "EN";
2870
+ else if (types[i] === "CS" && (before === "EN" || before === "AN") && types[i + 1] === before) types[i] = before;
2871
+ }
2872
+ for (let i = 0; i < n; i++) {
2873
+ if (types[i] !== "ET") continue;
2874
+ let end = i;
2875
+ while (end < n && types[end] === "ET") end++;
2876
+ if (i > 0 && types[i - 1] === "EN" || end < n && types[end] === "EN") types.fill("EN", i, end);
2877
+ i = end - 1;
2878
+ }
2879
+ for (let i = 0; i < n; i++) if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") types[i] = "ON";
2880
+ strong = "L";
2881
+ for (let i = 0; i < n; i++) {
2882
+ const type = types[i];
2883
+ if (type === "L" || type === "R") strong = type;
2884
+ else if (type === "EN" && strong === "L") types[i] = "L";
2885
+ }
2886
+ for (let i = 0; i < n; i++) {
2887
+ if (strongDirection(types[i]) !== void 0) continue;
2888
+ let end = i;
2889
+ while (end < n && strongDirection(types[end]) === void 0) end++;
2890
+ const before = i === 0 ? "L" : strongDirection(types[i - 1]);
2891
+ const after = end === n ? "L" : strongDirection(types[end]);
2892
+ types.fill(before === after ? before : "L", i, end);
2893
+ i = end - 1;
2894
+ }
2895
+ const levels = types.map((type) => type === "R" ? 1 : type === "AN" || type === "EN" ? 2 : 0);
2896
+ const isSpace = (i) => original[i] === "WS" || original[i] === "BN";
2897
+ for (let i = n - 1; i >= 0 && isSpace(i); i--) levels[i] = 0;
2898
+ for (let i = 0; i < n; i++) {
2899
+ if (original[i] !== "S" && original[i] !== "B") continue;
2900
+ levels[i] = 0;
2901
+ for (let k = i - 1; k >= 0 && isSpace(k); k--) levels[k] = 0;
2902
+ }
2903
+ return levels;
2904
+ }
2905
+ /**
2906
+ * This function puts items in the visual order of the rule L2 of UAX #9. Each item has the level `levels[i]`. From the
2907
+ * highest level to the level 1, it reverses each sequence of items at that level or higher.
2908
+ */
2909
+ function visualOrder(items, levels) {
2910
+ const order = items.map((_, i) => i);
2911
+ const at = [...levels];
2912
+ for (let level = Math.max(0, ...levels); level >= 1; level--) for (let i = 0; i < order.length; i++) {
2913
+ if (at[i] < level) continue;
2914
+ let end = i;
2915
+ while (end < order.length && at[end] >= level) end++;
2916
+ order.splice(i, end - i, ...order.slice(i, end).reverse());
2917
+ at.splice(i, end - i, ...at.slice(i, end).reverse());
2918
+ i = end;
2919
+ }
2920
+ return order.map((i) => items[i]);
2921
+ }
2922
+ // src/fonts/words.ts
2923
+ /**
2924
+ * The words of the text of a canvas. Blink shapes the text of `fillText` and `measureText` one word at a time, so no GSUB
2925
+ * context and no pair kerning goes across two words. This module follows `NextWordEndIndex` of
2926
+ * `third_party/blink/renderer/platform/fonts/plain_text_node.cc` and the CJK table of `character_property_data.h`.
2927
+ * The Chromium Authors wrote these files under a BSD-style license.
2928
+ */
2929
+ /** The single code points of the CJK table of Blink. */
2930
+ const cjkValues = /* @__PURE__ */ new Set([
2931
+ 711,
2932
+ 714,
2933
+ 715,
2934
+ 729,
2935
+ 8224,
2936
+ 8225,
2937
+ 8240,
2938
+ 8251,
2939
+ 8252,
2940
+ 8258,
2941
+ 8263,
2942
+ 8264,
2943
+ 8265,
2944
+ 8273,
2945
+ 8413,
2946
+ 8414,
2947
+ 8448,
2948
+ 8451,
2949
+ 8453,
2950
+ 8457,
2951
+ 8458,
2952
+ 8467,
2953
+ 8470,
2954
+ 8481,
2955
+ 8491,
2956
+ 8507,
2957
+ 8528,
2958
+ 8529,
2959
+ 8530,
2960
+ 8575,
2961
+ 8585,
2962
+ 8967,
2963
+ 8978,
2964
+ 9166,
2965
+ 9251,
2966
+ 9632,
2967
+ 9633,
2968
+ 9634,
2969
+ 9642,
2970
+ 9643,
2971
+ 9649,
2972
+ 9650,
2973
+ 9651,
2974
+ 9654,
2975
+ 9655,
2976
+ 9660,
2977
+ 9661,
2978
+ 9664,
2979
+ 9665,
2980
+ 9670,
2981
+ 9671,
2982
+ 9673,
2983
+ 9675,
2984
+ 9676,
2985
+ 9711,
2986
+ 9733,
2987
+ 9734,
2988
+ 9742,
2989
+ 9750,
2990
+ 9751,
2991
+ 9888,
2992
+ 10003,
2993
+ 10010,
2994
+ 10047,
2995
+ 10048,
2996
+ 10070,
2997
+ 10083,
2998
+ 11034,
2999
+ 65040,
3000
+ 65041,
3001
+ 65042,
3002
+ 65049,
3003
+ 65309,
3004
+ 127232,
3005
+ 127488,
3006
+ 127543,
3007
+ 127788,
3008
+ 127798,
3009
+ 127869,
3010
+ 128063,
3011
+ 128335,
3012
+ 129339,
3013
+ 129350
3014
+ ]);
3015
+ /** The ranges of the CJK table of Blink, as pairs of the first and the last code point. */
3016
+ const cjkRanges = [
3017
+ 11904,
3018
+ 12255,
3019
+ 12736,
3020
+ 12783,
3021
+ 13312,
3022
+ 19903,
3023
+ 19968,
3024
+ 40959,
3025
+ 63744,
3026
+ 64255,
3027
+ 131072,
3028
+ 196607,
3029
+ 8534,
3030
+ 8538,
3031
+ 8544,
3032
+ 8555,
3033
+ 8560,
3034
+ 8571,
3035
+ 9150,
3036
+ 9164,
3037
+ 9312,
3038
+ 9362,
3039
+ 9372,
3040
+ 9471,
3041
+ 9678,
3042
+ 9683,
3043
+ 9698,
3044
+ 9702,
3045
+ 9728,
3046
+ 9731,
3047
+ 9824,
3048
+ 9839,
3049
+ 9842,
3050
+ 9853,
3051
+ 10102,
3052
+ 10111,
3053
+ 12272,
3054
+ 12333,
3055
+ 12337,
3056
+ 12591,
3057
+ 12688,
3058
+ 12735,
3059
+ 12800,
3060
+ 13311,
3061
+ 19904,
3062
+ 19967,
3063
+ 63584,
3064
+ 63586,
3065
+ 65072,
3066
+ 65135,
3067
+ 65280,
3068
+ 65292,
3069
+ 65294,
3070
+ 65306,
3071
+ 65311,
3072
+ 65519,
3073
+ 94176,
3074
+ 94207,
3075
+ 94208,
3076
+ 100351,
3077
+ 100352,
3078
+ 101119,
3079
+ 110592,
3080
+ 110847,
3081
+ 110848,
3082
+ 110895,
3083
+ 110960,
3084
+ 111359,
3085
+ 127248,
3086
+ 127273,
3087
+ 127280,
3088
+ 127305,
3089
+ 127312,
3090
+ 127337,
3091
+ 127344,
3092
+ 127369,
3093
+ 127490,
3094
+ 127513,
3095
+ 127515,
3096
+ 127534,
3097
+ 127536,
3098
+ 127537,
3099
+ 127547,
3100
+ 127567,
3101
+ 127570,
3102
+ 127743,
3103
+ 127777,
3104
+ 127786,
3105
+ 127892,
3106
+ 127903,
3107
+ 127949,
3108
+ 127950,
3109
+ 127956,
3110
+ 127967,
3111
+ 127985,
3112
+ 127986,
3113
+ 127989,
3114
+ 127991,
3115
+ 128253,
3116
+ 128254,
3117
+ 128318,
3118
+ 128330,
3119
+ 128360,
3120
+ 128371,
3121
+ 128374,
3122
+ 128377,
3123
+ 128379,
3124
+ 128399,
3125
+ 128401,
3126
+ 128404,
3127
+ 128407,
3128
+ 128419,
3129
+ 128421,
3130
+ 128487,
3131
+ 128489,
3132
+ 128506,
3133
+ 128592,
3134
+ 128639,
3135
+ 128710,
3136
+ 128715,
3137
+ 128717,
3138
+ 128719,
3139
+ 128723,
3140
+ 128724,
3141
+ 128729,
3142
+ 128731,
3143
+ 128736,
3144
+ 128746,
3145
+ 128749,
3146
+ 128755,
3147
+ 128765,
3148
+ 128767,
3149
+ 129280,
3150
+ 129291,
3151
+ 129737,
3152
+ 129740
3153
+ ];
3154
+ const emojiPresentation = /\p{Emoji_Presentation}/u;
3155
+ const modifierLike = /[\p{M}\p{Lm}\p{Sk}]/u;
3156
+ const continuesEmoji = /[‍\p{Emoji_Component}\p{Extended_Pictographic}]/u;
3157
+ const commonOrInherited = /[\p{Script=Common}\p{Script=Inherited}]/u;
3158
+ /**
3159
+ * This function tells if Blink starts a CJK word at the code point `codePoint`. Blink also marks the pictographs of the
3160
+ * RGI emoji sequences. This function uses `Emoji_Presentation` for them, so some text-style pictographs are missing.
3161
+ */
3162
+ function isCjkIdeographOrSymbol(codePoint) {
3163
+ if (codePoint < 711) return false;
3164
+ if (cjkValues.has(codePoint)) return true;
3165
+ for (let i = 0; i < cjkRanges.length; i += 2) if (codePoint >= cjkRanges[i] && codePoint <= cjkRanges[i + 1]) return true;
3166
+ return emojiPresentation.test(String.fromCodePoint(codePoint));
3167
+ }
3168
+ /** This function tells if Blink ends a word before the character `character`: a space, a tab or a zero width space. */
3169
+ function isDelimiter(character) {
3170
+ return character === " " || character === " " || character === "​";
3171
+ }
3172
+ /**
3173
+ * This function divides the text of a canvas into the words that Blink shapes one at a time. Each space, tab and zero
3174
+ * width space is a word alone. Each CJK character starts a word, with its marks and the CJK characters of the script
3175
+ * Common after it.
3176
+ */
3177
+ function canvasWords(text) {
3178
+ const characters = [...text];
3179
+ const words = [];
3180
+ let start = 0;
3181
+ while (start < characters.length) {
3182
+ const end = wordEnd(characters, start);
3183
+ words.push(characters.slice(start, end).join(""));
3184
+ start = end;
3185
+ }
3186
+ return words;
3187
+ }
3188
+ /**
3189
+ * This function gives the segments of the text of a canvas in the visual order. A segment is a word of `canvasWords`, or a
3190
+ * part of a word where the bidi level changes. Blink shapes each bidi run alone and adds the widths of the words in this order.
3191
+ */
3192
+ function canvasSegments(text) {
3193
+ const characters = [...text];
3194
+ const levels = bidiLevels(text);
3195
+ const segments = [];
3196
+ const segmentLevels = [];
3197
+ let at = 0;
3198
+ for (const word of canvasWords(text)) {
3199
+ const end = at + [...word].length;
3200
+ let start = at;
3201
+ for (let i = at + 1; i <= end; i++) {
3202
+ if (i < end && levels[i] === levels[start]) continue;
3203
+ segments.push(characters.slice(start, i).join(""));
3204
+ segmentLevels.push(levels[start]);
3205
+ start = i;
3206
+ }
3207
+ at = end;
3208
+ }
3209
+ return visualOrder(segments, segmentLevels);
3210
+ }
3211
+ /** This function gives the index after the last character of the word that starts at `start`. */
3212
+ function wordEnd(characters, start) {
3213
+ const first = characters[start];
3214
+ if (start + 1 === characters.length || isDelimiter(first)) return start + 1;
3215
+ let end = start + 1;
3216
+ if (!isCjkIdeographOrSymbol(first.codePointAt(0))) {
3217
+ for (; end < characters.length; end++) {
3218
+ const character = characters[end];
3219
+ if (isDelimiter(character) || isCjkIdeographOrSymbol(character.codePointAt(0)) && !modifierLike.test(character)) return end;
3220
+ }
3221
+ return end;
3222
+ }
3223
+ let hasScript = !commonOrInherited.test(first);
3224
+ for (; end < characters.length; end++) {
3225
+ const character = characters[end];
3226
+ if (modifierLike.test(character) || continuesEmoji.test(character)) continue;
3227
+ if (isCjkIdeographOrSymbol(character.codePointAt(0))) {
3228
+ if (commonOrInherited.test(character)) continue;
3229
+ if (!hasScript) {
3230
+ hasScript = true;
3231
+ continue;
3232
+ }
3233
+ }
3234
+ return end;
3235
+ }
3236
+ return end;
3237
+ }
3238
+ // src/fonts/font.ts
3239
+ /** This function reads a TrueType, OpenType or WOFF2 file. */
3240
+ function readFont(bytes, options = {}) {
3241
+ const tables = readTables(bytes, options.decompress);
3242
+ const need = (tag) => {
3243
+ const table = tables.get(tag);
3244
+ if (table === void 0) throw new Error(`the font has no ${tag} table`);
3245
+ return table;
3246
+ };
3247
+ const header = readHeader(need("head"), need("hhea"), need("maxp"));
3248
+ const parsed = {
3249
+ tables,
3250
+ header,
3251
+ advances: readAdvances(need("hmtx"), header),
3252
+ characters: readCharacterMap(need("cmap")),
3253
+ glyphClass: readGlyphClasses(tables.get("GDEF")),
3254
+ filter: readGlyphFilter(tables.get("GDEF"), readGlyphClasses(tables.get("GDEF"))),
3255
+ axes: tables.has("fvar") ? readAxes(tables.get("fvar")) : [],
3256
+ points: lazy(() => pointsOf(tables, need("head"), header.numGlyphs))
3257
+ };
3258
+ const modes = {
3259
+ deltas: options.deltas ?? "round",
3260
+ advances: options.advances ?? "auto",
3261
+ metrics: options.metrics ?? "own"
3262
+ };
3263
+ return build(parsed, options.instance ?? {}, modes);
3264
+ }
3265
+ /** This function gives a function that computes a value at its first call and then keeps it. */
3266
+ function lazy(compute) {
3267
+ let done = false;
3268
+ let value;
3269
+ return () => {
3270
+ if (!done) {
3271
+ value = compute();
3272
+ done = true;
3273
+ }
3274
+ return value;
3275
+ };
3276
+ }
3277
+ /**
3278
+ * This function gives the points of each glyph, or `undefined` when the font has no `glyf` table. A WOFF2 file keeps
3279
+ * `glyf` in its transformed form, and its `loca` table is then empty.
3280
+ */
3281
+ function pointsOf(tables, head, numGlyphs) {
3282
+ const glyf = tables.get("glyf");
3283
+ const loca = tables.get("loca");
3284
+ if (glyf === void 0 || loca === void 0) return void 0;
3285
+ return loca.length === 0 ? readTransformedPointCounts(glyf, numGlyphs) : readPointCounts(glyf, loca, head.i16(50), numGlyphs);
3286
+ }
3287
+ /** This function gives the font at one instance. */
3288
+ function build(parsed, requested, modes) {
3289
+ const { tables, header, characters, glyphClass, axes } = parsed;
3290
+ const { deltas } = modes;
3291
+ const instance = Object.fromEntries(axes.map((axis) => [axis.tag, Math.min(axis.max, Math.max(axis.min, requested[axis.tag] ?? axis.default))]));
3292
+ const coordinates = normalize$1(axes, tables.get("avar"), instance);
3293
+ const variable = coordinates.some((value) => value !== 0);
3294
+ const hvar = tables.get("HVAR");
3295
+ const gvar = tables.get("gvar");
3296
+ const phantoms = gvar !== void 0 && (modes.advances === "gvar" || hvar === void 0) && parsed.points() !== void 0;
3297
+ const advanceSource = !variable ? "hmtx" : phantoms ? "gvar" : hvar !== void 0 ? "HVAR" : "hmtx";
3298
+ const advances = advanceSource === "gvar" ? phantomAdvances(parsed.advances, gvarAdvanceDeltas(gvar, parsed.points().counts, coordinates, deltas)) : instanceAdvances(parsed.advances, advanceSource === "HVAR" ? hvar : void 0, coordinates, deltas);
3299
+ const metrics = modes.metrics === "component" ? parsed.points()?.metrics : void 0;
3300
+ if (metrics !== void 0) componentAdvances(advances, metrics);
3301
+ const gdef = tables.get("GDEF");
3302
+ const storeOffset = variable && gdef !== void 0 && gdef.u16(2) >= 3 ? gdef.u32(14) : 0;
3303
+ const store = storeOffset === 0 ? void 0 : readVariationStore(gdef.part(storeOffset), coordinates);
3304
+ const gpos = tables.get("GPOS");
3305
+ const kern = tables.get("kern");
3306
+ const gsub = tables.get("GSUB");
3307
+ const lookups = /* @__PURE__ */ new Map();
3308
+ const substitutions = /* @__PURE__ */ new Map();
3309
+ const hashed = [
3310
+ tables.get("cmap"),
3311
+ gdef,
3312
+ gpos,
3313
+ kern,
3314
+ tables.get("fvar"),
3315
+ tables.get("avar"),
3316
+ tables.get("HVAR"),
3317
+ gsub
3318
+ ];
3319
+ return {
3320
+ epoch: `${header.revision.toString(16).padStart(8, "0")}-${widthHash(header.unitsPerEm, coordinates, advances, hashed)}${deltas === "exact" ? "-exact" : ""}${advanceSource === "gvar" && hvar !== void 0 ? "-gvar" : ""}${metrics !== void 0 ? "-metrics" : ""}`,
3321
+ unitsPerEm: header.unitsPerEm,
3322
+ numGlyphs: header.numGlyphs,
3323
+ axes,
3324
+ instance,
3325
+ coordinates,
3326
+ advanceSource,
3327
+ glyph: (codePoint) => characters.get(codePoint) ?? 0,
3328
+ advance: (glyph) => glyphClass(glyph) === 3 ? 0 : advances[glyph] ?? 0,
3329
+ glyphClass,
3330
+ kerning(script) {
3331
+ let found = lookups.get(script);
3332
+ if (found === void 0) {
3333
+ found = gpos !== void 0 ? readGposKerning(gpos, script, store, parsed.filter) : kern !== void 0 ? readKernTable(kern) : [];
3334
+ lookups.set(script, found);
3335
+ }
3336
+ return found;
3337
+ },
3338
+ substitutions(script, features) {
3339
+ const key = `${script} ${features.join(",")}`;
3340
+ let found = substitutions.get(key);
3341
+ if (found === void 0) {
3342
+ found = gsub === void 0 ? [] : readSubstitutions(gsub, script, features, parsed.filter);
3343
+ substitutions.set(key, found);
3344
+ }
3345
+ return found;
3346
+ },
3347
+ at: (next) => build(parsed, next, modes)
3348
+ };
3349
+ }
3350
+ /**
3351
+ * This function gives each composite glyph the advance of its component with the flag USE_MY_METRICS. A component can
3352
+ * have that flag in its own components, so the function follows the chain. A chain of more than 8 steps stops.
3353
+ */
3354
+ function componentAdvances(advances, metrics) {
3355
+ const own = Float64Array.from(advances);
3356
+ for (let glyph = 0; glyph < metrics.length; glyph++) {
3357
+ let source = glyph;
3358
+ for (let step = 0; step < 8 && (metrics[source] ?? -1) >= 0; step++) source = metrics[source];
3359
+ if (source !== glyph && source < own.length) advances[glyph] = own[source];
3360
+ }
3361
+ }
3362
+ /** This function adds the advance delta of each glyph to its default advance. A negative sum becomes 0. */
3363
+ function phantomAdvances(advances, deltas) {
3364
+ return Float64Array.from(advances, (advance, glyph) => Math.max(0, advance + (deltas[glyph] ?? 0)));
3365
+ }
3366
+ /**
3367
+ * This function gives 8 hexadecimal digits of FNV-1a. The hash covers the units per em, the coordinates of the instance,
3368
+ * the advances and the tables that change a width.
3369
+ */
3370
+ function widthHash(unitsPerEm, coordinates, advances, tables) {
3371
+ const parts = [String(unitsPerEm), coordinates.join(",")];
3372
+ const units = Uint16Array.from(advances, (value) => value & 65535);
3373
+ for (let i = 0; i < units.length; i += 4096) parts.push(String.fromCharCode(...units.subarray(i, i + 4096)));
3374
+ for (const table of tables) {
3375
+ parts.push(table === void 0 ? "-" : "+");
3376
+ if (table === void 0) continue;
3377
+ for (let i = 0; i < table.length; i += 8192) parts.push(String.fromCharCode(...table.bytes.subarray(i, i + 8192)));
3378
+ }
3379
+ return fnv1a32(parts.join("")).toString(16).padStart(8, "0");
3380
+ }
3381
+ /** This function tells if a lookup with the flags `flags` skips a glyph of the GDEF class `glyphClass`. */
3382
+ function skips(flags, glyphClass) {
3383
+ return (flags & 8) !== 0 && glyphClass === 3 || (flags & 2) !== 0 && glyphClass === 1 || (flags & 4) !== 0 && glyphClass === 2;
3384
+ }
3385
+ /**
3386
+ * This function divides a text into the words that the GSUB pass shapes one at a time. Each space and each tab is a word
3387
+ * alone. Blink shapes the text of a canvas in the same words, so no GSUB context goes across a space.
3388
+ */
3389
+ function shapingWords(text) {
3390
+ const words = [];
3391
+ let word = "";
3392
+ for (const character of text) {
3393
+ if (character !== " " && character !== " ") {
3394
+ word += character;
3395
+ continue;
3396
+ }
3397
+ if (word !== "") words.push(word);
3398
+ words.push(character);
3399
+ word = "";
3400
+ }
3401
+ if (word !== "") words.push(word);
3402
+ return words;
3403
+ }
3404
+ /** The GSUB pass of one set of options: a function that gives the glyphs of one word, and the kerning lookups. */
3405
+ function shaperOf(font, options) {
3406
+ const script = options.script ?? "DFLT";
3407
+ const features = options.features ?? defaultFeatures;
3408
+ const kerning = options.kerning === false ? [] : font.kerning(script);
3409
+ if (options.indic !== false && indicScripts.includes(script)) return {
3410
+ substitute: indicShaper(font, script, features),
3411
+ kerning
3412
+ };
3413
+ const joins = options.joining !== false && joiningScripts.includes(script);
3414
+ const lookups = joins ? [] : font.substitutions(script, features);
3415
+ const stages = joins ? joiningStages(features, features.includes("rclt") && font.substitutions(script, ["rclt"]).length > 0).map((stage) => ({
3416
+ stage,
3417
+ lookups: font.substitutions(script, stage.features)
3418
+ })) : [];
3419
+ const substitute = (word) => {
3420
+ const codePoints = [...word].map((character) => character.codePointAt(0));
3421
+ const wordGlyphs = codePoints.map((codePoint) => font.glyph(codePoint));
3422
+ for (const lookup of lookups) lookup.apply(wordGlyphs);
3423
+ if (joins) {
3424
+ const masks = joiningForms(codePoints).map((form) => 128 | (form === void 0 ? 0 : 1 << joiningFeatures.indexOf(form)));
3425
+ for (const { stage, lookups: stageLookups } of stages) {
3426
+ const mask = stage.form === void 0 ? 128 : 1 << joiningFeatures.indexOf(stage.form);
3427
+ for (const lookup of stageLookups) lookup.apply(wordGlyphs, masks, mask);
3428
+ }
3429
+ }
3430
+ return wordGlyphs;
3431
+ };
3432
+ return {
3433
+ substitute,
3434
+ kerning
3435
+ };
3436
+ }
3437
+ /**
3438
+ * This function gives the glyphs of a text and their advances in font units. With the shaping `dom`, it applies the GSUB
3439
+ * substitutions to each word of `shapingWords`, and then the pair kerning to all the text. With the shaping `canvas`, it
3440
+ * applies the substitutions and the kerning to each word of `canvasWords` alone. A script of `joiningScripts` also gets
3441
+ * the joining forms of each word, in the stages of `joiningStages`. A script of `indicScripts` uses `indicShaper`.
3442
+ */
3443
+ function glyphAdvances(font, text, options = {}) {
3444
+ const { substitute, kerning } = shaperOf(font, options);
3445
+ const canvas = options.shaping === "canvas";
3446
+ const glyphs = [];
3447
+ const advances = [];
3448
+ for (const word of canvas ? canvasWords(text) : shapingWords(text)) {
3449
+ const wordGlyphs = substitute(word);
3450
+ const wordAdvances = wordGlyphs.map((glyph) => font.advance(glyph));
3451
+ if (canvas) kern(font, kerning, wordGlyphs, wordAdvances);
3452
+ glyphs.push(...wordGlyphs);
3453
+ advances.push(...wordAdvances);
3454
+ }
3455
+ if (!canvas) kern(font, kerning, glyphs, advances);
3456
+ return {
3457
+ glyphs,
3458
+ advances
3459
+ };
3460
+ }
3461
+ /** This function rounds a value to the nearest whole number, and a half goes away from 0, as `roundf` does. */
3462
+ function roundHalfAway(value) {
3463
+ return Math.sign(value) * Math.floor(Math.abs(value) + .5);
3464
+ }
3465
+ /**
3466
+ * This function gives a kerning value in 1/65536 px, as HarfBuzz scales it. HarfBuzz scales the value of the ValueRecord
3467
+ * with `em_mult`, and the delta of the VariationIndex table in single precision with `em_scalef`.
3468
+ */
3469
+ function kerningPosition(value, delta, scale) {
3470
+ const base = Math.round(value - delta);
3471
+ const scaledDelta = delta === 0 ? 0 : roundHalfAway(Math.fround(Math.fround(Math.fround(delta) * scale.xScale) / scale.unitsPerEm));
3472
+ return Math.floor((base * scale.multiplier + 32768) / 65536) + scaledDelta;
3473
+ }
3474
+ /**
3475
+ * This function adds the values of the pair kerning lookups `lookups` to the advances `advances` of the glyphs `glyphs`.
3476
+ * When `positions` is given, it also adds each value in 1/65536 px, as HarfBuzz scales it at the scale `scale`.
3477
+ */
3478
+ function kern(font, lookups, glyphs, advances, positions, scale) {
3479
+ for (const lookup of lookups) {
3480
+ const next = (from) => {
3481
+ for (let j = from; j < glyphs.length; j++) if (!(lookup.skip?.(glyphs[j]) ?? skips(lookup.flags, font.glyphClass(glyphs[j])))) return j;
3482
+ return -1;
3483
+ };
3484
+ let i = next(0);
3485
+ while (i >= 0) {
3486
+ const j = next(i + 1);
3487
+ if (j < 0) break;
3488
+ const value = lookup.pair(glyphs[i], glyphs[j]);
3489
+ if (value === void 0) {
3490
+ i = j;
3491
+ continue;
3492
+ }
3493
+ advances[i] += value.first;
3494
+ advances[j] += value.second;
3495
+ if (positions !== void 0 && scale !== void 0) {
3496
+ positions[i] += kerningPosition(value.first, value.firstDelta ?? 0, scale);
3497
+ positions[j] += kerningPosition(value.second, value.secondDelta ?? 0, scale);
3498
+ }
3499
+ i = value.skipSecond ? next(j + 1) : j;
3500
+ }
3501
+ }
3502
+ }
3503
+ /**
3504
+ * This function gives the width of a text with the sum `float32`, as Blink adds the widths of a canvas. Each advance in
3505
+ * single precision is cut to 1/65536 px, and HarfBuzz scales each kerning value. The sum of a segment of `canvasSegments`
3506
+ * is exact, and the sum of the segments is in single precision.
3507
+ */
3508
+ function float32Width(font, text, size, options) {
3509
+ const { substitute, kerning } = shaperOf(font, options);
3510
+ const xScale = Math.trunc(Math.fround(size) * 65536);
3511
+ const scale = {
3512
+ xScale,
3513
+ multiplier: Math.floor(xScale * 65536 / font.unitsPerEm),
3514
+ unitsPerEm: font.unitsPerEm
3515
+ };
3516
+ const pxPerUnit = size / font.unitsPerEm;
3517
+ let total = 0;
3518
+ for (const segment of canvasSegments(text)) {
3519
+ const glyphs = substitute(segment);
3520
+ const positions = glyphs.map((glyph) => Math.trunc(Math.fround(font.advance(glyph) * pxPerUnit) * 65536));
3521
+ kern(font, kerning, glyphs, glyphs.map((glyph) => font.advance(glyph)), positions, scale);
3522
+ let units = 0;
3523
+ for (const position of positions) units += position;
3524
+ total = Math.fround(total + Math.fround(units / 65536));
3525
+ }
3526
+ return total;
3527
+ }
3528
+ /**
3529
+ * This function gives the width of a text in px at the size `size` in px. The width is the sum of the advances after the
3530
+ * GSUB substitutions and the pair kerning.
3531
+ */
3532
+ function measureWidth(font, text, size, options = {}) {
3533
+ if (options.sum === "float32" && options.rounding !== "pixel") return float32Width(font, text, size, options);
3534
+ const { glyphs, advances } = glyphAdvances(font, text, options);
3535
+ if (options.rounding === "pixel") {
3536
+ const scale = size / font.unitsPerEm;
3537
+ let px = 0;
3538
+ glyphs.forEach((glyph, i) => {
3539
+ const base = font.advance(glyph);
3540
+ px += Math.round(Math.round(base * scale * 64) / 64) + Math.round((advances[i] - base) * scale);
3541
+ });
3542
+ return px;
3543
+ }
3544
+ let units = 0;
3545
+ for (const advance of advances) units += advance;
3546
+ return units * size / font.unitsPerEm;
3547
+ }
3548
+ export { canvasWords as a, shapingWords as i, measureWidth as n, defaultFeatures as o, readFont as r, readTables as s, glyphAdvances as t };
3549
+
3550
+ //# sourceMappingURL=font.js.map