@embedpdf/engine-core 3.0.0-next.13 → 3.0.0-next.15

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 (135) hide show
  1. package/dist/AnnotationList-CkijRHet.d.cts +27163 -0
  2. package/dist/AnnotationList-CkijRHet.d.ts +27163 -0
  3. package/dist/Change-CKoxfTNY.js +889 -0
  4. package/dist/Change-CKoxfTNY.js.map +1 -0
  5. package/dist/Change-CjImsrN0.cjs +1020 -0
  6. package/dist/Change-CjImsrN0.cjs.map +1 -0
  7. package/dist/DocumentRenderService-BfFdFSwI.cjs +120 -0
  8. package/dist/DocumentRenderService-BfFdFSwI.cjs.map +1 -0
  9. package/dist/DocumentRenderService-DV3J9lMz.js +85 -0
  10. package/dist/DocumentRenderService-DV3J9lMz.js.map +1 -0
  11. package/dist/{DocumentSecurityService-_JRjqvoI.d.ts → DocumentSecurityService-CU1M6Q7m.d.ts} +100 -112
  12. package/dist/{DocumentSecurityService-ClTZc0zO.d.cts → DocumentSecurityService-e6vp2de8.d.cts} +100 -112
  13. package/dist/LocalDocumentHandle-D02FMm-Z.cjs +53 -0
  14. package/dist/LocalDocumentHandle-D02FMm-Z.cjs.map +1 -0
  15. package/dist/LocalDocumentHandle-DAPdaOt_.js +30 -0
  16. package/dist/LocalDocumentHandle-DAPdaOt_.js.map +1 -0
  17. package/dist/LocalEngine-DFoBm4B3.d.cts +1565 -0
  18. package/dist/LocalEngine-DPriFujS.d.ts +1565 -0
  19. package/dist/PageInsertBlankInput-CXHAM7l6.cjs +17 -0
  20. package/dist/PageInsertBlankInput-CXHAM7l6.cjs.map +1 -0
  21. package/dist/PageInsertBlankInput-DhVYVJ_S.js +12 -0
  22. package/dist/PageInsertBlankInput-DhVYVJ_S.js.map +1 -0
  23. package/dist/authority-B3BklsB4.js +2239 -0
  24. package/dist/authority-B3BklsB4.js.map +1 -0
  25. package/dist/authority-DLMNR7ff.cjs +2676 -0
  26. package/dist/authority-DLMNR7ff.cjs.map +1 -0
  27. package/dist/builders-CNj0POeY.js +161 -0
  28. package/dist/builders-CNj0POeY.js.map +1 -0
  29. package/dist/builders-Cgdp_YCn.cjs +184 -0
  30. package/dist/builders-Cgdp_YCn.cjs.map +1 -0
  31. package/dist/calibrate-7T3fnP8-.js +2404 -0
  32. package/dist/calibrate-7T3fnP8-.js.map +1 -0
  33. package/dist/calibrate-DJBXnhLE.cjs +2919 -0
  34. package/dist/calibrate-DJBXnhLE.cjs.map +1 -0
  35. package/dist/charmap-Cprti1ta.js +239 -0
  36. package/dist/charmap-Cprti1ta.js.map +1 -0
  37. package/dist/charmap-aYlefQP9.cjs +304 -0
  38. package/dist/charmap-aYlefQP9.cjs.map +1 -0
  39. package/dist/conformance.cjs +16753 -5247
  40. package/dist/conformance.cjs.map +1 -1
  41. package/dist/conformance.d.cts +928 -165
  42. package/dist/conformance.d.ts +928 -165
  43. package/dist/conformance.js +16731 -5268
  44. package/dist/conformance.js.map +1 -1
  45. package/dist/format-Bp3QFSH1.cjs +574 -0
  46. package/dist/format-Bp3QFSH1.cjs.map +1 -0
  47. package/dist/format-Dgg428Kb.js +461 -0
  48. package/dist/format-Dgg428Kb.js.map +1 -0
  49. package/dist/index.cjs +330 -103
  50. package/dist/index.d.cts +2 -2
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.js +14 -5
  53. package/dist/pageRefs-B3ur7Ckm.cjs +51 -0
  54. package/dist/pageRefs-B3ur7Ckm.cjs.map +1 -0
  55. package/dist/pageRefs-DkbuxSCV.js +40 -0
  56. package/dist/pageRefs-DkbuxSCV.js.map +1 -0
  57. package/dist/public.cjs +57 -0
  58. package/dist/public.d.cts +5 -0
  59. package/dist/public.d.ts +5 -0
  60. package/dist/public.js +9 -0
  61. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs +3646 -0
  62. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs.map +1 -0
  63. package/dist/resolveAnnotationDraft-CUpf0SMh.js +3167 -0
  64. package/dist/resolveAnnotationDraft-CUpf0SMh.js.map +1 -0
  65. package/dist/{resolver-inAVzsWt.js → resolver-CzxmD9Ck.js} +294 -144
  66. package/dist/resolver-CzxmD9Ck.js.map +1 -0
  67. package/dist/{resolver-CYVNV4hn.cjs → resolver-D2OCtE8F.cjs} +347 -143
  68. package/dist/resolver-D2OCtE8F.cjs.map +1 -0
  69. package/dist/runtime-Cl0v56mm.d.ts +2032 -0
  70. package/dist/runtime-p1WDzDo4.d.cts +2032 -0
  71. package/dist/runtime.cjs +733 -284
  72. package/dist/runtime.cjs.map +1 -1
  73. package/dist/runtime.d.cts +5 -1348
  74. package/dist/runtime.d.ts +5 -1348
  75. package/dist/runtime.js +396 -179
  76. package/dist/runtime.js.map +1 -1
  77. package/dist/schema-C-gU4vj7.cjs +3034 -0
  78. package/dist/schema-C-gU4vj7.cjs.map +1 -0
  79. package/dist/schema-DzlUNk38.js +2081 -0
  80. package/dist/schema-DzlUNk38.js.map +1 -0
  81. package/dist/schemas-Berf1swF.cjs +2481 -0
  82. package/dist/schemas-Berf1swF.cjs.map +1 -0
  83. package/dist/schemas-D6xOGjqg.js +1654 -0
  84. package/dist/schemas-D6xOGjqg.js.map +1 -0
  85. package/dist/subtype-CG4WpIRj.js +129 -0
  86. package/dist/subtype-CG4WpIRj.js.map +1 -0
  87. package/dist/subtype-aDt5TFmK.cjs +158 -0
  88. package/dist/subtype-aDt5TFmK.cjs.map +1 -0
  89. package/dist/validation.cjs +578 -0
  90. package/dist/validation.cjs.map +1 -0
  91. package/dist/validation.d.cts +174 -0
  92. package/dist/validation.d.ts +174 -0
  93. package/dist/validation.js +571 -0
  94. package/dist/validation.js.map +1 -0
  95. package/dist/value-BFS1R8ek.cjs +435 -0
  96. package/dist/value-BFS1R8ek.cjs.map +1 -0
  97. package/dist/value-Dhc8eYcx.js +340 -0
  98. package/dist/value-Dhc8eYcx.js.map +1 -0
  99. package/dist/viewport-BJW13EGZ.js +540 -0
  100. package/dist/viewport-BJW13EGZ.js.map +1 -0
  101. package/dist/viewport-DlL5Bxbg.cjs +671 -0
  102. package/dist/viewport-DlL5Bxbg.cjs.map +1 -0
  103. package/dist/wire.cjs +1056 -379
  104. package/dist/wire.cjs.map +1 -1
  105. package/dist/wire.d.cts +15069 -725
  106. package/dist/wire.d.ts +15069 -725
  107. package/dist/wire.js +825 -239
  108. package/dist/wire.js.map +1 -1
  109. package/package.json +10 -1
  110. package/dist/AbortError-Cq2igpZ6.cjs +0 -38
  111. package/dist/AbortError-Cq2igpZ6.cjs.map +0 -1
  112. package/dist/AbortError-DHqPBnYc.js +0 -27
  113. package/dist/AbortError-DHqPBnYc.js.map +0 -1
  114. package/dist/AnnotationListSnapshot-D3mFut1e.d.cts +0 -6871
  115. package/dist/AnnotationListSnapshot-D3mFut1e.d.ts +0 -6871
  116. package/dist/Engine-Bx0NOfmd.d.cts +0 -1264
  117. package/dist/Engine-DxtpCYcP.d.ts +0 -1264
  118. package/dist/PageInsertBlankInput-CqXBmFhN.cjs +0 -449
  119. package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +0 -1
  120. package/dist/PageInsertBlankInput-cHq9-i-z.js +0 -378
  121. package/dist/PageInsertBlankInput-cHq9-i-z.js.map +0 -1
  122. package/dist/builders-Bqm8yTpi.cjs +0 -3564
  123. package/dist/builders-Bqm8yTpi.cjs.map +0 -1
  124. package/dist/builders-Dqo2X-dt.js +0 -3121
  125. package/dist/builders-Dqo2X-dt.js.map +0 -1
  126. package/dist/fold-2V29cxCV.cjs +0 -447
  127. package/dist/fold-2V29cxCV.cjs.map +0 -1
  128. package/dist/fold-CdEJTBTn.js +0 -334
  129. package/dist/fold-CdEJTBTn.js.map +0 -1
  130. package/dist/resolver-CYVNV4hn.cjs.map +0 -1
  131. package/dist/resolver-inAVzsWt.js.map +0 -1
  132. package/dist/schemas-BLARNCgx.js +0 -3113
  133. package/dist/schemas-BLARNCgx.js.map +0 -1
  134. package/dist/schemas-DIqFMmrC.cjs +0 -4468
  135. package/dist/schemas-DIqFMmrC.cjs.map +0 -1
