reamkit 1.30.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +63 -0
  6. package/dist/esm/core/font/index.d.ts +2 -0
  7. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  8. package/dist/esm/core/font/ttf-build.js +1224 -0
  9. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  10. package/dist/esm/core/font/ttf-subset.js +14 -1
  11. package/dist/esm/core/fonts/provider.d.ts +17 -0
  12. package/dist/esm/core/fonts/provider.js +27 -2
  13. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  14. package/dist/esm/core/ir/flow.d.ts +71 -1
  15. package/dist/esm/core/numbering/index.d.ts +1 -1
  16. package/dist/esm/core/numbering/state.d.ts +11 -1
  17. package/dist/esm/core/numbering/state.js +10 -1
  18. package/dist/esm/core/style-cascade/resolver.js +35 -4
  19. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  20. package/dist/esm/core/style-cascade/types.js +3 -0
  21. package/dist/esm/index.d.ts +2 -2
  22. package/dist/esm/layout/page-doc.d.ts +10 -3
  23. package/dist/esm/layout/styled-layout.d.ts +48 -7
  24. package/dist/esm/layout/styled-layout.js +881 -119
  25. package/dist/esm/layout/turned-section.d.ts +38 -0
  26. package/dist/esm/layout/turned-section.js +193 -0
  27. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  28. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  29. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  30. package/dist/esm/pdf-reader/content.d.ts +53 -0
  31. package/dist/esm/pdf-reader/content.js +9 -1
  32. package/dist/esm/pdf-reader/display.d.ts +36 -0
  33. package/dist/esm/pdf-reader/display.js +66 -1
  34. package/dist/esm/pdf-reader/document.js +5 -1
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  36. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  37. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  38. package/dist/esm/pdf-reader/encodings.js +25 -3
  39. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  40. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  41. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  42. package/dist/esm/pdf-reader/figures.js +433 -0
  43. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  44. package/dist/esm/pdf-reader/flow-build.js +345 -26
  45. package/dist/esm/pdf-reader/font.js +321 -36
  46. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  47. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  48. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  49. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  50. package/dist/esm/pdf-reader/layout.js +1703 -214
  51. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  52. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  53. package/dist/esm/pdf-reader/tagged.js +182 -23
  54. package/dist/esm/pdf-reader/text.d.ts +6 -3
  55. package/dist/esm/pdf-reader/text.js +98 -9
  56. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  57. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  58. package/dist/esm/pdf-reader/vector.js +71 -1
  59. package/dist/esm/word/doc/doc-reader.js +6 -2
  60. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  61. package/dist/esm/word/doc/doc-text.js +19 -1
  62. package/dist/esm/word/document-parser.d.ts +2 -2
  63. package/dist/esm/word/document-parser.js +11 -1
  64. package/dist/esm/word/docx-reader.js +5 -3
  65. package/dist/esm/word/docx-writer.js +207 -21
  66. package/dist/esm/word/drawing-parser.d.ts +5 -3
  67. package/dist/esm/word/drawing-parser.js +49 -8
  68. package/dist/esm/word/font-embed.d.ts +30 -0
  69. package/dist/esm/word/font-embed.js +173 -0
  70. package/dist/esm/word/font-table.d.ts +10 -0
  71. package/dist/esm/word/font-table.js +13 -1
  72. package/dist/esm/word/index.js +1 -1
  73. package/dist/esm/word/numbering-parser.d.ts +3 -1
  74. package/dist/esm/word/numbering-parser.js +2 -1
  75. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  76. package/dist/esm/word/paragraph-properties.js +14 -2
  77. package/dist/esm/word/run-properties.js +26 -0
  78. package/package.json +8 -3
