@docx-editor.dev/docx-to-pdf 0.0.1 → 2.22.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2804 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ExportResourceError: () => import_export5.ExportResourceError,
24
+ PdfDocumentOpenError: () => PdfDocumentOpenError,
25
+ PdfEncodingError: () => PdfEncodingError,
26
+ PdfFidelityError: () => PdfFidelityError,
27
+ PdfOutputLimitError: () => PdfOutputLimitError,
28
+ PdfPageLimitError: () => PdfPageLimitError,
29
+ PdfWorkLimitError: () => PdfWorkLimitError,
30
+ createFontSource: () => import_editor2.createFontSource,
31
+ defineFontResolver: () => import_editor2.defineFontResolver,
32
+ exportPdf: () => exportPdf,
33
+ exportPdfFrom: () => exportPdfFrom,
34
+ openDocumentForExport: () => openDocumentForExport
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/export-pdf.ts
39
+ var import_pdf_lib6 = require("pdf-lib");
40
+ var import_export4 = require("@docx-editor.dev/core/export");
41
+
42
+ // src/context.ts
43
+ var import_export = require("@docx-editor.dev/core/export");
44
+ var import_node_zlib = require("zlib");
45
+ var MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
46
+ var MAX_OPERATIONS = 2e6;
47
+ function positiveLimit(value, maximum, name) {
48
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum)
49
+ throw new RangeError(`${name} must be an integer from 1 to ${maximum}`);
50
+ return value;
51
+ }
52
+ function number(value) {
53
+ if (!Number.isFinite(value) || Math.abs(value) > 1e6)
54
+ throw new RangeError("Invalid PDF coordinate");
55
+ return String(Number(value.toFixed(6)));
56
+ }
57
+ var PAINT_GRID_PT = 0.24;
58
+ var onGrid = (value) => Math.round(value / PAINT_GRID_PT) * PAINT_GRID_PT;
59
+ function rect(box, x, y, height, grid = false) {
60
+ const left = box.x + x;
61
+ const bottom = height - box.y - y - box.height;
62
+ if (!grid)
63
+ return `${number(left)} ${number(bottom)} ${number(box.width)} ${number(box.height)} re`;
64
+ const extent = (value) => value > 0 ? Math.max(PAINT_GRID_PT, Math.floor(value / PAINT_GRID_PT) * PAINT_GRID_PT) : 0;
65
+ return `${number(onGrid(left))} ${number(onGrid(bottom))} ${number(extent(box.width))} ${number(extent(box.height))} re`;
66
+ }
67
+ function pdfLiteralUri(href) {
68
+ if (href.length === 0 || href.length > 2048) return null;
69
+ for (let index = 0; index < href.length; index += 1) {
70
+ const code = href.charCodeAt(index);
71
+ if (code < 32 || code > 126) return null;
72
+ }
73
+ return href.replace(/[\\()]/g, (character) => `\\${character}`);
74
+ }
75
+ function flateStream(context, contents, dict = {}) {
76
+ const bytes = typeof contents === "string" ? Buffer.from(contents, "latin1") : contents;
77
+ const deflated = (0, import_node_zlib.deflateSync)(bytes);
78
+ const owned = deflated.buffer.byteLength > deflated.byteLength * 2 ? new Uint8Array(deflated) : deflated;
79
+ return context.stream(owned, { ...dict, Filter: "FlateDecode" });
80
+ }
81
+ var pageHeights = /* @__PURE__ */ new WeakMap();
82
+ function pageHeight(page) {
83
+ let height = pageHeights.get(page);
84
+ if (height === void 0) pageHeights.set(page, height = page.getHeight());
85
+ return height;
86
+ }
87
+ var HEX_BYTES = Array.from({ length: 256 }, (_, byte) => byte.toString(16).padStart(2, "0"));
88
+ function hex(value) {
89
+ if (value >= 0 && value <= 65535 && Number.isInteger(value))
90
+ return HEX_BYTES[value >> 8] + HEX_BYTES[value & 255];
91
+ return value.toString(16).padStart(4, "0");
92
+ }
93
+ function unicodeHex(text) {
94
+ const out = [];
95
+ for (let i = 0; i < text.length; i++) out.push(hex(text.charCodeAt(i)));
96
+ return out.join("");
97
+ }
98
+ function color(value) {
99
+ const v = value && /^[a-f\d]{6}$/i.test(value) ? value : "000000";
100
+ return [0, 2, 4].map((i) => number(parseInt(v.slice(i, i + 2), 16) / 255)).join(" ");
101
+ }
102
+ var Work = class {
103
+ constructor(signal, deadline = Infinity) {
104
+ this.signal = signal;
105
+ this.deadline = deadline;
106
+ }
107
+ signal;
108
+ deadline;
109
+ diagnostics = [];
110
+ seen = /* @__PURE__ */ new Set();
111
+ operations = 0;
112
+ contentBytes = 0;
113
+ check() {
114
+ if (Date.now() >= this.deadline)
115
+ throw new import_export.ExportResourceError("timedOut", "PDF conversion timed out");
116
+ if (this.signal.aborted)
117
+ throw this.signal.reason instanceof import_export.ExportResourceError ? this.signal.reason : new import_export.ExportResourceError("aborted", "PDF conversion aborted", {
118
+ cause: this.signal.reason
119
+ });
120
+ }
121
+ reserveContent(bytes) {
122
+ this.contentBytes += bytes;
123
+ if (this.contentBytes > MAX_OUTPUT_BYTES) throw new PdfWorkLimitError();
124
+ }
125
+ tick() {
126
+ this.check();
127
+ if (++this.operations > MAX_OPERATIONS) throw new PdfWorkLimitError();
128
+ }
129
+ async yield() {
130
+ this.check();
131
+ await new Promise((resolve) => setImmediate(resolve));
132
+ this.check();
133
+ }
134
+ report(code, message, pageIndex, severity = "unsupported", origin) {
135
+ const key = `${code}:${pageIndex ?? ""}:${origin?.originIndex ?? ""}:${message}`;
136
+ if (this.seen.has(key)) return;
137
+ if (this.diagnostics.length >= 1e4) throw new PdfWorkLimitError();
138
+ this.seen.add(key);
139
+ this.diagnostics.push(
140
+ Object.freeze({
141
+ code,
142
+ message,
143
+ pageIndex,
144
+ ...pageIndex === void 0 ? {} : { pageNumber: pageIndex + 1 },
145
+ severity,
146
+ ...origin
147
+ })
148
+ );
149
+ }
150
+ };
151
+ var PdfWorkLimitError = class extends Error {
152
+ code = "workLimitExceeded";
153
+ constructor() {
154
+ super("PDF content, operation, or diagnostic limit exceeded");
155
+ this.name = "PdfWorkLimitError";
156
+ }
157
+ };
158
+ var Commands = class extends Array {
159
+ constructor(work) {
160
+ super();
161
+ this.work = work;
162
+ }
163
+ work;
164
+ push(...values) {
165
+ for (const value of values) this.work.reserveContent(value.length + 1);
166
+ return super.push(...values);
167
+ }
168
+ unshift(...values) {
169
+ for (const value of values) this.work.reserveContent(value.length + 1);
170
+ return super.unshift(...values);
171
+ }
172
+ };
173
+
174
+ // src/options.ts
175
+ function validateOptions(options) {
176
+ if (!options || typeof options !== "object" || Array.isArray(options))
177
+ throw new TypeError("options must be an object");
178
+ for (const [name, choices] of [
179
+ ["fidelityPolicy", ["strict", "best-effort"]],
180
+ ["fontPolicy", ["strict", "best-effort"]],
181
+ ["displayMode", ["proposed", "original", "all-markup"]]
182
+ ]) {
183
+ const value = options[name];
184
+ if (value !== void 0 && !choices.includes(value))
185
+ throw new RangeError(`Invalid ${name}: expected ${choices.join(", ")}`);
186
+ }
187
+ for (const name of ["useSystemFonts", "comments", "documentLigatures"]) {
188
+ if (options[name] !== void 0 && typeof options[name] !== "boolean")
189
+ throw new TypeError(`${name} must be boolean`);
190
+ }
191
+ for (const name of ["resourceTimeoutMs", "fontResolutionTimeoutMs"]) {
192
+ const value = options[name];
193
+ if (value !== void 0 && (!Number.isFinite(value) || value <= 0 || value > 2147483647))
194
+ throw new RangeError(`${name} must be positive and at most 2147483647`);
195
+ }
196
+ for (const name of ["onFontResolution", "convertPreservedImage"]) {
197
+ if (options[name] !== void 0 && typeof options[name] !== "function")
198
+ throw new TypeError(`${name} must be a function`);
199
+ }
200
+ if (options.imageDecodePort !== void 0 && (!options.imageDecodePort || typeof options.imageDecodePort !== "object" || typeof options.imageDecodePort.decode !== "function"))
201
+ throw new TypeError("imageDecodePort must provide a decode function");
202
+ const signal = options.signal;
203
+ if (signal !== void 0 && (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function"))
204
+ throw new TypeError("signal must be an AbortSignal");
205
+ for (const name of ["measurer", "producer", "reuseAcrossRevisions"]) {
206
+ if (options[name] !== void 0)
207
+ throw new TypeError(`${name} is not supported by exportPdf`);
208
+ }
209
+ const { timeoutMs = 6e4, maxOutputBytes = MAX_OUTPUT_BYTES, maxPages = 1e4 } = options;
210
+ return {
211
+ timeoutMs: positiveLimit(timeoutMs, 2147483647, "timeoutMs"),
212
+ maxBytes: positiveLimit(maxOutputBytes, MAX_OUTPUT_BYTES, "maxOutputBytes"),
213
+ maxPages: positiveLimit(maxPages, 1e4, "maxPages")
214
+ };
215
+ }
216
+
217
+ // src/open-session.ts
218
+ var import_export2 = require("@docx-editor.dev/core/export");
219
+ var import_layout = require("@docx-editor.dev/core/layout");
220
+ var import_fonts2 = require("@docx-editor.dev/fonts");
221
+
222
+ // src/font-provisioning.ts
223
+ var import_fontkit = require("fontkit");
224
+ var import_promises = require("fs/promises");
225
+ var import_node_os = require("os");
226
+ var import_node_path = require("path");
227
+ var import_node_url = require("url");
228
+ var import_editor = require("@docx-editor.dev/core/editor");
229
+ var import_fonts = require("@docx-editor.dev/fonts");
230
+ var import_meta = {};
231
+ var assetRoot = new import_node_url.URL(
232
+ "../assets/",
233
+ typeof __dirname === "string" ? (0, import_node_url.pathToFileURL)(__dirname + "/").href : import_meta.url
234
+ );
235
+ var latinSubstitute = (family, prefix) => [
236
+ ["Regular", 400, "normal"],
237
+ ["Bold", 700, "normal"],
238
+ ["Italic", 400, "italic"],
239
+ ["BoldItalic", 700, "italic"]
240
+ ].map(([suffix, weight, style]) => [
241
+ family,
242
+ new import_node_url.URL(`${prefix}-${suffix}.ttf`, import_fonts.FONT_ASSET_ROOT),
243
+ weight,
244
+ style
245
+ ]);
246
+ var supplemental = [
247
+ ["Noto Sans Symbols 2", new import_node_url.URL("NotoSansSymbols2-Regular.ttf", assetRoot), 400, "normal"],
248
+ ["Noto Sans Math", new import_node_url.URL("NotoSansMath-Regular.ttf", assetRoot), 400, "normal"],
249
+ ["Noto Sans Arabic", new import_node_url.URL("NotoSansArabic-Regular.ttf", assetRoot), 400, "normal"],
250
+ ["Noto Sans CJK JP", new import_node_url.URL("NotoSansCJKjp-Regular.otf", assetRoot), 400, "normal"],
251
+ ["Twemoji Mozilla", new import_node_url.URL("TwemojiMozilla.ttf", assetRoot), 400, "normal"],
252
+ ["Noto Emoji", new import_node_url.URL("NotoEmoji-Regular.ttf", assetRoot), 400, "normal"],
253
+ ...latinSubstitute("Liberation Sans", "LiberationSans"),
254
+ ...latinSubstitute("Liberation Serif", "LiberationSerif"),
255
+ ...latinSubstitute("Liberation Mono", "LiberationMono"),
256
+ ...latinSubstitute("Carlito", "Carlito"),
257
+ ...latinSubstitute("Caladea", "Caladea")
258
+ ];
259
+ var PDF_GLYPH_FALLBACKS = [
260
+ "Segoe UI Symbol",
261
+ "Noto Sans Symbols 2",
262
+ "Noto Sans Math",
263
+ "Noto Sans Arabic",
264
+ // Prefer available Word CJK faces; packaged Noto remains the portable fallback.
265
+ "SimSun",
266
+ "Batang",
267
+ "Noto Sans CJK JP",
268
+ // Color first: a COLR face paints its layers in the writer. The monochrome face stays
269
+ // behind it for the few symbols the color set leaves out.
270
+ "Twemoji Mozilla",
271
+ "Noto Emoji"
272
+ ].map((family) => ({ family, weight: 400, style: "normal" }));
273
+ var face = (family, weight = 400, style = "normal") => ({ family, weight, style });
274
+ var substitutes = {
275
+ "Cambria Math": "Noto Sans Math",
276
+ Georgia: "Liberation Serif",
277
+ Verdana: "Liberation Sans",
278
+ Helvetica: "Liberation Sans",
279
+ ...Object.fromEntries(
280
+ [
281
+ "MS Gothic",
282
+ "MS PGothic",
283
+ "MS UI Gothic",
284
+ "MS Mincho",
285
+ "MS PMincho",
286
+ "Meiryo",
287
+ "Yu Gothic",
288
+ "Yu Mincho",
289
+ "SimSun",
290
+ "NSimSun",
291
+ "SimHei",
292
+ "FangSong",
293
+ "KaiTi",
294
+ "DengXian",
295
+ "Microsoft YaHei",
296
+ "Microsoft JhengHei",
297
+ "MingLiU",
298
+ "PMingLiU",
299
+ "Batang",
300
+ "Gulim",
301
+ "Dotum",
302
+ "Malgun Gothic"
303
+ ].map((family) => [family, "Noto Sans CJK JP"])
304
+ )
305
+ };
306
+ var localizedFamilies = {
307
+ \u5B8B\u4F53: "SimSun",
308
+ \u65B0\u5B8B\u4F53: "NSimSun",
309
+ \u9ED1\u4F53: "SimHei",
310
+ \u4EFF\u5B8B: "FangSong",
311
+ \u6977\u4F53: "KaiTi",
312
+ \u7B49\u7EBF: "DengXian",
313
+ \u5FAE\u8F6F\u96C5\u9ED1: "Microsoft YaHei",
314
+ \u5FAE\u8EDF\u6B63\u9ED1\u9AD4: "Microsoft JhengHei",
315
+ \u65B0\u7D30\u660E\u9AD4: "PMingLiU",
316
+ \u7D30\u660E\u9AD4: "MingLiU",
317
+ "\uFF2D\uFF33 \u660E\u671D": "MS Mincho",
318
+ "\uFF2D\uFF33 \uFF30\u660E\u671D": "MS PMincho",
319
+ "\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF": "MS Gothic",
320
+ "\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF": "MS PGothic",
321
+ "\uFF2D\uFF33 \uFF35\uFF29 \uFF27\uFF4F\uFF54\uFF48\uFF49\uFF43": "MS UI Gothic",
322
+ \u30E1\u30A4\u30EA\u30AA: "Meiryo",
323
+ \u6E38\u30B4\u30B7\u30C3\u30AF: "Yu Gothic",
324
+ \u6E38\u660E\u671D: "Yu Mincho",
325
+ "\uB9D1\uC740 \uACE0\uB515": "Malgun Gothic",
326
+ \uBC14\uD0D5: "Batang",
327
+ \uAD74\uB9BC: "Gulim",
328
+ \uB3CB\uC6C0: "Dotum"
329
+ };
330
+ function canonicalFamily(family) {
331
+ let base = family;
332
+ let bold = false;
333
+ let italic = false;
334
+ if (family.length <= 128) {
335
+ const lower = family.toLowerCase();
336
+ for (const [suffix, flags] of [
337
+ [" bold italic", [true, true]],
338
+ [" bolditalic", [true, true]],
339
+ [" bold", [true, false]],
340
+ [" italic", [false, true]]
341
+ ]) {
342
+ if (!lower.endsWith(suffix) || lower.length === suffix.length) continue;
343
+ base = family.slice(0, family.length - suffix.length).trimEnd();
344
+ [bold, italic] = flags;
345
+ break;
346
+ }
347
+ }
348
+ return {
349
+ family: Object.hasOwn(localizedFamilies, base) ? localizedFamilies[base] : base,
350
+ bold,
351
+ italic
352
+ };
353
+ }
354
+ var packagedSubstitutes = {
355
+ Calibri: "Carlito",
356
+ Cambria: "Caladea",
357
+ "Times New Roman": "Liberation Serif",
358
+ Arial: "Liberation Sans",
359
+ "Courier New": "Liberation Mono",
360
+ "Century Gothic": "TeX Gyre Adventor"
361
+ };
362
+ var substituteFor = (family) => {
363
+ const canonical = canonicalFamily(family).family;
364
+ if (Object.hasOwn(substitutes, canonical)) return substitutes[canonical];
365
+ if (canonical !== family && Object.hasOwn(packagedSubstitutes, canonical))
366
+ return packagedSubstitutes[canonical];
367
+ return void 0;
368
+ };
369
+ function genericSubstituteFor(family) {
370
+ const name = canonicalFamily(family).family;
371
+ if (/mono|courier|consol|menlo|typewriter|\bcode\b|fixed/i.test(name)) return "Liberation Mono";
372
+ if (/sans/i.test(name)) return "Liberation Sans";
373
+ if (/serif|roman|garamond|georgia|times|book|baskerville|cambria|minion|palatino|century|didot|caslon|constantia|sagona|lora|merriweather|playfair|charter|bodoni|perpetua|rockwell|goudy|bembo|sabon|antiqua/i.test(
374
+ name
375
+ ))
376
+ return "Liberation Serif";
377
+ return "Liberation Sans";
378
+ }
379
+ var knownFamilies = /* @__PURE__ */ new Set([
380
+ ...supplemental.map(([family]) => family),
381
+ ...Object.keys(substitutes),
382
+ ...Object.keys(packagedSubstitutes),
383
+ ...Object.values(packagedSubstitutes),
384
+ "Symbol",
385
+ "Wingdings",
386
+ "Wingdings 2",
387
+ "Wingdings 3",
388
+ "Webdings",
389
+ "MT Extra"
390
+ ]);
391
+ function isGenericSubstitution(family, sourceFamily) {
392
+ if (family === sourceFamily) return false;
393
+ return !knownFamilies.has(canonicalFamily(family).family);
394
+ }
395
+ var supplementalFonts = (0, import_editor.defineFontResolver)(async ({ families, signal }) => {
396
+ const wanted = /* @__PURE__ */ new Set();
397
+ for (const family of families) {
398
+ wanted.add(family);
399
+ const target = substituteFor(family);
400
+ if (target) wanted.add(target);
401
+ }
402
+ const sources = await Promise.all(
403
+ supplemental.filter(([family]) => wanted.has(family)).map(async ([family, file, weight, style]) => {
404
+ const bytes = new Uint8Array(await readFontFile(file, signal));
405
+ const result = (0, import_editor.createFontSource)(bytes, face(family, weight, style));
406
+ if ("failure" in result) throw new Error(`Invalid packaged PDF font: ${file.pathname}`);
407
+ return result.source;
408
+ })
409
+ );
410
+ return {
411
+ sources,
412
+ substitutions: families.flatMap((family) => {
413
+ const target = substituteFor(family);
414
+ if (!target) return [];
415
+ const { bold, italic } = canonicalFamily(family);
416
+ return [400, 700].flatMap(
417
+ (weight) => ["normal", "italic"].map((style) => ({
418
+ from: face(family, weight, style),
419
+ // The packaged CJK, symbol and mathematics faces come in one weight and one style.
420
+ to: target.startsWith("Noto") ? face(target) : face(
421
+ target,
422
+ bold || weight === 700 ? 700 : 400,
423
+ italic || style === "italic" ? "italic" : "normal"
424
+ )
425
+ }))
426
+ );
427
+ })
428
+ };
429
+ });
430
+ var standInFonts = (0, import_editor.defineFontResolver)(async ({ families, signal, resolvedFaces }) => {
431
+ const covered = new Set(
432
+ (resolvedFaces ?? []).map((face2) => `${face2.family.toLowerCase()}/${face2.weight}/${face2.style}`)
433
+ );
434
+ const has = (family, weight, style) => covered.has(`${family.toLowerCase()}/${weight}/${style}`);
435
+ const faces = [
436
+ [400, "normal"],
437
+ [700, "normal"],
438
+ [400, "italic"],
439
+ [700, "italic"]
440
+ ];
441
+ const substitutions = [];
442
+ const wanted = /* @__PURE__ */ new Set();
443
+ for (const family of families) {
444
+ if (has(family, 400, "normal")) {
445
+ for (const [weight, style] of faces)
446
+ if (!has(family, weight, style))
447
+ substitutions.push({ from: face(family, weight, style), to: face(family) });
448
+ continue;
449
+ }
450
+ if (knownFamilies.has(canonicalFamily(family).family)) continue;
451
+ const target = genericSubstituteFor(family);
452
+ wanted.add(target);
453
+ const { bold, italic } = canonicalFamily(family);
454
+ for (const [weight, style] of faces)
455
+ substitutions.push({
456
+ from: face(family, weight, style),
457
+ to: face(
458
+ target,
459
+ bold || weight === 700 ? 700 : 400,
460
+ italic || style === "italic" ? "italic" : "normal"
461
+ )
462
+ });
463
+ }
464
+ const sources = await Promise.all(
465
+ supplemental.filter(([family]) => wanted.has(family)).map(async ([family, file, weight, style]) => {
466
+ const bytes = new Uint8Array(await readFontFile(file, signal));
467
+ const result = (0, import_editor.createFontSource)(bytes, face(family, weight, style));
468
+ if ("failure" in result) throw new Error(`Invalid packaged PDF font: ${file.pathname}`);
469
+ return result.source;
470
+ })
471
+ );
472
+ return { sources, substitutions };
473
+ });
474
+ var wordFontRoots = process.platform === "darwin" ? [
475
+ "/System/Library/Fonts/Supplemental",
476
+ "/Library/Fonts",
477
+ (0, import_node_path.join)((0, import_node_os.homedir)(), "Library/Fonts"),
478
+ "/Applications/Microsoft Word.app/Contents/Resources/DFonts"
479
+ ] : process.platform === "win32" ? [(0, import_node_path.join)(process.env.WINDIR ?? "C:\\Windows", "Fonts")] : [
480
+ "/usr/share/fonts/truetype/msttcorefonts",
481
+ "/usr/share/fonts/truetype/msttcore",
482
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".local/share/fonts")
483
+ ];
484
+ function installedWordFontResolver(roots) {
485
+ return (0, import_editor.defineFontResolver)(async ({ families, defaultFamily, signal }) => {
486
+ const names = {
487
+ Arial: ["Arial", "arial"],
488
+ "Times New Roman": ["Times New Roman", "times"],
489
+ "Courier New": ["Courier New", "cour"],
490
+ Georgia: ["Georgia", "georgia"],
491
+ Verdana: ["Verdana", "verdana"],
492
+ Calibri: ["Calibri", "calibri"],
493
+ "Segoe UI Symbol": ["Segoe UI Symbol", "seguisym"],
494
+ SimSun: ["SimSun", "simsun"],
495
+ Batang: ["Batang", "batang"],
496
+ Cambria: ["Cambria", "cambria"],
497
+ "Cambria Math": ["Cambria Math"],
498
+ "MS Gothic": ["MS Gothic", "msgothic"],
499
+ "MS PGothic": ["MS PGothic"],
500
+ "MS UI Gothic": ["MS UI Gothic"],
501
+ "Arial Narrow": ["Arial Narrow", "ArialNarrow"],
502
+ "Malgun Gothic": ["malgun"],
503
+ // Legacy symbol-encoded faces. A Word bullet is the font's own byte plus 0xF000
504
+ // (U+F0B7 in Symbol), a private-use codepoint only these faces carry — and the face
505
+ // also SIZES the line it sits on, because a 12 pt Symbol ascends 12.06 pt where a
506
+ // 12 pt text face ascends 11.52. Without them the glyph falls to the shared symbol
507
+ // fallback, which has neither the right outline nor the right ascent.
508
+ Symbol: ["Symbol", "symbol"],
509
+ Wingdings: ["Wingdings", "wingding"],
510
+ "Wingdings 2": ["Wingdings 2", "wingdng2"],
511
+ "Wingdings 3": ["Wingdings 3", "wingdng3"],
512
+ Webdings: ["Webdings", "webdings"]
513
+ };
514
+ const files = {
515
+ Aptos: [["Aptos.ttf"], ["Aptos-Bold.ttf"], ["Aptos-Italic.ttf"], ["Aptos-Bold-Italic.ttf"]],
516
+ "Aptos Narrow": [
517
+ ["Aptos-Narrow.ttf"],
518
+ ["Aptos-Narrow-Bold.ttf"],
519
+ ["Aptos-Narrow-Italic.ttf"],
520
+ ["Aptos-Narrow-Bold-Italic.ttf"]
521
+ ],
522
+ Helvetica: [["Helvetica.ttc"], ["Helvetica.ttc"], ["Helvetica.ttc"], ["Helvetica.ttc"]],
523
+ SimHei: [["SimHei.ttf", "simhei.ttf"], [], [], []],
524
+ FangSong: [["Fangsong.ttf", "simfang.ttf"], [], [], []],
525
+ KaiTi: [["Kaiti.ttf", "simkai.ttf"], [], [], []],
526
+ DengXian: [["Deng.ttf"], ["Dengb.ttf"], [], []],
527
+ "MS Mincho": [["msmincho.ttc"], [], [], []],
528
+ "MS PMincho": [["msmincho.ttc"], [], [], []],
529
+ "Microsoft YaHei": [["msyh.ttc"], ["msyhbd.ttc"], [], []],
530
+ Meiryo: [["meiryo.ttc"], ["meiryob.ttc"], [], []],
531
+ MingLiU: [["mingliu.ttc"], [], [], []],
532
+ PMingLiU: [["mingliu.ttc"], [], [], []],
533
+ Gulim: [["gulim.ttc"], [], [], []],
534
+ Dotum: [["gulim.ttc"], [], [], []]
535
+ };
536
+ const sources = [];
537
+ for (const requested of /* @__PURE__ */ new Set([...families, ...defaultFamily ? [defaultFamily] : []])) {
538
+ const { family, bold, italic } = canonicalFamily(requested);
539
+ if (!Object.hasOwn(names, family) && !Object.hasOwn(files, family)) continue;
540
+ for (const [weight, style] of [
541
+ [400, "normal"],
542
+ [700, "normal"],
543
+ [400, "italic"],
544
+ [700, "italic"]
545
+ ]) {
546
+ const faceBold = bold || weight === 700;
547
+ const faceItalic = italic || style === "italic";
548
+ const slot = (faceBold ? 1 : 0) + (faceItalic ? 2 : 0);
549
+ const [suffix, short] = [
550
+ ["", ""],
551
+ [" Bold", "bd"],
552
+ [" Italic", "i"],
553
+ [" Bold Italic", "bi"]
554
+ ][slot];
555
+ let found = false;
556
+ for (const root of roots) {
557
+ if (found) break;
558
+ const shortSuffix = ["Georgia", "Verdana", "Calibri", "Cambria"].includes(family) ? { bd: "b", bi: "z" }[short] ?? short : short;
559
+ const candidates = (names[family] ?? []).flatMap((name) => [
560
+ name + suffix + ".ttf",
561
+ name + shortSuffix + ".ttf"
562
+ ]);
563
+ candidates.push(...files[family]?.[slot] ?? []);
564
+ if (slot === 0 && ["Cambria", "Cambria Math"].includes(family))
565
+ candidates.push("Cambria.ttc", "cambria.ttc");
566
+ if (slot === 0 && ["MS Gothic", "MS PGothic", "MS UI Gothic"].includes(family))
567
+ candidates.push("msgothic.ttc", "MSGOTHIC.TTC");
568
+ if (slot === 0) {
569
+ if (family === "SimSun") candidates.push("Simsun.ttc", "simsun.ttc");
570
+ if (family === "Batang") candidates.push("batang.ttc", "Batang.ttc");
571
+ }
572
+ for (const filename of new Set(candidates)) {
573
+ if (signal?.aborted) throw signal.reason;
574
+ try {
575
+ const bytes = new Uint8Array(await readFontFile((0, import_node_path.join)(root, filename), signal));
576
+ let faceIndex = 0;
577
+ if (filename.toLowerCase().endsWith(".ttc")) {
578
+ const collection = (0, import_fontkit.create)(Buffer.from(bytes));
579
+ if (!("fonts" in collection)) continue;
580
+ faceIndex = collection.fonts.findIndex(
581
+ (font) => collectionFaceMatches(font.postscriptName, family, faceBold, faceItalic)
582
+ );
583
+ if (faceIndex < 0) continue;
584
+ }
585
+ const source = (0, import_editor.createFontSource)(bytes, {
586
+ ...face(requested, weight, style),
587
+ faceIndex
588
+ });
589
+ if ("failure" in source) continue;
590
+ sources.push(source.source);
591
+ found = true;
592
+ break;
593
+ } catch (error) {
594
+ if (signal?.aborted) throw error;
595
+ if (error.code !== "ENOENT") throw error;
596
+ }
597
+ }
598
+ if (found) break;
599
+ }
600
+ }
601
+ }
602
+ return { sources };
603
+ });
604
+ }
605
+ function collectionFaceMatches(postscriptName, family, bold, italic) {
606
+ if (typeof postscriptName !== "string") return false;
607
+ const actual = postscriptName.replace(/[- ]/g, "").replace(/Oblique$/, "Italic").toLowerCase();
608
+ const expected = `${family.replace(/[- ]/g, "")}${bold ? "Bold" : ""}${italic ? "Italic" : ""}`;
609
+ return actual === expected.toLowerCase();
610
+ }
611
+ var installedWordFonts = installedWordFontResolver(wordFontRoots);
612
+ async function readFontFile(path, signal) {
613
+ if (signal?.aborted) throw signal.reason;
614
+ if ((await (0, import_promises.stat)(path)).size > 32 * 1024 * 1024) throw new RangeError("Local font exceeds 32 MiB");
615
+ let bytes;
616
+ try {
617
+ bytes = await (0, import_promises.readFile)(path, signal ? { signal } : void 0);
618
+ } catch (error) {
619
+ if (!signal || error.code !== "ERR_INVALID_ARG_TYPE") throw error;
620
+ bytes = await (0, import_promises.readFile)(path);
621
+ }
622
+ if (signal?.aborted) throw signal.reason;
623
+ return bytes;
624
+ }
625
+
626
+ // src/open-session.ts
627
+ var bundledFonts = (0, import_fonts2.packagedFonts)({
628
+ install: false,
629
+ fetcher: (0, import_export2.createPackagedFileFetch)({
630
+ trustedRoot: new URL("./", import_fonts2.FONT_ASSET_ROOT),
631
+ maxBytes: import_layout.HARD_MAX_FONT_BYTES
632
+ })
633
+ });
634
+ function openExportSession(source, options, signal) {
635
+ const { fonts, fallbackFonts, lastResortFonts, useSystemFonts = true, ...core } = options;
636
+ const origins = [
637
+ ...fonts ? Array.isArray(fonts) ? fonts : [fonts] : [],
638
+ ...useSystemFonts ? [installedWordFonts] : [],
639
+ bundledFonts,
640
+ ...fallbackFonts ? Array.isArray(fallbackFonts) ? fallbackFonts : [fallbackFonts] : [],
641
+ supplementalFonts
642
+ ];
643
+ return (0, import_export2.openFontBackedDocumentForExport)(source, {
644
+ ...core,
645
+ documentLigatures: options.documentLigatures ?? true,
646
+ signal,
647
+ displayMode: options.displayMode ?? "proposed",
648
+ reuseAcrossRevisions: false,
649
+ // Every packaged fallback face is on offer. They are read lazily, only when a family the
650
+ // document uses needs one, so offering the whole list costs a plain Latin document nothing.
651
+ glyphFallbacks: options.glyphFallbacks ?? PDF_GLYPH_FALLBACKS,
652
+ fonts: origins,
653
+ // After the document's own embedded fonts: a stand-in for what nothing else covers.
654
+ lastResortFonts: [
655
+ ...lastResortFonts ? Array.isArray(lastResortFonts) ? lastResortFonts : [lastResortFonts] : [],
656
+ standInFonts
657
+ ]
658
+ });
659
+ }
660
+
661
+ // src/font-diagnostics.ts
662
+ function reportFontDiagnostics(resolution, work) {
663
+ for (const failure of resolution.originFailures) {
664
+ let detail = "unknown error";
665
+ try {
666
+ if (failure.cause instanceof Error && typeof failure.cause.message === "string")
667
+ detail = failure.cause.message.slice(0, 1024);
668
+ } catch {
669
+ }
670
+ work.report("font-origin-failed", `A font source failed: ${detail}`, void 0, "information", {
671
+ originIndex: failure.originIndex,
672
+ ...failure.originName === void 0 ? {} : { originName: failure.originName }
673
+ });
674
+ }
675
+ for (const family of resolution.families) {
676
+ if (family.coverage !== "complete" || family.faces.some(
677
+ (face2) => face2.via === "substitution" && face2.sourceFamily.toLowerCase() === family.family.toLowerCase()
678
+ ))
679
+ work.report(
680
+ "incomplete-font",
681
+ family.coverage !== "complete" ? `Font face coverage is incomplete for ${family.family}; inspect fontResolution` : `Font variants use substitute faces from ${family.family}; inspect fontResolution`,
682
+ void 0,
683
+ "information"
684
+ );
685
+ const stand = family.faces.find(
686
+ (face2) => face2.via === "substitution" && isGenericSubstitution(family.family, face2.sourceFamily)
687
+ );
688
+ if (stand)
689
+ work.report(
690
+ "font-substitution",
691
+ `${family.family} is not available; best-effort export renders it in ${stand.sourceFamily}`
692
+ );
693
+ }
694
+ }
695
+
696
+ // src/paint.ts
697
+ var import_layout5 = require("@docx-editor.dev/core/layout");
698
+
699
+ // src/text.ts
700
+ var import_pdf_lib2 = require("pdf-lib");
701
+ var import_layout4 = require("@docx-editor.dev/core/layout");
702
+
703
+ // src/underline-gap.ts
704
+ var import_layout2 = require("@docx-editor.dev/core/layout");
705
+ function underlineGap(visit) {
706
+ const { line, span } = visit;
707
+ const index = line.spans.indexOf(span);
708
+ if (index < 0 || (line.drawings?.length ?? 0) > 0) return 0;
709
+ const next = line.spans[index + 1];
710
+ if (!next || next.style.hidden || next.equation || next.text.includes(" ")) return 0;
711
+ if (!span.text.endsWith(" ") || /\s/u.test(span.text.slice(0, -1))) return 0;
712
+ if ((next.wrapAdvanceBefore ?? 0) > 1e-3 || span.link !== next.link) return 0;
713
+ if (span.range.paragraphId !== next.range.paragraphId) return 0;
714
+ if (span.revisions?.length || next.revisions?.length) return 0;
715
+ const left = (0, import_layout2.styleForFontSlot)(span.style, span.fontSlot);
716
+ const right = (0, import_layout2.styleForFontSlot)(next.style, next.fontSlot);
717
+ if (left.underline?.variant !== "single" || right.underline?.variant !== "single") return 0;
718
+ if (left.horizontalScalePercent !== 100 || right.horizontalScalePercent !== 100) return 0;
719
+ if (left.shaping?.baseLevel === 1 || right.shaping?.baseLevel === 1) return 0;
720
+ if (left.fontFamily !== right.fontFamily || left.fontSizePt !== right.fontSizePt || left.bold !== right.bold || left.italic !== right.italic || left.verticalAlign !== right.verticalAlign || left.baselineShiftPt !== right.baselineShiftPt || (left.underline.color ?? left.color) !== (right.underline.color ?? right.color))
721
+ return 0;
722
+ const gap = next.box.x - (span.box.x + span.box.width);
723
+ return gap > 0.25 ? gap : 0;
724
+ }
725
+
726
+ // src/paragraph-grid-origin.ts
727
+ var PDF_PAINT_GRID_PT = 0.24;
728
+ function paragraphGridOffsetX(visit) {
729
+ const alignment = visit.paragraph.alignment;
730
+ const origin = alignment === "left" || alignment === "both" ? visit.storyOrigin.x + visit.line.contentX : visit.storyOrigin.x + visit.paragraph.box.x;
731
+ return Math.round(origin / PDF_PAINT_GRID_PT) * PDF_PAINT_GRID_PT - origin;
732
+ }
733
+
734
+ // src/fonts.ts
735
+ var import_fontkit3 = require("fontkit");
736
+ var import_pdf_lib = require("pdf-lib");
737
+ var import_layout3 = require("@docx-editor.dev/core/layout");
738
+
739
+ // src/pdf-font-embedding.ts
740
+ var import_fontkit2 = require("fontkit");
741
+ var MAX_SFNT_TABLES = 4096;
742
+ var MAX_TTC_FACES = 256;
743
+ var BITMAP_ONLY_EMBEDDING = 512;
744
+ var TTC_VERSION_1 = 65536;
745
+ var TTC_VERSION_2 = 131072;
746
+ function readUint16(bytes, offset) {
747
+ if (offset < 0 || offset > bytes.byteLength - 2) return null;
748
+ return bytes[offset] << 8 | bytes[offset + 1];
749
+ }
750
+ function readUint32(bytes, offset) {
751
+ if (offset < 0 || offset > bytes.byteLength - 4) return null;
752
+ return bytes[offset] * 16777216 + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3] >>> 0;
753
+ }
754
+ function tagAt(bytes, offset) {
755
+ if (offset < 0 || offset > bytes.byteLength - 4) return null;
756
+ return String.fromCharCode(
757
+ bytes[offset],
758
+ bytes[offset + 1],
759
+ bytes[offset + 2],
760
+ bytes[offset + 3]
761
+ );
762
+ }
763
+ function isFontCollection(bytes) {
764
+ return tagAt(bytes, 0) === "ttcf";
765
+ }
766
+ function refuse(reason) {
767
+ return { kind: "refuse", reason };
768
+ }
769
+ function embed(collectionSelector) {
770
+ return { kind: "embed", collectionSelector };
771
+ }
772
+ function isFontkitFont(value) {
773
+ return value !== null && typeof value.hasGlyphForCodePoint === "function";
774
+ }
775
+ function isFontkitCollection(value) {
776
+ return typeof value.getFont === "function";
777
+ }
778
+ function postscriptSelector(face2) {
779
+ const name = face2.postscriptName;
780
+ if (typeof name !== "string") return null;
781
+ const trimmed = name.trim();
782
+ return trimmed.length > 0 ? trimmed : null;
783
+ }
784
+ function sfntEmbeddingPermission(bytes, base) {
785
+ const tableCount = readUint16(bytes, base + 4);
786
+ if (tableCount === null || tableCount > MAX_SFNT_TABLES) {
787
+ return refuse("The admitted font has no bounded SFNT table directory");
788
+ }
789
+ const directoryEnd = base + 12 + tableCount * 16;
790
+ if (!Number.isSafeInteger(directoryEnd) || directoryEnd > bytes.byteLength) {
791
+ return refuse("The admitted font has a truncated SFNT table directory");
792
+ }
793
+ for (let index = 0; index < tableCount; index += 1) {
794
+ const record = base + 12 + index * 16;
795
+ const tag = tagAt(bytes, record);
796
+ if (tag !== "OS/2") continue;
797
+ const offset = readUint32(bytes, record + 8);
798
+ const length = readUint32(bytes, record + 12);
799
+ if (offset === null || length === null || offset > bytes.byteLength || length > bytes.byteLength - offset || length < 10) {
800
+ return refuse("The admitted font has an invalid OS/2 table range");
801
+ }
802
+ const fsType = readUint16(bytes, offset + 8);
803
+ if (fsType === null) {
804
+ return refuse("The admitted font has a truncated OS/2 fsType value");
805
+ }
806
+ if ((fsType & 2) !== 0) {
807
+ return refuse("The OS/2 fsType forbids font embedding");
808
+ }
809
+ if ((fsType & 256) !== 0) {
810
+ return refuse(
811
+ "The OS/2 fsType forbids subsetting, and this writer has no safe full-font embedding mode"
812
+ );
813
+ }
814
+ if ((fsType & BITMAP_ONLY_EMBEDDING) !== 0) {
815
+ return refuse(
816
+ "The OS/2 fsType permits bitmap embedding only, and this writer embeds outline data"
817
+ );
818
+ }
819
+ return embed(null);
820
+ }
821
+ return embed(null);
822
+ }
823
+ function collectionFaceOffset(bytes, faceIndex) {
824
+ if (bytes.byteLength < 12) {
825
+ return { reason: "The admitted font collection header is truncated" };
826
+ }
827
+ const version = readUint32(bytes, 4);
828
+ if (version !== TTC_VERSION_1 && version !== TTC_VERSION_2) {
829
+ return { reason: "The admitted font collection has an unsupported TTC version" };
830
+ }
831
+ const faceCount = readUint32(bytes, 8);
832
+ if (faceCount === null) {
833
+ return { reason: "The admitted font collection header is truncated" };
834
+ }
835
+ if (faceCount === 0) {
836
+ return { reason: "The admitted font collection has no faces" };
837
+ }
838
+ if (faceCount > MAX_TTC_FACES) {
839
+ return { reason: `The admitted font collection exceeds the ${MAX_TTC_FACES} face limit` };
840
+ }
841
+ const offsetsEnd = 12 + faceCount * 4;
842
+ if (!Number.isSafeInteger(offsetsEnd) || offsetsEnd > bytes.byteLength) {
843
+ return { reason: "The admitted font collection face directory is truncated" };
844
+ }
845
+ if (!Number.isSafeInteger(faceIndex) || faceIndex < 0) {
846
+ return { reason: "The admitted font collection faceIndex is invalid" };
847
+ }
848
+ if (faceIndex >= faceCount) {
849
+ return {
850
+ reason: `The admitted font collection faceIndex ${faceIndex} is out of range for ${faceCount} faces`
851
+ };
852
+ }
853
+ const faceOffset = readUint32(bytes, 12 + faceIndex * 4);
854
+ if (faceOffset === null || faceOffset < offsetsEnd || faceOffset > bytes.byteLength - 12) {
855
+ return { reason: "The admitted font collection face offset is invalid" };
856
+ }
857
+ return { offset: faceOffset };
858
+ }
859
+ function collectionFaceSelector(bytes, faceIndex) {
860
+ let opened;
861
+ try {
862
+ opened = (0, import_fontkit2.create)(Buffer.from(bytes));
863
+ } catch {
864
+ return { reason: "The admitted font collection could not be opened" };
865
+ }
866
+ if (!isFontkitCollection(opened)) {
867
+ return { reason: "The admitted font collection did not parse as a font collection" };
868
+ }
869
+ const fonts = opened.fonts;
870
+ if (faceIndex >= fonts.length) {
871
+ return {
872
+ reason: `The admitted font collection faceIndex ${faceIndex} is out of range for ${fonts.length} faces`
873
+ };
874
+ }
875
+ const face2 = fonts[faceIndex];
876
+ if (!isFontkitFont(face2)) {
877
+ return { reason: "The selected collection face is not a font face" };
878
+ }
879
+ const selector = postscriptSelector(face2);
880
+ if (selector === null) {
881
+ return { reason: "The selected collection face has no PostScript name" };
882
+ }
883
+ for (let index = 0; index < fonts.length; index += 1) {
884
+ if (index === faceIndex) continue;
885
+ if (postscriptSelector(fonts[index]) === selector) {
886
+ return { reason: "The selected collection face PostScript name is not unique" };
887
+ }
888
+ }
889
+ let selected;
890
+ try {
891
+ selected = (0, import_fontkit2.create)(Buffer.from(bytes), selector);
892
+ } catch {
893
+ return { reason: "The selected collection face PostScript name did not resolve" };
894
+ }
895
+ if (!isFontkitFont(selected)) {
896
+ return { reason: "The selected collection face PostScript name did not resolve" };
897
+ }
898
+ return { selector };
899
+ }
900
+ function fontEmbeddingDecision(font) {
901
+ const { bytes, faceIndex } = font;
902
+ if (!isFontCollection(bytes)) {
903
+ if (faceIndex !== 0) {
904
+ return refuse("A nonzero faceIndex requires a TrueType collection resource");
905
+ }
906
+ return sfntEmbeddingPermission(bytes, 0);
907
+ }
908
+ const located = collectionFaceOffset(bytes, faceIndex);
909
+ if ("reason" in located) return refuse(located.reason);
910
+ const permission = sfntEmbeddingPermission(bytes, located.offset);
911
+ if (permission.kind === "refuse") return permission;
912
+ const selected = collectionFaceSelector(bytes, faceIndex);
913
+ if ("reason" in selected) return refuse(selected.reason);
914
+ return embed(selected.selector);
915
+ }
916
+
917
+ // src/font-metrics.ts
918
+ function strikeMetrics(face2) {
919
+ const bytes = face2.bytes, view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
920
+ let base = 0;
921
+ if (bytes.length < 12) return null;
922
+ if (view.getUint32(0) === 1953784678) {
923
+ if (12 + face2.faceIndex * 4 + 4 > bytes.length) return null;
924
+ base = view.getUint32(12 + face2.faceIndex * 4);
925
+ }
926
+ if (base + 12 > bytes.length) return null;
927
+ const count = view.getUint16(base + 4);
928
+ if (count > 4096 || base + 12 + count * 16 > bytes.length) return null;
929
+ for (let i = 0; i < count; i++) {
930
+ const entry = base + 12 + i * 16;
931
+ if (view.getUint32(entry) !== 1330851634) continue;
932
+ const offset = view.getUint32(entry + 8), length = view.getUint32(entry + 12);
933
+ if (length < 30 || offset + length > bytes.length) return null;
934
+ return { thickness: view.getInt16(offset + 26), position: view.getInt16(offset + 28) };
935
+ }
936
+ return null;
937
+ }
938
+
939
+ // src/fonts.ts
940
+ function cmap(body, type) {
941
+ return `/CIDInit /ProcSet findresource begin
942
+ 12 dict begin
943
+ begincmap
944
+ /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> def
945
+ /CMapName /Docx${type} def
946
+ /CMapType ${type} def
947
+ 1 begincodespacerange
948
+ <0000> <ffff>
949
+ endcodespacerange
950
+ ${body}
951
+ endcmap
952
+ CMapName currentdict /CMap defineresource pop
953
+ end
954
+ end`;
955
+ }
956
+ function groups(rows, operator) {
957
+ const out = [];
958
+ for (let i = 0; i < rows.length; i += 100) {
959
+ const slice = rows.slice(i, i + 100);
960
+ out.push(`${slice.length} begin${operator}
961
+ ${slice.join("\n")}
962
+ end${operator}`);
963
+ }
964
+ return out.join("\n");
965
+ }
966
+ var EmbeddedFace = class {
967
+ constructor(doc, admitted, index) {
968
+ this.doc = doc;
969
+ const gate = fontEmbeddingDecision(admitted);
970
+ if (gate.kind === "refuse") throw new Error(gate.reason);
971
+ const font = gate.collectionSelector ? (0, import_fontkit3.create)(Buffer.from(admitted.bytes), gate.collectionSelector) : (0, import_fontkit3.create)(Buffer.from(admitted.bytes));
972
+ if (!font || !("createSubset" in font))
973
+ throw new Error("The selected resource is not a font face");
974
+ if (Object.keys(font.variationAxes ?? {}).length)
975
+ throw new Error("Variable fonts require an exact static instance");
976
+ this.font = font;
977
+ this.colorLayers = Boolean(font.COLR && font.CPAL);
978
+ this.family = admitted.request.family;
979
+ this.strike = strikeMetrics(admitted);
980
+ this.subset = font.createSubset();
981
+ this.ref = doc.context.nextRef();
982
+ this.name = `F${index}`;
983
+ }
984
+ doc;
985
+ ref;
986
+ name;
987
+ font;
988
+ strike;
989
+ /**
990
+ * A COLR/CPAL face. Its glyphs are painted as filled palette layers by the text writer and
991
+ * never written as text, so the face is not embedded: the subsetter cannot encode a color
992
+ * glyph, and the page carries the text in its line's `ActualText` instead.
993
+ */
994
+ colorLayers;
995
+ /** The family this face was admitted under, for legacy symbol-encoding extraction. */
996
+ family;
997
+ subset;
998
+ codes = /* @__PURE__ */ new Map();
999
+ rows = [];
1000
+ widths = /* @__PURE__ */ new Map();
1001
+ /**
1002
+ * One drawn glyph, and the text it should EXTRACT as.
1003
+ *
1004
+ * A legacy symbol face draws Word's own private-use codepoint (U+F0B7 is Symbol's bullet),
1005
+ * which is the right glyph and the wrong character: copied out of the page it is a private
1006
+ * character that means nothing outside that font. `ToUnicode` therefore carries the Unicode
1007
+ * twin, exactly as Word's own PDF export does. Drawing and extraction stay separate — the
1008
+ * glyph id is untouched — and a codepoint with no exact twin keeps what the file had rather
1009
+ * than gaining an approximate character.
1010
+ */
1011
+ encode(glyph, text) {
1012
+ if (!Number.isInteger(glyph) || glyph <= 0 || glyph >= this.font.numGlyphs)
1013
+ throw new Error("Core produced a missing or invalid glyph");
1014
+ text = (0, import_layout3.mapSymbolPuaText)(text, this.family);
1015
+ const key = `${glyph}:${text}`;
1016
+ let code = this.codes.get(key);
1017
+ if (code === void 0) {
1018
+ if (this.rows.length >= 65534) throw new Error("Font character code limit exceeded");
1019
+ const cid = this.subset.includeGlyph(glyph);
1020
+ if (!Number.isInteger(cid) || cid < 0 || cid > 65535)
1021
+ throw new Error("Invalid subset glyph identifier");
1022
+ code = this.rows.length + 1;
1023
+ this.rows.push({ cid, text });
1024
+ this.codes.set(key, code);
1025
+ this.widths.set(cid, this.font.getGlyph(glyph).advanceWidth * 1e3 / this.font.unitsPerEm);
1026
+ }
1027
+ return hex(code);
1028
+ }
1029
+ /**
1030
+ * A code that draws this face's space and extracts as `text`: the carrier for characters
1031
+ * painted some other way, such as the palette layers of a color emoji. Drawn invisibly, it
1032
+ * gives a reader that ignores `ActualText` the characters all the same. Null when the face
1033
+ * has no space glyph to lend.
1034
+ */
1035
+ carrier(text) {
1036
+ const space = this.font.glyphForCodePoint(32)?.id;
1037
+ if (!Number.isInteger(space) || space <= 0) return null;
1038
+ return this.encode(space, text);
1039
+ }
1040
+ /**
1041
+ * The advance this PDF declares for a character code, in 1/1000 em.
1042
+ *
1043
+ * What a viewer moves the pen by after drawing the glyph, so it is what a `TJ` adjustment
1044
+ * has to be measured against. Returns 0 for a code that was never encoded, which cannot
1045
+ * happen on the paint path because `encode` always runs first.
1046
+ */
1047
+ declaredWidth(code) {
1048
+ const row = this.rows[parseInt(code, 16) - 1];
1049
+ const width = row === void 0 ? 0 : this.widths.get(row.cid) ?? 0;
1050
+ return Math.round(width * 1e5) / 1e5;
1051
+ }
1052
+ async finish(work) {
1053
+ if (this.colorLayers) return;
1054
+ await work.yield();
1055
+ const bytes = this.subset.encode();
1056
+ work.check();
1057
+ const cff = bytes[0] === 1 && bytes[1] === 0;
1058
+ const ctx = this.doc.context;
1059
+ const prefix = Number(this.name.slice(1)).toString(26).padStart(6, "0").split("").map((c) => String.fromCharCode(65 + parseInt(c, 26))).join("");
1060
+ const base = import_pdf_lib.PDFName.of(`${prefix}+DocxFont`);
1061
+ const file = ctx.register(
1062
+ flateStream(ctx, bytes, cff ? { Subtype: "CIDFontType0C" } : { Length1: bytes.length })
1063
+ );
1064
+ const factor = 1e3 / this.font.unitsPerEm;
1065
+ const bbox = this.font.bbox;
1066
+ const descriptor = ctx.register(
1067
+ ctx.obj({
1068
+ Type: "FontDescriptor",
1069
+ FontName: base,
1070
+ Flags: 4,
1071
+ FontBBox: [bbox.minX, bbox.minY, bbox.maxX, bbox.maxY].map((v) => v * factor),
1072
+ ItalicAngle: this.font.italicAngle || 0,
1073
+ Ascent: this.font.ascent * factor,
1074
+ Descent: this.font.descent * factor,
1075
+ CapHeight: (this.font.capHeight || this.font.ascent) * factor,
1076
+ StemV: 80,
1077
+ [cff ? "FontFile3" : "FontFile2"]: file
1078
+ })
1079
+ );
1080
+ const widths = [];
1081
+ for (const [cid, width] of [...this.widths].sort((a, b) => a[0] - b[0]))
1082
+ widths.push(cid, [width]);
1083
+ const descendant = ctx.register(
1084
+ ctx.obj({
1085
+ Type: "Font",
1086
+ Subtype: cff ? "CIDFontType0" : "CIDFontType2",
1087
+ BaseFont: base,
1088
+ // Registry and Ordering are PDF byte strings, `(Adobe)` and `(Identity)`, not text
1089
+ // strings. `PDFHexString.fromText` writes UTF-16BE behind a BOM, and a consumer that
1090
+ // compares these as bytes, which Acrobat and PDF/A checkers do, cannot match the
1091
+ // CIDFont to its CMap and drops it.
1092
+ CIDSystemInfo: {
1093
+ Registry: import_pdf_lib.PDFString.of("Adobe"),
1094
+ Ordering: import_pdf_lib.PDFString.of("Identity"),
1095
+ Supplement: 0
1096
+ },
1097
+ FontDescriptor: descriptor,
1098
+ DW: 0,
1099
+ W: widths,
1100
+ ...cff ? {} : { CIDToGIDMap: "Identity" }
1101
+ })
1102
+ );
1103
+ const encoding = ctx.register(
1104
+ flateStream(
1105
+ ctx,
1106
+ cmap(
1107
+ groups(
1108
+ this.rows.map((row, i) => `<${hex(i + 1)}> ${row.cid}`),
1109
+ "cidchar"
1110
+ ),
1111
+ 1
1112
+ )
1113
+ )
1114
+ );
1115
+ const mappings = this.rows.flatMap(
1116
+ (row, i) => row.text ? [`<${hex(i + 1)}> <${unicodeHex(row.text)}>`] : []
1117
+ );
1118
+ const unicode = ctx.register(flateStream(ctx, cmap(groups(mappings, "bfchar"), 2)));
1119
+ ctx.assign(
1120
+ this.ref,
1121
+ ctx.obj({
1122
+ Type: "Font",
1123
+ Subtype: "Type0",
1124
+ BaseFont: base,
1125
+ Encoding: encoding,
1126
+ DescendantFonts: [descendant],
1127
+ ToUnicode: unicode
1128
+ })
1129
+ );
1130
+ }
1131
+ };
1132
+
1133
+ // src/color-glyphs.ts
1134
+ var MAX_LAYERS = 256;
1135
+ var MAX_COMMANDS = 8192;
1136
+ var cache = /* @__PURE__ */ new WeakMap();
1137
+ function colorGlyph(font, glyphId, work) {
1138
+ let byGlyph = cache.get(font);
1139
+ if (!byGlyph) {
1140
+ byGlyph = /* @__PURE__ */ new Map();
1141
+ cache.set(font, byGlyph);
1142
+ }
1143
+ const known = byGlyph.get(glyphId);
1144
+ if (known) return known;
1145
+ const result = readColorGlyph(font, glyphId, work);
1146
+ byGlyph.set(glyphId, result);
1147
+ return result;
1148
+ }
1149
+ function readColorGlyph(font, glyphId, work) {
1150
+ const glyph = font.getGlyph(glyphId);
1151
+ const layers = glyph.layers;
1152
+ if (!layers || layers.length === 0)
1153
+ return { kind: "none", outlined: glyph.path.commands.length > 0 };
1154
+ if (layers.length > MAX_LAYERS)
1155
+ return { kind: "refused", reason: `${layers.length} layers exceed the ${MAX_LAYERS} allowed` };
1156
+ const out = [];
1157
+ for (const layer of layers) {
1158
+ work.tick();
1159
+ const path = pathOperators(layer.glyph.path.commands, work);
1160
+ if (path === null)
1161
+ return { kind: "refused", reason: "a layer outline is malformed or too long" };
1162
+ if (!path) continue;
1163
+ const { red, green, blue, alpha } = layer.color;
1164
+ out.push({
1165
+ fill: `${number(clampByte(red) / 255)} ${number(clampByte(green) / 255)} ${number(clampByte(blue) / 255)} rg`,
1166
+ alpha: clampByte(alpha) / 255,
1167
+ path
1168
+ });
1169
+ }
1170
+ return { kind: "layers", layers: out };
1171
+ }
1172
+ function clampByte(value) {
1173
+ return Number.isFinite(value) ? Math.min(255, Math.max(0, Math.round(value))) : 0;
1174
+ }
1175
+ function pathOperators(commands, work) {
1176
+ if (commands.length > MAX_COMMANDS) return null;
1177
+ const out = [];
1178
+ let x = 0;
1179
+ let y = 0;
1180
+ for (const { command, args } of commands) {
1181
+ work.tick();
1182
+ if (args.some((value) => !Number.isFinite(value))) return null;
1183
+ let operator;
1184
+ switch (command) {
1185
+ case "moveTo":
1186
+ [x, y] = [args[0], args[1]];
1187
+ operator = `${number(x)} ${number(y)} m`;
1188
+ break;
1189
+ case "lineTo":
1190
+ [x, y] = [args[0], args[1]];
1191
+ operator = `${number(x)} ${number(y)} l`;
1192
+ break;
1193
+ case "quadraticCurveTo": {
1194
+ const [cx, cy, ex, ey] = [args[0], args[1], args[2], args[3]];
1195
+ const c1x = x + 2 / 3 * (cx - x);
1196
+ const c1y = y + 2 / 3 * (cy - y);
1197
+ const c2x = ex + 2 / 3 * (cx - ex);
1198
+ const c2y = ey + 2 / 3 * (cy - ey);
1199
+ operator = `${number(c1x)} ${number(c1y)} ${number(c2x)} ${number(c2y)} ${number(ex)} ${number(ey)} c`;
1200
+ [x, y] = [ex, ey];
1201
+ break;
1202
+ }
1203
+ case "bezierCurveTo":
1204
+ operator = args.slice(0, 6).map(number).join(" ") + " c";
1205
+ [x, y] = [args[4], args[5]];
1206
+ break;
1207
+ case "closePath":
1208
+ operator = "h";
1209
+ break;
1210
+ default:
1211
+ return null;
1212
+ }
1213
+ work.reserveContent(operator.length + 1);
1214
+ out.push(operator);
1215
+ }
1216
+ return out.join(" ");
1217
+ }
1218
+
1219
+ // src/text.ts
1220
+ var PDF_PAINT_GRID_PT2 = 0.24;
1221
+ function reducedHalfPoints(fontSizePt, factor) {
1222
+ const authored = Math.max(1, Math.round(fontSizePt * 2));
1223
+ return factor === 1 ? authored : Math.max(1, Math.round(authored * factor));
1224
+ }
1225
+ function snapToGrid(units) {
1226
+ const snapped = Math.round(units * 2) / 2;
1227
+ return Math.abs(units - snapped) < 1e-9 ? snapped : units;
1228
+ }
1229
+ function griddedBaselineInLine(line, face2, paragraph) {
1230
+ const units = (value) => Math.round(value / PDF_PAINT_GRID_PT2);
1231
+ const above = Math.abs(face2.font.ascent) + Math.abs(face2.font.lineGap);
1232
+ const descent = above > 0 ? Math.abs(face2.font.descent) / above * line.baseline : 0;
1233
+ const naturalBox = line.baseline + descent;
1234
+ const endsParagraph = paragraph.paragraphEnd === true && paragraph.spacing.after > 0 && paragraph.lines[paragraph.lines.length - 1] === line;
1235
+ if (endsParagraph || !(descent > 0) || line.box.height > naturalBox + 0.01)
1236
+ return units(line.baseline) * PDF_PAINT_GRID_PT2;
1237
+ return (units(naturalBox) - units(descent)) * PDF_PAINT_GRID_PT2;
1238
+ }
1239
+ var MAX_TJ_ADJUSTMENT = 1e6;
1240
+ function penOrigins(run) {
1241
+ const advances = run.exactAdvancesX;
1242
+ if (!advances || advances.length !== run.glyphs.length) return void 0;
1243
+ const origins = [];
1244
+ let pen = 0;
1245
+ for (const advance of advances) {
1246
+ origins.push(pen);
1247
+ pen += advance;
1248
+ }
1249
+ return origins;
1250
+ }
1251
+ function createGlyphBatch(out, horizontal) {
1252
+ let face2 = null;
1253
+ let size = 0;
1254
+ let baselineY = 0;
1255
+ let startX = 0;
1256
+ let penX = 0;
1257
+ let parts = [];
1258
+ const flush = () => {
1259
+ if (parts.length === 0) return;
1260
+ const show = parts.length === 1 && parts[0].startsWith("<") ? `${parts[0]} Tj` : `[${parts.join("")}] TJ`;
1261
+ out.push(`${number(horizontal)} 0 0 1 ${number(startX)} ${number(baselineY)} Tm ${show}`);
1262
+ parts = [];
1263
+ face2 = null;
1264
+ };
1265
+ return {
1266
+ flush,
1267
+ add(nextFace, nextSize, gx, gy, code) {
1268
+ const unit = nextSize * horizontal;
1269
+ const continues = face2 === nextFace && size === nextSize && baselineY === gy && unit !== 0;
1270
+ const adjustment = continues ? 1e3 * (penX - gx) / unit : Number.NaN;
1271
+ if (!Number.isFinite(adjustment) || Math.abs(adjustment) > MAX_TJ_ADJUSTMENT) {
1272
+ flush();
1273
+ face2 = nextFace;
1274
+ size = nextSize;
1275
+ baselineY = gy;
1276
+ startX = gx;
1277
+ penX = gx;
1278
+ } else if (gx !== penX) {
1279
+ const written = number(adjustment);
1280
+ parts.push(written);
1281
+ penX -= Number(written) / 1e3 * unit;
1282
+ }
1283
+ parts.push(`<${code}>`);
1284
+ penX += nextFace.declaredWidth(code) / 1e3 * unit;
1285
+ }
1286
+ };
1287
+ }
1288
+ var TextWriter = class {
1289
+ constructor(doc, session, work, showRevisionMarkup) {
1290
+ this.doc = doc;
1291
+ this.session = session;
1292
+ this.work = work;
1293
+ this.showRevisionMarkup = showRevisionMarkup;
1294
+ }
1295
+ doc;
1296
+ session;
1297
+ work;
1298
+ showRevisionMarkup;
1299
+ faces = /* @__PURE__ */ new Map();
1300
+ refused = /* @__PURE__ */ new Map();
1301
+ paint(visit, page) {
1302
+ const { span, line, storyOrigin, absoluteBox } = visit;
1303
+ if (span.style.hidden || !span.text) return "";
1304
+ if (span.noteSeparator) {
1305
+ const box = (0, import_layout4.noteSeparatorRuleBox)(span, line);
1306
+ return `${color(span.style.color)} rg ${rect(box, storyOrigin.x - visit.page.box.x, storyOrigin.y - visit.page.box.y, pageHeight(page), true)} f`;
1307
+ }
1308
+ if (/^[\t\n\r\f]+$/.test(span.text)) return span.tabLeader ? this.leader(visit, page) : "";
1309
+ const report = (code, message) => {
1310
+ this.work.report(code, message, visit.page.index);
1311
+ return "";
1312
+ };
1313
+ const shaped = this.session.shapeLaidOutText(span);
1314
+ const family = (0, import_layout4.styleForFontSlot)(span.style, span.fontSlot).fontFamily ?? "the default font";
1315
+ if (!shaped)
1316
+ return report(
1317
+ "unshaped-text",
1318
+ `Core could not provide exact shaping for a visible span in ${family}`
1319
+ );
1320
+ if (shaped.run.glyphs.some((g) => g.id === 0))
1321
+ return report("missing-glyph", `A visible span in ${family} contains missing glyphs`);
1322
+ const face2 = this.embeddedFace(shaped.font, visit, page);
1323
+ if (!face2) return "";
1324
+ const style = (0, import_layout4.styleForFontSlot)(span.style, span.fontSlot);
1325
+ const factor = (0, import_layout4.glyphSizeFactorOf)(style);
1326
+ const size = Number(
1327
+ (Math.max(
1328
+ 1,
1329
+ Math.round(reducedHalfPoints(style.fontSizePt, factor) / 2 / PDF_PAINT_GRID_PT2)
1330
+ ) * PDF_PAINT_GRID_PT2).toFixed(6)
1331
+ );
1332
+ const horizontal = style.horizontalScalePercent / 100;
1333
+ const scale = factor / shaped.fixedPointScale;
1334
+ const x = absoluteBox.x - visit.page.box.x + (span.glyphOffsetPt ?? 0) + paragraphGridOffsetX(visit);
1335
+ const baselineFromTop = storyOrigin.y - visit.page.box.y + line.box.y + griddedBaselineInLine(line, face2, visit.paragraph) - (0, import_layout4.baselineShiftPtOf)(style);
1336
+ const unitsFromTop = snapToGrid(baselineFromTop / PDF_PAINT_GRID_PT2);
1337
+ const baseline = pageHeight(page) - Math.ceil(unitsFromTop - 0.5) * PDF_PAINT_GRID_PT2;
1338
+ let foreground = style.color;
1339
+ const revisions = this.showRevisionMarkup ? span.revisions ?? [] : [];
1340
+ const insert = revisions.some((r) => r.kind === "insert" || r.kind === "moveTo");
1341
+ const deleted = revisions.some((r) => r.kind === "delete" || r.kind === "moveFrom");
1342
+ if (insert) foreground = "008000";
1343
+ if (deleted) foreground = "C00000";
1344
+ const out = [`${color(foreground)} rg`, "BT"];
1345
+ if (!face2.colorLayers) out.push(`/${face2.name} ${number(size)} Tf`);
1346
+ if (style.textOutline)
1347
+ out.unshift(`q ${color(style.textOutline.color)} RG ${number(style.textOutline.widthPt)} w 2 Tr`);
1348
+ const origins = penOrigins(shaped.run);
1349
+ const batch = createGlyphBatch(out, horizontal);
1350
+ const colorGlyphs = [];
1351
+ let extra = 0;
1352
+ let activeSize = size;
1353
+ let activeFace = face2;
1354
+ let activeIdentity = shaped.font.identity;
1355
+ for (const cluster of [...shaped.run.clusters].sort((a, b) => a.glyphStart - b.glyphStart)) {
1356
+ const identity = shaped.fonts?.[cluster.fontSpan];
1357
+ if (identity && identity.identity !== activeIdentity) {
1358
+ const clusterFace = this.embeddedFace(identity, visit, page);
1359
+ if (!clusterFace) return "";
1360
+ activeFace = clusterFace;
1361
+ activeIdentity = identity.identity;
1362
+ batch.flush();
1363
+ if (!activeFace.colorLayers) out.push(`/${activeFace.name} ${number(activeSize)} Tf`);
1364
+ }
1365
+ const text = shaped.run.text.slice(cluster.textStart, cluster.textEnd);
1366
+ for (let i = cluster.glyphStart; i < cluster.glyphEnd; i++) {
1367
+ this.work.tick();
1368
+ const glyph = shaped.run.glyphs[i];
1369
+ const glyphSize = size * (glyph.drawScale ?? 1);
1370
+ if (glyphSize !== activeSize) {
1371
+ batch.flush();
1372
+ if (!activeFace.colorLayers) out.push(`/${activeFace.name} ${number(glyphSize)} Tf`);
1373
+ activeSize = glyphSize;
1374
+ }
1375
+ const origin = origins?.[i] ?? glyph.originX;
1376
+ const gx = x + (origin + glyph.offsetX) * scale * horizontal + extra;
1377
+ const gy = baseline + (glyph.originY + glyph.offsetY) * scale;
1378
+ if (activeFace.colorLayers) {
1379
+ const color2 = colorGlyph(activeFace.font, glyph.id, this.work);
1380
+ if (color2.kind === "refused" || color2.kind === "none" && color2.outlined) {
1381
+ this.work.report(
1382
+ "color-glyph",
1383
+ color2.kind === "refused" ? `A color glyph could not be painted: ${color2.reason}` : "A glyph of a color face has no color layers and the face is not embedded",
1384
+ visit.page.index
1385
+ );
1386
+ continue;
1387
+ }
1388
+ if (color2.kind === "layers")
1389
+ colorGlyphs.push({
1390
+ layers: color2.layers,
1391
+ x: gx,
1392
+ y: gy,
1393
+ scale: glyphSize / activeFace.font.unitsPerEm
1394
+ });
1395
+ const host = i === cluster.glyphStart && text ? this.textHost(face2, page) : null;
1396
+ const carrier = host?.carrier(text) ?? null;
1397
+ if (host && carrier) {
1398
+ let advance = 0;
1399
+ for (let j = cluster.glyphStart; j < cluster.glyphEnd; j++)
1400
+ advance += shaped.run.glyphs[j].advanceX * scale;
1401
+ const spaceWidth = host.declaredWidth(carrier) / 1e3 * activeSize;
1402
+ const stretch = spaceWidth > 0 && advance > 0 ? Math.min(1e4, Math.max(1, 100 * advance / spaceWidth)) : 100;
1403
+ batch.flush();
1404
+ out.push(`/${host.name} ${number(activeSize)} Tf 3 Tr ${number(stretch)} Tz`);
1405
+ batch.add(host, activeSize, gx, gy, carrier);
1406
+ batch.flush();
1407
+ out.push("0 Tr 100 Tz");
1408
+ }
1409
+ continue;
1410
+ }
1411
+ const code = activeFace.encode(glyph.id, i === cluster.glyphStart ? text : "");
1412
+ batch.add(activeFace, activeSize, gx, gy, code);
1413
+ }
1414
+ extra += text.length * style.characterSpacingPt + (text.match(/ /g)?.length ?? 0) * (style.shaping?.wordSpacingPt ?? 0);
1415
+ }
1416
+ batch.flush();
1417
+ out.push("ET");
1418
+ if (style.textOutline) out.push("Q");
1419
+ for (const { layers, x: gx, y: gy, scale: glyphScale } of colorGlyphs) {
1420
+ out.push(`q ${number(glyphScale * horizontal)} 0 0 ${number(glyphScale)} ${number(gx)} ${number(gy)} cm`);
1421
+ for (const layer of layers) {
1422
+ const alpha = layer.alpha < 1 ? this.alphaState(page, layer.alpha) : null;
1423
+ out.push(
1424
+ alpha ? `q /${alpha} gs ${layer.fill} ${layer.path} f Q` : `${layer.fill} ${layer.path} f`
1425
+ );
1426
+ }
1427
+ out.push("Q");
1428
+ }
1429
+ const metricScale = size / face2.font.unitsPerEm;
1430
+ const onGrid2 = (value) => Math.round(value / PDF_PAINT_GRID_PT2) * PDF_PAINT_GRID_PT2;
1431
+ const lineRule = (y, thickness, c = foreground, width = absoluteBox.width) => {
1432
+ const left = onGrid2(x);
1433
+ const right = onGrid2(x + width);
1434
+ const painted = right > left ? right - left : width > 0 ? PDF_PAINT_GRID_PT2 : 0;
1435
+ if (painted <= 0) return;
1436
+ out.push(
1437
+ `${color(c)} rg ${number(left)} ${number(y - thickness / 2)} ${number(painted)} ${number(thickness)} re f`
1438
+ );
1439
+ };
1440
+ if (style.underline || insert) {
1441
+ const variant = style.underline?.variant ?? "single";
1442
+ const metricsUsable = face2.font.underlineThickness > 0 && Number.isFinite(face2.font.underlinePosition);
1443
+ if (!metricsUsable)
1444
+ this.work.report(
1445
+ "underline-metrics",
1446
+ "Font has no valid underline metrics; the underline uses a default position and thickness",
1447
+ visit.page.index,
1448
+ "information"
1449
+ );
1450
+ const rawThickness = metricsUsable ? face2.font.underlineThickness * metricScale : size / 20;
1451
+ const rawOffset = metricsUsable ? -face2.font.underlinePosition * metricScale : size / 10;
1452
+ const thickness = Math.max(1, Math.round(rawThickness / PDF_PAINT_GRID_PT2)) * PDF_PAINT_GRID_PT2;
1453
+ const position = baseline - Math.round(rawOffset / PDF_PAINT_GRID_PT2) * PDF_PAINT_GRID_PT2 - thickness / 2;
1454
+ {
1455
+ const c = style.underline?.color ?? foreground;
1456
+ const supported = [
1457
+ "single",
1458
+ "double",
1459
+ "thick",
1460
+ "dotted",
1461
+ "dash",
1462
+ "dashLong",
1463
+ "dotDash",
1464
+ "dotDotDash",
1465
+ "wave",
1466
+ "wavyDouble",
1467
+ "wavyHeavy",
1468
+ "dottedHeavy",
1469
+ "dashedHeavy",
1470
+ "dashLongHeavy",
1471
+ "dashDotHeavy",
1472
+ "dashDotDotHeavy",
1473
+ "words"
1474
+ ];
1475
+ if (!supported.includes(variant))
1476
+ this.work.report(
1477
+ "underline-style",
1478
+ `Unsupported underline: ${variant}`,
1479
+ visit.page.index
1480
+ );
1481
+ const heavy = variant === "thick" || variant.endsWith("Heavy");
1482
+ const weight = thickness * (heavy ? 2 : 1);
1483
+ if (variant === "wave" || variant.startsWith("wavy")) {
1484
+ const wave = (y) => {
1485
+ const period = Math.max(2, weight * 6), amplitude = Math.max(0.6, weight);
1486
+ const path = [`${number(x)} ${number(y)} m`];
1487
+ for (let dx = 0; dx < absoluteBox.width; dx += period) {
1488
+ this.work.tick();
1489
+ const end = Math.min(dx + period, absoluteBox.width), length = end - dx;
1490
+ path.push(
1491
+ `${number(x + dx + length / 4)} ${number(y + amplitude)} ${number(x + dx + length / 4)} ${number(y + amplitude)} ${number(x + dx + length / 2)} ${number(y)} c`,
1492
+ `${number(x + dx + 3 * length / 4)} ${number(y - amplitude)} ${number(x + dx + 3 * length / 4)} ${number(y - amplitude)} ${number(x + end)} ${number(y)} c`
1493
+ );
1494
+ }
1495
+ out.push(`q ${color(c)} RG ${number(weight)} w ${path.join(" ")} S Q`);
1496
+ };
1497
+ wave(position);
1498
+ if (variant === "wavyDouble") wave(position - weight * 4);
1499
+ } else if (variant.startsWith("dot") || variant.toLowerCase().includes("dash")) {
1500
+ const dotted = variant.startsWith("dotted");
1501
+ const pattern = dotted ? [weight, weight * 2] : variant.toLowerCase().includes("dotdot") ? [weight * 4, weight * 2, weight, weight * 2, weight, weight * 2] : variant.toLowerCase().includes("dot") ? [weight * 4, weight * 2, weight, weight * 2] : [weight * (variant.includes("Long") ? 8 : 4), weight * 2];
1502
+ out.push(
1503
+ `q ${color(c)} RG ${number(weight)} w [${pattern.map(number).join(" ")}] 0 d ${number(x)} ${number(position)} m ${number(x + absoluteBox.width)} ${number(position)} l S Q`
1504
+ );
1505
+ } else if (variant === "words") {
1506
+ for (const cluster of shaped.run.clusters) {
1507
+ if (/^\s+$/.test(shaped.run.text.slice(cluster.textStart, cluster.textEnd))) continue;
1508
+ const glyph = shaped.run.glyphs[cluster.glyphStart];
1509
+ out.push(
1510
+ `${color(c)} rg ${number(x + glyph.originX * scale * horizontal)} ${number(position - thickness / 2)} ${number(cluster.advance * scale * horizontal)} ${number(thickness)} re f`
1511
+ );
1512
+ }
1513
+ } else {
1514
+ lineRule(
1515
+ position,
1516
+ weight,
1517
+ c,
1518
+ absoluteBox.width + (variant === "single" ? underlineGap(visit) : 0)
1519
+ );
1520
+ if (variant === "double") lineRule(position - thickness * 2, thickness, c);
1521
+ }
1522
+ }
1523
+ }
1524
+ if (style.strike || style.doubleStrike || deleted) {
1525
+ const declared = face2.strike && face2.strike.thickness > 0 ? face2.strike : null;
1526
+ if (!declared)
1527
+ this.work.report(
1528
+ "strike-metrics",
1529
+ "Font has no strike metrics; the strike is placed from the ascent",
1530
+ visit.page.index,
1531
+ "information"
1532
+ );
1533
+ const rawThickness = (declared?.thickness ?? face2.font.underlineThickness) * metricScale;
1534
+ const rawOffset = declared ? declared.position * metricScale : Math.abs(face2.font.ascent) * 0.3 * metricScale;
1535
+ const thickness = Math.max(1, Math.round(rawThickness / PDF_PAINT_GRID_PT2)) * PDF_PAINT_GRID_PT2;
1536
+ const position = baseline + Math.round(rawOffset / PDF_PAINT_GRID_PT2) * PDF_PAINT_GRID_PT2;
1537
+ lineRule(position, thickness);
1538
+ if (style.doubleStrike) lineRule(position + thickness * 2, thickness);
1539
+ }
1540
+ return out.join("\n");
1541
+ }
1542
+ /**
1543
+ * The band a highlight or character shading fills: the span's own face box, seated on the
1544
+ * line's baseline.
1545
+ *
1546
+ * Layout publishes every span box at the LINE top with the span's own face height. That is
1547
+ * the selection band, and the screen painter seats it on the baseline with
1548
+ * `vertical-align`. Filling the box where it is published puts a small highlighted run at
1549
+ * the top of a line a larger run made tall, with its glyphs hanging out under the band.
1550
+ * Word draws the band around the run's own face, ascent and external leading above the
1551
+ * baseline and descent below, so a mixed-size line highlights stepped: each run at its own
1552
+ * height, all seated on one baseline. A raised or lowered script carries its band with it.
1553
+ *
1554
+ * The split above and below the baseline comes from the face when it is admitted, and from
1555
+ * the line's own glyph band otherwise, so a span this writer cannot shape (a tab, a face it
1556
+ * refused) still lands on the baseline instead of at the line top.
1557
+ */
1558
+ bandBox(visit) {
1559
+ const { span, line, absoluteBox } = visit;
1560
+ const shaped = this.session.shapeLaidOutText(span);
1561
+ const face2 = shaped ? this.admittedFace(shaped.font, visit) : void 0;
1562
+ const leading = line.leading ?? 0;
1563
+ const above = face2 ? Math.abs(face2.font.ascent) + Math.max(0, face2.font.lineGap) : line.baseline - leading;
1564
+ const total = face2 ? above + Math.abs(face2.font.descent) : line.box.height - leading - (line.trailingSpacing ?? 0);
1565
+ const faceBaseline = total > 0 ? span.box.height * above / total : span.box.height;
1566
+ const top = line.box.y + line.baseline - faceBaseline - (0, import_layout4.baselineShiftPtOf)((0, import_layout4.styleForFontSlot)(span.style, span.fontSlot));
1567
+ return { ...absoluteBox, y: absoluteBox.y + (top - span.box.y) };
1568
+ }
1569
+ alphaStates = /* @__PURE__ */ new Map();
1570
+ /** A registered constant-alpha graphics state, shared across layers of the same alpha. */
1571
+ alphaState(page, alpha) {
1572
+ const key = Math.round(alpha * 1e3);
1573
+ let ref = this.alphaStates.get(key);
1574
+ if (!ref) {
1575
+ ref = this.doc.context.register(
1576
+ this.doc.context.obj({ Type: "ExtGState", ca: key / 1e3, CA: key / 1e3 })
1577
+ );
1578
+ this.alphaStates.set(key, ref);
1579
+ }
1580
+ const name = `LayerAlpha${key}`;
1581
+ page.node.setExtGState(import_pdf_lib2.PDFName.of(name), ref);
1582
+ return name;
1583
+ }
1584
+ /** An embedded text face on this page to carry extractable characters: the run's own, or any. */
1585
+ textHost(face2, page) {
1586
+ const host = face2.colorLayers ? [...this.faces.values()].find((candidate) => !candidate.colorLayers) ?? null : face2;
1587
+ if (host) this.registerFont(page, host);
1588
+ return host;
1589
+ }
1590
+ embeddedFace(font, visit, page) {
1591
+ const face2 = this.admittedFace(font, visit);
1592
+ if (face2 && !face2.colorLayers) this.registerFont(page, face2);
1593
+ return face2;
1594
+ }
1595
+ pageFonts = /* @__PURE__ */ new WeakMap();
1596
+ /**
1597
+ * Name a face in the page's font resources once. pdf-lib re-reads the page's inherited
1598
+ * resources on every call, and nearly every span on a page names a face already there.
1599
+ */
1600
+ registerFont(page, face2) {
1601
+ let registered = this.pageFonts.get(page);
1602
+ if (!registered) this.pageFonts.set(page, registered = /* @__PURE__ */ new Set());
1603
+ if (registered.has(face2)) return;
1604
+ registered.add(face2);
1605
+ page.node.setFontDictionary(import_pdf_lib2.PDFName.of(face2.name), face2.ref);
1606
+ }
1607
+ /** The embedded face for a shaped font, without registering it on a page. */
1608
+ admittedFace(font, visit) {
1609
+ const report = (code, message) => {
1610
+ this.work.report(code, message, visit.page.index);
1611
+ return void 0;
1612
+ };
1613
+ let face2 = this.faces.get(font.identity);
1614
+ try {
1615
+ if (this.refused.has(font.identity))
1616
+ return report("font-embedding", this.refused.get(font.identity));
1617
+ if (!face2) {
1618
+ const admitted = this.session.admittedFontFace(font.request);
1619
+ if (!admitted || admitted.identity !== font.identity)
1620
+ return report("font-identity", "The shaped face does not match the admitted font");
1621
+ face2 = new EmbeddedFace(this.doc, admitted, this.faces.size + 1);
1622
+ this.faces.set(font.identity, face2);
1623
+ }
1624
+ } catch (error) {
1625
+ const reason = error instanceof Error ? error.message : "Font embedding refused";
1626
+ this.refused.set(font.identity, reason);
1627
+ return report("font-embedding", reason);
1628
+ }
1629
+ return face2;
1630
+ }
1631
+ leader(visit, page) {
1632
+ const glyph = import_layout4.TAB_LEADER_GLYPH.get(visit.span.tabLeader);
1633
+ if (!glyph || visit.absoluteBox.width <= 0) return "";
1634
+ const advance = visit.span.tabLeaderAdvancePt;
1635
+ if (!advance || advance <= 0) {
1636
+ this.work.report(
1637
+ "tab-leader-metrics",
1638
+ "Tab leader has no measured advance",
1639
+ visit.page.index
1640
+ );
1641
+ return "";
1642
+ }
1643
+ const x = visit.absoluteBox.x - visit.page.box.x;
1644
+ const pattern = (0, import_layout4.tabLeaderPattern)(
1645
+ visit.textboxDepth ? visit.span.box.x : x,
1646
+ visit.absoluteBox.width,
1647
+ advance
1648
+ );
1649
+ const y = pageHeight(page) - (visit.storyOrigin.y + visit.line.box.y - visit.page.box.y) - visit.line.box.height;
1650
+ const out = [
1651
+ `q ${number(x)} ${number(y)} ${number(visit.absoluteBox.width)} ${number(visit.line.box.height)} re W n`
1652
+ ];
1653
+ for (let i = 0; i < pattern.count; i++) {
1654
+ this.work.tick();
1655
+ out.push(
1656
+ this.paint(
1657
+ {
1658
+ ...visit,
1659
+ absoluteBox: {
1660
+ ...visit.absoluteBox,
1661
+ x: visit.absoluteBox.x + pattern.offsetPt + i * advance,
1662
+ width: advance
1663
+ },
1664
+ span: {
1665
+ ...visit.span,
1666
+ text: glyph,
1667
+ tabLeader: void 0,
1668
+ style: {
1669
+ ...visit.span.style,
1670
+ characterSpacingPt: 0,
1671
+ underline: null,
1672
+ bold: visit.span.tabLeader === "heavy" || visit.span.style.bold
1673
+ }
1674
+ }
1675
+ },
1676
+ page
1677
+ )
1678
+ );
1679
+ }
1680
+ out.push("Q");
1681
+ return out.join("\n");
1682
+ }
1683
+ };
1684
+
1685
+ // src/annotations.ts
1686
+ var import_pdf_lib3 = require("pdf-lib");
1687
+ function add(doc, page, dictionary) {
1688
+ const ref = doc.context.register(doc.context.obj(dictionary));
1689
+ page.node.addAnnot(ref);
1690
+ return ref;
1691
+ }
1692
+ function linkAnnotation(doc, page, visit, destinations2) {
1693
+ const link = visit.span.link;
1694
+ if (!link || !link.href) return;
1695
+ const b = visit.absoluteBox;
1696
+ const x = b.x - visit.page.box.x, y = pageHeight(page) - (b.y - visit.page.box.y) - b.height;
1697
+ const base = {
1698
+ Type: "Annot",
1699
+ Subtype: "Link",
1700
+ Rect: [x, y, x + b.width, y + b.height],
1701
+ Border: [0, 0, 0]
1702
+ };
1703
+ if (link.kind === "internal" && link.anchor && destinations2.has(link.anchor))
1704
+ add(doc, page, { ...base, Dest: import_pdf_lib3.PDFHexString.fromText(link.anchor) });
1705
+ else if (link.kind === "external" && /^(https?:|mailto:|tel:|ftp:)/i.test(link.href)) {
1706
+ const uri = pdfLiteralUri(link.href);
1707
+ if (uri !== null) add(doc, page, { ...base, A: { S: "URI", URI: import_pdf_lib3.PDFString.of(uri) } });
1708
+ }
1709
+ }
1710
+ function destinations(doc, pages, layout) {
1711
+ const names = [];
1712
+ const added = /* @__PURE__ */ new Set();
1713
+ for (const dest of [...layout.destinations ?? []].sort(
1714
+ (a, b) => import_pdf_lib3.PDFHexString.fromText(a.anchor.name).asString() < import_pdf_lib3.PDFHexString.fromText(b.anchor.name).asString() ? -1 : 1
1715
+ )) {
1716
+ if (added.has(dest.anchor.name)) continue;
1717
+ const page = pages[dest.pageIndex], record = layout.pages[dest.pageIndex];
1718
+ if (!page || !record) continue;
1719
+ added.add(dest.anchor.name);
1720
+ names.push(
1721
+ import_pdf_lib3.PDFHexString.fromText(dest.anchor.name),
1722
+ doc.context.obj([
1723
+ page.ref,
1724
+ "XYZ",
1725
+ dest.pageStack.x - record.box.x,
1726
+ pageHeight(page) - dest.pageStack.y + record.box.y,
1727
+ null
1728
+ ])
1729
+ );
1730
+ }
1731
+ if (names.length)
1732
+ doc.catalog.set(import_pdf_lib3.PDFName.of("Names"), doc.context.obj({ Dests: { Names: names } }));
1733
+ return added;
1734
+ }
1735
+ function comments(doc, pages, layout, work) {
1736
+ const primary = /* @__PURE__ */ new Map();
1737
+ const replies = [];
1738
+ let count = 0;
1739
+ for (const comment of layout.reviewArtifacts) {
1740
+ if (comment.kind !== "comment") continue;
1741
+ const occurrences = comment.occurrences.filter((o) => o.geometry?.pageStack.length);
1742
+ const targets = occurrences.length ? occurrences : [null];
1743
+ for (const occurrence of targets) {
1744
+ work.tick();
1745
+ if (++count > 5e4) throw new RangeError("PDF annotation limit exceeded");
1746
+ const pageIndex = occurrence?.pageIndex ?? 0;
1747
+ const page = pages[pageIndex], record = layout.pages[pageIndex];
1748
+ if (!page || !record) continue;
1749
+ const boxes = occurrence?.geometry?.pageStack ?? [];
1750
+ const quads = [];
1751
+ for (const b2 of boxes) {
1752
+ const x2 = b2.x - record.box.x, top = pageHeight(page) - b2.y + record.box.y;
1753
+ if (b2.width > 0)
1754
+ quads.push(x2, top, x2 + b2.width, top, x2, top - b2.height, x2 + b2.width, top - b2.height);
1755
+ }
1756
+ const b = boxes[0];
1757
+ const x = b ? b.x - record.box.x : 12;
1758
+ const y = b ? pageHeight(page) - b.y + record.box.y : pageHeight(page) - 12;
1759
+ const xs = quads.filter((_, i) => i % 2 === 0), ys = quads.filter((_, i) => i % 2 === 1);
1760
+ const min = (a) => a.reduce((v, n) => Math.min(v, n), Infinity);
1761
+ const max = (a) => a.reduce((v, n) => Math.max(v, n), -Infinity);
1762
+ const rect2 = quads.length ? [min(xs), min(ys), max(xs), max(ys)] : [x, y - 18, x + 18, y];
1763
+ const ref = add(doc, page, {
1764
+ Type: "Annot",
1765
+ Subtype: quads.length ? "Highlight" : "Text",
1766
+ Rect: rect2,
1767
+ ...quads.length ? { QuadPoints: quads } : { Name: "Comment" },
1768
+ T: import_pdf_lib3.PDFHexString.fromText(comment.author),
1769
+ Contents: import_pdf_lib3.PDFHexString.fromText(
1770
+ `${occurrence ? "" : "[Unanchored comment]\n"}${comment.text}`
1771
+ ),
1772
+ NM: import_pdf_lib3.PDFHexString.fromText(`comment-${comment.id}-${count}`),
1773
+ C: [1, 0.85, 0.2],
1774
+ CA: 0.25,
1775
+ ...comment.date && Number.isFinite(Date.parse(comment.date)) ? {
1776
+ M: import_pdf_lib3.PDFHexString.fromText(
1777
+ `D:${new Date(comment.date).toISOString().replace(/[-:T]/g, "").slice(0, 14)}Z`
1778
+ )
1779
+ } : {},
1780
+ ...comment.resolved ? { StateModel: "Review", State: "Completed" } : {},
1781
+ F: 4
1782
+ });
1783
+ if (!primary.has(comment.id)) primary.set(comment.id, ref);
1784
+ if (comment.parentId) replies.push({ parent: comment.parentId, ref });
1785
+ if (!occurrence)
1786
+ work.report(
1787
+ "unanchored-comment",
1788
+ "A comment without visible geometry is preserved as a first-page note",
1789
+ 0,
1790
+ "information"
1791
+ );
1792
+ }
1793
+ }
1794
+ for (const reply of replies) {
1795
+ const parent = primary.get(reply.parent);
1796
+ if (parent) {
1797
+ const dictionary = doc.context.lookup(reply.ref);
1798
+ if (dictionary instanceof import_pdf_lib3.PDFDict) {
1799
+ dictionary.set(import_pdf_lib3.PDFName.of("IRT"), parent);
1800
+ dictionary.set(import_pdf_lib3.PDFName.of("RT"), import_pdf_lib3.PDFName.of("R"));
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+
1806
+ // src/images.ts
1807
+ var import_pdf_lib5 = require("pdf-lib");
1808
+
1809
+ // src/vector-shapes.ts
1810
+ var import_pdf_lib4 = require("pdf-lib");
1811
+ function paintVectorShape(doc, page, visit, work) {
1812
+ const d = visit.drawing;
1813
+ const shape = d.vectorShape;
1814
+ const bounds = visit.absolutePaintBounds;
1815
+ if (bounds.width <= 0 || bounds.height <= 0 || d.accessibility.hidden) return "";
1816
+ const content = d.geometry.contentBounds;
1817
+ const x = visit.drawingOrigin.x - d.x - visit.page.box.x;
1818
+ const y = visit.drawingOrigin.y - d.y - visit.page.box.y;
1819
+ const unit = 12700;
1820
+ const sx = content.width / (shape.extentEmu.cx / unit);
1821
+ const sy = content.height / (shape.extentEmu.cy / unit);
1822
+ const out = new Commands(work);
1823
+ out.push(
1824
+ `q ${rect(bounds, -visit.page.box.x, -visit.page.box.y, pageHeight(page))} W n`,
1825
+ `${number(sx)} 0 0 ${number(-sy)} ${number(content.x + x)} ${number(pageHeight(page) - content.y - y)} cm`,
1826
+ "0 J 0 j 4 M [] 0 d"
1827
+ );
1828
+ const path = (points, closed) => {
1829
+ const commands = [];
1830
+ for (const [index, point] of points.entries()) {
1831
+ work.tick();
1832
+ commands.push(`${number(point.x / unit)} ${number(point.y / unit)} ${index === 0 ? "m" : "l"}`);
1833
+ }
1834
+ if (closed && points.length) commands.push("h");
1835
+ return commands.join(" ");
1836
+ };
1837
+ for (const component of shape.components) {
1838
+ work.tick();
1839
+ const state = doc.context.register(
1840
+ doc.context.obj({
1841
+ Type: "ExtGState",
1842
+ ca: component.fillAlpha,
1843
+ CA: component.strokeAlpha
1844
+ })
1845
+ );
1846
+ const stateName = `ShapeAlpha${state.objectNumber}`;
1847
+ page.node.setExtGState(import_pdf_lib4.PDFName.of(stateName), state);
1848
+ out.push(`q /${stateName} gs`);
1849
+ if (component.fillHex !== null) out.push(`${color(component.fillHex)} rg`);
1850
+ if (component.strokeHex !== null)
1851
+ out.push(
1852
+ `${color(component.strokeHex)} RG ${number(Math.max(1, component.strokeWidthEmu) / unit)} w`
1853
+ );
1854
+ for (const [index, points] of component.subpathsEmu.entries())
1855
+ out.push(path(points, component.subpathsClosed?.[index] !== false));
1856
+ out.push(
1857
+ component.fillHex !== null ? component.strokeHex !== null ? "B*" : "f*" : component.strokeHex !== null ? "S" : "n"
1858
+ );
1859
+ if (component.strokeHex !== null && component.arrowheadsEmu?.length) {
1860
+ const arrowState = doc.context.register(
1861
+ doc.context.obj({
1862
+ Type: "ExtGState",
1863
+ ca: component.strokeAlpha
1864
+ })
1865
+ );
1866
+ const arrowName = `ShapeAlpha${arrowState.objectNumber}`;
1867
+ page.node.setExtGState(import_pdf_lib4.PDFName.of(arrowName), arrowState);
1868
+ out.push(`/${arrowName} gs ${color(component.strokeHex)} rg`);
1869
+ for (const points of component.arrowheadsEmu) out.push(`${path(points, true)} f`);
1870
+ }
1871
+ out.push("Q");
1872
+ }
1873
+ out.push("Q");
1874
+ const uri = d.hyperlinkHref && /^(https?:|mailto:|tel:|ftp:)/i.test(d.hyperlinkHref) ? pdfLiteralUri(d.hyperlinkHref) : null;
1875
+ if (uri !== null) {
1876
+ const left = bounds.x - visit.page.box.x;
1877
+ const bottom = pageHeight(page) - bounds.y + visit.page.box.y - bounds.height;
1878
+ page.node.addAnnot(
1879
+ doc.context.register(
1880
+ doc.context.obj({
1881
+ Type: "Annot",
1882
+ Subtype: "Link",
1883
+ Rect: [left, bottom, left + bounds.width, bottom + bounds.height],
1884
+ Border: [0, 0, 0],
1885
+ A: { S: "URI", URI: import_pdf_lib4.PDFString.of(uri) }
1886
+ })
1887
+ )
1888
+ );
1889
+ }
1890
+ return out.join("\n");
1891
+ }
1892
+
1893
+ // src/images.ts
1894
+ var EMBEDDABLE_MIMES = /* @__PURE__ */ new Set(["image/png", "image/jpeg"]);
1895
+ function unpaintableBulletReason(picture, box) {
1896
+ const resource = picture.resource;
1897
+ if (resource.kind === "unrenderable") return `image refused: ${resource.reason}`;
1898
+ if (resource.kind !== "ready") return `image ${resource.kind}`;
1899
+ if (!EMBEDDABLE_MIMES.has(resource.mime)) return `format not embeddable: ${resource.mime}`;
1900
+ if (!(box.width > 0 && box.height > 0)) return "no visible area";
1901
+ return null;
1902
+ }
1903
+ var ImageWriter = class {
1904
+ constructor(doc, session, work) {
1905
+ this.doc = doc;
1906
+ this.session = session;
1907
+ this.work = work;
1908
+ }
1909
+ doc;
1910
+ session;
1911
+ work;
1912
+ images = /* @__PURE__ */ new Map();
1913
+ /**
1914
+ * Draw a list marker's picture bullet in the box layout published for it.
1915
+ *
1916
+ * Returns '' for anything not drawable — missing, external, still decoding, refused, or a
1917
+ * format this writer cannot embed — and the caller then paints the level's `w:lvlText`.
1918
+ *
1919
+ * That fall back is FOLLOWING the document, not approximating it: the same `w:lvl` authors
1920
+ * `w:lvlText` as this level's marker and `w:lvlPicBulletId` as a picture to use in its
1921
+ * place (ECMA-376 §17.9.12), so the text is the document's own alternative rather than
1922
+ * something this writer invented. It is reported all the same, at `information`: nothing
1923
+ * the document authors is lost from the page, so a strict export is still faithful, while
1924
+ * the reader is still told which of the two markers was drawn. An inline `w:drawing` whose
1925
+ * format cannot be embedded keeps reporting `unsupported`, because there the document
1926
+ * offers no alternative and the page really does lose content.
1927
+ *
1928
+ * No scaling decision is taken here: the box is the extent layout already placed on the
1929
+ * baseline, after the marker font scale.
1930
+ */
1931
+ async paintListMarkerPicture(picture, box, page, pageIndex) {
1932
+ await this.work.yield();
1933
+ const unpaintable = unpaintableBulletReason(picture, box);
1934
+ if (unpaintable !== null) {
1935
+ this.work.report(
1936
+ "list-picture-bullet",
1937
+ `Picture bullet drawn as its level text (${unpaintable})`,
1938
+ pageIndex,
1939
+ "information"
1940
+ );
1941
+ return "";
1942
+ }
1943
+ if (picture.resource.kind !== "ready") return "";
1944
+ const mime = picture.resource.mime;
1945
+ const bytes = this.session.validatedImageBytes(picture);
1946
+ if (!bytes) return "";
1947
+ let image = this.images.get(picture.resource.resourceKey);
1948
+ if (!image) {
1949
+ image = mime === "image/png" ? await this.doc.embedPng(bytes) : await this.doc.embedJpg(bytes);
1950
+ this.images.set(picture.resource.resourceKey, image);
1951
+ }
1952
+ const key = `Im${image.ref.objectNumber}`;
1953
+ page.node.setXObject(import_pdf_lib5.PDFName.of(key), image.ref);
1954
+ const x = box.x;
1955
+ const y = pageHeight(page) - box.y - box.height;
1956
+ return `q ${number(box.width)} 0 0 ${number(box.height)} ${number(x)} ${number(y)} cm /${key} Do Q`;
1957
+ }
1958
+ /**
1959
+ * A textbox: its fill and outline at the extent, then `body` (the story's decorations and
1960
+ * the text its owner collected) clipped to the content box, all clipped to the published
1961
+ * paint bounds — the same three boxes the screen painter draws, in the same order.
1962
+ *
1963
+ * The outline is stroked inside the extent, as a CSS border inside a `border-box` is, so a
1964
+ * bordered textbox is exactly as wide as Word laid it out. Word clips overflowing content
1965
+ * to the box as well, so a height clip is information: the page shows what Word shows.
1966
+ */
1967
+ paintTextbox(visit, page, body) {
1968
+ const d = visit.drawing;
1969
+ if (d.kind !== "anchoredDrawing" || !d.textboxStory) return "";
1970
+ const story = d.textboxStory;
1971
+ const bounds = visit.absolutePaintBounds;
1972
+ if (bounds.width <= 0 || bounds.height <= 0 || d.accessibility.hidden) return "";
1973
+ if (story.fallbackReason === "textbox-height-clip")
1974
+ this.work.report(
1975
+ "textbox-clip",
1976
+ "Textbox content taller than its box is clipped",
1977
+ visit.page.index,
1978
+ "information"
1979
+ );
1980
+ else if (story.fallbackReason) {
1981
+ this.work.report(
1982
+ "textbox",
1983
+ `Textbox story not laid out: ${story.fallbackReason}`,
1984
+ visit.page.index
1985
+ );
1986
+ return "";
1987
+ }
1988
+ const height = pageHeight(page);
1989
+ const x = visit.drawingOrigin.x - visit.page.box.x;
1990
+ const y = visit.drawingOrigin.y - visit.page.box.y;
1991
+ const extent = { x, y, width: d.width, height: d.height };
1992
+ const out = new Commands(this.work);
1993
+ out.push(`q ${rect(bounds, -visit.page.box.x, -visit.page.box.y, height)} W n`);
1994
+ if (story.fillHex !== null)
1995
+ out.push(`${color(story.fillHex)} rg ${rect(extent, 0, 0, height)} f`);
1996
+ if (story.strokeHex !== null) {
1997
+ const width = Math.max(0.5, story.strokeWidthPt);
1998
+ const inner = {
1999
+ x: x + width / 2,
2000
+ y: y + width / 2,
2001
+ width: Math.max(0, d.width - width),
2002
+ height: Math.max(0, d.height - width)
2003
+ };
2004
+ out.push(`${color(story.strokeHex)} RG ${number(width)} w ${rect(inner, 0, 0, height)} S`);
2005
+ }
2006
+ const content = {
2007
+ x: x + story.contentOffset.x,
2008
+ y: y + story.contentOffset.y,
2009
+ width: story.contentWidth,
2010
+ height: Math.max(0, story.contentHeight)
2011
+ };
2012
+ out.push(`q ${rect(content, 0, 0, height)} W n`, ...body, "Q", "Q");
2013
+ return out.join("\n");
2014
+ }
2015
+ async paint(visit, page) {
2016
+ await this.work.yield();
2017
+ const d = visit.drawing;
2018
+ const report = (message) => {
2019
+ this.work.report("drawing", message, visit.page.index);
2020
+ return "";
2021
+ };
2022
+ if (d.vectorShape) return paintVectorShape(this.doc, page, visit, this.work);
2023
+ if (d.kind === "anchoredDrawing" && d.textboxStory)
2024
+ return report("Textbox story not routed through paintTextbox");
2025
+ if (d.placeholderGraphicKind) return report(`Unsupported drawing: ${d.placeholderGraphicKind}`);
2026
+ const bytes = this.session.validatedImageBytes(d);
2027
+ if (!bytes || d.resource.kind !== "ready") return report("Image has no validated raster bytes");
2028
+ const mime = d.resource.mime;
2029
+ if (!EMBEDDABLE_MIMES.has(mime)) return report(`Unsupported PDF image format: ${mime}`);
2030
+ if (d.effects.grayscale || d.effects.brightness || d.effects.contrast || d.effects.bilevel !== void 0)
2031
+ this.work.report(
2032
+ "image-effects",
2033
+ "Image color adjustments are not encoded",
2034
+ visit.page.index
2035
+ );
2036
+ let image = this.images.get(d.resource.resourceKey);
2037
+ if (!image) {
2038
+ image = mime === "image/png" ? await this.doc.embedPng(bytes) : await this.doc.embedJpg(bytes);
2039
+ this.images.set(d.resource.resourceKey, image);
2040
+ }
2041
+ let opacityCommand = "";
2042
+ if (d.effects.opacity !== void 0 && d.effects.opacity !== 1) {
2043
+ const opacity = d.effects.opacity;
2044
+ if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1)
2045
+ return report("Invalid image opacity");
2046
+ const name = `Alpha${Math.round(opacity * 1e5)}`;
2047
+ const ref = this.doc.context.register(
2048
+ this.doc.context.obj({ Type: "ExtGState", ca: opacity, CA: opacity })
2049
+ );
2050
+ page.node.setExtGState(import_pdf_lib5.PDFName.of(name), ref);
2051
+ opacityCommand = `/${name} gs `;
2052
+ }
2053
+ const key = `Im${image.ref.objectNumber}`;
2054
+ page.node.setXObject(import_pdf_lib5.PDFName.of(key), image.ref);
2055
+ const points = d.geometry.imageTransformCorners ?? d.geometry.transformedCorners;
2056
+ if (points.length !== 4) return report("Image has no affine rectangle geometry");
2057
+ if (d.geometry.clipFallback !== "none")
2058
+ this.work.report("image-clip", "Image clipping uses a Core fallback", visit.page.index);
2059
+ const offsetX = visit.drawingOrigin.x - d.x - visit.page.box.x;
2060
+ const offsetY = visit.drawingOrigin.y - d.y - visit.page.box.y;
2061
+ const p = points.map((v) => ({ x: v.x + offsetX, y: pageHeight(page) - v.y - offsetY }));
2062
+ const topLeft = p[0], topRight = p[1], bottomLeft = p[3];
2063
+ const ax = topRight.x - topLeft.x, ay = topRight.y - topLeft.y;
2064
+ const bx = topLeft.x - bottomLeft.x, by = topLeft.y - bottomLeft.y;
2065
+ const crop = d.crop;
2066
+ const width = 1 - crop.left - crop.right, height = 1 - crop.top - crop.bottom;
2067
+ if (!(width > 0 && height > 0)) return report("Image crop has no visible area");
2068
+ if (d.geometry.clipPolygon && d.geometry.clipPolygon.length === 0) return "";
2069
+ const clip = d.geometry.clipPolygon?.length ? d.geometry.clipPolygon.map((v) => ({
2070
+ x: v.x + offsetX,
2071
+ y: pageHeight(page) - v.y - offsetY
2072
+ })) : p;
2073
+ const uri = d.hyperlinkHref && /^(https?:|mailto:|tel:|ftp:)/i.test(d.hyperlinkHref) ? pdfLiteralUri(d.hyperlinkHref) : null;
2074
+ if (uri !== null) {
2075
+ const bounds = [Infinity, Infinity, -Infinity, -Infinity];
2076
+ for (const point of clip) {
2077
+ bounds[0] = Math.min(bounds[0], point.x);
2078
+ bounds[1] = Math.min(bounds[1], point.y);
2079
+ bounds[2] = Math.max(bounds[2], point.x);
2080
+ bounds[3] = Math.max(bounds[3], point.y);
2081
+ }
2082
+ page.node.addAnnot(
2083
+ this.doc.context.register(
2084
+ this.doc.context.obj({
2085
+ Type: "Annot",
2086
+ Subtype: "Link",
2087
+ Rect: bounds,
2088
+ Border: [0, 0, 0],
2089
+ A: { S: "URI", URI: import_pdf_lib5.PDFString.of(uri) }
2090
+ })
2091
+ )
2092
+ );
2093
+ }
2094
+ const path = clip.map((v, i) => `${number(v.x)} ${number(v.y)} ${i === 0 ? "m" : "l"}`).join(" ");
2095
+ return `q ${opacityCommand}${path} h W n ${number(ax / width)} ${number(ay / width)} ${number(bx / height)} ${number(by / height)} ${number(bottomLeft.x - ax * crop.left / width - bx * crop.bottom / height)} ${number(bottomLeft.y - ay * crop.left / width - by * crop.bottom / height)} cm /${key} Do Q`;
2096
+ }
2097
+ };
2098
+
2099
+ // src/equations.ts
2100
+ function paintEquation(visit, page, text, work) {
2101
+ const equation = visit.span.equation;
2102
+ if (equation.truncated)
2103
+ work.report("equation-truncated", "Core truncated an equation", visit.page.index);
2104
+ const top = visit.storyOrigin.y + visit.line.box.y + visit.line.baseline - equation.geometry.baseline;
2105
+ const out = [];
2106
+ function node(g, parentX, parentY) {
2107
+ work.tick();
2108
+ const x = parentX + g.box.x, y = parentY + g.box.y;
2109
+ if (g.kind === "text" || g.kind === "fallback") {
2110
+ if (g.kind === "fallback")
2111
+ work.report("equation-fallback", "Core uses fallback equation text", visit.page.index);
2112
+ out.push(
2113
+ text.paint(
2114
+ {
2115
+ ...visit,
2116
+ storyOrigin: { x: 0, y: 0 },
2117
+ line: { ...visit.line, box: { ...visit.line.box, y }, baseline: g.baseline },
2118
+ absoluteBox: { ...g.box, x, y },
2119
+ span: {
2120
+ ...visit.span,
2121
+ text: g.text,
2122
+ equation: void 0,
2123
+ glyphOffsetPt: 0,
2124
+ style: {
2125
+ ...visit.span.style,
2126
+ fontFamily: "Cambria Math",
2127
+ fontFamilyEastAsia: "Cambria Math",
2128
+ fontSizePt: g.fontSizePt,
2129
+ verticalAlign: "baseline",
2130
+ baselineShiftPt: 0
2131
+ }
2132
+ }
2133
+ },
2134
+ page
2135
+ )
2136
+ );
2137
+ } else if (g.kind === "row") for (const child of g.items) node(child, x, y);
2138
+ else if (g.kind === "fraction" || g.kind === "radical") {
2139
+ out.push(
2140
+ `${color(visit.span.style.color)} rg ${rect(g.bar, x - visit.page.box.x, y - visit.page.box.y, pageHeight(page), true)} f`
2141
+ );
2142
+ if (g.kind === "fraction") {
2143
+ node(g.numerator, x, y);
2144
+ node(g.denominator, x, y);
2145
+ } else {
2146
+ node(g.sign, x, y);
2147
+ node(g.radicand, x, y);
2148
+ if (g.degree) node(g.degree, x, y);
2149
+ }
2150
+ } else if (g.kind === "script") {
2151
+ node(g.base, x, y);
2152
+ if (g.subscript) node(g.subscript, x, y);
2153
+ if (g.superscript) node(g.superscript, x, y);
2154
+ } else if (g.kind === "nary") {
2155
+ node(g.operator, x, y);
2156
+ node(g.body, x, y);
2157
+ if (g.lowerLimit) node(g.lowerLimit, x, y);
2158
+ if (g.upperLimit) node(g.upperLimit, x, y);
2159
+ }
2160
+ }
2161
+ node(equation.geometry, visit.absoluteBox.x, top);
2162
+ return out.join("\n");
2163
+ }
2164
+
2165
+ // src/paint.ts
2166
+ function rule(box, colorHex, style, x, y, height) {
2167
+ if (style === "double") {
2168
+ const horizontal2 = box.width >= box.height;
2169
+ const third = (horizontal2 ? box.height : box.width) / 3;
2170
+ return [0, 2 * third].map(
2171
+ (offset) => rule(
2172
+ {
2173
+ ...box,
2174
+ x: box.x + (horizontal2 ? 0 : offset),
2175
+ y: box.y + (horizontal2 ? offset : 0),
2176
+ width: horizontal2 ? box.width : third,
2177
+ height: horizontal2 ? third : box.height
2178
+ },
2179
+ colorHex,
2180
+ "single",
2181
+ x,
2182
+ y,
2183
+ height
2184
+ )
2185
+ ).join("\n");
2186
+ }
2187
+ if (style === "solid" || style === "single" || style === "thick")
2188
+ return `${color(colorHex)} rg ${rect(box, x, y, height, true)} f`;
2189
+ const horizontal = box.width >= box.height;
2190
+ const width = horizontal ? box.height : box.width;
2191
+ const sx = box.x + x + (horizontal ? 0 : width / 2), sy = height - box.y - y - (horizontal ? width / 2 : 0);
2192
+ return `${color(colorHex)} RG ${number(width)} w [${number(style === "dotted" ? width : width * 3)} ${number(width * 2)}] 0 d ${number(sx)} ${number(sy)} m ${number(sx + (horizontal ? box.width : 0))} ${number(sy - (horizontal ? 0 : box.height))} l S [] 0 d`;
2193
+ }
2194
+ function rotatedCellMatrix(cell, x, y, height) {
2195
+ const left = x + cell.x;
2196
+ const top = y + cell.y;
2197
+ return `0 1 -1 0 ${number(left - top + height)} ${number(height - top - cell.height - left)} cm`;
2198
+ }
2199
+ function decorations(blocks, x, y, page, work, pageIndex) {
2200
+ const out = [];
2201
+ for (const block of blocks) {
2202
+ work.tick();
2203
+ if (block.kind === "paragraph") {
2204
+ if (block.shading && block.shadingBox)
2205
+ out.push(
2206
+ `${color(block.shading)} rg ${rect(block.shadingBox, x, y, pageHeight(page), true)} f`
2207
+ );
2208
+ for (const border of block.borders ?? (block.bottomBorder ? [{ ...block.bottomBorder, side: "bottom" }] : [])) {
2209
+ const style = border.edge.val;
2210
+ if (border.edge.shadow)
2211
+ work.report("border-shadow", "Paragraph border shadow is not encoded", pageIndex);
2212
+ if (!["single", "thick", "dashed", "dotted", "double"].includes(style))
2213
+ work.report(
2214
+ "paragraph-border-style",
2215
+ `Unsupported paragraph border: ${style}`,
2216
+ pageIndex
2217
+ );
2218
+ out.push(rule(border.box, border.edge.color, style, x, y, pageHeight(page)));
2219
+ }
2220
+ } else {
2221
+ for (const row of block.rows)
2222
+ for (const cell of row.cells) {
2223
+ if (cell.paintInert || cell.vMergeContinue) continue;
2224
+ if (cell.shading)
2225
+ out.push(`${color(cell.shading)} rg ${rect(cell.box, x, y, pageHeight(page), true)} f`);
2226
+ const inner = decorations(cell.blocks, x, y, page, work, pageIndex);
2227
+ if (cell.textDirection && inner.length)
2228
+ out.push("q", rotatedCellMatrix(cell.box, x, y, pageHeight(page)), ...inner, "Q");
2229
+ else out.push(...inner);
2230
+ }
2231
+ for (const row of block.rows)
2232
+ for (const cell of row.cells) {
2233
+ if (cell.paintInert || cell.vMergeContinue) continue;
2234
+ for (const stroke of cell.borders?.strokes ?? [])
2235
+ out.push(
2236
+ rule(
2237
+ stroke,
2238
+ stroke.color,
2239
+ stroke.cssStyle,
2240
+ x + cell.box.x,
2241
+ y + cell.box.y,
2242
+ pageHeight(page)
2243
+ )
2244
+ );
2245
+ const publishedSides = new Set(
2246
+ (cell.borders?.strokes ?? []).map((stroke) => stroke.side)
2247
+ );
2248
+ for (const side of ["top", "right", "bottom", "left"]) {
2249
+ const edge = cell.borders?.[side];
2250
+ if (!edge || publishedSides.has(side)) continue;
2251
+ const b = cell.box, width = edge.widthPt;
2252
+ const box = {
2253
+ x: b.x + (side === "right" ? b.width - width : 0),
2254
+ y: b.y + (side === "bottom" ? b.height - width : 0),
2255
+ width: side === "left" || side === "right" ? width : b.width,
2256
+ height: side === "top" || side === "bottom" ? width : b.height
2257
+ };
2258
+ out.push(rule(box, edge.color, edge.style, x, y, pageHeight(page)));
2259
+ }
2260
+ }
2261
+ }
2262
+ }
2263
+ return out;
2264
+ }
2265
+ var HIGHLIGHTS = {
2266
+ yellow: "FFFF00",
2267
+ green: "00FF00",
2268
+ cyan: "00FFFF",
2269
+ magenta: "FF00FF",
2270
+ blue: "0000FF",
2271
+ red: "FF0000",
2272
+ darkBlue: "000080",
2273
+ darkCyan: "008080",
2274
+ darkGreen: "008000",
2275
+ darkMagenta: "800080",
2276
+ darkRed: "800000",
2277
+ darkYellow: "808000",
2278
+ darkGray: "808080",
2279
+ lightGray: "C0C0C0",
2280
+ black: "000000",
2281
+ white: "FFFFFF"
2282
+ };
2283
+ var PAGE_GRID_PT = 0.24;
2284
+ function onDeviceGrid(value) {
2285
+ return Number((Math.round(value / PAGE_GRID_PT) * PAGE_GRID_PT).toFixed(6));
2286
+ }
2287
+ async function paint(doc, session, layout, work, includeComments) {
2288
+ const pages = layout.pages.map((p) => {
2289
+ if (p.box.width <= 0 || p.box.height <= 0 || p.box.width > 14400 || p.box.height > 14400)
2290
+ throw new RangeError("Invalid PDF page dimensions");
2291
+ return doc.addPage([onDeviceGrid(p.box.width), onDeviceGrid(p.box.height)]);
2292
+ });
2293
+ for (const artifact of layout.reviewArtifacts) {
2294
+ if (layout.displayMode === "all-markup" && artifact.kind === "tracked-change" && !["insert", "delete", "replace"].includes(artifact.change))
2295
+ work.report("review-presentation", `Unsupported revision presentation: ${artifact.change}`);
2296
+ }
2297
+ const text = new TextWriter(doc, session, work, layout.displayMode === "all-markup");
2298
+ const images = new ImageWriter(doc, session, work);
2299
+ const names = destinations(doc, pages, layout);
2300
+ const behindStreams = pages.map(() => new Commands(work));
2301
+ const streams = pages.map(() => new Commands(work));
2302
+ const frontBorders = pages.map(() => new Commands(work));
2303
+ for (const record of layout.pages) {
2304
+ const out = streams[record.index];
2305
+ const height = pageHeight(pages[record.index]);
2306
+ if (record.pageBorders) {
2307
+ const borderOut = record.pageBorders.zOrder === "front" ? frontBorders[record.index] : out;
2308
+ for (const border of record.pageBorders.strokes) {
2309
+ if (!["single", "thick", "dashed", "dotted", "double"].includes(border.edge.val) || border.edge.shadow)
2310
+ work.report(
2311
+ "page-border-style",
2312
+ `Unsupported page border: ${border.edge.val}`,
2313
+ record.index
2314
+ );
2315
+ borderOut.push(rule(border.box, border.edge.color, border.edge.val, 0, 0, height));
2316
+ }
2317
+ }
2318
+ for (const separator of record.columnSeparators ?? [])
2319
+ out.push(
2320
+ `0 0 0 rg ${rect(separator, record.contentBox.x - record.box.x, record.contentBox.y - record.box.y, height, true)} f`
2321
+ );
2322
+ for (const area of [record.footnotes, record.endnotes]) {
2323
+ const sep = area?.separator;
2324
+ if (!sep || !(sep.ruleStyle || sep.synthetic)) continue;
2325
+ for (const offset of sep.ruleStyle === "double" ? [0, 2] : [0])
2326
+ out.push(
2327
+ `${color(sep.ruleColor)} rg ${rect({ ...sep.box, y: sep.box.y + offset, height: sep.ruleStyle === "double" ? 0.75 : sep.box.height }, -record.box.x, -record.box.y, height, true)} f`
2328
+ );
2329
+ }
2330
+ }
2331
+ const spans = [];
2332
+ const drawings = [];
2333
+ for (const warning of layout.contentWarnings ?? [])
2334
+ work.report(`core-${warning.code}`, warning.code);
2335
+ (0, import_layout5.forEachSemanticSpan)(layout, (visit) => {
2336
+ work.tick();
2337
+ spans.push(visit);
2338
+ });
2339
+ (0, import_layout5.forEachSemanticDrawing)(layout, (visit) => {
2340
+ work.tick();
2341
+ drawings.push(visit);
2342
+ });
2343
+ (0, import_layout5.forEachSemanticStory)(layout, (root) => {
2344
+ streams[root.page.index].push(
2345
+ ...decorations(
2346
+ root.host.fragments,
2347
+ root.origin.x - root.page.box.x,
2348
+ root.origin.y - root.page.box.y,
2349
+ pages[root.page.index],
2350
+ work,
2351
+ root.page.index
2352
+ )
2353
+ );
2354
+ });
2355
+ drawings.sort(
2356
+ (a, b) => (a.drawing.kind === "anchoredDrawing" ? a.drawing.relativeHeight : 0) - (b.drawing.kind === "anchoredDrawing" ? b.drawing.relativeHeight : 0)
2357
+ );
2358
+ const textboxBuffers = /* @__PURE__ */ new Map();
2359
+ const rotatedCellOf = /* @__PURE__ */ new Map();
2360
+ const markRotated = (blocks, cell) => {
2361
+ for (const block of blocks) {
2362
+ work.tick();
2363
+ if (block.kind === "paragraph") {
2364
+ if (cell) rotatedCellOf.set(block, cell);
2365
+ continue;
2366
+ }
2367
+ for (const row of block.rows)
2368
+ for (const inner of row.cells)
2369
+ markRotated(inner.blocks, cell ?? (inner.textDirection ? inner : void 0));
2370
+ }
2371
+ };
2372
+ (0, import_layout5.forEachSemanticStory)(layout, (root) => markRotated(root.host.fragments));
2373
+ const rotatedBuffers = /* @__PURE__ */ new Map();
2374
+ const outFor = (visit) => {
2375
+ const cell = visit.paragraph && rotatedCellOf.get(visit.paragraph);
2376
+ if (cell && visit.storyOrigin) {
2377
+ const key = `${visit.page.index}:${cell.id}`;
2378
+ let entry = rotatedBuffers.get(key);
2379
+ if (!entry) {
2380
+ entry = {
2381
+ cell,
2382
+ x: visit.storyOrigin.x - visit.page.box.x,
2383
+ y: visit.storyOrigin.y - visit.page.box.y,
2384
+ page: visit.page.index,
2385
+ commands: new Commands(work),
2386
+ behind: new Commands(work)
2387
+ };
2388
+ rotatedBuffers.set(key, entry);
2389
+ }
2390
+ return entry.commands;
2391
+ }
2392
+ if (visit.story !== "textbox" || !visit.textboxOwner) return streams[visit.page.index];
2393
+ let owners = textboxBuffers.get(visit.page.index);
2394
+ if (!owners) {
2395
+ owners = /* @__PURE__ */ new Map();
2396
+ textboxBuffers.set(visit.page.index, owners);
2397
+ }
2398
+ let buffer = owners.get(visit.textboxOwner);
2399
+ if (!buffer) {
2400
+ buffer = new Commands(work);
2401
+ owners.set(visit.textboxOwner, buffer);
2402
+ }
2403
+ return buffer;
2404
+ };
2405
+ const pageGroups = /* @__PURE__ */ new Map();
2406
+ for (const visit of spans) {
2407
+ let lines = pageGroups.get(visit.page.index);
2408
+ if (!lines) {
2409
+ lines = /* @__PURE__ */ new Map();
2410
+ pageGroups.set(visit.page.index, lines);
2411
+ }
2412
+ let group = lines.get(visit.line);
2413
+ if (!group) {
2414
+ group = [];
2415
+ lines.set(visit.line, group);
2416
+ }
2417
+ group.push(visit);
2418
+ }
2419
+ const lineStarts = /* @__PURE__ */ new Map();
2420
+ spans.length = 0;
2421
+ for (const lines of pageGroups.values())
2422
+ for (const group of lines.values()) {
2423
+ const first = group[0];
2424
+ const markerText = first.paragraph.lines[0] === first.line ? first.paragraph.marker?.text : void 0;
2425
+ const logical = (markerText ? markerText + " " : "") + group.map((v) => v.span.equation?.fallbackText ?? v.span.text).join("");
2426
+ group.sort((a, b) => a.absoluteBox.x - b.absoluteBox.x);
2427
+ lineStarts.set(group[0], logical);
2428
+ for (const visit of group) spans.push(visit);
2429
+ }
2430
+ let activeOut;
2431
+ const markersByPage = /* @__PURE__ */ new Map();
2432
+ for (let i = 0; i < spans.length; i++) {
2433
+ if (i % 128 === 0) await work.yield();
2434
+ const visit = spans[i], page = pages[visit.page.index], out = outFor(visit);
2435
+ if (lineStarts.has(visit)) {
2436
+ if (activeOut) activeOut.push("EMC");
2437
+ activeOut = out;
2438
+ out.push(`/Span << /ActualText <FEFF${unicodeHex(lineStarts.get(visit) ?? "")}> >> BDC`);
2439
+ }
2440
+ let markers = markersByPage.get(visit.page.index);
2441
+ if (!markers) {
2442
+ markers = /* @__PURE__ */ new Set();
2443
+ markersByPage.set(visit.page.index, markers);
2444
+ }
2445
+ const marker = visit.paragraph.marker;
2446
+ if (marker && !markers.has(visit.paragraph)) {
2447
+ markers.add(visit.paragraph);
2448
+ const picture = marker.picture ? await images.paintListMarkerPicture(
2449
+ marker.picture,
2450
+ {
2451
+ ...marker.picture.box,
2452
+ x: marker.picture.box.x + visit.storyOrigin.x - visit.page.box.x,
2453
+ y: marker.picture.box.y + visit.storyOrigin.y - visit.page.box.y
2454
+ },
2455
+ page,
2456
+ visit.page.index
2457
+ ) : "";
2458
+ if (picture) out.push(picture);
2459
+ else
2460
+ out.push(
2461
+ text.paint(
2462
+ {
2463
+ ...visit,
2464
+ span: {
2465
+ ...visit.span,
2466
+ text: marker.text,
2467
+ style: marker.style,
2468
+ box: marker.box,
2469
+ link: void 0,
2470
+ revisions: void 0
2471
+ },
2472
+ absoluteBox: {
2473
+ ...marker.box,
2474
+ x: marker.box.x + visit.storyOrigin.x,
2475
+ y: marker.box.y + visit.storyOrigin.y
2476
+ }
2477
+ },
2478
+ page
2479
+ )
2480
+ );
2481
+ }
2482
+ const fill = HIGHLIGHTS[visit.span.style.highlight ?? ""] ?? visit.span.style.shading;
2483
+ if (fill)
2484
+ out.push(
2485
+ `${color(fill)} rg ${rect(text.bandBox(visit), -visit.page.box.x, -visit.page.box.y, pageHeight(page), true)} f`
2486
+ );
2487
+ const clipping = visit.paragraph.clipToBox;
2488
+ if (clipping)
2489
+ out.push(
2490
+ `q ${rect(visit.paragraph.box, visit.storyOrigin.x - visit.page.box.x, visit.storyOrigin.y - visit.page.box.y, pageHeight(page))} W n`
2491
+ );
2492
+ out.push(
2493
+ visit.span.equation ? paintEquation(visit, page, text, work) : text.paint(visit, page)
2494
+ );
2495
+ if (clipping) out.push("Q");
2496
+ linkAnnotation(doc, page, visit, names);
2497
+ }
2498
+ if (activeOut) activeOut.push("EMC");
2499
+ for (const lines of pageGroups.values()) {
2500
+ for (const group of lines.values()) {
2501
+ const visit = group[0];
2502
+ const strokes = (0, import_layout5.runBorderStrokesForLine)(visit.line);
2503
+ if (strokes.length === 0) continue;
2504
+ work.tick();
2505
+ const out = outFor(visit);
2506
+ const height = pageHeight(pages[visit.page.index]);
2507
+ const x = visit.storyOrigin.x - visit.page.box.x;
2508
+ const y = visit.storyOrigin.y - visit.page.box.y;
2509
+ if (visit.paragraph.clipToBox) out.push(`q ${rect(visit.paragraph.box, x, y, height)} W n`);
2510
+ for (const { box, edge } of strokes) {
2511
+ if (edge.shadow || !["single", "thick", "dashed", "dotted", "double"].includes(edge.val))
2512
+ work.report(
2513
+ "run-border-style",
2514
+ `Unsupported character border: ${edge.val}${edge.shadow ? " with shadow" : ""}`,
2515
+ visit.page.index
2516
+ );
2517
+ out.push(rule(box, edge.color, edge.val, x, y, height));
2518
+ }
2519
+ if (visit.paragraph.clipToBox) out.push("Q");
2520
+ }
2521
+ }
2522
+ const paintDrawing = async (visit) => {
2523
+ const page = pages[visit.page.index];
2524
+ const d = visit.drawing;
2525
+ if (d.kind !== "anchoredDrawing" || !d.textboxStory) return images.paint(visit, page);
2526
+ const story = d.textboxStory;
2527
+ const origin = {
2528
+ x: visit.drawingOrigin.x + story.contentOffset.x - visit.page.box.x,
2529
+ y: visit.drawingOrigin.y + story.contentOffset.y - visit.page.box.y
2530
+ };
2531
+ return images.paintTextbox(visit, page, [
2532
+ ...decorations(story.fragments, origin.x, origin.y, page, work, visit.page.index),
2533
+ ...textboxBuffers.get(visit.page.index)?.get(d) ?? []
2534
+ ]);
2535
+ };
2536
+ const nested = drawings.filter((visit) => visit.story === "textbox" && visit.textboxOwner).sort((a, b) => b.textboxDepth - a.textboxDepth);
2537
+ for (const visit of nested) {
2538
+ const commands = await paintDrawing(visit);
2539
+ if (!commands) continue;
2540
+ const buffer = outFor(visit);
2541
+ if (visit.paintLayer === "behind-text") buffer.unshift(commands);
2542
+ else buffer.push(commands);
2543
+ }
2544
+ for (const visit of drawings) {
2545
+ if (visit.story === "textbox" && visit.textboxOwner) continue;
2546
+ const commands = await paintDrawing(visit);
2547
+ if (visit.paragraph && rotatedCellOf.has(visit.paragraph)) {
2548
+ const cell = rotatedCellOf.get(visit.paragraph);
2549
+ outFor(visit);
2550
+ const entry = rotatedBuffers.get(`${visit.page.index}:${cell.id}`);
2551
+ (visit.paintLayer === "behind-text" ? entry.behind : entry.commands).push(commands);
2552
+ continue;
2553
+ }
2554
+ (visit.paintLayer === "behind-text" ? behindStreams : streams)[visit.page.index].push(
2555
+ commands
2556
+ );
2557
+ }
2558
+ for (const { cell, x, y, page, commands, behind } of rotatedBuffers.values()) {
2559
+ const matrix = rotatedCellMatrix(cell.box, x, y, pageHeight(pages[page]));
2560
+ if (behind.length) behindStreams[page].push("q", matrix, ...behind, "Q");
2561
+ if (commands.length) streams[page].push("q", matrix, ...commands, "Q");
2562
+ }
2563
+ for (let i = 0; i < streams.length; i++) streams[i].push(...frontBorders[i]);
2564
+ for (let i = 0; i < pages.length; i++) {
2565
+ work.check();
2566
+ pages[i].node.addContentStream(
2567
+ doc.context.register(
2568
+ flateStream(doc.context, behindStreams[i].join("\n") + "\n" + streams[i].join("\n"))
2569
+ )
2570
+ );
2571
+ }
2572
+ for (const face2 of text.faces.values()) await face2.finish(work);
2573
+ if (includeComments) comments(doc, pages, layout, work);
2574
+ }
2575
+
2576
+ // src/pdf-work.ts
2577
+ var import_export3 = require("@docx-editor.dev/core/export");
2578
+
2579
+ // src/types.ts
2580
+ var PdfFidelityError = class extends Error {
2581
+ constructor(diagnostics) {
2582
+ super("PDF export cannot faithfully represent all requested content");
2583
+ this.diagnostics = diagnostics;
2584
+ this.name = "PdfFidelityError";
2585
+ }
2586
+ diagnostics;
2587
+ code = "fidelityUnsupported";
2588
+ };
2589
+ var PdfDocumentOpenError = class extends Error {
2590
+ constructor(reason, detail) {
2591
+ super(`Cannot open DOCX: ${reason}${detail ? `: ${detail}` : ""}`);
2592
+ this.reason = reason;
2593
+ this.detail = detail;
2594
+ this.name = "PdfDocumentOpenError";
2595
+ }
2596
+ reason;
2597
+ detail;
2598
+ code = "documentOpenFailed";
2599
+ };
2600
+ var PdfEncodingError = class extends Error {
2601
+ code = "encodingFailed";
2602
+ constructor(message, options) {
2603
+ super(message, options);
2604
+ this.name = "PdfEncodingError";
2605
+ }
2606
+ };
2607
+ var PdfOutputLimitError = class extends PdfEncodingError {
2608
+ constructor(limit, actual) {
2609
+ super(`PDF exceeds maxOutputBytes (${limit}); encoded ${actual} bytes`);
2610
+ this.limit = limit;
2611
+ this.actual = actual;
2612
+ this.name = "PdfOutputLimitError";
2613
+ }
2614
+ limit;
2615
+ actual;
2616
+ code = "outputTooLarge";
2617
+ };
2618
+ var PdfPageLimitError = class extends RangeError {
2619
+ constructor(limit, actual) {
2620
+ super(`PDF exceeds maxPages (${limit}); laid out ${actual} pages`);
2621
+ this.limit = limit;
2622
+ this.actual = actual;
2623
+ this.name = "PdfPageLimitError";
2624
+ }
2625
+ limit;
2626
+ actual;
2627
+ code = "pageLimitExceeded";
2628
+ };
2629
+
2630
+ // src/pdf-work.ts
2631
+ async function withPdfWork(options, run) {
2632
+ const { timeoutMs } = validateOptions(options);
2633
+ const controller = new AbortController();
2634
+ const signal = options.signal;
2635
+ const abort = () => controller.abort(
2636
+ new import_export3.ExportResourceError("aborted", "PDF conversion aborted", { cause: signal?.reason })
2637
+ );
2638
+ if (signal?.aborted) abort();
2639
+ signal?.addEventListener("abort", abort, { once: true });
2640
+ const timer = setTimeout(
2641
+ () => controller.abort(new import_export3.ExportResourceError("timedOut", "PDF conversion timed out")),
2642
+ timeoutMs
2643
+ );
2644
+ const work = new Work(controller.signal, Date.now() + timeoutMs);
2645
+ const started = performance.now();
2646
+ try {
2647
+ work.check();
2648
+ return await run(work, () => Math.round(performance.now() - started));
2649
+ } catch (error) {
2650
+ if (error instanceof PdfDocumentOpenError && error.reason === "aborted" || error instanceof import_export3.ExportResourceError && error.code === "aborted")
2651
+ work.check();
2652
+ if (error instanceof PdfFidelityError || error instanceof PdfDocumentOpenError || error instanceof import_export3.ExportResourceError || error instanceof RangeError || error instanceof PdfEncodingError || error instanceof PdfWorkLimitError)
2653
+ throw error;
2654
+ work.check();
2655
+ throw new PdfEncodingError("PDF encoding failed", { cause: error });
2656
+ } finally {
2657
+ clearTimeout(timer);
2658
+ signal?.removeEventListener("abort", abort);
2659
+ }
2660
+ }
2661
+ function waitForPdfWork(run, work) {
2662
+ work.check();
2663
+ return new Promise((resolve, reject) => {
2664
+ const abort = () => {
2665
+ try {
2666
+ work.check();
2667
+ } catch (error) {
2668
+ reject(error);
2669
+ }
2670
+ };
2671
+ work.signal.addEventListener("abort", abort, { once: true });
2672
+ const clear = () => work.signal.removeEventListener("abort", abort);
2673
+ Promise.resolve().then(() => {
2674
+ work.check();
2675
+ return run();
2676
+ }).then(
2677
+ (value) => {
2678
+ clear();
2679
+ resolve(value);
2680
+ },
2681
+ (error) => {
2682
+ clear();
2683
+ reject(error);
2684
+ }
2685
+ );
2686
+ });
2687
+ }
2688
+
2689
+ // src/export-pdf.ts
2690
+ async function exportPdf(source, options = {}) {
2691
+ if (!(source instanceof Uint8Array)) throw new TypeError("source must be a Uint8Array");
2692
+ validateOptions(options);
2693
+ return withPdfWork(options, async (work, phase) => {
2694
+ const {
2695
+ comments: _comments,
2696
+ fidelityPolicy: _fidelity,
2697
+ timeoutMs: _timeout,
2698
+ maxOutputBytes: _bytes,
2699
+ maxPages: _pages,
2700
+ ...open
2701
+ } = options;
2702
+ const opened = await openExportSession(source, open, work.signal);
2703
+ if (!opened.ok) throw new PdfDocumentOpenError(opened.reason, opened.detail);
2704
+ try {
2705
+ return await renderSession(opened.session, options, work, phase, phase());
2706
+ } finally {
2707
+ opened.session.dispose();
2708
+ }
2709
+ });
2710
+ }
2711
+ async function exportPdfFrom(session, options = {}) {
2712
+ validateOptions(options);
2713
+ for (const key of Object.keys(options)) {
2714
+ if (![
2715
+ "comments",
2716
+ "fidelityPolicy",
2717
+ "timeoutMs",
2718
+ "maxOutputBytes",
2719
+ "maxPages",
2720
+ "signal",
2721
+ "displayMode"
2722
+ ].includes(key))
2723
+ throw new TypeError(`${key} must be configured when opening the PDF session`);
2724
+ }
2725
+ if (!session || typeof session !== "object" || !(0, import_export4.hasFontBackedExportCapabilities)(session) || typeof session.layout !== "function" || typeof session.layoutFor !== "function" || !session.fontResolution)
2726
+ throw new TypeError("exportPdfFrom requires a font-backed export session");
2727
+ return withPdfWork(options, (work, phase) => renderSession(session, options, work, phase, 0));
2728
+ }
2729
+ async function renderSession(session, options, work, phase, openMs) {
2730
+ const { maxBytes, maxPages } = validateOptions(options);
2731
+ const { comments: comments2 = true, fidelityPolicy = "strict" } = options;
2732
+ work.check();
2733
+ const layout = await waitForPdfWork(
2734
+ () => options.displayMode ? session.layoutFor(options.displayMode) : session.layout(),
2735
+ work
2736
+ );
2737
+ const layoutMs = phase() - openMs;
2738
+ if (layout.pages.length > maxPages) throw new PdfPageLimitError(maxPages, layout.pages.length);
2739
+ reportFontDiagnostics(session.fontResolution, work);
2740
+ const doc = await import_pdf_lib6.PDFDocument.create({ updateMetadata: false });
2741
+ doc.setProducer("docx-editor.dev");
2742
+ doc.setCreator("docx-editor.dev");
2743
+ const date = /* @__PURE__ */ new Date("2020-01-01T00:00:00Z");
2744
+ doc.setCreationDate(date);
2745
+ doc.setModificationDate(date);
2746
+ const metadata = layout.documentMetadata;
2747
+ if (metadata?.title) doc.setTitle(metadata.title);
2748
+ if (metadata?.creator) doc.setAuthor(metadata.creator);
2749
+ if (metadata?.subject) doc.setSubject(metadata.subject);
2750
+ if (metadata?.keywords) doc.setKeywords([metadata.keywords]);
2751
+ await paint(doc, session, layout, work, comments2);
2752
+ const paintMs = phase() - openMs - layoutMs;
2753
+ const diagnostics = Object.freeze([...work.diagnostics]);
2754
+ if (fidelityPolicy === "strict" && diagnostics.some((d) => d.severity !== "information"))
2755
+ throw new PdfFidelityError(diagnostics);
2756
+ work.check();
2757
+ const bytes = await doc.save({ useObjectStreams: false, objectsPerTick: 1e3 });
2758
+ work.check();
2759
+ if (bytes.byteLength > maxBytes) throw new PdfOutputLimitError(maxBytes, bytes.byteLength);
2760
+ return Object.freeze({
2761
+ bytes,
2762
+ pageCount: layout.pages.length,
2763
+ layoutRevision: layout.revision,
2764
+ displayMode: layout.displayMode ?? "proposed",
2765
+ fontResolution: session.fontResolution,
2766
+ diagnostics,
2767
+ timings: Object.freeze({
2768
+ openMs,
2769
+ layoutMs,
2770
+ paintMs,
2771
+ saveMs: phase() - openMs - layoutMs - paintMs
2772
+ })
2773
+ });
2774
+ }
2775
+
2776
+ // src/pdf-session.ts
2777
+ async function openDocumentForExport(source, options = {}) {
2778
+ if (!(source instanceof Uint8Array)) throw new TypeError("source must be a Uint8Array");
2779
+ validateOptions(options);
2780
+ for (const key of ["comments", "fidelityPolicy", "timeoutMs", "maxOutputBytes", "maxPages"]) {
2781
+ if (options[key] !== void 0)
2782
+ throw new TypeError(`${key} must be configured in exportPdfFrom`);
2783
+ }
2784
+ return openExportSession(source, options, options.signal);
2785
+ }
2786
+
2787
+ // src/index.ts
2788
+ var import_editor2 = require("@docx-editor.dev/core/editor");
2789
+ var import_export5 = require("@docx-editor.dev/core/export");
2790
+ // Annotate the CommonJS export names for ESM import in node:
2791
+ 0 && (module.exports = {
2792
+ ExportResourceError,
2793
+ PdfDocumentOpenError,
2794
+ PdfEncodingError,
2795
+ PdfFidelityError,
2796
+ PdfOutputLimitError,
2797
+ PdfPageLimitError,
2798
+ PdfWorkLimitError,
2799
+ createFontSource,
2800
+ defineFontResolver,
2801
+ exportPdf,
2802
+ exportPdfFrom,
2803
+ openDocumentForExport
2804
+ });