@@ -0,0 +1,2239 @@
1
+ import { g as writesScripts } from "./value-Dhc8eYcx.js";
2
+ import { gt as normalizePdfRect, nt as kindFactsOf, wt as pdfRectTurnedBounds, xt as pdfRectIntersection } from "./resolveAnnotationDraft-CUpf0SMh.js";
3
+ import { h as EngineError, m as annotationKey, u as encodePageKey, v as _defineProperty, y as EngineErrorCode } from "./format-Dgg428Kb.js";
4
+ import { Et as pdfRectOf, J as pageDestinationOf, K as mapPageActions, M as pageAnnotationOf, Q as applyResolvedPatch, W as mapFieldActions, Y as pdfDestinationOf, c as foldText, ht as resolveBinarySource, q as mapTriggerActions, xt as pageBoxOf } from "./calibrate-7T3fnP8-.js";
5
+ import { C as lockCovers, E as protectedCapabilities, S as levelFromPermission, T as minLevel, _ as deriveProtection, a as checkFieldAction, i as checkCollab, j as PermissionDenied, n as checkAnyFieldAction, o as checkSetGroup, r as checkCapability, s as collabTargetOf, v as describeProtection, x as levelAllows } from "./resolver-CzxmD9Ck.js";
6
+ import { S as AbortablePromise, d as matchLiteral, s as matchRegex, u as foldOptionsFor } from "./viewport-BJW13EGZ.js";
7
+ //#region src/geometry/pageBoxes.ts
8
+ /** The media box of a page that has none, which the file needs: US Letter. */
9
+ const DEFAULT_MEDIA_BOX = {
10
+ left: 0,
11
+ bottom: 0,
12
+ right: 612,
13
+ top: 792
14
+ };
15
+ /** A box that shares nothing with the media box: no size, at the origin. */
16
+ const EMPTY_BOX = {
17
+ left: 0,
18
+ right: 0,
19
+ bottom: 0,
20
+ top: 0
21
+ };
22
+ /**
23
+ * The page's five boxes as ISO 32000-1 §14.11.2 defines them: the crop box
24
+ * defaults to the media box and the other three to the crop box, and each is
25
+ * reduced to the part it shares with the media box. The crop box is then the
26
+ * visible page, what the page shows.
27
+ *
28
+ * For broken files, which the standard doesn't cover, we do what Acrobat
29
+ * does: a page without a media box is US Letter, and a box that shares
30
+ * nothing with the media box is empty, so a crop box off the page leaves a
31
+ * page with no size.
32
+ */
33
+ function pageBoxesOf(written) {
34
+ const media = written.media ?? DEFAULT_MEDIA_BOX;
35
+ const within = (box) => pdfRectIntersection(box, media) ?? EMPTY_BOX;
36
+ const crop = within(written.crop ?? media);
37
+ return {
38
+ media,
39
+ crop,
40
+ bleed: within(written.bleed ?? crop),
41
+ trim: within(written.trim ?? crop),
42
+ art: within(written.art ?? crop)
43
+ };
44
+ }
45
+ /**
46
+ * A page's turn from its `/Rotate`, degrees clockwise. ISO 32000-1 Table 30
47
+ * allows multiples of 90; a negative one or one past 360 is the same turn.
48
+ * For a value that isn't a multiple of 90, which the file shouldn't hold, we
49
+ * do what Acrobat does: no turn.
50
+ */
51
+ function pageRotationOf(written) {
52
+ if (!Number.isInteger(written) || written % 90 !== 0) return 0;
53
+ return (written % 360 + 360) % 360;
54
+ }
55
+ //#endregion
56
+ //#region src/dto/PageRender.ts
57
+ /**
58
+ * What a page picture draws: the options as passed, and for each one left
59
+ * out, what the caller may read. A left-out option never asks for more than
60
+ * the caller may see; `true` for something they may not read is refused
61
+ * (`PermissionDenied`, naming the option).
62
+ */
63
+ function resolvePageLayers(options, may) {
64
+ const { includeAnnotations, includeFormFields } = options ?? {};
65
+ if (includeAnnotations === true && !may.annotations) throw new PermissionDenied("doc.annotate.read", "includeAnnotations");
66
+ if (includeFormFields === true && !may.formFields) throw new PermissionDenied("doc.forms.read", "includeFormFields");
67
+ return {
68
+ includeAnnotations: includeAnnotations ?? may.annotations,
69
+ includeFormFields: includeFormFields ?? (includeAnnotations === false ? false : may.formFields)
70
+ };
71
+ }
72
+ /** `InvalidArg` unless `quality` is absent or between 0 and 1. */
73
+ function checkImageQuality(quality) {
74
+ if (quality === void 0 || quality >= 0 && quality <= 1) return;
75
+ throw new EngineError(EngineErrorCode.InvalidArg, "quality must be between 0 and 1", { details: { field: "quality" } });
76
+ }
77
+ function createPageImageHandle(result, blobSource) {
78
+ return {
79
+ ...result,
80
+ ...imageSourceOf(blobSource)
81
+ };
82
+ }
83
+ /**
84
+ * An image whose encoded bytes this device holds (a picture it made, such as
85
+ * a stamp's preview), to paint like one an engine rendered.
86
+ */
87
+ function imageSourceOfBytes(bytes, contentType) {
88
+ return imageSourceOf({ blob: async () => new Blob([bytes.slice()], { type: contentType }) });
89
+ }
90
+ /** The image `blobSource` gives, as a blob and as an object URL. */
91
+ function imageSourceOf(blobSource) {
92
+ return {
93
+ blob() {
94
+ return AbortablePromise.run(async (signal) => {
95
+ if (typeof Blob === "undefined") throw new EngineError(EngineErrorCode.RuntimeUnavailable, "Blob is not available in this environment");
96
+ const blob = await blobSource.blob(signal);
97
+ if (signal.aborted) throw signal.reason;
98
+ return blob;
99
+ });
100
+ },
101
+ objectUrl() {
102
+ return AbortablePromise.run(async (signal) => {
103
+ if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new EngineError(EngineErrorCode.RuntimeUnavailable, "Object URLs are not available in this environment");
104
+ const blob = await blobSource.blob(signal);
105
+ const url = URL.createObjectURL(blob);
106
+ if (signal.aborted) {
107
+ URL.revokeObjectURL(url);
108
+ throw signal.reason;
109
+ }
110
+ return {
111
+ url,
112
+ revoke: () => URL.revokeObjectURL(url)
113
+ };
114
+ });
115
+ }
116
+ };
117
+ }
118
+ //#endregion
119
+ //#region src/dto/PdfSaveMode.ts
120
+ const DEFAULT_PDF_SAVE_MODE = "incremental";
121
+ //#endregion
122
+ //#region src/identity/uuid.ts
123
+ /**
124
+ * Generate an RFC 4122 v4 UUID using the Web Crypto API surface available in
125
+ * every realm this code runs in: Node 19+, Web Workers, browsers on https,
126
+ * http, intranet and file: pages, and edge runtimes.
127
+ *
128
+ * Why this implementation:
129
+ * - `crypto.randomUUID()` is limited to secure contexts in some browsers;
130
+ * `getRandomValues()` has no such restriction.
131
+ * - `import { randomUUID } from 'crypto'` makes Vite externalize the
132
+ * `crypto` module for browser builds and fail at runtime.
133
+ *
134
+ * Output is the canonical hex form: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`,
135
+ * where `y` is one of `8/9/a/b` (variant nibble = 10xx).
136
+ */
137
+ function generateUuid() {
138
+ const bytes = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
139
+ bytes[6] = bytes[6] & 15 | 64;
140
+ bytes[8] = bytes[8] & 63 | 128;
141
+ return formatUuid(bytes);
142
+ }
143
+ /**
144
+ * Generate an RFC 9562 v7 UUID: the Unix time in milliseconds in the first
145
+ * 48 bits, then random bits. Names made later sort later, which is what an
146
+ * annotation's `/NM` gets when the data names none.
147
+ */
148
+ function generateUuidV7(now = Date.now()) {
149
+ const bytes = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
150
+ let time = Math.max(0, Math.floor(now));
151
+ for (let i = 5; i >= 0; i--) {
152
+ bytes[i] = time % 256;
153
+ time = Math.floor(time / 256);
154
+ }
155
+ bytes[6] = bytes[6] & 15 | 112;
156
+ bytes[8] = bytes[8] & 63 | 128;
157
+ return formatUuid(bytes);
158
+ }
159
+ function formatUuid(bytes) {
160
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
161
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
162
+ }
163
+ //#endregion
164
+ //#region src/annotation/family.ts
165
+ /** The family an annotation of `subtype` belongs to. */
166
+ function familyOfSubtype(subtype) {
167
+ return subtype === "widget" ? "widgets" : "annotations";
168
+ }
169
+ //#endregion
170
+ //#region src/dto/normalizeAttachmentFileSource.ts
171
+ /**
172
+ * Resolve an attachment `file` into its wire halves: metadata into the
173
+ * JSON body, bytes into the resource map. Unlike stamps there is no
174
+ * format allowlist — attaching arbitrary files is the point — so the
175
+ * declared mime type wins, then the Blob's; attachment formats cannot be
176
+ * reliably sniffed, so without either the file has no type.
177
+ * Used by the document-level `attachments.create`.
178
+ */
179
+ async function normalizeAttachmentFileSource(file, key) {
180
+ const resolved = await resolveBinarySource(file.data instanceof ArrayBuffer ? new Uint8Array(file.data) : file.data);
181
+ const name = file.name ?? resolved.name;
182
+ if (!name) throw new EngineError(EngineErrorCode.InvalidArg, "attachment file requires a name — pass { data, name } or a File whose name is set");
183
+ const mimeType = file.mimeType ?? resolved.mimeType;
184
+ return {
185
+ wireFile: {
186
+ resources: { file: key },
187
+ name,
188
+ ...mimeType !== void 0 ? { mimeType } : {},
189
+ ...file.description !== void 0 ? { description: file.description } : {}
190
+ },
191
+ resource: {
192
+ bytes: resolved.bytes,
193
+ ...mimeType !== void 0 ? { mimeType } : {},
194
+ name
195
+ }
196
+ };
197
+ }
198
+ //#endregion
199
+ //#region src/transfer/annotationImport.ts
200
+ /**
201
+ * The facts an import committed: one `annotations.created` per annotation, in
202
+ * bundle order, each with the envelope of its own page and its own id. The
203
+ * cache pins move once, with the last.
204
+ */
205
+ function annotationImportFacts(result) {
206
+ const last = result.annotations.length - 1;
207
+ return result.annotations.map((annotation, index) => {
208
+ const { page } = annotation.ref;
209
+ return {
210
+ page,
211
+ annotation,
212
+ meta: {
213
+ affectedPages: result.meta.affectedPages.filter((affected) => affected.objectNumber === page.objectNumber),
214
+ cacheDelta: index === last ? result.meta.cacheDelta : null,
215
+ opId: result.meta.opId,
216
+ undoable: result.meta.undoable,
217
+ changed: [annotation.ref]
218
+ }
219
+ };
220
+ });
221
+ }
222
+ //#endregion
223
+ //#region src/transfer/exportSelection.ts
224
+ /**
225
+ * The annotations an export takes, in document order: page order, then each
226
+ * page's annotation order. Besides the selection:
227
+ *
228
+ * - a reply brings its parent, and the parent's, up to the root;
229
+ * - an annotation brings its popup, and a popup brings its parent;
230
+ * - with `include: 'threads'`, the default, every reply below a selected
231
+ * annotation.
232
+ *
233
+ * A reply is on its parent's page (ISO 32000-2 §12.5.6.2, `/IRT`) and a popup
234
+ * on its parent's, so none of this reaches another page. `readPage` returns a
235
+ * page's annotations in `/Annots` order and is called once for each page
236
+ * the selection is on. `pages` is the document's pages in order. A selected
237
+ * page or annotation the document doesn't have is refused with `NotFound`.
238
+ */
239
+ function closeExportSelection(selection, pages, readPage) {
240
+ const inDocument = new Set(pages.map(encodePageKey));
241
+ const read = /* @__PURE__ */ new Map();
242
+ const byKey = /* @__PURE__ */ new Map();
243
+ const readOnce = (page) => {
244
+ const pageKey = encodePageKey(page);
245
+ if (read.has(pageKey) || !inDocument.has(pageKey)) return;
246
+ const annotations = readPage(page);
247
+ read.set(pageKey, annotations);
248
+ for (const annotation of annotations) byKey.set(annotationKey(annotation.ref), annotation);
249
+ };
250
+ const find = (ref) => {
251
+ readOnce(ref.page);
252
+ return byKey.get(annotationKey(ref));
253
+ };
254
+ for (const page of selection.pages ?? []) if (!inDocument.has(encodePageKey(page))) throw new EngineError(EngineErrorCode.NotFound, `export: the document has no page ${encodePageKey(page)}`);
255
+ const everything = !selection.refs && !selection.pages;
256
+ const threads = (selection.include ?? "threads") === "threads";
257
+ for (const page of everything ? pages : selection.pages ?? []) readOnce(page);
258
+ const taken = /* @__PURE__ */ new Set();
259
+ const pending = [];
260
+ const take = (annotation) => {
261
+ if (!annotation) return;
262
+ const key = annotationKey(annotation.ref);
263
+ if (taken.has(key)) return;
264
+ taken.add(key);
265
+ pending.push(annotation);
266
+ };
267
+ if (everything) for (const annotations of read.values()) annotations.forEach(take);
268
+ else {
269
+ for (const page of selection.pages ?? []) read.get(encodePageKey(page))?.forEach(take);
270
+ for (const ref of selection.refs ?? []) {
271
+ const annotation = find(ref);
272
+ if (!annotation) throw new EngineError(EngineErrorCode.NotFound, `export: the document has no annotation ${annotationKey(ref)}`);
273
+ take(annotation);
274
+ }
275
+ }
276
+ if (threads) {
277
+ for (const annotation of [...pending]) if (annotation.subtype === "popup" && annotation.parent) take(find(annotation.parent));
278
+ const replies = /* @__PURE__ */ new Map();
279
+ for (const annotations of read.values()) for (const annotation of annotations) {
280
+ const parent = annotation.reply ? find(annotation.reply.to) : void 0;
281
+ if (!parent) continue;
282
+ const key = annotationKey(parent.ref);
283
+ replies.set(key, [...replies.get(key) ?? [], annotation]);
284
+ }
285
+ for (let i = 0; i < pending.length; i++) replies.get(annotationKey(pending[i].ref))?.forEach(take);
286
+ }
287
+ for (let i = 0; i < pending.length; i++) {
288
+ const annotation = pending[i];
289
+ if (annotation.reply) take(find(annotation.reply.to));
290
+ if (annotation.popup) take(find(annotation.popup));
291
+ if (annotation.subtype === "popup" && annotation.parent) take(find(annotation.parent));
292
+ }
293
+ const ordered = [];
294
+ for (const page of pages) for (const annotation of read.get(encodePageKey(page)) ?? []) if (taken.has(annotationKey(annotation.ref))) ordered.push(annotation);
295
+ return ordered;
296
+ }
297
+ //#endregion
298
+ //#region src/annotation/AnnotationList.ts
299
+ /** Lists of different pages as one, in their order. */
300
+ function concatAnnotationLists(lists) {
301
+ return {
302
+ annotations: lists.flatMap((list) => list.annotations),
303
+ pages: lists.flatMap((list) => list.pages)
304
+ };
305
+ }
306
+ //#endregion
307
+ //#region src/mutation/AnnotationMutationResults.ts
308
+ /**
309
+ * What a delete removed, as its `annotations.deleted` event names it for
310
+ * listeners that didn't make the call: the refs in `meta.changed`, the
311
+ * annotation first.
312
+ */
313
+ function deletedAnnotationsOf(result) {
314
+ return result.meta.changed;
315
+ }
316
+ //#endregion
317
+ //#region src/annotation/resolve/patchBetween.ts
318
+ /** Exact deep equality: the same value, or arrays and objects whose members are. */
319
+ function sameValue(left, right) {
320
+ if (Object.is(left, right)) return true;
321
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
322
+ if (Array.isArray(left) !== Array.isArray(right)) return false;
323
+ const leftRecord = left;
324
+ const rightRecord = right;
325
+ const names = Object.keys(leftRecord).filter((name) => leftRecord[name] !== void 0);
326
+ const others = Object.keys(rightRecord).filter((name) => rightRecord[name] !== void 0);
327
+ return names.length === others.length && names.every((name) => sameValue(leftRecord[name], rightRecord[name]));
328
+ }
329
+ /**
330
+ * What `after` changed from `before`, as `update` takes it: each data field
331
+ * whose value differs, whole. Fields the engine works out, stamps or keeps as
332
+ * found (a drawn kind's `rect`, the dates and authors, the refs it keeps) are
333
+ * left out. Values are compared exactly, so a value rebuilt with the same
334
+ * content is no change. Works in either space: it only compares values by
335
+ * field name.
336
+ */
337
+ function annotationPatchBetween(before, after) {
338
+ const patch = {};
339
+ if (before === after) return patch;
340
+ const owners = kindFactsOf(after.subtype)?.owners ?? {};
341
+ const was = before;
342
+ const now = after;
343
+ for (const [name, owner] of Object.entries(owners)) {
344
+ if (owner !== "data" || sameValue(was[name], now[name])) continue;
345
+ patch[name] = now[name] === void 0 ? null : now[name];
346
+ }
347
+ return patch;
348
+ }
349
+ /**
350
+ * `current` with `patch`'s data fields set, each as a read spells it: the
351
+ * patch as given. No field follows from another here; `applyAnnotationPatch`
352
+ * adds what the engine's rules make follow. Works in either space.
353
+ */
354
+ function mergeAnnotationPatch(current, patch) {
355
+ return applyResolvedPatch(current, patch);
356
+ }
357
+ //#endregion
358
+ //#region src/pageSpace/pages.ts
359
+ /**
360
+ * A page's five boxes in page space: measured from the top-left of the crop
361
+ * box, the visible page, which is therefore `{ x: 0, y: 0, width, height }`.
362
+ */
363
+ function pageSpaceBoxesOf(boxes) {
364
+ const visible = boxes.crop;
365
+ return {
366
+ media: pageBoxOf(boxes.media, visible),
367
+ crop: pageBoxOf(boxes.crop, visible),
368
+ bleed: pageBoxOf(boxes.bleed, visible),
369
+ trim: pageBoxOf(boxes.trim, visible),
370
+ art: pageBoxOf(boxes.art, visible)
371
+ };
372
+ }
373
+ /**
374
+ * A page's layout in page space. Its actions may go to other pages, so each
375
+ * destination is measured with `boxOf`, the box of the page it goes to.
376
+ */
377
+ function pageLayoutOf(layout, boxOf) {
378
+ const { actions, ...rest } = layout;
379
+ return {
380
+ ...rest,
381
+ boxes: pageSpaceBoxesOf(layout.boxes),
382
+ ...actions ? { actions: mapPageActions(actions, (destination) => pageDestinationOf(destination, boxOf)) } : {}
383
+ };
384
+ }
385
+ /** Every page's visible box, by page, from the pages' layouts. */
386
+ function visibleBoxesOf(pages) {
387
+ const boxes = new Map(pages.map((page) => [page.ref.objectNumber, page.boxes.crop]));
388
+ return (page) => {
389
+ const box = boxes.get(page.objectNumber);
390
+ if (!box) throw new EngineError(EngineErrorCode.InvalidArg, `No page ${page.objectNumber} in this document`);
391
+ return box;
392
+ };
393
+ }
394
+ /** The document's pages in page space. */
395
+ function pageListOf(snapshot) {
396
+ const boxOf = visibleBoxesOf(snapshot.pages);
397
+ return {
398
+ ...snapshot,
399
+ pages: snapshot.pages.map((page) => pageLayoutOf(page, boxOf))
400
+ };
401
+ }
402
+ //#endregion
403
+ //#region src/pageSpace/rendering.ts
404
+ /** A page-space render target in the file's coordinates. `visible` is the page's visible box. */
405
+ function pdfRenderTargetOf(target, visible) {
406
+ return target.kind === "rect" ? {
407
+ ...target,
408
+ rect: pdfRectOf(target.rect, visible)
409
+ } : target;
410
+ }
411
+ /**
412
+ * Where each rendered appearance goes, in page space: raster or encoded, each
413
+ * placed by its `rect`. `visible` is the page's visible box.
414
+ */
415
+ function pageAppearancesOf(appearances, visible) {
416
+ return appearances.map((appearance) => ({
417
+ ...appearance,
418
+ rect: pageBoxOf(appearance.rect, visible)
419
+ }));
420
+ }
421
+ //#endregion
422
+ //#region src/pageSpace/forms.ts
423
+ /** A page-space widget placement in the file's coordinates, measured on its page. */
424
+ function pdfWidgetPlacementOf(placement, boxOf) {
425
+ const { actions, ...rest } = placement;
426
+ const visible = boxOf(placement.page);
427
+ return {
428
+ ...rest,
429
+ ...placement.rect ? { rect: pdfRectOf(placement.rect, visible) } : {},
430
+ ...placement.box ? { box: pdfRectOf(placement.box, visible) } : {},
431
+ ...actions ? { actions: mapTriggerActions(actions, (destination) => pdfDestinationOf(destination, boxOf)) } : {}
432
+ };
433
+ }
434
+ /** A field draft in the file's coordinates: each widget measured on its own page. */
435
+ function pdfFormFieldDraftOf(draft, boxOf) {
436
+ if (!draft.widgets) return draft;
437
+ return {
438
+ ...draft,
439
+ widgets: draft.widgets.map((widget) => pdfWidgetPlacementOf(widget, boxOf))
440
+ };
441
+ }
442
+ /** A field's actions in page space, each destination measured on its page. */
443
+ function pageFieldActionsOf(actions, boxOf) {
444
+ return mapFieldActions(actions, (destination) => pageDestinationOf(destination, boxOf));
445
+ }
446
+ /** A form field in page space: each destination of its actions on the page it goes to. */
447
+ function pageFormFieldOf(field, boxOf) {
448
+ if (!field.actions) return field;
449
+ return {
450
+ ...field,
451
+ actions: pageFieldActionsOf(field.actions, boxOf)
452
+ };
453
+ }
454
+ /** A widget row in page space, measured on its page. */
455
+ function pageWidgetOf(widget, boxOf) {
456
+ return pageAnnotationOf(widget, boxOf(widget.page), boxOf);
457
+ }
458
+ /** A form in page space: its fields, and each widget row on its page. */
459
+ function pageFormSnapshotOf(snapshot, boxOf) {
460
+ return {
461
+ ...snapshot,
462
+ fields: snapshot.fields.map((field) => pageFormFieldOf(field, boxOf)),
463
+ widgets: snapshot.widgets.map((widget) => pageWidgetOf(widget, boxOf))
464
+ };
465
+ }
466
+ //#endregion
467
+ //#region src/forms/draft.ts
468
+ /**
469
+ * Whether creating a field from `draft` takes `doc.forms.script`: a script
470
+ * among its actions, or a widget action that holds JavaScript, a submit or a
471
+ * link.
472
+ */
473
+ function draftWritesScripts(draft) {
474
+ return writesScripts(draft.actions) || (draft.widgets ?? []).some((placement) => writesScripts(placement.actions));
475
+ }
476
+ //#endregion
477
+ //#region src/forms/placement.ts
478
+ /**
479
+ * A placement as the file holds it: `/Rect`, where the widget stands, and
480
+ * its turn. A `box` given is turned into the rect it stands in, as a box
481
+ * kind's create turns it; without one, `rect` is that place.
482
+ *
483
+ * Refused with `InvalidArg`: a placement with neither, and a turn that
484
+ * isn't a quarter turn (a widget's contents turn only so, `/MK /R`).
485
+ */
486
+ function placedWidgetOf(placement) {
487
+ const { box, rect, rotation, ...rest } = placement;
488
+ if (rotation !== void 0 && ![
489
+ 0,
490
+ 90,
491
+ 180,
492
+ 270
493
+ ].includes(rotation)) throw new EngineError(EngineErrorCode.InvalidArg, `a widget turns by a quarter turn (0, 90, 180 or 270), not ${rotation}`, { details: { field: "rotation" } });
494
+ const place = box ? pdfRectTurnedBounds(normalizePdfRect(box), rotation ?? 0) : rect;
495
+ if (!place) throw new EngineError(EngineErrorCode.InvalidArg, "a widget's placement needs its `rect` or its `box`", { details: { field: "rect" } });
496
+ return {
497
+ ...rest,
498
+ rect: place,
499
+ ...rotation !== void 0 ? { rotation } : {}
500
+ };
501
+ }
502
+ //#endregion
503
+ //#region src/signature/analysis/pdf-value.ts
504
+ /**
505
+ * Parse the fork's canonical shallow serialisation:
506
+ * dict <</Key value/Key2 value>> (keys sorted, `#xx`-escaped)
507
+ * array [item item]
508
+ * ref n 0 R
509
+ * stream stream(length,sha256hex)<<dict>>
510
+ * name /Name (with #xx escapes)
511
+ * string (literal with backslash escapes) or <hex>
512
+ * number -12.5 bool true|false null
513
+ */
514
+ function parsePdfValue(text) {
515
+ const parser = new Parser(text);
516
+ const value = parser.value();
517
+ parser.skipSpaces();
518
+ if (!parser.atEnd()) throw new Error(`trailing input at ${parser.pos}: ${text.slice(parser.pos, parser.pos + 20)}`);
519
+ return value;
520
+ }
521
+ var Parser = class {
522
+ constructor(text) {
523
+ this.text = text;
524
+ _defineProperty(this, "pos", 0);
525
+ }
526
+ atEnd() {
527
+ return this.pos >= this.text.length;
528
+ }
529
+ skipSpaces() {
530
+ while (this.pos < this.text.length && this.text[this.pos] === " ") this.pos++;
531
+ }
532
+ value() {
533
+ this.skipSpaces();
534
+ const t = this.text;
535
+ if (t.startsWith("<<", this.pos)) return this.dict();
536
+ if (t.startsWith("stream(", this.pos)) return this.stream();
537
+ if (t[this.pos] === "[") return this.array();
538
+ if (t[this.pos] === "/") return {
539
+ t: "name",
540
+ v: this.name()
541
+ };
542
+ if (t[this.pos] === "(") return {
543
+ t: "string",
544
+ v: this.literalString()
545
+ };
546
+ if (t[this.pos] === "<") return {
547
+ t: "string",
548
+ v: this.hexString()
549
+ };
550
+ if (t.startsWith("true", this.pos)) {
551
+ this.pos += 4;
552
+ return {
553
+ t: "bool",
554
+ v: true
555
+ };
556
+ }
557
+ if (t.startsWith("false", this.pos)) {
558
+ this.pos += 5;
559
+ return {
560
+ t: "bool",
561
+ v: false
562
+ };
563
+ }
564
+ if (t.startsWith("null", this.pos)) {
565
+ this.pos += 4;
566
+ return { t: "null" };
567
+ }
568
+ const ref = /^(\d+) 0 R(?![0-9A-Za-z])/.exec(t.slice(this.pos));
569
+ if (ref) {
570
+ this.pos += ref[0].length;
571
+ return {
572
+ t: "ref",
573
+ num: Number(ref[1])
574
+ };
575
+ }
576
+ const num = /^[+-]?(?:\d+\.?\d*|\.\d+)/.exec(t.slice(this.pos));
577
+ if (num) {
578
+ this.pos += num[0].length;
579
+ return {
580
+ t: "number",
581
+ v: Number(num[0])
582
+ };
583
+ }
584
+ throw new Error(`unexpected input at ${this.pos}: ${t.slice(this.pos, this.pos + 20)}`);
585
+ }
586
+ dict() {
587
+ return {
588
+ t: "dict",
589
+ entries: this.dictEntries()
590
+ };
591
+ }
592
+ dictEntries() {
593
+ this.expect("<<");
594
+ const entries = {};
595
+ for (;;) {
596
+ this.skipSpaces();
597
+ if (this.text.startsWith(">>", this.pos)) {
598
+ this.pos += 2;
599
+ return entries;
600
+ }
601
+ if (this.text[this.pos] !== "/") throw new Error(`expected a key at ${this.pos}`);
602
+ const key = this.name();
603
+ this.skipSpaces();
604
+ entries[key] = this.value();
605
+ }
606
+ }
607
+ stream() {
608
+ const m = /^stream\((\d+),([0-9a-fA-F]*)\)/.exec(this.text.slice(this.pos));
609
+ if (!m) throw new Error(`malformed stream signature at ${this.pos}`);
610
+ this.pos += m[0].length;
611
+ const dict = this.dictEntries();
612
+ return {
613
+ t: "stream",
614
+ length: Number(m[1]),
615
+ sha256: m[2].toLowerCase(),
616
+ dict
617
+ };
618
+ }
619
+ array() {
620
+ this.expect("[");
621
+ const items = [];
622
+ for (;;) {
623
+ this.skipSpaces();
624
+ if (this.text[this.pos] === "]") {
625
+ this.pos++;
626
+ return {
627
+ t: "array",
628
+ items
629
+ };
630
+ }
631
+ items.push(this.value());
632
+ }
633
+ }
634
+ name() {
635
+ this.expect("/");
636
+ let out = "";
637
+ while (this.pos < this.text.length) {
638
+ const ch = this.text[this.pos];
639
+ if (ch === " " || ch === "/" || ch === "[" || ch === "]" || ch === "<" || ch === ">" || ch === "(" || ch === ")") break;
640
+ if (ch === "#" && /^[0-9A-Fa-f]{2}/.test(this.text.slice(this.pos + 1, this.pos + 3))) {
641
+ out += String.fromCharCode(parseInt(this.text.slice(this.pos + 1, this.pos + 3), 16));
642
+ this.pos += 3;
643
+ continue;
644
+ }
645
+ out += ch;
646
+ this.pos++;
647
+ }
648
+ return out;
649
+ }
650
+ literalString() {
651
+ this.expect("(");
652
+ let depth = 1;
653
+ let out = "";
654
+ while (this.pos < this.text.length) {
655
+ const ch = this.text[this.pos++];
656
+ if (ch === "\\") {
657
+ const next = this.text[this.pos++];
658
+ switch (next) {
659
+ case "n":
660
+ out += "\n";
661
+ break;
662
+ case "r":
663
+ out += "\r";
664
+ break;
665
+ case "t":
666
+ out += " ";
667
+ break;
668
+ case "b":
669
+ out += "\b";
670
+ break;
671
+ case "f":
672
+ out += "\f";
673
+ break;
674
+ case "\n": break;
675
+ case "\r":
676
+ if (this.text[this.pos] === "\n") this.pos++;
677
+ break;
678
+ default: {
679
+ const oct = /^[0-7]{1,3}/.exec(this.text.slice(this.pos - 1));
680
+ if (oct) {
681
+ out += String.fromCharCode(parseInt(oct[0], 8));
682
+ this.pos += oct[0].length - 1;
683
+ } else out += next;
684
+ }
685
+ }
686
+ continue;
687
+ }
688
+ if (ch === "(") depth++;
689
+ if (ch === ")") {
690
+ depth--;
691
+ if (depth === 0) return out;
692
+ }
693
+ out += ch;
694
+ }
695
+ throw new Error("unterminated string");
696
+ }
697
+ hexString() {
698
+ this.expect("<");
699
+ const end = this.text.indexOf(">", this.pos);
700
+ if (end < 0) throw new Error("unterminated hex string");
701
+ const hex = this.text.slice(this.pos, end).replace(/\s+/g, "");
702
+ this.pos = end + 1;
703
+ let out = "";
704
+ for (let i = 0; i < hex.length; i += 2) out += String.fromCharCode(parseInt(hex.slice(i, i + 2).padEnd(2, "0"), 16));
705
+ return out;
706
+ }
707
+ expect(token) {
708
+ if (!this.text.startsWith(token, this.pos)) throw new Error(`expected ${token} at ${this.pos}`);
709
+ this.pos += token.length;
710
+ }
711
+ };
712
+ /** Structural equality of two parsed values. */
713
+ function pdfValueEquals(a, b) {
714
+ return stableStringify(a) === stableStringify(b);
715
+ }
716
+ function stableStringify(value) {
717
+ if (value === null) return "null";
718
+ switch (value.t) {
719
+ case "dict": return `<<${Object.keys(value.entries).sort().map((k) => `/${k} ${stableStringify(value.entries[k])}`).join("")}>>`;
720
+ case "stream": return `stream(${value.length},${value.sha256})${stableStringify({
721
+ t: "dict",
722
+ entries: value.dict
723
+ })}`;
724
+ case "array": return `[${value.items.map(stableStringify).join(" ")}]`;
725
+ case "ref": return `${value.num} 0 R`;
726
+ case "name": return `/${encodeName(value.v)}`;
727
+ case "string": return `(${value.v.replace(/[\\()]/g, (ch) => `\\${ch}`)})`;
728
+ case "number": return String(value.v);
729
+ case "bool": return value.v ? "true" : "false";
730
+ case "null": return "null";
731
+ }
732
+ }
733
+ function dictEntries(value) {
734
+ if (!value) return null;
735
+ if (value.t === "dict") return value.entries;
736
+ if (value.t === "stream") return value.dict;
737
+ return null;
738
+ }
739
+ /** Keys whose value differs (added, removed, or changed) between two dictionaries. */
740
+ function changedKeys(a, b) {
741
+ const ea = dictEntries(a) ?? {};
742
+ const eb = dictEntries(b) ?? {};
743
+ const out = /* @__PURE__ */ new Set();
744
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(ea), ...Object.keys(eb)])) if (!pdfValueEquals(ea[key] ?? null, eb[key] ?? null)) out.add(key);
745
+ return out;
746
+ }
747
+ function refsOf(value) {
748
+ if (!value) return [];
749
+ if (value.t === "ref") return [value.num];
750
+ if (value.t === "array") return value.items.flatMap(refsOf);
751
+ return [];
752
+ }
753
+ /** `#xx`-escape a name the way the PDF writer does: delimiters, whitespace, `#`, and non-printables. */
754
+ function encodeName(name) {
755
+ let out = "";
756
+ for (const ch of name) {
757
+ const code = ch.charCodeAt(0);
758
+ if (code < 33 || code > 126 || "#/[]<>(){}%".includes(ch)) out += `#${code.toString(16).toUpperCase().padStart(2, "0")}`;
759
+ else out += ch;
760
+ }
761
+ return out;
762
+ }
763
+ //#endregion
764
+ //#region src/signature/analysis/verdict.ts
765
+ const RANK = {
766
+ unchanged: 0,
767
+ permitted: 1,
768
+ indeterminate: 2,
769
+ forbidden: 3
770
+ };
771
+ /** The worst of several verdicts (a certification replay's steps). */
772
+ function worstVerdict(steps) {
773
+ let worst = "unchanged";
774
+ for (const s of steps) if (RANK[s.verdict] > RANK[worst]) worst = s.verdict;
775
+ return worst;
776
+ }
777
+ /**
778
+ * One aggregation rule everywhere: a proven violation decides, whatever
779
+ * else could not be established; missing evidence never becomes a pass.
780
+ */
781
+ function conclude(findings, hasEffectiveChanges) {
782
+ if (findings.some((f) => f.verdict === "forbidden")) return "forbidden";
783
+ if (findings.some((f) => f.verdict === "incomplete")) return "indeterminate";
784
+ return hasEffectiveChanges ? "permitted" : "unchanged";
785
+ }
786
+ /** The finding that explains a verdict: the violation, else the gap, else the most notable allowance. */
787
+ function primaryFinding(findings) {
788
+ return findings.find((f) => f.verdict === "forbidden") ?? findings.find((f) => f.verdict === "incomplete") ?? findings.find((f) => f.verdict === "permitted");
789
+ }
790
+ function assessmentOf(step) {
791
+ const primary = primaryFinding(step.findings);
792
+ return {
793
+ verdict: step.verdict,
794
+ complete: !step.findings.some((f) => f.verdict === "incomplete"),
795
+ ...primary ? { primary } : {},
796
+ findings: step.findings
797
+ };
798
+ }
799
+ /**
800
+ * A certification window: the net verdict and every replayed step, under
801
+ * the same rule. A step's violation is a violation of the certification
802
+ * even when the final state no longer shows it (corpus v3/83).
803
+ */
804
+ function combine(net, steps) {
805
+ const findings = [...net.findings];
806
+ for (const s of steps) {
807
+ if (s === net) continue;
808
+ for (const f of s.findings) {
809
+ if (f.verdict === "permitted") continue;
810
+ findings.push({
811
+ ...f,
812
+ detail: `revision ${s.newer}: ${f.detail ?? f.rule}`
813
+ });
814
+ }
815
+ }
816
+ const verdict = conclude(findings, net.verdict !== "unchanged" || steps.some((s) => s.verdict !== "unchanged"));
817
+ const primary = primaryFinding(findings);
818
+ return {
819
+ verdict,
820
+ complete: !findings.some((f) => f.verdict === "incomplete"),
821
+ ...primary ? { primary } : {},
822
+ findings
823
+ };
824
+ }
825
+ //#endregion
826
+ //#region src/signature/analysis/evaluate.ts
827
+ const SIDES = ["old", "new"];
828
+ const TRAILER_KEYS = /* @__PURE__ */ new Set([
829
+ "Size",
830
+ "Prev",
831
+ "XRefStm",
832
+ "Info",
833
+ "ID",
834
+ "Root",
835
+ "Type",
836
+ "W",
837
+ "Index",
838
+ "Filter",
839
+ "DecodeParms",
840
+ "Length",
841
+ "Encrypt"
842
+ ]);
843
+ const CATALOG_KEYS = /* @__PURE__ */ new Set([
844
+ "Metadata",
845
+ "Extensions",
846
+ "Version",
847
+ "DSS",
848
+ "AcroForm",
849
+ "Perms"
850
+ ]);
851
+ const ACROFORM_KEYS = /* @__PURE__ */ new Set([
852
+ "DR",
853
+ "DA",
854
+ "Q",
855
+ "SigFlags",
856
+ "NeedAppearances",
857
+ "Fields"
858
+ ]);
859
+ /**
860
+ * What a form field may change after a signature, by the level in force.
861
+ * Every key here has a corpus case behind it (`signature-compat`); a key
862
+ * not listed is forbidden until one does.
863
+ *
864
+ * annotate (an approval signature: "Form Fill-in, Signing and Commenting
865
+ * are allowed"): everything but the field's identity and its place in
866
+ * the tree. v1/08 /V+/AP, 09 /Ff, 10 /Rect, 11 /F, 18 /TU, 21 /DA; 13 /T
867
+ * forbidden. /AA (v1/12) and /A are accepted by Acrobat and deliberately
868
+ * not listed: an action on a signed form is executable content.
869
+ *
870
+ * fill (a P=2 certification): value and appearance. v1/26 /V+/AP, v3/64
871
+ * /Ff (ReadOnly added), 65 /DA, 68 /AP stream; v3/66 /TU, 67 /F, v2/50
872
+ * /Rect forbidden.
873
+ */
874
+ const FIELD_KEYS_BY_LEVEL = {
875
+ annotate: /* @__PURE__ */ new Set([
876
+ "V",
877
+ "AS",
878
+ "AP",
879
+ "I",
880
+ "DA",
881
+ "Ff",
882
+ "RV",
883
+ "MK",
884
+ "Q",
885
+ "TU",
886
+ "F",
887
+ "Rect",
888
+ "BS",
889
+ "Opt",
890
+ "DV",
891
+ "MaxLen"
892
+ ]),
893
+ fill: /* @__PURE__ */ new Set([
894
+ "V",
895
+ "AS",
896
+ "AP",
897
+ "I",
898
+ "RV",
899
+ "DA",
900
+ "Ff"
901
+ ])
902
+ };
903
+ /** A separate widget dictionary: the presentation subset of the same tables. */
904
+ const WIDGET_KEYS_BY_LEVEL = {
905
+ annotate: /* @__PURE__ */ new Set([
906
+ "AS",
907
+ "AP",
908
+ "DA",
909
+ "MK",
910
+ "Q",
911
+ "TU",
912
+ "F",
913
+ "Rect",
914
+ "BS"
915
+ ]),
916
+ fill: /* @__PURE__ */ new Set([
917
+ "AS",
918
+ "AP",
919
+ "DA"
920
+ ])
921
+ };
922
+ /**
923
+ * Signing an existing field: its value, appearance and ReadOnly at any
924
+ * level; after an approval signature also its tooltip and a /Lock installed
925
+ * with the signature (v1/18, 19: both valid; under P=2 the combination is
926
+ * rejected, v3/69, so neither is allowed at `fill`).
927
+ */
928
+ const SIGNED_FIELD_KEYS_BY_LEVEL = {
929
+ annotate: /* @__PURE__ */ new Set([
930
+ "V",
931
+ "AP",
932
+ "AS",
933
+ "Ff",
934
+ "TU",
935
+ "Lock"
936
+ ]),
937
+ fill: /* @__PURE__ */ new Set([
938
+ "V",
939
+ "AP",
940
+ "AS",
941
+ "Ff"
942
+ ])
943
+ };
944
+ const SIGNED_WIDGET_KEYS = /* @__PURE__ */ new Set(["AP", "AS"]);
945
+ const FF_READ_ONLY = 1;
946
+ function fieldKeysFor(level) {
947
+ return level === "annotate" ? FIELD_KEYS_BY_LEVEL.annotate : FIELD_KEYS_BY_LEVEL.fill;
948
+ }
949
+ function widgetKeysFor(level) {
950
+ return level === "annotate" ? WIDGET_KEYS_BY_LEVEL.annotate : WIDGET_KEYS_BY_LEVEL.fill;
951
+ }
952
+ function signedFieldKeysFor(level) {
953
+ return level === "annotate" ? SIGNED_FIELD_KEYS_BY_LEVEL.annotate : SIGNED_FIELD_KEYS_BY_LEVEL.fill;
954
+ }
955
+ /**
956
+ * Rules whose permitted findings do not make a revision a change of the
957
+ * document: an object written again with the sealed value, an object nobody
958
+ * references, a cross-reference container, the trailer's own bookkeeping.
959
+ * A window with nothing else is `unchanged` (Acrobat: "not modified").
960
+ */
961
+ const NON_EFFECTIVE_RULES = /* @__PURE__ */ new Set([
962
+ "identical-rewrite",
963
+ "orphan",
964
+ "xref-container",
965
+ "trailer"
966
+ ]);
967
+ /** The level a step runs at, and the locks it enforces: what the older revision's signatures established. */
968
+ function restrictionsOf(before) {
969
+ const protection = deriveProtection(before.signatures);
970
+ return {
971
+ level: protection.judged ?? "annotate",
972
+ locks: protection.fieldLocks
973
+ };
974
+ }
975
+ /**
976
+ * Judge one pairwise step. Every reference to a changed object, in the
977
+ * older and the newer revision, must be claimed by a rule that inspected
978
+ * that use; an unclaimed reference is `unexplained` and forbidden. A
979
+ * truncated value is `incomplete`, never permitted. Pure: JSON in, JSON
980
+ * out, the same code in the browser worker, on the server and in tests.
981
+ */
982
+ function evaluateStep(input) {
983
+ const changes = withEvidenceCheck(input.changes);
984
+ const { level, locks } = input.restrictions ?? restrictionsOf(input.before);
985
+ const levelInForce = input.levelOverride ?? level;
986
+ const ctx = new StepContext({
987
+ ...input,
988
+ changes
989
+ }, levelInForce, locks);
990
+ ruleRevisionHealth(ctx);
991
+ ruleXrefContainer(ctx);
992
+ ruleIdenticalRewrite(ctx);
993
+ ruleFieldLock(ctx);
994
+ ruleTrailer(ctx);
995
+ ruleInfo(ctx);
996
+ ruleMetadata(ctx);
997
+ ruleCatalog(ctx);
998
+ ruleAcroForm(ctx);
999
+ ruleDss(ctx);
1000
+ ruleSignatureFieldAdded(ctx);
1001
+ ruleSignatureAdded(ctx);
1002
+ ruleFormFill(ctx);
1003
+ ruleAnnotation(ctx);
1004
+ ruleUnexplained(ctx);
1005
+ const verdict = conclude(ctx.findings, ctx.findings.some((f) => f.verdict === "permitted" && !NON_EFFECTIVE_RULES.has(f.rule)));
1006
+ return {
1007
+ older: input.older,
1008
+ newer: input.newer,
1009
+ levelInForce,
1010
+ locks,
1011
+ verdict,
1012
+ findings: ctx.findings,
1013
+ changes
1014
+ };
1015
+ }
1016
+ /**
1017
+ * The evaluator never trusts its transport to be honest about gaps: a side
1018
+ * that exists must carry a value, and an object whose value is missing is
1019
+ * judged as truncated, whatever the flag says. Cross-reference containers
1020
+ * are exempt (their members are judged on their own).
1021
+ */
1022
+ function withEvidenceCheck(changes) {
1023
+ return changes.map((c) => {
1024
+ if (c.value.truncated || c.kind === "xref" || c.kind === "objstm") return c;
1025
+ const missingOld = c.present.old && (c.value.old === null || c.read?.old === "failed");
1026
+ const missingNew = c.present.new && (c.value.new === null || c.read?.new === "failed");
1027
+ return missingOld || missingNew ? {
1028
+ ...c,
1029
+ value: {
1030
+ ...c.value,
1031
+ truncated: true
1032
+ }
1033
+ } : c;
1034
+ });
1035
+ }
1036
+ /**
1037
+ * The transport's verdict on each revision as a whole. A sealed revision
1038
+ * Acrobat cannot judge later changes to (sparse cross-reference table, a
1039
+ * reachable bare-reference object) makes every step over it indeterminate:
1040
+ * our verdict would otherwise disagree with the validator recipients use,
1041
+ * and for a reason that has nothing to do with the change. An incomplete
1042
+ * reachability walk on either side leaves every use unproven.
1043
+ */
1044
+ function ruleRevisionHealth(ctx) {
1045
+ const health = ctx.input.health;
1046
+ if (!health) return;
1047
+ const base = health.old;
1048
+ if (base.sparseXref || base.bareReferences > 0) {
1049
+ const why = [base.sparseXref ? "its cross-reference table has no entry for some object numbers below /Size" : null, base.bareReferences > 0 ? `${base.bareReferences} reachable indirect object(s) consist of a bare reference` : null].filter((x) => x !== null).join("; ");
1050
+ ctx.findings.push({
1051
+ rule: "base-unverifiable",
1052
+ verdict: "incomplete",
1053
+ objectNumber: 0,
1054
+ detail: `the signed revision cannot be verified against later changes by Acrobat (${why}); no verdict is given rather than one Acrobat would contradict`
1055
+ });
1056
+ }
1057
+ for (const side of SIDES) if (!health[side].referrersComplete) ctx.findings.push({
1058
+ rule: "unexplained",
1059
+ verdict: "incomplete",
1060
+ objectNumber: 0,
1061
+ detail: `the ${side === "old" ? "older" : "newer"} revision's reachability walk hit its budget; not every use of a changed object is known`
1062
+ });
1063
+ }
1064
+ /**
1065
+ * Same effective value on both sides: same canonical serialisation (keys
1066
+ * sorted, whitespace normalised - Acrobat compares dictionaries by value,
1067
+ * corpus v3/61-62), same stream data (by bytes, v2/45), and both sides
1068
+ * actually read. A failed read serialises as "null" and would match another
1069
+ * failed read - evidence of nothing.
1070
+ */
1071
+ function sameEffectiveValue(c) {
1072
+ return c.change === "modified" && !c.value.truncated && c.read?.old !== "failed" && c.read?.new !== "failed" && c.raw.old !== null && c.raw.new !== null && c.raw.old === c.raw.new && !c.streamDataChanged;
1073
+ }
1074
+ function indexRoles(s) {
1075
+ const fieldByObj = /* @__PURE__ */ new Map();
1076
+ const widgetToField = /* @__PURE__ */ new Map();
1077
+ const sigFields = /* @__PURE__ */ new Set();
1078
+ for (const f of s.fields) {
1079
+ fieldByObj.set(f.objectNumber, f);
1080
+ if (f.family === "signature") sigFields.add(f.objectNumber);
1081
+ for (const w of f.widgets) widgetToField.set(w, f.objectNumber);
1082
+ }
1083
+ const signedFields = /* @__PURE__ */ new Set();
1084
+ for (const sig of s.signatures) if (sig.signed && sig.field.kind === "objectNumber") signedFields.add(sig.field.objectNumber);
1085
+ return {
1086
+ fieldByObj,
1087
+ widgetToField,
1088
+ pages: new Set(s.pages),
1089
+ signedFields,
1090
+ sigFields
1091
+ };
1092
+ }
1093
+ function edgeKey(e) {
1094
+ return `${e.parent}:${e.label}`;
1095
+ }
1096
+ var StepContext = class {
1097
+ constructor(input, level, locks) {
1098
+ this.input = input;
1099
+ this.level = level;
1100
+ this.locks = locks;
1101
+ _defineProperty(this, "findings", []);
1102
+ _defineProperty(this, "byNum", /* @__PURE__ */ new Map());
1103
+ _defineProperty(this, "before", void 0);
1104
+ _defineProperty(this, "after", void 0);
1105
+ _defineProperty(this, "claims", /* @__PURE__ */ new Map());
1106
+ _defineProperty(this, "locked", /* @__PURE__ */ new Set());
1107
+ for (const c of input.changes) this.byNum.set(c.objectNumber, c);
1108
+ this.before = indexRoles(input.before);
1109
+ this.after = indexRoles(input.after);
1110
+ }
1111
+ get changes() {
1112
+ return this.input.changes;
1113
+ }
1114
+ allows(needed) {
1115
+ return levelAllows(this.level, needed);
1116
+ }
1117
+ claimEdge(c, side, edge, rule, allow = true) {
1118
+ if (this.locked.has(c.objectNumber)) return;
1119
+ const key = `${c.objectNumber}:${side}:${edgeKey(edge)}`;
1120
+ if (!this.claims.has(key)) this.claims.set(key, {
1121
+ rule,
1122
+ allow
1123
+ });
1124
+ }
1125
+ claimAll(c, rule, allow = true) {
1126
+ for (const side of SIDES) for (const e of c.usage[side]) this.claimEdge(c, side, e, rule, allow);
1127
+ }
1128
+ /**
1129
+ * Claim the references that are the same in both revisions; a reference
1130
+ * gained or lost stays unexplained. `allow: false` explains a condemned
1131
+ * object's edges (one cause in the report, not a cascade) without letting
1132
+ * anything hang off it.
1133
+ */
1134
+ claimStable(c, rule, allow = true) {
1135
+ const oldKeys = new Set(c.usage.old.map(edgeKey));
1136
+ const newKeys = new Set(c.usage.new.map(edgeKey));
1137
+ for (const e of c.usage.old) if (newKeys.has(edgeKey(e))) this.claimEdge(c, "old", e, rule, allow);
1138
+ for (const e of c.usage.new) if (oldKeys.has(edgeKey(e))) this.claimEdge(c, "new", e, rule, allow);
1139
+ }
1140
+ isClaimed(c, side, edge) {
1141
+ return this.claims.has(`${c.objectNumber}:${side}:${edgeKey(edge)}`);
1142
+ }
1143
+ /** The use was inspected by a rule that allowed it. */
1144
+ isAllowed(c, side, edge) {
1145
+ return this.claims.get(`${c.objectNumber}:${side}:${edgeKey(edge)}`)?.allow === true;
1146
+ }
1147
+ permitted(c, rule, detail) {
1148
+ if (this.locked.has(c.objectNumber)) return;
1149
+ this.findings.push({
1150
+ rule,
1151
+ verdict: "permitted",
1152
+ objectNumber: c.objectNumber,
1153
+ detail
1154
+ });
1155
+ }
1156
+ /**
1157
+ * A violation is only proven on evidence: a rule condemning an object
1158
+ * whose value could not be read or inspected is reporting what it could
1159
+ * not see, and that is `incomplete`, never `forbidden`.
1160
+ */
1161
+ forbidden(c, rule, detail, edge) {
1162
+ this.findings.push({
1163
+ rule,
1164
+ verdict: c.value.truncated ? "incomplete" : "forbidden",
1165
+ objectNumber: c.objectNumber,
1166
+ edge: edge ? edgeKey(edge) : void 0,
1167
+ detail: c.value.truncated ? `${detail} (judged on missing evidence: not proven)` : detail
1168
+ });
1169
+ }
1170
+ /**
1171
+ * Claim every reference under a set of parents (an appearance subtree,
1172
+ * the DSS): objects whose references come from `parents` through labels
1173
+ * `accept`, then everything referenced from those objects, and so on.
1174
+ * Runs per side, because a replaced appearance stream exists on one
1175
+ * side only.
1176
+ */
1177
+ claimSubtree(side, roots, accept, rule) {
1178
+ const joined = /* @__PURE__ */ new Set();
1179
+ let grew = true;
1180
+ while (grew) {
1181
+ grew = false;
1182
+ for (const c of this.changes) {
1183
+ if (roots.has(c.objectNumber) || joined.has(c.objectNumber)) continue;
1184
+ if (this.locked.has(c.objectNumber)) continue;
1185
+ const edges = c.usage[side];
1186
+ if (edges.length === 0) continue;
1187
+ const inside = edges.filter((e) => roots.has(e.parent) && accept(e.label) || joined.has(e.parent));
1188
+ if (inside.length === 0) continue;
1189
+ for (const e of inside) this.claimEdge(c, side, e, rule);
1190
+ if (edges.every((e) => inside.includes(e) || this.isAllowed(c, side, e))) {
1191
+ joined.add(c.objectNumber);
1192
+ grew = true;
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+ isIdentical(c) {
1198
+ return sameEffectiveValue(c);
1199
+ }
1200
+ /** Signatures signed in the newer revision but not the older: this step's signing events. */
1201
+ newlySigned() {
1202
+ const out = [];
1203
+ for (const sig of this.input.after.signatures) {
1204
+ if (!sig.signed || sig.field.kind !== "objectNumber") continue;
1205
+ if (this.before.signedFields.has(sig.field.objectNumber)) continue;
1206
+ out.push({
1207
+ fieldObjectNumber: sig.field.objectNumber,
1208
+ sig
1209
+ });
1210
+ }
1211
+ return out;
1212
+ }
1213
+ };
1214
+ function ruleXrefContainer(ctx) {
1215
+ for (const c of ctx.changes) if (c.kind === "xref" || c.kind === "objstm") {
1216
+ ctx.claimAll(c, "xref-container");
1217
+ ctx.permitted(c, "xref-container", "cross-reference container; members are judged on their own");
1218
+ }
1219
+ }
1220
+ /**
1221
+ * An object written again with the value the sealed revision holds is not
1222
+ * a modification: Acrobat compares objects by value and reports such a
1223
+ * revision as "not modified" for every role tried - document information,
1224
+ * a custom catalog entry, a page, the signed signature's own widget, a text
1225
+ * field, an annotation, the encryption dictionary, reserialised with keys
1226
+ * reordered, under a certification, under a field lock (corpus v1/02-07,
1227
+ * 23, v2/47, v3/54-62, 70, 71, 77). Under P=1 nothing is established and
1228
+ * the rewrite stays forbidden. Claimed here so the object-kind rules skip
1229
+ * it; the verdict treats it as no effective change.
1230
+ */
1231
+ function ruleIdenticalRewrite(ctx) {
1232
+ for (const c of ctx.changes) {
1233
+ if (!ctx.isIdentical(c)) continue;
1234
+ if (ctx.allows("fill")) {
1235
+ ctx.claimAll(c, "identical-rewrite");
1236
+ ctx.permitted(c, "identical-rewrite", "written again with the sealed value");
1237
+ } else {
1238
+ ctx.claimAll(c, "identical-rewrite", false);
1239
+ ctx.forbidden(c, "identical-rewrite", `written again with the sealed value under level '${ctx.level}' (not established for this level)`);
1240
+ }
1241
+ }
1242
+ }
1243
+ function ruleTrailer(ctx) {
1244
+ const c = ctx.byNum.get(0);
1245
+ if (!c) return;
1246
+ const changed = changedKeys(c.value.old, c.value.new);
1247
+ const bad = [...changed].filter((k) => !TRAILER_KEYS.has(k));
1248
+ const oldRoot = dictEntries(c.value.old)?.Root;
1249
+ const newRoot = dictEntries(c.value.new)?.Root;
1250
+ if (bad.length > 0) {
1251
+ ctx.forbidden(c, "trailer", `trailer keys changed: ${bad.join(", ")}`);
1252
+ return;
1253
+ }
1254
+ if (changed.has("Root") && !pdfValueEquals(oldRoot ?? null, newRoot ?? null)) {
1255
+ ctx.forbidden(c, "trailer", "the trailer points at a different catalog");
1256
+ return;
1257
+ }
1258
+ if (changed.has("Encrypt") && !pdfValueEquals(dictEntries(c.value.old)?.Encrypt ?? null, dictEntries(c.value.new)?.Encrypt ?? null)) {
1259
+ ctx.forbidden(c, "trailer", "the trailer points at a different encryption dictionary");
1260
+ return;
1261
+ }
1262
+ ctx.claimAll(c, "trailer");
1263
+ ctx.permitted(c, "trailer");
1264
+ }
1265
+ function onlyEdges(c, test) {
1266
+ const all = [...c.usage.old, ...c.usage.new];
1267
+ return all.length > 0 && all.every(test);
1268
+ }
1269
+ function ruleInfo(ctx) {
1270
+ for (const c of ctx.changes) {
1271
+ if (ctx.isIdentical(c)) continue;
1272
+ if (c.objectNumber !== 0 && onlyEdges(c, (e) => e.parent === 0 && e.label === "Info")) {
1273
+ ctx.claimAll(c, "info");
1274
+ ctx.permitted(c, "info", "document information dictionary");
1275
+ }
1276
+ }
1277
+ }
1278
+ function ruleMetadata(ctx) {
1279
+ const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
1280
+ for (const c of ctx.changes) {
1281
+ if (ctx.isIdentical(c)) continue;
1282
+ if (onlyEdges(c, (e) => roots.has(e.parent) && e.label === "Metadata")) {
1283
+ ctx.claimAll(c, "metadata");
1284
+ ctx.permitted(c, "metadata", "XMP metadata stream");
1285
+ }
1286
+ }
1287
+ }
1288
+ function ruleCatalog(ctx) {
1289
+ const root = ctx.input.after.root;
1290
+ const c = ctx.byNum.get(root);
1291
+ if (!c || ctx.isIdentical(c)) return;
1292
+ const changed = changedKeys(c.value.old, c.value.new);
1293
+ const bad = [...changed].filter((k) => !CATALOG_KEYS.has(k));
1294
+ if (bad.length > 0) {
1295
+ ctx.forbidden(c, "catalog-housekeeping", `catalog keys changed: ${bad.join(", ")}`);
1296
+ return;
1297
+ }
1298
+ if (changed.has("AcroForm") && !ctx.input.after.acroForm && !ctx.input.before.acroForm) {
1299
+ const problem = acroFormProblem(ctx, dictEntries(c.value.old)?.AcroForm ?? null, dictEntries(c.value.new)?.AcroForm ?? null);
1300
+ if (problem) {
1301
+ ctx.forbidden(c, "acroform-housekeeping", problem);
1302
+ return;
1303
+ }
1304
+ for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([root]), (label) => label.startsWith("AcroForm/DR/"), "acroform-housekeeping");
1305
+ }
1306
+ if (changed.has("Perms")) {
1307
+ const after = deriveProtection(ctx.input.after.signatures);
1308
+ const before = deriveProtection(ctx.input.before.signatures);
1309
+ if (!after.certification || before.judged !== null) {
1310
+ ctx.forbidden(c, "catalog-housekeeping", "/Perms may only appear with the certification signature, in the first signed revision");
1311
+ return;
1312
+ }
1313
+ }
1314
+ ctx.claimAll(c, "catalog-housekeeping");
1315
+ ctx.permitted(c, "catalog-housekeeping");
1316
+ }
1317
+ function ruleAcroForm(ctx) {
1318
+ const acro = ctx.input.after.acroForm || ctx.input.before.acroForm;
1319
+ if (!acro) return;
1320
+ const c = ctx.byNum.get(acro);
1321
+ if (!c || ctx.isIdentical(c)) return;
1322
+ const problem = acroFormProblem(ctx, c.value.old, c.value.new);
1323
+ if (problem) {
1324
+ ctx.forbidden(c, "acroform-housekeeping", problem);
1325
+ return;
1326
+ }
1327
+ ctx.claimAll(c, "acroform-housekeeping");
1328
+ ctx.permitted(c, "acroform-housekeeping");
1329
+ for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([acro]), (label) => label.startsWith("DR/"), "acroform-housekeeping");
1330
+ }
1331
+ /** What is wrong with an AcroForm dictionary change, or null when it is housekeeping. */
1332
+ function acroFormProblem(ctx, oldValue, newValue) {
1333
+ const changed = changedKeys(oldValue, newValue);
1334
+ const bad = [...changed].filter((k) => !ACROFORM_KEYS.has(k));
1335
+ if (bad.length > 0) return `AcroForm keys changed: ${bad.join(", ")}`;
1336
+ if (changed.has("Fields")) {
1337
+ const oldRefs = refsOf(dictEntries(oldValue)?.Fields);
1338
+ const newRefs = refsOf(dictEntries(newValue)?.Fields);
1339
+ if (oldRefs.length > newRefs.length || oldRefs.some((n, i) => newRefs[i] !== n)) return "/AcroForm /Fields is not append-only";
1340
+ const newSigFields = newSignatureFields(ctx);
1341
+ const strangers = newRefs.slice(oldRefs.length).filter((n) => !newSigFields.has(n));
1342
+ if (strangers.length > 0) return `/AcroForm /Fields gained non-signature fields: ${strangers.join(", ")}`;
1343
+ }
1344
+ return null;
1345
+ }
1346
+ /** The refs appended to an array-valued key when the old array is a prefix of the new one; null otherwise. */
1347
+ function appendedRefs(c, key) {
1348
+ const oldItems = dictEntries(c.value.old)?.[key];
1349
+ const newItems = dictEntries(c.value.new)?.[key];
1350
+ const oldRefs = refsOf(oldItems);
1351
+ const newRefs = refsOf(newItems);
1352
+ if (oldItems && oldItems.t !== "array") return null;
1353
+ if (!newItems || newItems.t !== "array") return null;
1354
+ if (oldRefs.length > newRefs.length) return null;
1355
+ for (let i = 0; i < oldRefs.length; i++) if (oldRefs[i] !== newRefs[i]) return null;
1356
+ return newRefs.slice(oldRefs.length);
1357
+ }
1358
+ function newSignatureFields(ctx) {
1359
+ const out = /* @__PURE__ */ new Set();
1360
+ for (const n of ctx.after.sigFields) if (!ctx.before.fieldByObj.has(n)) out.add(n);
1361
+ return out;
1362
+ }
1363
+ function ruleDss(ctx) {
1364
+ const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
1365
+ for (const side of SIDES) ctx.claimSubtree(side, roots, (label) => label === "DSS" || label.startsWith("DSS/"), "dss");
1366
+ for (const c of ctx.changes) if (c.usage.new.some((e) => roots.has(e.parent) && e.label === "DSS") && !ctx.findings.some((f) => f.objectNumber === c.objectNumber)) ctx.permitted(c, "dss", "document security store");
1367
+ }
1368
+ function ruleSignatureFieldAdded(ctx) {
1369
+ const added = newSignatureFields(ctx);
1370
+ if (added.size === 0) return;
1371
+ const rule = "signature-field-added";
1372
+ if (!ctx.allows("fill")) {
1373
+ for (const n of added) {
1374
+ const c = ctx.byNum.get(n);
1375
+ if (c) ctx.forbidden(c, rule, `a signature field was added under level '${ctx.level}'`);
1376
+ }
1377
+ return;
1378
+ }
1379
+ const newWidgets = /* @__PURE__ */ new Set();
1380
+ for (const n of added) {
1381
+ const field = ctx.after.fieldByObj.get(n);
1382
+ const c = ctx.byNum.get(n);
1383
+ if (c) {
1384
+ ctx.claimAll(c, rule);
1385
+ ctx.permitted(c, rule, `new signature field "${field.name}"`);
1386
+ }
1387
+ for (const w of field.widgets) {
1388
+ newWidgets.add(w);
1389
+ if (w === n) continue;
1390
+ const wc = ctx.byNum.get(w);
1391
+ if (wc) {
1392
+ ctx.claimAll(wc, rule);
1393
+ ctx.permitted(wc, rule, `widget of new signature field "${field.name}"`);
1394
+ }
1395
+ }
1396
+ }
1397
+ for (const p of ctx.after.pages) {
1398
+ const c = ctx.byNum.get(p);
1399
+ if (!c || ctx.isIdentical(c)) continue;
1400
+ const changed = changedKeys(c.value.old, c.value.new);
1401
+ if (changed.size !== 1 || !changed.has("Annots")) continue;
1402
+ const appended = appendedRefs(c, "Annots");
1403
+ if (appended && appended.every((n) => newWidgets.has(n))) {
1404
+ ctx.claimAll(c, rule);
1405
+ ctx.permitted(c, rule, "page /Annots gained the new signature widget");
1406
+ }
1407
+ }
1408
+ for (const c of ctx.changes) {
1409
+ if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
1410
+ const changed = changedKeys(c.value.old, c.value.new);
1411
+ if (changed.size !== 1 || !changed.has("Kids")) continue;
1412
+ const appended = appendedRefs(c, "Kids");
1413
+ if (appended && appended.length > 0 && appended.every((n) => added.has(n) || newWidgets.has(n))) {
1414
+ ctx.claimAll(c, rule);
1415
+ ctx.permitted(c, rule, "field /Kids gained the new signature field");
1416
+ }
1417
+ }
1418
+ }
1419
+ function ruleSignatureAdded(ctx) {
1420
+ const rule = "signature-added";
1421
+ const events = ctx.newlySigned();
1422
+ if (events.length === 0) return;
1423
+ const addedThisStep = newSignatureFields(ctx);
1424
+ const beforeProtection = deriveProtection(ctx.input.before.signatures);
1425
+ const afterProtection = deriveProtection(ctx.input.after.signatures);
1426
+ const parents = /* @__PURE__ */ new Set();
1427
+ const values = /* @__PURE__ */ new Set();
1428
+ for (const { fieldObjectNumber, sig } of events) {
1429
+ const needed = sig.kind === "timestamp" ? "lta" : "fill";
1430
+ if (!ctx.allows(needed)) {
1431
+ const c = ctx.byNum.get(fieldObjectNumber);
1432
+ if (c) ctx.forbidden(c, rule, `a ${sig.kind} was added under level '${ctx.level}'`);
1433
+ continue;
1434
+ }
1435
+ if (sig.catalogCertification && beforeProtection.judged !== null) {
1436
+ const c = ctx.byNum.get(fieldObjectNumber);
1437
+ if (c) ctx.forbidden(c, rule, "a certification signature after another signature");
1438
+ continue;
1439
+ }
1440
+ const field = ctx.after.fieldByObj.get(fieldObjectNumber);
1441
+ parents.add(fieldObjectNumber);
1442
+ for (const w of field?.widgets ?? []) parents.add(w);
1443
+ const fieldIsNew = addedThisStep.has(fieldObjectNumber);
1444
+ const allowedFieldKeys = beforeProtection.judged === null ? /* @__PURE__ */ new Set([...signedFieldKeysFor(ctx.level), "Lock"]) : signedFieldKeysFor(ctx.level);
1445
+ const fc = ctx.byNum.get(fieldObjectNumber);
1446
+ if (fc && fieldIsNew) ctx.permitted(fc, rule, `new field "${sig.fieldName}" signed`);
1447
+ else if (fc && !ctx.isIdentical(fc)) {
1448
+ const changed = changedKeys(fc.value.old, fc.value.new);
1449
+ const bad = [...changed].filter((k) => !allowedFieldKeys.has(k));
1450
+ if (bad.length > 0) {
1451
+ ctx.forbidden(fc, rule, `signature field keys changed: ${bad.join(", ")}`);
1452
+ ctx.claimStable(fc, rule, false);
1453
+ } else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(fieldObjectNumber)?.flags)) {
1454
+ ctx.forbidden(fc, rule, "signature field flags changed beyond ReadOnly");
1455
+ ctx.claimStable(fc, rule, false);
1456
+ } else {
1457
+ ctx.claimStable(fc, rule);
1458
+ ctx.permitted(fc, rule, `field "${sig.fieldName}" signed`);
1459
+ }
1460
+ }
1461
+ for (const w of field?.widgets ?? []) {
1462
+ if (w === fieldObjectNumber || fieldIsNew) continue;
1463
+ const wc = ctx.byNum.get(w);
1464
+ if (!wc || ctx.isIdentical(wc)) continue;
1465
+ const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !SIGNED_WIDGET_KEYS.has(k));
1466
+ if (bad.length > 0) {
1467
+ ctx.forbidden(wc, rule, `signature widget keys changed: ${bad.join(", ")}`);
1468
+ ctx.claimStable(wc, rule, false);
1469
+ continue;
1470
+ }
1471
+ ctx.claimStable(wc, rule);
1472
+ ctx.permitted(wc, rule, "signature widget appearance");
1473
+ }
1474
+ for (const c of ctx.changes) {
1475
+ if (c.change !== "added") continue;
1476
+ const asValue = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "V");
1477
+ const asLock = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "Lock");
1478
+ const type = dictEntries(c.value.new)?.Type;
1479
+ if (asValue.length > 0) {
1480
+ if (!(type?.t === "name" && (type.v === "Sig" || type.v === "DocTimeStamp")) && type !== void 0) continue;
1481
+ for (const e of c.usage.new) if (e.parent === fieldObjectNumber && e.label === "V" || e.label === "DocMDP") ctx.claimEdge(c, "new", e, rule);
1482
+ ctx.permitted(c, rule, "signature value");
1483
+ values.add(c.objectNumber);
1484
+ } else if (asLock.length > 0) {
1485
+ if (!(type === void 0 || type.t === "name" && type.v === "SigFieldLock")) continue;
1486
+ for (const e of asLock) ctx.claimEdge(c, "new", e, rule);
1487
+ ctx.permitted(c, rule, "field lock installed with the signature");
1488
+ }
1489
+ }
1490
+ }
1491
+ if (afterProtection.certification && beforeProtection.judged === null) {
1492
+ const root = ctx.input.after.root;
1493
+ for (const c of ctx.changes) if (c.change === "added" && onlyEdges(c, (e) => e.parent === root && e.label === "Perms")) {
1494
+ ctx.claimAll(c, rule);
1495
+ ctx.permitted(c, rule, "certification permissions");
1496
+ }
1497
+ }
1498
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
1499
+ if (values.size > 0) ctx.claimSubtree("new", values, () => true, rule);
1500
+ }
1501
+ /**
1502
+ * Did `/Ff` change by gaining ReadOnly and nothing else? `/Ff` is
1503
+ * inheritable, and a lock writes `effective | ReadOnly` into the terminal
1504
+ * dictionary, so a terminal that carried no `/Ff` of its own is judged
1505
+ * against the effective flags the older revision's form model reported.
1506
+ */
1507
+ function readOnlyOnlyChange(c, effectiveBefore) {
1508
+ const oldFf = dictEntries(c.value.old)?.Ff;
1509
+ const newFf = dictEntries(c.value.new)?.Ff;
1510
+ const before = oldFf?.t === "number" ? oldFf.v : effectiveBefore ?? 0;
1511
+ const after = newFf?.t === "number" ? newFf.v : 0;
1512
+ return (before | FF_READ_ONLY) === after;
1513
+ }
1514
+ function ruleFormFill(ctx) {
1515
+ const rule = "form-fill";
1516
+ const fieldKeys = fieldKeysFor(ctx.level);
1517
+ const widgetKeys = widgetKeysFor(ctx.level);
1518
+ const parents = /* @__PURE__ */ new Set();
1519
+ const fields = new Map([...ctx.before.fieldByObj, ...ctx.after.fieldByObj]);
1520
+ for (const [num, field] of fields) {
1521
+ if (field.family === "signature") continue;
1522
+ const fc = ctx.byNum.get(num);
1523
+ const widgetChanges = field.widgets.filter((w) => w !== num).map((w) => ctx.byNum.get(w)).filter(Boolean);
1524
+ const owners = /* @__PURE__ */ new Set([num, ...field.widgets]);
1525
+ const isAp = (label) => label === "AP" || label.startsWith("AP/");
1526
+ const appearanceOnly = !fc && widgetChanges.length === 0 ? ctx.changes.filter((c) => SIDES.some((side) => c.usage[side].some((e) => owners.has(e.parent) && isAp(e.label))) && SIDES.every((side) => c.usage[side].every((e) => !isAp(e.label) || owners.has(e.parent)))) : [];
1527
+ if (!fc && widgetChanges.length === 0 && appearanceOnly.length === 0) continue;
1528
+ if (!ctx.allows("fill")) {
1529
+ for (const c of [
1530
+ fc,
1531
+ ...widgetChanges,
1532
+ ...appearanceOnly
1533
+ ]) if (c && !ctx.isIdentical(c)) ctx.forbidden(c, rule, `form fill under level '${ctx.level}'`);
1534
+ continue;
1535
+ }
1536
+ if (appearanceOnly.length > 0) {
1537
+ for (const c of appearanceOnly) ctx.permitted(c, rule, `appearance of field "${field.name}" regenerated`);
1538
+ parents.add(num);
1539
+ for (const w of field.widgets) parents.add(w);
1540
+ continue;
1541
+ }
1542
+ let ok = true;
1543
+ if (fc && !ctx.isIdentical(fc)) {
1544
+ const changed = changedKeys(fc.value.old, fc.value.new);
1545
+ const bad = [...changed].filter((k) => !fieldKeys.has(k));
1546
+ if (bad.length > 0) {
1547
+ ctx.forbidden(fc, rule, `field "${field.name}" keys changed: ${bad.join(", ")}`);
1548
+ ctx.claimStable(fc, rule, false);
1549
+ ok = false;
1550
+ } else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(num)?.flags)) {
1551
+ ctx.forbidden(fc, rule, `field "${field.name}" flags changed beyond adding ReadOnly`);
1552
+ ctx.claimStable(fc, rule, false);
1553
+ ok = false;
1554
+ } else {
1555
+ ctx.claimStable(fc, rule);
1556
+ const properties = [...changed].filter((k) => k !== "V" && k !== "AP" && k !== "AS" && k !== "I" && k !== "RV");
1557
+ ctx.permitted(fc, rule, properties.length > 0 ? `field "${field.name}" ${changed.has("V") || changed.has("AS") ? "filled, " : ""}properties changed: ${properties.join(", ")}` : `field "${field.name}" filled`);
1558
+ }
1559
+ }
1560
+ for (const wc of widgetChanges) {
1561
+ if (ctx.isIdentical(wc)) continue;
1562
+ const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !widgetKeys.has(k));
1563
+ if (bad.length > 0) {
1564
+ ctx.forbidden(wc, rule, `widget of "${field.name}" keys changed: ${bad.join(", ")}`);
1565
+ ctx.claimStable(wc, rule, false);
1566
+ ok = false;
1567
+ continue;
1568
+ }
1569
+ ctx.claimStable(wc, rule);
1570
+ ctx.permitted(wc, rule, `widget of "${field.name}" appearance`);
1571
+ }
1572
+ if (ok) {
1573
+ parents.add(num);
1574
+ for (const w of field.widgets) parents.add(w);
1575
+ }
1576
+ }
1577
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
1578
+ }
1579
+ function ruleAnnotation(ctx) {
1580
+ const rule = "annotation";
1581
+ const pages = /* @__PURE__ */ new Set([...ctx.before.pages, ...ctx.after.pages]);
1582
+ const widgets = /* @__PURE__ */ new Set([...ctx.before.widgetToField.keys(), ...ctx.after.widgetToField.keys()]);
1583
+ const isWidget = (c) => {
1584
+ if (widgets.has(c.objectNumber)) return true;
1585
+ const sub = dictEntries(c.value.new ?? c.value.old)?.Subtype;
1586
+ return sub?.t === "name" && sub.v === "Widget";
1587
+ };
1588
+ const annots = [];
1589
+ for (const c of ctx.changes) {
1590
+ if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
1591
+ if ([...c.usage.old, ...c.usage.new].some((e) => pages.has(e.parent) && e.label.startsWith("Annots/")) && !isWidget(c)) annots.push(c);
1592
+ }
1593
+ if (annots.length === 0) {}
1594
+ if (!ctx.allows("annotate")) {
1595
+ for (const c of annots) ctx.forbidden(c, rule, `annotation change under level '${ctx.level}'`);
1596
+ return;
1597
+ }
1598
+ const parents = /* @__PURE__ */ new Set();
1599
+ for (const c of annots) {
1600
+ ctx.claimAll(c, rule);
1601
+ ctx.permitted(c, rule, `${c.change} annotation`);
1602
+ parents.add(c.objectNumber);
1603
+ }
1604
+ for (const p of pages) {
1605
+ const c = ctx.byNum.get(p);
1606
+ if (!c || ctx.isIdentical(c)) continue;
1607
+ const changed = changedKeys(c.value.old, c.value.new);
1608
+ if (changed.size === 1 && changed.has("Annots")) {
1609
+ const removed = refsOf(dictEntries(c.value.old)?.Annots).filter((n) => !refsOf(dictEntries(c.value.new)?.Annots).includes(n));
1610
+ const added = refsOf(dictEntries(c.value.new)?.Annots).filter((n) => !refsOf(dictEntries(c.value.old)?.Annots).includes(n));
1611
+ if ([...removed, ...added].some((n) => widgets.has(n) && !ctx.findings.some((f) => f.objectNumber === n && f.verdict === "permitted"))) {
1612
+ ctx.forbidden(c, rule, "page /Annots added or removed a form widget");
1613
+ continue;
1614
+ }
1615
+ ctx.claimAll(c, rule);
1616
+ ctx.permitted(c, rule, "page /Annots changed");
1617
+ }
1618
+ }
1619
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/") || label === "Popup" || label === "IRT", rule);
1620
+ }
1621
+ function ruleFieldLock(ctx) {
1622
+ if (ctx.locks.length === 0) return;
1623
+ const lockedFields = ctx.input.before.fields.filter((f) => ctx.locks.some((l) => lockCovers(l.spec, f.name)));
1624
+ const lockedObjects = /* @__PURE__ */ new Set();
1625
+ for (const f of lockedFields) {
1626
+ lockedObjects.add(f.objectNumber);
1627
+ for (const w of f.widgets) lockedObjects.add(w);
1628
+ }
1629
+ const frontier = new Set(lockedObjects);
1630
+ let grew = true;
1631
+ while (grew) {
1632
+ grew = false;
1633
+ for (const c of ctx.changes) {
1634
+ if (frontier.has(c.objectNumber)) continue;
1635
+ if (c.usage.old.length > 0 && c.usage.old.every((e) => frontier.has(e.parent))) {
1636
+ frontier.add(c.objectNumber);
1637
+ grew = true;
1638
+ }
1639
+ }
1640
+ }
1641
+ for (const c of ctx.changes) {
1642
+ if (!frontier.has(c.objectNumber) || ctx.isIdentical(c)) continue;
1643
+ const field = lockedFields.find((f) => f.objectNumber === c.objectNumber || f.widgets.includes(c.objectNumber));
1644
+ if (field && field.objectNumber === c.objectNumber) {
1645
+ const changed = changedKeys(c.value.old, c.value.new);
1646
+ if (changed.size === 1 && changed.has("Ff") && readOnlyOnlyChange(c, ctx.before.fieldByObj.get(c.objectNumber)?.flags)) {
1647
+ ctx.claimStable(c, "field-lock");
1648
+ ctx.permitted(c, "field-lock", `ReadOnly set on locked field "${field.name}"`);
1649
+ continue;
1650
+ }
1651
+ }
1652
+ ctx.locked.add(c.objectNumber);
1653
+ ctx.forbidden(c, "field-lock", field ? `field "${field.name}" is locked by the signature` : "part of a locked field changed");
1654
+ }
1655
+ }
1656
+ function ruleUnexplained(ctx) {
1657
+ for (const c of ctx.changes) {
1658
+ if (c.value.truncated) {
1659
+ ctx.findings.push({
1660
+ rule: "unexplained",
1661
+ verdict: "incomplete",
1662
+ objectNumber: c.objectNumber,
1663
+ detail: "value too large to inspect"
1664
+ });
1665
+ continue;
1666
+ }
1667
+ if (c.usageIncomplete) {
1668
+ ctx.findings.push({
1669
+ rule: "unexplained",
1670
+ verdict: "incomplete",
1671
+ objectNumber: c.objectNumber,
1672
+ detail: "references could not be resolved within budget (depth, fan-out or total reads)"
1673
+ });
1674
+ continue;
1675
+ }
1676
+ if (ctx.locked.has(c.objectNumber)) continue;
1677
+ for (const side of SIDES) for (const e of c.usage[side]) if (!ctx.isClaimed(c, side, e)) ctx.forbidden(c, "unexplained", `${side === "old" ? "was" : "is"} referenced from object ${e.parent} at ${e.label} and no rule explains it`, e);
1678
+ if (c.usage.old.length === 0 && c.usage.new.length === 0 && !ctx.findings.some((f) => f.objectNumber === c.objectNumber)) ctx.permitted(c, "orphan", "referenced from nowhere reachable");
1679
+ }
1680
+ }
1681
+ //#endregion
1682
+ //#region src/signature/analysis/restrictions.ts
1683
+ /**
1684
+ * The restrictions a window is judged under, read from the sealed
1685
+ * revision's structure (what that revision declared, not what the final
1686
+ * file says).
1687
+ *
1688
+ * - `judged` given (a signature-anchored analysis): its own FieldMDP and
1689
+ * /Lock bind the window; other signatures' locks do not (corpus v3/85:
1690
+ * a second signature is valid with a fill of a field the first locked).
1691
+ * Every certification's level, own or earlier, applies (inherited:
1692
+ * conservative until observed otherwise), as does a /Lock's /P.
1693
+ * - no `judged` (a revision-anchored analysis): every signed signature's
1694
+ * locks and levels apply.
1695
+ */
1696
+ function restrictionsFor(before, judged) {
1697
+ const anchors = [];
1698
+ const locks = [];
1699
+ let enforced = null;
1700
+ let signed = false;
1701
+ for (const sig of before.signatures) {
1702
+ if (!sig.signed || sig.revisionIndex === null) continue;
1703
+ signed = true;
1704
+ const own = judged !== null && sig.index === judged.index;
1705
+ if (sig.catalogCertification && sig.docMdp !== null) {
1706
+ anchors.push({
1707
+ signatureIndex: sig.index,
1708
+ revisionIndex: sig.revisionIndex,
1709
+ source: "docmdp",
1710
+ own,
1711
+ permission: sig.docMdp
1712
+ });
1713
+ enforced = minLevel(enforced ?? "annotate", levelFromPermission(sig.docMdp));
1714
+ }
1715
+ if (sig.lock?.permission !== void 0) enforced = minLevel(enforced ?? "annotate", levelFromPermission(sig.lock.permission));
1716
+ if (judged !== null && !own) continue;
1717
+ if (sig.fieldMdp) {
1718
+ anchors.push({
1719
+ signatureIndex: sig.index,
1720
+ revisionIndex: sig.revisionIndex,
1721
+ source: "fieldmdp",
1722
+ own,
1723
+ fields: sig.fieldMdp
1724
+ });
1725
+ locks.push({
1726
+ signatureIndex: sig.index,
1727
+ source: "fieldmdp",
1728
+ spec: sig.fieldMdp
1729
+ });
1730
+ }
1731
+ if (sig.lock) {
1732
+ anchors.push({
1733
+ signatureIndex: sig.index,
1734
+ revisionIndex: sig.revisionIndex,
1735
+ source: "lock",
1736
+ own,
1737
+ fields: sig.lock
1738
+ });
1739
+ locks.push({
1740
+ signatureIndex: sig.index,
1741
+ source: "lock",
1742
+ spec: sig.lock
1743
+ });
1744
+ }
1745
+ }
1746
+ return {
1747
+ anchors,
1748
+ level: signed ? enforced ?? "annotate" : "annotate",
1749
+ locks
1750
+ };
1751
+ }
1752
+ //#endregion
1753
+ //#region src/signature/analysis/resolve.ts
1754
+ const DEFAULT_EDGE_RESOLVER_BUDGET = {
1755
+ maxDepth: 16,
1756
+ maxEdgesPerObject: 256,
1757
+ maxReads: 2e4
1758
+ };
1759
+ const USAGE_INCOMPLETE = "incomplete";
1760
+ /**
1761
+ * Resolves the raw inbound edges of an object (from the fork's referrer
1762
+ * index, which covers every reachable object of a revision) into edges
1763
+ * from *anchors*: the changed objects and the structural objects of the
1764
+ * revision (trailer, catalog, AcroForm, page tree root, pages, fields,
1765
+ * widgets). An edge from an unchanged, non-structural parent is replaced by
1766
+ * that parent's own anchored edges with the labels joined, so an
1767
+ * appearance stream under an unchanged indirect `/AP` dictionary reads
1768
+ * `widget:AP/N` rather than `apDict:N`.
1769
+ *
1770
+ * A graph traversal, not a path enumeration: results are memoised per
1771
+ * object, so a lattice of shared unchanged objects costs its size, not the
1772
+ * number of paths through it. Cycle edges contribute no anchors and are
1773
+ * skipped; a result computed while skipping one is not memoised.
1774
+ */
1775
+ var EdgeResolver = class {
1776
+ constructor(read, anchors, budget = DEFAULT_EDGE_RESOLVER_BUDGET) {
1777
+ this.read = read;
1778
+ this.anchors = anchors;
1779
+ this.budget = budget;
1780
+ _defineProperty(this, "memo", /* @__PURE__ */ new Map());
1781
+ _defineProperty(this, "reads", 0);
1782
+ _defineProperty(this, "exhausted", false);
1783
+ }
1784
+ /** Anchored edges of `objectNumber`, or `incomplete` when a budget was exceeded. */
1785
+ resolve(objectNumber) {
1786
+ if (this.exhausted) return USAGE_INCOMPLETE;
1787
+ const result = this.resolveInner(objectNumber, /* @__PURE__ */ new Set());
1788
+ return result === null ? USAGE_INCOMPLETE : result.edges;
1789
+ }
1790
+ resolveInner(n, active) {
1791
+ const hit = this.memo.get(n);
1792
+ if (hit) return hit;
1793
+ if (active.size >= this.budget.maxDepth) return null;
1794
+ if (this.reads >= this.budget.maxReads) {
1795
+ this.exhausted = true;
1796
+ return null;
1797
+ }
1798
+ this.reads += 1;
1799
+ const raw = this.read(n);
1800
+ active.add(n);
1801
+ const edges = [];
1802
+ let cyclic = false;
1803
+ try {
1804
+ for (const e of raw) {
1805
+ if (this.anchors.has(e.parent)) edges.push(e);
1806
+ else if (active.has(e.parent)) {
1807
+ cyclic = true;
1808
+ continue;
1809
+ } else {
1810
+ const up = this.resolveInner(e.parent, active);
1811
+ if (up === null) return null;
1812
+ cyclic || (cyclic = up.cyclic);
1813
+ for (const u of up.edges) edges.push({
1814
+ parent: u.parent,
1815
+ label: `${u.label}/${e.label}`,
1816
+ via: [...u.via ?? [], e.parent]
1817
+ });
1818
+ }
1819
+ if (edges.length > this.budget.maxEdgesPerObject) return null;
1820
+ }
1821
+ } finally {
1822
+ active.delete(n);
1823
+ }
1824
+ const resolution = {
1825
+ edges,
1826
+ cyclic
1827
+ };
1828
+ if (!cyclic) this.memo.set(n, resolution);
1829
+ return resolution;
1830
+ }
1831
+ };
1832
+ //#endregion
1833
+ //#region src/search/types.ts
1834
+ /** The query part of a request (or of any object that carries one): its text and flags, set flags only. */
1835
+ function searchQueryOf(query) {
1836
+ return {
1837
+ text: query.text,
1838
+ ...query.regex ? { regex: true } : {},
1839
+ ...query.matchCase ? { matchCase: true } : {},
1840
+ ...query.wholeWord ? { wholeWord: true } : {},
1841
+ ...query.matchDiacritics ? { matchDiacritics: true } : {},
1842
+ ...query.ignoreWhitespace ? { ignoreWhitespace: true } : {}
1843
+ };
1844
+ }
1845
+ //#endregion
1846
+ //#region src/search/matcher.ts
1847
+ /**
1848
+ * The whole match stage in one pure call: page text in, original-space
1849
+ * match ranges out. Engines with a pre-folded corpus for the default fold
1850
+ * skip this and call `matchLiteral` on the cached `FoldedText` directly;
1851
+ * everything else (non-default fold options, regex, tests) comes through
1852
+ * here.
1853
+ */
1854
+ function matchPageText(text, query) {
1855
+ if (query.regex) return matchRegex(text, query);
1856
+ return matchLiteral(foldText(text, foldOptionsFor(query)), query);
1857
+ }
1858
+ //#endregion
1859
+ //#region src/search/snippet.ts
1860
+ /** Default context on each side of a match, in code units. */
1861
+ const SEARCH_SNIPPET_CONTEXT = 48;
1862
+ const WHITESPACE = /\s/;
1863
+ const WHITESPACE_ALL = /\s/g;
1864
+ const isLowSurrogate = (text, index) => {
1865
+ const unit = text.charCodeAt(index);
1866
+ return unit >= 56320 && unit <= 57343;
1867
+ };
1868
+ /**
1869
+ * The excerpt around one match (`snippets: true`): up to `context` code
1870
+ * units of page text on each side, trimmed to a whitespace boundary when one
1871
+ * exists inside the window (so snippets start and end on whole words),
1872
+ * never splitting a surrogate pair, whitespace flattened to spaces.
1873
+ */
1874
+ function buildSnippet(text, range, context = 48) {
1875
+ const matchEnd = range.start + range.count;
1876
+ let start = Math.max(0, range.start - context);
1877
+ if (start > 0) {
1878
+ for (let i = start; i < range.start; i++) if (WHITESPACE.test(text[i])) {
1879
+ start = i + 1;
1880
+ break;
1881
+ }
1882
+ if (isLowSurrogate(text, start)) start++;
1883
+ }
1884
+ let end = Math.min(text.length, matchEnd + context);
1885
+ if (end < text.length) {
1886
+ for (let i = end - 1; i >= matchEnd; i--) if (WHITESPACE.test(text[i])) {
1887
+ end = i;
1888
+ break;
1889
+ }
1890
+ if (isLowSurrogate(text, end)) end--;
1891
+ }
1892
+ const flat = (from, to) => text.slice(from, to).replace(WHITESPACE_ALL, " ");
1893
+ return {
1894
+ before: flat(start, range.start),
1895
+ match: flat(range.start, matchEnd),
1896
+ after: flat(matchEnd, end)
1897
+ };
1898
+ }
1899
+ //#endregion
1900
+ //#region src/search/epoch.ts
1901
+ /**
1902
+ * The search content epoch: a deterministic fingerprint of everything a
1903
+ * search result depends on — page order/structure (`layoutVersion`) and
1904
+ * each page's text content (`contentVersion`), nothing else. Annotation
1905
+ * and metadata churn deliberately do not move it, so cached search
1906
+ * responses in a collaborative document survive comment storms.
1907
+ *
1908
+ * Both sides compute it from the manifest they already hold: the client
1909
+ * to mint versioned search URLs, the server to validate them (mismatch →
1910
+ * NotFound, the standard stale-versioned-read signal). FNV-1a 64-bit —
1911
+ * dependency-free and stable; not cryptographic, and doesn't need to be:
1912
+ * an adversary can only vary his own authorized query results.
1913
+ */
1914
+ function searchContentEpoch(manifest) {
1915
+ let input = `${manifest.layoutVersion}`;
1916
+ for (const page of manifest.pages) input += `|${page.page.objectNumber}:${page.cache.contentVersion}`;
1917
+ return fnv1a64(input);
1918
+ }
1919
+ const FNV_OFFSET = 14695981039346656037n;
1920
+ const FNV_PRIME = 1099511628211n;
1921
+ const MASK_64 = 18446744073709551615n;
1922
+ function fnv1a64(input) {
1923
+ let hash = FNV_OFFSET;
1924
+ for (let i = 0; i < input.length; i++) {
1925
+ hash ^= BigInt(input.charCodeAt(i));
1926
+ hash = hash * FNV_PRIME & MASK_64;
1927
+ }
1928
+ return hash.toString(16).padStart(16, "0");
1929
+ }
1930
+ /**
1931
+ * Canonicalize a query for cache keying: default-fold literal queries are
1932
+ * pre-folded ("Café", "café" and "CAFE" are the same search, so they
1933
+ * should be the same cache entry). Semantics are unchanged — the fold is
1934
+ * idempotent and exactly what the matcher applies to the needle anyway.
1935
+ * Case- or diacritic-sensitive literals and regex patterns pass through
1936
+ * untouched (their raw form is the query).
1937
+ */
1938
+ function canonicalSearchQuery(request) {
1939
+ const query = searchQueryOf(request);
1940
+ if (query.regex || query.matchCase || query.matchDiacritics || query.ignoreWhitespace) return query;
1941
+ const canonical = { text: foldText(query.text).folded };
1942
+ if (query.wholeWord) canonical.wholeWord = true;
1943
+ return canonical;
1944
+ }
1945
+ //#endregion
1946
+ //#region src/mutation/WriteOptions.ts
1947
+ /** What an `opId` may be: what an HTTP header carries (`Idempotency-Key`). */
1948
+ const OP_ID = /^[\x21-\x7e]{1,255}$/;
1949
+ /**
1950
+ * The `opId` a write runs under: the caller's, or a fresh one. The caller's
1951
+ * must be 1 to 255 visible ASCII characters, on every engine; anything else
1952
+ * is refused with `InvalidArg`.
1953
+ */
1954
+ function opIdOf(options) {
1955
+ const opId = options?.opId;
1956
+ if (opId === void 0) return generateUuid();
1957
+ if (!OP_ID.test(opId)) throw new EngineError(EngineErrorCode.InvalidArg, "opId must be 1 to 255 visible ASCII characters", { details: { field: "opId" } });
1958
+ return opId;
1959
+ }
1960
+ //#endregion
1961
+ //#region src/mutation/changeOutcome.ts
1962
+ /**
1963
+ * The refusals a change keeps under its `opId`, like a success: the document
1964
+ * or the caller refused it, so asking again gets the same answer, even once
1965
+ * the document has changed so that it would apply. Anything else (an abort, a
1966
+ * lost race with another writer, an engine failure) applied nothing, is not
1967
+ * kept, and may run again.
1968
+ */
1969
+ const KEPT_REFUSALS = /* @__PURE__ */ new Set([
1970
+ EngineErrorCode.InvalidArg,
1971
+ EngineErrorCode.NotFound,
1972
+ EngineErrorCode.Forbidden,
1973
+ EngineErrorCode.ProtectedDocument,
1974
+ EngineErrorCode.MalformedPdf,
1975
+ EngineErrorCode.PayloadTooLarge,
1976
+ EngineErrorCode.ObjectNumberUnavailable,
1977
+ EngineErrorCode.LayerFull,
1978
+ EngineErrorCode.ChangeConflict,
1979
+ EngineErrorCode.UndoUnavailable
1980
+ ]);
1981
+ /** Whether a change refused with `code` keeps that answer (see `KEPT_REFUSALS`). */
1982
+ function isKeptRefusal(code) {
1983
+ return KEPT_REFUSALS.has(code);
1984
+ }
1985
+ /**
1986
+ * What a retry under the same `opId` must send: the change's fingerprint. The
1987
+ * same ops with the same bytes give the same fingerprint whatever the order
1988
+ * of their keys; bytes count by their content.
1989
+ */
1990
+ function changeFingerprint(change) {
1991
+ const text = canonicalJson(change);
1992
+ return `${text.length.toString(36)}-${hashText(text)}`;
1993
+ }
1994
+ /** `value` as JSON with every object's keys sorted, and bytes as their length and hash. */
1995
+ function canonicalJson(value) {
1996
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1997
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
1998
+ return JSON.stringify({ $bytes: `${bytes.byteLength.toString(36)}-${hashBytes(bytes)}` });
1999
+ }
2000
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
2001
+ if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
2002
+ return JSON.stringify(value) ?? "null";
2003
+ }
2004
+ /** A 53-bit hash (cyrb53) of a string's UTF-16 units, in base 36. */
2005
+ function hashText(text) {
2006
+ let h1 = 3735928559;
2007
+ let h2 = 1103547991;
2008
+ for (let i = 0; i < text.length; i++) {
2009
+ const unit = text.charCodeAt(i);
2010
+ h1 = Math.imul(h1 ^ unit, 2654435761);
2011
+ h2 = Math.imul(h2 ^ unit, 1597334677);
2012
+ }
2013
+ return finish(h1, h2);
2014
+ }
2015
+ /** The same hash over bytes. */
2016
+ function hashBytes(bytes) {
2017
+ let h1 = 3735928559;
2018
+ let h2 = 1103547991;
2019
+ for (const byte of bytes) {
2020
+ h1 = Math.imul(h1 ^ byte, 2654435761);
2021
+ h2 = Math.imul(h2 ^ byte, 1597334677);
2022
+ }
2023
+ return finish(h1, h2);
2024
+ }
2025
+ function finish(h1, h2) {
2026
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
2027
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
2028
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
2029
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
2030
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
2031
+ }
2032
+ //#endregion
2033
+ //#region src/auth/scope/pdf-bits.ts
2034
+ /**
2035
+ * ISO 32000 user-access permission bit positions, expressed as integer
2036
+ * masks. Bit numbers in the spec are 1-indexed, so bit 3 = `1 << 2`.
2037
+ *
2038
+ * Bits 1, 2, 7, 8, and 13+ are reserved and not surfaced here.
2039
+ */
2040
+ const PDF_BITS = {
2041
+ /** Bit 3 — print the document. */
2042
+ PRINT: 4,
2043
+ /** Bit 4 — modify content streams. */
2044
+ MODIFY: 8,
2045
+ /** Bit 5 — copy/extract text and graphics. */
2046
+ COPY: 16,
2047
+ /** Bit 6 — annotate + fill forms (+ modify form fields with bit 4). */
2048
+ ANNOTATE_FILL: 32,
2049
+ /** Bit 9 — fill existing forms (even if bit 6 is clear). */
2050
+ FILL_FORMS: 256,
2051
+ /** Bit 10 — accessibility extraction (deprecated in PDF 2.0). */
2052
+ ACCESSIBILITY: 512,
2053
+ /** Bit 11 — assemble document (insert/rotate/delete pages, outline). */
2054
+ ASSEMBLE: 1024,
2055
+ /** Bit 12 — high-quality print (refines bit 3). */
2056
+ PRINT_HIGH: 2048
2057
+ };
2058
+ /**
2059
+ * Decode a raw PDFium permission integer into a typed boolean view.
2060
+ *
2061
+ * `null` input (the documents row column is nullable until probed)
2062
+ * yields all-false bits — i.e., a maximally restrictive interpretation.
2063
+ * That keeps `pdf.permissions` expansion safe on unprobed documents.
2064
+ */
2065
+ function decodePdfBits(raw) {
2066
+ if (raw === null) return {
2067
+ bit3: false,
2068
+ bit4: false,
2069
+ bit5: false,
2070
+ bit6: false,
2071
+ bit9: false,
2072
+ bit10: false,
2073
+ bit11: false,
2074
+ bit12: false
2075
+ };
2076
+ return {
2077
+ bit3: (raw & PDF_BITS.PRINT) !== 0,
2078
+ bit4: (raw & PDF_BITS.MODIFY) !== 0,
2079
+ bit5: (raw & PDF_BITS.COPY) !== 0,
2080
+ bit6: (raw & PDF_BITS.ANNOTATE_FILL) !== 0,
2081
+ bit9: (raw & PDF_BITS.FILL_FORMS) !== 0,
2082
+ bit10: (raw & PDF_BITS.ACCESSIBILITY) !== 0,
2083
+ bit11: (raw & PDF_BITS.ASSEMBLE) !== 0,
2084
+ bit12: (raw & PDF_BITS.PRINT_HIGH) !== 0
2085
+ };
2086
+ }
2087
+ //#endregion
2088
+ //#region src/auth/scope/authority.ts
2089
+ /**
2090
+ * Check that the caller may use `capability`: `ProtectedDocument` when a
2091
+ * signature in the document took it away, `PermissionDenied` when the
2092
+ * grants never gave it.
2093
+ */
2094
+ function authorizeCapability(authority, capability) {
2095
+ authorizeUnprotected(authority, capability);
2096
+ const { grants, protection } = authority;
2097
+ if (grants && !checkCapability(capability, grants.scope, grants.pdfBits, protection)) throw new PermissionDenied(capability, "target");
2098
+ }
2099
+ /** Whether the caller may use `capability`: what {@link authorizeCapability} checks, as a yes or no. */
2100
+ function allowsCapability(authority, capability) {
2101
+ const { grants, protection } = authority;
2102
+ if (protection && protectedCapabilities(protection).has(capability)) return false;
2103
+ return !grants || checkCapability(capability, grants.scope, grants.pdfBits, protection);
2104
+ }
2105
+ /**
2106
+ * Check only that no signature in the document took `capability` away
2107
+ * (`ProtectedDocument`). Annotation creates, updates and deletes take this
2108
+ * for `doc.annotate.modify`; their grants are checked per annotation.
2109
+ */
2110
+ function authorizeUnprotected(authority, capability) {
2111
+ const { protection } = authority;
2112
+ if (protection && protectedCapabilities(protection).has(capability)) throw new EngineError(EngineErrorCode.ProtectedDocument, describeProtection(capability, protection));
2113
+ }
2114
+ /**
2115
+ * The capability writing an annotation of `subtype` takes. A widget belongs
2116
+ * to a form field, so creating, changing, moving or deleting one is
2117
+ * designing the form: `doc.forms.modify`. The annotation scopes
2118
+ * (`annotations:*`) never apply to it, and it has no group of its own.
2119
+ */
2120
+ function annotationWriteCapability(subtype) {
2121
+ return subtype === "widget" ? "doc.forms.modify" : "doc.annotate.modify";
2122
+ }
2123
+ /** The capabilities writing all of `annotations` takes, each once. */
2124
+ function annotationWriteCapabilities(annotations) {
2125
+ return [...new Set(annotations.map((annotation) => annotationWriteCapability(annotation.subtype)))];
2126
+ }
2127
+ /**
2128
+ * Check a widget write (see {@link annotationWriteCapability}): form design,
2129
+ * and no group to set. Returns the actor the write stamps: the editor.
2130
+ */
2131
+ function authorizeWidgetWrite(authority, groupId) {
2132
+ const { identity, grants } = authority;
2133
+ if (grants && !checkCapability("doc.forms.modify", grants.scope, grants.pdfBits)) throw new PermissionDenied("doc.forms.modify", "target");
2134
+ if (typeof groupId === "string") throw new EngineError(EngineErrorCode.InvalidArg, "a widget has no group of its own: a form field's group is the field's");
2135
+ const actor = {
2136
+ ...identity.userId !== void 0 ? { userId: identity.userId } : {},
2137
+ ...identity.displayName !== void 0 ? { displayName: identity.displayName } : {}
2138
+ };
2139
+ return actor.userId || actor.displayName ? actor : void 0;
2140
+ }
2141
+ /**
2142
+ * Check a create of a `subtype` annotation in `groupId` (the identity's own
2143
+ * group without one) and return the actor the write stamps: the creator, in
2144
+ * that group. A group other than the identity's takes the authority a
2145
+ * reassignment takes. A widget takes form design instead, in no group.
2146
+ */
2147
+ function authorizeAnnotationCreate(authority, subtype, groupId) {
2148
+ if (subtype === "widget") return authorizeWidgetWrite(authority, groupId);
2149
+ const { identity, grants } = authority;
2150
+ const group = groupId ?? identity.groupId;
2151
+ if (grants) {
2152
+ if (group !== void 0 && !checkSetGroup("annotations", group, identity.groupId, grants.scope)) throw new PermissionDenied(`annotations:set-group:group=${group}`, "target");
2153
+ if (!checkCollab("create", collabTargetOf({
2154
+ userId: identity.userId,
2155
+ groupId: group
2156
+ }), grants.scope, identity, grants.pdfBits)) throw new PermissionDenied("annotations:create", "target");
2157
+ }
2158
+ const actor = {
2159
+ ...identity.userId !== void 0 ? { userId: identity.userId } : {},
2160
+ ...group !== void 0 ? { groupId: group } : {},
2161
+ ...identity.displayName !== void 0 ? { displayName: identity.displayName } : {}
2162
+ };
2163
+ return actor.userId || actor.groupId || actor.displayName ? actor : void 0;
2164
+ }
2165
+ /**
2166
+ * Check an update of the annotation `owner` describes and return the actor
2167
+ * the write stamps: the editor, and the group only when the patch reassigns
2168
+ * it. An existing group can be reassigned, never removed. A widget takes form
2169
+ * design instead, and its patch sets no group.
2170
+ */
2171
+ function authorizeAnnotationUpdate(authority, owner, patchGroupId) {
2172
+ if (owner.subtype === "widget") return authorizeWidgetWrite(authority, patchGroupId);
2173
+ const { identity, grants } = authority;
2174
+ const target = collabTargetOf(owner);
2175
+ if (grants && !checkCollab("update", target, grants.scope, identity, grants.pdfBits)) throw new PermissionDenied("annotations:update", "target");
2176
+ if (patchGroupId === null && target.groupId !== void 0) throw new EngineError(EngineErrorCode.InvalidArg, "an annotation's group can't be removed");
2177
+ const reassigning = typeof patchGroupId === "string" && patchGroupId !== target.groupId;
2178
+ if (reassigning && grants && !checkSetGroup("annotations", patchGroupId, identity.groupId, grants.scope)) throw new PermissionDenied(`annotations:set-group:group=${patchGroupId}`, "target");
2179
+ const actor = {
2180
+ ...identity.userId !== void 0 ? { userId: identity.userId } : {},
2181
+ ...identity.displayName !== void 0 ? { displayName: identity.displayName } : {},
2182
+ ...reassigning ? { groupId: patchGroupId } : {}
2183
+ };
2184
+ return actor.userId || actor.groupId || actor.displayName ? actor : void 0;
2185
+ }
2186
+ /**
2187
+ * Check a delete of every annotation it removes (an annotation goes with its
2188
+ * thread and popups): all or nothing, `PermissionDenied` naming each one
2189
+ * refused. A widget takes form design, never the annotation scopes.
2190
+ */
2191
+ function authorizeAnnotationDelete(authority, members) {
2192
+ const { identity, grants } = authority;
2193
+ if (!grants) return;
2194
+ const mayDesign = checkCapability("doc.forms.modify", grants.scope, grants.pdfBits);
2195
+ const refused = members.filter((member) => member.subtype === "widget" ? !mayDesign : !checkCollab("delete", collabTargetOf(member), grants.scope, identity, grants.pdfBits));
2196
+ if (refused.length > 0) throw new PermissionDenied("annotations:delete", "target", void 0, refused.map((member) => member.ref));
2197
+ }
2198
+ /**
2199
+ * Check that the caller may `action` (fill in, or sign) the field in
2200
+ * `groupId`: `ProtectedDocument` when a signature in the document took the
2201
+ * capability away, `PermissionDenied` naming the scope it would take when
2202
+ * the grants don't reach the field's group.
2203
+ */
2204
+ function authorizeFieldWrite(authority, groupId, action) {
2205
+ authorizeUnprotected(authority, fieldCapability(action));
2206
+ if (!allowsFieldWrite(authority, groupId, action)) throw new PermissionDenied(groupId === null ? fieldCapability(action) : `fields:${action}:group=${groupId}`, "target");
2207
+ }
2208
+ /** Whether the caller may `action` the field in `groupId`: what {@link authorizeFieldWrite} checks. */
2209
+ function allowsFieldWrite(authority, groupId, action) {
2210
+ const { grants, protection } = authority;
2211
+ if (protection && protectedCapabilities(protection).has(fieldCapability(action))) return false;
2212
+ return !grants || checkFieldAction(action, groupId, grants.scope, grants.pdfBits, protection);
2213
+ }
2214
+ /**
2215
+ * Whether the caller may `action` some field: what a form verb checks
2216
+ * before it knows which fields it writes. Each field is then checked in
2217
+ * the write with {@link authorizeFieldWrite}.
2218
+ */
2219
+ function allowsSomeFieldWrite(authority, action) {
2220
+ const { grants, protection } = authority;
2221
+ if (protection && protectedCapabilities(protection).has(fieldCapability(action))) return false;
2222
+ return !grants || checkAnyFieldAction(action, grants.scope, grants.pdfBits, protection);
2223
+ }
2224
+ /**
2225
+ * Check that the caller may put a form field in `groupId`: their own
2226
+ * default group always, another with `fields:set-group` for it.
2227
+ */
2228
+ function authorizeFieldGroup(authority, groupId) {
2229
+ const { identity, grants } = authority;
2230
+ if (grants && !checkSetGroup("fields", groupId, identity.groupId, grants.scope)) throw new PermissionDenied(`fields:set-group:group=${groupId}`, "target");
2231
+ }
2232
+ /** The broad capability a field action takes without a `fields:` scope. */
2233
+ function fieldCapability(action) {
2234
+ return action === "fill" ? "doc.forms.fill" : "doc.sign";
2235
+ }
2236
+ //#endregion
2237
+ export { pageListOf as $, sameEffectiveValue as A, refsOf as B, searchQueryOf as C, restrictionsFor as D, USAGE_INCOMPLETE as E, worstVerdict as F, pageFormFieldOf as G, placedWidgetOf as H, changedKeys as I, pdfFormFieldDraftOf as J, pageFormSnapshotOf as K, dictEntries as L, combine as M, conclude as N, evaluateStep as O, primaryFinding as P, pageLayoutOf as Q, parsePdfValue as R, matchPageText as S, EdgeResolver as T, draftWritesScripts as U, stableStringify as V, pageFieldActionsOf as W, pageAppearancesOf as X, pdfWidgetPlacementOf as Y, pdfRenderTargetOf as Z, opIdOf as _, DEFAULT_MEDIA_BOX as _t, annotationWriteCapability as a, concatAnnotationLists as at, SEARCH_SNIPPET_CONTEXT as b, authorizeAnnotationUpdate as c, normalizeAttachmentFileSource as ct, authorizeFieldWrite as d, generateUuidV7 as dt, pageSpaceBoxesOf as et, authorizeUnprotected as f, DEFAULT_PDF_SAVE_MODE as ft, isKeptRefusal as g, resolvePageLayers as gt, changeFingerprint as h, imageSourceOfBytes as ht, annotationWriteCapabilities as i, deletedAnnotationsOf as it, assessmentOf as j, restrictionsOf as k, authorizeCapability as l, familyOfSubtype as lt, decodePdfBits as m, createPageImageHandle as mt, allowsFieldWrite as n, annotationPatchBetween as nt, authorizeAnnotationCreate as o, closeExportSelection as ot, PDF_BITS as p, checkImageQuality as pt, pageWidgetOf as q, allowsSomeFieldWrite as r, mergeAnnotationPatch as rt, authorizeAnnotationDelete as s, annotationImportFacts as st, allowsCapability as t, visibleBoxesOf as tt, authorizeFieldGroup as u, generateUuid as ut, canonicalSearchQuery as v, pageBoxesOf as vt, DEFAULT_EDGE_RESOLVER_BUDGET as w, buildSnippet as x, searchContentEpoch as y, pageRotationOf as yt, pdfValueEquals as z };
2238
+
2239
+ //# sourceMappingURL=authority-B3BklsB4.js.map