@@ -0,0 +1,1224 @@
1
+ import { directoryGeometry, paddedChecksum } from "./ttf-subset.js";
2
+ //#region src/core/font/ttf-build.ts
3
+ /**
4
+ * The most pairs a `kern` subtable holds: its length is a 16-bit count of bytes,
5
+ * six to a pair after a fourteen-byte header.
6
+ */
7
+ var MAX_KERN_PAIRS = Math.floor(65521 / 6);
8
+ /** OS/2 `fsType` bits (ISO/IEC 14496-22, OS/2 table). */
9
+ var USAGE_BITS = 14;
10
+ var EDITABLE_EMBEDDING = 8;
11
+ var NO_SUBSETTING = 256;
12
+ var BITMAP_ONLY = 512;
13
+ /**
14
+ * Whether a face's licence lets it be embedded, as outlines and subset, in a
15
+ * document that stays EDITABLE: OS/2 `fsType` states installable embedding (no
16
+ * usage bit) or grants editable embedding, and neither forbids subsetting nor
17
+ * allows bitmaps only. Restricted-licence and preview-and-print faces are
18
+ * refused — the second may travel only in a document opened read-only.
19
+ *
20
+ * Several usage bits at once is an old font's way of stating permissions, and
21
+ * the least restrictive of them applies (OS/2 versions 0–2).
22
+ *
23
+ * @param fsType The field, or `undefined` where the program states none —
24
+ * which restricts nothing.
25
+ * @returns Whether an editable document may carry the face.
26
+ */
27
+ function editableEmbedding(fsType) {
28
+ if (fsType === void 0) return true;
29
+ if ((fsType & (NO_SUBSETTING | BITMAP_ONLY)) !== 0) return false;
30
+ const usage = fsType & USAGE_BITS;
31
+ return usage === 0 || (usage & EDITABLE_EMBEDDING) !== 0;
32
+ }
33
+ /** Units per em. */
34
+ var UPEM = 1e3;
35
+ /**
36
+ * How far, in font units, a quadratic may stray from the cubic it replaces.
37
+ * Half a unit is the rounding every coordinate takes anyway.
38
+ */
39
+ var CURVE_TOLERANCE = .5;
40
+ /** No cubic is cut into more quadratics than this. */
41
+ var MAX_CURVE_PIECES = 16;
42
+ /**
43
+ * The most glyphs a built face holds. A `cmap` format 4 subtable is limited to
44
+ * 64 KB, and past this many characters it may not fit; a face that large is a
45
+ * whole CJK font, which is no subset.
46
+ */
47
+ var MAX_BUILT_GLYPHS = 8e3;
48
+ /**
49
+ * Build a TrueType font from outlines (ISO/IEC 14496-22): `.notdef` first, then
50
+ * one glyph per {@link BuiltGlyph} in the order given, reachable through a
51
+ * Unicode `cmap`.
52
+ *
53
+ * Cubic curves become quadratic ones. A cubic that is a raised quadratic — what
54
+ * a TrueType source's outline arrives as — comes back as exactly that
55
+ * quadratic; any other is cut into as many quadratics as keep within half a
56
+ * unit of it. Each glyph's contours are turned to run clockwise round their
57
+ * ink, as TrueType's are.
58
+ *
59
+ * @param face The face: its names, metrics, licence and glyphs.
60
+ * @returns The font program's bytes.
61
+ * @throws When the face holds more than {@link MAX_BUILT_GLYPHS} glyphs.
62
+ */
63
+ function buildTrueType(face) {
64
+ const ligatures = reachableLigatures(face);
65
+ if (face.glyphs.length + ligatures.length > 8e3) throw new Error(`A built face holds at most ${MAX_BUILT_GLYPHS} glyphs`);
66
+ const drawn = [...face.glyphs, ...ligatures];
67
+ const encoded = [emptyGlyph(), ...drawn.map((g) => encodeGlyph(contoursOf(g.outline)))];
68
+ const advances = [0, ...drawn.map((g) => clamp(Math.round(g.advance), 0, 65535))];
69
+ const inked = encoded.filter((g) => g.contours > 0);
70
+ const bounds = {
71
+ xMin: Math.min(0, ...inked.map((g) => g.xMin)),
72
+ yMin: Math.min(0, ...inked.map((g) => g.yMin)),
73
+ xMax: Math.max(0, ...inked.map((g) => g.xMax)),
74
+ yMax: Math.max(0, ...inked.map((g) => g.yMax))
75
+ };
76
+ const ascent = clamp(Math.round(face.ascent), 1, 32767);
77
+ const descent = clamp(Math.round(face.descent), -32767, 0);
78
+ const metrics = {
79
+ ascent,
80
+ descent,
81
+ winAscent: Math.max(ascent, bounds.yMax),
82
+ winDescent: Math.max(-descent, -bounds.yMin),
83
+ bounds
84
+ };
85
+ const cmap = cmapTable(face.glyphs);
86
+ return assembleSfnt([
87
+ ["OS/2", os2Table(face, encoded, advances, metrics)],
88
+ ["cmap", cmap],
89
+ ["glyf", concat(encoded.map((g) => g.bytes))],
90
+ ["head", headTable(face, bounds)],
91
+ ["hhea", hheaTable(face, encoded, advances, metrics)],
92
+ ["hmtx", hmtxTable(encoded, advances)],
93
+ ...kerningTables(face),
94
+ ...ligatureTable(face, ligatures),
95
+ ["loca", locaTable(encoded)],
96
+ ["maxp", maxpTable(encoded)],
97
+ ["name", nameTable(face)],
98
+ ["post", postTable(face)]
99
+ ]);
100
+ }
101
+ /**
102
+ * The outline as TrueType contours, in font units and rounded: each closed,
103
+ * quadratic, and running clockwise round its ink.
104
+ */
105
+ function contoursOf(outline) {
106
+ const out = [];
107
+ let current = [];
108
+ let x = 0;
109
+ let y = 0;
110
+ let startX = 0;
111
+ let startY = 0;
112
+ const finish = () => {
113
+ const contour = tidy(current);
114
+ if (contour.length >= 3) out.push(contour);
115
+ current = [];
116
+ };
117
+ for (const seg of outline) if (seg.op === "move") {
118
+ finish();
119
+ x = seg.x * UPEM;
120
+ y = seg.y * UPEM;
121
+ startX = x;
122
+ startY = y;
123
+ current.push({
124
+ x,
125
+ y,
126
+ on: true
127
+ });
128
+ } else if (seg.op === "line") {
129
+ if (current.length === 0) current.push({
130
+ x,
131
+ y,
132
+ on: true
133
+ });
134
+ x = seg.x * UPEM;
135
+ y = seg.y * UPEM;
136
+ current.push({
137
+ x,
138
+ y,
139
+ on: true
140
+ });
141
+ } else if (seg.op === "cubic") {
142
+ if (current.length === 0) current.push({
143
+ x,
144
+ y,
145
+ on: true
146
+ });
147
+ const to = {
148
+ x: seg.x * UPEM,
149
+ y: seg.y * UPEM
150
+ };
151
+ quadraticsOf({
152
+ x,
153
+ y
154
+ }, {
155
+ x: seg.x1 * UPEM,
156
+ y: seg.y1 * UPEM
157
+ }, {
158
+ x: seg.x2 * UPEM,
159
+ y: seg.y2 * UPEM
160
+ }, to, current);
161
+ x = to.x;
162
+ y = to.y;
163
+ } else {
164
+ finish();
165
+ x = startX;
166
+ y = startY;
167
+ }
168
+ finish();
169
+ let biggest = 0;
170
+ for (const contour of out) {
171
+ const a = area(contour);
172
+ if (Math.abs(a) > Math.abs(biggest)) biggest = a;
173
+ }
174
+ if (biggest > 0) for (const contour of out) contour.reverse();
175
+ return out;
176
+ }
177
+ /**
178
+ * One cubic as quadratics, appended to `out` (its start is already there).
179
+ *
180
+ * A cubic raised from a quadratic has both its controls two thirds of the way
181
+ * to the same point, and that point is the quadratic's control. Any other
182
+ * cubic is cut evenly into pieces, each replaced by the quadratic through its
183
+ * ends whose control is the average of the two its controls imply — which
184
+ * strays from the piece by at most √3/36 of |P3 − 3C2 + 3C1 − P0|, a length
185
+ * that shrinks with the cube of the piece.
186
+ */
187
+ function quadraticsOf(p0, c1, c2, p3, out) {
188
+ const q1 = {
189
+ x: (3 * c1.x - p0.x) / 2,
190
+ y: (3 * c1.y - p0.y) / 2
191
+ };
192
+ const q2 = {
193
+ x: (3 * c2.x - p3.x) / 2,
194
+ y: (3 * c2.y - p3.y) / 2
195
+ };
196
+ if (Math.hypot(q1.x - q2.x, q1.y - q2.y) <= RAISED_QUADRATIC) {
197
+ out.push({
198
+ x: (q1.x + q2.x) / 2,
199
+ y: (q1.y + q2.y) / 2,
200
+ on: false
201
+ });
202
+ out.push({
203
+ x: p3.x,
204
+ y: p3.y,
205
+ on: true
206
+ });
207
+ return;
208
+ }
209
+ const dx = p3.x - 3 * c2.x + 3 * c1.x - p0.x;
210
+ const dy = p3.y - 3 * c2.y + 3 * c1.y - p0.y;
211
+ const stray = Math.hypot(dx, dy) * Math.sqrt(3) / 36;
212
+ const pieces = clamp(Math.ceil(Math.cbrt(stray / CURVE_TOLERANCE)), 1, MAX_CURVE_PIECES);
213
+ const at = (t) => {
214
+ const u = 1 - t;
215
+ return {
216
+ x: u * u * u * p0.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * p3.x,
217
+ y: u * u * u * p0.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * p3.y
218
+ };
219
+ };
220
+ const lean = (t) => {
221
+ const u = 1 - t;
222
+ const k = 1 / pieces;
223
+ return {
224
+ x: k * (u * u * (c1.x - p0.x) + 2 * u * t * (c2.x - c1.x) + t * t * (p3.x - c2.x)),
225
+ y: k * (u * u * (c1.y - p0.y) + 2 * u * t * (c2.y - c1.y) + t * t * (p3.y - c2.y))
226
+ };
227
+ };
228
+ for (let i = 0; i < pieces; i++) {
229
+ const t0 = i / pieces;
230
+ const t1 = (i + 1) / pieces;
231
+ const a = at(t0);
232
+ const b = i + 1 === pieces ? p3 : at(t1);
233
+ const la = lean(t0);
234
+ const lb = lean(t1);
235
+ const s1 = {
236
+ x: a.x + la.x,
237
+ y: a.y + la.y
238
+ };
239
+ const s2 = {
240
+ x: b.x - lb.x,
241
+ y: b.y - lb.y
242
+ };
243
+ out.push({
244
+ x: (3 * (s1.x + s2.x) - (a.x + b.x)) / 4,
245
+ y: (3 * (s1.y + s2.y) - (a.y + b.y)) / 4,
246
+ on: false
247
+ });
248
+ out.push({
249
+ x: b.x,
250
+ y: b.y,
251
+ on: true
252
+ });
253
+ }
254
+ }
255
+ /** How near, in font units, a cubic's two implied controls must be to be one. */
256
+ var RAISED_QUADRATIC = .01;
257
+ /**
258
+ * A contour rounded to whole units, with the points rounding merges into one
259
+ * and the closing point that repeats the first taken out.
260
+ */
261
+ function tidy(points) {
262
+ const out = [];
263
+ for (const p of points) {
264
+ const q = {
265
+ x: clamp(Math.round(p.x), -32768, 32767),
266
+ y: clamp(Math.round(p.y), -32768, 32767),
267
+ on: p.on
268
+ };
269
+ const last = out[out.length - 1];
270
+ if (last && last.x === q.x && last.y === q.y) last.on = last.on || q.on;
271
+ else out.push(q);
272
+ }
273
+ while (out.length > 1) {
274
+ const first = out[0];
275
+ const last = out[out.length - 1];
276
+ if (first.x !== last.x || first.y !== last.y) break;
277
+ first.on = first.on || last.on;
278
+ out.pop();
279
+ }
280
+ return out;
281
+ }
282
+ /** Twice the signed area a contour's points enclose — positive counter-clockwise. */
283
+ function area(points) {
284
+ let sum = 0;
285
+ for (let i = 0; i < points.length; i++) {
286
+ const a = points[i];
287
+ const b = points[(i + 1) % points.length];
288
+ sum += a.x * b.y - b.x * a.y;
289
+ }
290
+ return sum;
291
+ }
292
+ function emptyGlyph() {
293
+ return {
294
+ bytes: new Uint8Array(0),
295
+ contours: 0,
296
+ points: 0,
297
+ xMin: 0,
298
+ yMin: 0,
299
+ xMax: 0,
300
+ yMax: 0
301
+ };
302
+ }
303
+ var ON_CURVE = 1;
304
+ var X_SHORT = 2;
305
+ var Y_SHORT = 4;
306
+ var REPEAT = 8;
307
+ var X_SAME_OR_POSITIVE = 16;
308
+ var Y_SAME_OR_POSITIVE = 32;
309
+ /**
310
+ * A simple glyph as `glyf` stores it: the contours' last points, no
311
+ * instructions, then flags and coordinate deltas packed as small as they go.
312
+ */
313
+ function encodeGlyph(contours) {
314
+ const points = contours.flat();
315
+ if (points.length === 0) return emptyGlyph();
316
+ const xs = points.map((p) => p.x);
317
+ const ys = points.map((p) => p.y);
318
+ const box = {
319
+ xMin: Math.min(...xs),
320
+ yMin: Math.min(...ys),
321
+ xMax: Math.max(...xs),
322
+ yMax: Math.max(...ys)
323
+ };
324
+ const flags = [];
325
+ const xBytes = [];
326
+ const yBytes = [];
327
+ let px = 0;
328
+ let py = 0;
329
+ const delta = (d, short, same, bytes) => {
330
+ if (d === 0) return same;
331
+ if (Math.abs(d) <= 255) {
332
+ bytes.push(Math.abs(d));
333
+ return short | (d > 0 ? same : 0);
334
+ }
335
+ bytes.push(d >> 8 & 255, d & 255);
336
+ return 0;
337
+ };
338
+ for (const p of points) {
339
+ let flag = p.on ? ON_CURVE : 0;
340
+ flag |= delta(p.x - px, X_SHORT, X_SAME_OR_POSITIVE, xBytes);
341
+ flag |= delta(p.y - py, Y_SHORT, Y_SAME_OR_POSITIVE, yBytes);
342
+ flags.push(flag);
343
+ px = p.x;
344
+ py = p.y;
345
+ }
346
+ const packed = [];
347
+ for (let i = 0; i < flags.length;) {
348
+ let run = 1;
349
+ while (i + run < flags.length && flags[i + run] === flags[i] && run < 256) run++;
350
+ if (run > 1) packed.push(flags[i] | REPEAT, run - 1);
351
+ else packed.push(flags[i]);
352
+ i += run;
353
+ }
354
+ const size = 10 + contours.length * 2 + 2 + packed.length + xBytes.length + yBytes.length;
355
+ const out = new Writer(size + (4 - size % 4) % 4);
356
+ out.i16(contours.length);
357
+ out.i16(box.xMin);
358
+ out.i16(box.yMin);
359
+ out.i16(box.xMax);
360
+ out.i16(box.yMax);
361
+ let end = -1;
362
+ for (const contour of contours) {
363
+ end += contour.length;
364
+ out.u16(end);
365
+ }
366
+ out.u16(0);
367
+ out.bytes(packed);
368
+ out.bytes(xBytes);
369
+ out.bytes(yBytes);
370
+ return {
371
+ bytes: out.data,
372
+ contours: contours.length,
373
+ points: points.length,
374
+ ...box
375
+ };
376
+ }
377
+ /**
378
+ * `cmap`: the BMP through format 4, under both Unicode encodings a reader looks
379
+ * for, and — where the face reaches past it — every character through format 12.
380
+ */
381
+ function cmapTable(glyphs) {
382
+ const pairs = [];
383
+ glyphs.forEach((g, i) => {
384
+ for (const cp of g.codePoints) pairs.push([cp, i + 1]);
385
+ });
386
+ pairs.sort((a, b) => a[0] - b[0]);
387
+ const unique = pairs.filter((p, i) => i === 0 || p[0] !== pairs[i - 1][0]);
388
+ const format4 = cmapFormat4(unique.filter(([cp]) => cp <= 65535 && (cp < 55296 || cp > 57343)));
389
+ const wide = unique.some(([cp]) => cp > 65535) ? cmapFormat12(unique) : void 0;
390
+ const records = [
391
+ [
392
+ 0,
393
+ 3,
394
+ "bmp"
395
+ ],
396
+ ...wide ? [[
397
+ 0,
398
+ 4,
399
+ "wide"
400
+ ]] : [],
401
+ [
402
+ 3,
403
+ 1,
404
+ "bmp"
405
+ ],
406
+ ...wide ? [[
407
+ 3,
408
+ 10,
409
+ "wide"
410
+ ]] : []
411
+ ];
412
+ const headerSize = 4 + records.length * 8;
413
+ const out = new Writer(headerSize + format4.length + (wide?.length ?? 0));
414
+ out.u16(0);
415
+ out.u16(records.length);
416
+ for (const [platform, encoding, which] of records) {
417
+ out.u16(platform);
418
+ out.u16(encoding);
419
+ out.u32(which === "bmp" ? headerSize : headerSize + format4.length);
420
+ }
421
+ out.bytes(format4);
422
+ if (wide) out.bytes(wide);
423
+ return out.data;
424
+ }
425
+ /** Format 4: segments of consecutive characters on consecutive glyphs. */
426
+ function cmapFormat4(pairs) {
427
+ const segments = [];
428
+ for (const [cp, gid] of pairs) {
429
+ const last = segments[segments.length - 1];
430
+ if (last && cp === last.end + 1 && gid === last.gid + (cp - last.start)) last.end = cp;
431
+ else segments.push({
432
+ start: cp,
433
+ end: cp,
434
+ gid
435
+ });
436
+ }
437
+ if (segments[segments.length - 1]?.end !== 65535) segments.push({
438
+ start: 65535,
439
+ end: 65535,
440
+ gid: 0
441
+ });
442
+ const count = segments.length;
443
+ const entrySelector = Math.floor(Math.log2(count));
444
+ const searchRange = 2 * 2 ** entrySelector;
445
+ const length = 16 + count * 8;
446
+ const out = new Writer(length);
447
+ out.u16(4);
448
+ out.u16(length);
449
+ out.u16(0);
450
+ out.u16(count * 2);
451
+ out.u16(searchRange);
452
+ out.u16(entrySelector);
453
+ out.u16(count * 2 - searchRange);
454
+ for (const s of segments) out.u16(s.end);
455
+ out.u16(0);
456
+ for (const s of segments) out.u16(s.start);
457
+ for (const s of segments) out.u16(s.gid === 0 ? 1 : s.gid - s.start & 65535);
458
+ for (let i = 0; i < count; i++) out.u16(0);
459
+ return out.data;
460
+ }
461
+ /** Format 12: groups of consecutive characters on consecutive glyphs. */
462
+ function cmapFormat12(pairs) {
463
+ const groups = [];
464
+ for (const [cp, gid] of pairs) {
465
+ if (cp >= 55296 && cp <= 57343) continue;
466
+ const last = groups[groups.length - 1];
467
+ if (last && cp === last.end + 1 && gid === last.gid + (cp - last.start)) last.end = cp;
468
+ else groups.push({
469
+ start: cp,
470
+ end: cp,
471
+ gid
472
+ });
473
+ }
474
+ const length = 16 + groups.length * 12;
475
+ const out = new Writer(length);
476
+ out.u16(12);
477
+ out.u16(0);
478
+ out.u32(length);
479
+ out.u32(0);
480
+ out.u32(groups.length);
481
+ for (const g of groups) {
482
+ out.u32(g.start);
483
+ out.u32(g.end);
484
+ out.u32(g.gid);
485
+ }
486
+ return out.data;
487
+ }
488
+ /** `head` — version 1.0, a thousand units to the em, long `loca` offsets. */
489
+ function headTable(face, bounds) {
490
+ const out = new Writer(54);
491
+ out.u32(65536);
492
+ out.u32(65536);
493
+ out.u32(0);
494
+ out.u32(1594834165);
495
+ out.u16(11);
496
+ out.u16(UPEM);
497
+ out.u32(0);
498
+ out.u32(0);
499
+ out.u32(0);
500
+ out.u32(0);
501
+ out.i16(bounds.xMin);
502
+ out.i16(bounds.yMin);
503
+ out.i16(bounds.xMax);
504
+ out.i16(bounds.yMax);
505
+ out.u16((face.bold ? 1 : 0) | (face.italic ? 2 : 0));
506
+ out.u16(8);
507
+ out.i16(2);
508
+ out.i16(1);
509
+ out.i16(0);
510
+ return out.data;
511
+ }
512
+ /** `hhea` — the horizontal header, one metric per glyph. */
513
+ function hheaTable(face, glyphs, advances, metrics) {
514
+ const inked = glyphs.map((g, i) => ({
515
+ g,
516
+ advance: advances[i]
517
+ })).filter((e) => e.g.contours > 0);
518
+ const slope = slopeOf(face.italicAngle ?? 0);
519
+ const out = new Writer(36);
520
+ out.u32(65536);
521
+ out.i16(metrics.ascent);
522
+ out.i16(metrics.descent);
523
+ out.i16(0);
524
+ out.u16(Math.max(0, ...advances));
525
+ out.i16(Math.min(0, ...inked.map((e) => e.g.xMin)));
526
+ out.i16(Math.min(0, ...inked.map((e) => e.advance - e.g.xMax)));
527
+ out.i16(Math.max(0, ...inked.map((e) => e.g.xMax)));
528
+ out.i16(slope.rise);
529
+ out.i16(slope.run);
530
+ out.i16(0);
531
+ for (let i = 0; i < 4; i++) out.i16(0);
532
+ out.i16(0);
533
+ out.u16(glyphs.length);
534
+ return out.data;
535
+ }
536
+ /** The caret's slope for a face slanted by `angle` degrees. */
537
+ function slopeOf(angle) {
538
+ if (!Number.isFinite(angle) || angle === 0) return {
539
+ rise: 1,
540
+ run: 0
541
+ };
542
+ return {
543
+ rise: UPEM,
544
+ run: Math.round(-Math.tan(angle * Math.PI / 180) * UPEM)
545
+ };
546
+ }
547
+ function hmtxTable(glyphs, advances) {
548
+ const out = new Writer(glyphs.length * 4);
549
+ glyphs.forEach((g, i) => {
550
+ out.u16(advances[i]);
551
+ out.i16(g.contours > 0 ? g.xMin : 0);
552
+ });
553
+ return out.data;
554
+ }
555
+ function locaTable(glyphs) {
556
+ const out = new Writer((glyphs.length + 1) * 4);
557
+ let at = 0;
558
+ for (const g of glyphs) {
559
+ out.u32(at);
560
+ at += g.bytes.length;
561
+ }
562
+ out.u32(at);
563
+ return out.data;
564
+ }
565
+ /** Each character's glyph — the first that carries it — by code point. */
566
+ function glyphsByCharacter(face) {
567
+ const glyphOf = /* @__PURE__ */ new Map();
568
+ face.glyphs.forEach((g, i) => {
569
+ for (const cp of g.codePoints) if (!glyphOf.has(cp)) glyphOf.set(cp, i + 1);
570
+ });
571
+ return glyphOf;
572
+ }
573
+ /**
574
+ * The ligatures a substitution can reach: two characters or more, every one of
575
+ * them a glyph of the face — a ligature of a letter the face does not carry
576
+ * joins nothing a reader lays out in it — and each run of characters once.
577
+ */
578
+ function reachableLigatures(face) {
579
+ const glyphOf = glyphsByCharacter(face);
580
+ const seen = /* @__PURE__ */ new Set();
581
+ return (face.ligatures ?? []).filter((ligature) => {
582
+ const key = ligature.codePoints.join(",");
583
+ if (ligature.codePoints.length < 2 || seen.has(key)) return false;
584
+ seen.add(key);
585
+ return ligature.codePoints.every((cp) => glyphOf.has(cp));
586
+ });
587
+ }
588
+ /**
589
+ * GSUB 1.0 with one feature, `liga`, under the default script and Latin: one
590
+ * ligature-substitution lookup (type 4, format 1) joining each run of
591
+ * character glyphs into the glyph the page drew for it — the longest run
592
+ * first, so "ffi" is tried before "ff".
593
+ *
594
+ * @param face The face, for its character glyphs.
595
+ * @param ligatures Its reachable ligatures, glyph `face.glyphs.length + 1` on.
596
+ * @returns The table to add, or none where the face draws no ligature.
597
+ */
598
+ function ligatureTable(face, ligatures) {
599
+ if (ligatures.length === 0) return [];
600
+ const glyphOf = glyphsByCharacter(face);
601
+ const byFirst = /* @__PURE__ */ new Map();
602
+ ligatures.forEach((ligature, i) => {
603
+ const [first, ...rest] = ligature.codePoints.map((cp) => glyphOf.get(cp));
604
+ const entry = {
605
+ glyph: face.glyphs.length + 1 + i,
606
+ rest
607
+ };
608
+ byFirst.set(first, [...byFirst.get(first) ?? [], entry]);
609
+ });
610
+ const firsts = [...byFirst.keys()].sort((a, b) => a - b);
611
+ const sets = firsts.map((first) => [...byFirst.get(first)].sort((a, b) => b.rest.length - a.rest.length || a.rest.join(",").localeCompare(b.rest.join(","))));
612
+ const substHeader = 6 + firsts.length * 2;
613
+ const coverageSize = 4 + firsts.length * 2;
614
+ const setSize = (set) => 2 + set.length * 2 + set.reduce((n, lig) => n + 4 + lig.rest.length * 2, 0);
615
+ const SUBST_AT = 62;
616
+ const out = new Writer(SUBST_AT + substHeader + coverageSize + sets.reduce((n, set) => n + setSize(set), 0));
617
+ out.u16(1);
618
+ out.u16(0);
619
+ out.u16(10);
620
+ out.u16(36);
621
+ out.u16(50);
622
+ out.u16(2);
623
+ for (const tag of ["DFLT", "latn"]) {
624
+ out.bytes([...tag].map((c) => c.charCodeAt(0)));
625
+ out.u16(14);
626
+ }
627
+ out.u16(4);
628
+ out.u16(0);
629
+ out.u16(0);
630
+ out.u16(65535);
631
+ out.u16(1);
632
+ out.u16(0);
633
+ out.u16(1);
634
+ out.bytes([..."liga"].map((c) => c.charCodeAt(0)));
635
+ out.u16(8);
636
+ out.u16(0);
637
+ out.u16(1);
638
+ out.u16(0);
639
+ out.u16(1);
640
+ out.u16(4);
641
+ out.u16(4);
642
+ out.u16(0);
643
+ out.u16(1);
644
+ out.u16(SUBST_AT - 54);
645
+ out.u16(1);
646
+ out.u16(substHeader);
647
+ out.u16(firsts.length);
648
+ let at = substHeader + coverageSize;
649
+ for (const set of sets) {
650
+ out.u16(at);
651
+ at += setSize(set);
652
+ }
653
+ out.u16(1);
654
+ out.u16(firsts.length);
655
+ for (const first of firsts) out.u16(first);
656
+ for (const set of sets) {
657
+ out.u16(set.length);
658
+ let lig = 2 + set.length * 2;
659
+ for (const entry of set) {
660
+ out.u16(lig);
661
+ lig += 4 + entry.rest.length * 2;
662
+ }
663
+ for (const entry of set) {
664
+ out.u16(entry.glyph);
665
+ out.u16(entry.rest.length + 1);
666
+ for (const g of entry.rest) out.u16(g);
667
+ }
668
+ }
669
+ return [["GSUB", out.data]];
670
+ }
671
+ /**
672
+ * The face's kerning pairs by glyph — the first glyph of each character — in
673
+ * the order a binary search expects them, less those of glyphs it does not
674
+ * hold and those that move nothing.
675
+ */
676
+ function glyphPairs(face) {
677
+ const glyphOf = glyphsByCharacter(face);
678
+ const byGlyphs = /* @__PURE__ */ new Map();
679
+ for (const pair of face.kerning ?? []) {
680
+ const left = glyphOf.get(pair.left);
681
+ const right = glyphOf.get(pair.right);
682
+ const value = Math.round(pair.value);
683
+ if (left === void 0 || right === void 0 || value === 0) continue;
684
+ const key = left * 65536 + right;
685
+ if (!byGlyphs.has(key)) byGlyphs.set(key, [
686
+ left,
687
+ right,
688
+ clamp(value, -32768, 32767)
689
+ ]);
690
+ }
691
+ return [...byGlyphs.entries()].sort((a, b) => a[0] - b[0]).slice(0, MAX_KERN_PAIRS).map(([, pair]) => pair);
692
+ }
693
+ /**
694
+ * The face's kerning, twice over: GPOS's `kern` feature, which is what a
695
+ * shaper reads — Word's own, DirectWrite, HarfBuzz — and the legacy `kern`
696
+ * table, for what reads nothing else. Word kerned the page's pairs from the
697
+ * legacy table alone not at all: both are written, and a shaper that finds
698
+ * GPOS leaves the other be.
699
+ *
700
+ * @returns The tables to add, or none where the face is kerned by no pair
701
+ * whose glyphs it holds.
702
+ */
703
+ function kerningTables(face) {
704
+ const pairs = glyphPairs(face);
705
+ if (pairs.length === 0) return [];
706
+ return [["GPOS", gposTable(pairs)], ["kern", kernTable(pairs)]];
707
+ }
708
+ /** `kern` version 0 with one horizontal format 0 subtable. */
709
+ function kernTable(pairs) {
710
+ const entrySelector = Math.floor(Math.log2(pairs.length));
711
+ const searchRange = 2 ** entrySelector * 6;
712
+ const length = 14 + pairs.length * 6;
713
+ const out = new Writer(4 + length);
714
+ out.u16(0);
715
+ out.u16(1);
716
+ out.u16(0);
717
+ out.u16(length);
718
+ out.u16(1);
719
+ out.u16(pairs.length);
720
+ out.u16(searchRange);
721
+ out.u16(entrySelector);
722
+ out.u16(pairs.length * 6 - searchRange);
723
+ for (const [left, right, value] of pairs) {
724
+ out.u16(left);
725
+ out.u16(right);
726
+ out.i16(value);
727
+ }
728
+ return out.data;
729
+ }
730
+ /**
731
+ * GPOS 1.0 with one feature, `kern`, under the default script and Latin: one
732
+ * pair-adjustment lookup (type 2, format 1) moving the first glyph's advance.
733
+ */
734
+ function gposTable(pairs) {
735
+ const byLeft = /* @__PURE__ */ new Map();
736
+ for (const [left, right, value] of pairs) byLeft.set(left, [...byLeft.get(left) ?? [], [right, value]]);
737
+ const lefts = [...byLeft.keys()].sort((a, b) => a - b);
738
+ const pairPosHeader = 10 + lefts.length * 2;
739
+ const coverageSize = 4 + lefts.length * 2;
740
+ const pairSetsSize = lefts.reduce((n, left) => n + 2 + byLeft.get(left).length * 4, 0);
741
+ const PAIR_POS_AT = 62;
742
+ const out = new Writer(PAIR_POS_AT + pairPosHeader + coverageSize + pairSetsSize);
743
+ out.u16(1);
744
+ out.u16(0);
745
+ out.u16(10);
746
+ out.u16(36);
747
+ out.u16(50);
748
+ out.u16(2);
749
+ for (const tag of ["DFLT", "latn"]) {
750
+ out.bytes([...tag].map((c) => c.charCodeAt(0)));
751
+ out.u16(14);
752
+ }
753
+ out.u16(4);
754
+ out.u16(0);
755
+ out.u16(0);
756
+ out.u16(65535);
757
+ out.u16(1);
758
+ out.u16(0);
759
+ out.u16(1);
760
+ out.bytes([..."kern"].map((c) => c.charCodeAt(0)));
761
+ out.u16(8);
762
+ out.u16(0);
763
+ out.u16(1);
764
+ out.u16(0);
765
+ out.u16(1);
766
+ out.u16(4);
767
+ out.u16(2);
768
+ out.u16(0);
769
+ out.u16(1);
770
+ out.u16(PAIR_POS_AT - 54);
771
+ out.u16(1);
772
+ out.u16(pairPosHeader);
773
+ out.u16(4);
774
+ out.u16(0);
775
+ out.u16(lefts.length);
776
+ let at = pairPosHeader + coverageSize;
777
+ for (const left of lefts) {
778
+ out.u16(at);
779
+ at += 2 + byLeft.get(left).length * 4;
780
+ }
781
+ out.u16(1);
782
+ out.u16(lefts.length);
783
+ for (const left of lefts) out.u16(left);
784
+ for (const left of lefts) {
785
+ const set = [...byLeft.get(left)].sort((a, b) => a[0] - b[0]);
786
+ out.u16(set.length);
787
+ for (const [right, value] of set) {
788
+ out.u16(right);
789
+ out.i16(value);
790
+ }
791
+ }
792
+ return out.data;
793
+ }
794
+ /** `maxp` version 1.0: the most points and contours any glyph holds, and no hinting. */
795
+ function maxpTable(glyphs) {
796
+ const out = new Writer(32);
797
+ out.u32(65536);
798
+ out.u16(glyphs.length);
799
+ out.u16(Math.max(0, ...glyphs.map((g) => g.points)));
800
+ out.u16(Math.max(0, ...glyphs.map((g) => g.contours)));
801
+ out.u16(0);
802
+ out.u16(0);
803
+ out.u16(2);
804
+ for (let i = 0; i < 8; i++) out.u16(0);
805
+ return out.data;
806
+ }
807
+ /** OS/2 version 4 — the metrics and the licence Windows reads. */
808
+ function os2Table(face, glyphs, advances, metrics) {
809
+ const codePoints = face.glyphs.flatMap((g) => g.codePoints);
810
+ const inked = advances.filter((a, i) => a > 0 && (glyphs[i]?.contours ?? 0) > 0);
811
+ const average = inked.length > 0 ? inked.reduce((s, a) => s + a, 0) / inked.length : UPEM / 2;
812
+ const bmp = codePoints.filter((cp) => cp <= 65535);
813
+ const out = new Writer(96);
814
+ out.u16(4);
815
+ out.i16(Math.round(average));
816
+ out.u16(face.bold ? 700 : 400);
817
+ out.u16(5);
818
+ out.u16(face.fsType & 65535);
819
+ for (const v of [
820
+ 650,
821
+ 600,
822
+ 0,
823
+ 75,
824
+ 650,
825
+ 600,
826
+ 0,
827
+ 350
828
+ ]) out.i16(v);
829
+ out.i16(50);
830
+ out.i16(Math.round((face.xHeight ?? 500) / 2));
831
+ out.i16(0);
832
+ out.bytes([
833
+ 0,
834
+ 0,
835
+ 0,
836
+ 0,
837
+ 0,
838
+ 0,
839
+ 0,
840
+ 0,
841
+ 0,
842
+ 0
843
+ ]);
844
+ const ranges = unicodeRanges(codePoints);
845
+ for (const r of ranges) out.u32(r);
846
+ out.bytes([
847
+ 32,
848
+ 32,
849
+ 32,
850
+ 32
851
+ ]);
852
+ out.u16((face.italic ? 1 : 0) | (face.bold ? 32 : 0) | (!face.bold && !face.italic ? 64 : 0));
853
+ out.u16(bmp.length > 0 ? Math.min(...bmp) : 32);
854
+ out.u16(bmp.length > 0 ? Math.max(...bmp) : 32);
855
+ out.i16(metrics.ascent);
856
+ out.i16(metrics.descent);
857
+ out.i16(0);
858
+ out.u16(clamp(metrics.winAscent, 0, 65535));
859
+ out.u16(clamp(metrics.winDescent, 0, 65535));
860
+ const pages = codePages(codePoints);
861
+ out.u32(pages[0]);
862
+ out.u32(pages[1]);
863
+ out.i16(Math.round(face.xHeight ?? 500));
864
+ out.i16(Math.round(face.capHeight ?? 700));
865
+ out.u16(0);
866
+ out.u16(32);
867
+ out.u16(0);
868
+ return out.data;
869
+ }
870
+ /** OS/2 `ulUnicodeRange` bits for the blocks a face's characters fall in. */
871
+ var UNICODE_RANGES = [
872
+ [
873
+ 0,
874
+ 0,
875
+ 127
876
+ ],
877
+ [
878
+ 1,
879
+ 128,
880
+ 255
881
+ ],
882
+ [
883
+ 2,
884
+ 256,
885
+ 383
886
+ ],
887
+ [
888
+ 3,
889
+ 384,
890
+ 591
891
+ ],
892
+ [
893
+ 4,
894
+ 592,
895
+ 687
896
+ ],
897
+ [
898
+ 5,
899
+ 688,
900
+ 767
901
+ ],
902
+ [
903
+ 6,
904
+ 768,
905
+ 879
906
+ ],
907
+ [
908
+ 7,
909
+ 880,
910
+ 1023
911
+ ],
912
+ [
913
+ 9,
914
+ 1024,
915
+ 1327
916
+ ],
917
+ [
918
+ 10,
919
+ 1328,
920
+ 1423
921
+ ],
922
+ [
923
+ 11,
924
+ 1424,
925
+ 1535
926
+ ],
927
+ [
928
+ 13,
929
+ 1536,
930
+ 1791
931
+ ],
932
+ [
933
+ 24,
934
+ 3584,
935
+ 3711
936
+ ],
937
+ [
938
+ 29,
939
+ 7680,
940
+ 7935
941
+ ],
942
+ [
943
+ 30,
944
+ 7936,
945
+ 8191
946
+ ],
947
+ [
948
+ 31,
949
+ 8192,
950
+ 8303
951
+ ],
952
+ [
953
+ 32,
954
+ 8304,
955
+ 8351
956
+ ],
957
+ [
958
+ 33,
959
+ 8352,
960
+ 8399
961
+ ],
962
+ [
963
+ 35,
964
+ 8448,
965
+ 8527
966
+ ],
967
+ [
968
+ 36,
969
+ 8528,
970
+ 8591
971
+ ],
972
+ [
973
+ 37,
974
+ 8592,
975
+ 8703
976
+ ],
977
+ [
978
+ 38,
979
+ 8704,
980
+ 8959
981
+ ],
982
+ [
983
+ 39,
984
+ 8960,
985
+ 9215
986
+ ],
987
+ [
988
+ 43,
989
+ 9472,
990
+ 9599
991
+ ],
992
+ [
993
+ 44,
994
+ 9600,
995
+ 9631
996
+ ],
997
+ [
998
+ 45,
999
+ 9632,
1000
+ 9727
1001
+ ],
1002
+ [
1003
+ 46,
1004
+ 9728,
1005
+ 9983
1006
+ ],
1007
+ [
1008
+ 47,
1009
+ 9984,
1010
+ 10175
1011
+ ],
1012
+ [
1013
+ 48,
1014
+ 12288,
1015
+ 12351
1016
+ ],
1017
+ [
1018
+ 49,
1019
+ 12352,
1020
+ 12447
1021
+ ],
1022
+ [
1023
+ 50,
1024
+ 12448,
1025
+ 12543
1026
+ ],
1027
+ [
1028
+ 56,
1029
+ 44032,
1030
+ 55215
1031
+ ],
1032
+ [
1033
+ 59,
1034
+ 19968,
1035
+ 40959
1036
+ ],
1037
+ [
1038
+ 60,
1039
+ 57344,
1040
+ 63743
1041
+ ],
1042
+ [
1043
+ 62,
1044
+ 64256,
1045
+ 64335
1046
+ ],
1047
+ [
1048
+ 68,
1049
+ 65280,
1050
+ 65519
1051
+ ]
1052
+ ];
1053
+ function unicodeRanges(codePoints) {
1054
+ const words = [
1055
+ 0,
1056
+ 0,
1057
+ 0,
1058
+ 0
1059
+ ];
1060
+ const set = (bit) => {
1061
+ words[bit >> 5] = (words[bit >> 5] | 1 << (bit & 31)) >>> 0;
1062
+ };
1063
+ for (const cp of codePoints) {
1064
+ if (cp > 65535) set(57);
1065
+ for (const [bit, from, to] of UNICODE_RANGES) if (cp >= from && cp <= to) set(bit);
1066
+ }
1067
+ return words;
1068
+ }
1069
+ /**
1070
+ * OS/2 `ulCodePageRange` — the Windows code pages a face's characters serve.
1071
+ * Latin 1 stands where nothing else does: a face that claims no code page at
1072
+ * all is one Windows may decline to use.
1073
+ */
1074
+ function codePages(codePoints) {
1075
+ let low = 0;
1076
+ const has = (from, to) => codePoints.some((cp) => cp >= from && cp <= to);
1077
+ if (has(32, 255)) low |= 1;
1078
+ if (has(256, 383)) low |= 146;
1079
+ if (has(1024, 1279)) low |= 4;
1080
+ if (has(880, 1023)) low |= 8;
1081
+ if (has(1424, 1535)) low |= 32;
1082
+ if (has(1536, 1791)) low |= 64;
1083
+ if (has(7840, 7929)) low |= 256;
1084
+ if (has(3584, 3711)) low |= 65536;
1085
+ if (has(12352, 12543)) low |= 1 << 17;
1086
+ if (has(19968, 40959)) low |= 1310720;
1087
+ if (has(44032, 55215)) low |= 1 << 19;
1088
+ if (low === 0) low = 1;
1089
+ return [low >>> 0, 0];
1090
+ }
1091
+ /** `name` — the family, style, full and PostScript names, in Windows' encoding. */
1092
+ function nameTable(face) {
1093
+ const style = face.bold && face.italic ? "Bold Italic" : face.bold ? "Bold" : face.italic ? "Italic" : "Regular";
1094
+ const family = face.family.trim() || "Embedded";
1095
+ const postScript = face.postScriptName.replace(/[^!-~]|[[\](){}<>/%]/gu, "").slice(0, 63) || family.replace(/[^A-Za-z0-9-]/gu, "");
1096
+ const full = style === "Regular" ? family : `${family} ${style}`;
1097
+ const records = [
1098
+ [1, family],
1099
+ [2, style],
1100
+ [3, `${postScript};${style}`],
1101
+ [4, full],
1102
+ [5, "Version 1.000"],
1103
+ [6, postScript]
1104
+ ];
1105
+ const strings = records.map(([, text]) => utf16be(text));
1106
+ const storage = 6 + records.length * 12;
1107
+ const out = new Writer(storage + strings.reduce((s, b) => s + b.length, 0));
1108
+ out.u16(0);
1109
+ out.u16(records.length);
1110
+ out.u16(storage);
1111
+ let offset = 0;
1112
+ records.forEach(([id], i) => {
1113
+ out.u16(3);
1114
+ out.u16(1);
1115
+ out.u16(1033);
1116
+ out.u16(id);
1117
+ out.u16(strings[i].length);
1118
+ out.u16(offset);
1119
+ offset += strings[i].length;
1120
+ });
1121
+ for (const s of strings) out.bytes(s);
1122
+ return out.data;
1123
+ }
1124
+ /** `post` version 3.0: no glyph names, only the slant and the pitch. */
1125
+ function postTable(face) {
1126
+ const out = new Writer(32);
1127
+ out.u32(196608);
1128
+ out.u32(Math.round((face.italicAngle ?? 0) * 65536) >>> 0);
1129
+ out.i16(-100);
1130
+ out.i16(50);
1131
+ out.u32(face.fixedPitch === true ? 1 : 0);
1132
+ for (let i = 0; i < 4; i++) out.u32(0);
1133
+ return out.data;
1134
+ }
1135
+ /**
1136
+ * An sfnt from its tables: the directory sorted by tag, each table on a
1137
+ * four-byte boundary, and `head.checkSumAdjustment` set so the whole file sums
1138
+ * to the magic number.
1139
+ */
1140
+ function assembleSfnt(tables) {
1141
+ const sorted = [...tables].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
1142
+ const directory = 12 + sorted.length * 16;
1143
+ const placed = [];
1144
+ let cursor = directory;
1145
+ for (const [tag, data] of sorted) {
1146
+ placed.push({
1147
+ tag,
1148
+ data,
1149
+ offset: cursor
1150
+ });
1151
+ cursor += data.length + (4 - data.length % 4) % 4;
1152
+ }
1153
+ const out = new Uint8Array(cursor);
1154
+ const view = new DataView(out.buffer);
1155
+ view.setUint32(0, 65536);
1156
+ view.setUint16(4, sorted.length);
1157
+ const geometry = directoryGeometry(sorted.length);
1158
+ view.setUint16(6, geometry.searchRange);
1159
+ view.setUint16(8, geometry.entrySelector);
1160
+ view.setUint16(10, geometry.rangeShift);
1161
+ placed.forEach((t, i) => {
1162
+ const at = 12 + i * 16;
1163
+ for (let k = 0; k < 4; k++) out[at + k] = t.tag.charCodeAt(k);
1164
+ view.setUint32(at + 4, paddedChecksum(t.data));
1165
+ view.setUint32(at + 8, t.offset);
1166
+ view.setUint32(at + 12, t.data.length);
1167
+ out.set(t.data, t.offset);
1168
+ });
1169
+ const head = placed.find((t) => t.tag === "head");
1170
+ if (head) view.setUint32(head.offset + 8, 2981146554 - paddedChecksum(out) >>> 0);
1171
+ return out;
1172
+ }
1173
+ /** Big-endian bytes, written in order into a buffer of known size. */
1174
+ var Writer = class {
1175
+ data;
1176
+ view;
1177
+ at = 0;
1178
+ constructor(size) {
1179
+ this.data = new Uint8Array(size);
1180
+ this.view = new DataView(this.data.buffer);
1181
+ }
1182
+ /** Write an unsigned 16-bit value. */
1183
+ u16(v) {
1184
+ this.view.setUint16(this.at, v & 65535);
1185
+ this.at += 2;
1186
+ }
1187
+ /** Write a signed 16-bit value. */
1188
+ i16(v) {
1189
+ this.view.setInt16(this.at, clamp(Math.round(v), -32768, 32767));
1190
+ this.at += 2;
1191
+ }
1192
+ /** Write an unsigned 32-bit value. */
1193
+ u32(v) {
1194
+ this.view.setUint32(this.at, v >>> 0);
1195
+ this.at += 4;
1196
+ }
1197
+ /** Write raw bytes. */
1198
+ bytes(values) {
1199
+ for (let i = 0; i < values.length; i++) this.data[this.at + i] = values[i];
1200
+ this.at += values.length;
1201
+ }
1202
+ };
1203
+ function utf16be(text) {
1204
+ const out = new Uint8Array(text.length * 2);
1205
+ for (let i = 0; i < text.length; i++) {
1206
+ out[i * 2] = text.charCodeAt(i) >> 8;
1207
+ out[i * 2 + 1] = text.charCodeAt(i) & 255;
1208
+ }
1209
+ return out;
1210
+ }
1211
+ function concat(parts) {
1212
+ const out = new Uint8Array(parts.reduce((s, p) => s + p.length, 0));
1213
+ let at = 0;
1214
+ for (const p of parts) {
1215
+ out.set(p, at);
1216
+ at += p.length;
1217
+ }
1218
+ return out;
1219
+ }
1220
+ function clamp(v, lo, hi) {
1221
+ return Math.min(hi, Math.max(lo, v));
1222
+ }
1223
+ //#endregion
1224
+ export { MAX_BUILT_GLYPHS, buildTrueType, editableEmbedding };