@poe-platform/safe-bash 0.1.839 → 0.1.841

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/audio-ast/binary.d.ts +19 -0
  2. package/dist/audio-ast/index.d.ts +1 -0
  3. package/dist/audio-ast/index.js +92 -91
  4. package/dist/audio-ast/mpeg.d.ts +23 -0
  5. package/dist/mp4-ast/containers/adapters.d.ts +1 -0
  6. package/dist/mp4-ast/types.d.ts +8 -2
  7. package/dist/pdf-ast/cos/security.d.ts +10 -0
  8. package/dist/pdf-ast/edit/retained-attachment-edits.d.ts +4 -0
  9. package/dist/pdf-ast/edit/retained-graph.d.ts +1 -1
  10. package/dist/pdf-ast/image.js +1 -1
  11. package/dist/pdf-ast/index.d.ts +2 -1
  12. package/dist/pdf-ast/index.js +7 -1
  13. package/dist/pdf-ast/vendor/pdfjs-fonts.d.mts +8 -0
  14. package/dist/pdf-ast/vendor/pdfjs-fonts.mjs +28 -0
  15. package/dist/safe-bash/audio-ast.browser.js +2 -1
  16. package/dist/safe-bash/audio-ast.js +1 -1
  17. package/dist/safe-bash/command-chunks/{chunk-SG2KOOD4.js → chunk-3SAYZFMD.js} +2 -2
  18. package/dist/safe-bash/command-chunks/{chunk-T6OXG7QQ.js → chunk-AAOPVEPF.js} +66 -1
  19. package/dist/safe-bash/command-chunks/{chunk-FH5VMAPS.js → chunk-BZW25LNV.js} +420 -131
  20. package/dist/safe-bash/command-chunks/{chunk-DQVOBGI2.js → chunk-CIV3GER2.js} +6770 -6523
  21. package/dist/safe-bash/command-chunks/{chunk-46GA576V.js → chunk-CPKVUB7S.js} +2 -2
  22. package/dist/safe-bash/command-chunks/{chunk-MBXNG6C3.js → chunk-DLLN3C3Q.js} +1 -1
  23. package/dist/safe-bash/command-chunks/{chunk-ZSQCROFE.js → chunk-DM5T5D2K.js} +5 -1
  24. package/dist/safe-bash/command-chunks/{chunk-3EAZRKI4.js → chunk-G7HQYFIU.js} +10 -1
  25. package/dist/safe-bash/command-chunks/{chunk-GYKQ3IOV.js → chunk-LMYQMJYY.js} +94 -18
  26. package/dist/safe-bash/command-chunks/{chunk-DX7KU6YU.js → chunk-N52AHO6S.js} +1 -1
  27. package/dist/safe-bash/command-chunks/{chunk-4BSD2RGK.js → chunk-QPKL5GWH.js} +102 -42
  28. package/dist/safe-bash/command-chunks/{chunk-Z2T7E4ID.js → chunk-R7QLP5RH.js} +1212 -1119
  29. package/dist/safe-bash/command-chunks/{chunk-77ROD2TR.js → chunk-X3WNWRVT.js} +11 -5
  30. package/dist/safe-bash/command-chunks/{chunk-RSFKQE2U.js → chunk-YAXP4EHP.js} +19 -10
  31. package/dist/safe-bash/command-chunks/{chunk-TCXM7BSZ.js → chunk-YFMNXPLP.js} +4 -4
  32. package/dist/safe-bash-command-exiftool/index.js +25 -1
  33. package/dist/safe-bash-command-ffmpeg/index.js +191 -49
  34. package/dist/safe-bash-command-ffprobe/index.js +2 -2
  35. package/dist/safe-bash-command-ffprobe/media.js +2 -2
  36. package/dist/safe-bash-command-imagemagick/index.js +498 -101
  37. package/dist/safe-bash-command-mdq/index.js +2 -0
  38. package/dist/safe-bash-command-pandoc/citeproc-filters.js +7 -7
  39. package/dist/safe-bash-command-pandoc/command.js +8 -8
  40. package/dist/safe-bash-command-pandoc/index.js +52 -52
  41. package/dist/safe-bash-command-qpdf/index.js +1037 -654
  42. package/dist/safe-bash-command-qpdf/page-range.d.ts +2 -0
  43. package/dist/safe-bash-command-soffice/index.js +28 -8
  44. package/dist/safe-bash-command-sqlite3/btree.d.ts +2 -1
  45. package/dist/safe-bash-command-sqlite3/engine.d.ts +15 -1
  46. package/dist/safe-bash-command-sqlite3/index.js +294 -68
  47. package/dist/safe-bash-command-tar/create.js +2 -2
  48. package/dist/safe-bash-command-tar/extract.js +2 -2
  49. package/dist/safe-bash-command-tar/format.js +1 -1
  50. package/dist/safe-bash-command-tar/index.js +4 -4
  51. package/dist/safe-bash-command-tar/modes.js +4 -4
  52. package/dist/safe-bash-command-unzip/index.js +1 -1
  53. package/dist/safe-bash-command-unzip/unzip.js +1 -1
  54. package/dist/safe-bash-markdown-engine/commonmark-blocks.d.ts +17 -1
  55. package/dist/safe-bash-markdown-engine/commonmark-blocks.js +3 -3
  56. package/dist/safe-bash-markdown-engine/commonmark-inlines.d.ts +1 -1
  57. package/dist/safe-bash-markdown-engine/commonmark-inlines.js +3 -3
  58. package/dist/safe-bash-markdown-engine/commonmark-syntax.d.ts +5 -0
  59. package/dist/safe-bash-markdown-engine/commonmark-syntax.js +5 -1
  60. package/dist/safe-bash-markdown-engine/gfm-syntax.js +2 -2
  61. package/dist/safe-bash-markdown-engine/index.js +4 -4
  62. package/dist/safe-bash-zip-engine/index.js +1 -1
  63. package/dist/safe-bash-zip-engine/zip/repair.js +1 -1
  64. package/dist/safe-bash-zip-engine/zip/sfx.js +1 -1
  65. package/dist/safe-bash-zip-engine/zip-format.d.ts +2 -0
  66. package/dist/safe-bash-zip-engine/zip-format.js +1 -1
  67. package/package.json +2 -2
  68. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +2 -0
@@ -1,21 +1,504 @@
1
1
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
2
2
 
