@asterdoc/render 0.0.0-stage → 0.9.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/pdf.js ADDED
@@ -0,0 +1,4638 @@
1
+ import {
2
+ requestRenderData
3
+ } from "./chunk-U7LF3GN4.js";
4
+ import {
5
+ formatFieldData,
6
+ unwrapFormattedValue,
7
+ validateFieldBindings
8
+ } from "./chunk-ICQLU4FL.js";
9
+ import {
10
+ PdfBindingKeyError,
11
+ PdfBindingSyntaxError,
12
+ getPath,
13
+ parseBinding,
14
+ parseBindings,
15
+ pathSegments,
16
+ renderDataPath,
17
+ resolveLayerBindings,
18
+ splitPartIndex
19
+ } from "./chunk-4QVR6LUY.js";
20
+
21
+ // src/pdf/field-appearance.ts
22
+ var PdfFieldFillError = class extends Error {
23
+ /** Fully qualified name of the PDF form field, or the overlay that failed. */
24
+ field;
25
+ reason;
26
+ limit;
27
+ character;
28
+ script;
29
+ constructor(field, reason, detail, message) {
30
+ super(`Cannot fill PDF field "${field}": ${message}`);
31
+ this.name = "PdfFieldFillError";
32
+ this.field = field;
33
+ this.reason = reason;
34
+ if (detail.limit !== void 0) this.limit = detail.limit;
35
+ if (detail.character !== void 0) this.character = detail.character;
36
+ if (detail.script !== void 0) this.script = detail.script;
37
+ }
38
+ };
39
+ var MIN_FONT_SIZE = 6;
40
+ var AUTO_MULTILINE_MAX = 12;
41
+ var PADDING_X = 2;
42
+ var PADDING_Y = 1;
43
+ var LINE_SPACING = 1.15;
44
+ var SHRINK_STEP = 0.5;
45
+ function parseDefaultAppearance(value) {
46
+ const tokens = (value ?? "").trim().split(/\s+/).filter(Boolean);
47
+ let size = 0;
48
+ let fontName;
49
+ let color = "0 g";
50
+ const operands = { g: 1, rg: 3, k: 4 };
51
+ tokens.forEach((token, index) => {
52
+ if (token === "Tf") {
53
+ const declared = Number(tokens[index - 1]);
54
+ size = Number.isFinite(declared) && declared > 0 ? declared : 0;
55
+ const name2 = tokens[index - 2];
56
+ if (name2?.startsWith("/")) fontName = name2.slice(1);
57
+ } else if (token in operands) {
58
+ const count = operands[token];
59
+ const values = tokens.slice(index - count, index);
60
+ if (values.length === count && values.every((item) => Number.isFinite(Number(item)))) {
61
+ color = `${values.join(" ")} ${token}`;
62
+ }
63
+ }
64
+ });
65
+ return { size, color, ...fontName && { fontName } };
66
+ }
67
+ var format = (value) => String(Number(value.toFixed(3)));
68
+ function show(font, size, x, y, text) {
69
+ return `/${font.resourceName} ${format(size)} Tf
70
+ 1 0 0 1 ${format(x)} ${format(y)} Tm
71
+ ${font.encode(text)} Tj`;
72
+ }
73
+ function alignedX(layout, lineWidth) {
74
+ if (layout.alignment === 1) return (layout.width - lineWidth) / 2;
75
+ if (layout.alignment === 2) return layout.width - PADDING_X - lineWidth;
76
+ return PADDING_X;
77
+ }
78
+ function centeredBaseline(layout, font, size) {
79
+ const glyphHeight = (font.ascent - font.descent) / 1e3 * size;
80
+ return (layout.height - glyphHeight) / 2 - font.descent / 1e3 * size;
81
+ }
82
+ function overflow(layout, floor) {
83
+ throw new PdfFieldFillError(
84
+ layout.field,
85
+ "overflow",
86
+ { limit: floor },
87
+ `the value does not fit at the minimum size of ${format(floor)} pt`
88
+ );
89
+ }
90
+ function wrap(text, font, size, width) {
91
+ const measure2 = (value) => font.width(value) / 1e3 * size;
92
+ const lines2 = [];
93
+ for (const paragraph of text.split(/\r\n|\r|\n/)) {
94
+ let line = "";
95
+ for (const word of paragraph.split(" ")) {
96
+ const candidate = line ? `${line} ${word}` : word;
97
+ if (measure2(candidate) <= width) {
98
+ line = candidate;
99
+ continue;
100
+ }
101
+ if (line) lines2.push(line);
102
+ line = "";
103
+ let rest = word;
104
+ while (measure2(rest) > width && [...rest].length > 1) {
105
+ const characters = [...rest];
106
+ let count = 1;
107
+ while (count < characters.length && measure2(characters.slice(0, count + 1).join("")) <= width) count++;
108
+ lines2.push(characters.slice(0, count).join(""));
109
+ rest = characters.slice(count).join("");
110
+ }
111
+ line = rest;
112
+ }
113
+ lines2.push(line);
114
+ }
115
+ return lines2;
116
+ }
117
+ var sizeFloor = (layout) => layout.appearance.size ? Math.min(MIN_FONT_SIZE, layout.appearance.size) : MIN_FONT_SIZE;
118
+ function oneLineSize(layout, font, unitWidth, width) {
119
+ const heightFit = (layout.height - 2 * PADDING_Y) / ((font.ascent - font.descent) / 1e3);
120
+ const widthFit = unitWidth > 0 ? width / unitWidth : Number.POSITIVE_INFINITY;
121
+ const size = Math.min(layout.appearance.size || Number.POSITIVE_INFINITY, heightFit, widthFit);
122
+ const floor = sizeFloor(layout);
123
+ if (size < floor) overflow(layout, floor);
124
+ return size;
125
+ }
126
+ function singleLine(layout, text, font) {
127
+ const unitWidth = font.width(text) / 1e3;
128
+ const size = oneLineSize(layout, font, unitWidth, layout.width - 2 * PADDING_X);
129
+ return show(font, size, alignedX(layout, unitWidth * size), centeredBaseline(layout, font, size), text);
130
+ }
131
+ function combed(layout, boxes, text, font) {
132
+ const characters = [...text];
133
+ if (characters.length > boxes) {
134
+ throw new PdfFieldFillError(
135
+ layout.field,
136
+ "comb-length",
137
+ { limit: boxes },
138
+ `the field has ${boxes} comb boxes, but the value has ${characters.length} characters`
139
+ );
140
+ }
141
+ const cell = layout.width / boxes;
142
+ const widest = Math.max(0, ...characters.map((character) => font.width(character) / 1e3));
143
+ const size = oneLineSize(layout, font, widest, cell);
144
+ const unused = boxes - characters.length;
145
+ const first = layout.alignment === 1 ? Math.floor(unused / 2) : layout.alignment === 2 ? unused : 0;
146
+ const baseline = centeredBaseline(layout, font, size);
147
+ return characters.map((character, index) => {
148
+ const width = font.width(character) / 1e3 * size;
149
+ const x = (first + index) * cell + (cell - width) / 2;
150
+ return show(font, size, x, baseline, character);
151
+ }).join("\n");
152
+ }
153
+ function multiline(layout, text, font) {
154
+ const floor = sizeFloor(layout);
155
+ const available = layout.width - 2 * PADDING_X;
156
+ const fits = (size, lines2) => lines2.every((line) => font.width(line) / 1e3 * size <= available) && 2 * PADDING_Y + (font.ascent - font.descent) / 1e3 * size + (lines2.length - 1) * LINE_SPACING * size <= layout.height;
157
+ const start = layout.appearance.size || AUTO_MULTILINE_MAX;
158
+ const candidates = [];
159
+ for (let size = start; size > floor; size -= SHRINK_STEP) candidates.push(size);
160
+ candidates.push(floor);
161
+ for (const size of candidates) {
162
+ const lines2 = wrap(text, font, size, available);
163
+ if (!fits(size, lines2)) continue;
164
+ const firstBaseline = layout.height - PADDING_Y - font.ascent / 1e3 * size;
165
+ return lines2.map((line, index) => show(
166
+ font,
167
+ size,
168
+ alignedX(layout, font.width(line) / 1e3 * size),
169
+ firstBaseline - index * LINE_SPACING * size,
170
+ line
171
+ )).join("\n");
172
+ }
173
+ return overflow(layout, floor);
174
+ }
175
+ function layoutFieldText(layout, text, font) {
176
+ if (layout.comb !== void 0 && layout.comb > 0 && !layout.multiline) {
177
+ return combed(layout, layout.comb, text, font);
178
+ }
179
+ if (layout.multiline) return multiline(layout, text, font);
180
+ return singleLine(layout, text.replace(/\r\n|\r|\n/g, " "), font);
181
+ }
182
+
183
+ // src/pdf/text-string.ts
184
+ var PDF_DOC_DIFFERENCES = [
185
+ [24, 728],
186
+ [25, 711],
187
+ [26, 710],
188
+ [27, 729],
189
+ [28, 733],
190
+ [29, 731],
191
+ [30, 730],
192
+ [31, 732],
193
+ [128, 8226],
194
+ [129, 8224],
195
+ [130, 8225],
196
+ [131, 8230],
197
+ [132, 8212],
198
+ [133, 8211],
199
+ [134, 402],
200
+ [135, 8260],
201
+ [136, 8249],
202
+ [137, 8250],
203
+ [138, 8722],
204
+ [139, 8240],
205
+ [140, 8222],
206
+ [141, 8220],
207
+ [142, 8221],
208
+ [143, 8216],
209
+ [144, 8217],
210
+ [145, 8218],
211
+ [146, 8482],
212
+ [147, 64257],
213
+ [148, 64258],
214
+ [149, 321],
215
+ [150, 338],
216
+ [151, 352],
217
+ [152, 376],
218
+ [153, 381],
219
+ [154, 305],
220
+ [155, 322],
221
+ [156, 339],
222
+ [157, 353],
223
+ [158, 382],
224
+ [160, 8364]
225
+ ];
226
+ var UNDEFINED_PDF_DOC = /* @__PURE__ */ new Set([
227
+ 0,
228
+ 1,
229
+ 2,
230
+ 3,
231
+ 4,
232
+ 5,
233
+ 6,
234
+ 7,
235
+ 8,
236
+ 11,
237
+ 12,
238
+ 14,
239
+ 15,
240
+ 16,
241
+ 17,
242
+ 18,
243
+ 19,
244
+ 20,
245
+ 21,
246
+ 22,
247
+ 23,
248
+ 127,
249
+ 159,
250
+ 173
251
+ ]);
252
+ var PDF_DOC_TO_UNICODE = (() => {
253
+ const table = Array.from({ length: 256 }, (_, code) => code);
254
+ for (const [code, unicode] of PDF_DOC_DIFFERENCES) table[code] = unicode;
255
+ return table;
256
+ })();
257
+ var UNICODE_TO_PDF_DOC = new Map(
258
+ PDF_DOC_TO_UNICODE.flatMap((unicode, code) => UNDEFINED_PDF_DOC.has(code) ? [] : [[unicode, code]])
259
+ );
260
+ var ESCAPE = "\x1B";
261
+ function withoutLanguageEscapes(text) {
262
+ let result = "";
263
+ let cursor = 0;
264
+ while (cursor < text.length) {
265
+ const start = text.indexOf(ESCAPE, cursor);
266
+ const end = start === -1 ? -1 : text.indexOf(ESCAPE, start + 1);
267
+ if (end === -1) return result + text.slice(cursor);
268
+ result += text.slice(cursor, start);
269
+ cursor = end + 1;
270
+ }
271
+ return result;
272
+ }
273
+ function byteString(bytes) {
274
+ let result = "";
275
+ for (let offset = 0; offset < bytes.length; offset += 32768) {
276
+ result += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
277
+ }
278
+ return result;
279
+ }
280
+ function stringBytes(value) {
281
+ const bytes = new Uint8Array(value.length);
282
+ for (let index = 0; index < value.length; index++) bytes[index] = value.charCodeAt(index);
283
+ return bytes;
284
+ }
285
+ function decodeTextString(value) {
286
+ if (value.charCodeAt(0) === 254 && value.charCodeAt(1) === 255) {
287
+ let text2 = "";
288
+ for (let index = 2; index + 1 < value.length; index += 2) {
289
+ text2 += String.fromCharCode(value.charCodeAt(index) << 8 | value.charCodeAt(index + 1));
290
+ }
291
+ return withoutLanguageEscapes(text2);
292
+ }
293
+ if (value.charCodeAt(0) === 239 && value.charCodeAt(1) === 187 && value.charCodeAt(2) === 191) {
294
+ return withoutLanguageEscapes(new TextDecoder("utf-8").decode(stringBytes(value.slice(3))));
295
+ }
296
+ let text = "";
297
+ for (let index = 0; index < value.length; index++) text += String.fromCharCode(PDF_DOC_TO_UNICODE[value.charCodeAt(index) & 255]);
298
+ return text;
299
+ }
300
+ function encodeTextString(text) {
301
+ let encoded = "";
302
+ for (let index = 0; index < text.length; index++) {
303
+ const code = UNICODE_TO_PDF_DOC.get(text.charCodeAt(index));
304
+ if (code === void 0) return utf16(text);
305
+ encoded += String.fromCharCode(code);
306
+ }
307
+ return encoded;
308
+ }
309
+ function utf16(text) {
310
+ let encoded = "\xFE\xFF";
311
+ for (let index = 0; index < text.length; index++) {
312
+ const unit = text.charCodeAt(index);
313
+ encoded += String.fromCharCode(unit >> 8, unit & 255);
314
+ }
315
+ return encoded;
316
+ }
317
+
318
+ // src/pdf/writer.ts
319
+ function encodeLatin1(value) {
320
+ const bytes = new Uint8Array(value.length);
321
+ for (let index = 0; index < value.length; index++) bytes[index] = value.charCodeAt(index) & 255;
322
+ return bytes;
323
+ }
324
+ function serializeName(value) {
325
+ return `/${value.replace(/[^!-'*-.0-;=?-Z\\^-z|~]/g, (char) => `#${char.charCodeAt(0).toString(16).padStart(2, "0")}`)}`;
326
+ }
327
+ function serializeString(value) {
328
+ let hex = "";
329
+ let printable = true;
330
+ for (let index = 0; index < value.length; index++) {
331
+ const code = value.charCodeAt(index);
332
+ if (code > 255) throw new Error(`PDF string holds U+${code.toString(16).toUpperCase().padStart(4, "0")}; encode text with encodeTextString before storing it`);
333
+ if (code < 32 ? code !== 9 && code !== 10 && code !== 13 : code > 126) printable = false;
334
+ hex += code.toString(16).padStart(2, "0");
335
+ }
336
+ if (!printable) return `<${hex}>`;
337
+ return `(${value.replace(/([\\()])/g, "\\$1").replace(/\r/g, "\\r").replace(/\n/g, "\\n")})`;
338
+ }
339
+ function serializePdfValue(value) {
340
+ if (value === null) return "null";
341
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
342
+ if (typeof value === "string") return serializeString(value);
343
+ if (Array.isArray(value)) return `[${value.map(serializePdfValue).join(" ")}]`;
344
+ if (value.kind === "name") return serializeName(value.value);
345
+ if (value.kind === "ref") return `${value.object} ${value.generation} R`;
346
+ return `<<${[...value.entries].map(([key, item]) => ` ${serializeName(key)} ${serializePdfValue(item)}`).join("")} >>`;
347
+ }
348
+ function serializeObjects(objects, start) {
349
+ const chunks = [];
350
+ const offsets = /* @__PURE__ */ new Map();
351
+ let offset = start;
352
+ for (const record of [...objects].sort((a, b) => a.object - b.object)) {
353
+ offsets.set(record.object, offset);
354
+ const header = encodeLatin1(`${record.object} ${record.generation} obj
355
+ `);
356
+ chunks.push(header);
357
+ offset += header.length;
358
+ if (record.stream) {
359
+ if (typeof record.value !== "object" || record.value === null || Array.isArray(record.value) || record.value.kind !== "dict") {
360
+ throw new Error(`PDF stream object ${record.object} must contain a dictionary`);
361
+ }
362
+ const entries = new Map(record.value.entries);
363
+ entries.set("Length", record.stream.length);
364
+ const dictionary = encodeLatin1(`${serializePdfValue({ kind: "dict", entries })}
365
+ stream
366
+ `);
367
+ const footer = encodeLatin1("\nendstream\nendobj\n");
368
+ chunks.push(dictionary, record.stream, footer);
369
+ offset += dictionary.length + record.stream.length + footer.length;
370
+ } else {
371
+ const body = encodeLatin1(`${serializePdfValue(record.value)}
372
+ endobj
373
+ `);
374
+ chunks.push(body);
375
+ offset += body.length;
376
+ }
377
+ }
378
+ return { chunks, offsets, end: offset };
379
+ }
380
+ function trailerSection(trailer, xrefOffset) {
381
+ return `trailer
382
+ ${serializePdfValue({ kind: "dict", entries: new Map(trailer) })}
383
+ startxref
384
+ ${xrefOffset}
385
+ %%EOF
386
+ `;
387
+ }
388
+ function concat(chunks) {
389
+ const output = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
390
+ let cursor = 0;
391
+ for (const chunk of chunks) {
392
+ output.set(chunk, cursor);
393
+ cursor += chunk.length;
394
+ }
395
+ return output;
396
+ }
397
+ function writePdf(objects, trailer) {
398
+ const header = encodeLatin1("%PDF-1.7\n%\xE2\xE3\xCF\xD3\n");
399
+ const body = serializeObjects(objects, header.length);
400
+ const size = objects.reduce((highest, record) => Math.max(highest, record.object), 0) + 1;
401
+ let table = `xref
402
+ 0 ${size}
403
+ 0000000000 65535 f
404
+ `;
405
+ for (let number = 1; number < size; number++) {
406
+ const at = body.offsets.get(number);
407
+ table += at === void 0 ? "0000000000 65535 f \n" : `${String(at).padStart(10, "0")} 00000 n
408
+ `;
409
+ }
410
+ const entries = new Map(trailer);
411
+ entries.set("Size", size);
412
+ table += trailerSection(entries, body.end);
413
+ return concat([header, ...body.chunks, encodeLatin1(table)]);
414
+ }
415
+ function appendPdfUpdate(base, objects, trailer) {
416
+ const separator = encodeLatin1(base.at(-1) === 10 ? "" : "\n");
417
+ const body = serializeObjects(objects, base.length + separator.length);
418
+ let xref = "xref\n";
419
+ for (const record of [...objects].sort((a, b) => a.object - b.object)) {
420
+ xref += `${record.object} 1
421
+ ${String(body.offsets.get(record.object)).padStart(10, "0")} ${String(record.generation).padStart(5, "0")} n
422
+ `;
423
+ }
424
+ xref += trailerSection(trailer, body.end);
425
+ return concat([base, separator, ...body.chunks, encodeLatin1(xref)]);
426
+ }
427
+ function collectRefs(model, value, seen, excluded = /* @__PURE__ */ new Set()) {
428
+ if (value === null || value === void 0 || typeof value !== "object") return;
429
+ if (Array.isArray(value)) {
430
+ for (const entry of value) collectRefs(model, entry, seen, excluded);
431
+ return;
432
+ }
433
+ if (value.kind === "ref") {
434
+ if (seen.has(value.object) || excluded.has(value.object)) return;
435
+ seen.add(value.object);
436
+ const record = model.objects.get(value.object);
437
+ if (record) collectRefs(model, record.value, seen, excluded);
438
+ return;
439
+ }
440
+ if (value.kind === "dict") {
441
+ for (const entry of value.entries.values()) collectRefs(model, entry, seen, excluded);
442
+ }
443
+ }
444
+ function renumber(value, mapping) {
445
+ if (value === null || typeof value !== "object") return value;
446
+ if (Array.isArray(value)) {
447
+ const references = value.length > 0 && value.every((entry) => isRefValue(entry));
448
+ const kept = references ? value.filter((entry) => isRefValue(entry) && mapping.has(entry.object)) : value;
449
+ return kept.map((entry) => renumber(entry, mapping));
450
+ }
451
+ if (value.kind === "ref") {
452
+ const target = mapping.get(value.object);
453
+ return target === void 0 ? null : { kind: "ref", object: target, generation: 0 };
454
+ }
455
+ if (value.kind === "dict") {
456
+ const entries = /* @__PURE__ */ new Map();
457
+ for (const [key, entry] of value.entries) entries.set(key, renumber(entry, mapping));
458
+ return { kind: "dict", entries };
459
+ }
460
+ return value;
461
+ }
462
+ function isRefValue(value) {
463
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.kind === "ref";
464
+ }
465
+
466
+ // src/pdf/syntax.ts
467
+ var isName = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && value.kind === "name");
468
+ var isRef = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && value.kind === "ref");
469
+ var isDict = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && value.kind === "dict");
470
+ var Parser = class {
471
+ constructor(source2, position = 0) {
472
+ this.source = source2;
473
+ this.position = position;
474
+ }
475
+ source;
476
+ position;
477
+ get offset() {
478
+ return this.position;
479
+ }
480
+ parse() {
481
+ this.skip();
482
+ if (this.source.startsWith("<<", this.position)) return this.dictionary();
483
+ const char = this.source[this.position];
484
+ if (char === "[") return this.array();
485
+ if (char === "/") return this.name();
486
+ if (char === "(") return this.literalString();
487
+ if (char === "<") return this.hexString();
488
+ if (this.source.startsWith("true", this.position)) {
489
+ this.position += 4;
490
+ return true;
491
+ }
492
+ if (this.source.startsWith("false", this.position)) {
493
+ this.position += 5;
494
+ return false;
495
+ }
496
+ if (this.source.startsWith("null", this.position)) {
497
+ this.position += 4;
498
+ return null;
499
+ }
500
+ return this.numberOrRef();
501
+ }
502
+ dictionary() {
503
+ this.position += 2;
504
+ const entries = /* @__PURE__ */ new Map();
505
+ while (true) {
506
+ this.skip();
507
+ this.assertNotAtEnd();
508
+ if (this.source.startsWith(">>", this.position)) {
509
+ this.position += 2;
510
+ return { kind: "dict", entries };
511
+ }
512
+ const key = this.name().value;
513
+ entries.set(key, this.parse());
514
+ }
515
+ }
516
+ array() {
517
+ this.position++;
518
+ const values = [];
519
+ while (true) {
520
+ this.skip();
521
+ this.assertNotAtEnd();
522
+ if (this.source[this.position] === "]") {
523
+ this.position++;
524
+ return values;
525
+ }
526
+ values.push(this.parse());
527
+ }
528
+ }
529
+ name() {
530
+ this.position++;
531
+ const start = this.position;
532
+ while (!this.atDelimiter()) this.position++;
533
+ return {
534
+ kind: "name",
535
+ value: this.source.slice(start, this.position).replace(/#([0-9a-f]{2})/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
536
+ };
537
+ }
538
+ literalString() {
539
+ this.position++;
540
+ let depth = 1;
541
+ let result = "";
542
+ while (this.position < this.source.length && depth > 0) {
543
+ const char = this.source[this.position++];
544
+ if (char === "\\") {
545
+ const escaped = this.source[this.position++];
546
+ if (escaped === void 0) break;
547
+ const simple = { n: "\n", r: "\r", t: " ", b: "\b", f: "\f" };
548
+ if (simple[escaped]) result += simple[escaped];
549
+ else if (escaped === "\n") continue;
550
+ else if (escaped === "\r") {
551
+ if (this.source[this.position] === "\n") this.position++;
552
+ } else if (/[0-7]/.test(escaped)) {
553
+ let octal = escaped;
554
+ while (octal.length < 3 && /[0-7]/.test(this.source[this.position] ?? "")) octal += this.source[this.position++];
555
+ result += String.fromCharCode(Number.parseInt(octal, 8) & 255);
556
+ } else result += escaped;
557
+ } else if (char === "(") {
558
+ depth++;
559
+ result += char;
560
+ } else if (char === ")") {
561
+ depth--;
562
+ if (depth > 0) result += char;
563
+ } else result += char;
564
+ }
565
+ return result;
566
+ }
567
+ hexString() {
568
+ this.position++;
569
+ const start = this.position;
570
+ while (this.position < this.source.length && this.source[this.position] !== ">") this.position++;
571
+ let hex = this.source.slice(start, this.position++).replace(/\s/g, "");
572
+ if (hex.length % 2 === 1) hex += "0";
573
+ const bytes = new Uint8Array(hex.length / 2);
574
+ for (let index = 0; index < bytes.length; index++) bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
575
+ return byteString(bytes);
576
+ }
577
+ numberOrRef() {
578
+ const first = this.number();
579
+ const saved = this.position;
580
+ this.skip();
581
+ if (/[-+\d.]/.test(this.source[this.position] ?? "")) {
582
+ const second = this.number();
583
+ this.skip();
584
+ if (this.source[this.position] === "R" && Number.isInteger(first) && Number.isInteger(second)) {
585
+ this.position++;
586
+ return { kind: "ref", object: first, generation: second };
587
+ }
588
+ }
589
+ this.position = saved;
590
+ return first;
591
+ }
592
+ number() {
593
+ const start = this.position;
594
+ while (/[-+\d.]/.test(this.source[this.position] ?? "")) this.position++;
595
+ const value = Number(this.source.slice(start, this.position));
596
+ if (!Number.isFinite(value)) throw new Error(`Invalid PDF number at byte ${start}`);
597
+ return value;
598
+ }
599
+ assertNotAtEnd() {
600
+ if (this.position >= this.source.length) throw new Error("Unexpected end of PDF data");
601
+ }
602
+ skip() {
603
+ while (this.position < this.source.length) {
604
+ if (/\s|\0/.test(this.source[this.position])) this.position++;
605
+ else if (this.source[this.position] === "%") {
606
+ while (this.position < this.source.length && !/[\r\n]/.test(this.source[this.position])) this.position++;
607
+ } else break;
608
+ }
609
+ }
610
+ atDelimiter() {
611
+ const char = this.source[this.position];
612
+ return char === void 0 || /\s/.test(char) || ["(", ")", "<", ">", "[", "]", "{", "}", "/", "%"].includes(char);
613
+ }
614
+ };
615
+ async function inflate(bytes) {
616
+ if (typeof DecompressionStream === "undefined") throw new Error("FlateDecode is unavailable in this runtime");
617
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate"));
618
+ return new Uint8Array(await new Response(stream).arrayBuffer());
619
+ }
620
+ var PdfEncryptedError = class extends Error {
621
+ code = "encrypted_pdf";
622
+ constructor() {
623
+ super("The PDF is encrypted. Asterdoc reads and writes unencrypted PDFs only; remove the encryption and try again.");
624
+ this.name = "PdfEncryptedError";
625
+ }
626
+ };
627
+ var SECTION_KEYS = ["Size", "Prev", "XRefStm", "Type", "Index", "W", "Length", "Filter", "DecodeParms"];
628
+ var isXrefStream = (value) => {
629
+ if (!isDict(value)) return false;
630
+ const type = value.entries.get("Type");
631
+ return isName(type) && type.value === "XRef";
632
+ };
633
+ function trailerDictionaries(source2, bodies) {
634
+ const trailers = [];
635
+ for (const match of source2.matchAll(/trailer\s*<</g)) {
636
+ const at = match.index ?? 0;
637
+ if (bodies.some(([start, end]) => at >= start && at < end)) continue;
638
+ try {
639
+ const value = new Parser(source2, at + "trailer".length).parse();
640
+ if (isDict(value)) trailers.push({ offset: at, dict: value });
641
+ } catch {
642
+ }
643
+ }
644
+ return trailers;
645
+ }
646
+ function newestTrailer(source2, startxref, trailers, objects) {
647
+ if (startxref !== void 0) {
648
+ if (/^\s*xref\b/.test(source2.slice(startxref, startxref + 16))) {
649
+ const trailer = trailers.find(({ offset }) => offset > startxref);
650
+ if (trailer) return trailer.dict;
651
+ }
652
+ const header = /^\s*(\d+)\s+\d+\s+obj\b/.exec(source2.slice(startxref, startxref + 32));
653
+ const stream = header ? objects.get(Number(header[1]))?.value : void 0;
654
+ if (isXrefStream(stream)) return stream;
655
+ }
656
+ if (trailers.length > 0) return trailers.at(-1).dict;
657
+ return [...objects.values()].map(({ value }) => value).filter(isXrefStream).at(-1);
658
+ }
659
+ var PdfModel = class _PdfModel {
660
+ constructor(bytes, objects, trailer, startxref) {
661
+ this.bytes = bytes;
662
+ this.objects = objects;
663
+ this.trailer = trailer;
664
+ this.startxref = startxref;
665
+ }
666
+ bytes;
667
+ objects;
668
+ trailer;
669
+ startxref;
670
+ updated = /* @__PURE__ */ new Set();
671
+ static async load(bytes) {
672
+ const source2 = byteString(bytes);
673
+ const objects = /* @__PURE__ */ new Map();
674
+ const indirectLengths = [];
675
+ const bodies = [];
676
+ const pattern = /(?:^|[\r\n])\s*(\d+)\s+(\d+)\s+obj\b/g;
677
+ for (const match of source2.matchAll(pattern)) {
678
+ const object = Number(match[1]);
679
+ const generation = Number(match[2]);
680
+ const valueOffset = (match.index ?? 0) + match[0].length;
681
+ try {
682
+ const parser = new Parser(source2, valueOffset);
683
+ const value = parser.parse();
684
+ const after = parser.offset;
685
+ const streamMatch = source2.slice(after, after + 32).match(/^\s*stream\r?\n/);
686
+ let stream;
687
+ if (streamMatch) {
688
+ const start = after + streamMatch[0].length;
689
+ const declaredLength = isDict(value) ? value.entries.get("Length") : void 0;
690
+ if (typeof declaredLength === "number") {
691
+ stream = bytes.slice(start, start + declaredLength);
692
+ } else {
693
+ if (isRef(declaredLength)) indirectLengths.push({ object, start, length: declaredLength });
694
+ let end = source2.indexOf("endstream", start);
695
+ if (end !== -1) {
696
+ if (bytes[end - 1] === 10) end--;
697
+ if (bytes[end - 1] === 13) end--;
698
+ stream = bytes.slice(start, end);
699
+ }
700
+ }
701
+ }
702
+ objects.set(object, { object, generation, value, stream });
703
+ bodies.push([valueOffset, streamMatch ? after + streamMatch[0].length + (stream?.length ?? 0) : after]);
704
+ } catch {
705
+ }
706
+ }
707
+ for (const pending of indirectLengths) {
708
+ const length = objects.get(pending.length.object)?.value;
709
+ const record = objects.get(pending.object);
710
+ if (typeof length === "number" && record) {
711
+ record.stream = bytes.slice(pending.start, pending.start + length);
712
+ }
713
+ }
714
+ const trailers = trailerDictionaries(source2, bodies);
715
+ if (trailers.some(({ dict }) => dict.entries.has("Encrypt")) || [...objects.values()].some(({ value }) => isXrefStream(value) && value.entries.has("Encrypt"))) {
716
+ throw new PdfEncryptedError();
717
+ }
718
+ const startxref = lastStartxref(source2);
719
+ const trailer = newestTrailer(source2, startxref, trailers, objects) ?? { kind: "dict", entries: /* @__PURE__ */ new Map() };
720
+ const model = new _PdfModel(bytes, objects, trailer, startxref);
721
+ await model.expandObjectStreams();
722
+ return model;
723
+ }
724
+ /**
725
+ * The document catalog: the object the newest trailer's `/Root` names. A file
726
+ * with no readable trailer falls back to the first object typed `Catalog`.
727
+ */
728
+ catalog() {
729
+ const root = this.trailer.entries.get("Root");
730
+ if (isRef(root)) {
731
+ const record = this.objects.get(root.object);
732
+ if (!record || !isDict(record.value)) return void 0;
733
+ const type = record.value.entries.get("Type");
734
+ return type === void 0 || isName(type) && type.value === "Catalog" ? record : void 0;
735
+ }
736
+ return [...this.objects.values()].find((record) => {
737
+ if (!isDict(record.value)) return false;
738
+ const type = record.value.entries.get("Type");
739
+ return isName(type) && type.value === "Catalog";
740
+ });
741
+ }
742
+ resolve(value) {
743
+ return isRef(value) ? this.objects.get(value.object)?.value : value;
744
+ }
745
+ dict(value) {
746
+ const resolved = this.resolve(value);
747
+ return isDict(resolved) ? resolved : void 0;
748
+ }
749
+ record(value) {
750
+ return isRef(value) ? this.objects.get(value.object) : void 0;
751
+ }
752
+ markUpdated(value) {
753
+ if (!value) return;
754
+ this.updated.add(value.object);
755
+ }
756
+ addObject(value, stream) {
757
+ const object = nextObjectNumber(this.trailer, this.objects);
758
+ this.objects.set(object, { object, generation: 0, value, stream });
759
+ this.updated.add(object);
760
+ return { kind: "ref", object, generation: 0 };
761
+ }
762
+ /**
763
+ * The file with every updated object appended as an incremental update. The
764
+ * update trailer repeats the newest trailer's document entries (`/Info`,
765
+ * `/ID`) and points `/Root` at the catalog.
766
+ */
767
+ save() {
768
+ if (this.updated.size === 0) return this.bytes.slice();
769
+ const catalog = this.catalog();
770
+ if (!catalog) throw new Error("PDF catalog not found");
771
+ const records = [...this.updated].map((object) => this.objects.get(object)).filter((record) => record !== void 0);
772
+ const trailer = new Map(this.trailer.entries);
773
+ for (const key of SECTION_KEYS) trailer.delete(key);
774
+ trailer.set("Size", nextObjectNumber(this.trailer, this.objects));
775
+ trailer.set("Root", { kind: "ref", object: catalog.object, generation: catalog.generation });
776
+ if (this.startxref !== void 0) trailer.set("Prev", this.startxref);
777
+ return appendPdfUpdate(this.bytes, records, trailer);
778
+ }
779
+ async expandObjectStreams() {
780
+ for (const record of [...this.objects.values()]) {
781
+ if (!isDict(record.value) || !record.stream) continue;
782
+ const type = record.value.entries.get("Type");
783
+ if (!isName(type) || type.value !== "ObjStm") continue;
784
+ const count = record.value.entries.get("N");
785
+ const first = record.value.entries.get("First");
786
+ if (typeof count !== "number" || typeof first !== "number") continue;
787
+ const filter = record.value.entries.get("Filter");
788
+ const inflated = isName(filter) && filter.value === "FlateDecode" ? await inflate(record.stream) : record.stream;
789
+ const content = byteString(inflated);
790
+ const header = content.slice(0, first).trim().split(/\s+/).map(Number);
791
+ for (let index = 0; index < count; index++) {
792
+ const object = header[index * 2];
793
+ const offset = header[index * 2 + 1];
794
+ if (!Number.isInteger(object) || !Number.isInteger(offset)) continue;
795
+ try {
796
+ const parser = new Parser(content, first + offset);
797
+ if (!this.objects.has(object)) {
798
+ this.objects.set(object, { object, generation: 0, value: parser.parse() });
799
+ }
800
+ } catch {
801
+ }
802
+ }
803
+ }
804
+ }
805
+ };
806
+ function lastStartxref(source2) {
807
+ const tail = source2.slice(Math.max(0, source2.length - 2048));
808
+ const value = [...tail.matchAll(/startxref\s+(\d+)/g)].at(-1)?.[1];
809
+ return value === void 0 ? void 0 : Number(value);
810
+ }
811
+ function nextObjectNumber(trailer, objects) {
812
+ const size = trailer.entries.get("Size");
813
+ return Math.max(typeof size === "number" ? size : 0, Math.max(0, ...objects.keys()) + 1);
814
+ }
815
+
816
+ // src/pdf/acroform.ts
817
+ var MULTILINE = 1 << 12;
818
+ var COMBO = 1 << 17;
819
+ var MULTI_SELECT = 1 << 21;
820
+ var COMB = 1 << 24;
821
+ function nameValue(value) {
822
+ if (typeof value === "string") return value;
823
+ return isName(value) ? value.value : void 0;
824
+ }
825
+ function textValue(value) {
826
+ if (typeof value === "string") return decodeTextString(value);
827
+ return isName(value) ? value.value : void 0;
828
+ }
829
+ function classifyField(value, flags) {
830
+ if (value === "Tx") return "text";
831
+ if (value === "Ch") return "dropdown";
832
+ if (value === "Sig") return "signature";
833
+ if (value !== "Btn") return "unknown";
834
+ if ((flags & 1 << 16) !== 0) return "button";
835
+ if ((flags & 1 << 15) !== 0) return "radio";
836
+ return "checkbox";
837
+ }
838
+ function widget(model, value) {
839
+ const dict = model.dict(value);
840
+ if (!dict) return void 0;
841
+ const subtype = dict.entries.get("Subtype");
842
+ if (!isName(subtype) || subtype.value !== "Widget") return void 0;
843
+ return { ref: isRef(value) ? value : void 0, dict };
844
+ }
845
+ function isChildField(model, kid) {
846
+ const dict = model.dict(kid);
847
+ return widget(model, kid) === void 0 || dict?.entries.has("T") === true || dict?.entries.has("FT") === true || dict?.entries.has("Kids") === true;
848
+ }
849
+ function acroFields(model) {
850
+ const catalog = model.catalog();
851
+ if (!catalog || !isDict(catalog.value)) throw new Error("PDF catalog not found");
852
+ const acroValue = catalog.value.entries.get("AcroForm");
853
+ const acroForm = model.dict(acroValue);
854
+ if (!acroForm) return void 0;
855
+ const roots = model.resolve(acroForm.entries.get("Fields"));
856
+ const fields = [];
857
+ const formAppearance = model.resolve(acroForm.entries.get("DA"));
858
+ const formAlignment = model.resolve(acroForm.entries.get("Q"));
859
+ const visit = (value, inherited = {}) => {
860
+ const dict = model.dict(value);
861
+ if (!dict) return;
862
+ const ownName = textValue(dict.entries.get("T"));
863
+ const name2 = ownName ? inherited.name ? `${inherited.name}.${ownName}` : ownName : inherited.name;
864
+ const typeName = nameValue(dict.entries.get("FT")) ?? inherited.fieldType;
865
+ const flags = typeof dict.entries.get("Ff") === "number" ? dict.entries.get("Ff") : inherited.flags ?? 0;
866
+ const ownAppearance = model.resolve(dict.entries.get("DA"));
867
+ const ownAlignment = model.resolve(dict.entries.get("Q"));
868
+ const ownMaxLength = model.resolve(dict.entries.get("MaxLen"));
869
+ const settings = {
870
+ name: name2,
871
+ fieldType: typeName,
872
+ flags,
873
+ defaultAppearance: typeof ownAppearance === "string" ? ownAppearance : inherited.defaultAppearance,
874
+ alignment: typeof ownAlignment === "number" ? ownAlignment : inherited.alignment,
875
+ maxLength: typeof ownMaxLength === "number" ? ownMaxLength : inherited.maxLength
876
+ };
877
+ const kids = model.resolve(dict.entries.get("Kids"));
878
+ const widgets = Array.isArray(kids) ? kids.filter((child) => !isChildField(model, child)).map((child) => widget(model, child)).filter((item) => item !== void 0) : [];
879
+ const childFields = Array.isArray(kids) ? kids.filter((child) => isChildField(model, child)) : [];
880
+ if (childFields.length > 0) {
881
+ childFields.forEach((child) => visit(child, settings));
882
+ return;
883
+ }
884
+ if (!name2) return;
885
+ const self = widget(model, value);
886
+ fields.push({
887
+ ref: isRef(value) ? value : void 0,
888
+ dict,
889
+ name: name2,
890
+ type: classifyField(typeName, flags),
891
+ flags,
892
+ widgets: self ? [self] : widgets,
893
+ defaultAppearance: settings.defaultAppearance ?? (typeof formAppearance === "string" ? formAppearance : void 0),
894
+ alignment: settings.alignment ?? (typeof formAlignment === "number" ? formAlignment : 0),
895
+ maxLength: settings.maxLength
896
+ });
897
+ };
898
+ if (Array.isArray(roots)) roots.forEach((field) => visit(field));
899
+ return {
900
+ fields,
901
+ acroForm,
902
+ acroRef: isRef(acroValue) ? acroValue : void 0,
903
+ catalogRef: { kind: "ref", object: catalog.object, generation: catalog.generation }
904
+ };
905
+ }
906
+ function onState(model, widget2) {
907
+ const appearance = model.dict(widget2.dict.entries.get("AP"));
908
+ const normal = model.dict(appearance?.entries.get("N"));
909
+ return [...normal?.entries.keys() ?? []].find((name2) => name2 !== "Off") ?? "Yes";
910
+ }
911
+ function updateWidgetState(model, widget2, state) {
912
+ widget2.dict.entries.set("AS", { kind: "name", value: state });
913
+ if (widget2.ref) model.markUpdated(widget2.ref);
914
+ }
915
+ function textFieldDrawing(field) {
916
+ const multiline2 = (field.flags & MULTILINE) !== 0;
917
+ const comb = (field.flags & COMB) !== 0 && !multiline2 && field.maxLength !== void 0 && field.maxLength > 0 ? field.maxLength : void 0;
918
+ return comb === void 0 ? { multiline: multiline2 } : { multiline: multiline2, comb };
919
+ }
920
+ function widgetLayout(model, field, widget2, drawing) {
921
+ const rawRect = model.resolve(widget2.dict.entries.get("Rect"));
922
+ if (!Array.isArray(rawRect) || rawRect.length !== 4 || !rawRect.every((item) => typeof item === "number")) return void 0;
923
+ const [x1, y1, x2, y2] = rawRect;
924
+ const widgetAppearance = model.resolve(widget2.dict.entries.get("DA"));
925
+ const widgetAlignment = model.resolve(widget2.dict.entries.get("Q"));
926
+ return {
927
+ field: field.name,
928
+ width: Math.max(1, Math.abs(x2 - x1)),
929
+ height: Math.max(1, Math.abs(y2 - y1)),
930
+ appearance: parseDefaultAppearance(typeof widgetAppearance === "string" ? widgetAppearance : field.defaultAppearance),
931
+ alignment: typeof widgetAlignment === "number" ? widgetAlignment : field.alignment,
932
+ comb: drawing.comb,
933
+ multiline: drawing.multiline
934
+ };
935
+ }
936
+ function textAppearance(model, fonts, field, widget2, value) {
937
+ const layout = widgetLayout(model, field, widget2, value);
938
+ if (!layout) return;
939
+ const { width, height, appearance } = layout;
940
+ const font = fonts.select(field.name, value.text, appearance.fontName);
941
+ const text = layoutFieldText(layout, value.text, font);
942
+ const stream = new TextEncoder().encode(
943
+ `/Tx BMC
944
+ q
945
+ 0 0 ${width} ${height} re W n
946
+ BT
947
+ ${appearance.color}
948
+ ${text}
949
+ ET
950
+ Q
951
+ EMC`
952
+ );
953
+ const appearanceRef2 = model.addObject({
954
+ kind: "dict",
955
+ entries: /* @__PURE__ */ new Map([
956
+ ["Type", { kind: "name", value: "XObject" }],
957
+ ["Subtype", { kind: "name", value: "Form" }],
958
+ ["FormType", 1],
959
+ ["BBox", [0, 0, width, height]],
960
+ ["Resources", {
961
+ kind: "dict",
962
+ entries: /* @__PURE__ */ new Map([
963
+ ["Font", {
964
+ kind: "dict",
965
+ entries: /* @__PURE__ */ new Map([[font.resourceName, font.reference()]])
966
+ }]
967
+ ])
968
+ }]
969
+ ])
970
+ }, stream);
971
+ widget2.dict.entries.set("AP", {
972
+ kind: "dict",
973
+ entries: /* @__PURE__ */ new Map([["N", appearanceRef2]])
974
+ });
975
+ if (widget2.ref) model.markUpdated(widget2.ref);
976
+ }
977
+ function choiceOptions(model, field) {
978
+ const options = model.resolve(field.dict.entries.get("Opt"));
979
+ if (!Array.isArray(options)) return [];
980
+ return options.flatMap((option) => {
981
+ const resolved = model.resolve(option);
982
+ if (typeof resolved === "string") {
983
+ const text = decodeTextString(resolved);
984
+ return [{ value: text, display: text }];
985
+ }
986
+ if (Array.isArray(resolved)) {
987
+ const [exported, display] = resolved.map((item) => model.resolve(item));
988
+ if (typeof exported === "string") {
989
+ const value = decodeTextString(exported);
990
+ return [{ value, display: typeof display === "string" ? decodeTextString(display) : value }];
991
+ }
992
+ }
993
+ return [];
994
+ });
995
+ }
996
+ function setChoiceValue(model, fonts, field, value) {
997
+ const multiSelect = (field.flags & MULTI_SELECT) !== 0;
998
+ const selected = multiSelect && Array.isArray(value) ? value.map(String) : [String(value)];
999
+ const options = choiceOptions(model, field);
1000
+ const stored = selected.map(encodeTextString);
1001
+ field.dict.entries.set("V", multiSelect && stored.length !== 1 ? stored : stored[0] ?? "");
1002
+ const indices = selected.map((item) => options.findIndex((option) => option.value === item));
1003
+ if (indices.length > 0 && indices.every((index) => index >= 0)) {
1004
+ field.dict.entries.set("I", [...new Set(indices)].sort((left, right) => left - right));
1005
+ } else field.dict.entries.delete("I");
1006
+ const shown = selected.map((item) => options.find((option) => option.value === item)?.display ?? item);
1007
+ const listBox = (field.flags & COMBO) === 0;
1008
+ field.widgets.forEach((widget2) => textAppearance(model, fonts, field, widget2, {
1009
+ text: listBox ? shown.join("\n") : shown.join(", "),
1010
+ multiline: listBox
1011
+ }));
1012
+ }
1013
+ function setTextValue(model, fonts, field, value) {
1014
+ const text = String(value);
1015
+ field.dict.entries.set("V", encodeTextString(text));
1016
+ field.widgets.forEach((widget2) => textAppearance(model, fonts, field, widget2, { text, ...textFieldDrawing(field) }));
1017
+ }
1018
+ function setAcroFieldValue(model, fonts, field, value) {
1019
+ if (field.type === "text") setTextValue(model, fonts, field, value);
1020
+ else if (field.type === "dropdown") setChoiceValue(model, fonts, field, value);
1021
+ else if (field.type === "checkbox") {
1022
+ const checked = Boolean(value);
1023
+ const state = checked ? onState(model, field.widgets[0] ?? { dict: field.dict }) : "Off";
1024
+ field.dict.entries.set("V", { kind: "name", value: state });
1025
+ field.widgets.forEach((item) => updateWidgetState(model, item, checked ? onState(model, item) : "Off"));
1026
+ } else if (field.type === "radio") {
1027
+ const selected = String(value);
1028
+ field.dict.entries.set("V", { kind: "name", value: selected });
1029
+ field.widgets.forEach((item) => updateWidgetState(model, item, onState(model, item) === selected ? selected : "Off"));
1030
+ } else return;
1031
+ if (field.ref) model.markUpdated(field.ref);
1032
+ }
1033
+
1034
+ // src/pdf/page-tree.ts
1035
+ var INHERITED_PAGE_KEYS = ["Resources", "MediaBox", "CropBox", "Rotate"];
1036
+ function pageRecords(model, catalog) {
1037
+ const pages = [];
1038
+ const visited = /* @__PURE__ */ new Set();
1039
+ const visit = (value, inherited = /* @__PURE__ */ new Map()) => {
1040
+ const dict = model.dict(value);
1041
+ if (!dict || visited.has(dict)) return;
1042
+ visited.add(dict);
1043
+ const pageInherited = new Map(inherited);
1044
+ for (const key of INHERITED_PAGE_KEYS) {
1045
+ const own = dict.entries.get(key);
1046
+ if (own !== void 0) pageInherited.set(key, own);
1047
+ }
1048
+ const type = dict.entries.get("Type");
1049
+ if (isName(type) && type.value === "Page") {
1050
+ const ref = isRef(value) ? value : model.addObject(dict);
1051
+ const record = model.record(ref);
1052
+ if (record) pages.push({ record, ref, inherited: pageInherited });
1053
+ return;
1054
+ }
1055
+ const kids = model.resolve(dict.entries.get("Kids"));
1056
+ if (Array.isArray(kids)) kids.forEach((kid) => visit(kid, pageInherited));
1057
+ };
1058
+ visit(catalog.entries.get("Pages"));
1059
+ return pages;
1060
+ }
1061
+ function documentPages(model) {
1062
+ const catalog = model.catalog();
1063
+ if (!catalog || !isDict(catalog.value)) return [];
1064
+ return pageRecords(model, catalog.value);
1065
+ }
1066
+ function selfContainedPage(page) {
1067
+ if (!isDict(page.record.value)) throw new Error("PDF page object does not hold a dictionary");
1068
+ const entries = new Map(page.record.value.entries);
1069
+ for (const key of INHERITED_PAGE_KEYS) {
1070
+ if (!entries.has(key)) {
1071
+ const inherited = page.inherited.get(key);
1072
+ if (inherited !== void 0) entries.set(key, inherited);
1073
+ }
1074
+ }
1075
+ entries.delete("Parent");
1076
+ return { kind: "dict", entries };
1077
+ }
1078
+ function cloneDict(dict) {
1079
+ return { kind: "dict", entries: new Map(dict?.entries) };
1080
+ }
1081
+ function addPageResource(model, page, category, name2, ref) {
1082
+ if (!isDict(page.record.value)) return name2;
1083
+ const resources = cloneDict(
1084
+ model.dict(page.record.value.entries.get("Resources") ?? page.inherited.get("Resources"))
1085
+ );
1086
+ const entries = cloneDict(model.dict(resources.entries.get(category)));
1087
+ const holds = (candidate, value = entries.entries.get(candidate)) => value !== void 0 && !(isRef(value) && value.object === ref.object && value.generation === ref.generation);
1088
+ let free = name2;
1089
+ for (let suffix = 1; holds(free); suffix++) free = `${name2}_${suffix}`;
1090
+ entries.entries.set(free, ref);
1091
+ resources.entries.set(category, entries);
1092
+ page.record.value.entries.set("Resources", resources);
1093
+ model.markUpdated(page.record);
1094
+ return free;
1095
+ }
1096
+ function appendPageContent(model, page, stream) {
1097
+ if (!isDict(page.record.value)) return;
1098
+ const current = page.record.value.entries.get("Contents");
1099
+ const resolved = model.resolve(current);
1100
+ if (Array.isArray(resolved)) page.record.value.entries.set("Contents", [...resolved, stream]);
1101
+ else if (current === void 0) page.record.value.entries.set("Contents", stream);
1102
+ else page.record.value.entries.set("Contents", [current, stream]);
1103
+ model.markUpdated(page.record);
1104
+ }
1105
+
1106
+ // src/pdf/inspect.ts
1107
+ async function inspectPdf(template) {
1108
+ const model = await PdfModel.load(template);
1109
+ const pages = documentPages(model).map(({ inherited }, index) => {
1110
+ const box = model.resolve(inherited.get("MediaBox"));
1111
+ if (!Array.isArray(box) || box.length !== 4 || !box.every((item) => typeof item === "number")) {
1112
+ throw new Error(`PDF page ${index + 1} has no readable MediaBox`);
1113
+ }
1114
+ const [left, bottom, right, top] = box;
1115
+ return { page: index + 1, width: right - left, height: top - bottom };
1116
+ });
1117
+ return { pageCount: pages.length, pages };
1118
+ }
1119
+ async function inspectAcroFormFields(template, options = {}) {
1120
+ const model = await PdfModel.load(template);
1121
+ const form = acroFields(model);
1122
+ if (!form) return [];
1123
+ const pages = new Map(documentPages(model).map(({ ref }, index) => [ref.object, index + 1]));
1124
+ const result = [];
1125
+ for (const field of form.fields) {
1126
+ const { type, flags } = field;
1127
+ if (type === "button" && !options.includeButton) continue;
1128
+ if (type === "signature" && !options.includeSignature) continue;
1129
+ const rawValue = model.resolve(field.dict.entries.get("V"));
1130
+ let fieldValue;
1131
+ if (type === "checkbox") {
1132
+ const state = textValue(rawValue);
1133
+ fieldValue = state !== void 0 && state !== "Off";
1134
+ } else if (type === "dropdown") {
1135
+ const selected = Array.isArray(rawValue) ? rawValue : rawValue === void 0 ? [] : [rawValue];
1136
+ fieldValue = selected.map(textValue).filter((item) => item !== void 0);
1137
+ } else if (Array.isArray(rawValue)) fieldValue = rawValue.map(textValue).filter((item) => item !== void 0);
1138
+ else fieldValue = textValue(rawValue);
1139
+ const widget2 = field.widgets[0]?.dict;
1140
+ const rectangle = model.resolve(widget2?.entries.get("Rect"));
1141
+ const rect = Array.isArray(rectangle) && rectangle.length === 4 && rectangle.every((item) => typeof item === "number") ? rectangle : void 0;
1142
+ const pageRef = widget2?.entries.get("P");
1143
+ const page = isRef(pageRef) ? pages.get(pageRef.object) : void 0;
1144
+ result.push({
1145
+ name: field.name,
1146
+ type,
1147
+ value: fieldValue,
1148
+ required: type === "text" || type === "checkbox" || type === "dropdown" || type === "radio" ? (flags & 2) !== 0 : void 0,
1149
+ page,
1150
+ rect,
1151
+ maxLen: type === "text" ? field.maxLength ?? null : void 0
1152
+ });
1153
+ }
1154
+ return result;
1155
+ }
1156
+
1157
+ // src/pdf/flatten.ts
1158
+ var encoder = new TextEncoder();
1159
+ function appearanceRef(model, widget2) {
1160
+ const appearance = model.dict(widget2.dict.entries.get("AP"));
1161
+ const normal = appearance?.entries.get("N");
1162
+ if (isRef(normal) && model.record(normal)?.stream) return normal;
1163
+ const states = model.dict(normal);
1164
+ if (!states) return void 0;
1165
+ const state = widget2.dict.entries.get("AS");
1166
+ const selected = isName(state) ? state.value : "Off";
1167
+ const candidate = states.entries.get(selected) ?? states.entries.get("Off");
1168
+ return isRef(candidate) && model.record(candidate)?.stream ? candidate : void 0;
1169
+ }
1170
+ var HIDDEN = 2;
1171
+ var NO_VIEW = 32;
1172
+ function hidden(model, widget2) {
1173
+ const flags = model.resolve(widget2.dict.entries.get("F"));
1174
+ return typeof flags === "number" && (flags & (HIDDEN | NO_VIEW)) !== 0;
1175
+ }
1176
+ function widgetPage(model, pages, widget2) {
1177
+ const page = widget2.dict.entries.get("P");
1178
+ if (isRef(page)) return pages.find(({ record }) => record.object === page.object);
1179
+ if (!widget2.ref) return void 0;
1180
+ return pages.find(({ record }) => {
1181
+ if (!isDict(record.value)) return false;
1182
+ const annotations = model.resolve(record.value.entries.get("Annots"));
1183
+ return Array.isArray(annotations) && annotations.some((annotation) => isRef(annotation) && annotation.object === widget2.ref.object);
1184
+ });
1185
+ }
1186
+ function removeWidgetAnnotation(model, page, widget2) {
1187
+ if (!widget2.ref || !isDict(page.record.value)) return;
1188
+ const annotationsValue = page.record.value.entries.get("Annots");
1189
+ const annotations = model.resolve(annotationsValue);
1190
+ if (!Array.isArray(annotations)) return;
1191
+ const filtered = annotations.filter(
1192
+ (annotation) => !isRef(annotation) || annotation.object !== widget2.ref.object
1193
+ );
1194
+ if (filtered.length === annotations.length) return;
1195
+ if (isRef(annotationsValue)) {
1196
+ const record = model.record(annotationsValue);
1197
+ if (record) {
1198
+ record.value = filtered;
1199
+ model.markUpdated(record);
1200
+ }
1201
+ } else {
1202
+ page.record.value.entries.set("Annots", filtered);
1203
+ model.markUpdated(page.record);
1204
+ }
1205
+ }
1206
+ function placement(model, widget2, appearance) {
1207
+ const rectangle = model.resolve(widget2.dict.entries.get("Rect"));
1208
+ const appearanceDict = model.dict(appearance);
1209
+ const box = model.resolve(appearanceDict?.entries.get("BBox"));
1210
+ if (!Array.isArray(rectangle) || rectangle.length !== 4 || !rectangle.every((value) => typeof value === "number") || !Array.isArray(box) || box.length !== 4 || !box.every((value) => typeof value === "number")) return void 0;
1211
+ const [left, bottom, right, top] = rectangle;
1212
+ const [boxLeft, boxBottom, boxRight, boxTop] = box;
1213
+ const boxWidth = boxRight - boxLeft;
1214
+ const boxHeight = boxTop - boxBottom;
1215
+ if (boxWidth === 0 || boxHeight === 0) return void 0;
1216
+ const scaleX = (right - left) / boxWidth;
1217
+ const scaleY = (top - bottom) / boxHeight;
1218
+ const translateX = left - boxLeft * scaleX;
1219
+ const translateY = bottom - boxBottom * scaleY;
1220
+ return `${scaleX} 0 0 ${scaleY} ${translateX} ${translateY} cm`;
1221
+ }
1222
+ async function flattenPdf(template) {
1223
+ const model = await PdfModel.load(template);
1224
+ const form = acroFields(model);
1225
+ if (!form) return new Uint8Array(template);
1226
+ const pages = documentPages(model);
1227
+ let appearanceIndex = 0;
1228
+ for (const field of form.fields) {
1229
+ for (const widget2 of field.widgets) {
1230
+ const page = widgetPage(model, pages, widget2);
1231
+ if (!page) continue;
1232
+ const appearance = hidden(model, widget2) ? void 0 : appearanceRef(model, widget2);
1233
+ if (appearance) {
1234
+ const transform = placement(model, widget2, appearance);
1235
+ if (transform) {
1236
+ const name2 = addPageResource(model, page, "XObject", `PdrA${appearanceIndex++}`, appearance);
1237
+ appendPageContent(
1238
+ model,
1239
+ page,
1240
+ model.addObject(
1241
+ { kind: "dict", entries: /* @__PURE__ */ new Map() },
1242
+ encoder.encode(`q
1243
+ ${transform}
1244
+ /${name2} Do
1245
+ Q`)
1246
+ )
1247
+ );
1248
+ }
1249
+ }
1250
+ removeWidgetAnnotation(model, page, widget2);
1251
+ }
1252
+ }
1253
+ const catalog = form.catalogRef ? model.record(form.catalogRef) : void 0;
1254
+ if (catalog && isDict(catalog.value)) {
1255
+ catalog.value.entries.delete("AcroForm");
1256
+ model.markUpdated(catalog);
1257
+ }
1258
+ return model.save();
1259
+ }
1260
+
1261
+ // src/pdf/merge.ts
1262
+ var PdfMergeError = class extends Error {
1263
+ /** Zero-based position of the offending source in the input. */
1264
+ source;
1265
+ constructor(source2, detail) {
1266
+ super(`Cannot merge PDF ${source2 + 1}: ${detail}`);
1267
+ this.name = "PdfMergeError";
1268
+ this.source = source2;
1269
+ }
1270
+ };
1271
+ async function mergePdfs(sources) {
1272
+ if (sources.length === 0) throw new Error("At least one PDF is required to merge");
1273
+ if (sources.length === 1) {
1274
+ await PdfModel.load(new Uint8Array(sources[0]));
1275
+ return new Uint8Array(sources[0]);
1276
+ }
1277
+ const CATALOG = 1;
1278
+ const PAGE_TREE = 2;
1279
+ const pageTree = { kind: "ref", object: PAGE_TREE, generation: 0 };
1280
+ let next = 3;
1281
+ const objects = [];
1282
+ const pageRefs = [];
1283
+ for (const [index, source2] of sources.entries()) {
1284
+ try {
1285
+ const model = await PdfModel.load(new Uint8Array(source2));
1286
+ const catalog = model.catalog();
1287
+ if (!catalog || !isDict(catalog.value)) throw new PdfMergeError(index, "it has no catalog");
1288
+ const pages = pageRecords(model, catalog.value);
1289
+ if (pages.length === 0) throw new PdfMergeError(index, "it has no pages");
1290
+ const dictionaries = pages.map((page) => selfContainedPage(page));
1291
+ const reachable = new Set(pages.map((page) => page.ref.object));
1292
+ for (const dictionary of dictionaries) collectRefs(model, dictionary, reachable);
1293
+ const mapping = /* @__PURE__ */ new Map();
1294
+ for (const object of [...reachable].sort((a, b) => a - b)) {
1295
+ const record = model.objects.get(object);
1296
+ if (record && isDict(record.value)) {
1297
+ const type = record.value.entries.get("Type");
1298
+ if (isName(type) && (type.value === "ObjStm" || type.value === "XRef")) continue;
1299
+ }
1300
+ mapping.set(object, next++);
1301
+ }
1302
+ for (const [position, page] of pages.entries()) {
1303
+ const number = mapping.get(page.ref.object);
1304
+ const value = renumber(dictionaries[position], mapping);
1305
+ value.entries.set("Parent", pageTree);
1306
+ objects.push({ object: number, generation: 0, value, stream: page.record.stream });
1307
+ pageRefs.push({ kind: "ref", object: number, generation: 0 });
1308
+ }
1309
+ const pageObjects = new Set(pages.map((page) => page.ref.object));
1310
+ for (const [object, number] of mapping) {
1311
+ if (pageObjects.has(object)) continue;
1312
+ const record = model.objects.get(object);
1313
+ if (!record) continue;
1314
+ objects.push({ object: number, generation: 0, value: renumber(record.value, mapping), stream: record.stream });
1315
+ }
1316
+ } catch (error) {
1317
+ if (error instanceof PdfMergeError || error instanceof PdfEncryptedError) throw error;
1318
+ throw new PdfMergeError(
1319
+ index,
1320
+ `reading it failed: ${error instanceof Error ? error.message : String(error)}`
1321
+ );
1322
+ }
1323
+ }
1324
+ objects.push({
1325
+ object: PAGE_TREE,
1326
+ generation: 0,
1327
+ value: {
1328
+ kind: "dict",
1329
+ entries: /* @__PURE__ */ new Map([
1330
+ ["Type", { kind: "name", value: "Pages" }],
1331
+ ["Kids", pageRefs],
1332
+ ["Count", pageRefs.length]
1333
+ ])
1334
+ }
1335
+ });
1336
+ objects.push({
1337
+ object: CATALOG,
1338
+ generation: 0,
1339
+ value: {
1340
+ kind: "dict",
1341
+ entries: /* @__PURE__ */ new Map([
1342
+ ["Type", { kind: "name", value: "Catalog" }],
1343
+ ["Pages", pageTree]
1344
+ ])
1345
+ }
1346
+ });
1347
+ return writePdf(objects, /* @__PURE__ */ new Map([["Root", { kind: "ref", object: CATALOG, generation: 0 }]]));
1348
+ }
1349
+
1350
+ // src/pdf/select-pages.ts
1351
+ async function selectPdfPages(template, requestedPages) {
1352
+ if (requestedPages.length === 0) throw new Error("At least one PDF page must be selected");
1353
+ if (requestedPages.some((page) => !Number.isSafeInteger(page) || page < 1)) {
1354
+ throw new Error("PDF pages must be positive one-based integers");
1355
+ }
1356
+ if (new Set(requestedPages).size !== requestedPages.length) {
1357
+ throw new Error("PDF pages must not contain duplicates");
1358
+ }
1359
+ const model = await PdfModel.load(template);
1360
+ const catalog = model.catalog();
1361
+ if (!catalog || !isDict(catalog.value)) throw new Error("PDF catalog not found");
1362
+ const pages = pageRecords(model, catalog.value);
1363
+ const selectedNumbers = [...requestedPages].sort((left, right) => left - right);
1364
+ const invalid = selectedNumbers.filter((page) => page > pages.length);
1365
+ if (invalid.length > 0) {
1366
+ throw new Error(`PDF page ${invalid[0]} does not exist; document has ${pages.length} pages`);
1367
+ }
1368
+ if (selectedNumbers.length === pages.length && selectedNumbers.every((page, index) => page === index + 1)) {
1369
+ return new Uint8Array(template);
1370
+ }
1371
+ const selected = new Set(selectedNumbers.map((page) => page - 1));
1372
+ const kept = pages.filter((_, index) => selected.has(index));
1373
+ const dropped = pages.filter((_, index) => !selected.has(index));
1374
+ const excluded = droppedObjects(model, kept, dropped, catalog.value.entries.get("AcroForm"));
1375
+ const CATALOG = 1;
1376
+ const PAGE_TREE = 2;
1377
+ const pageTree = { kind: "ref", object: PAGE_TREE, generation: 0 };
1378
+ const catalogEntries = new Map(catalog.value.entries);
1379
+ catalogEntries.delete("Pages");
1380
+ const pageDictionaries = kept.map((page) => selfContainedPage(page));
1381
+ const info = model.trailer.entries.get("Info");
1382
+ excluded.add(catalog.object);
1383
+ const oldTree = catalog.value.entries.get("Pages");
1384
+ const reachable = new Set(kept.map((page) => page.ref.object));
1385
+ for (const dictionary of pageDictionaries) collectRefs(model, dictionary, reachable, excluded);
1386
+ collectRefs(model, { kind: "dict", entries: catalogEntries }, reachable, excluded);
1387
+ collectRefs(model, info, reachable, excluded);
1388
+ const mapping = /* @__PURE__ */ new Map([[catalog.object, CATALOG]]);
1389
+ if (isRef(oldTree)) mapping.set(oldTree.object, PAGE_TREE);
1390
+ let next = PAGE_TREE + 1;
1391
+ for (const object of [...reachable].sort((a, b) => a - b)) mapping.set(object, next++);
1392
+ const objects = [];
1393
+ for (const [position, page] of kept.entries()) {
1394
+ const value = renumber(pageDictionaries[position], mapping);
1395
+ if (isDict(value)) value.entries.set("Parent", pageTree);
1396
+ objects.push({ object: mapping.get(page.ref.object), generation: 0, value, stream: page.record.stream });
1397
+ }
1398
+ const keptPages = new Set(kept.map((page) => page.ref.object));
1399
+ for (const [object, number] of mapping) {
1400
+ if (keptPages.has(object) || number <= PAGE_TREE) continue;
1401
+ const record = model.objects.get(object);
1402
+ if (record) objects.push({ object: number, generation: 0, value: renumber(record.value, mapping), stream: record.stream });
1403
+ }
1404
+ const newCatalog = renumber({ kind: "dict", entries: catalogEntries }, mapping);
1405
+ if (isDict(newCatalog)) newCatalog.entries.set("Pages", pageTree);
1406
+ objects.push({ object: CATALOG, generation: 0, value: newCatalog });
1407
+ objects.push({
1408
+ object: PAGE_TREE,
1409
+ generation: 0,
1410
+ value: {
1411
+ kind: "dict",
1412
+ entries: /* @__PURE__ */ new Map([
1413
+ ["Type", { kind: "name", value: "Pages" }],
1414
+ ["Kids", kept.map((page) => ({ kind: "ref", object: mapping.get(page.ref.object), generation: 0 }))],
1415
+ ["Count", kept.length]
1416
+ ])
1417
+ }
1418
+ });
1419
+ const trailer = /* @__PURE__ */ new Map([["Root", { kind: "ref", object: CATALOG, generation: 0 }]]);
1420
+ if (info !== void 0) trailer.set("Info", renumber(info, mapping));
1421
+ const id = model.resolve(model.trailer.entries.get("ID"));
1422
+ if (Array.isArray(id)) trailer.set("ID", id);
1423
+ return writePdf(objects, trailer);
1424
+ }
1425
+ function droppedObjects(model, kept, dropped, acroForm) {
1426
+ const excluded = new Set(dropped.map((page) => page.ref.object));
1427
+ for (const record of model.objects.values()) {
1428
+ if (!isDict(record.value)) continue;
1429
+ const type = record.value.entries.get("Type");
1430
+ if (isName(type) && ["Pages", "ObjStm", "XRef"].includes(type.value)) excluded.add(record.object);
1431
+ }
1432
+ const annotations = (page) => {
1433
+ const value = isDict(page.record.value) ? model.resolve(page.record.value.entries.get("Annots")) : void 0;
1434
+ return Array.isArray(value) ? value : [];
1435
+ };
1436
+ const keptAnnotations = new Set(kept.flatMap(annotations).filter(isRef).map((ref) => ref.object));
1437
+ for (const annotation of dropped.flatMap(annotations)) {
1438
+ if (isRef(annotation) && !keptAnnotations.has(annotation.object)) excluded.add(annotation.object);
1439
+ }
1440
+ const visited = /* @__PURE__ */ new Set();
1441
+ const prune = (value) => {
1442
+ if (!isRef(value)) return false;
1443
+ if (excluded.has(value.object)) return true;
1444
+ if (visited.has(value.object)) return false;
1445
+ visited.add(value.object);
1446
+ const kids = model.resolve(model.dict(value)?.entries.get("Kids"));
1447
+ if (!Array.isArray(kids) || kids.length === 0) return false;
1448
+ const gone = kids.map(prune).every(Boolean);
1449
+ if (gone) excluded.add(value.object);
1450
+ return gone;
1451
+ };
1452
+ const fields = model.resolve(model.dict(acroForm)?.entries.get("Fields"));
1453
+ if (Array.isArray(fields)) fields.forEach(prune);
1454
+ return excluded;
1455
+ }
1456
+
1457
+ // src/pdf/render.ts
1458
+ import { defaultFormatter } from "@asterdoc/format";
1459
+
1460
+ // src/pdf/drawing-fonts.ts
1461
+ import { zlibSync } from "fflate";
1462
+
1463
+ // src/pdf/pages.ts
1464
+ async function decodeStream(model, dict, raw) {
1465
+ const filter = model.resolve(dict.entries.get("Filter"));
1466
+ const filters = [];
1467
+ if (isName(filter)) filters.push(filter.value);
1468
+ else if (Array.isArray(filter)) {
1469
+ for (const entry of filter) {
1470
+ const resolved = model.resolve(entry);
1471
+ if (isName(resolved)) filters.push(resolved.value);
1472
+ }
1473
+ }
1474
+ let bytes = raw;
1475
+ for (const name2 of filters) {
1476
+ if (name2 === "FlateDecode") {
1477
+ const parms = model.dict(dict.entries.get("DecodeParms"));
1478
+ const predictor = parms ? model.resolve(parms.entries.get("Predictor")) : void 0;
1479
+ if (typeof predictor === "number" && predictor > 1) {
1480
+ throw new Error("FlateDecode predictors are not supported in content streams");
1481
+ }
1482
+ bytes = await inflate(bytes);
1483
+ } else {
1484
+ throw new Error(`Unsupported stream filter: ${name2}`);
1485
+ }
1486
+ }
1487
+ return bytes;
1488
+ }
1489
+ function asNumberArray(model, value) {
1490
+ const resolved = model.resolve(value);
1491
+ if (!Array.isArray(resolved)) return void 0;
1492
+ const numbers = resolved.map((entry) => model.resolve(entry)).filter((entry) => typeof entry === "number");
1493
+ return numbers.length === resolved.length ? numbers : void 0;
1494
+ }
1495
+ async function loadPages(model) {
1496
+ const catalog = model.catalog();
1497
+ if (!catalog || !isDict(catalog.value)) throw new Error("PDF catalog not found");
1498
+ if (!model.dict(catalog.value.entries.get("Pages"))) throw new Error("PDF page tree not found");
1499
+ const pages = [];
1500
+ for (const { record, inherited } of pageRecords(model, catalog.value)) {
1501
+ if (!isDict(record.value)) continue;
1502
+ const mediaBox = asNumberArray(model, inherited.get("MediaBox"));
1503
+ const box = mediaBox && mediaBox.length === 4 ? mediaBox : [0, 0, 612, 792];
1504
+ pages.push({
1505
+ dict: record.value,
1506
+ resources: model.dict(inherited.get("Resources")),
1507
+ mediaBox: [box[0], box[1], box[2], box[3]],
1508
+ content: await pageContent(model, record.value)
1509
+ });
1510
+ }
1511
+ return pages;
1512
+ }
1513
+ async function pageContent(model, page) {
1514
+ const contents = page.entries.get("Contents");
1515
+ const parts = [];
1516
+ const append = async (value) => {
1517
+ if (isRef(value)) {
1518
+ const record = model.objects.get(value.object);
1519
+ if (record?.stream && isDict(record.value)) {
1520
+ parts.push(await decodeStream(model, record.value, record.stream));
1521
+ }
1522
+ return;
1523
+ }
1524
+ const resolved = model.resolve(value);
1525
+ if (Array.isArray(resolved)) {
1526
+ for (const entry of resolved) await append(entry);
1527
+ }
1528
+ };
1529
+ await append(contents);
1530
+ if (parts.length === 1) return parts[0];
1531
+ const total = parts.reduce((sum, part) => sum + part.length + 1, 0);
1532
+ const joined = new Uint8Array(total);
1533
+ let cursor = 0;
1534
+ for (const part of parts) {
1535
+ joined.set(part, cursor);
1536
+ cursor += part.length;
1537
+ joined[cursor++] = 10;
1538
+ }
1539
+ return joined;
1540
+ }
1541
+
1542
+ // src/pdf/win-ansi.ts
1543
+ var EXTENSIONS = "\u20AC\x81\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\x8D\u017D\x8F\x90\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\x9D\u017E\u0178";
1544
+ function winAnsiCodes(value) {
1545
+ return [...value].map((char) => {
1546
+ const code = char.codePointAt(0);
1547
+ if (char === "\n" || char === "\r") return 32;
1548
+ if (code >= 32 && code <= 126) return code;
1549
+ const extension = EXTENSIONS.indexOf(char);
1550
+ const byte = code >= 160 && code <= 255 ? code : extension >= 0 && code > 255 ? extension + 128 : void 0;
1551
+ if (byte === void 0) throw new Error(`Native PDF font cannot render U+${code.toString(16).toUpperCase()}; use a renderer with the required font and script support`);
1552
+ return byte;
1553
+ });
1554
+ }
1555
+ function winAnsiText(value) {
1556
+ return winAnsiCodes(value).map((code) => {
1557
+ if (code >= 32 && code <= 126) {
1558
+ const char = String.fromCharCode(code);
1559
+ return /[\\()]/.test(char) ? `\\${char}` : char;
1560
+ }
1561
+ return `\\${code.toString(8).padStart(3, "0")}`;
1562
+ }).join("");
1563
+ }
1564
+
1565
+ // src/pdf/standard-font.ts
1566
+ var HELVETICA_WIDTHS = [
1567
+ 278,
1568
+ 278,
1569
+ 355,
1570
+ 556,
1571
+ 556,
1572
+ 889,
1573
+ 667,
1574
+ 191,
1575
+ 333,
1576
+ 333,
1577
+ 389,
1578
+ 584,
1579
+ 278,
1580
+ 333,
1581
+ 278,
1582
+ 278,
1583
+ 556,
1584
+ 556,
1585
+ 556,
1586
+ 556,
1587
+ 556,
1588
+ 556,
1589
+ 556,
1590
+ 556,
1591
+ 556,
1592
+ 556,
1593
+ 278,
1594
+ 278,
1595
+ 584,
1596
+ 584,
1597
+ 584,
1598
+ 556,
1599
+ 1015,
1600
+ 667,
1601
+ 667,
1602
+ 722,
1603
+ 722,
1604
+ 667,
1605
+ 611,
1606
+ 778,
1607
+ 722,
1608
+ 278,
1609
+ 500,
1610
+ 667,
1611
+ 556,
1612
+ 833,
1613
+ 722,
1614
+ 778,
1615
+ 667,
1616
+ 778,
1617
+ 722,
1618
+ 667,
1619
+ 611,
1620
+ 722,
1621
+ 667,
1622
+ 944,
1623
+ 667,
1624
+ 667,
1625
+ 611,
1626
+ 278,
1627
+ 278,
1628
+ 278,
1629
+ 469,
1630
+ 556,
1631
+ 333,
1632
+ 556,
1633
+ 556,
1634
+ 500,
1635
+ 556,
1636
+ 556,
1637
+ 278,
1638
+ 556,
1639
+ 556,
1640
+ 222,
1641
+ 222,
1642
+ 500,
1643
+ 222,
1644
+ 833,
1645
+ 556,
1646
+ 556,
1647
+ 556,
1648
+ 556,
1649
+ 333,
1650
+ 500,
1651
+ 278,
1652
+ 556,
1653
+ 500,
1654
+ 722,
1655
+ 500,
1656
+ 500,
1657
+ 500,
1658
+ 334,
1659
+ 260,
1660
+ 334,
1661
+ 584,
1662
+ 0,
1663
+ 556,
1664
+ 0,
1665
+ 222,
1666
+ 556,
1667
+ 333,
1668
+ 1e3,
1669
+ 556,
1670
+ 556,
1671
+ 333,
1672
+ 1e3,
1673
+ 667,
1674
+ 333,
1675
+ 1e3,
1676
+ 0,
1677
+ 611,
1678
+ 0,
1679
+ 0,
1680
+ 222,
1681
+ 222,
1682
+ 333,
1683
+ 333,
1684
+ 350,
1685
+ 556,
1686
+ 1e3,
1687
+ 333,
1688
+ 1e3,
1689
+ 500,
1690
+ 333,
1691
+ 944,
1692
+ 0,
1693
+ 500,
1694
+ 500,
1695
+ 278,
1696
+ 333,
1697
+ 556,
1698
+ 556,
1699
+ 556,
1700
+ 556,
1701
+ 260,
1702
+ 556,
1703
+ 333,
1704
+ 737,
1705
+ 370,
1706
+ 556,
1707
+ 584,
1708
+ 333,
1709
+ 737,
1710
+ 333,
1711
+ 400,
1712
+ 584,
1713
+ 333,
1714
+ 333,
1715
+ 333,
1716
+ 556,
1717
+ 537,
1718
+ 278,
1719
+ 333,
1720
+ 333,
1721
+ 365,
1722
+ 556,
1723
+ 834,
1724
+ 834,
1725
+ 834,
1726
+ 611,
1727
+ 667,
1728
+ 667,
1729
+ 667,
1730
+ 667,
1731
+ 667,
1732
+ 667,
1733
+ 1e3,
1734
+ 722,
1735
+ 667,
1736
+ 667,
1737
+ 667,
1738
+ 667,
1739
+ 278,
1740
+ 278,
1741
+ 278,
1742
+ 278,
1743
+ 722,
1744
+ 722,
1745
+ 778,
1746
+ 778,
1747
+ 778,
1748
+ 778,
1749
+ 778,
1750
+ 584,
1751
+ 778,
1752
+ 722,
1753
+ 722,
1754
+ 722,
1755
+ 722,
1756
+ 667,
1757
+ 667,
1758
+ 611,
1759
+ 556,
1760
+ 556,
1761
+ 556,
1762
+ 556,
1763
+ 556,
1764
+ 556,
1765
+ 889,
1766
+ 500,
1767
+ 556,
1768
+ 556,
1769
+ 556,
1770
+ 556,
1771
+ 278,
1772
+ 278,
1773
+ 278,
1774
+ 278,
1775
+ 556,
1776
+ 556,
1777
+ 556,
1778
+ 556,
1779
+ 556,
1780
+ 556,
1781
+ 556,
1782
+ 584,
1783
+ 611,
1784
+ 556,
1785
+ 556,
1786
+ 556,
1787
+ 556,
1788
+ 500,
1789
+ 556,
1790
+ 500
1791
+ ];
1792
+ var helvetica = {
1793
+ ascent: 718,
1794
+ descent: -207,
1795
+ width(text) {
1796
+ return winAnsiCodes(text).reduce((total, code) => total + (HELVETICA_WIDTHS[code - 32] ?? 0), 0);
1797
+ }
1798
+ };
1799
+
1800
+ // src/pdf/truetype.ts
1801
+ var FontProgramError = class extends Error {
1802
+ problem;
1803
+ constructor(problem, detail) {
1804
+ super(detail);
1805
+ this.name = "FontProgramError";
1806
+ this.problem = problem;
1807
+ }
1808
+ };
1809
+ function tag(bytes, offset) {
1810
+ return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
1811
+ }
1812
+ function parseTrueType(bytes) {
1813
+ if (bytes.length < 12) throw new FontProgramError("unreadable", "the data is too short to be a font");
1814
+ const signature = tag(bytes, 0);
1815
+ if (signature === "OTTO") {
1816
+ throw new FontProgramError("unsupported-format", "it is an OpenType font with CFF outlines; use a font with TrueType outlines (.ttf)");
1817
+ }
1818
+ if (signature === "ttcf") throw new FontProgramError("unsupported-format", "it is a font collection; use a single .ttf font");
1819
+ if (signature === "wOFF" || signature === "wOF2") throw new FontProgramError("unsupported-format", "it is a WOFF web font; use the .ttf font it was made from");
1820
+ const version = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
1821
+ if (version !== 65536 && signature !== "true") throw new FontProgramError("unreadable", "it is not a TrueType or OpenType font");
1822
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1823
+ const u16 = (offset) => view.getUint16(offset);
1824
+ const i16 = (offset) => view.getInt16(offset);
1825
+ const u32 = (offset) => view.getUint32(offset);
1826
+ try {
1827
+ const tables = /* @__PURE__ */ new Map();
1828
+ const count = u16(4);
1829
+ for (let index = 0; index < count; index++) {
1830
+ const record = 12 + index * 16;
1831
+ const table = { offset: u32(record + 8), length: u32(record + 12) };
1832
+ if (table.offset + table.length > bytes.length) throw new FontProgramError("unreadable", `its ${tag(bytes, record)} table is truncated`);
1833
+ tables.set(tag(bytes, record), table);
1834
+ }
1835
+ const required = (name2) => {
1836
+ const table = tables.get(name2);
1837
+ if (!table) {
1838
+ throw new FontProgramError(name2 === "glyf" || name2 === "loca" ? "unsupported-format" : "unreadable", `it has no ${name2} table`);
1839
+ }
1840
+ return table;
1841
+ };
1842
+ const head = required("head").offset;
1843
+ const unitsPerEm = u16(head + 18);
1844
+ const bbox = [i16(head + 36), i16(head + 38), i16(head + 40), i16(head + 42)];
1845
+ const longLoca = i16(head + 50) === 1;
1846
+ const hhea = required("hhea").offset;
1847
+ const numberOfHMetrics = u16(hhea + 34);
1848
+ const numGlyphs = u16(required("maxp").offset + 4);
1849
+ const hmtx = required("hmtx").offset;
1850
+ const loca = required("loca").offset;
1851
+ required("glyf");
1852
+ let ascent = i16(hhea + 4);
1853
+ let descent = i16(hhea + 6);
1854
+ let capHeight = ascent;
1855
+ const os2 = tables.get("OS/2");
1856
+ if (os2) {
1857
+ const fsType = u16(os2.offset + 8);
1858
+ if ((fsType & 15) === 2) throw new FontProgramError("not-embeddable", "its license forbids embedding (restricted license)");
1859
+ if ((fsType & 512) !== 0) throw new FontProgramError("not-embeddable", "its license allows embedding bitmaps only");
1860
+ const os2Version = u16(os2.offset);
1861
+ if (os2Version >= 2 && os2.length >= 90) capHeight = i16(os2.offset + 88);
1862
+ if (ascent === 0 && descent === 0) {
1863
+ ascent = i16(os2.offset + 68);
1864
+ descent = i16(os2.offset + 70);
1865
+ }
1866
+ }
1867
+ const post = tables.get("post");
1868
+ const italicAngle = post ? view.getInt32(post.offset + 4) / 65536 : 0;
1869
+ const fixedPitch = post ? u32(post.offset + 12) !== 0 : false;
1870
+ const glyphOffset = (glyph) => longLoca ? u32(loca + glyph * 4) : u16(loca + glyph * 2) * 2;
1871
+ const hasOutline = (glyph) => glyph > 0 && glyph < numGlyphs && glyphOffset(glyph + 1) > glyphOffset(glyph);
1872
+ const advance = (glyph) => {
1873
+ const metric = Math.min(glyph, numberOfHMetrics - 1);
1874
+ return u16(hmtx + metric * 4);
1875
+ };
1876
+ const glyphFor = characterMap(tables.get("cmap"), u16, u32);
1877
+ const postScriptName = nameOf(tables.get("name"), bytes, u16) ?? "EmbeddedFont";
1878
+ return {
1879
+ postScriptName: postScriptName.replace(/[^!-~]|[()<>[\]{}/%#]/g, ""),
1880
+ unitsPerEm,
1881
+ ascent,
1882
+ descent,
1883
+ capHeight,
1884
+ bbox,
1885
+ italicAngle,
1886
+ fixedPitch,
1887
+ numGlyphs,
1888
+ glyphFor: (codePoint) => {
1889
+ const glyph = glyphFor(codePoint);
1890
+ return glyph < numGlyphs ? glyph : 0;
1891
+ },
1892
+ advance,
1893
+ hasOutline
1894
+ };
1895
+ } catch (error) {
1896
+ if (error instanceof FontProgramError) throw error;
1897
+ throw new FontProgramError("unreadable", "its tables could not be read");
1898
+ }
1899
+ }
1900
+ function characterMap(table, u16, u32) {
1901
+ if (!table) throw new FontProgramError("unreadable", "it has no cmap table");
1902
+ const subtables = [];
1903
+ const count = u16(table.offset + 2);
1904
+ for (let index = 0; index < count; index++) {
1905
+ const record = table.offset + 4 + index * 8;
1906
+ const offset = table.offset + u32(record + 4);
1907
+ subtables.push({ platform: u16(record), encoding: u16(record + 2), offset, format: u16(offset) });
1908
+ }
1909
+ const unicode = (entry) => entry.platform === 0 || entry.platform === 3 && (entry.encoding === 1 || entry.encoding === 10);
1910
+ const full = subtables.find((entry) => unicode(entry) && entry.format === 12);
1911
+ if (full) {
1912
+ const groups = u32(full.offset + 12);
1913
+ return (codePoint) => {
1914
+ let low = 0;
1915
+ let high = groups - 1;
1916
+ while (low <= high) {
1917
+ const middle = low + high >> 1;
1918
+ const group = full.offset + 16 + middle * 12;
1919
+ const start = u32(group);
1920
+ const end = u32(group + 4);
1921
+ if (codePoint < start) high = middle - 1;
1922
+ else if (codePoint > end) low = middle + 1;
1923
+ else return u32(group + 8) + codePoint - start;
1924
+ }
1925
+ return 0;
1926
+ };
1927
+ }
1928
+ const basic = subtables.find((entry) => unicode(entry) && entry.format === 4);
1929
+ if (!basic) throw new FontProgramError("unsupported-format", "it has no Unicode character map");
1930
+ const segments = u16(basic.offset + 6) / 2;
1931
+ const ends = basic.offset + 14;
1932
+ const starts = ends + segments * 2 + 2;
1933
+ const deltas = starts + segments * 2;
1934
+ const ranges = deltas + segments * 2;
1935
+ return (codePoint) => {
1936
+ if (codePoint > 65535) return 0;
1937
+ for (let segment = 0; segment < segments; segment++) {
1938
+ if (codePoint > u16(ends + segment * 2)) continue;
1939
+ const start = u16(starts + segment * 2);
1940
+ if (codePoint < start) return 0;
1941
+ const delta = u16(deltas + segment * 2);
1942
+ const rangeOffset = u16(ranges + segment * 2);
1943
+ if (rangeOffset === 0) return codePoint + delta & 65535;
1944
+ const glyph = u16(ranges + segment * 2 + rangeOffset + (codePoint - start) * 2);
1945
+ return glyph === 0 ? 0 : glyph + delta & 65535;
1946
+ }
1947
+ return 0;
1948
+ };
1949
+ }
1950
+ function nameOf(table, bytes, u16) {
1951
+ if (!table) return void 0;
1952
+ const count = u16(table.offset + 2);
1953
+ const storage = table.offset + u16(table.offset + 4);
1954
+ for (let index = 0; index < count; index++) {
1955
+ const record = table.offset + 6 + index * 12;
1956
+ if (u16(record + 6) !== 6) continue;
1957
+ const platform = u16(record);
1958
+ const length = u16(record + 8);
1959
+ const start = storage + u16(record + 10);
1960
+ const raw = bytes.subarray(start, start + length);
1961
+ if (platform === 3 || platform === 0) {
1962
+ let text = "";
1963
+ for (let offset = 0; offset + 1 < raw.length; offset += 2) text += String.fromCharCode(raw[offset] << 8 | raw[offset + 1]);
1964
+ return text;
1965
+ }
1966
+ return String.fromCharCode(...raw);
1967
+ }
1968
+ return void 0;
1969
+ }
1970
+
1971
+ // src/pdf/drawing-fonts.ts
1972
+ var PdfFontError = class extends Error {
1973
+ source;
1974
+ problem;
1975
+ constructor(source2, problem, detail) {
1976
+ super(`Cannot use font "${source2}": ${detail}`);
1977
+ this.name = "PdfFontError";
1978
+ this.source = source2;
1979
+ this.problem = problem;
1980
+ }
1981
+ };
1982
+ var SHAPED_SCRIPTS = [
1983
+ "Arabic",
1984
+ "Hebrew",
1985
+ "Syriac",
1986
+ "Thaana",
1987
+ "Nko",
1988
+ "Adlam",
1989
+ "Mandaic",
1990
+ "Samaritan",
1991
+ "Devanagari",
1992
+ "Bengali",
1993
+ "Gurmukhi",
1994
+ "Gujarati",
1995
+ "Oriya",
1996
+ "Tamil",
1997
+ "Telugu",
1998
+ "Kannada",
1999
+ "Malayalam",
2000
+ "Sinhala",
2001
+ "Thai",
2002
+ "Lao",
2003
+ "Tibetan",
2004
+ "Myanmar",
2005
+ "Khmer",
2006
+ "Mongolian"
2007
+ ].map((script) => ({ script, pattern: new RegExp(`\\p{Script=${script}}`, "u") }));
2008
+ var LINE_BREAK = /[\r\n]/u;
2009
+ var WHITESPACE = /\s/u;
2010
+ function shapedScript(text) {
2011
+ return SHAPED_SCRIPTS.find(({ pattern }) => pattern.test(text))?.script;
2012
+ }
2013
+ function programCovers(program, text) {
2014
+ return [...text].every((character) => {
2015
+ if (LINE_BREAK.test(character)) return true;
2016
+ const glyph = program.glyphFor(character.codePointAt(0));
2017
+ return glyph !== 0 && (WHITESPACE.test(character) || program.hasOutline(glyph));
2018
+ });
2019
+ }
2020
+ var hex4 = (value) => value.toString(16).padStart(4, "0").toUpperCase();
2021
+ var StandardHelvetica = class {
2022
+ constructor(model) {
2023
+ this.model = model;
2024
+ }
2025
+ model;
2026
+ resourceName = "PdrFontHelvetica";
2027
+ ascent = helvetica.ascent;
2028
+ descent = helvetica.descent;
2029
+ ref;
2030
+ covers(text) {
2031
+ try {
2032
+ winAnsiCodes(text);
2033
+ return true;
2034
+ } catch {
2035
+ return false;
2036
+ }
2037
+ }
2038
+ width(text) {
2039
+ return helvetica.width(text);
2040
+ }
2041
+ encode(text) {
2042
+ return `(${winAnsiText(text)})`;
2043
+ }
2044
+ reference() {
2045
+ this.ref ??= this.model.addObject({
2046
+ kind: "dict",
2047
+ entries: /* @__PURE__ */ new Map([
2048
+ ["Type", { kind: "name", value: "Font" }],
2049
+ ["Subtype", { kind: "name", value: "Type1" }],
2050
+ ["BaseFont", { kind: "name", value: "Helvetica" }],
2051
+ ["Encoding", { kind: "name", value: "WinAnsiEncoding" }]
2052
+ ])
2053
+ });
2054
+ return this.ref;
2055
+ }
2056
+ finish() {
2057
+ }
2058
+ };
2059
+ var EmbeddedTrueType = class {
2060
+ constructor(model, program, resourceName, file) {
2061
+ this.model = model;
2062
+ this.program = program;
2063
+ this.resourceName = resourceName;
2064
+ this.file = file;
2065
+ this.scale = 1e3 / program.unitsPerEm;
2066
+ this.ascent = program.ascent * this.scale;
2067
+ this.descent = program.descent * this.scale;
2068
+ }
2069
+ model;
2070
+ program;
2071
+ resourceName;
2072
+ file;
2073
+ ascent;
2074
+ descent;
2075
+ scale;
2076
+ used = /* @__PURE__ */ new Map();
2077
+ font;
2078
+ descendant;
2079
+ type0;
2080
+ glyph(character) {
2081
+ return this.program.glyphFor(character.codePointAt(0));
2082
+ }
2083
+ covers(text) {
2084
+ return programCovers(this.program, text);
2085
+ }
2086
+ width(text) {
2087
+ let total = 0;
2088
+ for (const character of text) {
2089
+ if (!LINE_BREAK.test(character)) total += this.program.advance(this.glyph(character)) * this.scale;
2090
+ }
2091
+ return total;
2092
+ }
2093
+ encode(text) {
2094
+ let hex = "";
2095
+ for (const character of text) {
2096
+ if (LINE_BREAK.test(character)) continue;
2097
+ const glyph = this.glyph(character);
2098
+ if (!this.used.has(glyph)) this.used.set(glyph, character);
2099
+ hex += hex4(glyph);
2100
+ }
2101
+ return `<${hex}>`;
2102
+ }
2103
+ reference() {
2104
+ if (this.font) return this.font;
2105
+ const { program, model } = this;
2106
+ const fontFile = this.file instanceof Uint8Array ? model.addObject({
2107
+ kind: "dict",
2108
+ entries: /* @__PURE__ */ new Map([
2109
+ ["Filter", { kind: "name", value: "FlateDecode" }],
2110
+ ["Length1", this.file.length]
2111
+ ])
2112
+ }, zlibSync(this.file)) : this.file;
2113
+ const name2 = { kind: "name", value: program.postScriptName };
2114
+ const descriptor = model.addObject({
2115
+ kind: "dict",
2116
+ entries: /* @__PURE__ */ new Map([
2117
+ ["Type", { kind: "name", value: "FontDescriptor" }],
2118
+ ["FontName", name2],
2119
+ // Symbolic: the glyphs are addressed by index, not by a standard encoding.
2120
+ ["Flags", 4 | (program.fixedPitch ? 1 : 0)],
2121
+ ["FontBBox", program.bbox.map((value) => Math.round(value * this.scale))],
2122
+ ["ItalicAngle", program.italicAngle],
2123
+ ["Ascent", Math.round(this.ascent)],
2124
+ ["Descent", Math.round(this.descent)],
2125
+ ["CapHeight", Math.round(program.capHeight * this.scale)],
2126
+ ["StemV", 80],
2127
+ ["FontFile2", fontFile]
2128
+ ])
2129
+ });
2130
+ this.descendant = {
2131
+ kind: "dict",
2132
+ entries: /* @__PURE__ */ new Map([
2133
+ ["Type", { kind: "name", value: "Font" }],
2134
+ ["Subtype", { kind: "name", value: "CIDFontType2" }],
2135
+ ["BaseFont", name2],
2136
+ ["CIDSystemInfo", {
2137
+ kind: "dict",
2138
+ entries: /* @__PURE__ */ new Map([["Registry", "Adobe"], ["Ordering", "Identity"], ["Supplement", 0]])
2139
+ }],
2140
+ ["FontDescriptor", descriptor],
2141
+ ["CIDToGIDMap", { kind: "name", value: "Identity" }],
2142
+ ["DW", Math.round(program.advance(0) * this.scale)]
2143
+ ])
2144
+ };
2145
+ this.type0 = {
2146
+ kind: "dict",
2147
+ entries: /* @__PURE__ */ new Map([
2148
+ ["Type", { kind: "name", value: "Font" }],
2149
+ ["Subtype", { kind: "name", value: "Type0" }],
2150
+ ["BaseFont", name2],
2151
+ ["Encoding", { kind: "name", value: "Identity-H" }],
2152
+ ["DescendantFonts", [model.addObject(this.descendant)]]
2153
+ ])
2154
+ };
2155
+ this.font = model.addObject(this.type0);
2156
+ return this.font;
2157
+ }
2158
+ finish() {
2159
+ if (!this.descendant || !this.type0) return;
2160
+ const glyphs = [...this.used.keys()].sort((left, right) => left - right);
2161
+ this.descendant.entries.set("W", glyphs.flatMap((glyph) => [
2162
+ glyph,
2163
+ [Math.round(this.program.advance(glyph) * this.scale)]
2164
+ ]));
2165
+ const entries = glyphs.map((glyph) => {
2166
+ const text = this.used.get(glyph);
2167
+ let units = "";
2168
+ for (let index = 0; index < text.length; index++) units += hex4(text.charCodeAt(index));
2169
+ return `<${hex4(glyph)}> <${units}>`;
2170
+ });
2171
+ const blocks = [];
2172
+ for (let start = 0; start < entries.length; start += 100) {
2173
+ const block = entries.slice(start, start + 100);
2174
+ blocks.push(`${block.length} beginbfchar
2175
+ ${block.join("\n")}
2176
+ endbfchar`);
2177
+ }
2178
+ const cmap = [
2179
+ "/CIDInit /ProcSet findresource begin",
2180
+ "12 dict begin",
2181
+ "begincmap",
2182
+ "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def",
2183
+ "/CMapName /Adobe-Identity-UCS def",
2184
+ "/CMapType 2 def",
2185
+ "1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange",
2186
+ ...blocks,
2187
+ "endcmap",
2188
+ "CMapName currentdict /CMap defineresource pop",
2189
+ "end",
2190
+ "end"
2191
+ ].join("\n");
2192
+ this.type0.entries.set("ToUnicode", this.model.addObject({ kind: "dict", entries: /* @__PURE__ */ new Map() }, new TextEncoder().encode(cmap)));
2193
+ }
2194
+ };
2195
+ function parseSupplied(font) {
2196
+ try {
2197
+ return parseTrueType(font.bytes);
2198
+ } catch (error) {
2199
+ if (error instanceof FontProgramError) throw new PdfFontError(font.source, error.problem, error.message);
2200
+ throw error;
2201
+ }
2202
+ }
2203
+ async function formFontProgram(model, font) {
2204
+ const subtype = model.resolve(font.entries.get("Subtype"));
2205
+ let described = font;
2206
+ if (isName(subtype) && subtype.value === "Type0") {
2207
+ const descendants = model.resolve(font.entries.get("DescendantFonts"));
2208
+ described = Array.isArray(descendants) ? model.dict(descendants[0]) : void 0;
2209
+ }
2210
+ const descriptor = model.dict(described?.entries.get("FontDescriptor"));
2211
+ const file = descriptor?.entries.get("FontFile2");
2212
+ const record = model.record(file);
2213
+ if (!isRef(file) || !record?.stream || !model.dict(file)) return void 0;
2214
+ try {
2215
+ return { program: parseTrueType(await decodeStream(model, model.dict(file), record.stream)), file };
2216
+ } catch {
2217
+ return void 0;
2218
+ }
2219
+ }
2220
+ var PdfFontSet = class _PdfFontSet {
2221
+ constructor(model, supplied) {
2222
+ this.model = model;
2223
+ this.helvetica = new StandardHelvetica(model);
2224
+ this.supplied = supplied;
2225
+ }
2226
+ model;
2227
+ helvetica;
2228
+ supplied;
2229
+ formFonts = /* @__PURE__ */ new Map();
2230
+ overlayFonts = /* @__PURE__ */ new Map();
2231
+ drawn = /* @__PURE__ */ new Set();
2232
+ /** Read the supplied fonts, failing now, naming its source, on one that cannot be used. */
2233
+ static create(model, options = {}) {
2234
+ return new _PdfFontSet(model, [
2235
+ options.font && new EmbeddedTrueType(model, parseSupplied(options.font), "PdrFontOverride", options.font.bytes),
2236
+ options.layerFont && new EmbeddedTrueType(model, parseSupplied(options.layerFont), "PdrFontLayer", options.layerFont.bytes)
2237
+ ].filter((font) => font !== void 0));
2238
+ }
2239
+ /** Read the embedded TrueType fonts in the form's default resources (`/DR`). */
2240
+ async readFormFonts(resources) {
2241
+ const fonts = this.model.dict(resources?.entries.get("Font"));
2242
+ for (const [name2, value] of fonts?.entries ?? []) {
2243
+ const dict = this.model.dict(value);
2244
+ const found = dict ? await formFontProgram(this.model, dict) : void 0;
2245
+ if (found) {
2246
+ this.formFonts.set(name2, new EmbeddedTrueType(this.model, found.program, `PdrFontForm${this.formFonts.size}`, found.file));
2247
+ }
2248
+ }
2249
+ }
2250
+ /** A font one overlay names, read on first use and embedded once however many overlays name it. */
2251
+ overlayFont(font) {
2252
+ let embedded = this.overlayFonts.get(font.bytes);
2253
+ if (!embedded) {
2254
+ embedded = new EmbeddedTrueType(this.model, parseSupplied(font), `PdrFontOverlay${this.overlayFonts.size}`, font.bytes);
2255
+ this.overlayFonts.set(font.bytes, embedded);
2256
+ }
2257
+ return embedded;
2258
+ }
2259
+ /**
2260
+ * The first font that can draw every character of `text`.
2261
+ *
2262
+ * @param subject - The field or overlay, named in errors.
2263
+ * @param formFont - The form font resource the field's appearance names.
2264
+ * @param overlayFont - The font a text overlay names; it is tried first.
2265
+ * @throws {PdfFieldFillError} for a script that needs shaping, or a character no font can draw.
2266
+ * @throws {PdfFontError} when `overlayFont` cannot be used.
2267
+ */
2268
+ select(subject, text, formFont, overlayFont) {
2269
+ const script = shapedScript(text);
2270
+ if (script) {
2271
+ throw new PdfFieldFillError(
2272
+ subject,
2273
+ "unsupported-script",
2274
+ { script },
2275
+ `the value contains ${script} script, which needs glyph shaping that PDF form filling and overlays do not do. Render documents in this script from a React composition layer.`
2276
+ );
2277
+ }
2278
+ const own = formFont === void 0 ? void 0 : this.formFonts.get(formFont);
2279
+ const candidates = [
2280
+ ...overlayFont ? [this.overlayFont(overlayFont)] : [],
2281
+ ...this.supplied,
2282
+ ...own ? [own] : [],
2283
+ this.helvetica
2284
+ ];
2285
+ const chosen = candidates.find((font) => font.covers(text));
2286
+ if (chosen) {
2287
+ this.drawn.add(chosen);
2288
+ return chosen;
2289
+ }
2290
+ const characters = [...text].filter((character2) => !LINE_BREAK.test(character2));
2291
+ const uncovered = characters.find((item) => candidates.every((font) => !font.covers(item)));
2292
+ const character = uncovered ?? characters.find((item) => !candidates[0].covers(item));
2293
+ const code = `U+${hex4(character.codePointAt(0))}`;
2294
+ throw new PdfFieldFillError(
2295
+ subject,
2296
+ "missing-glyph",
2297
+ { character },
2298
+ (uncovered ? `no available font can draw "${character}" (${code}).` : `no single available font can draw every character of the value; the first font lacks "${character}" (${code}).`) + " Declare a font that covers the value on the PDF layer, or supply one at render time."
2299
+ );
2300
+ }
2301
+ /** Write widths and Unicode maps for every embedded font this render drew with. */
2302
+ finish() {
2303
+ for (const font of this.drawn) font.finish();
2304
+ }
2305
+ };
2306
+ function pdfFontMetrics(font) {
2307
+ const program = parseSupplied(font);
2308
+ return {
2309
+ canDraw: (text) => shapedScript(text) === void 0 && programCovers(program, text),
2310
+ width: (text, size) => {
2311
+ let units = 0;
2312
+ for (const character of text) {
2313
+ if (!LINE_BREAK.test(character)) units += program.advance(program.glyphFor(character.codePointAt(0)));
2314
+ }
2315
+ return units * size / program.unitsPerEm;
2316
+ }
2317
+ };
2318
+ }
2319
+
2320
+ // src/pdf/overlay.ts
2321
+ import { unzlibSync, zlibSync as zlibSync2 } from "fflate";
2322
+ function textValue2(overlay, data) {
2323
+ if ("text" in overlay) return overlay.text;
2324
+ return getPath(data, renderDataPath(data, overlay.field));
2325
+ }
2326
+ function overlaySubject(overlay, index) {
2327
+ return "field" in overlay && overlay.field ? overlay.field : `text overlay ${index + 1} (page ${overlay.page})`;
2328
+ }
2329
+ function fittedFontSize(text, overlay, font, subject) {
2330
+ const declared = overlay.fontSize ?? 12;
2331
+ let size = Math.min(declared, overlay.height ?? Number.POSITIVE_INFINITY);
2332
+ const width = overlay.width;
2333
+ if (width === void 0) return size;
2334
+ const floor = Math.min(MIN_FONT_SIZE, size);
2335
+ const unitWidth = font.width(text) / 1e3;
2336
+ if (unitWidth * size <= width) return size;
2337
+ size = width / unitWidth;
2338
+ if (size < floor) {
2339
+ throw new PdfFieldFillError(subject, "overflow", { limit: floor }, `the text does not fit its ${width} pt width at the minimum size of ${floor} pt`);
2340
+ }
2341
+ return size;
2342
+ }
2343
+ function readUint32(bytes, offset) {
2344
+ return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
2345
+ }
2346
+ function concatBytes(chunks) {
2347
+ const bytes = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.length, 0));
2348
+ let offset = 0;
2349
+ for (const chunk of chunks) {
2350
+ bytes.set(chunk, offset);
2351
+ offset += chunk.length;
2352
+ }
2353
+ return bytes;
2354
+ }
2355
+ function paeth(left, above, upperLeft) {
2356
+ const estimate = left + above - upperLeft;
2357
+ const leftDistance = Math.abs(estimate - left);
2358
+ const aboveDistance = Math.abs(estimate - above);
2359
+ const upperLeftDistance = Math.abs(estimate - upperLeft);
2360
+ if (leftDistance <= aboveDistance && leftDistance <= upperLeftDistance) return left;
2361
+ return aboveDistance <= upperLeftDistance ? above : upperLeft;
2362
+ }
2363
+ function decodePng(bytes) {
2364
+ const signature = [137, 80, 78, 71, 13, 10, 26, 10];
2365
+ if (!signature.every((byte, index) => bytes[index] === byte)) throw new Error("Invalid PNG image");
2366
+ let width = 0;
2367
+ let height = 0;
2368
+ let bitDepth = 0;
2369
+ let colorType = 0;
2370
+ let interlace = 0;
2371
+ const idat = [];
2372
+ for (let offset = 8; offset + 12 <= bytes.length; ) {
2373
+ const length = readUint32(bytes, offset);
2374
+ const type = String.fromCharCode(...bytes.slice(offset + 4, offset + 8));
2375
+ const data = bytes.slice(offset + 8, offset + 8 + length);
2376
+ if (type === "IHDR") {
2377
+ width = readUint32(data, 0);
2378
+ height = readUint32(data, 4);
2379
+ bitDepth = data[8];
2380
+ colorType = data[9];
2381
+ interlace = data[12];
2382
+ } else if (type === "IDAT") idat.push(data);
2383
+ offset += length + 12;
2384
+ if (type === "IEND") break;
2385
+ }
2386
+ if (width < 1 || height < 1 || bitDepth !== 8 || interlace !== 0 || ![0, 2, 4, 6].includes(colorType)) {
2387
+ throw new Error("PNG overlays require non-interlaced 8-bit grayscale, RGB, grayscale-alpha, or RGBA images");
2388
+ }
2389
+ const channels = { 0: 1, 2: 3, 4: 2, 6: 4 }[colorType];
2390
+ const rowLength = width * channels;
2391
+ const inflated = unzlibSync(concatBytes(idat));
2392
+ if (inflated.length < (rowLength + 1) * height) throw new Error("PNG image data is truncated");
2393
+ const decoded = new Uint8Array(rowLength * height);
2394
+ let source2 = 0;
2395
+ for (let row = 0; row < height; row++) {
2396
+ const filter = inflated[source2++];
2397
+ const rowOffset = row * rowLength;
2398
+ for (let column = 0; column < rowLength; column++) {
2399
+ const raw = inflated[source2++];
2400
+ const left = column >= channels ? decoded[rowOffset + column - channels] : 0;
2401
+ const above = row > 0 ? decoded[rowOffset + column - rowLength] : 0;
2402
+ const upperLeft = row > 0 && column >= channels ? decoded[rowOffset + column - rowLength - channels] : 0;
2403
+ const predictor = filter === 0 ? 0 : filter === 1 ? left : filter === 2 ? above : filter === 3 ? Math.floor((left + above) / 2) : filter === 4 ? paeth(left, above, upperLeft) : Number.NaN;
2404
+ if (Number.isNaN(predictor)) throw new Error(`Unsupported PNG filter ${filter}`);
2405
+ decoded[rowOffset + column] = raw + predictor & 255;
2406
+ }
2407
+ }
2408
+ const hasAlpha = colorType === 4 || colorType === 6;
2409
+ const colorChannels = colorType === 0 || colorType === 4 ? 1 : 3;
2410
+ const pixels = new Uint8Array(width * height * colorChannels);
2411
+ const alpha = hasAlpha ? new Uint8Array(width * height) : void 0;
2412
+ for (let pixel = 0; pixel < width * height; pixel++) {
2413
+ const input = pixel * channels;
2414
+ const output = pixel * colorChannels;
2415
+ pixels.set(decoded.slice(input, input + colorChannels), output);
2416
+ if (alpha) alpha[pixel] = decoded[input + channels - 1];
2417
+ }
2418
+ return { width, height, colorSpace: colorChannels === 1 ? "DeviceGray" : "DeviceRGB", pixels, alpha };
2419
+ }
2420
+ function embedPng(model, bytes) {
2421
+ const png = decodePng(bytes);
2422
+ const alphaRef = png.alpha ? model.addObject({
2423
+ kind: "dict",
2424
+ entries: /* @__PURE__ */ new Map([
2425
+ ["Type", { kind: "name", value: "XObject" }],
2426
+ ["Subtype", { kind: "name", value: "Image" }],
2427
+ ["Width", png.width],
2428
+ ["Height", png.height],
2429
+ ["ColorSpace", { kind: "name", value: "DeviceGray" }],
2430
+ ["BitsPerComponent", 8],
2431
+ ["Filter", { kind: "name", value: "FlateDecode" }]
2432
+ ])
2433
+ }, zlibSync2(png.alpha)) : void 0;
2434
+ const entries = /* @__PURE__ */ new Map([
2435
+ ["Type", { kind: "name", value: "XObject" }],
2436
+ ["Subtype", { kind: "name", value: "Image" }],
2437
+ ["Width", png.width],
2438
+ ["Height", png.height],
2439
+ ["ColorSpace", { kind: "name", value: png.colorSpace }],
2440
+ ["BitsPerComponent", 8],
2441
+ ["Filter", { kind: "name", value: "FlateDecode" }]
2442
+ ]);
2443
+ if (alphaRef) entries.set("SMask", alphaRef);
2444
+ return { ref: model.addObject({ kind: "dict", entries }, zlibSync2(png.pixels)), width: png.width, height: png.height };
2445
+ }
2446
+ function embedJpeg(model, bytes) {
2447
+ if (bytes[0] !== 255 || bytes[1] !== 216) throw new Error("Invalid JPEG image");
2448
+ let offset = 2;
2449
+ while (offset + 9 < bytes.length) {
2450
+ if (bytes[offset] !== 255) {
2451
+ offset++;
2452
+ continue;
2453
+ }
2454
+ const marker = bytes[offset + 1];
2455
+ offset += 2;
2456
+ if (marker === 216 || marker === 217 || marker === 1 || marker >= 208 && marker <= 215) continue;
2457
+ const length = bytes[offset] << 8 | bytes[offset + 1];
2458
+ if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(marker)) {
2459
+ const height = bytes[offset + 3] << 8 | bytes[offset + 4];
2460
+ const width = bytes[offset + 5] << 8 | bytes[offset + 6];
2461
+ const components = bytes[offset + 7];
2462
+ const colorSpace = components === 1 ? "DeviceGray" : components === 4 ? "DeviceCMYK" : "DeviceRGB";
2463
+ const ref = model.addObject({
2464
+ kind: "dict",
2465
+ entries: /* @__PURE__ */ new Map([
2466
+ ["Type", { kind: "name", value: "XObject" }],
2467
+ ["Subtype", { kind: "name", value: "Image" }],
2468
+ ["Width", width],
2469
+ ["Height", height],
2470
+ ["ColorSpace", { kind: "name", value: colorSpace }],
2471
+ ["BitsPerComponent", 8],
2472
+ ["Filter", { kind: "name", value: "DCTDecode" }]
2473
+ ])
2474
+ }, bytes);
2475
+ return { ref, width, height };
2476
+ }
2477
+ if (length < 2) break;
2478
+ offset += length;
2479
+ }
2480
+ throw new Error("JPEG dimensions could not be read");
2481
+ }
2482
+ var component = (value) => Math.max(0, Math.min(1, value));
2483
+ function applyPdfOverlays(model, overlays, data, fonts) {
2484
+ if (overlays.length === 0) return;
2485
+ const pages = documentPages(model);
2486
+ const grouped = /* @__PURE__ */ new Map();
2487
+ for (const overlay of overlays) {
2488
+ if (!Number.isInteger(overlay.page) || overlay.page < 1 || overlay.page > pages.length) {
2489
+ throw new Error(`PDF overlay page ${overlay.page} is outside the document's ${pages.length} pages`);
2490
+ }
2491
+ grouped.set(overlay.page, [...grouped.get(overlay.page) ?? [], overlay]);
2492
+ }
2493
+ let imageIndex = 0;
2494
+ for (const [pageNumber, items] of grouped) {
2495
+ const page = pages[pageNumber - 1];
2496
+ const commands = [];
2497
+ const pageFonts = /* @__PURE__ */ new Map();
2498
+ for (const overlay of items) {
2499
+ if ("image" in overlay) {
2500
+ const embedded = overlay.mediaType === "image/png" ? embedPng(model, overlay.image) : embedJpeg(model, overlay.image);
2501
+ const name2 = addPageResource(model, page, "XObject", `PdrI${imageIndex++}`, embedded.ref);
2502
+ const scale = overlay.fit === "fill" ? void 0 : Math.min(overlay.width / embedded.width, overlay.height / embedded.height);
2503
+ const width = scale === void 0 ? overlay.width : embedded.width * scale;
2504
+ const height = scale === void 0 ? overlay.height : embedded.height * scale;
2505
+ commands.push(`q
2506
+ ${width} 0 0 ${height} ${overlay.x} ${overlay.y} cm
2507
+ /${name2} Do
2508
+ Q`);
2509
+ continue;
2510
+ }
2511
+ const text = String(textValue2(overlay, data) ?? "").replace(/\r\n|\r|\n/g, " ");
2512
+ const subject = overlaySubject(overlay, overlays.indexOf(overlay));
2513
+ const font = fonts.select(subject, text, void 0, overlay.font);
2514
+ let fontName = pageFonts.get(font.resourceName);
2515
+ if (fontName === void 0) {
2516
+ fontName = addPageResource(model, page, "Font", font.resourceName, font.reference());
2517
+ pageFonts.set(font.resourceName, fontName);
2518
+ }
2519
+ const size = fittedFontSize(text, overlay, font, subject);
2520
+ const x = overlay.x + (overlay.width === void 0 ? 0 : 1);
2521
+ const y = overlay.y + (overlay.height === void 0 ? 0 : Math.max(1, (overlay.height - size) / 2));
2522
+ const [red, green, blue] = (overlay.color ?? [0, 0, 0]).map(component);
2523
+ commands.push(`BT
2524
+ /${fontName} ${size} Tf
2525
+ ${red} ${green} ${blue} rg
2526
+ ${x} ${y} Td
2527
+ ${font.encode(text)} Tj
2528
+ ET`);
2529
+ }
2530
+ const stream = model.addObject({ kind: "dict", entries: /* @__PURE__ */ new Map() }, new TextEncoder().encode(`q
2531
+ ${commands.join("\n")}
2532
+ Q`));
2533
+ appendPageContent(model, page, stream);
2534
+ }
2535
+ }
2536
+
2537
+ // src/pdf/render.ts
2538
+ function assign(field, value, model, fonts) {
2539
+ if (!field || value === null || value === void 0) return;
2540
+ setAcroFieldValue(model, fonts, field, field.type === "checkbox" || field.type === "radio" || field.type === "dropdown" ? unwrapFormattedValue(value) : value);
2541
+ }
2542
+ function sourcePaths(bindings) {
2543
+ return bindings.flatMap(([, parts]) => parts.map((part) => pathSegments(part.path)));
2544
+ }
2545
+ function fieldDefinition(form, path) {
2546
+ const [root, ...segments] = pathSegments(path);
2547
+ let field = root ? form?.fields?.[root] : void 0;
2548
+ for (const segment of segments) {
2549
+ if (field?.type === "list" && /^\d+$/.test(segment)) field = field.item;
2550
+ else if (field?.type === "fieldset") field = field.fields[segment];
2551
+ else return void 0;
2552
+ }
2553
+ return field;
2554
+ }
2555
+ function displayPath(segments) {
2556
+ return segments.reduce((path, segment) => /^\d+$/.test(segment) ? `${path}[${segment}]` : path ? `${path}.${segment}` : segment, "");
2557
+ }
2558
+ function assertListBindingCapacity(form, data, bindings) {
2559
+ if (!form?.fields || bindings.length === 0) return;
2560
+ const paths = sourcePaths(bindings);
2561
+ const visit = (field, value, prefix) => {
2562
+ if (field.type === "list") {
2563
+ if (!Array.isArray(value)) return;
2564
+ const indices = paths.filter((path) => prefix.every((segment, index) => path[index] === segment)).map((path) => path[prefix.length]).filter((segment) => segment !== void 0 && /^\d+$/.test(segment)).map(Number);
2565
+ if (indices.length > 0) {
2566
+ const capacity = Math.max(...indices) + 1;
2567
+ if (value.length > capacity) {
2568
+ throw new Error(`PDF bindings for "${displayPath(prefix)}" support ${capacity} list items, but received ${value.length}`);
2569
+ }
2570
+ }
2571
+ value.forEach((item, index) => visit(field.item, item, [...prefix, String(index)]));
2572
+ return;
2573
+ }
2574
+ if (field.type === "fieldset" && value !== null && typeof value === "object" && !Array.isArray(value)) {
2575
+ for (const [key, nestedField] of Object.entries(field.fields)) {
2576
+ visit(nestedField, value[key], [...prefix, key]);
2577
+ }
2578
+ }
2579
+ };
2580
+ for (const [key, field] of Object.entries(form.fields)) visit(field, data[key], [key]);
2581
+ }
2582
+ async function renderPdf({
2583
+ template,
2584
+ form,
2585
+ data,
2586
+ bindings,
2587
+ formatter = defaultFormatter,
2588
+ overlays = [],
2589
+ font,
2590
+ layerFont,
2591
+ format: format3
2592
+ }) {
2593
+ const preprocessed = form ? formatFieldData(data, form, formatter, { choices: "value", ...format3?.money && { money: format3.money } }) : data;
2594
+ const parsed = parseBindings(bindings ?? {});
2595
+ if (form) {
2596
+ const sources = parsed.flatMap(([, parts]) => parts.map((part) => part.source));
2597
+ sources.push(...overlays.flatMap((overlay) => "field" in overlay && overlay.field ? [overlay.field] : []));
2598
+ validateFieldBindings(form, Object.fromEntries(sources.map((source2, index) => [String(index), source2])));
2599
+ }
2600
+ assertListBindingCapacity(form, data, parsed);
2601
+ const model = await PdfModel.load(template);
2602
+ const fonts = PdfFontSet.create(model, { font, layerFont });
2603
+ const shouldFill = parsed.length > 0 || Boolean(form?.fields && Object.keys(form.fields).length > 0);
2604
+ if (shouldFill) {
2605
+ const acroFormData = acroFields(model);
2606
+ const byName = new Map((acroFormData?.fields ?? []).map((field) => [field.name, field]));
2607
+ const unmatched = parsed.map(([pdfName]) => pdfName).filter((pdfName) => !byName.has(pdfName));
2608
+ if (unmatched.length > 0) {
2609
+ throw new PdfBindingKeyError(unmatched, [...byName.keys()]);
2610
+ }
2611
+ if (acroFormData) {
2612
+ const { acroForm, acroRef, catalogRef } = acroFormData;
2613
+ await fonts.readFormFonts(model.dict(acroForm.entries.get("DR")));
2614
+ acroForm.entries.delete("NeedAppearances");
2615
+ model.markUpdated(acroRef ?? catalogRef);
2616
+ if (bindings) {
2617
+ for (const [pdfName, parts] of parsed) {
2618
+ const field = byName.get(pdfName);
2619
+ if (parts.length > 1) {
2620
+ const combined = parts.map((part) => getPath(preprocessed, renderDataPath(preprocessed, part.source))).filter((value) => value !== null && value !== void 0 && String(value) !== "").join(", ");
2621
+ if (combined) assign(field, combined, model, fonts);
2622
+ continue;
2623
+ }
2624
+ const { path, qualifier } = parts[0];
2625
+ if (qualifier !== void 0) {
2626
+ const value = getPath(data, renderDataPath(data, parts[0].source));
2627
+ if (typeof value === "boolean") assign(field, value, model, fonts);
2628
+ else if (Array.isArray(value)) assign(field, value.includes(qualifier), model, fonts);
2629
+ else if (fieldDefinition(form, path)?.type === "enum") assign(field, String(value) === qualifier, model, fonts);
2630
+ else {
2631
+ const index = splitPartIndex(qualifier);
2632
+ if (index !== void 0 && value !== null && value !== void 0) {
2633
+ const part = String(value).split("-")[index];
2634
+ if (part) assign(field, part, model, fonts);
2635
+ }
2636
+ }
2637
+ continue;
2638
+ }
2639
+ assign(field, getPath(preprocessed, renderDataPath(preprocessed, parts[0].source)), model, fonts);
2640
+ }
2641
+ } else if (form) {
2642
+ for (const [name2, definition] of Object.entries(form.fields ?? {})) {
2643
+ if (definition.type === "fieldset") continue;
2644
+ assign(byName.get(name2), preprocessed.fields?.[name2] ?? preprocessed[name2], model, fonts);
2645
+ }
2646
+ }
2647
+ }
2648
+ }
2649
+ applyPdfOverlays(model, overlays, preprocessed, fonts);
2650
+ fonts.finish();
2651
+ return model.save();
2652
+ }
2653
+
2654
+ // src/pdf/signature.ts
2655
+ var BYTE_RANGE_PLACEHOLDER = 9999999999;
2656
+ var BYTE_RANGE_TEXT = `[${Array(4).fill(BYTE_RANGE_PLACEHOLDER).join(" ")}]`;
2657
+ var name = (value) => ({ kind: "name", value });
2658
+ function pdfDate(date) {
2659
+ const pad = (value) => String(value).padStart(2, "0");
2660
+ return `D:${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}Z`;
2661
+ }
2662
+ function parsePdfDate(value) {
2663
+ const match = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?(?:([Zz+-])(\d{2})?'?(\d{2})?'?)?/.exec(value);
2664
+ if (!match) return void 0;
2665
+ const [, year, month = "01", day = "01", hour = "00", minute = "00", second = "00", sign, offsetHours = "00", offsetMinutes = "00"] = match;
2666
+ const utc = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second));
2667
+ const offset = (Number(offsetHours) * 60 + Number(offsetMinutes)) * 6e4;
2668
+ return new Date(sign === "+" ? utc - offset : sign === "-" ? utc + offset : utc);
2669
+ }
2670
+ function editableArray(model, holder, dict, key) {
2671
+ const value = dict.entries.get(key);
2672
+ const indirect = model.record(value);
2673
+ if (indirect && Array.isArray(indirect.value)) {
2674
+ model.markUpdated(indirect);
2675
+ return indirect.value;
2676
+ }
2677
+ const array = Array.isArray(value) ? value : [];
2678
+ dict.entries.set(key, array);
2679
+ model.markUpdated(holder);
2680
+ return array;
2681
+ }
2682
+ function editableAcroForm(model, catalog) {
2683
+ const value = catalog.value.entries.get("AcroForm");
2684
+ const indirect = model.record(value);
2685
+ if (indirect && isDict(indirect.value)) return { holder: indirect, form: indirect.value };
2686
+ const form = isDict(value) ? value : { kind: "dict", entries: /* @__PURE__ */ new Map() };
2687
+ catalog.value.entries.set("AcroForm", form);
2688
+ return { holder: catalog, form };
2689
+ }
2690
+ function fieldNames(model, fields) {
2691
+ const names = /* @__PURE__ */ new Set();
2692
+ for (const field of fields) {
2693
+ const title = model.dict(field)?.entries.get("T");
2694
+ if (typeof title === "string") names.add(decodeTextString(title));
2695
+ }
2696
+ return names;
2697
+ }
2698
+ function indexOf(haystack, needle, from) {
2699
+ const bytes = encodeLatin1(needle);
2700
+ outer: for (let index = from; index <= haystack.length - bytes.length; index++) {
2701
+ for (let offset = 0; offset < bytes.length; offset++) {
2702
+ if (haystack[index + offset] !== bytes[offset]) continue outer;
2703
+ }
2704
+ return index;
2705
+ }
2706
+ return -1;
2707
+ }
2708
+ async function preparePdfSignature(pdf, options) {
2709
+ if (!Number.isInteger(options.contentsSize) || options.contentsSize < 1) {
2710
+ throw new Error("contentsSize must be a positive whole number of bytes");
2711
+ }
2712
+ const model = await PdfModel.load(pdf);
2713
+ const catalog = model.catalog();
2714
+ if (!catalog || !isDict(catalog.value)) throw new Error("PDF catalog not found");
2715
+ const [firstPage] = documentPages(model);
2716
+ if (!firstPage || !isDict(firstPage.record.value)) throw new Error("PDF has no page to hold the signature widget");
2717
+ const signatureEntries = /* @__PURE__ */ new Map([
2718
+ ["Type", name("Sig")],
2719
+ ["Filter", name("Adobe.PPKLite")],
2720
+ ["SubFilter", name("adbe.pkcs7.detached")],
2721
+ ["ByteRange", Array(4).fill(BYTE_RANGE_PLACEHOLDER)],
2722
+ ["Contents", "\0".repeat(options.contentsSize)],
2723
+ ["M", pdfDate(options.signingTime)]
2724
+ ]);
2725
+ const text = [
2726
+ ["Name", options.name],
2727
+ ["Reason", options.reason],
2728
+ ["Location", options.location],
2729
+ ["ContactInfo", options.contactInfo]
2730
+ ];
2731
+ for (const [key, value] of text) if (value) signatureEntries.set(key, encodeTextString(value));
2732
+ const signatureRef = model.addObject({ kind: "dict", entries: signatureEntries });
2733
+ const { holder, form } = editableAcroForm(model, catalog);
2734
+ const fields = editableArray(model, holder, form, "Fields");
2735
+ const taken = fieldNames(model, fields);
2736
+ let ordinal = 1;
2737
+ while (taken.has(`Signature${ordinal}`)) ordinal++;
2738
+ const widgetRef = model.addObject({
2739
+ kind: "dict",
2740
+ entries: /* @__PURE__ */ new Map([
2741
+ ["Type", name("Annot")],
2742
+ ["Subtype", name("Widget")],
2743
+ ["FT", name("Sig")],
2744
+ ["T", `Signature${ordinal}`],
2745
+ ["V", signatureRef],
2746
+ // Print (4) and Locked (128).
2747
+ ["F", 132],
2748
+ ["Rect", [0, 0, 0, 0]],
2749
+ ["P", firstPage.ref]
2750
+ ])
2751
+ });
2752
+ fields.push(widgetRef);
2753
+ form.entries.set("SigFlags", 3);
2754
+ model.markUpdated(holder);
2755
+ editableArray(model, firstPage.record, firstPage.record.value, "Annots").push(widgetRef);
2756
+ const bytes = model.save();
2757
+ const objectStart = indexOf(bytes, `
2758
+ ${signatureRef.object} 0 obj
2759
+ `, pdf.length - 1);
2760
+ if (objectStart === -1) throw new Error("Signature dictionary not found in the saved PDF");
2761
+ const byteRangeKey = indexOf(bytes, `/ByteRange ${BYTE_RANGE_TEXT}`, objectStart);
2762
+ const contentsKey = indexOf(bytes, "/Contents <", objectStart);
2763
+ if (byteRangeKey === -1 || contentsKey === -1) throw new Error("Signature placeholders not found in the saved PDF");
2764
+ const byteRangeAt = byteRangeKey + "/ByteRange ".length;
2765
+ const contentsAt = contentsKey + "/Contents ".length;
2766
+ const gapEnd = contentsAt + options.contentsSize * 2 + 2;
2767
+ const byteRange = [0, contentsAt, gapEnd, bytes.length - gapEnd];
2768
+ const written = `[${byteRange.join(" ")}`;
2769
+ bytes.set(encodeLatin1(`${written}${" ".repeat(BYTE_RANGE_TEXT.length - written.length - 1)}]`), byteRangeAt);
2770
+ return { bytes, byteRange };
2771
+ }
2772
+ function signedBytes(prepared) {
2773
+ const [start, length, gapEnd, rest] = prepared.byteRange;
2774
+ const output = new Uint8Array(length + rest);
2775
+ output.set(prepared.bytes.subarray(start, start + length), 0);
2776
+ output.set(prepared.bytes.subarray(gapEnd, gapEnd + rest), length);
2777
+ return output;
2778
+ }
2779
+ function embedPdfSignature(prepared, container) {
2780
+ const [, gapStart, gapEnd] = prepared.byteRange;
2781
+ const capacity = (gapEnd - gapStart - 2) / 2;
2782
+ if (container.length > capacity) {
2783
+ throw new Error(`Signature container is ${container.length} bytes; the field reserves ${capacity}`);
2784
+ }
2785
+ let hex = "";
2786
+ for (const byte of container) hex += byte.toString(16).padStart(2, "0");
2787
+ const output = prepared.bytes.slice();
2788
+ output.set(encodeLatin1(hex.padEnd(capacity * 2, "0")), gapStart + 1);
2789
+ return output;
2790
+ }
2791
+ function isSignatureDictionary(value) {
2792
+ if (!isDict(value)) return false;
2793
+ const type = value.entries.get("Type");
2794
+ const byteRange = value.entries.get("ByteRange");
2795
+ return (type === void 0 || isName(type) && (type.value === "Sig" || type.value === "DocTimeStamp")) && Array.isArray(byteRange) && byteRange.length === 4 && byteRange.every((entry) => typeof entry === "number" && Number.isInteger(entry) && entry >= 0) && typeof value.entries.get("Contents") === "string";
2796
+ }
2797
+ function textEntry(dict, key) {
2798
+ const value = dict.entries.get(key);
2799
+ return typeof value === "string" ? decodeTextString(value) : void 0;
2800
+ }
2801
+ async function readPdfSignature(pdf) {
2802
+ const model = await PdfModel.load(pdf);
2803
+ let newest;
2804
+ let newestEnd = -1;
2805
+ for (const record of model.objects.values()) {
2806
+ if (!isSignatureDictionary(record.value)) continue;
2807
+ const [, , offset, length2] = record.value.entries.get("ByteRange");
2808
+ if (offset + length2 > newestEnd) {
2809
+ newest = record.value;
2810
+ newestEnd = offset + length2;
2811
+ }
2812
+ }
2813
+ if (!newest) return void 0;
2814
+ const byteRange = newest.entries.get("ByteRange");
2815
+ const contents = encodeLatin1(newest.entries.get("Contents"));
2816
+ const [start, length, gapEnd, rest] = byteRange;
2817
+ const inFile = start + length <= gapEnd && gapEnd + rest <= pdf.length;
2818
+ const signed = new Uint8Array(inFile ? length + rest : 0);
2819
+ if (inFile) {
2820
+ signed.set(pdf.subarray(start, start + length), 0);
2821
+ signed.set(pdf.subarray(gapEnd, gapEnd + rest), length);
2822
+ }
2823
+ const gap = byteString(pdf.subarray(start + length, gapEnd));
2824
+ const gapHex = /^<([0-9A-Fa-f]*)>$/.exec(gap)?.[1];
2825
+ const gapIsContents = gapHex !== void 0 && gapHex.length === contents.length * 2 && contents.every((byte, index) => Number.parseInt(gapHex.slice(index * 2, index * 2 + 2), 16) === byte);
2826
+ const signingTime = newest.entries.get("M");
2827
+ return {
2828
+ contents,
2829
+ byteRange,
2830
+ signedBytes: signed,
2831
+ coversWholeFile: inFile && start === 0 && gapEnd + rest === pdf.length && gapIsContents,
2832
+ name: textEntry(newest, "Name"),
2833
+ reason: textEntry(newest, "Reason"),
2834
+ location: textEntry(newest, "Location"),
2835
+ contactInfo: textEntry(newest, "ContactInfo"),
2836
+ signingTime: typeof signingTime === "string" ? parsePdfDate(signingTime) : void 0
2837
+ };
2838
+ }
2839
+
2840
+ // src/pdf/renderer.ts
2841
+ import { defaultFormatter as defaultFormatter2 } from "@asterdoc/format";
2842
+ function pdfRenderer(options = {}) {
2843
+ const formatter = options.formatter ?? defaultFormatter2;
2844
+ const fromLayer = (template) => ({
2845
+ font: options.font,
2846
+ layerFont: template.font && { bytes: template.font.content, source: template.font.path },
2847
+ ...template.format && { format: template.format }
2848
+ });
2849
+ return {
2850
+ id: "pdf",
2851
+ render(request) {
2852
+ const { data, form } = requestRenderData(request);
2853
+ return renderPdf({
2854
+ template: request.template.content,
2855
+ data,
2856
+ form,
2857
+ formatter: request.ctx?.formatter ?? formatter,
2858
+ bindings: request.template.bindings,
2859
+ ...fromLayer(request.template)
2860
+ });
2861
+ }
2862
+ };
2863
+ }
2864
+
2865
+ // src/pdf/encoding.ts
2866
+ var ALPHABET = [
2867
+ "\u2800",
2868
+ // Braille Pattern Blank
2869
+ "\u2801",
2870
+ // Braille Pattern Dots-1
2871
+ "\u2802",
2872
+ // Braille Pattern Dots-2
2873
+ "\u2804"
2874
+ // Braille Pattern Dots-3
2875
+ ];
2876
+ var FieldType = {
2877
+ SIGNATURE: 0,
2878
+ INITIALS: 1
2879
+ };
2880
+ var MAX_SIGNER_INDEX = 4095;
2881
+ var MAX_FIELD_TYPE = 15;
2882
+ var ENCODING_LENGTH = 8;
2883
+ function encode(signerIndex, fieldType) {
2884
+ if (!Number.isInteger(signerIndex) || signerIndex < 0 || signerIndex > MAX_SIGNER_INDEX) {
2885
+ throw new Error(`Signer index must be an integer 0-${MAX_SIGNER_INDEX}, got ${signerIndex}`);
2886
+ }
2887
+ if (!Number.isInteger(fieldType) || fieldType < 0 || fieldType > MAX_FIELD_TYPE) {
2888
+ throw new Error(`Field type must be an integer 0-${MAX_FIELD_TYPE}, got ${fieldType}`);
2889
+ }
2890
+ let result = "";
2891
+ let remaining = signerIndex;
2892
+ for (let index = 0; index < 6; index++) {
2893
+ result = ALPHABET[remaining % 4] + result;
2894
+ remaining = Math.floor(remaining / 4);
2895
+ }
2896
+ result += ALPHABET[Math.floor(fieldType / 4)];
2897
+ result += ALPHABET[fieldType % 4];
2898
+ return result;
2899
+ }
2900
+ function decodeAll(text) {
2901
+ const results = [];
2902
+ let found = [];
2903
+ let start = -1;
2904
+ for (let index = 0; index < text.length; index++) {
2905
+ const digit = ALPHABET.indexOf(text[index]);
2906
+ if (digit === -1) {
2907
+ found = [];
2908
+ start = -1;
2909
+ continue;
2910
+ }
2911
+ if (found.length === 0) start = index;
2912
+ found.push(digit);
2913
+ if (found.length < ENCODING_LENGTH) continue;
2914
+ let signerIndex = 0;
2915
+ for (let place = 0; place < 6; place++) signerIndex = signerIndex * 4 + found[place];
2916
+ results.push({ signerIndex, fieldType: found[6] * 4 + found[7], position: start });
2917
+ found = [];
2918
+ start = -1;
2919
+ }
2920
+ return results;
2921
+ }
2922
+ function stripEncoding(text) {
2923
+ const pattern = new RegExp(`[${ALPHABET.join("")}]`, "g");
2924
+ return text.replace(pattern, "");
2925
+ }
2926
+ function containsEncoding(text) {
2927
+ return ALPHABET.some((character) => text.includes(character));
2928
+ }
2929
+ function fieldTypeToString(fieldType) {
2930
+ if (fieldType === FieldType.SIGNATURE) return "signature";
2931
+ if (fieldType === FieldType.INITIALS) return "initials";
2932
+ return "unknown";
2933
+ }
2934
+
2935
+ // src/pdf/fonts.ts
2936
+ function parseToUnicode(cmap) {
2937
+ const map = /* @__PURE__ */ new Map();
2938
+ const hexToString = (hex) => {
2939
+ let text = "";
2940
+ for (let index = 0; index + 3 < hex.length + 1; index += 4) {
2941
+ text += String.fromCharCode(Number.parseInt(hex.slice(index, index + 4), 16));
2942
+ }
2943
+ return text;
2944
+ };
2945
+ for (const block of cmap.matchAll(/beginbfchar([\s\S]*?)endbfchar/g)) {
2946
+ for (const entry of block[1].matchAll(/<([0-9a-fA-F]+)>\s*<([0-9a-fA-F]+)>/g)) {
2947
+ map.set(Number.parseInt(entry[1], 16), hexToString(entry[2].padStart(Math.ceil(entry[2].length / 4) * 4, "0")));
2948
+ }
2949
+ }
2950
+ for (const block of cmap.matchAll(/beginbfrange([\s\S]*?)endbfrange/g)) {
2951
+ const body = block[1];
2952
+ for (const entry of body.matchAll(/<([0-9a-fA-F]+)>\s*<([0-9a-fA-F]+)>\s*<([0-9a-fA-F]+)>/g)) {
2953
+ const low = Number.parseInt(entry[1], 16);
2954
+ const high = Number.parseInt(entry[2], 16);
2955
+ const start = entry[3].padStart(Math.ceil(entry[3].length / 4) * 4, "0");
2956
+ const base = hexToString(start);
2957
+ const last = base.charCodeAt(base.length - 1);
2958
+ for (let code = low; code <= high && code - low < 65536; code++) {
2959
+ map.set(code, base.slice(0, -1) + String.fromCharCode(last + (code - low)));
2960
+ }
2961
+ }
2962
+ for (const entry of body.matchAll(/<([0-9a-fA-F]+)>\s*<([0-9a-fA-F]+)>\s*\[([\s\S]*?)\]/g)) {
2963
+ const low = Number.parseInt(entry[1], 16);
2964
+ const targets = [...entry[3].matchAll(/<([0-9a-fA-F]+)>/g)];
2965
+ for (let offset = 0; offset < targets.length; offset++) {
2966
+ map.set(low + offset, hexToString(targets[offset][1].padStart(Math.ceil(targets[offset][1].length / 4) * 4, "0")));
2967
+ }
2968
+ }
2969
+ }
2970
+ return map;
2971
+ }
2972
+ function simpleWidths(model, font) {
2973
+ const first = model.resolve(font.entries.get("FirstChar"));
2974
+ const widths = model.resolve(font.entries.get("Widths"));
2975
+ if (typeof first !== "number" || !Array.isArray(widths)) return () => 500;
2976
+ const table = widths.map((entry) => model.resolve(entry)).map((entry) => typeof entry === "number" ? entry : 0);
2977
+ return (code) => {
2978
+ const width = table[code - first];
2979
+ return typeof width === "number" && width > 0 ? width : 500;
2980
+ };
2981
+ }
2982
+ function cidWidths(model, font) {
2983
+ const descendants = model.resolve(font.entries.get("DescendantFonts"));
2984
+ const descendant = Array.isArray(descendants) ? model.dict(descendants[0]) : void 0;
2985
+ if (!descendant) return () => 1e3;
2986
+ const dw = model.resolve(descendant.entries.get("DW"));
2987
+ const defaultWidth = typeof dw === "number" ? dw : 1e3;
2988
+ const w = model.resolve(descendant.entries.get("W"));
2989
+ const table = /* @__PURE__ */ new Map();
2990
+ if (Array.isArray(w)) {
2991
+ let index = 0;
2992
+ while (index < w.length) {
2993
+ const start = model.resolve(w[index]);
2994
+ const next = model.resolve(w[index + 1]);
2995
+ if (typeof start !== "number") break;
2996
+ if (Array.isArray(next)) {
2997
+ const widths = next.map((entry) => model.resolve(entry));
2998
+ for (let offset = 0; offset < widths.length; offset++) {
2999
+ const width = widths[offset];
3000
+ if (typeof width === "number") table.set(start + offset, width);
3001
+ }
3002
+ index += 2;
3003
+ } else if (typeof next === "number") {
3004
+ const width = model.resolve(w[index + 2]);
3005
+ if (typeof width === "number") {
3006
+ for (let code = start; code <= next && code - start < 65536; code++) table.set(code, width);
3007
+ }
3008
+ index += 3;
3009
+ } else break;
3010
+ }
3011
+ }
3012
+ return (code) => table.get(code) ?? defaultWidth;
3013
+ }
3014
+ async function buildFontDecoder(model, font) {
3015
+ const subtype = model.resolve(font.entries.get("Subtype"));
3016
+ const twoByte = isName(subtype) && subtype.value === "Type0";
3017
+ let toUnicode;
3018
+ const toUnicodeRecord = model.record(font.entries.get("ToUnicode"));
3019
+ if (toUnicodeRecord?.stream && isDict(toUnicodeRecord.value)) {
3020
+ const bytes = await decodeStream(model, toUnicodeRecord.value, toUnicodeRecord.stream);
3021
+ toUnicode = parseToUnicode(byteString(bytes));
3022
+ }
3023
+ const widthOf = twoByte ? cidWidths(model, font) : simpleWidths(model, font);
3024
+ return {
3025
+ twoByte,
3026
+ decode(bytes) {
3027
+ const glyphs = [];
3028
+ if (twoByte) {
3029
+ for (let index = 0; index + 1 < bytes.length; index += 2) {
3030
+ const code = bytes[index] << 8 | bytes[index + 1];
3031
+ glyphs.push({ code, unicode: toUnicode?.get(code) ?? "", width: widthOf(code) });
3032
+ }
3033
+ } else {
3034
+ for (const code of bytes) {
3035
+ glyphs.push({ code, unicode: toUnicode?.get(code) ?? String.fromCharCode(code), width: widthOf(code) });
3036
+ }
3037
+ }
3038
+ return glyphs;
3039
+ }
3040
+ };
3041
+ }
3042
+
3043
+ // src/pdf/scanner.ts
3044
+ function mergeRuns(runs) {
3045
+ const merged = [];
3046
+ for (const run of runs) {
3047
+ const previous = merged[merged.length - 1];
3048
+ const boundary = !previous || containsEncoding(previous.text) || containsEncoding(run.text);
3049
+ if (!boundary && Math.abs(run.y - previous.y) < 0.5 && Math.abs(run.x - (previous.x + previous.width)) < 1) {
3050
+ previous.text += run.text;
3051
+ previous.width = run.x + run.width - previous.x;
3052
+ previous.height = Math.max(previous.height, run.height);
3053
+ continue;
3054
+ }
3055
+ merged.push({ ...run });
3056
+ }
3057
+ return merged;
3058
+ }
3059
+ var IDENTITY = [1, 0, 0, 1, 0, 0];
3060
+ function multiply(m, n) {
3061
+ return [
3062
+ m[0] * n[0] + m[1] * n[2],
3063
+ m[0] * n[1] + m[1] * n[3],
3064
+ m[2] * n[0] + m[3] * n[2],
3065
+ m[2] * n[1] + m[3] * n[3],
3066
+ m[4] * n[0] + m[5] * n[2] + n[4],
3067
+ m[4] * n[1] + m[5] * n[3] + n[5]
3068
+ ];
3069
+ }
3070
+ function tokenize(content) {
3071
+ const source2 = byteString(content);
3072
+ const tokens = [];
3073
+ let position = 0;
3074
+ const stack = [tokens];
3075
+ const isWhitespace = (char) => /[\s\0]/.test(char);
3076
+ const isDelimiter = (char) => char === void 0 || isWhitespace(char) || "()<>[]{}/%".includes(char);
3077
+ while (position < source2.length) {
3078
+ const char = source2[position];
3079
+ if (isWhitespace(char)) {
3080
+ position++;
3081
+ continue;
3082
+ }
3083
+ if (char === "%") {
3084
+ while (position < source2.length && !"\r\n".includes(source2[position])) position++;
3085
+ continue;
3086
+ }
3087
+ const sink = stack[stack.length - 1];
3088
+ if (char === "[") {
3089
+ const items = [];
3090
+ sink.push({ kind: "array", items });
3091
+ stack.push(items);
3092
+ position++;
3093
+ continue;
3094
+ }
3095
+ if (char === "]") {
3096
+ if (stack.length > 1) stack.pop();
3097
+ position++;
3098
+ continue;
3099
+ }
3100
+ if (char === "(") {
3101
+ position++;
3102
+ const bytes = [];
3103
+ let depth = 1;
3104
+ while (position < source2.length && depth > 0) {
3105
+ const current = source2[position++];
3106
+ if (current === "\\") {
3107
+ const escaped = source2[position++];
3108
+ if (escaped === void 0) break;
3109
+ const simple = { n: 10, r: 13, t: 9, b: 8, f: 12, "(": 40, ")": 41, "\\": 92 };
3110
+ if (simple[escaped] !== void 0) bytes.push(simple[escaped]);
3111
+ else if (escaped === "\n") continue;
3112
+ else if (escaped === "\r") {
3113
+ if (source2[position] === "\n") position++;
3114
+ } else if (/[0-7]/.test(escaped)) {
3115
+ let octal = escaped;
3116
+ while (octal.length < 3 && /[0-7]/.test(source2[position] ?? "")) octal += source2[position++];
3117
+ bytes.push(Number.parseInt(octal, 8) & 255);
3118
+ } else bytes.push(escaped.charCodeAt(0));
3119
+ } else if (current === "(") {
3120
+ depth++;
3121
+ bytes.push(40);
3122
+ } else if (current === ")") {
3123
+ depth--;
3124
+ if (depth > 0) bytes.push(41);
3125
+ } else bytes.push(current.charCodeAt(0) & 255);
3126
+ }
3127
+ sink.push({ kind: "string", bytes });
3128
+ continue;
3129
+ }
3130
+ if (char === "<" && source2[position + 1] === "<") {
3131
+ let depth = 0;
3132
+ while (position < source2.length) {
3133
+ if (source2.startsWith("<<", position)) {
3134
+ depth++;
3135
+ position += 2;
3136
+ } else if (source2.startsWith(">>", position)) {
3137
+ depth--;
3138
+ position += 2;
3139
+ if (depth === 0) break;
3140
+ } else position++;
3141
+ }
3142
+ continue;
3143
+ }
3144
+ if (char === "<") {
3145
+ position++;
3146
+ const start2 = position;
3147
+ while (position < source2.length && source2[position] !== ">") position++;
3148
+ let hex = source2.slice(start2, position++).replace(/\s/g, "");
3149
+ if (hex.length % 2 === 1) hex += "0";
3150
+ const bytes = [];
3151
+ for (let index = 0; index < hex.length; index += 2) bytes.push(Number.parseInt(hex.slice(index, index + 2), 16));
3152
+ sink.push({ kind: "string", bytes });
3153
+ continue;
3154
+ }
3155
+ if (char === "/") {
3156
+ position++;
3157
+ const start2 = position;
3158
+ while (!isDelimiter(source2[position])) position++;
3159
+ sink.push({
3160
+ kind: "name",
3161
+ value: source2.slice(start2, position).replace(/#([0-9a-f]{2})/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
3162
+ });
3163
+ continue;
3164
+ }
3165
+ if (/[-+.\d]/.test(char)) {
3166
+ const start2 = position;
3167
+ while (/[-+.\d]/.test(source2[position] ?? "")) position++;
3168
+ const value = Number(source2.slice(start2, position));
3169
+ sink.push({ kind: "number", value: Number.isFinite(value) ? value : 0 });
3170
+ continue;
3171
+ }
3172
+ const start = position;
3173
+ while (!isDelimiter(source2[position]) && !/[-+.\d]/.test(source2[position] ?? "")) position++;
3174
+ const operator = source2.slice(start, position);
3175
+ if (operator === "BI") {
3176
+ const match = /\sEI(?=[\s\0]|$)/.exec(source2.slice(position));
3177
+ position = match ? position + match.index + match[0].length : source2.length;
3178
+ continue;
3179
+ }
3180
+ if (operator.length > 0) sink.push({ kind: "operator", value: operator });
3181
+ else position++;
3182
+ }
3183
+ return tokens;
3184
+ }
3185
+ async function scanPage(model, page) {
3186
+ const runs = [];
3187
+ const fontCache = /* @__PURE__ */ new Map();
3188
+ const fontFor = async (resources, name2) => {
3189
+ const fonts = resources ? model.dict(resources.entries.get("Font")) : void 0;
3190
+ const dict = fonts ? model.dict(fonts.entries.get(name2)) : void 0;
3191
+ if (!dict) return void 0;
3192
+ let cached = fontCache.get(dict);
3193
+ if (!cached) {
3194
+ cached = await buildFontDecoder(model, dict);
3195
+ fontCache.set(dict, cached);
3196
+ }
3197
+ return cached;
3198
+ };
3199
+ const interpret = async (content, resources, base, depth) => {
3200
+ if (depth > 8) return;
3201
+ const tokens = tokenize(content);
3202
+ const state = {
3203
+ ctm: base,
3204
+ font: void 0,
3205
+ fontSize: 0,
3206
+ charSpacing: 0,
3207
+ wordSpacing: 0,
3208
+ horizontalScale: 1,
3209
+ leading: 0,
3210
+ rise: 0
3211
+ };
3212
+ const saved = [];
3213
+ let textMatrix = IDENTITY;
3214
+ let lineMatrix = IDENTITY;
3215
+ const operands = [];
3216
+ const numbers = (count) => {
3217
+ const slice = operands.slice(-count);
3218
+ return slice.map((token) => token.kind === "number" ? token.value : 0);
3219
+ };
3220
+ const nextLine = (tx, ty) => {
3221
+ lineMatrix = multiply([1, 0, 0, 1, tx, ty], lineMatrix);
3222
+ textMatrix = lineMatrix;
3223
+ };
3224
+ const show2 = (bytes) => {
3225
+ if (!state.font || bytes.length === 0) return;
3226
+ const glyphs = state.font.decode(bytes);
3227
+ const trm = multiply(
3228
+ multiply([state.fontSize * state.horizontalScale, 0, 0, state.fontSize, 0, state.rise], textMatrix),
3229
+ state.ctm
3230
+ );
3231
+ let advance = 0;
3232
+ let text = "";
3233
+ for (const glyph of glyphs) {
3234
+ text += glyph.unicode;
3235
+ const word = !state.font.twoByte && glyph.code === 32 ? state.wordSpacing : 0;
3236
+ advance += (glyph.width / 1e3 * state.fontSize + state.charSpacing + word) * state.horizontalScale;
3237
+ }
3238
+ const scale = Math.hypot(multiply(textMatrix, state.ctm)[0], multiply(textMatrix, state.ctm)[1]);
3239
+ runs.push({
3240
+ text,
3241
+ x: trm[4],
3242
+ y: trm[5],
3243
+ width: advance * scale,
3244
+ height: Math.hypot(trm[2], trm[3])
3245
+ });
3246
+ textMatrix = multiply([1, 0, 0, 1, advance, 0], textMatrix);
3247
+ };
3248
+ const adjust = (amount) => {
3249
+ const tx = -amount / 1e3 * state.fontSize * state.horizontalScale;
3250
+ textMatrix = multiply([1, 0, 0, 1, tx, 0], textMatrix);
3251
+ };
3252
+ for (const token of tokens) {
3253
+ if (token.kind !== "operator") {
3254
+ operands.push(token);
3255
+ continue;
3256
+ }
3257
+ const op = token.value;
3258
+ switch (op) {
3259
+ case "q":
3260
+ saved.push({ ...state });
3261
+ break;
3262
+ case "Q": {
3263
+ const restored = saved.pop();
3264
+ if (restored) Object.assign(state, restored);
3265
+ break;
3266
+ }
3267
+ case "cm": {
3268
+ const [a, b, c, d, e, f] = numbers(6);
3269
+ state.ctm = multiply([a, b, c, d, e, f], state.ctm);
3270
+ break;
3271
+ }
3272
+ case "BT":
3273
+ textMatrix = IDENTITY;
3274
+ lineMatrix = IDENTITY;
3275
+ break;
3276
+ case "ET":
3277
+ break;
3278
+ case "Tf": {
3279
+ const size = numbers(1)[0] ?? 0;
3280
+ const name2 = operands.at(-2);
3281
+ state.fontSize = size;
3282
+ if (name2?.kind === "name") state.font = await fontFor(resources, name2.value);
3283
+ break;
3284
+ }
3285
+ case "Td": {
3286
+ const [tx, ty] = numbers(2);
3287
+ nextLine(tx, ty);
3288
+ break;
3289
+ }
3290
+ case "TD": {
3291
+ const [tx, ty] = numbers(2);
3292
+ state.leading = -ty;
3293
+ nextLine(tx, ty);
3294
+ break;
3295
+ }
3296
+ case "Tm": {
3297
+ const [a, b, c, d, e, f] = numbers(6);
3298
+ textMatrix = [a, b, c, d, e, f];
3299
+ lineMatrix = textMatrix;
3300
+ break;
3301
+ }
3302
+ case "T*":
3303
+ nextLine(0, -state.leading);
3304
+ break;
3305
+ case "TL":
3306
+ state.leading = numbers(1)[0] ?? 0;
3307
+ break;
3308
+ case "Tc":
3309
+ state.charSpacing = numbers(1)[0] ?? 0;
3310
+ break;
3311
+ case "Tw":
3312
+ state.wordSpacing = numbers(1)[0] ?? 0;
3313
+ break;
3314
+ case "Tz":
3315
+ state.horizontalScale = (numbers(1)[0] ?? 100) / 100;
3316
+ break;
3317
+ case "Ts":
3318
+ state.rise = numbers(1)[0] ?? 0;
3319
+ break;
3320
+ case "Tj": {
3321
+ const operand = operands.at(-1);
3322
+ if (operand?.kind === "string") show2(operand.bytes);
3323
+ break;
3324
+ }
3325
+ case "'": {
3326
+ nextLine(0, -state.leading);
3327
+ const operand = operands.at(-1);
3328
+ if (operand?.kind === "string") show2(operand.bytes);
3329
+ break;
3330
+ }
3331
+ case '"': {
3332
+ const spacing = numbers(3);
3333
+ state.wordSpacing = spacing[0] ?? 0;
3334
+ state.charSpacing = spacing[1] ?? 0;
3335
+ nextLine(0, -state.leading);
3336
+ const operand = operands.at(-1);
3337
+ if (operand?.kind === "string") show2(operand.bytes);
3338
+ break;
3339
+ }
3340
+ case "TJ": {
3341
+ const operand = operands.at(-1);
3342
+ if (operand?.kind === "array") {
3343
+ for (const item of operand.items) {
3344
+ if (item.kind === "string") show2(item.bytes);
3345
+ else if (item.kind === "number") adjust(item.value);
3346
+ }
3347
+ }
3348
+ break;
3349
+ }
3350
+ case "Do": {
3351
+ const name2 = operands.at(-1);
3352
+ if (name2?.kind !== "name") break;
3353
+ const xobjects = resources ? model.dict(resources.entries.get("XObject")) : void 0;
3354
+ const record = xobjects ? model.record(xobjects.entries.get(name2.value)) : void 0;
3355
+ if (!record?.stream || !isDict(record.value)) break;
3356
+ const subtype = model.resolve(record.value.entries.get("Subtype"));
3357
+ if (!isName(subtype) || subtype.value !== "Form") break;
3358
+ const matrixValues = model.resolve(record.value.entries.get("Matrix"));
3359
+ const matrix = Array.isArray(matrixValues) && matrixValues.length === 6 && matrixValues.every((entry) => typeof entry === "number") ? matrixValues : IDENTITY;
3360
+ const formResources = model.dict(record.value.entries.get("Resources")) ?? resources;
3361
+ const bytes = await decodeStream(model, record.value, record.stream);
3362
+ await interpret(bytes, formResources, multiply(matrix, state.ctm), depth + 1);
3363
+ break;
3364
+ }
3365
+ default:
3366
+ break;
3367
+ }
3368
+ operands.length = 0;
3369
+ }
3370
+ };
3371
+ await interpret(page.content, page.resources, IDENTITY, 0);
3372
+ return runs;
3373
+ }
3374
+
3375
+ // src/pdf/extract.ts
3376
+ var DEFAULT_INITIALS_DIMENSIONS = { width: 80, height: 40 };
3377
+ var UnknownMarkerError = class extends Error {
3378
+ constructor(markers) {
3379
+ const names = markers.map((marker) => `page ${marker.page} signer ${marker.signerIndex} field type ${marker.fieldType}`).join(", ");
3380
+ super(`PDF contains ${markers.length} marker${markers.length === 1 ? "" : "s"} with an unknown field type: ${names}`);
3381
+ this.markers = markers;
3382
+ this.name = "UnknownMarkerError";
3383
+ }
3384
+ markers;
3385
+ };
3386
+ function findEncodingPosition(runs, charPosition) {
3387
+ let cursor = 0;
3388
+ let foundIndex = -1;
3389
+ for (let index = 0; index < runs.length; index++) {
3390
+ const end = cursor + runs[index].text.length;
3391
+ if (charPosition >= cursor && charPosition < end) {
3392
+ foundIndex = index;
3393
+ break;
3394
+ }
3395
+ cursor = end;
3396
+ }
3397
+ if (foundIndex === -1) return null;
3398
+ const found = runs[foundIndex];
3399
+ const fontHeight = found.height > 0 ? found.height : 12;
3400
+ let placeholderWidth = 0;
3401
+ const lineTolerance = 5;
3402
+ const scanStart = Math.max(0, foundIndex - 10);
3403
+ const scanEnd = Math.min(foundIndex + 10, runs.length);
3404
+ for (let index = scanStart; index < scanEnd; index++) {
3405
+ const run = runs[index];
3406
+ const underscores = (run.text.match(/_/g) ?? []).length;
3407
+ if (underscores === 0 || Math.abs(run.y - found.y) > lineTolerance) continue;
3408
+ if (run.width > 0) {
3409
+ placeholderWidth += run.width / run.text.length * underscores;
3410
+ } else {
3411
+ placeholderWidth += fontHeight * 0.4 * underscores;
3412
+ }
3413
+ break;
3414
+ }
3415
+ return {
3416
+ x: found.x,
3417
+ y: found.y,
3418
+ width: Math.max(placeholderWidth, 30),
3419
+ height: Math.max(fontHeight * 2.5, 25)
3420
+ };
3421
+ }
3422
+ async function extractFieldsFromPdf(pdf) {
3423
+ return fieldsFromPageRuns(await pageTextRuns(pdf));
3424
+ }
3425
+ function fieldsFromPageRuns(pages) {
3426
+ const fields = [];
3427
+ const unknown = [];
3428
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
3429
+ const { mediaBox, runs } = pages[pageIndex];
3430
+ const pageHeight = mediaBox[3] - mediaBox[1];
3431
+ const originX = mediaBox[0];
3432
+ const originY = mediaBox[1];
3433
+ const accumulated = runs.map((run) => run.text).join("");
3434
+ for (const encoding of decodeAll(accumulated)) {
3435
+ const position = findEncodingPosition(runs, encoding.position);
3436
+ if (!position) continue;
3437
+ const name2 = fieldTypeToString(encoding.fieldType);
3438
+ if (name2 === "unknown") {
3439
+ unknown.push({ page: pageIndex + 1, signerIndex: encoding.signerIndex, fieldType: encoding.fieldType });
3440
+ continue;
3441
+ }
3442
+ const isInitials = name2 === "initials";
3443
+ const width = isInitials ? Math.min(position.width, DEFAULT_INITIALS_DIMENSIONS.width) : position.width;
3444
+ const height = isInitials ? Math.min(position.height * 0.8, DEFAULT_INITIALS_DIMENSIONS.height) : position.height;
3445
+ const rawY = position.y - originY;
3446
+ fields.push({
3447
+ signerIndex: encoding.signerIndex,
3448
+ fieldType: name2,
3449
+ page: pageIndex + 1,
3450
+ x: position.x - originX,
3451
+ y: pageHeight - rawY - height,
3452
+ width: Math.round(width),
3453
+ height: Math.round(height),
3454
+ rawY,
3455
+ pageHeight
3456
+ });
3457
+ }
3458
+ }
3459
+ if (unknown.length > 0) throw new UnknownMarkerError(unknown);
3460
+ return fields;
3461
+ }
3462
+ async function pageTextRuns(pdf) {
3463
+ const model = await PdfModel.load(pdf);
3464
+ const pages = await loadPages(model);
3465
+ const result = [];
3466
+ for (const page of pages) {
3467
+ result.push({ mediaBox: page.mediaBox, runs: mergeRuns(await scanPage(model, page)) });
3468
+ }
3469
+ return result;
3470
+ }
3471
+ async function pdfContainsEncoding(pdf) {
3472
+ const model = await PdfModel.load(pdf);
3473
+ for (const page of await loadPages(model)) {
3474
+ const runs = await scanPage(model, page);
3475
+ for (const run of runs) {
3476
+ if (ALPHABET.some((character) => run.text.includes(character))) return true;
3477
+ }
3478
+ }
3479
+ return false;
3480
+ }
3481
+
3482
+ // src/pdf/locate.ts
3483
+ function locator() {
3484
+ return { locate };
3485
+ }
3486
+ var LocateError = class extends Error {
3487
+ constructor(message, failures) {
3488
+ super(message);
3489
+ this.failures = failures;
3490
+ this.name = "LocateError";
3491
+ }
3492
+ failures;
3493
+ };
3494
+ function lines(runs) {
3495
+ const byLine = [];
3496
+ for (const run of runs) {
3497
+ const line = byLine.find((candidate) => Math.abs(candidate[0].y - run.y) < 2);
3498
+ if (line) line.push(run);
3499
+ else byLine.push([run]);
3500
+ }
3501
+ for (const line of byLine) line.sort((a, b) => a.x - b.x);
3502
+ byLine.sort((a, b) => b[0].y - a[0].y);
3503
+ return byLine;
3504
+ }
3505
+ function findAnchorMatches(pagesRuns, text) {
3506
+ const matches = [];
3507
+ for (let pageIndex = 0; pageIndex < pagesRuns.length; pageIndex++) {
3508
+ for (const line of lines(pagesRuns[pageIndex])) {
3509
+ let lineText = "";
3510
+ const owners = [];
3511
+ for (const run of line) {
3512
+ owners.push({ run, start: lineText.length });
3513
+ lineText += run.text;
3514
+ }
3515
+ let from = 0;
3516
+ while (true) {
3517
+ const index = lineText.indexOf(text, from);
3518
+ if (index === -1) break;
3519
+ from = index + 1;
3520
+ const owner = [...owners].reverse().find((candidate) => candidate.start <= index);
3521
+ const offsetChars = index - owner.start;
3522
+ const perChar = owner.run.text.length > 0 ? owner.run.width / owner.run.text.length : 0;
3523
+ const matchWidth = perChar * text.length;
3524
+ matches.push({
3525
+ page: pageIndex + 1,
3526
+ x: owner.run.x + perChar * offsetChars,
3527
+ y: owner.run.y,
3528
+ width: matchWidth,
3529
+ height: owner.run.height > 0 ? owner.run.height : 12
3530
+ });
3531
+ }
3532
+ }
3533
+ }
3534
+ return matches;
3535
+ }
3536
+ async function locate(pdf, queries) {
3537
+ if (queries.length === 0) return [];
3538
+ for (const query of queries) {
3539
+ if (query.kind === "anchor" && query.occurrence !== void 0 && !(Number.isInteger(query.occurrence) && query.occurrence >= 1)) {
3540
+ throw new RangeError(`Placement "${query.id}": occurrence must be a positive integer, got ${query.occurrence}`);
3541
+ }
3542
+ }
3543
+ const pages = await pageTextRuns(pdf);
3544
+ const pageHeights = pages.map((page) => page.mediaBox[3] - page.mediaBox[1]);
3545
+ const pageOrigins = pages.map((page) => ({ x: page.mediaBox[0], y: page.mediaBox[1] }));
3546
+ const pagesRuns = pages.map((page) => page.runs);
3547
+ const markerFields = queries.some((query) => query.kind === "marker") ? fieldsFromPageRuns(pages) : [];
3548
+ const hits = [];
3549
+ const failures = [];
3550
+ for (const query of queries) {
3551
+ if (query.kind === "marker") {
3552
+ const matches2 = markerFields.filter(
3553
+ (field) => field.signerIndex === query.signerIndex && field.fieldType === fieldTypeToString(query.fieldType)
3554
+ );
3555
+ if (matches2.length === 0) {
3556
+ failures.push({ id: query.id, reason: "missing" });
3557
+ } else if (matches2.length > 1) {
3558
+ failures.push({ id: query.id, reason: "ambiguous", matches: matches2.length });
3559
+ } else {
3560
+ const field = matches2[0];
3561
+ hits.push({
3562
+ id: query.id,
3563
+ page: field.page,
3564
+ x: field.x,
3565
+ y: field.y,
3566
+ width: field.width,
3567
+ height: field.height
3568
+ });
3569
+ }
3570
+ continue;
3571
+ }
3572
+ const matches = findAnchorMatches(pagesRuns, query.text);
3573
+ if (matches.length === 0) {
3574
+ failures.push({ id: query.id, reason: "missing" });
3575
+ continue;
3576
+ }
3577
+ if (query.occurrence === void 0 && matches.length > 1) {
3578
+ failures.push({ id: query.id, reason: "ambiguous", matches: matches.length });
3579
+ continue;
3580
+ }
3581
+ const pick = matches[(query.occurrence ?? 1) - 1];
3582
+ if (!pick) {
3583
+ failures.push({ id: query.id, reason: "missing", matches: matches.length });
3584
+ continue;
3585
+ }
3586
+ const origin = pageOrigins[pick.page - 1];
3587
+ const pageHeight = pageHeights[pick.page - 1];
3588
+ hits.push({
3589
+ id: query.id,
3590
+ page: pick.page,
3591
+ x: pick.x - origin.x,
3592
+ y: pageHeight - (pick.y - origin.y) - pick.height,
3593
+ width: pick.width,
3594
+ height: pick.height
3595
+ });
3596
+ }
3597
+ if (failures.length > 0) {
3598
+ const summary = failures.map(
3599
+ (failure) => failure.reason === "ambiguous" ? `${failure.id} (ambiguous: ${failure.matches} matches, pass occurrence)` : `${failure.id} (not found)`
3600
+ ).join(", ");
3601
+ throw new LocateError(`Could not resolve ${failures.length} of ${queries.length} placements: ${summary}`, failures);
3602
+ }
3603
+ return hits;
3604
+ }
3605
+
3606
+ // src/pdf/read-form.ts
3607
+ import { defaultFormatter as defaultFormatter3 } from "@asterdoc/format";
3608
+ var PdfExtractionError = class extends Error {
3609
+ code;
3610
+ constructor(code, message) {
3611
+ super(message);
3612
+ this.name = "PdfExtractionError";
3613
+ this.code = code;
3614
+ }
3615
+ };
3616
+ function stateName(value) {
3617
+ if (isName(value)) return value.value;
3618
+ return typeof value === "string" ? value : void 0;
3619
+ }
3620
+ function buttonState(model, field) {
3621
+ const value = stateName(model.resolve(field.dict.entries.get("V")));
3622
+ if (value !== void 0) return value;
3623
+ const states = field.widgets.map((widget2) => stateName(model.resolve(widget2.dict.entries.get("AS")))).filter((state) => state !== void 0);
3624
+ return states.find((state) => state !== "Off") ?? states[0];
3625
+ }
3626
+ function readField(model, field) {
3627
+ if (field.type === "checkbox" || field.type === "radio") {
3628
+ const raw = buttonState(model, field);
3629
+ return { field, raw, on: raw !== void 0 && raw !== "Off" };
3630
+ }
3631
+ const value = model.resolve(field.dict.entries.get("V"));
3632
+ if (field.type === "dropdown") {
3633
+ const selected = (Array.isArray(value) ? value : [value]).map((entry) => typeof entry === "string" ? decodeTextString(entry) : void 0).filter((entry) => entry !== void 0 && entry.trim() !== "");
3634
+ return { field, raw: selected.length > 0 ? selected.join(", ") : void 0, selected };
3635
+ }
3636
+ const decoded = typeof value === "string" ? decodeTextString(value) : void 0;
3637
+ const text = decoded !== void 0 && decoded.trim() !== "" ? decoded : void 0;
3638
+ return { field, raw: text };
3639
+ }
3640
+ function assertReadablePdf(bytes) {
3641
+ const head = byteString(bytes.subarray(0, 1024));
3642
+ if (!head.includes("%PDF-")) {
3643
+ throw new PdfExtractionError("malformed_pdf", "The input is not a PDF: it has no %PDF- header.");
3644
+ }
3645
+ const tail = byteString(bytes.subarray(Math.max(0, bytes.length - 2048)));
3646
+ const end = tail.lastIndexOf("%%EOF");
3647
+ if (end === -1 || !/^[\s\0]*$/.test(tail.slice(end + 5))) {
3648
+ throw new PdfExtractionError("malformed_pdf", "The PDF is truncated: it does not end with an end-of-file marker.");
3649
+ }
3650
+ }
3651
+ async function loadFormFields(bytes) {
3652
+ assertReadablePdf(bytes);
3653
+ let model;
3654
+ try {
3655
+ model = await PdfModel.load(bytes);
3656
+ } catch (error) {
3657
+ if (error instanceof PdfEncryptedError) throw error;
3658
+ throw new PdfExtractionError("malformed_pdf", `The PDF could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
3659
+ }
3660
+ let fields;
3661
+ try {
3662
+ fields = acroFields(model)?.fields ?? [];
3663
+ } catch (error) {
3664
+ throw new PdfExtractionError("malformed_pdf", `The PDF could not be read: ${error instanceof Error ? error.message : String(error)}`);
3665
+ }
3666
+ const fillable = fields.filter((field) => field.type !== "signature" && field.type !== "button" && field.type !== "unknown");
3667
+ if (fillable.length === 0) {
3668
+ throw new PdfExtractionError(
3669
+ "no_form_fields",
3670
+ "The PDF has no AcroForm fields to read. Flattened and scanned documents need the hosted Asterdoc extraction service."
3671
+ );
3672
+ }
3673
+ return { model, fields: fillable, names: new Set(fields.map((field) => field.name)) };
3674
+ }
3675
+ var numericMembers = {
3676
+ money: /* @__PURE__ */ new Set(["amount"]),
3677
+ coordinate: /* @__PURE__ */ new Set(["lat", "lon"]),
3678
+ bbox: /* @__PURE__ */ new Set(["southWest.lat", "southWest.lon", "northEast.lat", "northEast.lon"])
3679
+ };
3680
+ function targetFor(form, path) {
3681
+ const segments = pathSegments(path);
3682
+ const [root, ...rest] = segments;
3683
+ if (root === "parties") {
3684
+ return rest.length >= 2 && !(rest.length === 2 && /^\d+$/.test(rest[1])) ? { kind: "member", type: "text" } : { kind: "fixed", reason: "A whole party is written as formatted text; bind its parts to recover them." };
3685
+ }
3686
+ if (root === "defs") return { kind: "fixed", reason: "Computed values are derived from other data, not read back." };
3687
+ if (root === "annexes") return { kind: "fixed", reason: "Annexes are attachments, not form field values." };
3688
+ let field = root ? form.fields?.[root] : void 0;
3689
+ if (!field) return { kind: "fixed", reason: "The path is artifact metadata, not a data value." };
3690
+ for (let index = 0; index < rest.length; index++) {
3691
+ const segment = rest[index];
3692
+ if (field.type === "list" && /^\d+$/.test(segment)) field = field.item;
3693
+ else if (field.type === "fieldset" && field.fields[segment]) field = field.fields[segment];
3694
+ else return { kind: "member", type: numericMembers[field.type]?.has(rest.slice(index).join(".")) ? "number" : "text" };
3695
+ }
3696
+ return { kind: "field", field };
3697
+ }
3698
+ var ok = (value) => ({ ok: true, value });
3699
+ var fail = (reason) => ({ ok: false, reason });
3700
+ function numberSymbols(locale) {
3701
+ try {
3702
+ const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
3703
+ return {
3704
+ group: parts.find((part) => part.type === "group")?.value ?? ",",
3705
+ decimal: parts.find((part) => part.type === "decimal")?.value ?? "."
3706
+ };
3707
+ } catch {
3708
+ return { group: ",", decimal: "." };
3709
+ }
3710
+ }
3711
+ function parseNumber(raw, locale) {
3712
+ const text = raw.trim();
3713
+ if (/^[-+]?\d+(\.\d+)?$/.test(text)) return Number(text);
3714
+ const { group, decimal } = numberSymbols(locale);
3715
+ const grouped = text.split(group).join("").replace(/[\s\u00a0\u202f]/g, "").replace(/\u2212/g, "-");
3716
+ const normalized = decimal === "." ? grouped : grouped.split(decimal).join(".");
3717
+ if (!/^[-+]?\d+(\.\d+)?$/.test(normalized)) return void 0;
3718
+ const value = Number(normalized);
3719
+ return Number.isFinite(value) ? value : void 0;
3720
+ }
3721
+ function isoDate(year, month, day) {
3722
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return void 0;
3723
+ const date = new Date(Date.UTC(year, month - 1, day));
3724
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return void 0;
3725
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
3726
+ }
3727
+ function numericDateOrder(locale) {
3728
+ try {
3729
+ return new Intl.DateTimeFormat(locale, { year: "numeric", month: "2-digit", day: "2-digit", timeZone: "UTC" }).formatToParts(new Date(Date.UTC(2e3, 0, 2))).map((part) => part.type).filter((type) => type === "year" || type === "month" || type === "day");
3730
+ } catch {
3731
+ return ["month", "day", "year"];
3732
+ }
3733
+ }
3734
+ function monthIndex(token, locale) {
3735
+ const needle = token.replace(/\.$/, "").toLocaleLowerCase(locale);
3736
+ for (const style of ["long", "short"]) {
3737
+ const format3 = new Intl.DateTimeFormat(locale, { month: style, timeZone: "UTC" });
3738
+ for (let month = 0; month < 12; month++) {
3739
+ const name2 = format3.format(new Date(Date.UTC(2e3, month, 1))).replace(/\.$/, "").toLocaleLowerCase(locale);
3740
+ if (name2 === needle) return month + 1;
3741
+ }
3742
+ }
3743
+ return void 0;
3744
+ }
3745
+ function parseDate(raw, locale) {
3746
+ const text = raw.trim();
3747
+ const iso = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
3748
+ if (iso) return isoDate(Number(iso[1]), Number(iso[2]), Number(iso[3]));
3749
+ const numeric = text.match(/^(\d{1,4})[/.-](\d{1,2})[/.-](\d{1,4})$/);
3750
+ if (numeric) {
3751
+ const values = [Number(numeric[1]), Number(numeric[2]), Number(numeric[3])];
3752
+ const order = numericDateOrder(locale);
3753
+ if (order.length !== 3) return void 0;
3754
+ const part = (type) => values[order.indexOf(type)];
3755
+ const yearText = numeric[order.indexOf("year") + 1];
3756
+ if (yearText.length !== 4) return void 0;
3757
+ return isoDate(part("year"), part("month"), part("day"));
3758
+ }
3759
+ const tokens = text.split(/[\s,]+/).filter(Boolean);
3760
+ if (tokens.length !== 3) return void 0;
3761
+ let month;
3762
+ const numbers = [];
3763
+ for (const token of tokens) {
3764
+ if (/^\d+\.?$/.test(token)) numbers.push(token.replace(/\.$/, ""));
3765
+ else if (month === void 0) month = monthIndex(token, locale);
3766
+ else return void 0;
3767
+ }
3768
+ if (month === void 0 || numbers.length !== 2) return void 0;
3769
+ const year = numbers.find((value) => value.length === 4);
3770
+ const day = numbers.find((value) => value.length <= 2);
3771
+ if (!year || !day) return void 0;
3772
+ return isoDate(Number(year), month, Number(day));
3773
+ }
3774
+ function parseTime(raw) {
3775
+ const text = raw.trim();
3776
+ const clock = text.match(/^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/);
3777
+ if (clock) return text;
3778
+ const meridiem = text.match(/^(\d{1,2}):([0-5]\d)(?::([0-5]\d))?\s*([AaPp])\.?\s*[Mm]\.?$/);
3779
+ if (!meridiem) return void 0;
3780
+ const hour = Number(meridiem[1]);
3781
+ if (hour < 1 || hour > 12) return void 0;
3782
+ const pm = meridiem[4].toLowerCase() === "p";
3783
+ const hours = String(hour % 12 + (pm ? 12 : 0)).padStart(2, "0");
3784
+ return `${hours}:${meridiem[2]}${meridiem[3] ? `:${meridiem[3]}` : ""}`;
3785
+ }
3786
+ function matchOption(raw, options, formatter) {
3787
+ const label = (option) => {
3788
+ const result = formatter.safeFormatEnum(option.value, { options });
3789
+ return result.success ? result.value : option.label ?? String(option.value);
3790
+ };
3791
+ const byValue = options.filter((option) => String(option.value) === raw);
3792
+ if (byValue.length === 1) return byValue[0];
3793
+ const byLabel = options.filter((option) => label(option) === raw);
3794
+ if (byLabel.length === 1) return byLabel[0];
3795
+ const folded = raw.trim().toLowerCase();
3796
+ const loose = options.filter((option) => String(option.value).toLowerCase() === folded || label(option).toLowerCase() === folded);
3797
+ return loose.length === 1 ? loose[0] : void 0;
3798
+ }
3799
+ var currencyCodes;
3800
+ function currencies() {
3801
+ if (currencyCodes) return currencyCodes;
3802
+ const intl = Intl;
3803
+ try {
3804
+ currencyCodes = intl.supportedValuesOf?.("currency") ?? [];
3805
+ } catch {
3806
+ currencyCodes = [];
3807
+ }
3808
+ if (currencyCodes.length === 0) currencyCodes = ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "CNY", "INR", "MXN"];
3809
+ return currencyCodes;
3810
+ }
3811
+ function parseMoney(raw, field, formatter) {
3812
+ const text = raw.trim();
3813
+ const amountText = text.replace(/^[^\d(+\-\u2212]+|[^\d)]+$/g, "").replace(/^\((.*)\)$/, "-$1");
3814
+ const amount = parseNumber(amountText, formatter.locale);
3815
+ if (amount === void 0) return fail("The value is not an amount of money.");
3816
+ const declared = field.type === "money" ? field.currency : void 0;
3817
+ const preferred = field.type === "money" && field.default?.currency ? [field.default.currency] : [];
3818
+ const candidates = declared ? [declared] : [...preferred, ...currencies().filter((code) => !preferred.includes(code))];
3819
+ const matches = /* @__PURE__ */ new Set();
3820
+ for (const currency of candidates) {
3821
+ const result = formatter.safeFormatMoney({ amount, currency });
3822
+ if (result.success && result.value === text) matches.add(currency);
3823
+ if (matches.size > 1) break;
3824
+ }
3825
+ if (matches.size === 1) return ok({ amount, currency: [...matches][0] });
3826
+ return fail(matches.size === 0 ? "The currency cannot be determined from the text; write the amount with its currency symbol, or bind the amount on its own." : "The text matches more than one currency; declare the field's currency, or write the amount with its currency symbol.");
3827
+ }
3828
+ var composite = /* @__PURE__ */ new Set(["address", "person", "organization", "identification", "coordinate", "bbox", "list", "fieldset"]);
3829
+ function parseFieldValue(raw, field, formatter) {
3830
+ if (composite.has(field.type)) {
3831
+ return fail(`A ${field.type} value written as one piece of text cannot be split back into its parts; bind its parts to recover it.`);
3832
+ }
3833
+ switch (field.type) {
3834
+ case "text":
3835
+ case "email":
3836
+ case "uuid":
3837
+ case "uri":
3838
+ return ok(raw);
3839
+ case "enum": {
3840
+ const option = matchOption(raw, field.enum, formatter);
3841
+ return option ? ok(option.value) : fail("The text matches none of the field's options.");
3842
+ }
3843
+ case "multiselect": {
3844
+ for (const parts of [raw.split(", "), raw.split(",")]) {
3845
+ const values = parts.map((part) => part.trim()).filter(Boolean);
3846
+ const options = values.map((value) => matchOption(value, field.enum, formatter));
3847
+ if (options.every((option) => option !== void 0)) return ok(options.map((option) => option.value));
3848
+ }
3849
+ return fail("The selection includes a value that matches none of the field's options.");
3850
+ }
3851
+ case "boolean": {
3852
+ const text = raw.trim();
3853
+ const yes = formatter.safeFormatBoolean(true);
3854
+ const no = formatter.safeFormatBoolean(false);
3855
+ if (yes.success && text === yes.value) return ok(true);
3856
+ if (no.success && text === no.value) return ok(false);
3857
+ const folded = text.toLowerCase();
3858
+ if (folded === "true" || folded === "yes") return ok(true);
3859
+ if (folded === "false" || folded === "no") return ok(false);
3860
+ return fail("The text is not a yes or no value.");
3861
+ }
3862
+ case "number":
3863
+ case "rating": {
3864
+ const value = parseNumber(raw, formatter.locale);
3865
+ return value === void 0 ? fail("The text is not a number.") : ok(value);
3866
+ }
3867
+ case "percentage": {
3868
+ const value = parseNumber(raw.trim().replace(/\s*%$/, ""), formatter.locale);
3869
+ return value === void 0 ? fail("The text is not a percentage.") : ok(value);
3870
+ }
3871
+ case "money":
3872
+ return parseMoney(raw, field, formatter);
3873
+ case "date": {
3874
+ const value = parseDate(raw, formatter.locale);
3875
+ return value === void 0 ? fail("The text is not a date.") : ok(value);
3876
+ }
3877
+ case "datetime": {
3878
+ const text = raw.trim();
3879
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(text) && !Number.isNaN(Date.parse(text)) ? ok(text) : fail("Only an ISO 8601 date and time can be read back exactly.");
3880
+ }
3881
+ case "time": {
3882
+ const value = parseTime(raw);
3883
+ return value === void 0 ? fail("The text is not a time of day.") : ok(value);
3884
+ }
3885
+ case "duration": {
3886
+ const text = raw.trim();
3887
+ return /^P(?!$)(\d+(\.\d+)?Y)?(\d+(\.\d+)?M)?(\d+(\.\d+)?W)?(\d+(\.\d+)?D)?(T(?=\d)(\d+(\.\d+)?H)?(\d+(\.\d+)?M)?(\d+(\.\d+)?S)?)?$/.test(text) ? ok(text) : fail("Only an ISO 8601 duration can be read back exactly.");
3888
+ }
3889
+ case "phone": {
3890
+ const text = raw.trim().replace(/[\s().-]/g, "");
3891
+ return /^\+[1-9]\d{1,14}$/.test(text) ? ok({ number: text }) : fail("The text is not an international phone number.");
3892
+ }
3893
+ }
3894
+ return fail(`Values of type ${field.type} cannot be read back.`);
3895
+ }
3896
+ function parseTarget(raw, target, formatter) {
3897
+ if (target.kind === "fixed") return fail(target.reason);
3898
+ if (target.kind === "field") return parseFieldValue(raw, target.field, formatter);
3899
+ if (target.type === "number") {
3900
+ const value = parseNumber(raw, formatter.locale);
3901
+ return value === void 0 ? fail("The text is not a number.") : ok(value);
3902
+ }
3903
+ return ok(raw);
3904
+ }
3905
+ function source(state, name2) {
3906
+ return state?.raw === void 0 ? { field: name2 } : { field: name2, value: state.raw };
3907
+ }
3908
+ function readDirect(state, target, formatter) {
3909
+ const { field } = state;
3910
+ if (field.type === "checkbox") {
3911
+ const isBoolean = target.kind === "field" ? target.field.type === "boolean" : false;
3912
+ if (!isBoolean) {
3913
+ return state.on ? { kind: "unparseable", reason: "A checkbox can only be read back into a yes or no field." } : { kind: "empty" };
3914
+ }
3915
+ return state.on ? { kind: "value", value: true } : { kind: "empty" };
3916
+ }
3917
+ if (field.type === "radio") {
3918
+ if (!state.on || state.raw === void 0) return { kind: "empty" };
3919
+ const parsed2 = parseTarget(state.raw, target, formatter);
3920
+ return parsed2.ok ? { kind: "value", value: parsed2.value } : { kind: "unparseable", reason: parsed2.reason };
3921
+ }
3922
+ if (field.type === "dropdown") {
3923
+ if (!state.selected || state.selected.length === 0) return { kind: "empty" };
3924
+ const raw = state.selected.join(", ");
3925
+ const parsed2 = parseTarget(raw, target, formatter);
3926
+ return parsed2.ok ? { kind: "value", value: parsed2.value } : { kind: "unparseable", reason: parsed2.reason };
3927
+ }
3928
+ if (state.raw === void 0) return { kind: "empty" };
3929
+ if (target.kind === "fixed") return { kind: "not_recoverable", reason: target.reason };
3930
+ if (target.kind === "field" && composite.has(target.field.type)) {
3931
+ return { kind: "not_recoverable", reason: `A ${target.field.type} value written as one piece of text cannot be split back into its parts; bind its parts to recover it.` };
3932
+ }
3933
+ const parsed = parseTarget(state.raw, target, formatter);
3934
+ return parsed.ok ? { kind: "value", value: parsed.value } : { kind: "unparseable", reason: parsed.reason };
3935
+ }
3936
+ function sameValue(left, right) {
3937
+ return JSON.stringify(left) === JSON.stringify(right);
3938
+ }
3939
+ function resolvePath(work, target, formatter) {
3940
+ const readings = [...work.readings];
3941
+ if (work.parts.size > 0) {
3942
+ const positions = [...work.parts.keys()].sort((left, right) => left - right);
3943
+ const values2 = positions.map((position) => work.parts.get(position));
3944
+ const lastFilled = values2.reduce((last, value, index) => value === void 0 ? last : index, -1);
3945
+ if (lastFilled === -1) readings.push({ kind: "empty" });
3946
+ else if (positions.some((position, index) => position !== index + 1)) {
3947
+ readings.push({ kind: "not_recoverable", reason: "The split parts are not all bound, so the value cannot be joined back." });
3948
+ } else if (values2.slice(0, lastFilled).some((value) => value === void 0)) {
3949
+ readings.push({ kind: "unparseable", reason: "A part before the last filled part is empty, so the parts cannot be joined in order." });
3950
+ } else {
3951
+ const parsed = parseTarget(values2.slice(0, lastFilled + 1).join("-"), target, formatter);
3952
+ readings.push(parsed.ok ? { kind: "value", value: parsed.value } : { kind: "unparseable", reason: parsed.reason });
3953
+ }
3954
+ }
3955
+ if (work.options.size > 0) {
3956
+ const checked = [...work.options].filter(([, on]) => on).map(([qualifier]) => qualifier);
3957
+ const declared = target.kind === "field" && (target.field.type === "enum" || target.field.type === "multiselect") ? target.field.enum : [];
3958
+ const valueOf = (qualifier) => declared.find((option) => String(option.value) === qualifier)?.value ?? qualifier;
3959
+ if (checked.length === 0) readings.push({ kind: "empty" });
3960
+ else if (work.optionKind === "multiselect") readings.push({ kind: "value", value: checked.map(valueOf) });
3961
+ else if (checked.length === 1) readings.push({ kind: "value", value: valueOf(checked[0]) });
3962
+ else readings.push({ kind: "unparseable", reason: "More than one option is checked for a field that takes one." });
3963
+ }
3964
+ const problems = readings.filter((reading) => reading.kind === "unparseable");
3965
+ if (problems.length > 0) return { status: "unparseable", reason: problems.map((problem) => problem.reason).join(" ") };
3966
+ const values = readings.filter((reading) => reading.kind === "value");
3967
+ if (values.length > 0) {
3968
+ const [first] = values;
3969
+ if (values.every((reading) => sameValue(reading.value, first.value))) return { status: "recovered", value: first.value };
3970
+ return { status: "unparseable", reason: "The PDF fields that carry this path hold different values." };
3971
+ }
3972
+ const lost = readings.find((reading) => reading.kind === "not_recoverable");
3973
+ if (lost) return { status: "not_recoverable", reason: lost.reason };
3974
+ return { status: "empty" };
3975
+ }
3976
+ function setValue(root, segments, value) {
3977
+ let current = root;
3978
+ for (let index = 0; index < segments.length - 1; index++) {
3979
+ const key = segments[index];
3980
+ const nextIsIndex = /^\d+$/.test(segments[index + 1]);
3981
+ const container = current;
3982
+ if (container[key] === void 0 || container[key] === null || typeof container[key] !== "object") {
3983
+ container[key] = nextIsIndex ? [] : {};
3984
+ }
3985
+ current = container[key];
3986
+ }
3987
+ ;
3988
+ current[segments.at(-1)] = value;
3989
+ }
3990
+ function placeValue(data, path, value) {
3991
+ const segments = pathSegments(path);
3992
+ const [root, ...rest] = segments;
3993
+ if (root === "parties" && rest.length > 0) {
3994
+ data.parties ??= {};
3995
+ setValue(data.parties, rest, value);
3996
+ return;
3997
+ }
3998
+ setValue(data.fields, segments, value);
3999
+ }
4000
+ async function extractPdfData({ pdf, form, bindings, formatter: filledWith = defaultFormatter3, format: format3 }) {
4001
+ const formatter = format3?.money ? filledWith.compose({ money: format3.money }) : filledWith;
4002
+ const parsed = parseBindings(bindings);
4003
+ const sources = parsed.flatMap(([, parts]) => parts.map((part) => part.source));
4004
+ validateFieldBindings(form, Object.fromEntries(sources.map((source2, index) => [String(index), source2])));
4005
+ const { model, fields, names } = await loadFormFields(pdf);
4006
+ const states = new Map(fields.map((field) => [field.name, readField(model, field)]));
4007
+ const bindingNames = Object.keys(bindings);
4008
+ const missing = bindingNames.filter((name2) => !names.has(name2));
4009
+ if (missing.length === bindingNames.length) {
4010
+ throw new PdfExtractionError(
4011
+ "not_matching",
4012
+ `None of the PDF's ${fields.length} form fields match the layer's ${bindingNames.length} bindings. Check that the PDF is this artifact's form.`
4013
+ );
4014
+ }
4015
+ if (missing.length > 0) {
4016
+ throw new PdfExtractionError(
4017
+ "not_matching",
4018
+ `The PDF has no field for ${missing.length} of the layer's ${bindingNames.length} bindings: ${missing.join(", ")}. Check that the PDF is this artifact's form.`
4019
+ );
4020
+ }
4021
+ const work = /* @__PURE__ */ new Map();
4022
+ const workFor = (path) => {
4023
+ let entry = work.get(path);
4024
+ if (!entry) {
4025
+ entry = { sources: [], readings: [], parts: /* @__PURE__ */ new Map(), options: /* @__PURE__ */ new Map() };
4026
+ work.set(path, entry);
4027
+ }
4028
+ return entry;
4029
+ };
4030
+ for (const [pdfName, parts] of parsed) {
4031
+ const state = states.get(pdfName);
4032
+ if (parts.length > 1) {
4033
+ for (const part of parts) {
4034
+ const entry2 = workFor(part.path);
4035
+ entry2.sources.push(source(state, pdfName));
4036
+ entry2.readings.push(state?.raw === void 0 ? { kind: "empty" } : { kind: "not_recoverable", reason: "Several values are joined into one PDF field, and extraction does not guess how to split them." });
4037
+ }
4038
+ continue;
4039
+ }
4040
+ const { path, qualifier } = parts[0];
4041
+ const entry = workFor(path);
4042
+ entry.sources.push(source(state, pdfName));
4043
+ const target = targetFor(form, path);
4044
+ const fieldType = target.kind === "field" ? target.field.type : void 0;
4045
+ const isPart = qualifier !== void 0 && fieldType !== "boolean" && fieldType !== "enum" && fieldType !== "multiselect" && splitPartIndex(qualifier) !== void 0;
4046
+ if (!state) {
4047
+ if (isPart) entry.parts.set(Number(qualifier), void 0);
4048
+ else entry.readings.push({ kind: "empty" });
4049
+ continue;
4050
+ }
4051
+ if (qualifier === void 0) {
4052
+ entry.readings.push(readDirect(state, target, formatter));
4053
+ continue;
4054
+ }
4055
+ if (fieldType === "boolean") {
4056
+ entry.readings.push(readDirect(state, target, formatter));
4057
+ } else if (fieldType === "enum" || fieldType === "multiselect") {
4058
+ entry.optionKind = fieldType;
4059
+ entry.options.set(qualifier, state.field.type === "checkbox" || state.field.type === "radio" ? Boolean(state.on) : state.raw !== void 0);
4060
+ } else if (isPart) {
4061
+ entry.parts.set(Number(qualifier), state.raw);
4062
+ } else {
4063
+ entry.readings.push(state.raw === void 0 ? { kind: "empty" } : { kind: "not_recoverable", reason: `The qualifier "${qualifier}" has no reverse mapping for this field.` });
4064
+ }
4065
+ }
4066
+ const data = { fields: {} };
4067
+ const entries = [];
4068
+ for (const [path, entry] of work) {
4069
+ const resolved = resolvePath(entry, targetFor(form, path), formatter);
4070
+ if (resolved.status === "recovered") placeValue(data, path, resolved.value);
4071
+ entries.push({
4072
+ path,
4073
+ status: resolved.status,
4074
+ sources: entry.sources,
4075
+ ...resolved.reason ? { reason: resolved.reason } : {}
4076
+ });
4077
+ }
4078
+ const boundNames = new Set(Object.keys(bindings));
4079
+ const unbound = [];
4080
+ for (const state of states.values()) {
4081
+ if (boundNames.has(state.field.name)) continue;
4082
+ const type = state.field.type;
4083
+ if (type !== "text" && type !== "checkbox" && type !== "radio" && type !== "dropdown") continue;
4084
+ const hasValue = type === "checkbox" || type === "radio" ? state.on : state.raw !== void 0;
4085
+ if (hasValue && state.raw !== void 0) unbound.push({ field: state.field.name, type, value: state.raw });
4086
+ }
4087
+ return { data, report: { entries, unbound } };
4088
+ }
4089
+ function selectPdfExtractionLayer(layers, requested) {
4090
+ const all = layers ?? {};
4091
+ const pdfKeys = Object.keys(all).filter((key2) => all[key2]?.mimeType?.toLowerCase() === "application/pdf");
4092
+ let key = requested;
4093
+ if (key !== void 0) {
4094
+ const layer2 = all[key];
4095
+ if (!layer2) {
4096
+ throw new PdfExtractionError("layer_not_found", `Layer "${key}" not found. PDF layers: ${pdfKeys.join(", ") || "(none)"}.`);
4097
+ }
4098
+ if (layer2.mimeType?.toLowerCase() !== "application/pdf") {
4099
+ throw new PdfExtractionError("not_pdf_layer", `Layer "${key}" is ${layer2.mimeType ?? "not a PDF"}. PDF layers: ${pdfKeys.join(", ") || "(none)"}.`);
4100
+ }
4101
+ } else if (pdfKeys.length === 0) {
4102
+ throw new PdfExtractionError("no_pdf_layer", `The artifact has no PDF layer to read against. Layers: ${Object.keys(all).join(", ") || "(none)"}.`);
4103
+ } else if (pdfKeys.length > 1) {
4104
+ throw new PdfExtractionError("layer_required", `The artifact has several PDF layers; name one: ${pdfKeys.join(", ")}.`);
4105
+ } else {
4106
+ key = pdfKeys[0];
4107
+ }
4108
+ const layer = all[key];
4109
+ if (layer.bindingsFrom !== void 0 && !layer.bindings && !all[layer.bindingsFrom]) {
4110
+ throw new PdfExtractionError(
4111
+ "unknown_bindings_source",
4112
+ `Layer "${key}" takes its bindings from "${layer.bindingsFrom}", which is not a layer. Layers: ${Object.keys(all).join(", ")}.`
4113
+ );
4114
+ }
4115
+ const bindings = resolveLayerBindings(all, layer);
4116
+ if (!bindings || Object.keys(bindings).length === 0) {
4117
+ throw new PdfExtractionError("not_matching", `Layer "${key}" has no bindings, so no PDF field maps to the artifact.`);
4118
+ }
4119
+ return { key, bindings, ...layer.format && { format: layer.format } };
4120
+ }
4121
+
4122
+ // src/pdf/pattern-bounds.ts
4123
+ var REFERENCE_REPERTOIRE = [
4124
+ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZ",
4125
+ ..."abcdefghijklmnopqrstuvwxyz",
4126
+ ..."0123456789"
4127
+ ];
4128
+ var MAX_RANGE = 256;
4129
+ var Unsupported = class extends Error {
4130
+ };
4131
+ var DIGITS = [..."0123456789"];
4132
+ var WORD = [...REFERENCE_REPERTOIRE, "_"];
4133
+ var ANY = { any: true };
4134
+ var set = (chars) => ({ any: false, chars });
4135
+ var Parser2 = class {
4136
+ constructor(source2) {
4137
+ this.source = source2;
4138
+ }
4139
+ source;
4140
+ index = 0;
4141
+ parse() {
4142
+ const node = this.alternation();
4143
+ if (this.index < this.source.length) throw new Unsupported(`unexpected ${this.source[this.index]}`);
4144
+ return node;
4145
+ }
4146
+ peek() {
4147
+ return this.source[this.index];
4148
+ }
4149
+ alternation() {
4150
+ const options = [this.sequence()];
4151
+ while (this.peek() === "|") {
4152
+ this.index++;
4153
+ options.push(this.sequence());
4154
+ }
4155
+ return options.length === 1 ? options[0] : { kind: "alt", options };
4156
+ }
4157
+ sequence() {
4158
+ const items = [];
4159
+ while (this.index < this.source.length && this.peek() !== "|" && this.peek() !== ")") {
4160
+ const atom = this.atom();
4161
+ items.push(this.quantified(atom));
4162
+ }
4163
+ return { kind: "seq", items };
4164
+ }
4165
+ quantified(node) {
4166
+ const char = this.peek();
4167
+ let bounds;
4168
+ if (char === "*") bounds = [0, Infinity];
4169
+ else if (char === "+") bounds = [1, Infinity];
4170
+ else if (char === "?") bounds = [0, 1];
4171
+ if (bounds) this.index++;
4172
+ else if (char === "{") {
4173
+ const match = /^\{(\d+)(,(\d*))?\}/.exec(this.source.slice(this.index));
4174
+ if (!match) return node;
4175
+ this.index += match[0].length;
4176
+ const min = Number(match[1]);
4177
+ bounds = [min, match[2] === void 0 ? min : match[3] ? Number(match[3]) : Infinity];
4178
+ } else return node;
4179
+ if (this.peek() === "?") this.index++;
4180
+ return this.quantified({ kind: "repeat", node, min: bounds[0], max: bounds[1] });
4181
+ }
4182
+ atom() {
4183
+ const char = this.source[this.index++];
4184
+ if (char === "^" || char === "$") return { kind: "empty" };
4185
+ if (char === ".") return { kind: "chars", set: ANY };
4186
+ if (char === "[") return { kind: "chars", set: this.characterClass() };
4187
+ if (char === "\\") return this.escape(false);
4188
+ if (char === "(") {
4189
+ let lookaround = false;
4190
+ if (this.peek() === "?") {
4191
+ const rest = this.source.slice(this.index);
4192
+ if (rest.startsWith("?:")) this.index += 2;
4193
+ else if (/^\?(=|!|<=|<!)/.test(rest)) {
4194
+ lookaround = true;
4195
+ this.index += rest.startsWith("?<") ? 3 : 2;
4196
+ } else if (/^\?<[A-Za-z_$][\w$]*>/.test(rest)) this.index += rest.indexOf(">") + 1;
4197
+ else throw new Unsupported("group modifier");
4198
+ }
4199
+ const inner = this.alternation();
4200
+ if (this.peek() !== ")") throw new Unsupported("unclosed group");
4201
+ this.index++;
4202
+ return lookaround ? { kind: "empty" } : inner;
4203
+ }
4204
+ if (char === ")" || char === "*" || char === "+" || char === "?") throw new Unsupported(`unexpected ${char}`);
4205
+ return { kind: "chars", set: set([char]) };
4206
+ }
4207
+ /** An escape after the backslash; inside a class, `\b` is a backspace. */
4208
+ escape(inClass) {
4209
+ const char = this.source[this.index++];
4210
+ if (char === void 0) throw new Unsupported("trailing backslash");
4211
+ if (char === "d") return { kind: "chars", set: set(DIGITS) };
4212
+ if (char === "w") return { kind: "chars", set: set(WORD) };
4213
+ if (char === "s") return { kind: "chars", set: set([" "]) };
4214
+ if (char === "D" || char === "W" || char === "S") return { kind: "chars", set: ANY };
4215
+ if (!inClass && (char === "b" || char === "B")) return { kind: "empty" };
4216
+ if (/[1-9]/.test(char) || char === "k") throw new Unsupported("backreference");
4217
+ return { kind: "chars", set: set([this.escapedCharacter(char)]) };
4218
+ }
4219
+ escapedCharacter(char) {
4220
+ const code = (length) => {
4221
+ const hex = this.source.slice(this.index, this.index + length);
4222
+ if (!new RegExp(`^[0-9a-fA-F]{${length}}$`).test(hex)) throw new Unsupported("escape");
4223
+ this.index += length;
4224
+ return String.fromCodePoint(Number.parseInt(hex, 16));
4225
+ };
4226
+ if (char === "u") {
4227
+ if (this.peek() === "{") {
4228
+ const end = this.source.indexOf("}", this.index);
4229
+ if (end < 0) throw new Unsupported("escape");
4230
+ const value = String.fromCodePoint(Number.parseInt(this.source.slice(this.index + 1, end), 16));
4231
+ this.index = end + 1;
4232
+ return value;
4233
+ }
4234
+ return code(4);
4235
+ }
4236
+ if (char === "x") return code(2);
4237
+ if (char === "t") return " ";
4238
+ if (char === "n") return "\n";
4239
+ if (char === "r") return "\r";
4240
+ if (char === "b") return "\b";
4241
+ if (char === "p" || char === "P") throw new Unsupported("property escape");
4242
+ return char;
4243
+ }
4244
+ characterClass() {
4245
+ const negated = this.peek() === "^";
4246
+ if (negated) this.index++;
4247
+ const chars = [];
4248
+ let any = false;
4249
+ let first = true;
4250
+ while (this.peek() !== "]" || first) {
4251
+ first = false;
4252
+ let char = this.source[this.index++];
4253
+ if (char === void 0) throw new Unsupported("unclosed class");
4254
+ if (char === "\\") {
4255
+ const escaped = this.escape(true);
4256
+ if (escaped.kind !== "chars") continue;
4257
+ if (escaped.set.any) {
4258
+ any = true;
4259
+ continue;
4260
+ }
4261
+ if (escaped.set.chars.length > 1) {
4262
+ chars.push(...escaped.set.chars);
4263
+ continue;
4264
+ }
4265
+ char = escaped.set.chars[0];
4266
+ }
4267
+ if (this.peek() === "-" && this.source[this.index + 1] !== "]" && this.source[this.index + 1] !== void 0) {
4268
+ this.index++;
4269
+ let end = this.source[this.index++];
4270
+ if (end === "\\") {
4271
+ const escaped = this.escape(true);
4272
+ if (escaped.kind !== "chars" || escaped.set.any || escaped.set.chars.length !== 1) throw new Unsupported("range");
4273
+ end = escaped.set.chars[0];
4274
+ }
4275
+ const from = char.codePointAt(0);
4276
+ const to = end.codePointAt(0);
4277
+ if (to < from) throw new Unsupported("range");
4278
+ if (to - from > MAX_RANGE) any = true;
4279
+ else for (let point = from; point <= to; point++) chars.push(String.fromCodePoint(point));
4280
+ continue;
4281
+ }
4282
+ chars.push(char);
4283
+ }
4284
+ this.index++;
4285
+ return negated || any ? ANY : set([...new Set(chars)]);
4286
+ }
4287
+ };
4288
+ function parsePattern(pattern) {
4289
+ try {
4290
+ return new Parser2(pattern).parse();
4291
+ } catch (error) {
4292
+ if (error instanceof Unsupported) return void 0;
4293
+ throw error;
4294
+ }
4295
+ }
4296
+ function maxLength(node) {
4297
+ switch (node.kind) {
4298
+ case "chars":
4299
+ return 1;
4300
+ case "empty":
4301
+ return 0;
4302
+ case "seq":
4303
+ return node.items.reduce((total, item) => total + maxLength(item), 0);
4304
+ case "alt":
4305
+ return Math.max(...node.options.map(maxLength));
4306
+ case "repeat":
4307
+ return node.max === 0 ? 0 : node.max * maxLength(node.node);
4308
+ }
4309
+ }
4310
+ function charSets(node) {
4311
+ switch (node.kind) {
4312
+ case "chars":
4313
+ return [node.set];
4314
+ case "empty":
4315
+ return [];
4316
+ case "seq":
4317
+ return node.items.flatMap(charSets);
4318
+ case "alt":
4319
+ return node.options.flatMap(charSets);
4320
+ case "repeat":
4321
+ return node.max === 0 ? [] : charSets(node.node);
4322
+ }
4323
+ }
4324
+ function longestValue(node, pick, width, start = 0) {
4325
+ switch (node.kind) {
4326
+ case "chars":
4327
+ return pick(node.set, start);
4328
+ case "empty":
4329
+ return "";
4330
+ case "seq": {
4331
+ let text = "";
4332
+ for (const item of node.items) {
4333
+ const part = longestValue(item, pick, width, start + [...text].length);
4334
+ if (part === void 0) return void 0;
4335
+ text += part;
4336
+ }
4337
+ return text;
4338
+ }
4339
+ case "alt": {
4340
+ const options = node.options.map((option) => longestValue(option, pick, width, start));
4341
+ if (options.some((option) => option === void 0)) return void 0;
4342
+ return options.reduce((best, option) => width(option) > width(best) ? option : best);
4343
+ }
4344
+ case "repeat": {
4345
+ if (node.max === Infinity) return void 0;
4346
+ let text = "";
4347
+ for (let count = 0; count < node.max; count++) {
4348
+ const part = longestValue(node.node, pick, width, start + [...text].length);
4349
+ if (part === void 0) return void 0;
4350
+ text += part;
4351
+ }
4352
+ return text;
4353
+ }
4354
+ }
4355
+ }
4356
+ function isHyphen(node) {
4357
+ return node.kind === "chars" && !node.set.any && node.set.chars.length === 1 && node.set.chars[0] === "-";
4358
+ }
4359
+ function canHoldHyphen(node) {
4360
+ return charSets(node).some((item) => item.any || item.chars.includes("-"));
4361
+ }
4362
+ function topLevelItems(node) {
4363
+ if (node.kind === "seq") {
4364
+ return node.items.flatMap((item) => item.kind === "seq" ? topLevelItems(item) ?? [item] : [item]);
4365
+ }
4366
+ if (node.kind === "alt") return void 0;
4367
+ return [node];
4368
+ }
4369
+ function analyzePattern(pattern) {
4370
+ const node = parsePattern(pattern);
4371
+ return node && new PatternAnalysis(node);
4372
+ }
4373
+ var PatternAnalysis = class {
4374
+ constructor(node) {
4375
+ this.node = node;
4376
+ }
4377
+ node;
4378
+ /** The whole value's shape. */
4379
+ shape(width, pick) {
4380
+ return shapeOf(this.node, width, pick);
4381
+ }
4382
+ /**
4383
+ * The shape of the `index`th (0-based) part of a value split on `-`, when
4384
+ * the pattern is a sequence whose hyphens are fixed separators. Undefined
4385
+ * when the pattern does not fix where the hyphens fall.
4386
+ */
4387
+ part(index, width, pick) {
4388
+ const items = topLevelItems(this.node);
4389
+ if (!items) return void 0;
4390
+ const parts = [[]];
4391
+ for (const item of items) {
4392
+ if (isHyphen(item)) parts.push([]);
4393
+ else if (canHoldHyphen(item)) return void 0;
4394
+ else parts.at(-1).push(item);
4395
+ }
4396
+ const part = parts[index];
4397
+ return part ? shapeOf({ kind: "seq", items: part }, width, pick) : void 0;
4398
+ }
4399
+ /** Every character set the pattern allows anywhere. */
4400
+ sets() {
4401
+ return charSets(this.node);
4402
+ }
4403
+ };
4404
+ function shapeOf(node, width, pick) {
4405
+ const value = longestValue(node, pick, width);
4406
+ return {
4407
+ maxLength: maxLength(node),
4408
+ sets: charSets(node),
4409
+ ...value !== void 0 && { value }
4410
+ };
4411
+ }
4412
+
4413
+ // src/pdf/binding-fit.ts
4414
+ var TYPICAL_TEXT = "The quick brown fox jumps over the lazy dog ";
4415
+ var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "email", "uuid", "uri"]);
4416
+ var UUID_PATTERN = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
4417
+ var ANY2 = { any: true };
4418
+ var SHOWN_LENGTH = 40;
4419
+ var format2 = (value) => String(Number(value.toFixed(3)));
4420
+ var displayPath2 = (path) => `fields.${path}`;
4421
+ function widestCharacter(font, sets) {
4422
+ const pool = sets.some((item) => item.any) ? [.../* @__PURE__ */ new Set([...REFERENCE_REPERTOIRE, ...sets.flatMap((item) => item.any ? [] : item.chars)])] : [...new Set(sets.flatMap((item) => item.any ? [] : item.chars))];
4423
+ const drawable = pool.filter((character) => font.covers(character));
4424
+ const candidates = drawable.length > 0 ? drawable : pool;
4425
+ return candidates.reduce((best, character) => measure(font, character) > measure(font, best) ? character : best, candidates[0] ?? "W");
4426
+ }
4427
+ function typicalCharacter(font, set2, position) {
4428
+ const sample = TYPICAL_TEXT[position % TYPICAL_TEXT.length];
4429
+ if (set2.any || set2.chars.includes(sample)) return sample;
4430
+ const drawable = set2.chars.filter((character) => font.covers(character));
4431
+ const pool = drawable.length > 0 ? drawable : set2.chars;
4432
+ const mean = pool.reduce((total, character) => total + measure(font, character), 0) / pool.length;
4433
+ return pool.reduce((best, character) => Math.abs(measure(font, character) - mean) < Math.abs(measure(font, best) - mean) ? character : best, pool[0]);
4434
+ }
4435
+ function picker(font, kind) {
4436
+ return kind === "widest" ? (set2) => widestCharacter(font, [set2]) : (set2, position) => typicalCharacter(font, set2, position);
4437
+ }
4438
+ function unionSet(sets) {
4439
+ return sets.some((item) => item.any) ? ANY2 : { any: false, chars: [...new Set(sets.flatMap((item) => item.any ? [] : item.chars))] };
4440
+ }
4441
+ function measure(font, text) {
4442
+ return font.covers(text) ? font.width(text) : 0;
4443
+ }
4444
+ function probeOf(sets) {
4445
+ const chars = new Set(sets.flatMap((item) => item.any ? REFERENCE_REPERTOIRE : item.chars));
4446
+ return [...chars].join("");
4447
+ }
4448
+ function textSource(path, field, part) {
4449
+ const declared = field.pattern ?? (field.type === "uuid" ? UUID_PATTERN : void 0);
4450
+ const analysis = declared === void 0 ? void 0 : analyzePattern(declared);
4451
+ const shapeFor = (font, kind) => {
4452
+ const width = (text) => measure(font, text);
4453
+ const pick = picker(font, kind);
4454
+ return part === void 0 ? analysis?.shape(width, pick) : analysis?.part(part, width, pick);
4455
+ };
4456
+ const lengthOnly = { width: (text) => text.length, covers: () => true };
4457
+ const shape = shapeFor(lengthOnly, "widest");
4458
+ const maxLength2 = Math.min(field.maxLength ?? Infinity, shape?.maxLength ?? Infinity);
4459
+ if (maxLength2 === Infinity) return { kind: "unbounded", path };
4460
+ const sets = shape?.sets ?? analysis?.sets() ?? [ANY2];
4461
+ const byPattern = shape !== void 0 && shape.value !== void 0 && (field.maxLength === void 0 || shape.maxLength <= field.maxLength);
4462
+ const partLabel = part === void 0 ? "" : ` (part ${part + 1})`;
4463
+ const basis = byPattern ? `pattern ${declared}${partLabel}` : `maxLength ${field.maxLength}${partLabel}`;
4464
+ return {
4465
+ kind: "bounded",
4466
+ source: {
4467
+ path,
4468
+ probe: probeOf(sets),
4469
+ basis,
4470
+ exact: false,
4471
+ value(font, kind) {
4472
+ const measured = shapeFor(font, kind);
4473
+ if (byPattern && measured?.value !== void 0) return measured.value;
4474
+ const pick = picker(font, kind);
4475
+ const set2 = unionSet(sets);
4476
+ return Array.from({ length: maxLength2 }, (_, position) => pick(set2, position)).join("");
4477
+ }
4478
+ }
4479
+ };
4480
+ }
4481
+ function choiceSource(path, field) {
4482
+ if (field.type !== "enum" && field.type !== "multiselect") return { kind: "other" };
4483
+ const values = field.enum.map((option) => String(option.value));
4484
+ if (values.length === 0) return { kind: "other" };
4485
+ const count = field.type === "multiselect" ? Math.min(field.max ?? values.length, values.length) : 1;
4486
+ return {
4487
+ kind: "bounded",
4488
+ source: {
4489
+ path,
4490
+ probe: values.join(""),
4491
+ basis: field.type === "enum" ? "its widest option value" : count === values.length ? "all its option values" : `its ${count} widest option values`,
4492
+ exact: true,
4493
+ value(font) {
4494
+ return [...values].sort((left, right) => measure(font, right) - measure(font, left)).slice(0, count).join(", ");
4495
+ }
4496
+ }
4497
+ };
4498
+ }
4499
+ function analyzeSource(form, { path, qualifier }) {
4500
+ const field = fieldDefinition(form, path);
4501
+ if (!field) return { kind: "other" };
4502
+ if (qualifier !== void 0) {
4503
+ if (!TEXT_TYPES.has(field.type)) return { kind: "other" };
4504
+ const part = splitPartIndex(qualifier);
4505
+ return part === void 0 ? { kind: "other" } : textSource(path, field, part);
4506
+ }
4507
+ if (TEXT_TYPES.has(field.type)) return textSource(path, field);
4508
+ return choiceSource(path, field);
4509
+ }
4510
+ function analyzeBinding(form, binding) {
4511
+ const sources = parseBinding(binding).map((part) => analyzeSource(form, part));
4512
+ if (sources.some((source2) => source2.kind === "other")) return void 0;
4513
+ return sources;
4514
+ }
4515
+ function describe(layout) {
4516
+ return `${format2(layout.width)} \xD7 ${format2(layout.height)} pt box`;
4517
+ }
4518
+ function fitFailure(field, binding, sources, layout, text, kind, error) {
4519
+ if (error.reason !== "overflow" && error.reason !== "comb-length") return void 0;
4520
+ const length = [...text].length;
4521
+ const paths = sources.map((source2) => displayPath2(source2.path));
4522
+ const shown = length <= SHOWN_LENGTH ? `: "${text}"` : "";
4523
+ const exact = sources.every((source2) => source2.exact);
4524
+ const adjective = exact || error.reason === "comb-length" ? "longest" : kind;
4525
+ const what = sources.length === 1 ? `the ${adjective} value ${paths[0]} accepts under ${sources[0].basis} (${length} characters${shown})` : `the ${adjective} joined value of ${paths.join(", ")} (${length} characters)`;
4526
+ const message = error.reason === "comb-length" ? `${what} has more characters than the field's ${error.limit} comb boxes` : `${what} does not fit the ${describe(layout)} at the minimum size of ${format2(error.limit ?? MIN_FONT_SIZE)} pt` + (kind === "widest" ? "; a typical value fits, but one made mostly of wide letters such as W does not" : "");
4527
+ return {
4528
+ field: field.name,
4529
+ binding,
4530
+ paths,
4531
+ severity: kind === "typical" ? "error" : "warning",
4532
+ reason: error.reason,
4533
+ value: kind,
4534
+ ...error.limit !== void 0 && { limit: error.limit },
4535
+ length,
4536
+ message
4537
+ };
4538
+ }
4539
+ async function checkPdfBindingFit({ template, form, bindings, layerFont }) {
4540
+ const model = await PdfModel.load(template);
4541
+ const acro = acroFields(model);
4542
+ if (!acro) return [];
4543
+ const fonts = PdfFontSet.create(model, { layerFont });
4544
+ await fonts.readFormFonts(model.dict(acro.acroForm.entries.get("DR")));
4545
+ const byName = new Map(acro.fields.map((field) => [field.name, field]));
4546
+ const entries = bindings ? Object.entries(bindings) : Object.entries(form.fields ?? {}).filter(([, field]) => field.type !== "fieldset").map(([name2]) => [name2, name2]);
4547
+ const issues = [];
4548
+ for (const [name2, binding] of entries) {
4549
+ const field = byName.get(name2);
4550
+ if (!field || field.type !== "text") continue;
4551
+ const analyzed = analyzeBinding(form, binding);
4552
+ if (!analyzed) continue;
4553
+ const drawing = textFieldDrawing(field);
4554
+ const layouts = field.widgets.map((widget2) => widgetLayout(model, field, widget2, drawing)).filter((layout) => layout !== void 0);
4555
+ if (layouts.length === 0) continue;
4556
+ const unbounded = analyzed.filter((source2) => source2.kind === "unbounded");
4557
+ if (unbounded.length > 0) {
4558
+ const paths = unbounded.map((source2) => displayPath2(source2.path));
4559
+ issues.push({
4560
+ field: field.name,
4561
+ binding,
4562
+ paths,
4563
+ severity: "warning",
4564
+ reason: "unbounded",
4565
+ message: `${paths.join(", ")} has no maxLength or pattern that bounds its length, so a long value can fail to fill the ${describe(layouts[0])}${drawing.comb ? ` with ${drawing.comb} comb boxes` : ""}`
4566
+ });
4567
+ continue;
4568
+ }
4569
+ const sources = analyzed.map((source2) => source2.source);
4570
+ for (const layout of layouts) {
4571
+ const issue = checkLayout(fonts, field, binding, sources, layout);
4572
+ if (issue) {
4573
+ issues.push(issue);
4574
+ break;
4575
+ }
4576
+ }
4577
+ }
4578
+ return issues;
4579
+ }
4580
+ function checkLayout(fonts, field, binding, sources, layout) {
4581
+ let chosen;
4582
+ try {
4583
+ chosen = fonts.select(field.name, sources.map((source2) => source2.probe).join(""), layout.appearance.fontName);
4584
+ } catch (error) {
4585
+ if (error instanceof PdfFieldFillError) return void 0;
4586
+ throw error;
4587
+ }
4588
+ for (const kind of ["typical", "widest"]) {
4589
+ if (kind === "widest" && sources.every((source2) => source2.exact)) break;
4590
+ const text = sources.map((source2) => source2.value(chosen, kind)).join(", ");
4591
+ try {
4592
+ layoutFieldText(layout, text, fonts.select(field.name, text, layout.appearance.fontName));
4593
+ } catch (error) {
4594
+ if (!(error instanceof PdfFieldFillError)) throw error;
4595
+ return fitFailure(field, binding, sources, layout, text, kind, error);
4596
+ }
4597
+ }
4598
+ return void 0;
4599
+ }
4600
+ export {
4601
+ FieldType,
4602
+ LocateError,
4603
+ MIN_FONT_SIZE,
4604
+ PdfBindingKeyError,
4605
+ PdfBindingSyntaxError,
4606
+ PdfEncryptedError,
4607
+ PdfExtractionError,
4608
+ PdfFieldFillError,
4609
+ PdfFontError,
4610
+ PdfMergeError,
4611
+ TYPICAL_TEXT,
4612
+ UnknownMarkerError,
4613
+ checkPdfBindingFit,
4614
+ containsEncoding,
4615
+ decodeAll,
4616
+ embedPdfSignature,
4617
+ encode,
4618
+ extractFieldsFromPdf,
4619
+ extractPdfData,
4620
+ flattenPdf,
4621
+ inspectAcroFormFields,
4622
+ inspectPdf,
4623
+ locate,
4624
+ locator,
4625
+ mergePdfs,
4626
+ pageTextRuns,
4627
+ parseBinding,
4628
+ pdfContainsEncoding,
4629
+ pdfFontMetrics,
4630
+ pdfRenderer,
4631
+ preparePdfSignature,
4632
+ readPdfSignature,
4633
+ renderPdf,
4634
+ selectPdfExtractionLayer,
4635
+ selectPdfPages,
4636
+ signedBytes,
4637
+ stripEncoding
4638
+ };