3
+ // packages/safe-bash-command-qpdf/src/linearization-report.ts
4
+ import { PdfError } from "../pdf-ast/index.js";
5
+ var HintBits = class _HintBits {
6
+ constructor(size, offset = 0) {
7
+ this.size = size;
8
+ this.position = offset * 8;
9
+ }
10
+ size;
11
+ position;
12
+ bytes = new Uint8Array(0);
13
+ start = 0;
14
+ *read(width) {
15
+ if (!Number.isInteger(width) || width < 0 || width > 32 || this.position + width > this.size * 8) throw new PdfError("E_PARSE", "Invalid linearization hint table");
16
+ let value = 0;
17
+ for (let bit = 0; bit < width; bit++, this.position++) {
18
+ const offset = Math.floor(this.position / 8);
19
+ if (offset < this.start || offset >= this.start + this.bytes.length) {
20
+ this.start = offset;
21
+ this.bytes = yield { offset, length: Math.min(4096, this.size - offset) };
22
+ if (!this.bytes.length) throw new PdfError("E_PARSE", "Truncated linearization hint table");
23
+ }
24
+ value = value * 2 + (this.bytes[offset - this.start] >> 7 - this.position % 8 & 1);
25
+ }
26
+ return value;
27
+ }
28
+ column(count, width) {
29
+ const cursor = new _HintBits(this.size, this.position / 8);
30
+ const end = this.position + count * width;
31
+ if (!Number.isSafeInteger(end) || end > this.size * 8) throw new PdfError("E_PARSE", "Truncated linearization hint table");
32
+ this.position = Math.ceil(end / 8) * 8;
33
+ return cursor;
34
+ }
35
+ };
36
+ function* linearizationReport(filename, values, size, sharedOffset, outlineOffset) {
37
+ const adjusted = (offset) => offset >= values.H_offset ? offset + values.H_length : offset;
38
+ yield `${filename}: linearization data:
39
+
40
+ `;
41
+ for (const key of ["file_size", "first_page_object", "first_page_end", "npages", "xref_zero_offset", "first_page", "H_offset", "H_length"]) yield `${key}: ${values[key]}
42
+ `;
43
+ if (!size) return;
44
+ yield "\nPage Offsets Hint Table\n\n";
45
+ const page = new HintBits(size), header = {};
46
+ for (const [key, width] of [
47
+ ["min_nobjects", 32],
48
+ ["first_page_offset", 32],
49
+ ["nbits_delta_nobjects", 16],
50
+ ["min_page_length", 32],
51
+ ["nbits_delta_page_length", 16],
52
+ ["min_content_offset", 32],
53
+ ["nbits_delta_content_offset", 16],
54
+ ["min_content_length", 32],
55
+ ["nbits_delta_content_length", 16],
56
+ ["nbits_nshared_objects", 16],
57
+ ["nbits_shared_identifier", 16],
58
+ ["nbits_shared_numerator", 16],
59
+ ["shared_denominator", 16]
60
+ ]) {
61
+ header[key] = yield* page.read(width);
62
+ yield `${key}: ${key === "first_page_offset" ? adjusted(header[key]) : header[key]}
63
+ `;
64
+ }
65
+ const n = values.npages, objects = page.column(n, header.nbits_delta_nobjects), lengths = page.column(n, header.nbits_delta_page_length);
66
+ const counts = page.column(n, header.nbits_nshared_objects);
67
+ const countScan = new HintBits(size, counts.position / 8);
68
+ let total = 0;
69
+ for (let i = 0; i < n; i++) total += yield* countScan.read(header.nbits_nshared_objects);
70
+ const identifiers = page.column(total, header.nbits_shared_identifier), numerators = page.column(total, header.nbits_shared_numerator);
71
+ const contentOffsets = page.column(n, header.nbits_delta_content_offset), contentLengths = page.column(n, header.nbits_delta_content_length);
72
+ for (let i = 0; i < n; i++) {
73
+ yield `Page ${i}:
74
+ nobjects: ${header.min_nobjects + (yield* objects.read(header.nbits_delta_nobjects))}
75
+ `;
76
+ yield ` length: ${header.min_page_length + (yield* lengths.read(header.nbits_delta_page_length))}
77
+ `;
78
+ yield ` content_offset: ${header.min_content_offset + (yield* contentOffsets.read(header.nbits_delta_content_offset))}
79
+ `;
80
+ yield ` content_length: ${header.min_content_length + (yield* contentLengths.read(header.nbits_delta_content_length))}
81
+ `;
82
+ const count = yield* counts.read(header.nbits_nshared_objects);
83
+ yield ` nshared_objects: ${count}
84
+ `;
85
+ for (let j = 0; j < count; j++) yield ` identifier ${j}: ${yield* identifiers.read(header.nbits_shared_identifier)}
86
+ numerator ${j}: ${yield* numerators.read(header.nbits_shared_numerator)}
87
+ `;
88
+ }
89
+ yield "\nShared Objects Hint Table\n\n";
90
+ const shared = new HintBits(size, sharedOffset), fields = {};
91
+ for (const [key, width] of [["first_shared_obj", 32], ["first_shared_offset", 32], ["nshared_first_page", 32], ["nshared_total", 32], ["nbits_nobjects", 16], ["min_group_length", 32], ["nbits_delta_group_length", 16]]) {
92
+ fields[key] = yield* shared.read(width);
93
+ yield `${key}: ${key === "first_shared_offset" ? adjusted(fields[key]) : fields[key]}
94
+ `;
95
+ }
96
+ const groups = fields.nshared_total, groupLengths = shared.column(groups, fields.nbits_delta_group_length), signatures = shared.column(groups, 1);
97
+ const signatureScan = new HintBits(size, signatures.position / 8);
98
+ for (let i = 0; i < groups; i++) if (yield* signatureScan.read(1)) shared.column(4, 32);
99
+ const groupCounts = shared.column(groups, fields.nbits_nobjects);
100
+ for (let i = 0; i < groups; i++) {
101
+ yield `Shared Object ${i}:
102
+ group length: ${fields.min_group_length + (yield* groupLengths.read(fields.nbits_delta_group_length))}
103
+ `;
104
+ if (yield* signatures.read(1)) yield " signature present\n";
105
+ const count = yield* groupCounts.read(fields.nbits_nobjects);
106
+ if (count) yield ` nobjects: ${count + 1}
107
+ `;
108
+ }
109
+ if (outlineOffset !== void 0) {
110
+ const outline = new HintBits(size, outlineOffset);
111
+ const first = yield* outline.read(32), offset = yield* outline.read(32), count = yield* outline.read(32), length = yield* outline.read(32);
112
+ if (count) yield `
113
+ Outlines Hint Table
114
+
115
+ first_object: ${first}
116
+ first_object_offset: ${adjusted(offset)}
117
+ nobjects: ${count}
118
+ group_length: ${length}
119
+ `;
120
+ }
121
+ }
122
+
123
+ // packages/safe-bash-command-qpdf/src/encryption-report.ts
124
+ import { getPdfEncryptionInfo } from "../pdf-ast/index.js";
125
+ function encryptionReport(encryption) {
126
+ if (!encryption) return "File is not encrypted\n";
127
+ const info = getPdfEncryptionInfo(encryption), p = encryption.permissions;
128
+ const legacy = encryption.revision === 2;
129
+ const assembly = legacy ? p.modify : p.assemble;
130
+ const forms = legacy ? p.addNotes : p.fillForms;
131
+ const permissions = [
132
+ ["extract for accessibility", legacy ? p.copy : encryption.revision >= 5 || p.extractAccessibility],
133
+ ["extract for any purpose", p.copy],
134
+ ["print low resolution", p.print],
135
+ ["print high resolution", p.print && (legacy || p.printHighRes)],
136
+ ["modify document assembly", assembly],
137
+ ["modify forms", forms],
138
+ ["modify annotations", p.addNotes],
139
+ ["modify other", p.modify],
140
+ ["modify anything", assembly && forms && p.addNotes && p.modify]
141
+ ];
142
+ const lines = [`R = ${encryption.revision}`, `P = ${info.permissionsMask}`, `User password = ${info.userPassword}`];
143
+ if (info.ownerPasswordMatched) lines.push("Supplied password is owner password");
144
+ if (info.userPasswordMatched) lines.push("Supplied password is user password");
145
+ for (const [name, allowed] of permissions) lines.push(`${name}: ${allowed ? "allowed" : "not allowed"}`);
146
+ if (encryption.version >= 4) lines.push(`stream encryption method: ${info.streamMethod}`, `string encryption method: ${info.stringMethod}`, `file encryption method: ${info.fileMethod}`);
147
+ return lines.join("\n") + "\n";
148
+ }
149
+
150
+ // packages/safe-bash-command-qpdf/src/json-pages.ts
151
+ import { cosDict, cosNumber, decodePdfString as decodePdfString2, dictGet } from "../pdf-ast/index.js";
152
+
153
+ // packages/safe-bash-command-qpdf/src/json-output.ts
154
+ import { IntegerTable, PagedStorage } from "@poe-platform/safe-fs/storage";
155
+ import { PdfNameIndex, decodePdfString } from "../pdf-ast/index.js";
156
+ function jsonRecord(record) {
157
+ return { kind: "object", entries: Object.entries(record) };
158
+ }
159
+ function cosJson(node, storage, signal) {
160
+ if (!node) return null;
161
+ switch (node.kind) {
162
+ case "null":
163
+ return null;
164
+ case "boolean":
165
+ case "number":
166
+ return node.value;
167
+ case "name":
168
+ return `/${node.decoded}`;
169
+ case "string":
170
+ return `u:${decodePdfString(node)}`;
171
+ case "ref":
172
+ return `${node.objectNumber} ${node.generationNumber} R`;
173
+ case "array":
174
+ return { kind: "array", values: (function* () {
175
+ for (const item of node.items) yield cosJson(item, storage, signal);
176
+ })() };
177
+ case "dict":
178
+ return { kind: "object", entries: (async function* () {
179
+ if (node.entries.length > 64) {
180
+ const names = new PdfNameIndex(storage, Infinity, signal), backing = new PagedStorage({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4), latest = new IntegerTable(backing);
181
+ let failed = false;
182
+ try {
183
+ for (let index = 0; index < node.entries.length; index++) {
184
+ signal.throwIfAborted();
185
+ const name = await names.intern(node.entries[index].key.decoded);
186
+ await latest.set(BigInt(name.index), BigInt(index));
187
+ }
188
+ for await (const [, index] of latest.entries()) {
189
+ const entry = node.entries[Number(index)];
190
+ yield [`/${entry.key.decoded}`, cosJson(entry.value, storage, signal)];
191
+ }
192
+ } catch (error) {
193
+ failed = true;
194
+ throw error;
195
+ } finally {
196
+ const results = await Promise.allSettled([names.close(), backing.close()]);
197
+ if (!failed) {
198
+ for (const result of results) if (result.status === "rejected") await Promise.reject(result.reason);
199
+ }
200
+ }
201
+ return;
202
+ }
203
+ for (let i = 0; i < node.entries.length; i++) {
204
+ const entry = node.entries[i], key = entry.key.decoded;
205
+ if (node.entries.findIndex((pair) => pair.key.decoded === key) !== i) continue;
206
+ let value = entry.value;
207
+ for (let j = node.entries.length - 1; j > i; j--) if (node.entries[j].key.decoded === key) {
208
+ value = node.entries[j].value;
209
+ break;
210
+ }
211
+ yield [`/${key}`, cosJson(value, storage, signal)];
212
+ }
213
+ })() };
214
+ case "stream":
215
+ return jsonRecord({ dict: cosJson(node.dict, storage, signal), length: node.rawBytes.length });
216
+ }
217
+ }
218
+ async function* jsonChunks(value, signal) {
219
+ async function* string(parts2) {
220
+ yield '"';
221
+ for await (const part of parts2) for (let at = 0; at < part.length; ) {
222
+ signal.throwIfAborted();
223
+ let end = Math.min(part.length, at + 4096);
224
+ const last = part.charCodeAt(end - 1), next = part.charCodeAt(end);
225
+ if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
226
+ yield JSON.stringify(part.slice(at, end)).slice(1, -1);
227
+ at = end;
228
+ }
229
+ yield '"';
230
+ }
231
+ async function* parts(node, depth = 0) {
232
+ signal.throwIfAborted();
233
+ if (typeof node === "string") {
234
+ yield* string([node]);
235
+ return;
236
+ }
237
+ if (node === null || typeof node !== "object") {
238
+ yield JSON.stringify(node);
239
+ return;
240
+ }
241
+ if (node.kind === "text") {
242
+ yield* string(node.parts);
243
+ return;
244
+ }
245
+ if (node.kind === "formatted") {
246
+ const decoder = new TextDecoder(), chunks = node.chunks;
247
+ let newline = false;
248
+ async function* text() {
249
+ for await (const bytes of chunks) yield decoder.decode(bytes, { stream: true });
250
+ yield decoder.decode();
251
+ }
252
+ for await (const part of text()) {
253
+ signal.throwIfAborted();
254
+ let at = 0;
255
+ while (at < part.length) {
256
+ if (newline) {
257
+ yield "\n";
258
+ yield " ".repeat(depth);
259
+ newline = false;
260
+ }
261
+ const end = part.indexOf("\n", at);
262
+ if (end < 0) {
263
+ yield part.slice(at);
264
+ break;
265
+ }
266
+ yield part.slice(at, end);
267
+ newline = true;
268
+ at = end + 1;
269
+ }
270
+ }
271
+ return;
272
+ }
273
+ const object = node.kind === "object";
274
+ yield object ? "{" : "[";
275
+ let first = true;
276
+ if (node.kind === "object") for await (const [key, child] of node.entries) {
277
+ yield first ? "\n" : ",\n";
278
+ first = false;
279
+ yield " ".repeat(depth + 1);
280
+ yield* string([key]);
281
+ yield ": ";
282
+ yield* parts(child, depth + 1);
283
+ }
284
+ else for await (const child of node.values) {
285
+ yield first ? "\n" : ",\n";
286
+ first = false;
287
+ yield " ".repeat(depth + 1);
288
+ yield* parts(child, depth + 1);
289
+ }
290
+ if (!first) {
291
+ yield "\n";
292
+ yield " ".repeat(depth);
293
+ }
294
+ yield object ? "}" : "]";
295
+ }
296
+ const encoder = new TextEncoder();
297
+ let buffer = new Uint8Array(16384), used = 0, work = 0;
298
+ for await (const part of parts(value)) {
299
+ if (++work % 256 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
300
+ signal.throwIfAborted();
301
+ for (let at = 0; at < part.length; ) {
302
+ const result = encoder.encodeInto(part.slice(at), buffer.subarray(used));
303
+ used += result.written;
304
+ at += result.read;
305
+ if (buffer.length - used < 4) {
306
+ yield buffer.subarray(0, used);
307
+ buffer = new Uint8Array(16384);
308
+ used = 0;
309
+ }
310
+ }
311
+ }
312
+ if (used === buffer.length) {
313
+ yield buffer;
314
+ buffer = new Uint8Array(16384);
315
+ used = 0;
316
+ }
317
+ buffer[used++] = 10;
318
+ yield buffer.subarray(0, used);
319
+ }
320
+ async function* base64Parts(bytes, signal) {
321
+ let pending = 0, count = 0, output = "";
322
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
323
+ for await (const chunk of bytes) for (const byte of chunk) {
324
+ pending = pending << 8 | byte;
325
+ if (++count === 3) {
326
+ output += alphabet[pending >>> 18 & 63] + alphabet[pending >>> 12 & 63] + alphabet[pending >>> 6 & 63] + alphabet[pending & 63];
327
+ pending = 0;
328
+ count = 0;
329
+ }
330
+ if (output.length >= 4096) {
331
+ signal.throwIfAborted();
332
+ yield output;
333
+ output = "";
334
+ }
335
+ }
336
+ if (count) {
337
+ pending <<= (3 - count) * 8;
338
+ output += alphabet[pending >>> 18 & 63] + alphabet[pending >>> 12 & 63] + (count === 2 ? alphabet[pending >>> 6 & 63] : "=") + "=";
339
+ }
340
+ if (output) yield output;
341
+ }
342
+
343
+ // packages/safe-bash-command-qpdf/src/json-pages.ts
344
+ function* qpdfJsonPage(page, reference, index, root, version) {
345
+ function json(node2) {
346
+ if (!node2 || node2.kind === "null") return null;
347
+ switch (node2.kind) {
348
+ case "string":
349
+ return `${version === 1 ? "" : "u:"}${decodePdfString2(node2)}`;
350
+ case "name":
351
+ return `/${node2.decoded}`;
352
+ case "ref":
353
+ return referenceText(node2);
354
+ case "number":
355
+ case "boolean":
356
+ return node2.value;
357
+ case "array":
358
+ return { kind: "array", values: node2.items.map(json) };
359
+ case "dict":
360
+ return jsonRecord(Object.fromEntries(node2.entries.map((entry) => [`/${entry.key.decoded}`, json(entry.value)])));
361
+ case "stream":
362
+ return json(node2.dict);
363
+ }
364
+ }
365
+ const referenceText = (ref) => `${ref.objectNumber} ${ref.generationNumber} R`;
366
+ function* dict(node2) {
367
+ const value2 = yield node2;
368
+ return value2?.kind === "stream" ? value2.dict : value2?.kind === "dict" ? value2 : void 0;
369
+ }
370
+ function* tree(node2, children, visit, ancestors = /* @__PURE__ */ new Set()) {
371
+ if (!node2) return;
372
+ const key = node2.kind === "ref" ? referenceText(node2) : void 0;
373
+ if (key && ancestors.has(key)) return;
374
+ if (key) ancestors.add(key);
375
+ try {
376
+ const value2 = yield* dict(node2);
377
+ if (!value2) return;
378
+ yield* visit(node2, value2);
379
+ const child = dictGet(value2, children);
380
+ if (children === "Kids") {
381
+ const kids = yield child;
382
+ if (kids?.kind === "array") for (const item of kids.items) yield* tree(item, children, visit, ancestors);
383
+ } else {
384
+ yield* tree(child, children, visit, ancestors);
385
+ yield* tree(dictGet(value2, "Next"), children, visit, ancestors);
386
+ }
387
+ } finally {
388
+ if (key) ancestors.delete(key);
389
+ }
390
+ }
391
+ const contents = [], images = [], outlines = [];
392
+ const node = dictGet(page, "Contents"), value = yield node;
393
+ for (const item of value?.kind === "array" ? value.items : node ? [node] : []) if (item.kind === "ref") contents.push(referenceText(item));
394
+ let resources = dictGet(page, "Resources"), parent = dictGet(page, "Parent");
395
+ const parents = /* @__PURE__ */ new Set();
396
+ while (!resources && parent) {
397
+ if (parent.kind === "ref") {
398
+ const key = referenceText(parent);
399
+ if (parents.has(key)) break;
400
+ parents.add(key);
401
+ }
402
+ const value2 = yield* dict(parent);
403
+ if (!value2) break;
404
+ resources = dictGet(value2, "Resources");
405
+ parent = dictGet(value2, "Parent");
406
+ }
407
+ let label, labelIndex = -1;
408
+ yield* tree(root && dictGet(root, "PageLabels"), "Kids", function* (_node, value2) {
409
+ const nums = yield dictGet(value2, "Nums");
410
+ if (nums?.kind !== "array") return;
411
+ for (let at = 0; at + 1 < nums.items.length; at += 2) {
412
+ const start = yield nums.items[at];
413
+ if (start?.kind === "number" && start.value <= index && start.value >= labelIndex) {
414
+ label = yield* dict(nums.items[at + 1]);
415
+ labelIndex = start.value;
416
+ }
417
+ }
418
+ });
419
+ if (label) {
420
+ const start = yield dictGet(label, "St");
421
+ label = cosDict({ ...Object.fromEntries(label.entries.map((entry) => [entry.key.decoded, entry.value])), St: cosNumber((start?.kind === "number" ? start.value : 1) + index - labelIndex) });
422
+ }
423
+ const resource = yield* dict(resources), objects = resource && (yield* dict(dictGet(resource, "XObject")));
424
+ for (const entry of objects?.entries.slice().sort((a, b) => a.key.decoded < b.key.decoded ? -1 : a.key.decoded > b.key.decoded ? 1 : 0) ?? []) {
425
+ const image = yield* dict(entry.value), subtype = image && (yield dictGet(image, "Subtype"));
426
+ if (!image || subtype?.kind !== "name" || subtype.decoded !== "Image") continue;
427
+ const mask = yield dictGet(image, "ImageMask");
428
+ if (mask?.kind === "boolean" && mask.value) continue;
429
+ const filter = yield dictGet(image, "Filter"), parms = yield dictGet(image, "DecodeParms");
430
+ const filters = filter?.kind === "array" ? filter.items : [filter];
431
+ const parameters = parms?.kind === "array" ? parms.items : filters.map(() => parms);
432
+ images.push(jsonRecord({
433
+ bitspercomponent: json(yield dictGet(image, "BitsPerComponent")),
434
+ colorspace: json(yield dictGet(image, "ColorSpace")),
435
+ decodeparms: { kind: "array", values: parameters.map(json) },
436
+ filter: { kind: "array", values: filters.map(json) },
437
+ filterable: filters.every((item) => !item || item.kind === "null" || item.kind === "name" && ["FlateDecode", "Fl", "LZWDecode", "LZW", "ASCII85Decode", "A85", "ASCIIHexDecode", "AHx", "RunLengthDecode", "RL"].includes(item.decoded)),
438
+ height: json(yield dictGet(image, "Height")),
439
+ name: `/${entry.key.decoded}`,
440
+ object: json(entry.value),
441
+ width: json(yield dictGet(image, "Width"))
442
+ }));
443
+ }
444
+ const outlineRoot = root && (yield* dict(dictGet(root, "Outlines")));
445
+ yield* tree(outlineRoot && dictGet(outlineRoot, "First"), "First", function* (node2, value2) {
446
+ const action = yield* dict(dictGet(value2, "A"));
447
+ let dest = yield dictGet(value2, "Dest") ?? (action && dictGet(action, "D"));
448
+ if (dest?.kind === "name" || dest?.kind === "string") {
449
+ const name = dest.kind === "name" ? dest.decoded : decodePdfString2(dest), old = root && (yield* dict(dictGet(root, "Dests")));
450
+ let found = dest.kind === "name" && old ? dictGet(old, name) : void 0;
451
+ const names = root && (yield* dict(dictGet(root, "Names")));
452
+ if (dest.kind === "string") yield* tree(names && dictGet(names, "Dests"), "Kids", function* (_node, value3) {
453
+ const pairs = yield dictGet(value3, "Names");
454
+ if (pairs?.kind === "array") for (let at = 0; at + 1 < pairs.items.length; at += 2) {
455
+ const key = yield pairs.items[at];
456
+ if (key?.kind === "string" && decodePdfString2(key) === name) found = pairs.items[at + 1];
457
+ }
458
+ });
459
+ dest = yield found;
460
+ }
461
+ if (dest?.kind === "dict") dest = yield dictGet(dest, "D");
462
+ const target = dest?.kind === "array" ? dest.items[0] : void 0;
463
+ if (target?.kind !== "ref" || referenceText(target) !== referenceText(reference)) return;
464
+ const title = yield dictGet(value2, "Title");
465
+ outlines.push(jsonRecord({ dest: json(dest), object: json(node2), title: title?.kind === "string" ? decodePdfString2(title) : "" }));
466
+ });
467
+ return jsonRecord({ contents: { kind: "array", values: contents }, images: { kind: "array", values: images }, label: json(label), object: referenceText(reference), outlines: { kind: "array", values: outlines }, pageposfrom1: index + 1 });
468
+ }
469
+
3
470
  // packages/safe-bash-command-qpdf/src/page-labels.ts
4
- function* parseQpdfPageLabels(specs) {
471
+ function parseQpdfPageLabels(specs, pageCount) {
472
+ const labels = [];
5
473
  for (const spec of specs) {
6
474
  const colon = spec.indexOf(":");
7
- if (colon <= 0) continue;
8
- const index = Math.max(1, Number.parseInt(spec.slice(0, colon), 10) || 1) - 1;
9
- const parts = spec.slice(colon + 1).split("/"), style = parts[0] ?? "D";
10
- const parsed = parts[1] !== void 0 && parts[1].length > 0 ? Number.parseInt(parts[1], 10) : Number.NaN;
11
- const start = Number.isFinite(parsed) ? parsed || 1 : 1;
12
- const prefix = parts.length >= 3 ? parts[2] ?? "" : !Number.isFinite(parsed) ? parts[1] ?? "" : "";
13
- yield { index, start, prefix, ...style === "n" ? {} : { style } };
475
+ const page = spec.slice(0, colon);
476
+ const digits = page === "z" ? "1" : page.startsWith("r") ? page.slice(1) : page;
477
+ const parts = spec.slice(colon + 1).split("/");
478
+ const style = parts[0] ?? "";
479
+ const number = parts[1] ?? "";
480
+ if (colon <= 0 || digits.length === 0 || [...digits, ...number].some((char) => char < "0" || char > "9") || !["", "D", "a", "A", "r", "R"].includes(style)) {
481
+ throw new Error("page label spec must be n:[D|a|A|r|R][/start[/prefix]]");
482
+ }
483
+ for (const operand of [digits, number]) {
484
+ if (operand !== "" && BigInt(operand) > 9223372036854775807n) {
485
+ throw new Error(`overflow/underflow converting ${operand} to 64-bit integer`);
486
+ }
487
+ }
488
+ const index = page === "z" ? pageCount - 1 : page.startsWith("r") ? pageCount - Number(digits) : Number(page) - 1;
489
+ const start = number === "" ? 1 : Number(number);
490
+ if (labels.length === 0 && index !== 0) throw new Error("the first page label specification must start with page 1");
491
+ if (start < 1) throw new Error("starting page number must be >= 1");
492
+ if (labels.length > 0 && index <= labels[labels.length - 1].index) {
493
+ throw new Error("page label specifications must be in order by first page");
494
+ }
495
+ labels.push({ index, start, prefix: parts.slice(2).join("/"), ...style === "" ? {} : { style } });
14
496
  }
497
+ return labels;
15
498
  }
16
499
 
17
500
  // packages/safe-bash-command-qpdf/src/split.ts
18
- import { IntegerTable, PagedStorage } from "@poe-platform/safe-fs/storage";
501
+ import { IntegerTable as IntegerTable2, PagedStorage as PagedStorage2 } from "@poe-platform/safe-fs/storage";
19
502
 
20
503
  // packages/safe-bash-command-qpdf/src/page-range.ts
21
504
  function parseSingleTokenPageNumber(tok, totalPages) {
@@ -34,7 +517,13 @@ function parseSingleTokenPageNumber(tok, totalPages) {
34
517
  }
35
518
  return num;
36
519
  }
520
+ var QpdfPageRangeError = class extends Error {
521
+ };
37
522
  function* iterateQpdfPageRange(rangeSpec, totalPages) {
523
+ const suffix = rangeSpec.indexOf(":");
524
+ if (suffix !== -1 && rangeSpec.includes(",", suffix)) {
525
+ throw new QpdfPageRangeError(`error at * in numeric range ${rangeSpec.slice(0, suffix)}*${rangeSpec.slice(suffix)}: expected :even or :odd`);
526
+ }
38
527
  const ranges = [];
39
528
  let groupParity;
40
529
  for (const raw of rangeSpec.trim().split(",").filter((part) => part.length > 0)) {
@@ -126,8 +615,8 @@ function formatSplitName(template, total, group, start, end) {
126
615
  return `${stem}-${group === 1 ? first : `${first}-${last}`}.pdf`;
127
616
  }
128
617
  async function* splitPageOutputs(document, storage, target, group, edits, signal, normalizeContent = false) {
129
- const backing = edits.length ? new PagedStorage({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4) : void 0;
130
- const rotations = backing ? new IntegerTable(backing) : void 0;
618
+ const backing = edits.length ? new PagedStorage2({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4) : void 0;
619
+ const rotations = backing ? new IntegerTable2(backing) : void 0;
131
620
  let failed = false;
132
621
  try {
133
622
  let count = 0;
@@ -166,7 +655,7 @@ async function* splitPageOutputs(document, storage, target, group, edits, signal
166
655
  }
167
656
 
168
657
  // packages/safe-bash-command-qpdf/src/display.ts
169
- import { decodePdfString } from "../pdf-ast/index.js";
658
+ import { decodePdfString as decodePdfString3 } from "../pdf-ast/index.js";
170
659
  function* displayNodeParts(node) {
171
660
  if (!node) {
172
661
  yield "null";
@@ -188,7 +677,7 @@ function* displayNodeParts(node) {
188
677
  return;
189
678
  case "string":
190
679
  yield "(";
191
- yield decodePdfString(node);
680
+ yield decodePdfString3(node);
192
681
  yield ")";
193
682
  return;
194
683
  case "ref":
@@ -305,324 +794,134 @@ async function applyJsonInput(owner, source, options, signal) {
305
794
 
306
795
  // packages/safe-bash-command-qpdf/src/json.ts
307
796
  import { IntegerTable as IntegerTable4, PagedStorage as PagedStorage4 } from "@poe-platform/safe-fs/storage";
308
- import { PdfError as PdfError2, PdfFileSource, PdfMutableObjectStore as PdfMutableObjectStore2, PdfStagedOutputs, cosArray, cosDict, cosNumber, cosString, decodePdfString as decodePdfString4, dictGet as dictGet2 } from "../pdf-ast/index.js";
797
+ import { PdfError as PdfError3, PdfFileSource, PdfMutableObjectStore as PdfMutableObjectStore2, PdfStagedOutputs, cosArray, cosDict as cosDict2, cosNumber as cosNumber2, cosString, decodePdfString as decodePdfString5, dictGet as dictGet3 } from "../pdf-ast/index.js";
309
798
 
310
799
  // packages/safe-bash-command-qpdf/src/attachments.ts
311
- import { IntegerTable as IntegerTable2, PagedStorage as PagedStorage2 } from "@poe-platform/safe-fs/storage";
312
- import { PdfError, PdfMutableObjectStore, PdfNameIndex, decodePdfString as decodePdfString2, dictGet } from "../pdf-ast/index.js";
800
+ import { IntegerTable as IntegerTable3, PagedStorage as PagedStorage3 } from "@poe-platform/safe-fs/storage";
801
+ import { PdfError as PdfError2, PdfMutableObjectStore, PdfNameIndex as PdfNameIndex2, decodePdfString as decodePdfString4, dictGet as dictGet2 } from "../pdf-ast/index.js";
313
802
  var QpdfMissingAttachment = class extends Error {
314
803
  constructor(key) {
315
804
  super(`qpdf: attachment ${key} not found
316
- `);
317
- }
318
- };
319
- async function* retainedAttachments(document, storage, signal) {
320
- const values = new PdfMutableObjectStore(storage, { signal }), names = new PdfNameIndex(storage, Infinity, signal);
321
- const backing = new PagedStorage2({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4);
322
- const visited = new IntegerTable2(backing), accepted = new IntegerTable2(backing);
323
- let failed = false, top = -1, work = 0;
324
- async function dictionary(node) {
325
- const resolved = await document.lookup(node);
326
- return resolved?.value.kind === "dict" && !resolved.stream ? resolved.value : void 0;
327
- }
328
- async function* specifications() {
329
- const root = await dictionary(document.crossReference.rootRef), nameRoot = await dictionary(root && dictGet(root, "Names"));
330
- let node = nameRoot && dictGet(nameRoot, "EmbeddedFiles");
331
- for (; ; ) {
332
- signal.throwIfAborted();
333
- if (++work % 64 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
334
- const dict = await dictionary(node);
335
- if (dict) {
336
- const entries = (await document.lookup(dictGet(dict, "Names")))?.value;
337
- if (entries?.kind === "array") for (let index = 0; index + 1 < entries.items.length; index += 2) {
338
- const label = (await document.lookup(entries.items[index]))?.value;
339
- yield { node: entries.items[index + 1], fallback: label?.kind === "string" ? decodePdfString2(label) : `att_${index}` };
340
- }
341
- const kids = (await document.lookup(dictGet(dict, "Kids")))?.value;
342
- if (kids?.kind === "array" && kids.items.length) {
343
- const ref = await values.allocate(kids), bytes = new Uint8Array(24), view = new DataView(bytes.buffer);
344
- view.setFloat64(0, top);
345
- view.setFloat64(8, ref.objectNumber);
346
- const position = backing.allocate(24);
347
- await backing.write(position, bytes);
348
- top = position;
349
- }
350
- }
351
- node = void 0;
352
- while (top >= 0) {
353
- const bytes = await backing.read(top, 24), frame = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
354
- const kids = (await values.get(frame.getFloat64(8))).value;
355
- if (kids.kind !== "array") throw new Error("Invalid retained attachment traversal frame");
356
- const index = frame.getFloat64(16), child = kids.items[index];
357
- if (!child) {
358
- top = frame.getFloat64(0);
359
- continue;
360
- }
361
- frame.setFloat64(16, index + 1);
362
- await backing.write(top, bytes);
363
- if (child.kind === "ref") {
364
- if (await visited.get(BigInt(child.objectNumber)) !== void 0) continue;
365
- await visited.set(BigInt(child.objectNumber), 1n);
366
- }
367
- node = child;
368
- break;
369
- }
370
- if (!node) break;
371
- }
372
- const associated = (await document.lookup(root && dictGet(root, "AF")))?.value;
373
- if (associated?.kind === "array") for (const item of associated.items) yield { node: item, fallback: "" };
374
- for await (const page of document.pages()) {
375
- const annotations = (await document.lookup(dictGet(page.dict, "Annots")))?.value;
376
- if (annotations?.kind !== "array") continue;
377
- for (const item of annotations.items) {
378
- const annotation = await dictionary(item), subtype = annotation && dictGet(annotation, "Subtype");
379
- if (subtype?.kind !== "name" || subtype.decoded !== "FileAttachment") continue;
380
- const file = dictGet(annotation, "FS");
381
- if (file) yield { node: file, fallback: "" };
382
- }
383
- }
384
- }
385
- try {
386
- for await (const spec of specifications()) {
387
- const dict = await dictionary(spec.node);
388
- if (!dict) continue;
389
- const filenameNode = (await document.lookup(dictGet(dict, "UF") ?? dictGet(dict, "F")))?.value;
390
- const filename = filenameNode?.kind === "string" ? decodePdfString2(filenameNode) : spec.fallback, name = spec.fallback || filename;
391
- const identity = (await names.intern(name)).index;
392
- if (await accepted.get(BigInt(identity)) !== void 0) continue;
393
- const embedded = await dictionary(dictGet(dict, "EF"));
394
- const stream = embedded && await document.lookup(dictGet(embedded, "UF") ?? dictGet(embedded, "F") ?? dictGet(embedded, "DOS") ?? dictGet(embedded, "Mac") ?? dictGet(embedded, "Unix"));
395
- if (!stream?.stream || !stream.reference) continue;
396
- await accepted.set(BigInt(identity), 1n);
397
- yield { key: name, filename, reference: stream.reference };
398
- }
399
- } catch (error) {
400
- failed = true;
401
- if (error instanceof PdfError && error.message.startsWith("Unsupported streaming PDF filter: ")) {
402
- throw new PdfError(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
403
- }
404
- throw error;
405
- } finally {
406
- const results = await Promise.allSettled([values.close(), names.close(), backing.close()]);
407
- if (!failed) {
408
- for (const result of results) if (result.status === "rejected") await Promise.reject(result.reason);
409
- }
410
- }
411
- }
412
- async function* attachmentChunks(document, storage, key, signal) {
413
- let found = false;
414
- try {
415
- for await (const attachment of retainedAttachments(document, storage, signal)) {
416
- const selected = key !== void 0 && !found && (attachment.key === key || attachment.filename === key);
417
- if (selected) found = true;
418
- for await (const bytes of document.objects.decodeStream(attachment.reference.objectNumber, attachment.reference.generationNumber)) {
419
- signal.throwIfAborted();
420
- if (selected) yield bytes;
421
- }
422
- if (key === void 0) yield* encodeDisplayParts([attachment.key, " -> ", attachment.filename, "\n"], signal);
423
- }
424
- if (key !== void 0 && !found) throw new QpdfMissingAttachment(key);
425
- } catch (error) {
426
- if (error instanceof PdfError && error.message.startsWith("Unsupported streaming PDF filter: ")) {
427
- throw new PdfError(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
428
- }
429
- throw error;
430
- }
431
- }
432
-
433
- // packages/safe-bash-command-qpdf/src/object-order.ts
434
- import { retainedObjectOrder } from "../pdf-ast/index.js";
435
-
436
- // packages/safe-bash-command-qpdf/src/json-output.ts
437
- import { IntegerTable as IntegerTable3, PagedStorage as PagedStorage3 } from "@poe-platform/safe-fs/storage";
438
- import { PdfNameIndex as PdfNameIndex2, decodePdfString as decodePdfString3 } from "../pdf-ast/index.js";
439
- function jsonRecord(record) {
440
- return { kind: "object", entries: Object.entries(record) };
441
- }
442
- function cosJson(node, storage, signal) {
443
- if (!node) return null;
444
- switch (node.kind) {
445
- case "null":
446
- return null;
447
- case "boolean":
448
- case "number":
449
- return node.value;
450
- case "name":
451
- return `/${node.decoded}`;
452
- case "string":
453
- return `u:${decodePdfString3(node)}`;
454
- case "ref":
455
- return `${node.objectNumber} ${node.generationNumber} R`;
456
- case "array":
457
- return { kind: "array", values: (function* () {
458
- for (const item of node.items) yield cosJson(item, storage, signal);
459
- })() };
460
- case "dict":
461
- return { kind: "object", entries: (async function* () {
462
- if (node.entries.length > 64) {
463
- const names = new PdfNameIndex2(storage, Infinity, signal), backing = new PagedStorage3({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4), latest = new IntegerTable3(backing);
464
- let failed = false;
465
- try {
466
- for (let index = 0; index < node.entries.length; index++) {
467
- signal.throwIfAborted();
468
- const name = await names.intern(node.entries[index].key.decoded);
469
- await latest.set(BigInt(name.index), BigInt(index));
470
- }
471
- for await (const [, index] of latest.entries()) {
472
- const entry = node.entries[Number(index)];
473
- yield [`/${entry.key.decoded}`, cosJson(entry.value, storage, signal)];
474
- }
475
- } catch (error) {
476
- failed = true;
477
- throw error;
478
- } finally {
479
- const results = await Promise.allSettled([names.close(), backing.close()]);
480
- if (!failed) {
481
- for (const result of results) if (result.status === "rejected") await Promise.reject(result.reason);
482
- }
483
- }
484
- return;
485
- }
486
- for (let i = 0; i < node.entries.length; i++) {
487
- const entry = node.entries[i], key = entry.key.decoded;
488
- if (node.entries.findIndex((pair) => pair.key.decoded === key) !== i) continue;
489
- let value = entry.value;
490
- for (let j = node.entries.length - 1; j > i; j--) if (node.entries[j].key.decoded === key) {
491
- value = node.entries[j].value;
492
- break;
493
- }
494
- yield [`/${key}`, cosJson(value, storage, signal)];
495
- }
496
- })() };
497
- case "stream":
498
- return jsonRecord({ dict: cosJson(node.dict, storage, signal), length: node.rawBytes.length });
499
- }
500
- }
501
- async function* jsonChunks(value, signal) {
502
- async function* string(parts2) {
503
- yield '"';
504
- for await (const part of parts2) for (let at = 0; at < part.length; ) {
505
- signal.throwIfAborted();
506
- let end = Math.min(part.length, at + 4096);
507
- const last = part.charCodeAt(end - 1), next = part.charCodeAt(end);
508
- if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
509
- yield JSON.stringify(part.slice(at, end)).slice(1, -1);
510
- at = end;
511
- }
512
- yield '"';
513
- }
514
- async function* parts(node, depth = 0) {
515
- signal.throwIfAborted();
516
- if (typeof node === "string") {
517
- yield* string([node]);
518
- return;
519
- }
520
- if (node === null || typeof node !== "object") {
521
- yield JSON.stringify(node);
522
- return;
523
- }
524
- if (node.kind === "text") {
525
- yield* string(node.parts);
526
- return;
527
- }
528
- if (node.kind === "formatted") {
529
- const decoder = new TextDecoder(), chunks = node.chunks;
530
- let newline = false;
531
- async function* text() {
532
- for await (const bytes of chunks) yield decoder.decode(bytes, { stream: true });
533
- yield decoder.decode();
805
+ `);
806
+ }
807
+ };
808
+ async function* retainedAttachments(document, storage, signal) {
809
+ const values = new PdfMutableObjectStore(storage, { signal }), names = new PdfNameIndex2(storage, Infinity, signal);
810
+ const backing = new PagedStorage3({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4);
811
+ const visited = new IntegerTable3(backing), accepted = new IntegerTable3(backing);
812
+ let failed = false, top = -1, work = 0;
813
+ async function dictionary(node) {
814
+ const resolved = await document.lookup(node);
815
+ return resolved?.value.kind === "dict" && !resolved.stream ? resolved.value : void 0;
816
+ }
817
+ async function* specifications() {
818
+ const root = await dictionary(document.crossReference.rootRef), nameRoot = await dictionary(root && dictGet2(root, "Names"));
819
+ let node = nameRoot && dictGet2(nameRoot, "EmbeddedFiles");
820
+ for (; ; ) {
821
+ signal.throwIfAborted();
822
+ if (++work % 64 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
823
+ const dict = await dictionary(node);
824
+ if (dict) {
825
+ const entries = (await document.lookup(dictGet2(dict, "Names")))?.value;
826
+ if (entries?.kind === "array") for (let index = 0; index + 1 < entries.items.length; index += 2) {
827
+ const label = (await document.lookup(entries.items[index]))?.value;
828
+ yield { node: entries.items[index + 1], fallback: label?.kind === "string" ? decodePdfString4(label) : `att_${index}` };
829
+ }
830
+ const kids = (await document.lookup(dictGet2(dict, "Kids")))?.value;
831
+ if (kids?.kind === "array" && kids.items.length) {
832
+ const ref = await values.allocate(kids), bytes = new Uint8Array(24), view = new DataView(bytes.buffer);
833
+ view.setFloat64(0, top);
834
+ view.setFloat64(8, ref.objectNumber);
835
+ const position = backing.allocate(24);
836
+ await backing.write(position, bytes);
837
+ top = position;
838
+ }
534
839
  }
535
- for await (const part of text()) {
536
- signal.throwIfAborted();
537
- let at = 0;
538
- while (at < part.length) {
539
- if (newline) {
540
- yield "\n";
541
- yield " ".repeat(depth);
542
- newline = false;
543
- }
544
- const end = part.indexOf("\n", at);
545
- if (end < 0) {
546
- yield part.slice(at);
547
- break;
548
- }
549
- yield part.slice(at, end);
550
- newline = true;
551
- at = end + 1;
840
+ node = void 0;
841
+ while (top >= 0) {
842
+ const bytes = await backing.read(top, 24), frame = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
843
+ const kids = (await values.get(frame.getFloat64(8))).value;
844
+ if (kids.kind !== "array") throw new Error("Invalid retained attachment traversal frame");
845
+ const index = frame.getFloat64(16), child = kids.items[index];
846
+ if (!child) {
847
+ top = frame.getFloat64(0);
848
+ continue;
849
+ }
850
+ frame.setFloat64(16, index + 1);
851
+ await backing.write(top, bytes);
852
+ if (child.kind === "ref") {
853
+ if (await visited.get(BigInt(child.objectNumber)) !== void 0) continue;
854
+ await visited.set(BigInt(child.objectNumber), 1n);
552
855
  }
856
+ node = child;
857
+ break;
553
858
  }
554
- return;
859
+ if (!node) break;
555
860
  }
556
- const object = node.kind === "object";
557
- yield object ? "{" : "[";
558
- let first = true;
559
- if (node.kind === "object") for await (const [key, child] of node.entries) {
560
- yield first ? "\n" : ",\n";
561
- first = false;
562
- yield " ".repeat(depth + 1);
563
- yield* string([key]);
564
- yield ": ";
565
- yield* parts(child, depth + 1);
861
+ const associated = (await document.lookup(root && dictGet2(root, "AF")))?.value;
862
+ if (associated?.kind === "array") for (const item of associated.items) yield { node: item, fallback: "" };
863
+ for await (const page of document.pages()) {
864
+ const annotations = (await document.lookup(dictGet2(page.dict, "Annots")))?.value;
865
+ if (annotations?.kind !== "array") continue;
866
+ for (const item of annotations.items) {
867
+ const annotation = await dictionary(item), subtype = annotation && dictGet2(annotation, "Subtype");
868
+ if (subtype?.kind !== "name" || subtype.decoded !== "FileAttachment") continue;
869
+ const file = dictGet2(annotation, "FS");
870
+ if (file) yield { node: file, fallback: "" };
871
+ }
566
872
  }
567
- else for await (const child of node.values) {
568
- yield first ? "\n" : ",\n";
569
- first = false;
570
- yield " ".repeat(depth + 1);
571
- yield* parts(child, depth + 1);
873
+ }
874
+ try {
875
+ for await (const spec of specifications()) {
876
+ const dict = await dictionary(spec.node);
877
+ if (!dict) continue;
878
+ const filenameNode = (await document.lookup(dictGet2(dict, "UF") ?? dictGet2(dict, "F")))?.value;
879
+ const filename = filenameNode?.kind === "string" ? decodePdfString4(filenameNode) : spec.fallback, name = spec.fallback || filename;
880
+ const identity = (await names.intern(name)).index;
881
+ if (await accepted.get(BigInt(identity)) !== void 0) continue;
882
+ const embedded = await dictionary(dictGet2(dict, "EF"));
883
+ const stream = embedded && await document.lookup(dictGet2(embedded, "UF") ?? dictGet2(embedded, "F") ?? dictGet2(embedded, "DOS") ?? dictGet2(embedded, "Mac") ?? dictGet2(embedded, "Unix"));
884
+ if (!stream?.stream || !stream.reference) continue;
885
+ await accepted.set(BigInt(identity), 1n);
886
+ yield { key: name, filename, reference: stream.reference };
572
887
  }
573
- if (!first) {
574
- yield "\n";
575
- yield " ".repeat(depth);
888
+ } catch (error) {
889
+ failed = true;
890
+ if (error instanceof PdfError2 && error.message.startsWith("Unsupported streaming PDF filter: ")) {
891
+ throw new PdfError2(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
576
892
  }
577
- yield object ? "}" : "]";
578
- }
579
- const encoder = new TextEncoder();
580
- let buffer = new Uint8Array(16384), used = 0, work = 0;
581
- for await (const part of parts(value)) {
582
- if (++work % 256 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
583
- signal.throwIfAborted();
584
- for (let at = 0; at < part.length; ) {
585
- const result = encoder.encodeInto(part.slice(at), buffer.subarray(used));
586
- used += result.written;
587
- at += result.read;
588
- if (buffer.length - used < 4) {
589
- yield buffer.subarray(0, used);
590
- buffer = new Uint8Array(16384);
591
- used = 0;
592
- }
893
+ throw error;
894
+ } finally {
895
+ const results = await Promise.allSettled([values.close(), names.close(), backing.close()]);
896
+ if (!failed) {
897
+ for (const result of results) if (result.status === "rejected") await Promise.reject(result.reason);
593
898
  }
594
899
  }
595
- if (used === buffer.length) {
596
- yield buffer;
597
- buffer = new Uint8Array(16384);
598
- used = 0;
599
- }
600
- buffer[used++] = 10;
601
- yield buffer.subarray(0, used);
602
900
  }
603
- async function* base64Parts(bytes, signal) {
604
- let pending = 0, count = 0, output = "";
605
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
606
- for await (const chunk of bytes) for (const byte of chunk) {
607
- pending = pending << 8 | byte;
608
- if (++count === 3) {
609
- output += alphabet[pending >>> 18 & 63] + alphabet[pending >>> 12 & 63] + alphabet[pending >>> 6 & 63] + alphabet[pending & 63];
610
- pending = 0;
611
- count = 0;
901
+ async function* attachmentChunks(document, storage, key, signal) {
902
+ let found = false;
903
+ try {
904
+ for await (const attachment of retainedAttachments(document, storage, signal)) {
905
+ const selected = key !== void 0 && !found && (attachment.key === key || attachment.filename === key);
906
+ if (selected) found = true;
907
+ for await (const bytes of document.objects.decodeStream(attachment.reference.objectNumber, attachment.reference.generationNumber)) {
908
+ signal.throwIfAborted();
909
+ if (selected) yield bytes;
910
+ }
911
+ if (key === void 0) yield* encodeDisplayParts([attachment.key, " -> ", attachment.filename, "\n"], signal);
612
912
  }
613
- if (output.length >= 4096) {
614
- signal.throwIfAborted();
615
- yield output;
616
- output = "";
913
+ if (key !== void 0 && !found) throw new QpdfMissingAttachment(key);
914
+ } catch (error) {
915
+ if (error instanceof PdfError2 && error.message.startsWith("Unsupported streaming PDF filter: ")) {
916
+ throw new PdfError2(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
617
917
  }
918
+ throw error;
618
919
  }
619
- if (count) {
620
- pending <<= (3 - count) * 8;
621
- output += alphabet[pending >>> 18 & 63] + alphabet[pending >>> 12 & 63] + (count === 2 ? alphabet[pending >>> 6 & 63] : "=") + "=";
622
- }
623
- if (output) yield output;
624
920
  }
625
921
 
922
+ // packages/safe-bash-command-qpdf/src/object-order.ts
923
+ import { retainedObjectOrder } from "../pdf-ast/index.js";
924
+
626
925
  // packages/safe-bash-command-qpdf/src/json.ts
627
926
  async function createQpdfJson(sourceDocument, document, source, storage, options, inputNames, maxOutputBytes, signal) {
628
927
  const pages = new PagedStorage4({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4);
@@ -638,6 +937,8 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
638
937
  for (const result of results) if (result.status === "rejected") await Promise.reject(result.reason);
639
938
  })();
640
939
  }
940
+ const include = (key) => !options.jsonKeys.length || options.jsonKeys.includes(key);
941
+ const includeObjects = include(options.jsonVersion === 1 ? "objects" : "qpdf");
641
942
  const selected = (number, generation) => !options.jsonObjectSelectors.length || options.jsonObjectSelectors.some((item) => item.kind === "obj" && item.objNum === number && item.genNum === generation);
642
943
  async function* objects() {
643
944
  for await (const number of retainedObjectOrder(sourceDocument, source, storage, signal)) {
@@ -663,16 +964,38 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
663
964
  } else value = jsonRecord({ value: cosJson(object.value, storage, signal) });
664
965
  yield [`obj:${object.objectNumber} ${object.generationNumber} R`, value];
665
966
  }
666
- if (!options.jsonObjectSelectors.length || options.jsonObjectSelectors.some((item) => item.kind === "trailer")) yield ["trailer", jsonRecord({ value: cosJson(cosDict({ Root: document.crossReference.rootRef, ...document.crossReference.infoRef ? { Info: document.crossReference.infoRef } : {}, Size: cosNumber(maximum + 1) }), storage, signal) })];
967
+ if (!options.jsonObjectSelectors.length || options.jsonObjectSelectors.some((item) => item.kind === "trailer")) yield ["trailer", jsonRecord({ value: cosJson(cosDict2({ Root: document.crossReference.rootRef, ...document.crossReference.infoRef ? { Info: document.crossReference.infoRef } : {}, Size: cosNumber2(maximum + 1) }), storage, signal) })];
968
+ }
969
+ async function* objectInfoEntries() {
970
+ for await (const object of objects()) {
971
+ if (!selected(object.objectNumber, object.generationNumber)) continue;
972
+ const dict = object.stream && object.value.kind === "dict" ? object.value : void 0;
973
+ const filter = dict && dictGet3(dict, "Filter"), length = dict && dictGet3(dict, "Length");
974
+ yield [`${object.objectNumber} ${object.generationNumber} R`, jsonRecord({ stream: jsonRecord({
975
+ filter: filter ? cosJson(filter, storage, signal) : null,
976
+ is: Boolean(dict),
977
+ length: length ? cosJson(length, storage, signal) : null
978
+ }) })];
979
+ }
667
980
  }
668
981
  async function pageObject(index) {
669
982
  if (!count) return "obj:0 0 R";
670
- if (index >= count) throw new PdfError2("E_CAPABILITY", `Page index out of bounds: ${index}`);
983
+ if (index >= count) throw new PdfError3("E_CAPABILITY", `Page index out of bounds: ${index}`);
671
984
  const bytes = await pages.read(pageBase + index * 16, 16), view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
672
985
  return `obj:${view.getFloat64(0)} ${view.getFloat64(8)} R`;
673
986
  }
674
987
  async function* pageValues() {
675
- for (let index = 0; index < count; index++) yield jsonRecord({ object: await pageObject(index), pageposfrom1: index + 1 });
988
+ const root = (await document.lookup(document.crossReference.rootRef))?.value;
989
+ let index = 0;
990
+ for await (const page of document.pages()) {
991
+ const steps = qpdfJsonPage(page.dict, page.reference, index++, root?.kind === "dict" ? root : void 0, options.jsonVersion ?? 2);
992
+ let step = steps.next();
993
+ while (!step.done) {
994
+ signal.throwIfAborted();
995
+ step = steps.next((await document.lookup(step.value))?.value);
996
+ }
997
+ yield step.value;
998
+ }
676
999
  }
677
1000
  async function* outlines() {
678
1001
  for await (const outline of document.outlines()) {
@@ -699,7 +1022,7 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
699
1022
  async function row(number) {
700
1023
  const node = (await values.get(number)).value;
701
1024
  if (node.kind !== "array" || node.items[0]?.kind !== "string" || node.items[1]?.kind !== "string") throw new Error("Invalid JSON attachment record");
702
- return [decodePdfString4(node.items[0]), jsonRecord({ filename: decodePdfString4(node.items[1]) })];
1025
+ return [decodePdfString5(node.items[0]), jsonRecord({ filename: decodePdfString5(node.items[1]) })];
703
1026
  }
704
1027
  try {
705
1028
  for await (const attachment of retainedAttachments(document, storage, signal)) {
@@ -739,14 +1062,14 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
739
1062
  yield { name, chunks: [] };
740
1063
  continue;
741
1064
  }
742
- if (options.jsonStreamDataMode !== "file" || !name.startsWith(prefix)) continue;
1065
+ if (!includeObjects || options.jsonStreamDataMode !== "file" || !name.startsWith(prefix)) continue;
743
1066
  const number = Number(name.slice(prefix.length));
744
1067
  if (!Number.isSafeInteger(number) || number < 1 || String(number) !== name.slice(prefix.length)) continue;
745
1068
  const entry = await document.crossReference.index.get(number, signal);
746
1069
  if (!entry || !selected(number, entry.generationNumber ?? 0)) continue;
747
1070
  if ((await document.objects.get(number, entry.generationNumber ?? 0))?.stream) yield { name, chunks: [] };
748
1071
  }
749
- if (options.jsonStreamDataMode !== "file") return;
1072
+ if (!includeObjects || options.jsonStreamDataMode !== "file") return;
750
1073
  for await (const object of objects()) if (object.stream && selected(object.objectNumber, object.generationNumber)) {
751
1074
  async function* admitted() {
752
1075
  let size = 0;
@@ -760,20 +1083,20 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
760
1083
  }
761
1084
  }
762
1085
  files = await PdfStagedOutputs.create(storage, fileEntries(), { signal, maxNameChars: Infinity });
763
- objectJson = await PdfFileSource.fromStream(storage.fs, storage.directory, jsonChunks({ kind: "object", entries: objectEntries() }, signal), { signal, maxInputBytes: maxOutputBytes });
764
- const objectValue = { kind: "formatted", chunks: objectJson.stream(0, objectJson.size, signal) };
1086
+ if (includeObjects) objectJson = await PdfFileSource.fromStream(storage.fs, storage.directory, jsonChunks({ kind: "object", entries: objectEntries() }, signal), { signal, maxInputBytes: maxOutputBytes });
1087
+ const objectValue = objectJson ? { kind: "formatted", chunks: objectJson.stream(0, objectJson.size, signal) } : jsonRecord({});
765
1088
  async function summary(value) {
766
1089
  const source2 = await PdfFileSource.fromStream(storage.fs, storage.directory, jsonChunks(value, signal), { signal, maxInputBytes: maxOutputBytes });
767
1090
  summaries.push(source2);
768
1091
  return { kind: "formatted", chunks: source2.stream(0, source2.size, signal) };
769
1092
  }
770
- const attachmentValue = await summary({ kind: "object", entries: attachments() });
1093
+ const attachmentValue = include("attachments") ? await summary({ kind: "object", entries: attachments() }) : jsonRecord({});
771
1094
  const root = (await document.lookup(document.crossReference.rootRef))?.value;
772
- const form = root?.kind === "dict" ? (await document.lookup(dictGet2(root, "AcroForm")))?.value : void 0;
773
- const appearances = form?.kind === "dict" ? (await document.lookup(dictGet2(form, "NeedAppearances")))?.value : void 0;
774
- const fieldValue = await summary({ kind: "array", values: fields() });
775
- const outlineValue = await summary({ kind: "array", values: outlines() });
776
- const labelValue = await summary({ kind: "array", values: labels() });
1095
+ const form = root?.kind === "dict" ? (await document.lookup(dictGet3(root, "AcroForm")))?.value : void 0;
1096
+ const appearances = form?.kind === "dict" ? (await document.lookup(dictGet3(form, "NeedAppearances")))?.value : void 0;
1097
+ const fieldValue = await summary({ kind: "array", values: include("acroform") ? fields() : [] });
1098
+ const outlineValue = await summary({ kind: "array", values: include("outlines") ? outlines() : [] });
1099
+ const labelValue = await summary({ kind: "array", values: include("pagelabels") ? labels() : [] });
777
1100
  const metadata = {
778
1101
  pdfversion: document.crossReference.version,
779
1102
  ...options.jsonVersion === 2 ? { maxobjectid: maximum } : {},
@@ -785,7 +1108,15 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
785
1108
  attachments: attachmentValue
786
1109
  };
787
1110
  const payload = options.jsonVersion === 1 ? jsonRecord({ version: 1, ...metadata, objects: objectValue }) : jsonRecord({ version: 2, qpdf: { kind: "array", values: [jsonRecord(metadata), objectValue] } });
788
- json = await PdfFileSource.fromStream(storage.fs, storage.directory, jsonChunks(payload, signal), { signal, maxInputBytes: maxOutputBytes });
1111
+ const sections = {
1112
+ ...metadata,
1113
+ objects: objectValue,
1114
+ objectinfo: { kind: "object", entries: objectInfoEntries() },
1115
+ qpdf: { kind: "array", values: [jsonRecord({ jsonversion: 2, pdfversion: document.crossReference.version, maxobjectid: maximum }), objectValue] }
1116
+ };
1117
+ const selectedPayload = { version: options.jsonVersion ?? 2, parameters: jsonRecord({ decodelevel: "generalized" }) };
1118
+ for (const key of options.jsonKeys) selectedPayload[key] = sections[key];
1119
+ json = await PdfFileSource.fromStream(storage.fs, storage.directory, jsonChunks(options.jsonKeys.length ? jsonRecord(selectedPayload) : payload, signal), { signal, maxInputBytes: maxOutputBytes });
789
1120
  let total = json.size;
790
1121
  for await (const entry of files.entries()) {
791
1122
  if (entry.name === options.outputFile && options.outputFile !== "-") continue;
@@ -796,14 +1127,14 @@ async function createQpdfJson(sourceDocument, document, source, storage, options
796
1127
  } catch (error) {
797
1128
  await close().catch(() => {
798
1129
  });
799
- if (error instanceof PdfError2 && error.message.startsWith("Unsupported streaming PDF filter: ")) throw new PdfError2(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
1130
+ if (error instanceof PdfError3 && error.message.startsWith("Unsupported streaming PDF filter: ")) throw new PdfError3(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
800
1131
  throw error;
801
1132
  }
802
1133
  }
803
1134
 
804
1135
  // packages/safe-bash-command-qpdf/src/linearization.ts
805
1136
  import { IntegerTable as IntegerTable5, PagedStorage as PagedStorage5 } from "@poe-platform/safe-fs/storage";
806
- import { PdfError as PdfError3, cosDict as cosDict2, dictGet as dictGet3, readCosXrefRevision, scanCosRangeObjects } from "../pdf-ast/index.js";
1137
+ import { PdfError as PdfError4, cosDict as cosDict3, dictGet as dictGet4, readCosXrefRevision, scanCosRangeObjects, PdfFileSource as PdfFileSource2 } from "../pdf-ast/index.js";
807
1138
  async function firstDictionary(document, source, storage, signal) {
808
1139
  const backing = new PagedStorage5({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4);
809
1140
  const seen = new IntegerTable5(backing), revisions = new IntegerTable5(backing), streams = new IntegerTable5(backing);
@@ -814,7 +1145,7 @@ async function firstDictionary(document, source, storage, signal) {
814
1145
  const entry = await document.crossReference.index.get(number, signal);
815
1146
  if (!entry || entry.type === "free") return;
816
1147
  const object = await document.objects.get(number, entry.generationNumber ?? 0);
817
- if (!object?.stream && object?.value.kind === "dict" && dictGet3(object.value, "Linearized") !== void 0) return { dict: object.value, type: entry.type };
1148
+ if (!object?.stream && object?.value.kind === "dict" && dictGet4(object.value, "Linearized") !== void 0) return { dict: object.value, type: entry.type };
818
1149
  return;
819
1150
  }
820
1151
  try {
@@ -831,10 +1162,10 @@ async function firstDictionary(document, source, storage, signal) {
831
1162
  if (match && !compressed) compressed = match.dict;
832
1163
  step = await revision.next();
833
1164
  }
834
- const previous = dictGet3(step.value, "Prev");
1165
+ const previous = dictGet4(step.value, "Prev");
835
1166
  offset = previous?.kind === "number" ? previous.value : void 0;
836
1167
  } finally {
837
- await revision.return(cosDict2({}));
1168
+ await revision.return(cosDict3({}));
838
1169
  }
839
1170
  }
840
1171
  if (compressed) return compressed;
@@ -850,7 +1181,7 @@ async function firstDictionary(document, source, storage, signal) {
850
1181
  await streams.set(BigInt(number), 1n);
851
1182
  const entry = await document.crossReference.index.get(number, signal);
852
1183
  if (!entry || entry.type !== "uncompressed") continue;
853
- const object = await document.objects.get(number, entry.generationNumber ?? 0), type = object?.value.kind === "dict" ? dictGet3(object.value, "Type") : void 0;
1184
+ const object = await document.objects.get(number, entry.generationNumber ?? 0), type = object?.value.kind === "dict" ? dictGet4(object.value, "Type") : void 0;
854
1185
  if (!object?.stream || type?.kind !== "name" || type.decoded !== "ObjStm") continue;
855
1186
  try {
856
1187
  for await (const member of document.objects.objectStreamEntries(number, entry.generationNumber ?? 0)) {
@@ -858,11 +1189,11 @@ async function firstDictionary(document, source, storage, signal) {
858
1189
  if (match) return match.dict;
859
1190
  }
860
1191
  } catch (error) {
861
- if (!(error instanceof PdfError3) || error.code !== "E_PARSE") throw error;
1192
+ if (!(error instanceof PdfError4) || error.code !== "E_PARSE") throw error;
862
1193
  }
863
1194
  }
864
1195
  }
865
- for await (const page of document.pages()) if (!page.reference && dictGet3(page.dict, "Linearized") !== void 0) return page.dict;
1196
+ for await (const page of document.pages()) if (!page.reference && dictGet4(page.dict, "Linearized") !== void 0) return page.dict;
866
1197
  return void 0;
867
1198
  } catch (error) {
868
1199
  failed = true;
@@ -873,31 +1204,57 @@ async function firstDictionary(document, source, storage, signal) {
873
1204
  });
874
1205
  }
875
1206
  }
876
- async function* linearizationParts(document, source, storage, filename, pageCount, signal) {
1207
+ async function* linearizationParts(document, source, storage, filename, pageCount, signal, mode) {
877
1208
  const dict = await firstDictionary(document, source, storage, signal);
878
- yield filename ?? "empty";
879
1209
  if (!dict) {
880
- yield ": not linearized\n";
1210
+ yield `${filename ?? "empty"} is not linearized
1211
+ `.repeat(mode === "both" ? 2 : 1);
881
1212
  return;
882
1213
  }
1214
+ if (mode !== "show") {
1215
+ yield `${filename ?? "empty"}: no linearization errors
1216
+ `;
1217
+ if (mode === "check") return;
1218
+ }
883
1219
  async function number(key, fallback = 0) {
884
- const node = (await document.lookup(dictGet3(dict, key)))?.value;
1220
+ const node = (await document.lookup(dictGet4(dict, key)))?.value;
885
1221
  return node?.kind === "number" ? node.value : fallback;
886
1222
  }
887
- const hints = (await document.lookup(dictGet3(dict, "H")))?.value;
1223
+ const hints = (await document.lookup(dictGet4(dict, "H")))?.value;
888
1224
  async function hint(index) {
889
1225
  const node = hints?.kind === "array" ? (await document.lookup(hints.items[index]))?.value : void 0;
890
- return node && "value" in node ? node.value ?? 0 : 0;
1226
+ return node?.kind === "number" ? node.value : 0;
1227
+ }
1228
+ const values = { file_size: await number("L"), first_page_object: await number("O"), first_page_end: await number("E"), npages: pageCount, xref_zero_offset: await number("T"), first_page: await number("P"), H_offset: await hint(0), H_length: await hint(1) };
1229
+ let decoded, sharedOffset = 0, outlineOffset;
1230
+ try {
1231
+ for await (const entry of document.crossReference.index.entries(signal)) {
1232
+ if (entry.type !== "uncompressed" || entry.offset !== values.H_offset) continue;
1233
+ const object = await document.objects.get(entry.objectNumber, entry.generationNumber ?? 0);
1234
+ if (!object?.stream || object.value.kind !== "dict") break;
1235
+ const shared = (await document.lookup(dictGet4(object.value, "S")))?.value, outline = (await document.lookup(dictGet4(object.value, "O")))?.value;
1236
+ sharedOffset = shared?.kind === "number" ? shared.value : 0;
1237
+ outlineOffset = outline?.kind === "number" ? outline.value : void 0;
1238
+ decoded = await PdfFileSource2.fromStream(storage.fs, storage.directory, document.objects.decodeStream(entry.objectNumber, entry.generationNumber ?? 0), { signal });
1239
+ break;
1240
+ }
1241
+ const report = linearizationReport(filename ?? "empty", values, decoded?.size ?? 0, sharedOffset, outlineOffset);
1242
+ let step = report.next();
1243
+ while (!step.done) {
1244
+ signal.throwIfAborted();
1245
+ if (typeof step.value === "string") {
1246
+ yield step.value;
1247
+ step = report.next(new Uint8Array());
1248
+ } else step = report.next(await decoded.read(step.value.offset, step.value.length, signal));
1249
+ }
1250
+ } finally {
1251
+ await decoded?.close();
891
1252
  }
892
- yield `: linearized
893
- Linearization dictionary: L=${await number("L")} H=[${await hint(0)} ${await hint(1)}] O=${await number("O")} E=${await number("E")} N=${await number("N", pageCount)} T=${await number("T")}
894
- no linearization errors
895
- `;
896
1253
  }
897
1254
 
898
1255
  // packages/safe-bash-command-qpdf/src/selection.ts
899
1256
  import { IntegerTable as IntegerTable6, PagedStorage as PagedStorage6 } from "@poe-platform/safe-fs/storage";
900
- import { PdfRetainedDocument, createRetainedPageCopy, retainedCosObjects, decodePdfString as decodePdfString5, dictGet as dictGet4 } from "../pdf-ast/index.js";
1257
+ import { PdfRetainedDocument, createRetainedPageCopy, retainedCosObjects, decodePdfString as decodePdfString6, dictGet as dictGet5 } from "../pdf-ast/index.js";
901
1258
  var QpdfMissingInput = class extends Error {
902
1259
  constructor(file) {
903
1260
  super(`qpdf: cannot open ${file}
@@ -921,8 +1278,8 @@ async function copyQpdfSelections(base, source, inputs, storage, options, signal
921
1278
  if (!options.emptyInput) {
922
1279
  const info = (await base.lookup(base.crossReference.infoRef))?.value;
923
1280
  if (info?.kind === "dict") for (const key of ["Title", "Author"]) {
924
- const value = (await base.lookup(dictGet4(info, key)))?.value;
925
- const text = value?.kind === "string" ? decodePdfString5(value) : value?.kind === "name" ? value.decoded : void 0;
1281
+ const value = (await base.lookup(dictGet5(info, key)))?.value;
1282
+ const text = value?.kind === "string" ? decodePdfString6(value) : value?.kind === "name" ? value.decoded : void 0;
926
1283
  if (text) metadata[key] = text;
927
1284
  }
928
1285
  }
@@ -1026,67 +1383,50 @@ async function* collatedSelections(inputs, storage, options, signal) {
1026
1383
  }
1027
1384
 
1028
1385
  // packages/safe-bash-command-qpdf/src/xref-display.ts
1029
- import { PdfError as PdfError4, PdfObjectIndex, findPdfStartXref, readCosXrefRevision as readCosXrefRevision2 } from "../pdf-ast/index.js";
1030
- async function* xrefDisplayParts(document, source, storage, highest, signal) {
1031
- let latest, failed = false;
1032
- try {
1033
- try {
1034
- const offset = await findPdfStartXref(source, { signal });
1035
- latest = await PdfObjectIndex.build(readCosXrefRevision2(source, offset, { signal }), storage, { signal, duplicate: "last" });
1036
- } catch (error) {
1037
- signal.throwIfAborted();
1038
- if (!(error instanceof PdfError4) || error.code !== "E_PARSE") throw error;
1039
- }
1040
- let count = 0;
1041
- for await (const entry of document.crossReference.index.entries(signal)) {
1042
- if (entry.type === "free") continue;
1043
- const number = entry.objectNumber, generation = entry.generationNumber ?? 0;
1386
+ async function* xrefDisplayParts(document, highest, signal) {
1387
+ let count = 0;
1388
+ for await (const entry of document.crossReference.index.entries(signal)) {
1389
+ if (entry.type === "free") continue;
1390
+ const number = entry.objectNumber, generation = entry.generationNumber ?? 0;
1391
+ if (entry.type === "compressed" && document.crossReference.revisionCount === 0) {
1044
1392
  const object = await document.objects.get(number, generation);
1045
- if (!object) throw new PdfError4("E_PARSE", "Missing indexed PDF object");
1046
- const type = object.stream ? "stream" : object.value.kind, revision = await latest?.get(number, signal);
1047
- yield revision?.type === "compressed" ? `${number}/0: compressed; stream = ${revision.objectStreamNumber}; index = ${revision.indexInObjectStream}; type = ${type}
1048
- ` : `${number}/${generation}: uncompressed; type = ${type}
1393
+ yield `${number}/${generation}: uncompressed; type = ${object.value.kind}
1049
1394
  `;
1050
- if (++count % 64 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
1051
- }
1052
- for await (const page of document.pages()) if (!page.reference) {
1053
- yield `${++highest}/0: uncompressed; type = dict
1395
+ } else yield entry.type === "compressed" ? `${number}/0: compressed; stream = ${entry.objectStreamNumber}, index = ${entry.indexInStream ?? entry.indexInObjectStream}
1396
+ ` : `${number}/${generation}: uncompressed; offset = ${entry.offset}
1054
1397
  `;
1055
- count++;
1056
- }
1057
- if (!count) yield "\n";
1058
- } catch (error) {
1059
- failed = true;
1060
- throw error;
1061
- } finally {
1062
- await latest?.close().catch((error) => {
1063
- if (!failed) return Promise.reject(error);
1064
- });
1398
+ if (++count % 64 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
1399
+ }
1400
+ for await (const page of document.pages()) if (!page.reference) {
1401
+ yield `${++highest}/0: uncompressed; type = dict
1402
+ `;
1403
+ count++;
1065
1404
  }
1405
+ if (!count) yield "\n";
1066
1406
  }
1067
1407
 
1068
1408
  // packages/safe-bash-command-qpdf/src/page-display.ts
1069
1409
  import { IntegerTable as IntegerTable7, PagedStorage as PagedStorage7 } from "@poe-platform/safe-fs/storage";
1070
- import { PdfMutableObjectStore as PdfMutableObjectStore3, cosDict as cosDict3, dictGet as dictGet5 } from "../pdf-ast/index.js";
1410
+ import { PdfMutableObjectStore as PdfMutableObjectStore3, cosDict as cosDict4, dictGet as dictGet6 } from "../pdf-ast/index.js";
1071
1411
  async function* imageParts(document, page, storage, signal) {
1072
1412
  const values = new PdfMutableObjectStore3(storage, { signal });
1073
1413
  const backing = new PagedStorage7({ fs: storage.fs, cwd: storage.directory, env: {}, signal }, 4);
1074
1414
  const visited = new IntegerTable7(backing, 64);
1075
- let top = -1, failed = false, work = 0;
1415
+ let top = -1, failed = false, work = 0, hasImages = false;
1076
1416
  async function pageResources() {
1077
- const direct = await document.lookup(dictGet5(page, "Resources"));
1417
+ const direct = await document.lookup(dictGet6(page, "Resources"));
1078
1418
  if (direct?.value.kind === "dict") return { value: direct.value, inherited: false };
1079
1419
  const parents = new IntegerTable7(backing, 64);
1080
1420
  let current = page;
1081
1421
  while (current) {
1082
1422
  signal.throwIfAborted();
1083
1423
  if (++work % 64 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
1084
- const node = dictGet5(current, "Resources");
1424
+ const node = dictGet6(current, "Resources");
1085
1425
  if (node) {
1086
1426
  const found2 = await document.lookup(node);
1087
1427
  return found2?.value.kind === "dict" && !found2.stream ? { value: found2.value, inherited: true } : void 0;
1088
1428
  }
1089
- const parent = dictGet5(current, "Parent");
1429
+ const parent = dictGet6(current, "Parent");
1090
1430
  if (parent?.kind === "ref") {
1091
1431
  if (await parents.get(BigInt(parent.objectNumber)) !== void 0) break;
1092
1432
  await parents.set(BigInt(parent.objectNumber), 1n);
@@ -1099,9 +1439,9 @@ async function* imageParts(document, page, storage, signal) {
1099
1439
  async function push(node, inherited = false) {
1100
1440
  const resource = await document.lookup(node);
1101
1441
  if (resource?.value.kind !== "dict" || resource.stream) return;
1102
- const found = await document.lookup(dictGet5(resource.value, "XObject"));
1442
+ const found = await document.lookup(dictGet6(resource.value, "XObject"));
1103
1443
  if (found?.value.kind !== "dict" || found.stream && !inherited) return;
1104
- const objects = inherited ? cosDict3(Object.fromEntries(found.value.entries.map((entry) => [entry.key.decoded, entry.value]))) : found.value;
1444
+ const objects = inherited ? cosDict4(Object.fromEntries(found.value.entries.map((entry) => [entry.key.decoded, entry.value]))) : found.value;
1105
1445
  const ref = await values.allocate(objects), bytes = new Uint8Array(24), view = new DataView(bytes.buffer);
1106
1446
  view.setFloat64(0, top);
1107
1447
  view.setFloat64(8, ref.objectNumber);
@@ -1128,20 +1468,24 @@ async function* imageParts(document, page, storage, signal) {
1128
1468
  const reference = entry.value.kind === "ref" ? entry.value : void 0;
1129
1469
  const value = (await document.lookup(entry.value))?.value;
1130
1470
  if (value?.kind !== "dict") continue;
1131
- const subtype = dictGet5(value, "Subtype");
1471
+ const subtype = dictGet6(value, "Subtype");
1132
1472
  if (subtype?.kind !== "name") continue;
1133
1473
  if (subtype.decoded === "Image") {
1134
- const width = dictGet5(value, "Width"), height = dictGet5(value, "Height");
1474
+ const width = dictGet6(value, "Width"), height = dictGet6(value, "Height");
1475
+ if (!hasImages) {
1476
+ yield " images:\n";
1477
+ hasImages = true;
1478
+ }
1135
1479
  yield " /";
1136
1480
  yield entry.key.decoded;
1137
- yield `: ${reference ? `${reference.objectNumber} ${reference.generationNumber} R` : "inline"} (${width?.kind === "number" ? width.value : 0} x ${height?.kind === "number" ? height.value : 0})
1481
+ yield `: ${reference ? `${reference.objectNumber} ${reference.generationNumber} R` : "inline"}, ${width?.kind === "number" ? width.value : 0} x ${height?.kind === "number" ? height.value : 0}
1138
1482
  `;
1139
1483
  } else if (subtype.decoded === "Form") {
1140
1484
  if (reference) {
1141
1485
  if (await visited.get(BigInt(reference.objectNumber)) !== void 0) continue;
1142
1486
  await visited.set(BigInt(reference.objectNumber), 1n);
1143
1487
  }
1144
- await push(dictGet5(value, "Resources"));
1488
+ await push(dictGet6(value, "Resources"));
1145
1489
  }
1146
1490
  }
1147
1491
  } catch (error) {
@@ -1160,12 +1504,9 @@ async function* pageDisplayParts(document, storage, highest, withImages, signal)
1160
1504
  const ref = page.reference ?? { objectNumber: ++highest, generationNumber: 0 };
1161
1505
  yield `page ${++count}: ${ref.objectNumber} ${ref.generationNumber} R
1162
1506
  `;
1163
- if (withImages) {
1164
- yield " images:\n";
1165
- yield* imageParts(document, page.dict, storage, signal);
1166
- }
1507
+ if (withImages) yield* imageParts(document, page.dict, storage, signal);
1167
1508
  yield " content:\n";
1168
- const contents = dictGet5(page.dict, "Contents");
1509
+ const contents = dictGet6(page.dict, "Contents");
1169
1510
  for (const node of contents?.kind === "array" ? contents.items : contents ? [contents] : []) {
1170
1511
  if (node.kind === "ref") yield ` ${node.objectNumber} ${node.generationNumber} R
1171
1512
  `;
@@ -1175,7 +1516,7 @@ async function* pageDisplayParts(document, storage, highest, withImages, signal)
1175
1516
  }
1176
1517
 
1177
1518
  // packages/safe-bash-command-qpdf/src/retained.ts
1178
- import { QpdfJsonDocument, encryptRetainedPdfChunks, copyRetainedAttachments, editRetainedDocument, PdfDuplicateAttachment, PdfError as PdfError5, PdfFileSource as PdfFileSource2, PdfRetainedDocument as PdfRetainedDocument2, PdfStagedOutputs as PdfStagedOutputs2, saveRetainedDocumentChunks, retainedCosObjects as retainedCosObjects2, serializeRetainedCosDocumentChunks, cosArray as cosArray2, cosDict as cosDict4, cosName, cosNumber as cosNumber2, cosRef, cosString as cosString2, dictGet as dictGet6 } from "../pdf-ast/index.js";
1519
+ import { QpdfJsonDocument, encryptRetainedPdfChunks, copyRetainedAttachments, editRetainedDocument, PdfDuplicateAttachment, PdfMissingAttachment, PdfError as PdfError5, PdfFileSource as PdfFileSource3, PdfRetainedDocument as PdfRetainedDocument2, PdfStagedOutputs as PdfStagedOutputs2, saveRetainedDocumentChunks, retainedCosObjects as retainedCosObjects2, serializeRetainedCosDocumentChunks, cosArray as cosArray2, cosDict as cosDict5, cosName, cosNumber as cosNumber3, cosRef, cosString as cosString2, dictGet as dictGet7 } from "../pdf-ast/index.js";
1179
1520
  import { FsError } from "../safe-bash-contracts/errors.js";
1180
1521
  import { writeBytes } from "../safe-bash-contracts/io.js";
1181
1522
  import { resolvePath } from "../safe-bash-contracts/path.js";
@@ -1205,7 +1546,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1205
1546
  yield bytes;
1206
1547
  }
1207
1548
  }
1208
- output = await PdfFileSource2.fromStream(context.fs, storage.directory, admitted(), { signal, maxInputBytes: limits.maxOutputBytes });
1549
+ output = await PdfFileSource3.fromStream(context.fs, storage.directory, admitted(), { signal, maxInputBytes: limits.maxOutputBytes });
1209
1550
  for await (const bytes of output.stream(0, output.size, signal)) await writeBytes(context.stdout, bytes, signal);
1210
1551
  }
1211
1552
  try {
@@ -1215,7 +1556,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1215
1556
  if (inputs.has(candidate)) return inputs.get(candidate);
1216
1557
  let acquired;
1217
1558
  try {
1218
- acquired = await PdfFileSource2.open(context.fs, resolvePath(context.cwd, candidate), { signal, maxInputBytes: maximum - inputBytes });
1559
+ acquired = await PdfFileSource3.open(context.fs, resolvePath(context.cwd, candidate), { signal, maxInputBytes: maximum - inputBytes });
1219
1560
  } catch (error) {
1220
1561
  signal.throwIfAborted();
1221
1562
  if (error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR", "EACCES", "EISDIR"].includes(String(error.code)) || error instanceof PdfError5 && error.message === "PDF source must be a regular file") {
@@ -1230,7 +1571,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1230
1571
  return acquired;
1231
1572
  };
1232
1573
  const reconstruct = async () => {
1233
- const next = await PdfFileSource2.fromStream(context.fs, storage.directory, jsonDocument.chunks(), { signal });
1574
+ const next = await PdfFileSource3.fromStream(context.fs, storage.directory, jsonDocument.chunks(), { signal });
1234
1575
  rebuilt.push(next);
1235
1576
  await document?.close();
1236
1577
  document = void 0;
@@ -1245,14 +1586,14 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1245
1586
  await acquire(candidate);
1246
1587
  }
1247
1588
  if (inputName === "-" || options.updateFromJsonFiles.includes("-") || [...options.pageSpecs, ...options.addAttachmentSpecs, ...options.copyAttachmentsSpecs, ...options.stampSpecs].some((spec) => spec.file === "-")) {
1248
- const acquired = await PdfFileSource2.fromStream(context.fs, storage.directory, context.stdin, { signal, maxInputBytes: maximum - inputBytes });
1589
+ const acquired = await PdfFileSource3.fromStream(context.fs, storage.directory, context.stdin, { signal, maxInputBytes: maximum - inputBytes });
1249
1590
  inputs.set("-", acquired);
1250
1591
  inputBytes += acquired.size;
1251
1592
  context.inputBudget?.check(inputBytes);
1252
1593
  }
1253
1594
  if (useEmpty) {
1254
- const objects = [cosDict4({ Type: cosName("Catalog"), Pages: cosRef(2) }), cosDict4({ Type: cosName("Pages"), Count: cosNumber2(0), Kids: cosArray2([]) }), cosDict4({ Producer: cosString2("@poe-code/pdf-ast") })];
1255
- source = await PdfFileSource2.fromStream(context.fs, storage.directory, serializeRetainedCosDocumentChunks({
1595
+ const objects = [cosDict5({ Type: cosName("Catalog"), Pages: cosRef(2) }), cosDict5({ Type: cosName("Pages"), Count: cosNumber3(0), Kids: cosArray2([]) }), cosDict5({ Producer: cosString2("@poe-code/pdf-ast") })];
1596
+ source = await PdfFileSource3.fromStream(context.fs, storage.directory, serializeRetainedCosDocumentChunks({
1256
1597
  objects: objects.map((value, index) => ({ objectNumber: index + 1, generationNumber: 0, value })),
1257
1598
  rootRef: cosRef(1),
1258
1599
  infoRef: cosRef(3),
@@ -1339,7 +1680,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1339
1680
  selectedValue = object.value;
1340
1681
  selectedLength = object.stream?.length;
1341
1682
  }
1342
- if (!object.stream && object.value.kind === "dict" && dictGet6(object.value, "Linearized") !== void 0) linearized = true;
1683
+ if (!object.stream && object.value.kind === "dict" && dictGet7(object.value, "Linearized") !== void 0) linearized = true;
1343
1684
  if (object.stream) for await (const ignored of object.stream.chunks) void ignored;
1344
1685
  }
1345
1686
  for await (const page of document.pages()) {
@@ -1353,7 +1694,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1353
1694
  `);
1354
1695
  }
1355
1696
  if (options.showLinearization) {
1356
- const parts = linearizationParts(document, source, storage, inputName, count, signal);
1697
+ const parts = linearizationParts(document, source, storage, inputName, count, signal, options.showLinearization);
1357
1698
  await publishInspection((async function* () {
1358
1699
  for await (const part of parts) yield* encodeDisplayParts([part], signal);
1359
1700
  })());
@@ -1368,7 +1709,7 @@ async function executeRetainedQpdf(context, options, limits, signal, inputBytes
1368
1709
  for await (const part of parts) yield* encodeDisplayParts([part], signal);
1369
1710
  })();
1370
1711
  } else if (options.showXref) {
1371
- const parts = xrefDisplayParts(document, source, storage, highest, signal);
1712
+ const parts = xrefDisplayParts(document, highest, signal);
1372
1713
  chunks = (async function* () {
1373
1714
  for await (const part of parts) yield* encodeDisplayParts([part], signal);
1374
1715
  })();
@@ -1400,13 +1741,7 @@ File is ${count === 0 ? "empty" : linearized ? "linearized" : "not linearized"}
1400
1741
  No syntax or stream encoding errors found; the file may still contain
1401
1742
  errors that qpdf cannot detect
1402
1743
  ` : options.showNpages ? `${count}
1403
- ` : encryption ? `R = ${encryption.revision}
1404
- V = ${encryption.version}
1405
- Length = ${encryption.keyLengthBits}
1406
- print: ${encryption.permissions.print ? "allowed" : "not allowed"}
1407
- modify: ${encryption.permissions.modify ? "allowed" : "not allowed"}
1408
- extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allowed"}
1409
- ` : "File is not encrypted\n";
1744
+ ` : encryptionReport(encryption);
1410
1745
  const bytes = new TextEncoder().encode(message);
1411
1746
  if (bytes.length > limits.maxOutputBytes) throw new RangeError("Output byte limit exceeded");
1412
1747
  await writeBytes(context.stdout, bytes, signal);
@@ -1466,6 +1801,18 @@ extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allo
1466
1801
  selectionGraph = await copyQpdfSelections(document, source, inputs, storage, options, signal);
1467
1802
  document = await selectionGraph.openDocument();
1468
1803
  }
1804
+ let pageCount = 0;
1805
+ if (options.pageLabelSpecs.length) for await (const page of document.pages()) {
1806
+ void page;
1807
+ pageCount++;
1808
+ }
1809
+ let pageLabels;
1810
+ try {
1811
+ pageLabels = parseQpdfPageLabels(options.pageLabelSpecs, pageCount);
1812
+ } catch (error) {
1813
+ return await diagnostic(`qpdf: ${error.message}
1814
+ `);
1815
+ }
1469
1816
  const destination = options.replaceInput ? inputName : options.outputFile;
1470
1817
  const { removeInfo, removeMetadata, removeStructure, removeAcroform, removePageLabels } = options;
1471
1818
  async function* rotations() {
@@ -1570,8 +1917,8 @@ extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allo
1570
1917
  yield { ...spec, length: input.size, chunks: input.stream(0, input.size, signal) };
1571
1918
  }
1572
1919
  }
1573
- if (options.generateAppearances || options.flattenAnnotations || options.stampSpecs.length || options.externalizeInlineImages || options.removeUnreferencedResources !== "no" || options.flattenRotation || options.copyAttachmentsSpecs.length || options.addAttachmentSpecs.length || options.removeAttachmentKeys.length || options.linearize || options.pageLabelSpecs.length > 0 || options.splitPagesGroup !== void 0 && (removeInfo || removeMetadata || removeStructure || removeAcroform || removePageLabels)) {
1574
- editedGraph = await editRetainedDocument(document, storage, { generateAppearances: options.generateAppearances, ...options.flattenAnnotations ? { flattenAnnotations: options.flattenAnnotations === true ? "all" : options.flattenAnnotations } : {}, ...options.stampSpecs.length ? { stamps: stamps() } : {}, ...options.externalizeInlineImages ? { externalizeInlineImages: { minBytes: options.iiMinBytes, compress: options.streamDataMode !== "uncompress" } } : {}, removeUnreferencedResources: options.removeUnreferencedResources !== "no", flattenRotation: options.flattenRotation, ...options.copyAttachmentsSpecs.length ? { attachmentCopies: attachmentCopies() } : {}, ...options.removeAttachmentKeys.length ? { removeAttachments: options.removeAttachmentKeys } : {}, ...options.addAttachmentSpecs.length ? { attachments: attachments() } : {}, ...options.pageLabelSpecs.length ? { pageLabels: parseQpdfPageLabels(options.pageLabelSpecs) } : {}, linearize: options.linearize, removeInfo, removeMetadata, removeStructure, removeAcroform, removePageLabels, rotations: rotations(), signal });
1920
+ if (options.generateAppearances || options.flattenAnnotations || options.stampSpecs.length || options.externalizeInlineImages || options.removeUnreferencedResources !== "no" || options.flattenRotation || options.copyAttachmentsSpecs.length || options.addAttachmentSpecs.length || options.removeAttachmentKeys.length || options.linearize || pageLabels.length > 0 || options.splitPagesGroup !== void 0 && (removeInfo || removeMetadata || removeStructure || removeAcroform || removePageLabels)) {
1921
+ editedGraph = await editRetainedDocument(document, storage, { generateAppearances: options.generateAppearances, ...options.flattenAnnotations ? { flattenAnnotations: options.flattenAnnotations === true ? "all" : options.flattenAnnotations } : {}, ...options.stampSpecs.length ? { stamps: stamps() } : {}, ...options.externalizeInlineImages ? { externalizeInlineImages: { minBytes: options.iiMinBytes, compress: options.streamDataMode !== "uncompress" } } : {}, removeUnreferencedResources: options.removeUnreferencedResources !== "no", flattenRotation: options.flattenRotation, ...options.copyAttachmentsSpecs.length ? { attachmentCopies: attachmentCopies() } : {}, ...options.removeAttachmentKeys.length ? { removeAttachments: options.removeAttachmentKeys } : {}, ...options.addAttachmentSpecs.length ? { attachments: attachments() } : {}, ...pageLabels.length ? { pageLabels } : {}, linearize: options.linearize, removeInfo, removeMetadata, removeStructure, removeAcroform, removePageLabels, rotations: rotations(), signal });
1575
1922
  }
1576
1923
  if (!destination) return await diagnostic("qpdf: an output file is required\n");
1577
1924
  if (!options.replaceInput && inputName !== "-" && destination === inputName) return await diagnostic("qpdf: output file may not be the same as the input file (use --replace-input)\n");
@@ -1613,7 +1960,7 @@ extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allo
1613
1960
  const normalizeContent = options.normalizeContentFlag ?? (options.qdf || options.streamDataMode === "uncompress");
1614
1961
  const producer = saveRetainedDocumentChunks(editedGraph?.document ?? document, storage, { ...editedGraph ? {} : { removeInfo, removeMetadata, removeStructure, removeAcroform, removePageLabels, rotations: rotations() }, signal, normalizeContent, objectStreams: options.decrypt && document.encryption ? "preserve" : options.objectStreamsMode, maxOutputBytes: limits.maxOutputBytes, ...options.decrypt && document.encryption ? { version: "1.7", omitId: true } : {} });
1615
1962
  try {
1616
- output = await PdfFileSource2.fromStream(context.fs, storage.directory, producer, { signal, maxInputBytes: limits.maxOutputBytes });
1963
+ output = await PdfFileSource3.fromStream(context.fs, storage.directory, producer, { signal, maxInputBytes: limits.maxOutputBytes });
1617
1964
  } finally {
1618
1965
  await producer.return(void 0);
1619
1966
  }
@@ -1621,7 +1968,7 @@ extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allo
1621
1968
  const plaintext = output, config = options.encryptConfig;
1622
1969
  let encryptionFailed = false;
1623
1970
  try {
1624
- output = await PdfFileSource2.fromStream(context.fs, storage.directory, encryptRetainedPdfChunks(plaintext, storage, { userPassword: config.userPassword, ownerPassword: config.ownerPassword, permissions: { print: config.print, modify: config.modify, copy: config.copy, addNotes: config.addNotes }, signal, maxOutputBytes: limits.maxOutputBytes }), { signal, maxInputBytes: limits.maxOutputBytes });
1971
+ output = await PdfFileSource3.fromStream(context.fs, storage.directory, encryptRetainedPdfChunks(plaintext, storage, { ...config, signal, maxOutputBytes: limits.maxOutputBytes }), { signal, maxInputBytes: limits.maxOutputBytes });
1625
1972
  } catch (error) {
1626
1973
  encryptionFailed = true;
1627
1974
  throw error;
@@ -1651,6 +1998,8 @@ extract for accessibility: ${encryption.permissions.copy ? "allowed" : "not allo
1651
1998
  if (error instanceof PdfError5 && error.message.startsWith("Unsupported streaming PDF filter: ")) {
1652
1999
  throw new PdfError5(error.code, `Unsupported PDF filter: ${error.message.slice("Unsupported streaming PDF filter: ".length)}`);
1653
2000
  }
2001
+ if (error instanceof PdfMissingAttachment) return await diagnostic(`qpdf: attachment ${error.key} not found
2002
+ `);
1654
2003
  if (error instanceof PdfDuplicateAttachment) return await diagnostic(`qpdf: duplicate attachment key ${error.key}
1655
2004
  `);
1656
2005
  if (error instanceof QpdfMissingInput || error instanceof QpdfMissingAttachment) return await diagnostic(error.message);
@@ -1715,21 +2064,23 @@ import { readBytes, writeBytes as writeBytes2 } from "../safe-bash-contracts/io.
1715
2064
  import { createOutputOperation } from "../safe-bash-contracts/output.js";
1716
2065
  import {
1717
2066
  PdfDocument,
1718
- PdfFileSource as PdfFileSource3,
2067
+ retainedStampTransform,
2068
+ PdfFileSource as PdfFileSource4,
1719
2069
  parseCosDocumentSteps,
1720
2070
  serializeCosDocumentSteps,
1721
2071
  encryptCosDocumentSteps,
1722
2072
  generateDocumentFormAppearancesSteps,
1723
2073
  resolveDestinationPageIndex,
1724
- decodePdfString as decodePdfString6,
1725
- cosDict as cosDict5,
1726
- cosNumber as cosNumber3,
2074
+ decodePdfString as decodePdfString7,
2075
+ cosDict as cosDict6,
2076
+ cosNumber as cosNumber4,
2077
+ cosNull,
1727
2078
  cosArray as cosArray3,
1728
2079
  cosName as cosName2,
1729
2080
  cosString as cosString3,
1730
2081
  cosHexString,
1731
2082
  cosStream,
1732
- dictGet as dictGet7,
2083
+ dictGet as dictGet8,
1733
2084
  dictSet,
1734
2085
  dictDelete
1735
2086
  } from "../pdf-ast/index.js";
@@ -1796,7 +2147,7 @@ function cosNodeToJson(node) {
1796
2147
  case "name":
1797
2148
  return `/${node.decoded}`;
1798
2149
  case "string":
1799
- return `u:${decodePdfString6(node)}`;
2150
+ return `u:${decodePdfString7(node)}`;
1800
2151
  case "ref":
1801
2152
  return `${node.objectNumber} ${node.generationNumber} R`;
1802
2153
  case "array":
@@ -1876,7 +2227,7 @@ function parseJsonRefToken(raw) {
1876
2227
  function jsonToCosNode(val) {
1877
2228
  if (val === null || val === void 0) return { kind: "null" };
1878
2229
  if (typeof val === "boolean") return { kind: "boolean", value: val };
1879
- if (typeof val === "number") return cosNumber3(val);
2230
+ if (typeof val === "number") return cosNumber4(val);
1880
2231
  if (typeof val === "string") {
1881
2232
  if (val.startsWith("n:/")) return cosName2(val.slice(3));
1882
2233
  if (val.startsWith("n:")) return cosName2(val.slice(2));
@@ -1891,7 +2242,7 @@ function jsonToCosNode(val) {
1891
2242
  return cosArray3(val.map(jsonToCosNode));
1892
2243
  }
1893
2244
  if (typeof val === "object") {
1894
- const d = cosDict5({});
2245
+ const d = cosDict6({});
1895
2246
  for (const [k, v] of Object.entries(val)) {
1896
2247
  const keyName = k.startsWith("n:/") ? k.slice(3) : k.startsWith("/") ? k.slice(1) : k;
1897
2248
  dictSet(d, keyName, jsonToCosNode(v));
@@ -1929,9 +2280,9 @@ function applyQpdfJsonObjects(targetObjects, parsedJson, files) {
1929
2280
  if (objKey === "trailer") {
1930
2281
  const trVal = jsonToCosNode(entry.value);
1931
2282
  if (trVal.kind === "dict") {
1932
- const rNode = dictGet7(trVal, "Root");
2283
+ const rNode = dictGet8(trVal, "Root");
1933
2284
  if (rNode?.kind === "ref") rootRef = rNode;
1934
- const iNode = dictGet7(trVal, "Info");
2285
+ const iNode = dictGet8(trVal, "Info");
1935
2286
  if (iNode?.kind === "ref") infoRef = iNode;
1936
2287
  }
1937
2288
  continue;
@@ -1941,7 +2292,7 @@ function applyQpdfJsonObjects(targetObjects, parsedJson, files) {
1941
2292
  if ("stream" in entry && typeof entry.stream === "object" && entry.stream !== null) {
1942
2293
  const stObj = entry.stream;
1943
2294
  const dictConverted = jsonToCosNode(stObj.dict ?? {});
1944
- const stDict = dictConverted.kind === "dict" ? dictConverted : cosDict5({});
2295
+ const stDict = dictConverted.kind === "dict" ? dictConverted : cosDict6({});
1945
2296
  let streamBytes;
1946
2297
  if (typeof stObj.data === "string") {
1947
2298
  streamBytes = decodeBytesBase64(stObj.data);
@@ -1955,13 +2306,13 @@ function applyQpdfJsonObjects(targetObjects, parsedJson, files) {
1955
2306
  const existing = targetObjects.get(ref.objectNumber)?.value;
1956
2307
  streamBytes = existing?.kind === "stream" ? existing.rawBytes : new Uint8Array(0);
1957
2308
  }
1958
- const filterNode = dictGet7(stDict, "Filter");
2309
+ const filterNode = dictGet8(stDict, "Filter");
1959
2310
  const shouldStripFilter = filterNode?.kind === "name" && DECOMPRESSIBLE_FILTERS.has(filterNode.decoded) || filterNode?.kind === "array" && filterNode.items.every((it) => it.kind === "name" && DECOMPRESSIBLE_FILTERS.has(it.decoded));
1960
2311
  if (shouldStripFilter) {
1961
2312
  dictDelete(stDict, "Filter");
1962
2313
  dictDelete(stDict, "DecodeParms");
1963
2314
  }
1964
- dictSet(stDict, "Length", cosNumber3(streamBytes.byteLength));
2315
+ dictSet(stDict, "Length", cosNumber4(streamBytes.byteLength));
1965
2316
  targetObjects.set(ref.objectNumber, {
1966
2317
  objectNumber: ref.objectNumber,
1967
2318
  generationNumber: ref.generationNumber,
@@ -1982,7 +2333,7 @@ function applyQpdfJsonObjects(targetObjects, parsedJson, files) {
1982
2333
  if (!rootRef) {
1983
2334
  for (const obj of targetObjects.values()) {
1984
2335
  if (obj.value.kind === "dict") {
1985
- const t = dictGet7(obj.value, "Type");
2336
+ const t = dictGet8(obj.value, "Type");
1986
2337
  if (t?.kind === "name" && t.decoded === "Catalog") {
1987
2338
  rootRef = { kind: "ref", objectNumber: obj.objectNumber, generationNumber: obj.generationNumber };
1988
2339
  break;
@@ -2026,13 +2377,13 @@ function collectDocumentAttachments(doc) {
2026
2377
  const seenKeys = /* @__PURE__ */ new Set();
2027
2378
  const pushFilespec = (fsDict, fallbackKey) => {
2028
2379
  if (!fsDict) return;
2029
- const ufNode = doc.cos.resolve(dictGet7(fsDict, "UF") ?? dictGet7(fsDict, "F"));
2030
- const fnStr = ufNode?.kind === "string" ? decodePdfString6(ufNode) : fallbackKey;
2380
+ const ufNode = doc.cos.resolve(dictGet8(fsDict, "UF") ?? dictGet8(fsDict, "F"));
2381
+ const fnStr = ufNode?.kind === "string" ? decodePdfString7(ufNode) : fallbackKey;
2031
2382
  const keyStr = fallbackKey || fnStr;
2032
2383
  if (seenKeys.has(keyStr)) return;
2033
- const efDict = asDict(doc.cos.resolve(dictGet7(fsDict, "EF")));
2384
+ const efDict = asDict(doc.cos.resolve(dictGet8(fsDict, "EF")));
2034
2385
  const stNode = efDict ? doc.cos.resolve(
2035
- dictGet7(efDict, "UF") ?? dictGet7(efDict, "F") ?? dictGet7(efDict, "DOS") ?? dictGet7(efDict, "Mac") ?? dictGet7(efDict, "Unix")
2386
+ dictGet8(efDict, "UF") ?? dictGet8(efDict, "F") ?? dictGet8(efDict, "DOS") ?? dictGet8(efDict, "Mac") ?? dictGet8(efDict, "Unix")
2036
2387
  ) : void 0;
2037
2388
  if (stNode?.kind === "stream") {
2038
2389
  seenKeys.add(keyStr);
@@ -2041,16 +2392,16 @@ function collectDocumentAttachments(doc) {
2041
2392
  };
2042
2393
  const walkNames = (nodeDict, visited = /* @__PURE__ */ new Set()) => {
2043
2394
  if (!nodeDict) return;
2044
- const namesArr = asArray(doc.cos.resolve(dictGet7(nodeDict, "Names")));
2395
+ const namesArr = asArray(doc.cos.resolve(dictGet8(nodeDict, "Names")));
2045
2396
  if (namesArr) {
2046
2397
  for (let idx = 0; idx + 1 < namesArr.items.length; idx += 2) {
2047
2398
  const kNode = doc.cos.resolve(namesArr.items[idx]);
2048
- const keyStr = kNode?.kind === "string" ? decodePdfString6(kNode) : `att_${idx}`;
2399
+ const keyStr = kNode?.kind === "string" ? decodePdfString7(kNode) : `att_${idx}`;
2049
2400
  const fsDict = asDict(doc.cos.resolve(namesArr.items[idx + 1]));
2050
2401
  pushFilespec(fsDict, keyStr);
2051
2402
  }
2052
2403
  }
2053
- const kidsArr = asArray(doc.cos.resolve(dictGet7(nodeDict, "Kids")));
2404
+ const kidsArr = asArray(doc.cos.resolve(dictGet8(nodeDict, "Kids")));
2054
2405
  if (kidsArr) {
2055
2406
  for (const kid of kidsArr.items) {
2056
2407
  if (kid.kind === "ref") {
@@ -2062,22 +2413,22 @@ function collectDocumentAttachments(doc) {
2062
2413
  }
2063
2414
  };
2064
2415
  const rootDict = asDict(doc.cos.resolve(doc.cos.rootRef));
2065
- const namesRoot = rootDict ? asDict(doc.cos.resolve(dictGet7(rootDict, "Names"))) : void 0;
2066
- walkNames(namesRoot ? asDict(doc.cos.resolve(dictGet7(namesRoot, "EmbeddedFiles"))) : void 0);
2067
- const rootAfArr = rootDict ? asArray(doc.cos.resolve(dictGet7(rootDict, "AF"))) : void 0;
2416
+ const namesRoot = rootDict ? asDict(doc.cos.resolve(dictGet8(rootDict, "Names"))) : void 0;
2417
+ walkNames(namesRoot ? asDict(doc.cos.resolve(dictGet8(namesRoot, "EmbeddedFiles"))) : void 0);
2418
+ const rootAfArr = rootDict ? asArray(doc.cos.resolve(dictGet8(rootDict, "AF"))) : void 0;
2068
2419
  if (rootAfArr) {
2069
2420
  for (const item of rootAfArr.items) {
2070
2421
  pushFilespec(asDict(doc.cos.resolve(item)), "");
2071
2422
  }
2072
2423
  }
2073
2424
  for (const p of doc.getPages()) {
2074
- const annotsArr = asArray(doc.cos.resolve(dictGet7(p.dict, "Annots")));
2425
+ const annotsArr = asArray(doc.cos.resolve(dictGet8(p.dict, "Annots")));
2075
2426
  if (!annotsArr) continue;
2076
2427
  for (const item of annotsArr.items) {
2077
2428
  const aDict = asDict(doc.cos.resolve(item));
2078
2429
  if (!aDict) continue;
2079
- if (asName(dictGet7(aDict, "Subtype"))?.decoded === "FileAttachment") {
2080
- pushFilespec(asDict(doc.cos.resolve(dictGet7(aDict, "FS"))), "");
2430
+ if (asName(dictGet8(aDict, "Subtype"))?.decoded === "FileAttachment") {
2431
+ pushFilespec(asDict(doc.cos.resolve(dictGet8(aDict, "FS"))), "");
2081
2432
  }
2082
2433
  }
2083
2434
  }
@@ -2086,7 +2437,7 @@ function collectDocumentAttachments(doc) {
2086
2437
  function collectNumberTreeDicts(doc, treeDict, visited = /* @__PURE__ */ new Set()) {
2087
2438
  const out = [];
2088
2439
  if (!treeDict) return out;
2089
- const numsArr = asArray(doc.cos.resolve(dictGet7(treeDict, "Nums")));
2440
+ const numsArr = asArray(doc.cos.resolve(dictGet8(treeDict, "Nums")));
2090
2441
  if (numsArr) {
2091
2442
  for (let idx = 0; idx + 1 < numsArr.items.length; idx += 2) {
2092
2443
  const kNode = doc.cos.resolve(numsArr.items[idx]);
@@ -2096,7 +2447,7 @@ function collectNumberTreeDicts(doc, treeDict, visited = /* @__PURE__ */ new Set
2096
2447
  }
2097
2448
  }
2098
2449
  }
2099
- const kidsArr = asArray(doc.cos.resolve(dictGet7(treeDict, "Kids")));
2450
+ const kidsArr = asArray(doc.cos.resolve(dictGet8(treeDict, "Kids")));
2100
2451
  if (kidsArr) {
2101
2452
  for (const kid of kidsArr.items) {
2102
2453
  if (kid.kind === "ref") {
@@ -2209,7 +2560,8 @@ function parseQpdfArguments(argv) {
2209
2560
  } else if (arg === "--linearize") {
2210
2561
  linearize = true;
2211
2562
  } else if (arg === "--show-linearization" || arg === "--check-linearization") {
2212
- showLinearization = true;
2563
+ const mode = arg === "--show-linearization" ? "show" : "check";
2564
+ showLinearization = !showLinearization || showLinearization === mode ? mode : "both";
2213
2565
  } else if (arg === "--collate") {
2214
2566
  collateCount = 1;
2215
2567
  } else if (arg.startsWith("--collate=")) {
@@ -2462,23 +2814,30 @@ function parseQpdfArguments(argv) {
2462
2814
  } else if (arg === "--encrypt") {
2463
2815
  const userPassword = argv[++i] ?? "";
2464
2816
  const ownerPassword = argv[++i] ?? "";
2465
- const keyLength = Number.parseInt(argv[++i] ?? "256", 10) || 256;
2817
+ if (argv[++i] !== "256") {
2818
+ return { result: { exitCode: 2, stdout: "", stderr: "qpdf: only 256-bit encryption is supported; use --encrypt user-password owner-password 256 --\n" } };
2819
+ }
2466
2820
  const cfg = {
2467
2821
  userPassword,
2468
2822
  ownerPassword,
2469
- keyLength,
2470
- print: true,
2471
- modify: true,
2472
- copy: true,
2473
- addNotes: true
2823
+ permissions: {
2824
+ print: true,
2825
+ printHighRes: true,
2826
+ modify: true,
2827
+ copy: true,
2828
+ addNotes: true,
2829
+ fillForms: true,
2830
+ assemble: true,
2831
+ extractAccessibility: true
2832
+ }
2474
2833
  };
2475
2834
  i++;
2476
2835
  while (i < argv.length && argv[i] !== "--") {
2477
2836
  const sub = argv[i];
2478
- if (sub === "--print=none") cfg.print = false;
2479
- if (sub === "--modify=none") cfg.modify = false;
2480
- if (sub === "--extract=n") cfg.copy = false;
2481
- if (sub === "--annotate=n") cfg.addNotes = false;
2837
+ if (sub === "--print=none") cfg.permissions.print = cfg.permissions.printHighRes = false;
2838
+ if (sub === "--modify=none") cfg.permissions.modify = cfg.permissions.addNotes = cfg.permissions.fillForms = cfg.permissions.assemble = false;
2839
+ if (sub === "--extract=n") cfg.permissions.copy = false;
2840
+ if (sub === "--annotate=n") cfg.permissions.addNotes = false;
2482
2841
  i++;
2483
2842
  }
2484
2843
  encryptConfig = cfg;
@@ -2721,27 +3080,45 @@ function* executeQpdfCli(options, files) {
2721
3080
  let linDict;
2722
3081
  for (const obj of cos.objects.values()) {
2723
3082
  const d = asDict(obj.value);
2724
- if (d && dictGet7(d, "Linearized") !== void 0) {
3083
+ if (d && dictGet8(d, "Linearized") !== void 0) {
2725
3084
  linDict = d;
2726
3085
  break;
2727
3086
  }
2728
3087
  }
2729
3088
  if (!linDict) {
2730
- return { exitCode: 0, stdout: `${inputFile ?? "empty"}: not linearized
2731
- `, stderr: "" };
3089
+ return { exitCode: 0, stdout: `${inputFile ?? "empty"} is not linearized
3090
+ `.repeat(showLinearization === "both" ? 2 : 1), stderr: "" };
2732
3091
  }
2733
3092
  const numVal = (k, fb = 0) => {
2734
- const n = cos.resolve(dictGet7(linDict, k));
3093
+ const n = cos.resolve(dictGet8(linDict, k));
2735
3094
  return n?.kind === "number" ? n.value : fb;
2736
3095
  };
2737
- const hArr = cos.resolveArray(dictGet7(linDict, "H"));
2738
- const h0 = hArr && hArr.items[0] ? cos.resolve(hArr.items[0])?.value ?? 0 : 0;
2739
- const h1 = hArr && hArr.items[1] ? cos.resolve(hArr.items[1])?.value ?? 0 : 0;
2740
- const out = [
2741
- `${inputFile ?? "empty"}: linearized`,
2742
- `Linearization dictionary: L=${numVal("L")} H=[${h0} ${h1}] O=${numVal("O")} E=${numVal("E")} N=${numVal("N", baseDoc.getPageCount())} T=${numVal("T")}`,
2743
- `no linearization errors`
2744
- ].join("\n") + "\n";
3096
+ const hArr = cos.resolveArray(dictGet8(linDict, "H"));
3097
+ const hint = (index) => {
3098
+ const value = cos.resolve(hArr?.items[index]);
3099
+ return value?.kind === "number" ? value.value : 0;
3100
+ };
3101
+ const h0 = hint(0), h1 = hint(1);
3102
+ if (showLinearization === "check") return { exitCode: 0, stdout: `${inputFile ?? "empty"}: no linearization errors
3103
+ `, stderr: "" };
3104
+ const values = { file_size: numVal("L"), first_page_object: numVal("O"), first_page_end: numVal("E"), npages: baseDoc.getPageCount(), xref_zero_offset: numVal("T"), first_page: numVal("P"), H_offset: h0, H_length: h1 };
3105
+ let hintStream;
3106
+ for (const object of cos.objects.values()) if (object.span?.start === h0 && object.value.kind === "stream") {
3107
+ hintStream = object.value;
3108
+ break;
3109
+ }
3110
+ const hintBytes = hintStream ? cos.decodeStream(hintStream) : new Uint8Array();
3111
+ const shared = hintStream && cos.resolve(dictGet8(hintStream.dict, "S")), outline = hintStream && cos.resolve(dictGet8(hintStream.dict, "O"));
3112
+ const report = linearizationReport(inputFile ?? "empty", values, hintBytes.length, shared?.kind === "number" ? shared.value : 0, outline?.kind === "number" ? outline.value : void 0);
3113
+ let out = showLinearization === "both" ? `${inputFile ?? "empty"}: no linearization errors
3114
+ ` : "", step = report.next();
3115
+ while (!step.done) {
3116
+ yield;
3117
+ if (typeof step.value === "string") {
3118
+ out += step.value;
3119
+ step = report.next(new Uint8Array());
3120
+ } else step = report.next(hintBytes.subarray(step.value.offset, step.value.offset + step.value.length));
3121
+ }
2745
3122
  return { exitCode: 0, stdout: out, stderr: "" };
2746
3123
  }
2747
3124
  if (check) {
@@ -2749,7 +3126,7 @@ function* executeQpdfCli(options, files) {
2749
3126
  let isLin = false;
2750
3127
  for (const obj of cos.objects.values()) {
2751
3128
  const d = asDict(obj.value);
2752
- if (d && dictGet7(d, "Linearized") !== void 0) {
3129
+ if (d && dictGet8(d, "Linearized") !== void 0) {
2753
3130
  isLin = true;
2754
3131
  break;
2755
3132
  }
@@ -2770,22 +3147,22 @@ errors that qpdf cannot detect
2770
3147
  const pages = baseDoc.getPages();
2771
3148
  const collectImagesFromResources = (resDict, visitedForms = /* @__PURE__ */ new Set()) => {
2772
3149
  if (!resDict) return;
2773
- const xobjDict = asDict(baseDoc.cos.resolve(dictGet7(resDict, "XObject")));
3150
+ const xobjDict = asDict(baseDoc.cos.resolve(dictGet8(resDict, "XObject")));
2774
3151
  if (!xobjDict) return;
2775
3152
  for (const entry of xobjDict.entries) {
2776
3153
  const ref = asRef(entry.value);
2777
3154
  const resolved = baseDoc.cos.resolve(entry.value);
2778
3155
  const streamDict = resolved?.kind === "stream" ? resolved.dict : asDict(resolved);
2779
- const subtype = streamDict ? asName(dictGet7(streamDict, "Subtype"))?.decoded : void 0;
3156
+ const subtype = streamDict ? asName(dictGet8(streamDict, "Subtype"))?.decoded : void 0;
2780
3157
  if (subtype === "Image") {
2781
- const w = asNumber(dictGet7(streamDict, "Width"))?.value ?? 0;
2782
- const h = asNumber(dictGet7(streamDict, "Height"))?.value ?? 0;
3158
+ const w = asNumber(dictGet8(streamDict, "Width"))?.value ?? 0;
3159
+ const h = asNumber(dictGet8(streamDict, "Height"))?.value ?? 0;
2783
3160
  const refStr = ref ? `${ref.objectNumber} ${ref.generationNumber} R` : "inline";
2784
- lines.push(` /${entry.key.decoded}: ${refStr} (${w} x ${h})`);
3161
+ lines.push(` /${entry.key.decoded}: ${refStr}, ${w} x ${h}`);
2785
3162
  } else if (subtype === "Form" && streamDict) {
2786
3163
  if (ref && visitedForms.has(ref.objectNumber)) continue;
2787
3164
  if (ref) visitedForms.add(ref.objectNumber);
2788
- const formRes = asDict(baseDoc.cos.resolve(dictGet7(streamDict, "Resources")));
3165
+ const formRes = asDict(baseDoc.cos.resolve(dictGet8(streamDict, "Resources")));
2789
3166
  collectImagesFromResources(formRes, visitedForms);
2790
3167
  }
2791
3168
  }
@@ -2794,11 +3171,12 @@ errors that qpdf cannot detect
2794
3171
  const p = pages[idx];
2795
3172
  lines.push(`page ${idx + 1}: ${p.ref.objectNumber} ${p.ref.generationNumber} R`);
2796
3173
  if (withImages) {
2797
- lines.push(" images:");
3174
+ const imageStart = lines.length;
2798
3175
  collectImagesFromResources(p.getResourcesDict());
3176
+ if (lines.length > imageStart) lines.splice(imageStart, 0, " images:");
2799
3177
  }
2800
3178
  lines.push(" content:");
2801
- const contentsNode = dictGet7(p.dict, "Contents");
3179
+ const contentsNode = dictGet8(p.dict, "Contents");
2802
3180
  if (contentsNode?.kind === "ref") {
2803
3181
  lines.push(` ${contentsNode.objectNumber} ${contentsNode.generationNumber} R`);
2804
3182
  } else if (contentsNode?.kind === "array") {
@@ -2816,30 +3194,20 @@ errors that qpdf cannot detect
2816
3194
  `, stderr: "" };
2817
3195
  }
2818
3196
  if (showEncryption) {
2819
- const enc = baseDoc.cos.encryption;
2820
- if (!enc) {
2821
- return { exitCode: 0, stdout: "File is not encrypted\n", stderr: "" };
2822
- }
2823
- const out = `R = ${enc.revision}
2824
- V = ${enc.version}
2825
- Length = ${enc.keyLengthBits}
2826
- print: ${enc.permissions.print ? "allowed" : "not allowed"}
2827
- modify: ${enc.permissions.modify ? "allowed" : "not allowed"}
2828
- extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2829
- `;
2830
- return { exitCode: 0, stdout: out, stderr: "" };
3197
+ return { exitCode: 0, stdout: encryptionReport(baseDoc.cos.encryption), stderr: "" };
2831
3198
  }
2832
3199
  if (showXref) {
2833
3200
  const lines = [];
2834
- const revEntries = baseDoc.cos.revisions[0]?.entries;
3201
+ const revEntries = new Map(baseDoc.cos.revisions.flatMap((revision) => [...revision.entries]).reverse());
2835
3202
  for (const [objNum, obj] of [...baseDoc.cos.objects.entries()].sort((a, b) => a[0] - b[0])) {
2836
3203
  const xEntry = revEntries?.get(objNum);
2837
3204
  if (xEntry?.type === "compressed") {
2838
3205
  lines.push(
2839
- `${objNum}/0: compressed; stream = ${xEntry.objectStreamNumber}; index = ${xEntry.indexInObjectStream}; type = ${obj.value.kind}`
3206
+ `${objNum}/0: compressed; stream = ${xEntry.objectStreamNumber}, index = ${xEntry.indexInStream ?? xEntry.indexInObjectStream}`
2840
3207
  );
2841
3208
  } else {
2842
- lines.push(`${objNum}/${obj.generationNumber}: uncompressed; type = ${obj.value.kind}`);
3209
+ const offset = xEntry?.offset ?? obj.span?.start;
3210
+ lines.push(`${objNum}/${obj.generationNumber}: uncompressed; ${offset === void 0 ? `type = ${obj.value.kind}` : `offset = ${offset}`}`);
2843
3211
  }
2844
3212
  }
2845
3213
  return { exitCode: 0, stdout: lines.join("\n") + "\n", stderr: "" };
@@ -2883,11 +3251,23 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2883
3251
  stderr: "qpdf: --json-stream-data=file requires --json-stream-prefix or an output file\n"
2884
3252
  };
2885
3253
  }
3254
+ const include = (key) => !jsonKeys.length || jsonKeys.includes(key);
3255
+ const includeObjects = include(jsonVersion === 1 ? "objects" : "qpdf");
3256
+ const objectInfo = {};
2886
3257
  const objectsMap = {};
2887
3258
  const hasSelector = jsonObjectSelectors.length > 0;
2888
3259
  const matchesSelector = (objNum, genNum) => !hasSelector || jsonObjectSelectors.some((s) => s.kind === "obj" && s.objNum === objNum && s.genNum === genNum);
2889
3260
  for (const [objNum, obj] of baseDoc.cos.objects.entries()) {
2890
3261
  if (!matchesSelector(objNum, obj.generationNumber)) continue;
3262
+ if (jsonKeys.includes("objectinfo")) {
3263
+ const dict = obj.value.kind === "stream" ? obj.value.dict : void 0;
3264
+ objectInfo[`${objNum} ${obj.generationNumber} R`] = { stream: {
3265
+ filter: dict ? cosNodeToJson(dictGet8(dict, "Filter") ?? cosNull()) : null,
3266
+ is: Boolean(dict),
3267
+ length: dict ? cosNodeToJson(dictGet8(dict, "Length") ?? cosNull()) : null
3268
+ } };
3269
+ }
3270
+ if (!includeObjects) continue;
2891
3271
  if (obj.value.kind === "stream") {
2892
3272
  const streamObj = {
2893
3273
  dict: cosNodeToJson(obj.value.dict)
@@ -2908,10 +3288,10 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2908
3288
  }
2909
3289
  }
2910
3290
  if (!hasSelector || jsonObjectSelectors.some((s) => s.kind === "trailer")) {
2911
- const trailerDict = cosDict5({
3291
+ const trailerDict = cosDict6({
2912
3292
  Root: baseDoc.cos.rootRef,
2913
3293
  ...baseDoc.cos.infoRef ? { Info: baseDoc.cos.infoRef } : {},
2914
- Size: cosNumber3(baseDoc.cos.maxObjectNumber + 1)
3294
+ Size: cosNumber4(baseDoc.cos.maxObjectNumber + 1)
2915
3295
  });
2916
3296
  objectsMap.trailer = {
2917
3297
  value: cosNodeToJson(trailerDict)
@@ -2919,12 +3299,12 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2919
3299
  }
2920
3300
  const attachmentsJson = {};
2921
3301
  const rootDictForJson = asDict(baseDoc.cos.resolve(baseDoc.cos.rootRef));
2922
- for (const att of collectDocumentAttachments(baseDoc)) {
3302
+ for (const att of include("attachments") ? collectDocumentAttachments(baseDoc) : []) {
2923
3303
  attachmentsJson[att.key] = { filename: att.filename };
2924
3304
  }
2925
- const acroDictJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet7(rootDictForJson, "AcroForm"))) : void 0;
2926
- const needAppJson = acroDictJson ? baseDoc.cos.resolve(dictGet7(acroDictJson, "NeedAppearances")) : void 0;
2927
- const acroFieldsJson = baseDoc.getFormFields().map((f) => ({
3305
+ const acroDictJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet8(rootDictForJson, "AcroForm"))) : void 0;
3306
+ const needAppJson = acroDictJson ? baseDoc.cos.resolve(dictGet8(acroDictJson, "NeedAppearances")) : void 0;
3307
+ const acroFieldsJson = (include("acroform") ? baseDoc.getFormFields() : []).map((f) => ({
2928
3308
  name: f.name,
2929
3309
  type: f.type,
2930
3310
  value: f.value
@@ -2935,7 +3315,7 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2935
3315
  fields: acroFieldsJson
2936
3316
  };
2937
3317
  const outlinesJson = [];
2938
- const outlinesRootJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet7(rootDictForJson, "Outlines"))) : void 0;
3318
+ const outlinesRootJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet8(rootDictForJson, "Outlines"))) : void 0;
2939
3319
  const collectJsonOutlines = (nodeOrRef, visited = /* @__PURE__ */ new Set()) => {
2940
3320
  let cur = nodeOrRef;
2941
3321
  while (cur) {
@@ -2945,9 +3325,9 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2945
3325
  }
2946
3326
  const d = asDict(baseDoc.cos.resolve(cur));
2947
3327
  if (!d) break;
2948
- const tNode = baseDoc.cos.resolve(dictGet7(d, "Title"));
2949
- const title = tNode?.kind === "string" ? decodePdfString6(tNode) : "";
2950
- const destPageIdx = resolveDestinationPageIndex(baseDoc, dictGet7(d, "Dest") ?? dictGet7(d, "A")) ?? 0;
3328
+ const tNode = baseDoc.cos.resolve(dictGet8(d, "Title"));
3329
+ const title = tNode?.kind === "string" ? decodePdfString7(tNode) : "";
3330
+ const destPageIdx = resolveDestinationPageIndex(baseDoc, dictGet8(d, "Dest") ?? dictGet8(d, "A")) ?? 0;
2951
3331
  const pageRef = baseDoc.getPageCount() > 0 ? baseDoc.getPage(destPageIdx).ref : { objectNumber: 0, generationNumber: 0 };
2952
3332
  if (title) {
2953
3333
  outlinesJson.push({
@@ -2956,29 +3336,46 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
2956
3336
  destpageobject: `obj:${pageRef.objectNumber} ${pageRef.generationNumber} R`
2957
3337
  });
2958
3338
  }
2959
- const firstChild = dictGet7(d, "First");
3339
+ const firstChild = dictGet8(d, "First");
2960
3340
  if (firstChild) collectJsonOutlines(firstChild, visited);
2961
- cur = dictGet7(d, "Next");
3341
+ cur = dictGet8(d, "Next");
2962
3342
  }
2963
3343
  };
2964
- if (outlinesRootJson) collectJsonOutlines(dictGet7(outlinesRootJson, "First"));
3344
+ if (include("outlines") && outlinesRootJson) collectJsonOutlines(dictGet8(outlinesRootJson, "First"));
2965
3345
  const pagelabelsJson = [];
2966
- const plTreeJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet7(rootDictForJson, "PageLabels"))) : void 0;
2967
- for (const { index: plIdx, dict: vDict } of collectNumberTreeDicts(baseDoc, plTreeJson)) {
2968
- const stNode = baseDoc.cos.resolve(dictGet7(vDict, "St"));
2969
- const pNode = baseDoc.cos.resolve(dictGet7(vDict, "P"));
2970
- const sNode = baseDoc.cos.resolve(dictGet7(vDict, "S"));
3346
+ const plTreeJson = rootDictForJson ? asDict(baseDoc.cos.resolve(dictGet8(rootDictForJson, "PageLabels"))) : void 0;
3347
+ for (const { index: plIdx, dict: vDict } of include("pagelabels") ? collectNumberTreeDicts(baseDoc, plTreeJson) : []) {
3348
+ const stNode = baseDoc.cos.resolve(dictGet8(vDict, "St"));
3349
+ const pNode = baseDoc.cos.resolve(dictGet8(vDict, "P"));
3350
+ const sNode = baseDoc.cos.resolve(dictGet8(vDict, "S"));
2971
3351
  pagelabelsJson.push({
2972
3352
  index: plIdx,
2973
3353
  start: stNode?.kind === "number" ? stNode.value : 1,
2974
- ...pNode?.kind === "string" ? { prefix: decodePdfString6(pNode) } : {},
3354
+ ...pNode?.kind === "string" ? { prefix: decodePdfString7(pNode) } : {},
2975
3355
  ...sNode?.kind === "name" ? { style: sNode.decoded } : {}
2976
3356
  });
2977
3357
  }
2978
- const pagesVal = jsonKeys.includes("pages") ? baseDoc.getPages().map((pg, idx) => ({
2979
- object: `obj:${pg.ref.objectNumber} ${pg.ref.generationNumber} R`,
2980
- pageposfrom1: idx + 1
2981
- })) : baseDoc.getPageCount();
3358
+ let pagesVal = baseDoc.getPageCount();
3359
+ if (jsonKeys.includes("pages")) {
3360
+ const values = [];
3361
+ for (const [index, page] of baseDoc.getPages().entries()) {
3362
+ let materialize2 = function(value) {
3363
+ if (value === null || typeof value !== "object") return value;
3364
+ if (value.kind === "array") return Array.from(value.values, materialize2);
3365
+ if (value.kind === "object") return Object.fromEntries(Array.from(value.entries, ([key, item]) => [key, materialize2(item)]));
3366
+ throw new Error("Unexpected page JSON value");
3367
+ };
3368
+ var materialize = materialize2;
3369
+ const steps = qpdfJsonPage(page.dict, page.ref, index, rootDictForJson, jsonVersion);
3370
+ let step = steps.next();
3371
+ while (!step.done) {
3372
+ yield;
3373
+ step = steps.next(baseDoc.cos.resolve(step.value));
3374
+ }
3375
+ values.push(materialize2(step.value));
3376
+ }
3377
+ pagesVal = values;
3378
+ }
2982
3379
  const payload = jsonVersion === 1 ? {
2983
3380
  version: 1,
2984
3381
  pdfversion: baseDoc.cos.version,
@@ -3005,7 +3402,20 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
3005
3402
  objectsMap
3006
3403
  ]
3007
3404
  };
3008
- const jsonOut = JSON.stringify(payload, null, 2) + "\n";
3405
+ const sections = {
3406
+ pages: pagesVal,
3407
+ pagelabels: pagelabelsJson,
3408
+ outlines: outlinesJson,
3409
+ acroform: acroformSection,
3410
+ encrypt: { encrypted: Boolean(baseDoc.cos.encryption) },
3411
+ attachments: attachmentsJson,
3412
+ objects: objectsMap,
3413
+ objectinfo: objectInfo,
3414
+ qpdf: [{ jsonversion: 2, pdfversion: baseDoc.cos.version, maxobjectid: baseDoc.cos.maxObjectNumber }, objectsMap]
3415
+ };
3416
+ const selectedPayload = { version: jsonVersion, parameters: { decodelevel: "generalized" } };
3417
+ for (const key of jsonKeys) selectedPayload[key] = sections[key];
3418
+ const jsonOut = JSON.stringify(jsonKeys.length ? selectedPayload : payload, null, 2) + "\n";
3009
3419
  if (outputFile && outputFile !== "-") {
3010
3420
  files.set(outputFile, new TextEncoder().encode(jsonOut));
3011
3421
  return { exitCode: 0, stdout: "", stderr: "" };
@@ -3132,17 +3542,17 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
3132
3542
  const targetRes = targetPage.getResourcesDict();
3133
3543
  const srcRes = srcStampPage.getResourcesDict();
3134
3544
  for (const catKey of ["Font", "XObject", "ExtGState", "ColorSpace", "Pattern", "Shading", "Properties"]) {
3135
- const srcSub = asDict(stampDoc.cos.resolve(dictGet7(srcRes, catKey)));
3545
+ const srcSub = asDict(stampDoc.cos.resolve(dictGet8(srcRes, catKey)));
3136
3546
  if (!srcSub) continue;
3137
- let dstSub = asDict(workingDoc.cos.resolve(dictGet7(targetRes, catKey)));
3547
+ let dstSub = asDict(workingDoc.cos.resolve(dictGet8(targetRes, catKey)));
3138
3548
  if (!dstSub) {
3139
- dstSub = cosDict5({});
3549
+ dstSub = cosDict6({});
3140
3550
  dictSet(targetRes, catKey, dstSub);
3141
3551
  }
3142
3552
  for (const entry of srcSub.entries) {
3143
3553
  const origKey = entry.key.decoded;
3144
3554
  let finalKey = origKey;
3145
- if (dictGet7(dstSub, origKey)) {
3555
+ if (dictGet8(dstSub, origKey)) {
3146
3556
  finalKey = `QStp${stampResCounter++}_${origKey}`;
3147
3557
  stampStreamStr = replacePdfNameToken(stampStreamStr, origKey, finalKey);
3148
3558
  }
@@ -3150,43 +3560,10 @@ extract for accessibility: ${enc.permissions.copy ? "allowed" : "not allowed"}
3150
3560
  }
3151
3561
  }
3152
3562
  const stampStream = iso88591ToBytes(stampStreamStr);
3153
- const dstSize = targetPage.getSize();
3154
- const srcSize = srcStampPage.getSize();
3155
- const rotDiff = ((targetPage.getRotation() - srcStampPage.getRotation()) % 360 + 360) % 360;
3156
- let stampOpenStr = "q\n";
3157
- if (srcSize.width > 0 && srcSize.height > 0) {
3158
- const fmt6 = (n) => Number(n.toFixed(6));
3159
- const fmt4 = (n) => Number(n.toFixed(4));
3160
- if (rotDiff === 90) {
3161
- const s = Math.min(dstSize.height / srcSize.width, dstSize.width / srcSize.height);
3162
- const ox = (dstSize.height - srcSize.width * s) / 2;
3163
- const oy = (dstSize.width - srcSize.height * s) / 2;
3164
- stampOpenStr = `q
3165
- 0 ${fmt6(s)} ${fmt6(-s)} 0 ${fmt4(dstSize.width - oy)} ${fmt4(ox)} cm
3166
- `;
3167
- } else if (rotDiff === 180) {
3168
- const s = Math.min(dstSize.width / srcSize.width, dstSize.height / srcSize.height);
3169
- const ox = (dstSize.width - srcSize.width * s) / 2;
3170
- const oy = (dstSize.height - srcSize.height * s) / 2;
3171
- stampOpenStr = `q
3172
- ${fmt6(-s)} 0 0 ${fmt6(-s)} ${fmt4(dstSize.width - ox)} ${fmt4(dstSize.height - oy)} cm
3173
- `;
3174
- } else if (rotDiff === 270) {
3175
- const s = Math.min(dstSize.height / srcSize.width, dstSize.width / srcSize.height);
3176
- const ox = (dstSize.height - srcSize.width * s) / 2;
3177
- const oy = (dstSize.width - srcSize.height * s) / 2;
3178
- stampOpenStr = `q
3179
- 0 ${fmt6(-s)} ${fmt6(s)} 0 ${fmt4(oy)} ${fmt4(dstSize.height - ox)} cm
3180
- `;
3181
- } else if (Math.abs(srcSize.width - dstSize.width) > 0.5 || Math.abs(srcSize.height - dstSize.height) > 0.5) {
3182
- const s = Math.min(dstSize.width / srcSize.width, dstSize.height / srcSize.height);
3183
- const tx = (dstSize.width - srcSize.width * s) / 2;
3184
- const ty = (dstSize.height - srcSize.height * s) / 2;
3185
- stampOpenStr = `q
3186
- ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3187
- `;
3188
- }
3189
- }
3563
+ const stampOpenStr = retainedStampTransform(
3564
+ { mediaBox: targetPage.getMediaBox(), rotation: targetPage.getRotation() },
3565
+ { mediaBox: srcStampPage.getMediaBox(), rotation: srcStampPage.getRotation() }
3566
+ );
3190
3567
  const qOpenTarget = enc.encode("q\n");
3191
3568
  const qOpenStamp = enc.encode(stampOpenStr);
3192
3569
  const qClose = enc.encode("\nQ\n");
@@ -3235,10 +3612,10 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3235
3612
  const [p0x, p0y] = mapPt(nums[0], nums[1]);
3236
3613
  const [p1x, p1y] = mapPt(nums[2], nums[3]);
3237
3614
  return cosArray3([
3238
- cosNumber3(Math.min(p0x, p1x)),
3239
- cosNumber3(Math.min(p0y, p1y)),
3240
- cosNumber3(Math.max(p0x, p1x)),
3241
- cosNumber3(Math.max(p0y, p1y))
3615
+ cosNumber4(Math.min(p0x, p1x)),
3616
+ cosNumber4(Math.min(p0y, p1y)),
3617
+ cosNumber4(Math.max(p0x, p1x)),
3618
+ cosNumber4(Math.max(p0y, p1y))
3242
3619
  ]);
3243
3620
  };
3244
3621
  let cmCmd = "";
@@ -3246,7 +3623,7 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3246
3623
  cmCmd = `q
3247
3624
  0 -1 1 0 0 ${W} cm
3248
3625
  `;
3249
- dictSet(page.dict, "MediaBox", cosArray3([cosNumber3(0), cosNumber3(0), cosNumber3(H), cosNumber3(W)]));
3626
+ dictSet(page.dict, "MediaBox", cosArray3([cosNumber4(0), cosNumber4(0), cosNumber4(H), cosNumber4(W)]));
3250
3627
  } else if (rot === 180) {
3251
3628
  cmCmd = `q
3252
3629
  -1 0 0 -1 ${W} ${H} cm
@@ -3255,18 +3632,18 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3255
3632
  cmCmd = `q
3256
3633
  0 1 -1 0 ${H} 0 cm
3257
3634
  `;
3258
- dictSet(page.dict, "MediaBox", cosArray3([cosNumber3(0), cosNumber3(0), cosNumber3(H), cosNumber3(W)]));
3635
+ dictSet(page.dict, "MediaBox", cosArray3([cosNumber4(0), cosNumber4(0), cosNumber4(H), cosNumber4(W)]));
3259
3636
  }
3260
3637
  for (const boxKey of ["CropBox", "BleedBox", "TrimBox", "ArtBox"]) {
3261
- const mapped = mapRectNode(dictGet7(page.dict, boxKey));
3638
+ const mapped = mapRectNode(dictGet8(page.dict, boxKey));
3262
3639
  if (mapped) dictSet(page.dict, boxKey, mapped);
3263
3640
  }
3264
- const annotsArr = asArray(workingDoc.cos.resolve(dictGet7(page.dict, "Annots")));
3641
+ const annotsArr = asArray(workingDoc.cos.resolve(dictGet8(page.dict, "Annots")));
3265
3642
  if (annotsArr) {
3266
3643
  for (const item of annotsArr.items) {
3267
3644
  const aDict = asDict(workingDoc.cos.resolve(item));
3268
3645
  if (!aDict) continue;
3269
- const mappedRect = mapRectNode(dictGet7(aDict, "Rect"));
3646
+ const mappedRect = mapRectNode(dictGet8(aDict, "Rect"));
3270
3647
  if (mappedRect) dictSet(aDict, "Rect", mappedRect);
3271
3648
  }
3272
3649
  }
@@ -3286,19 +3663,19 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3286
3663
  const enc = new TextEncoder();
3287
3664
  for (const page of workingDoc.getPages()) {
3288
3665
  yield;
3289
- const annotsNode = workingDoc.cos.resolve(dictGet7(page.dict, "Annots"));
3666
+ const annotsNode = workingDoc.cos.resolve(dictGet8(page.dict, "Annots"));
3290
3667
  const annotsArr = asArray(annotsNode);
3291
3668
  if (!annotsArr) continue;
3292
3669
  const survivingAnnots = [];
3293
3670
  for (const item of annotsArr.items) {
3294
3671
  const annotDict = asDict(workingDoc.cos.resolve(item));
3295
3672
  if (!annotDict) continue;
3296
- const subtype = asName(dictGet7(annotDict, "Subtype"))?.decoded;
3673
+ const subtype = asName(dictGet8(annotDict, "Subtype"))?.decoded;
3297
3674
  if (subtype === "Link" || subtype === "Popup") {
3298
3675
  survivingAnnots.push(item);
3299
3676
  continue;
3300
3677
  }
3301
- const flagsNode = asNumber(workingDoc.cos.resolve(dictGet7(annotDict, "F")));
3678
+ const flagsNode = asNumber(workingDoc.cos.resolve(dictGet8(annotDict, "F")));
3302
3679
  if (flagsNode !== void 0) {
3303
3680
  const flags = flagsNode.value;
3304
3681
  const isInvisibleOrHidden = (flags & 1) !== 0 || (flags & 2) !== 0;
@@ -3310,26 +3687,26 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3310
3687
  continue;
3311
3688
  }
3312
3689
  }
3313
- const rectArr = asArray(workingDoc.cos.resolve(dictGet7(annotDict, "Rect")));
3690
+ const rectArr = asArray(workingDoc.cos.resolve(dictGet8(annotDict, "Rect")));
3314
3691
  const rx0 = asNumber(workingDoc.cos.resolve(rectArr?.items[0]))?.value ?? 72;
3315
3692
  const ry0 = asNumber(workingDoc.cos.resolve(rectArr?.items[1]))?.value ?? 72;
3316
3693
  const rx1 = asNumber(workingDoc.cos.resolve(rectArr?.items[2]))?.value ?? rx0 + 50;
3317
3694
  const ry1 = asNumber(workingDoc.cos.resolve(rectArr?.items[3]))?.value ?? ry0 + 20;
3318
- const apDict = asDict(workingDoc.cos.resolve(dictGet7(annotDict, "AP")));
3319
- let apStreamNode = apDict ? workingDoc.cos.resolve(dictGet7(apDict, "N")) : void 0;
3695
+ const apDict = asDict(workingDoc.cos.resolve(dictGet8(annotDict, "AP")));
3696
+ let apStreamNode = apDict ? workingDoc.cos.resolve(dictGet8(apDict, "N")) : void 0;
3320
3697
  if (apStreamNode?.kind === "dict") {
3321
- const asState = asName(workingDoc.cos.resolve(dictGet7(annotDict, "AS")))?.decoded;
3322
- apStreamNode = (asState ? workingDoc.cos.resolve(dictGet7(apStreamNode, asState)) : void 0) ?? workingDoc.cos.resolve(apStreamNode.entries[0]?.value);
3698
+ const asState = asName(workingDoc.cos.resolve(dictGet8(annotDict, "AS")))?.decoded;
3699
+ apStreamNode = (asState ? workingDoc.cos.resolve(dictGet8(apStreamNode, asState)) : void 0) ?? workingDoc.cos.resolve(apStreamNode.entries[0]?.value);
3323
3700
  }
3324
3701
  if (apStreamNode?.kind === "stream") {
3325
3702
  let apBytes = workingDoc.cos.decodeStream(apStreamNode);
3326
3703
  if (apBytes.byteLength > 0) {
3327
- const bboxArr = asArray(workingDoc.cos.resolve(dictGet7(apStreamNode.dict, "BBox")));
3704
+ const bboxArr = asArray(workingDoc.cos.resolve(dictGet8(apStreamNode.dict, "BBox")));
3328
3705
  const bx0 = asNumber(workingDoc.cos.resolve(bboxArr?.items[0]))?.value ?? 0;
3329
3706
  const by0 = asNumber(workingDoc.cos.resolve(bboxArr?.items[1]))?.value ?? 0;
3330
3707
  const bx1 = asNumber(workingDoc.cos.resolve(bboxArr?.items[2]))?.value ?? Math.max(1, rx1 - rx0);
3331
3708
  const by1 = asNumber(workingDoc.cos.resolve(bboxArr?.items[3]))?.value ?? Math.max(1, ry1 - ry0);
3332
- const matArr = asArray(workingDoc.cos.resolve(dictGet7(apStreamNode.dict, "Matrix")));
3709
+ const matArr = asArray(workingDoc.cos.resolve(dictGet8(apStreamNode.dict, "Matrix")));
3333
3710
  let ma = 1, mb = 0, mc = 0, md = 1, me = 0, mf = 0;
3334
3711
  let hasFormMatrix = false;
3335
3712
  if (matArr && matArr.items.length >= 6) {
@@ -3358,27 +3735,27 @@ ${fmt6(s)} 0 0 ${fmt6(s)} ${fmt4(tx)} ${fmt4(ty)} cm
3358
3735
  const scaleY = (ry1 - ry0) / bh;
3359
3736
  const tx = rx0 - tbx0 * scaleX;
3360
3737
  const ty = ry0 - tby0 * scaleY;
3361
- const apRes = asDict(workingDoc.cos.resolve(dictGet7(apStreamNode.dict, "Resources")));
3738
+ const apRes = asDict(workingDoc.cos.resolve(dictGet8(apStreamNode.dict, "Resources")));
3362
3739
  const resRenames = /* @__PURE__ */ new Map();
3363
3740
  if (apRes) {
3364
3741
  const pageRes = page.getResourcesDict();
3365
3742
  for (const catKey of ["Font", "XObject", "ExtGState", "ColorSpace", "Pattern", "Shading", "Properties"]) {
3366
- const srcSub = asDict(workingDoc.cos.resolve(dictGet7(apRes, catKey)));
3743
+ const srcSub = asDict(workingDoc.cos.resolve(dictGet8(apRes, catKey)));
3367
3744
  if (!srcSub) continue;
3368
- let dstSub = asDict(workingDoc.cos.resolve(dictGet7(pageRes, catKey)));
3745
+ let dstSub = asDict(workingDoc.cos.resolve(dictGet8(pageRes, catKey)));
3369
3746
  if (!dstSub) {
3370
- dstSub = cosDict5({});
3747
+ dstSub = cosDict6({});
3371
3748
  dictSet(pageRes, catKey, dstSub);
3372
3749
  }
3373
3750
  for (const entry of srcSub.entries) {
3374
3751
  const origName = entry.key.decoded;
3375
- const existingEntry = dictGet7(dstSub, origName);
3752
+ const existingEntry = dictGet8(dstSub, origName);
3376
3753
  if (!existingEntry) {
3377
3754
  dictSet(dstSub, origName, entry.value);
3378
3755
  } else if (existingEntry.kind === "ref" && entry.value.kind === "ref" && existingEntry.objectNumber === entry.value.objectNumber) {
3379
3756
  } else {
3380
3757
  let suffix2 = 1;
3381
- while (dictGet7(dstSub, `${origName}_qap${suffix2}`)) suffix2++;
3758
+ while (dictGet8(dstSub, `${origName}_qap${suffix2}`)) suffix2++;
3382
3759
  const newName = `${origName}_qap${suffix2}`;
3383
3760
  dictSet(dstSub, newName, entry.value);
3384
3761
  resRenames.set(origName, newName);
@@ -3410,14 +3787,14 @@ Q
3410
3787
  continue;
3411
3788
  }
3412
3789
  }
3413
- const contentsNode = dictGet7(annotDict, "Contents");
3414
- let text = contentsNode?.kind === "string" ? decodePdfString6(contentsNode).trim() : "";
3790
+ const contentsNode = dictGet8(annotDict, "Contents");
3791
+ let text = contentsNode?.kind === "string" ? decodePdfString7(contentsNode).trim() : "";
3415
3792
  if (!text) {
3416
- const parentDict = asDict(workingDoc.cos.resolve(dictGet7(annotDict, "Parent")));
3417
- const vNode = workingDoc.cos.resolve(dictGet7(annotDict, "V")) ?? (parentDict ? workingDoc.cos.resolve(dictGet7(parentDict, "V")) : void 0);
3418
- const asStateNode = workingDoc.cos.resolve(dictGet7(annotDict, "AS"));
3793
+ const parentDict = asDict(workingDoc.cos.resolve(dictGet8(annotDict, "Parent")));
3794
+ const vNode = workingDoc.cos.resolve(dictGet8(annotDict, "V")) ?? (parentDict ? workingDoc.cos.resolve(dictGet8(parentDict, "V")) : void 0);
3795
+ const asStateNode = workingDoc.cos.resolve(dictGet8(annotDict, "AS"));
3419
3796
  if (vNode?.kind === "string") {
3420
- text = decodePdfString6(vNode).trim();
3797
+ text = decodePdfString7(vNode).trim();
3421
3798
  } else if (vNode?.kind === "name" && vNode.decoded !== "Off") {
3422
3799
  text = vNode.decoded;
3423
3800
  } else if (asStateNode?.kind === "name" && asStateNode.decoded !== "Off") {
@@ -3467,7 +3844,7 @@ Q
3467
3844
  yield;
3468
3845
  let modified = false;
3469
3846
  const pageRes = page.getResourcesDict();
3470
- let xobjSub = asDict(workingDoc.cos.resolve(dictGet7(pageRes, "XObject")));
3847
+ let xobjSub = asDict(workingDoc.cos.resolve(dictGet8(pageRes, "XObject")));
3471
3848
  const transformNodes = (nodes) => {
3472
3849
  const out = [];
3473
3850
  for (const node of nodes) {
@@ -3477,14 +3854,14 @@ Q
3477
3854
  out.push({ ...node, children: transformNodes(node.children) });
3478
3855
  } else if (node.kind === "inline-image" && node.data.byteLength >= iiMinBytes) {
3479
3856
  if (!xobjSub) {
3480
- xobjSub = cosDict5({});
3857
+ xobjSub = cosDict6({});
3481
3858
  dictSet(pageRes, "XObject", xobjSub);
3482
3859
  }
3483
3860
  let resName = `ImExt${extCounter++}`;
3484
- while (dictGet7(xobjSub, resName)) {
3861
+ while (dictGet8(xobjSub, resName)) {
3485
3862
  resName = `ImExt${extCounter++}`;
3486
3863
  }
3487
- const imgDict = cosDict5({
3864
+ const imgDict = cosDict6({
3488
3865
  Type: cosName2("XObject"),
3489
3866
  Subtype: cosName2("Image")
3490
3867
  });
@@ -3553,12 +3930,12 @@ Q
3553
3930
  yield;
3554
3931
  const refNames = /* @__PURE__ */ new Set();
3555
3932
  collectPdfNamesFromBytes(page.getRawContentStream(), refNames);
3556
- const annotsArr = asArray(workingDoc.cos.resolve(dictGet7(page.dict, "Annots")));
3933
+ const annotsArr = asArray(workingDoc.cos.resolve(dictGet8(page.dict, "Annots")));
3557
3934
  if (annotsArr) {
3558
3935
  for (const item of annotsArr.items) {
3559
3936
  const aDict = asDict(workingDoc.cos.resolve(item));
3560
- const apDict = aDict ? asDict(workingDoc.cos.resolve(dictGet7(aDict, "AP"))) : void 0;
3561
- const apN = apDict ? workingDoc.cos.resolve(dictGet7(apDict, "N")) : void 0;
3937
+ const apDict = aDict ? asDict(workingDoc.cos.resolve(dictGet8(aDict, "AP"))) : void 0;
3938
+ const apN = apDict ? workingDoc.cos.resolve(dictGet8(apDict, "N")) : void 0;
3562
3939
  if (apN?.kind === "stream") {
3563
3940
  collectPdfNamesFromBytes(workingDoc.cos.decodeStream(apN), refNames);
3564
3941
  }
@@ -3570,7 +3947,7 @@ Q
3570
3947
  while (changed) {
3571
3948
  changed = false;
3572
3949
  for (const catKey of ["XObject", "Pattern"]) {
3573
- const sub = asDict(workingDoc.cos.resolve(dictGet7(pageRes, catKey)));
3950
+ const sub = asDict(workingDoc.cos.resolve(dictGet8(pageRes, catKey)));
3574
3951
  if (!sub) continue;
3575
3952
  for (const entry of sub.entries) {
3576
3953
  const visitId = `${catKey}:${entry.key.decoded}`;
@@ -3587,7 +3964,7 @@ Q
3587
3964
  }
3588
3965
  }
3589
3966
  for (const catKey of resourceCategories) {
3590
- const sub = asDict(workingDoc.cos.resolve(dictGet7(pageRes, catKey)));
3967
+ const sub = asDict(workingDoc.cos.resolve(dictGet8(pageRes, catKey)));
3591
3968
  if (!sub) continue;
3592
3969
  for (const entry of [...sub.entries]) {
3593
3970
  if (!refNames.has(entry.key.decoded)) {
@@ -3601,33 +3978,40 @@ Q
3601
3978
  }
3602
3979
  }
3603
3980
  if (linearize) {
3604
- const linDict = cosDict5({
3605
- Linearized: cosNumber3(1),
3606
- N: cosNumber3(workingDoc.getPageCount())
3981
+ const linDict = cosDict6({
3982
+ Linearized: cosNumber4(1),
3983
+ N: cosNumber4(workingDoc.getPageCount())
3607
3984
  });
3608
3985
  workingDoc.cos.allocateObject(linDict);
3609
3986
  }
3610
- if (removePageLabels || pageLabelSpecs.length > 0) {
3987
+ let pageLabels;
3988
+ try {
3989
+ pageLabels = parseQpdfPageLabels(pageLabelSpecs, workingDoc.getPageCount());
3990
+ } catch (error) {
3991
+ return { exitCode: 2, stdout: "", stderr: `qpdf: ${error.message}
3992
+ ` };
3993
+ }
3994
+ if (removePageLabels || pageLabels.length > 0) {
3611
3995
  const rootDict = asDict(workingDoc.cos.resolve(workingDoc.cos.rootRef));
3612
3996
  if (rootDict) {
3613
- if (removePageLabels && pageLabelSpecs.length === 0) {
3997
+ if (removePageLabels && pageLabels.length === 0) {
3614
3998
  dictDelete(rootDict, "PageLabels");
3615
- } else if (pageLabelSpecs.length > 0) {
3999
+ } else if (pageLabels.length > 0) {
3616
4000
  const numsItems = [];
3617
- for (const { index, style, start, prefix } of parseQpdfPageLabels(pageLabelSpecs)) {
3618
- const labelDict = cosDict5({});
4001
+ for (const { index, style, start, prefix } of pageLabels) {
4002
+ const labelDict = cosDict6({});
3619
4003
  if (style !== void 0) {
3620
4004
  dictSet(labelDict, "S", cosName2(style));
3621
4005
  }
3622
4006
  if (start !== void 0 && start !== 1) {
3623
- dictSet(labelDict, "St", cosNumber3(start));
4007
+ dictSet(labelDict, "St", cosNumber4(start));
3624
4008
  }
3625
4009
  if (prefix) {
3626
4010
  dictSet(labelDict, "P", cosString3(prefix));
3627
4011
  }
3628
- numsItems.push(cosNumber3(index), labelDict);
4012
+ numsItems.push(cosNumber4(index), labelDict);
3629
4013
  }
3630
- dictSet(rootDict, "PageLabels", workingDoc.cos.allocateObject(cosDict5({ Nums: cosArray3(numsItems) })));
4014
+ dictSet(rootDict, "PageLabels", workingDoc.cos.allocateObject(cosDict6({ Nums: cosArray3(numsItems) })));
3631
4015
  }
3632
4016
  }
3633
4017
  }
@@ -3640,7 +4024,7 @@ Q
3640
4024
  if (rootDict) dictDelete(rootDict, "Metadata");
3641
4025
  const infoDict = workingDoc.cos.infoRef ? asDict(workingDoc.cos.resolve(workingDoc.cos.infoRef)) : void 0;
3642
4026
  if (infoDict) {
3643
- const modDate = dictGet7(infoDict, "ModDate");
4027
+ const modDate = dictGet8(infoDict, "ModDate");
3644
4028
  infoDict.entries.length = 0;
3645
4029
  if (modDate) {
3646
4030
  dictSet(infoDict, "ModDate", modDate);
@@ -3669,14 +4053,14 @@ Q
3669
4053
  }
3670
4054
  const srcDoc = yield* PdfDocument.loadSteps(srcBytes, copySpec.password ? { password: copySpec.password } : {});
3671
4055
  const srcRoot = asDict(srcDoc.cos.resolve(srcDoc.cos.rootRef));
3672
- const srcNames = srcRoot ? asDict(srcDoc.cos.resolve(dictGet7(srcRoot, "Names"))) : void 0;
3673
- const srcEf = srcNames ? asDict(srcDoc.cos.resolve(dictGet7(srcNames, "EmbeddedFiles"))) : void 0;
4056
+ const srcNames = srcRoot ? asDict(srcDoc.cos.resolve(dictGet8(srcRoot, "Names"))) : void 0;
4057
+ const srcEf = srcNames ? asDict(srcDoc.cos.resolve(dictGet8(srcNames, "EmbeddedFiles"))) : void 0;
3674
4058
  const flatPairs = [];
3675
4059
  const collectSrcEf = (nodeDict, visited = /* @__PURE__ */ new Set()) => {
3676
4060
  if (!nodeDict) return;
3677
- const nArr = asArray(srcDoc.cos.resolve(dictGet7(nodeDict, "Names")));
4061
+ const nArr = asArray(srcDoc.cos.resolve(dictGet8(nodeDict, "Names")));
3678
4062
  if (nArr) flatPairs.push(...nArr.items);
3679
- const kArr = asArray(srcDoc.cos.resolve(dictGet7(nodeDict, "Kids")));
4063
+ const kArr = asArray(srcDoc.cos.resolve(dictGet8(nodeDict, "Kids")));
3680
4064
  if (kArr) {
3681
4065
  for (const kid of kArr.items) {
3682
4066
  if (kid.kind === "ref") {
@@ -3692,56 +4076,56 @@ Q
3692
4076
  if (srcNamesArr) {
3693
4077
  for (let idx = 0; idx + 1 < srcNamesArr.items.length; idx += 2) {
3694
4078
  const kNode = srcDoc.cos.resolve(srcNamesArr.items[idx]);
3695
- const origKey = kNode?.kind === "string" ? decodePdfString6(kNode) : `att_${idx}`;
4079
+ const origKey = kNode?.kind === "string" ? decodePdfString7(kNode) : `att_${idx}`;
3696
4080
  const fsDict = asDict(srcDoc.cos.resolve(srcNamesArr.items[idx + 1]));
3697
4081
  if (!fsDict) continue;
3698
- const ufNode = srcDoc.cos.resolve(dictGet7(fsDict, "UF") ?? dictGet7(fsDict, "F"));
3699
- const fnStr = ufNode?.kind === "string" ? decodePdfString6(ufNode) : origKey;
3700
- const efDict = asDict(srcDoc.cos.resolve(dictGet7(fsDict, "EF")));
4082
+ const ufNode = srcDoc.cos.resolve(dictGet8(fsDict, "UF") ?? dictGet8(fsDict, "F"));
4083
+ const fnStr = ufNode?.kind === "string" ? decodePdfString7(ufNode) : origKey;
4084
+ const efDict = asDict(srcDoc.cos.resolve(dictGet8(fsDict, "EF")));
3701
4085
  const stNode = efDict ? srcDoc.cos.resolve(
3702
- dictGet7(efDict, "UF") ?? dictGet7(efDict, "F") ?? dictGet7(efDict, "DOS") ?? dictGet7(efDict, "Mac") ?? dictGet7(efDict, "Unix")
4086
+ dictGet8(efDict, "UF") ?? dictGet8(efDict, "F") ?? dictGet8(efDict, "DOS") ?? dictGet8(efDict, "Mac") ?? dictGet8(efDict, "Unix")
3703
4087
  ) : void 0;
3704
4088
  if (stNode?.kind === "stream") {
3705
4089
  const data = srcDoc.cos.decodeStream(stNode);
3706
4090
  const newKey = `${copySpec.prefix}${origKey}`;
3707
4091
  const rootDict = asDict(workingDoc.cos.resolve(workingDoc.cos.rootRef));
3708
4092
  if (rootDict) {
3709
- let namesDict = asDict(workingDoc.cos.resolve(dictGet7(rootDict, "Names")));
4093
+ let namesDict = asDict(workingDoc.cos.resolve(dictGet8(rootDict, "Names")));
3710
4094
  if (!namesDict) {
3711
- namesDict = cosDict5({});
4095
+ namesDict = cosDict6({});
3712
4096
  dictSet(rootDict, "Names", workingDoc.cos.allocateObject(namesDict));
3713
4097
  }
3714
- let efTree = asDict(workingDoc.cos.resolve(dictGet7(namesDict, "EmbeddedFiles")));
4098
+ let efTree = asDict(workingDoc.cos.resolve(dictGet8(namesDict, "EmbeddedFiles")));
3715
4099
  if (!efTree) {
3716
- efTree = cosDict5({ Names: cosArray3([]) });
4100
+ efTree = cosDict6({ Names: cosArray3([]) });
3717
4101
  dictSet(namesDict, "EmbeddedFiles", workingDoc.cos.allocateObject(efTree));
3718
4102
  }
3719
- let namesArr = asArray(workingDoc.cos.resolve(dictGet7(efTree, "Names")));
4103
+ let namesArr = asArray(workingDoc.cos.resolve(dictGet8(efTree, "Names")));
3720
4104
  if (!namesArr) {
3721
4105
  namesArr = cosArray3([]);
3722
4106
  dictSet(efTree, "Names", namesArr);
3723
4107
  }
3724
4108
  for (let j = 0; j + 1 < namesArr.items.length; j += 2) {
3725
4109
  const exNode = workingDoc.cos.resolve(namesArr.items[j]);
3726
- if (exNode?.kind === "string" && decodePdfString6(exNode) === newKey) {
4110
+ if (exNode?.kind === "string" && decodePdfString7(exNode) === newKey) {
3727
4111
  return { exitCode: 2, stdout: "", stderr: `qpdf: duplicate attachment key ${newKey}
3728
4112
  ` };
3729
4113
  }
3730
4114
  }
3731
4115
  const efStreamRef = workingDoc.cos.allocateObject(
3732
4116
  cosStream(data, {
3733
- dict: cosDict5({
4117
+ dict: cosDict6({
3734
4118
  Type: cosName2("EmbeddedFile"),
3735
- Params: cosDict5({ Size: cosNumber3(data.byteLength) })
4119
+ Params: cosDict6({ Size: cosNumber4(data.byteLength) })
3736
4120
  })
3737
4121
  })
3738
4122
  );
3739
4123
  const fsRef = workingDoc.cos.allocateObject(
3740
- cosDict5({
4124
+ cosDict6({
3741
4125
  Type: cosName2("Filespec"),
3742
4126
  F: cosString3(fnStr),
3743
4127
  UF: cosString3(fnStr),
3744
- EF: cosDict5({ F: efStreamRef, UF: efStreamRef })
4128
+ EF: cosDict6({ F: efStreamRef, UF: efStreamRef })
3745
4129
  })
3746
4130
  );
3747
4131
  namesArr.items.push(cosString3(newKey), fsRef);
@@ -3753,22 +4137,22 @@ Q
3753
4137
  if (removeAttachmentKeys.length > 0 || addAttachmentSpecs.length > 0) {
3754
4138
  const rootDict = asDict(workingDoc.cos.resolve(workingDoc.cos.rootRef));
3755
4139
  if (rootDict) {
3756
- let namesDict = asDict(workingDoc.cos.resolve(dictGet7(rootDict, "Names")));
4140
+ let namesDict = asDict(workingDoc.cos.resolve(dictGet8(rootDict, "Names")));
3757
4141
  if (!namesDict) {
3758
- namesDict = cosDict5({});
4142
+ namesDict = cosDict6({});
3759
4143
  dictSet(rootDict, "Names", workingDoc.cos.allocateObject(namesDict));
3760
4144
  }
3761
- let efTree = asDict(workingDoc.cos.resolve(dictGet7(namesDict, "EmbeddedFiles")));
4145
+ let efTree = asDict(workingDoc.cos.resolve(dictGet8(namesDict, "EmbeddedFiles")));
3762
4146
  if (!efTree) {
3763
- efTree = cosDict5({ Names: cosArray3([]) });
4147
+ efTree = cosDict6({ Names: cosArray3([]) });
3764
4148
  dictSet(namesDict, "EmbeddedFiles", workingDoc.cos.allocateObject(efTree));
3765
4149
  }
3766
4150
  const flattenedEfPairs = [];
3767
4151
  const flattenEfTree = (nodeDict, visited = /* @__PURE__ */ new Set()) => {
3768
4152
  if (!nodeDict) return;
3769
- const nArr = asArray(workingDoc.cos.resolve(dictGet7(nodeDict, "Names")));
4153
+ const nArr = asArray(workingDoc.cos.resolve(dictGet8(nodeDict, "Names")));
3770
4154
  if (nArr) flattenedEfPairs.push(...nArr.items);
3771
- const kArr = asArray(workingDoc.cos.resolve(dictGet7(nodeDict, "Kids")));
4155
+ const kArr = asArray(workingDoc.cos.resolve(dictGet8(nodeDict, "Kids")));
3772
4156
  if (kArr) {
3773
4157
  for (const kid of kArr.items) {
3774
4158
  if (kid.kind === "ref") {
@@ -3784,17 +4168,20 @@ Q
3784
4168
  let namesArr = cosArray3(flattenedEfPairs);
3785
4169
  dictSet(efTree, "Names", namesArr);
3786
4170
  if (removeAttachmentKeys.length > 0) {
3787
- const remSet = new Set(removeAttachmentKeys);
4171
+ const remSet = new Set(removeAttachmentKeys), missing = new Set(removeAttachmentKeys);
3788
4172
  const kept = [];
3789
4173
  for (let k = 0; k + 1 < namesArr.items.length; k += 2) {
3790
4174
  const keyNode = workingDoc.cos.resolve(namesArr.items[k]);
3791
- const keyStr = keyNode?.kind === "string" ? decodePdfString6(keyNode) : "";
4175
+ const keyStr = keyNode?.kind === "string" ? decodePdfString7(keyNode) : "";
4176
+ missing.delete(keyStr);
3792
4177
  if (!remSet.has(keyStr)) {
3793
4178
  kept.push(namesArr.items[k], namesArr.items[k + 1]);
3794
4179
  }
3795
4180
  }
4181
+ if (missing.size) return { exitCode: 2, stdout: "", stderr: `qpdf: attachment ${missing.values().next().value} not found
4182
+ ` };
3796
4183
  dictSet(efTree, "Names", cosArray3(kept));
3797
- namesArr = asArray(dictGet7(efTree, "Names"));
4184
+ namesArr = asArray(dictGet8(efTree, "Names"));
3798
4185
  }
3799
4186
  for (const addSpec of addAttachmentSpecs) {
3800
4187
  yield;
@@ -3806,7 +4193,7 @@ Q
3806
4193
  let existingIdx = -1;
3807
4194
  for (let idx = 0; idx + 1 < namesArr.items.length; idx += 2) {
3808
4195
  const kNode = workingDoc.cos.resolve(namesArr.items[idx]);
3809
- if (kNode?.kind === "string" && decodePdfString6(kNode) === addSpec.key) {
4196
+ if (kNode?.kind === "string" && decodePdfString7(kNode) === addSpec.key) {
3810
4197
  existingIdx = idx;
3811
4198
  break;
3812
4199
  }
@@ -3817,17 +4204,17 @@ Q
3817
4204
  }
3818
4205
  const efStreamRef = workingDoc.cos.allocateObject(
3819
4206
  cosStream(attBytes, {
3820
- dict: cosDict5({
4207
+ dict: cosDict6({
3821
4208
  Type: cosName2("EmbeddedFile"),
3822
- Params: cosDict5({ Size: cosNumber3(attBytes.byteLength) })
4209
+ Params: cosDict6({ Size: cosNumber4(attBytes.byteLength) })
3823
4210
  })
3824
4211
  })
3825
4212
  );
3826
- const fsDict = cosDict5({
4213
+ const fsDict = cosDict6({
3827
4214
  Type: cosName2("Filespec"),
3828
4215
  F: cosString3(addSpec.filename),
3829
4216
  UF: cosString3(addSpec.filename),
3830
- EF: cosDict5({ F: efStreamRef, UF: efStreamRef })
4217
+ EF: cosDict6({ F: efStreamRef, UF: efStreamRef })
3831
4218
  });
3832
4219
  if (addSpec.description) {
3833
4220
  dictSet(fsDict, "Desc", cosString3(addSpec.description));
@@ -3878,16 +4265,7 @@ Q
3878
4265
  });
3879
4266
  if (encryptConfig && !decrypt) {
3880
4267
  const parsedOut = yield* parseCosDocumentSteps(outBytes);
3881
- outBytes = yield* encryptCosDocumentSteps(parsedOut, {
3882
- userPassword: encryptConfig.userPassword,
3883
- ownerPassword: encryptConfig.ownerPassword,
3884
- permissions: {
3885
- print: encryptConfig.print,
3886
- modify: encryptConfig.modify,
3887
- copy: encryptConfig.copy,
3888
- addNotes: encryptConfig.addNotes
3889
- }
3890
- });
4268
+ outBytes = yield* encryptCosDocumentSteps(parsedOut, encryptConfig);
3891
4269
  } else if (decrypt && workingDoc.cos.encryption) {
3892
4270
  workingDoc.cos.encryptRef = void 0;
3893
4271
  workingDoc.cos.encryption = void 0;
@@ -3932,7 +4310,7 @@ async function qpdf(context, options = {}) {
3932
4310
  const path = token.slice(1);
3933
4311
  let source;
3934
4312
  try {
3935
- source = await PdfFileSource3.open(context.fs, resolvePath2(context.cwd, path), { signal: invocation.signal, chunkBytes: 16384 });
4313
+ source = await PdfFileSource4.open(context.fs, resolvePath2(context.cwd, path), { signal: invocation.signal, chunkBytes: 16384 });
3936
4314
  } catch {
3937
4315
  invocation.signal.throwIfAborted();
3938
4316
  await writeBytes2(context.stderr, encoder.encode(`qpdf: cannot open ${path}
@@ -4103,6 +4481,11 @@ async function qpdf(context, options = {}) {
4103
4481
  }
4104
4482
  }
4105
4483
  return { exitCode: res.exitCode };
4484
+ } catch (error) {
4485
+ if (!(error instanceof QpdfPageRangeError)) throw error;
4486
+ await writeBytes2(context.stderr, new TextEncoder().encode(`qpdf: ${error.message}
4487
+ `), invocation.signal);
4488
+ return { exitCode: 2 };
4106
4489
  } finally {
4107
4490
  await invocation.close();
4108
4491
  }