@embedpdf/engine-core 3.0.0-next.12 → 3.0.0-next.14

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 (64) hide show
  1. package/dist/{AbortError-8-9JAX1Y.js → AbortError-DwuHPK_I.js} +2 -2
  2. package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DwuHPK_I.js.map} +1 -1
  3. package/dist/{AbortError-CRSooKsk.cjs → AbortError-P0RU0Mgg.cjs} +4 -4
  4. package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-P0RU0Mgg.cjs.map} +1 -1
  5. package/dist/{AnnotationListSnapshot-x6tKulNV.d.cts → AnnotationListSnapshot-D9R5n2Kf.d.cts} +1390 -176
  6. package/dist/{AnnotationListSnapshot-x6tKulNV.d.ts → AnnotationListSnapshot-D9R5n2Kf.d.ts} +1390 -176
  7. package/dist/{DocumentSecurityService-CK0pejAK.d.cts → DocumentSecurityService-CIx1MT0T.d.cts} +2 -2
  8. package/dist/{DocumentSecurityService-9MawYxP-.d.ts → DocumentSecurityService-Ca6EK3-3.d.ts} +2 -2
  9. package/dist/{Engine--0XF0UAK.d.cts → Engine-EWWHhEIi.d.ts} +179 -11
  10. package/dist/{Engine-Ds0ytbl6.d.ts → Engine-FNaAEjfb.d.cts} +179 -11
  11. package/dist/conformance.cjs +1441 -88
  12. package/dist/conformance.cjs.map +1 -1
  13. package/dist/conformance.d.cts +74 -3
  14. package/dist/conformance.d.ts +74 -3
  15. package/dist/conformance.js +1376 -25
  16. package/dist/conformance.js.map +1 -1
  17. package/dist/derive-BbsDk_Kz.cjs +880 -0
  18. package/dist/derive-BbsDk_Kz.cjs.map +1 -0
  19. package/dist/derive-DAFJolWu.js +647 -0
  20. package/dist/derive-DAFJolWu.js.map +1 -0
  21. package/dist/{PageInsertBlankInput-WKY0tr6S.js → format-Bbphf4zR.js} +95 -3
  22. package/dist/format-Bbphf4zR.js.map +1 -0
  23. package/dist/{PageInsertBlankInput-CzShuSJI.cjs → format-D_epWlob.cjs} +106 -2
  24. package/dist/format-D_epWlob.cjs.map +1 -0
  25. package/dist/index.cjs +137 -88
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +5 -5
  29. package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
  30. package/dist/resolver-CYVNV4hn.cjs.map +1 -0
  31. package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
  32. package/dist/resolver-inAVzsWt.js.map +1 -0
  33. package/dist/runtime.cjs +147 -98
  34. package/dist/runtime.cjs.map +1 -1
  35. package/dist/runtime.d.cts +216 -5
  36. package/dist/runtime.d.ts +216 -5
  37. package/dist/runtime.js +6 -6
  38. package/dist/{schemas-DOyVEPk7.js → schemas-CFQGvWiu.js} +551 -5
  39. package/dist/schemas-CFQGvWiu.js.map +1 -0
  40. package/dist/{schemas-RChsAMWw.cjs → schemas-D0g6tKa9.cjs} +829 -25
  41. package/dist/schemas-D0g6tKa9.cjs.map +1 -0
  42. package/dist/{builders-8Hl1AYNU.cjs → viewport-BWeNHZmb.cjs} +1522 -117
  43. package/dist/viewport-BWeNHZmb.cjs.map +1 -0
  44. package/dist/{builders-DOjcKDWQ.js → viewport-CvMcadfQ.js} +1386 -89
  45. package/dist/viewport-CvMcadfQ.js.map +1 -0
  46. package/dist/wire.cjs +252 -13
  47. package/dist/wire.cjs.map +1 -1
  48. package/dist/wire.d.cts +3147 -5
  49. package/dist/wire.d.ts +3147 -5
  50. package/dist/wire.js +207 -14
  51. package/dist/wire.js.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
  54. package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
  55. package/dist/builders-8Hl1AYNU.cjs.map +0 -1
  56. package/dist/builders-DOjcKDWQ.js.map +0 -1
  57. package/dist/fold-BfyxxwS6.js +0 -334
  58. package/dist/fold-BfyxxwS6.js.map +0 -1
  59. package/dist/fold-C7I5GpBz.cjs +0 -447
  60. package/dist/fold-C7I5GpBz.cjs.map +0 -1
  61. package/dist/resolver-CHG4waXL.js.map +0 -1
  62. package/dist/resolver-D-jumvgM.cjs.map +0 -1
  63. package/dist/schemas-DOyVEPk7.js.map +0 -1
  64. package/dist/schemas-RChsAMWw.cjs.map +0 -1
@@ -1,5 +1,6 @@
1
- const require_fold = require("./fold-C7I5GpBz.cjs");
2
- const require_PageInsertBlankInput = require("./PageInsertBlankInput-CzShuSJI.cjs");
1
+ const require_derive = require("./derive-BbsDk_Kz.cjs");
2
+ const require_format = require("./format-D_epWlob.cjs");
3
+ const require_resolver = require("./resolver-CYVNV4hn.cjs");
3
4
  //#region src/dto/PdfAction.ts
4
5
  /**
5
6
  * The one Table-240 decode, shared by the reader, the schema's producers,
@@ -29,7 +30,7 @@ function createPageImageHandle(result, blobSource) {
29
30
  return {
30
31
  ...result,
31
32
  async objectUrl(signal) {
32
- if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.RuntimeUnavailable, "Object URLs are not available in this environment");
33
+ if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new require_derive.EngineError(require_format.EngineErrorCode.RuntimeUnavailable, "Object URLs are not available in this environment");
33
34
  const blob = await blobSource.blob(signal);
34
35
  const url = URL.createObjectURL(blob);
35
36
  return {
@@ -43,6 +44,18 @@ function createPageImageHandle(result, blobSource) {
43
44
  //#region src/dto/PdfSaveMode.ts
44
45
  const DEFAULT_PDF_SAVE_MODE = "incremental";
45
46
  //#endregion
47
+ //#region src/dto/RichText.ts
48
+ /** The plain-text projection of a rich document: paragraphs joined by `\r`,
49
+ * runs concatenated — what the engine writes to `/Contents`. */
50
+ function richTextPlainText(doc) {
51
+ return doc.paragraphs.map((p) => p.runs.map((r) => r.text).join("")).join("\r");
52
+ }
53
+ /** Plain text as rich paragraphs: one per line break, one run each, no
54
+ * overrides (the engine's own `/Contents` fallback shape). */
55
+ function richTextParagraphsFromPlainText(text) {
56
+ return text.split(/\r\n|\r|\n/).map((line) => ({ runs: [{ text: line }] }));
57
+ }
58
+ //#endregion
46
59
  //#region src/identity/PageObjectNumber.ts
47
60
  /**
48
61
  * Type guard for the rare malformed-PDF case where a page is a direct
@@ -259,7 +272,7 @@ function parseJpeg(buffer) {
259
272
  async function normalizeAttachmentFileSource(file, key) {
260
273
  const resolved = await resolveBinarySource(file.data);
261
274
  const name = file.name ?? resolved.name;
262
- if (!name) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.InvalidArg, "attachment file requires a name — pass { data, name } or a File whose name is set");
275
+ if (!name) throw new require_derive.EngineError(require_format.EngineErrorCode.InvalidArg, "attachment file requires a name — pass { data, name } or a File whose name is set");
263
276
  const mimeType = file.mimeType ?? resolved.mimeType;
264
277
  return {
265
278
  wireFile: {
@@ -297,7 +310,7 @@ async function normalizeFileAttachmentDraft(draft, allocateKey) {
297
310
  async function resolveStampSource(source) {
298
311
  const resolved = await resolveBinarySource(source);
299
312
  const meta = sniffBinaryMetadata(resolved.bytes);
300
- if (!meta) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.InvalidArg, "stamp source must be PNG, JPEG, or single-page PDF bytes (format is detected from the data, not the declared mime type)");
313
+ if (!meta) throw new require_derive.EngineError(require_format.EngineErrorCode.InvalidArg, "stamp source must be PNG, JPEG, or single-page PDF bytes (format is detected from the data, not the declared mime type)");
301
314
  return {
302
315
  ...resolved,
303
316
  mimeType: meta.mimeType
@@ -360,85 +373,6 @@ async function normalizeAnnotationPatch(patch) {
360
373
  }
361
374
  }
362
375
  //#endregion
363
- //#region src/annotation/relationships.ts
364
- /**
365
- * Classify one annotation by its relationship edge alone. Mirrors the
366
- * spec's "practical parser rule": no `/IRT` is top-level; `/RT /Group` is
367
- * a grouped subordinate; anything else with an `/IRT` is a reply (the
368
- * engine has already normalized a missing `/RT` to `'reply'`).
369
- */
370
- function classifyRelation(a) {
371
- if (!a.inReplyTo) return "top-level";
372
- return a.replyType === "group" ? "grouped-subordinate" : "reply";
373
- }
374
- /**
375
- * Stable string key for an {@link AnnotationRef}, used to match a child's
376
- * `inReplyTo` against a candidate parent's `ref`. Both sides are produced
377
- * by the engine with the same identity precedence (objectNumber, then nm,
378
- * then index), so equal keys mean "same annotation".
379
- */
380
- function refKey(ref) {
381
- switch (ref.kind) {
382
- case "objectNumber": return `obj:${ref.pageObjectNumber}:${ref.annotObjectNumber}`;
383
- case "nm": return `nm:${ref.pageObjectNumber}:${ref.nm}`;
384
- case "index": return `idx:${ref.pageObjectNumber}:${ref.index}`;
385
- }
386
- }
387
- /**
388
- * Compose a flat annotation list (one page, or a whole document via
389
- * `listRawAll()` flattened) into {@link AnnotationThread}s in primary
390
- * order.
391
- *
392
- * Rules (ISO 32000 §12.5.6.2 + the spec's UI rule):
393
- * - Annotations with no `/IRT` become thread primaries, in input order.
394
- * - A `/RT /Group` child is attached to its primary's `groupedParts`.
395
- * - A `/RT /R` (or default) child is attached to its primary's `replies`.
396
- * - A child whose parent is not in the input set is treated as an
397
- * orphan primary (defensive — e.g. a partial/filtered page slice).
398
- *
399
- * Limitations (v1, matches the legacy stack): threading is one level deep
400
- * — a reply-to-a-reply is keyed to its direct parent, so it only appears
401
- * nested if that direct parent is itself a primary. Callers that want a
402
- * sidebar of "most annotations, not widgets/popups" should pre-filter the
403
- * input; this helper is intentionally unopinionated about eligibility.
404
- */
405
- function buildThreads(annotations) {
406
- const byKey = /* @__PURE__ */ new Map();
407
- for (const a of annotations) {
408
- byKey.set(refKey(a.ref), a);
409
- if (a.nm && a.nm.length > 0) byKey.set(`nm:${a.ref.pageObjectNumber}:${a.nm}`, a);
410
- }
411
- const threads = [];
412
- const threadByPrimaryKey = /* @__PURE__ */ new Map();
413
- const primaryThread = (primary) => {
414
- const key = refKey(primary.ref);
415
- let thread = threadByPrimaryKey.get(key);
416
- if (!thread) {
417
- thread = {
418
- primary,
419
- groupedParts: [],
420
- replies: []
421
- };
422
- threadByPrimaryKey.set(key, thread);
423
- threads.push(thread);
424
- }
425
- return thread;
426
- };
427
- for (const a of annotations) if (!a.inReplyTo) primaryThread(a);
428
- for (const a of annotations) {
429
- if (!a.inReplyTo) continue;
430
- const parent = byKey.get(refKey(a.inReplyTo));
431
- if (!parent || parent.inReplyTo) {
432
- primaryThread(a);
433
- continue;
434
- }
435
- const thread = primaryThread(parent);
436
- if (a.replyType === "group") thread.groupedParts.push(a);
437
- else thread.replies.push(a);
438
- }
439
- return threads;
440
- }
441
- //#endregion
442
376
  //#region src/annotation/comments.ts
443
377
  /** Subtypes that never participate in comment threads. `unsupported`
444
378
  * covers `/Popup` dictionaries (raw code 16) and unreadable foreign
@@ -469,10 +403,10 @@ function buildCommentThreads(annotations, opts = {}) {
469
403
  const eligible = annotations.filter((a) => !EXCLUDED_SUBTYPES.has(a.subtype));
470
404
  const byKey = /* @__PURE__ */ new Map();
471
405
  for (const a of eligible) {
472
- const key = refKey(a.ref);
406
+ const key = require_derive.refKey(a.ref);
473
407
  if (!byKey.has(key)) byKey.set(key, a);
474
408
  if (a.nm) {
475
- const aliasKey = refKey({
409
+ const aliasKey = require_derive.refKey({
476
410
  kind: "nm",
477
411
  pageObjectNumber: a.pageObjectNumber,
478
412
  nm: a.nm
@@ -483,9 +417,9 @@ function buildCommentThreads(annotations, opts = {}) {
483
417
  const children = /* @__PURE__ */ new Map();
484
418
  for (const a of eligible) {
485
419
  if (!a.inReplyTo) continue;
486
- const parent = byKey.get(refKey(a.inReplyTo));
420
+ const parent = byKey.get(require_derive.refKey(a.inReplyTo));
487
421
  if (!parent) continue;
488
- const parentKey = refKey(parent.ref);
422
+ const parentKey = require_derive.refKey(parent.ref);
489
423
  const list = children.get(parentKey);
490
424
  if (list) list.push(a);
491
425
  else children.set(parentKey, [a]);
@@ -496,15 +430,15 @@ function buildCommentThreads(annotations, opts = {}) {
496
430
  const replies = [];
497
431
  const groupedParts = [];
498
432
  const states = [];
499
- visited.add(refKey(root.ref));
500
- const stack = [...children.get(refKey(root.ref)) ?? []];
433
+ visited.add(require_derive.refKey(root.ref));
434
+ const stack = [...children.get(require_derive.refKey(root.ref)) ?? []];
501
435
  while (stack.length > 0) {
502
436
  const a = stack.pop();
503
- const key = refKey(a.ref);
437
+ const key = require_derive.refKey(a.ref);
504
438
  if (visited.has(key)) continue;
505
439
  visited.add(key);
506
440
  if (isStateAnnotation(a)) states.push(a);
507
- else if (classifyRelation(a) === "grouped-subordinate") groupedParts.push(a);
441
+ else if (require_derive.classifyRelation(a) === "grouped-subordinate") groupedParts.push(a);
508
442
  else replies.push(a);
509
443
  stack.push(...children.get(key) ?? []);
510
444
  }
@@ -520,7 +454,7 @@ function buildCommentThreads(annotations, opts = {}) {
520
454
  };
521
455
  for (const a of eligible) if (!a.inReplyTo && !isStateAnnotation(a)) walk(a);
522
456
  for (const a of eligible) {
523
- if (visited.has(refKey(a.ref))) continue;
457
+ if (visited.has(require_derive.refKey(a.ref))) continue;
524
458
  if (isStateAnnotation(a)) continue;
525
459
  walk(a);
526
460
  }
@@ -790,15 +724,22 @@ function appearanceImpactOf(current, patch) {
790
724
  const cur = current;
791
725
  const pat = patch;
792
726
  const subtype = patch.subtype;
727
+ const caption = pat.caption === void 0 ? cur.caption : pat.caption;
728
+ const contentsPainted = CONTENTS_PAINTED.has(subtype) || [
729
+ "line",
730
+ "polygon",
731
+ "polyline"
732
+ ].includes(subtype) && !!caption?.enabled;
793
733
  const touched = [];
794
734
  for (const [key, value] of Object.entries(pat)) {
795
735
  if (value === void 0 || INERT_KEYS.has(key)) continue;
796
- if (key === "contents" && !CONTENTS_PAINTED.has(subtype)) continue;
736
+ if (key === "contents" && !contentsPainted) continue;
797
737
  if (key === "rotation" && ADVISORY_ROTATION.has(subtype)) continue;
798
738
  if (!semanticEqual(value, cur[key])) touched.push(key);
799
739
  }
800
740
  if (touched.length === 0) return "inert";
801
- const geometryKeys = TRANSLATABLE_GEOMETRY[subtype];
741
+ const baseGeometryKeys = TRANSLATABLE_GEOMETRY[subtype];
742
+ const geometryKeys = baseGeometryKeys && (subtype === "polygon" || subtype === "polyline") && cur.caption?.center ? [...baseGeometryKeys, "caption"] : baseGeometryKeys;
802
743
  if (!geometryKeys) return "regenerate";
803
744
  if (!touched.every((k) => geometryKeys.includes(k))) return "regenerate";
804
745
  return isRigidTranslation(cur, pat, subtype, geometryKeys) ? "translation" : "regenerate";
@@ -855,6 +796,1336 @@ function decodeFieldRefKey(key) {
855
796
  return null;
856
797
  }
857
798
  //#endregion
799
+ //#region src/signature/analysis/pdf-value.ts
800
+ /**
801
+ * Parse the fork's canonical shallow serialisation:
802
+ * dict <</Key value/Key2 value>> (keys sorted, `#xx`-escaped)
803
+ * array [item item]
804
+ * ref n 0 R
805
+ * stream stream(length,sha256hex)<<dict>>
806
+ * name /Name (with #xx escapes)
807
+ * string (literal with backslash escapes) or <hex>
808
+ * number -12.5 bool true|false null
809
+ */
810
+ function parsePdfValue(text) {
811
+ const parser = new Parser(text);
812
+ const value = parser.value();
813
+ parser.skipSpaces();
814
+ if (!parser.atEnd()) throw new Error(`trailing input at ${parser.pos}: ${text.slice(parser.pos, parser.pos + 20)}`);
815
+ return value;
816
+ }
817
+ var Parser = class {
818
+ constructor(text) {
819
+ this.text = text;
820
+ require_derive._defineProperty(this, "pos", 0);
821
+ }
822
+ atEnd() {
823
+ return this.pos >= this.text.length;
824
+ }
825
+ skipSpaces() {
826
+ while (this.pos < this.text.length && this.text[this.pos] === " ") this.pos++;
827
+ }
828
+ value() {
829
+ this.skipSpaces();
830
+ const t = this.text;
831
+ if (t.startsWith("<<", this.pos)) return this.dict();
832
+ if (t.startsWith("stream(", this.pos)) return this.stream();
833
+ if (t[this.pos] === "[") return this.array();
834
+ if (t[this.pos] === "/") return {
835
+ t: "name",
836
+ v: this.name()
837
+ };
838
+ if (t[this.pos] === "(") return {
839
+ t: "string",
840
+ v: this.literalString()
841
+ };
842
+ if (t[this.pos] === "<") return {
843
+ t: "string",
844
+ v: this.hexString()
845
+ };
846
+ if (t.startsWith("true", this.pos)) {
847
+ this.pos += 4;
848
+ return {
849
+ t: "bool",
850
+ v: true
851
+ };
852
+ }
853
+ if (t.startsWith("false", this.pos)) {
854
+ this.pos += 5;
855
+ return {
856
+ t: "bool",
857
+ v: false
858
+ };
859
+ }
860
+ if (t.startsWith("null", this.pos)) {
861
+ this.pos += 4;
862
+ return { t: "null" };
863
+ }
864
+ const ref = /^(\d+) 0 R(?![0-9A-Za-z])/.exec(t.slice(this.pos));
865
+ if (ref) {
866
+ this.pos += ref[0].length;
867
+ return {
868
+ t: "ref",
869
+ num: Number(ref[1])
870
+ };
871
+ }
872
+ const num = /^[+-]?(?:\d+\.?\d*|\.\d+)/.exec(t.slice(this.pos));
873
+ if (num) {
874
+ this.pos += num[0].length;
875
+ return {
876
+ t: "number",
877
+ v: Number(num[0])
878
+ };
879
+ }
880
+ throw new Error(`unexpected input at ${this.pos}: ${t.slice(this.pos, this.pos + 20)}`);
881
+ }
882
+ dict() {
883
+ return {
884
+ t: "dict",
885
+ entries: this.dictEntries()
886
+ };
887
+ }
888
+ dictEntries() {
889
+ this.expect("<<");
890
+ const entries = {};
891
+ for (;;) {
892
+ this.skipSpaces();
893
+ if (this.text.startsWith(">>", this.pos)) {
894
+ this.pos += 2;
895
+ return entries;
896
+ }
897
+ if (this.text[this.pos] !== "/") throw new Error(`expected a key at ${this.pos}`);
898
+ const key = this.name();
899
+ this.skipSpaces();
900
+ entries[key] = this.value();
901
+ }
902
+ }
903
+ stream() {
904
+ const m = /^stream\((\d+),([0-9a-fA-F]*)\)/.exec(this.text.slice(this.pos));
905
+ if (!m) throw new Error(`malformed stream signature at ${this.pos}`);
906
+ this.pos += m[0].length;
907
+ const dict = this.dictEntries();
908
+ return {
909
+ t: "stream",
910
+ length: Number(m[1]),
911
+ sha256: m[2].toLowerCase(),
912
+ dict
913
+ };
914
+ }
915
+ array() {
916
+ this.expect("[");
917
+ const items = [];
918
+ for (;;) {
919
+ this.skipSpaces();
920
+ if (this.text[this.pos] === "]") {
921
+ this.pos++;
922
+ return {
923
+ t: "array",
924
+ items
925
+ };
926
+ }
927
+ items.push(this.value());
928
+ }
929
+ }
930
+ name() {
931
+ this.expect("/");
932
+ let out = "";
933
+ while (this.pos < this.text.length) {
934
+ const ch = this.text[this.pos];
935
+ if (ch === " " || ch === "/" || ch === "[" || ch === "]" || ch === "<" || ch === ">" || ch === "(" || ch === ")") break;
936
+ if (ch === "#" && /^[0-9A-Fa-f]{2}/.test(this.text.slice(this.pos + 1, this.pos + 3))) {
937
+ out += String.fromCharCode(parseInt(this.text.slice(this.pos + 1, this.pos + 3), 16));
938
+ this.pos += 3;
939
+ continue;
940
+ }
941
+ out += ch;
942
+ this.pos++;
943
+ }
944
+ return out;
945
+ }
946
+ literalString() {
947
+ this.expect("(");
948
+ let depth = 1;
949
+ let out = "";
950
+ while (this.pos < this.text.length) {
951
+ const ch = this.text[this.pos++];
952
+ if (ch === "\\") {
953
+ const next = this.text[this.pos++];
954
+ switch (next) {
955
+ case "n":
956
+ out += "\n";
957
+ break;
958
+ case "r":
959
+ out += "\r";
960
+ break;
961
+ case "t":
962
+ out += " ";
963
+ break;
964
+ case "b":
965
+ out += "\b";
966
+ break;
967
+ case "f":
968
+ out += "\f";
969
+ break;
970
+ case "\n": break;
971
+ case "\r":
972
+ if (this.text[this.pos] === "\n") this.pos++;
973
+ break;
974
+ default: {
975
+ const oct = /^[0-7]{1,3}/.exec(this.text.slice(this.pos - 1));
976
+ if (oct) {
977
+ out += String.fromCharCode(parseInt(oct[0], 8));
978
+ this.pos += oct[0].length - 1;
979
+ } else out += next;
980
+ }
981
+ }
982
+ continue;
983
+ }
984
+ if (ch === "(") depth++;
985
+ if (ch === ")") {
986
+ depth--;
987
+ if (depth === 0) return out;
988
+ }
989
+ out += ch;
990
+ }
991
+ throw new Error("unterminated string");
992
+ }
993
+ hexString() {
994
+ this.expect("<");
995
+ const end = this.text.indexOf(">", this.pos);
996
+ if (end < 0) throw new Error("unterminated hex string");
997
+ const hex = this.text.slice(this.pos, end).replace(/\s+/g, "");
998
+ this.pos = end + 1;
999
+ let out = "";
1000
+ for (let i = 0; i < hex.length; i += 2) out += String.fromCharCode(parseInt(hex.slice(i, i + 2).padEnd(2, "0"), 16));
1001
+ return out;
1002
+ }
1003
+ expect(token) {
1004
+ if (!this.text.startsWith(token, this.pos)) throw new Error(`expected ${token} at ${this.pos}`);
1005
+ this.pos += token.length;
1006
+ }
1007
+ };
1008
+ /** Structural equality of two parsed values. */
1009
+ function pdfValueEquals(a, b) {
1010
+ return stableStringify(a) === stableStringify(b);
1011
+ }
1012
+ function stableStringify(value) {
1013
+ if (value === null) return "null";
1014
+ switch (value.t) {
1015
+ case "dict": return `<<${Object.keys(value.entries).sort().map((k) => `/${k} ${stableStringify(value.entries[k])}`).join("")}>>`;
1016
+ case "stream": return `stream(${value.length},${value.sha256})${stableStringify({
1017
+ t: "dict",
1018
+ entries: value.dict
1019
+ })}`;
1020
+ case "array": return `[${value.items.map(stableStringify).join(" ")}]`;
1021
+ case "ref": return `${value.num} 0 R`;
1022
+ case "name": return `/${encodeName(value.v)}`;
1023
+ case "string": return `(${value.v.replace(/[\\()]/g, (ch) => `\\${ch}`)})`;
1024
+ case "number": return String(value.v);
1025
+ case "bool": return value.v ? "true" : "false";
1026
+ case "null": return "null";
1027
+ }
1028
+ }
1029
+ function dictEntries(value) {
1030
+ if (!value) return null;
1031
+ if (value.t === "dict") return value.entries;
1032
+ if (value.t === "stream") return value.dict;
1033
+ return null;
1034
+ }
1035
+ /** Keys whose value differs (added, removed, or changed) between two dictionaries. */
1036
+ function changedKeys(a, b) {
1037
+ const ea = dictEntries(a) ?? {};
1038
+ const eb = dictEntries(b) ?? {};
1039
+ const out = /* @__PURE__ */ new Set();
1040
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(ea), ...Object.keys(eb)])) if (!pdfValueEquals(ea[key] ?? null, eb[key] ?? null)) out.add(key);
1041
+ return out;
1042
+ }
1043
+ function refsOf(value) {
1044
+ if (!value) return [];
1045
+ if (value.t === "ref") return [value.num];
1046
+ if (value.t === "array") return value.items.flatMap(refsOf);
1047
+ return [];
1048
+ }
1049
+ /** `#xx`-escape a name the way the PDF writer does: delimiters, whitespace, `#`, and non-printables. */
1050
+ function encodeName(name) {
1051
+ let out = "";
1052
+ for (const ch of name) {
1053
+ const code = ch.charCodeAt(0);
1054
+ if (code < 33 || code > 126 || "#/[]<>(){}%".includes(ch)) out += `#${code.toString(16).toUpperCase().padStart(2, "0")}`;
1055
+ else out += ch;
1056
+ }
1057
+ return out;
1058
+ }
1059
+ //#endregion
1060
+ //#region src/signature/analysis/verdict.ts
1061
+ const RANK = {
1062
+ unchanged: 0,
1063
+ permitted: 1,
1064
+ indeterminate: 2,
1065
+ forbidden: 3
1066
+ };
1067
+ /** The worst of several verdicts (a certification replay's steps). */
1068
+ function worstVerdict(steps) {
1069
+ let worst = "unchanged";
1070
+ for (const s of steps) if (RANK[s.verdict] > RANK[worst]) worst = s.verdict;
1071
+ return worst;
1072
+ }
1073
+ /**
1074
+ * One aggregation rule everywhere: a proven violation decides, whatever
1075
+ * else could not be established; missing evidence never becomes a pass.
1076
+ */
1077
+ function conclude(findings, hasEffectiveChanges) {
1078
+ if (findings.some((f) => f.verdict === "forbidden")) return "forbidden";
1079
+ if (findings.some((f) => f.verdict === "incomplete")) return "indeterminate";
1080
+ return hasEffectiveChanges ? "permitted" : "unchanged";
1081
+ }
1082
+ /** The finding that explains a verdict: the violation, else the gap, else the most notable allowance. */
1083
+ function primaryFinding(findings) {
1084
+ return findings.find((f) => f.verdict === "forbidden") ?? findings.find((f) => f.verdict === "incomplete") ?? findings.find((f) => f.verdict === "permitted");
1085
+ }
1086
+ function assessmentOf(step) {
1087
+ const primary = primaryFinding(step.findings);
1088
+ return {
1089
+ verdict: step.verdict,
1090
+ complete: !step.findings.some((f) => f.verdict === "incomplete"),
1091
+ ...primary ? { primary } : {},
1092
+ findings: step.findings
1093
+ };
1094
+ }
1095
+ /**
1096
+ * A certification window: the net verdict and every replayed step, under
1097
+ * the same rule. A step's violation is a violation of the certification
1098
+ * even when the final state no longer shows it (corpus v3/83).
1099
+ */
1100
+ function combine(net, steps) {
1101
+ const findings = [...net.findings];
1102
+ for (const s of steps) {
1103
+ if (s === net) continue;
1104
+ for (const f of s.findings) {
1105
+ if (f.verdict === "permitted") continue;
1106
+ findings.push({
1107
+ ...f,
1108
+ detail: `revision ${s.newer}: ${f.detail ?? f.rule}`
1109
+ });
1110
+ }
1111
+ }
1112
+ const verdict = conclude(findings, net.verdict !== "unchanged" || steps.some((s) => s.verdict !== "unchanged"));
1113
+ const primary = primaryFinding(findings);
1114
+ return {
1115
+ verdict,
1116
+ complete: !findings.some((f) => f.verdict === "incomplete"),
1117
+ ...primary ? { primary } : {},
1118
+ findings
1119
+ };
1120
+ }
1121
+ //#endregion
1122
+ //#region src/signature/analysis/evaluate.ts
1123
+ const SIDES = ["old", "new"];
1124
+ const TRAILER_KEYS = /* @__PURE__ */ new Set([
1125
+ "Size",
1126
+ "Prev",
1127
+ "XRefStm",
1128
+ "Info",
1129
+ "ID",
1130
+ "Root",
1131
+ "Type",
1132
+ "W",
1133
+ "Index",
1134
+ "Filter",
1135
+ "DecodeParms",
1136
+ "Length",
1137
+ "Encrypt"
1138
+ ]);
1139
+ const CATALOG_KEYS = /* @__PURE__ */ new Set([
1140
+ "Metadata",
1141
+ "Extensions",
1142
+ "Version",
1143
+ "DSS",
1144
+ "AcroForm",
1145
+ "Perms"
1146
+ ]);
1147
+ const ACROFORM_KEYS = /* @__PURE__ */ new Set([
1148
+ "DR",
1149
+ "DA",
1150
+ "Q",
1151
+ "SigFlags",
1152
+ "NeedAppearances",
1153
+ "Fields"
1154
+ ]);
1155
+ /**
1156
+ * What a form field may change after a signature, by the level in force.
1157
+ * Every key here has a corpus case behind it (`signature-compat`); a key
1158
+ * not listed is forbidden until one does.
1159
+ *
1160
+ * annotate (an approval signature: "Form Fill-in, Signing and Commenting
1161
+ * are allowed"): everything but the field's identity and its place in
1162
+ * the tree. v1/08 /V+/AP, 09 /Ff, 10 /Rect, 11 /F, 18 /TU, 21 /DA; 13 /T
1163
+ * forbidden. /AA (v1/12) and /A are accepted by Acrobat and deliberately
1164
+ * NOT listed: an action on a signed form is executable content.
1165
+ *
1166
+ * fill (a P=2 certification): value and appearance. v1/26 /V+/AP, v3/64
1167
+ * /Ff (ReadOnly added), 65 /DA, 68 /AP stream; v3/66 /TU, 67 /F, v2/50
1168
+ * /Rect forbidden.
1169
+ */
1170
+ const FIELD_KEYS_BY_LEVEL = {
1171
+ annotate: /* @__PURE__ */ new Set([
1172
+ "V",
1173
+ "AS",
1174
+ "AP",
1175
+ "I",
1176
+ "DA",
1177
+ "Ff",
1178
+ "RV",
1179
+ "MK",
1180
+ "Q",
1181
+ "TU",
1182
+ "F",
1183
+ "Rect",
1184
+ "BS",
1185
+ "Opt",
1186
+ "DV",
1187
+ "MaxLen"
1188
+ ]),
1189
+ fill: /* @__PURE__ */ new Set([
1190
+ "V",
1191
+ "AS",
1192
+ "AP",
1193
+ "I",
1194
+ "RV",
1195
+ "DA",
1196
+ "Ff"
1197
+ ])
1198
+ };
1199
+ /** A separate widget dictionary: the presentation subset of the same tables. */
1200
+ const WIDGET_KEYS_BY_LEVEL = {
1201
+ annotate: /* @__PURE__ */ new Set([
1202
+ "AS",
1203
+ "AP",
1204
+ "DA",
1205
+ "MK",
1206
+ "Q",
1207
+ "TU",
1208
+ "F",
1209
+ "Rect",
1210
+ "BS"
1211
+ ]),
1212
+ fill: /* @__PURE__ */ new Set([
1213
+ "AS",
1214
+ "AP",
1215
+ "DA"
1216
+ ])
1217
+ };
1218
+ /**
1219
+ * Signing an EXISTING field: its value, appearance and ReadOnly at any
1220
+ * level; after an approval signature also its tooltip and a /Lock installed
1221
+ * with the signature (v1/18, 19: both valid; under P=2 the combination is
1222
+ * rejected, v3/69, so neither is allowed at `fill`).
1223
+ */
1224
+ const SIGNED_FIELD_KEYS_BY_LEVEL = {
1225
+ annotate: /* @__PURE__ */ new Set([
1226
+ "V",
1227
+ "AP",
1228
+ "AS",
1229
+ "Ff",
1230
+ "TU",
1231
+ "Lock"
1232
+ ]),
1233
+ fill: /* @__PURE__ */ new Set([
1234
+ "V",
1235
+ "AP",
1236
+ "AS",
1237
+ "Ff"
1238
+ ])
1239
+ };
1240
+ const SIGNED_WIDGET_KEYS = /* @__PURE__ */ new Set(["AP", "AS"]);
1241
+ const FF_READ_ONLY = 1;
1242
+ function fieldKeysFor(level) {
1243
+ return level === "annotate" ? FIELD_KEYS_BY_LEVEL.annotate : FIELD_KEYS_BY_LEVEL.fill;
1244
+ }
1245
+ function widgetKeysFor(level) {
1246
+ return level === "annotate" ? WIDGET_KEYS_BY_LEVEL.annotate : WIDGET_KEYS_BY_LEVEL.fill;
1247
+ }
1248
+ function signedFieldKeysFor(level) {
1249
+ return level === "annotate" ? SIGNED_FIELD_KEYS_BY_LEVEL.annotate : SIGNED_FIELD_KEYS_BY_LEVEL.fill;
1250
+ }
1251
+ /**
1252
+ * Rules whose permitted findings do not make a revision a CHANGE of the
1253
+ * document: an object written again with the sealed value, an object nobody
1254
+ * references, a cross-reference container, the trailer's own bookkeeping.
1255
+ * A window with nothing else is `unchanged` (Acrobat: "not modified").
1256
+ */
1257
+ const NON_EFFECTIVE_RULES = /* @__PURE__ */ new Set([
1258
+ "identical-rewrite",
1259
+ "orphan",
1260
+ "xref-container",
1261
+ "trailer"
1262
+ ]);
1263
+ /** The level a step runs at, and the locks it enforces: what the OLDER revision's signatures established. */
1264
+ function restrictionsOf(before) {
1265
+ const protection = require_resolver.deriveProtection(before.signatures);
1266
+ return {
1267
+ level: protection.judged ?? "annotate",
1268
+ locks: protection.fieldLocks
1269
+ };
1270
+ }
1271
+ /**
1272
+ * Judge one pairwise step. Every reference to a changed object, in the
1273
+ * older AND the newer revision, must be claimed by a rule that inspected
1274
+ * that use; an unclaimed reference is `unexplained` and forbidden. A
1275
+ * truncated value is `incomplete`, never permitted. Pure: JSON in, JSON
1276
+ * out, the same code in the browser worker, on the server and in tests.
1277
+ */
1278
+ function evaluateStep(input) {
1279
+ const changes = withEvidenceCheck(input.changes);
1280
+ const { level, locks } = input.restrictions ?? restrictionsOf(input.before);
1281
+ const levelInForce = input.levelOverride ?? level;
1282
+ const ctx = new StepContext({
1283
+ ...input,
1284
+ changes
1285
+ }, levelInForce, locks);
1286
+ ruleRevisionHealth(ctx);
1287
+ ruleXrefContainer(ctx);
1288
+ ruleIdenticalRewrite(ctx);
1289
+ ruleFieldLock(ctx);
1290
+ ruleTrailer(ctx);
1291
+ ruleInfo(ctx);
1292
+ ruleMetadata(ctx);
1293
+ ruleCatalog(ctx);
1294
+ ruleAcroForm(ctx);
1295
+ ruleDss(ctx);
1296
+ ruleSignatureFieldAdded(ctx);
1297
+ ruleSignatureAdded(ctx);
1298
+ ruleFormFill(ctx);
1299
+ ruleAnnotation(ctx);
1300
+ ruleUnexplained(ctx);
1301
+ const verdict = conclude(ctx.findings, ctx.findings.some((f) => f.verdict === "permitted" && !NON_EFFECTIVE_RULES.has(f.rule)));
1302
+ return {
1303
+ older: input.older,
1304
+ newer: input.newer,
1305
+ levelInForce,
1306
+ locks,
1307
+ verdict,
1308
+ findings: ctx.findings,
1309
+ changes
1310
+ };
1311
+ }
1312
+ /**
1313
+ * The evaluator never trusts its transport to be honest about gaps: a side
1314
+ * that exists must carry a value, and an object whose value is missing is
1315
+ * judged as truncated, whatever the flag says. Cross-reference containers
1316
+ * are exempt (their members are judged on their own).
1317
+ */
1318
+ function withEvidenceCheck(changes) {
1319
+ return changes.map((c) => {
1320
+ if (c.value.truncated || c.kind === "xref" || c.kind === "objstm") return c;
1321
+ const missingOld = c.present.old && (c.value.old === null || c.read?.old === "failed");
1322
+ const missingNew = c.present.new && (c.value.new === null || c.read?.new === "failed");
1323
+ return missingOld || missingNew ? {
1324
+ ...c,
1325
+ value: {
1326
+ ...c.value,
1327
+ truncated: true
1328
+ }
1329
+ } : c;
1330
+ });
1331
+ }
1332
+ /**
1333
+ * The transport's verdict on each revision as a whole. A sealed revision
1334
+ * Acrobat cannot judge later changes to (sparse cross-reference table, a
1335
+ * reachable bare-reference object) makes every step over it indeterminate:
1336
+ * our verdict would otherwise disagree with the validator recipients use,
1337
+ * and for a reason that has nothing to do with the change. An incomplete
1338
+ * reachability walk on either side leaves every use unproven.
1339
+ */
1340
+ function ruleRevisionHealth(ctx) {
1341
+ const health = ctx.input.health;
1342
+ if (!health) return;
1343
+ const base = health.old;
1344
+ if (base.sparseXref || base.bareReferences > 0) {
1345
+ 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("; ");
1346
+ ctx.findings.push({
1347
+ rule: "base-unverifiable",
1348
+ verdict: "incomplete",
1349
+ objectNumber: 0,
1350
+ detail: `the signed revision cannot be verified against later changes by Acrobat (${why}); no verdict is given rather than one Acrobat would contradict`
1351
+ });
1352
+ }
1353
+ for (const side of SIDES) if (!health[side].referrersComplete) ctx.findings.push({
1354
+ rule: "unexplained",
1355
+ verdict: "incomplete",
1356
+ objectNumber: 0,
1357
+ detail: `the ${side === "old" ? "older" : "newer"} revision's reachability walk hit its budget; not every use of a changed object is known`
1358
+ });
1359
+ }
1360
+ /**
1361
+ * Same effective value on both sides: same canonical serialisation (keys
1362
+ * sorted, whitespace normalised - Acrobat compares dictionaries by value,
1363
+ * corpus v3/61-62), same stream data (by bytes, v2/45), and both sides
1364
+ * actually read. A failed read serialises as "null" and would match another
1365
+ * failed read - evidence of nothing.
1366
+ */
1367
+ function sameEffectiveValue(c) {
1368
+ 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;
1369
+ }
1370
+ function indexRoles(s) {
1371
+ const fieldByObj = /* @__PURE__ */ new Map();
1372
+ const widgetToField = /* @__PURE__ */ new Map();
1373
+ const sigFields = /* @__PURE__ */ new Set();
1374
+ for (const f of s.fields) {
1375
+ fieldByObj.set(f.objectNumber, f);
1376
+ if (f.family === "signature") sigFields.add(f.objectNumber);
1377
+ for (const w of f.widgets) widgetToField.set(w, f.objectNumber);
1378
+ }
1379
+ const signedFields = /* @__PURE__ */ new Set();
1380
+ for (const sig of s.signatures) if (sig.signed && sig.field.kind === "objectNumber") signedFields.add(sig.field.fieldObjectNumber);
1381
+ return {
1382
+ fieldByObj,
1383
+ widgetToField,
1384
+ pages: new Set(s.pages),
1385
+ signedFields,
1386
+ sigFields
1387
+ };
1388
+ }
1389
+ function edgeKey(e) {
1390
+ return `${e.parent}:${e.label}`;
1391
+ }
1392
+ var StepContext = class {
1393
+ constructor(input, level, locks) {
1394
+ this.input = input;
1395
+ this.level = level;
1396
+ this.locks = locks;
1397
+ require_derive._defineProperty(this, "findings", []);
1398
+ require_derive._defineProperty(this, "byNum", /* @__PURE__ */ new Map());
1399
+ require_derive._defineProperty(this, "before", void 0);
1400
+ require_derive._defineProperty(this, "after", void 0);
1401
+ require_derive._defineProperty(this, "claims", /* @__PURE__ */ new Map());
1402
+ require_derive._defineProperty(this, "locked", /* @__PURE__ */ new Set());
1403
+ for (const c of input.changes) this.byNum.set(c.objectNumber, c);
1404
+ this.before = indexRoles(input.before);
1405
+ this.after = indexRoles(input.after);
1406
+ }
1407
+ get changes() {
1408
+ return this.input.changes;
1409
+ }
1410
+ allows(needed) {
1411
+ return require_resolver.levelAllows(this.level, needed);
1412
+ }
1413
+ claimEdge(c, side, edge, rule, allow = true) {
1414
+ if (this.locked.has(c.objectNumber)) return;
1415
+ const key = `${c.objectNumber}:${side}:${edgeKey(edge)}`;
1416
+ if (!this.claims.has(key)) this.claims.set(key, {
1417
+ rule,
1418
+ allow
1419
+ });
1420
+ }
1421
+ claimAll(c, rule, allow = true) {
1422
+ for (const side of SIDES) for (const e of c.usage[side]) this.claimEdge(c, side, e, rule, allow);
1423
+ }
1424
+ /**
1425
+ * Claim the references that are the same in both revisions; a reference
1426
+ * gained or lost stays unexplained. `allow: false` explains a condemned
1427
+ * object's edges (one cause in the report, not a cascade) without letting
1428
+ * anything hang off it.
1429
+ */
1430
+ claimStable(c, rule, allow = true) {
1431
+ const oldKeys = new Set(c.usage.old.map(edgeKey));
1432
+ const newKeys = new Set(c.usage.new.map(edgeKey));
1433
+ for (const e of c.usage.old) if (newKeys.has(edgeKey(e))) this.claimEdge(c, "old", e, rule, allow);
1434
+ for (const e of c.usage.new) if (oldKeys.has(edgeKey(e))) this.claimEdge(c, "new", e, rule, allow);
1435
+ }
1436
+ isClaimed(c, side, edge) {
1437
+ return this.claims.has(`${c.objectNumber}:${side}:${edgeKey(edge)}`);
1438
+ }
1439
+ /** The use was inspected by a rule that allowed it. */
1440
+ isAllowed(c, side, edge) {
1441
+ return this.claims.get(`${c.objectNumber}:${side}:${edgeKey(edge)}`)?.allow === true;
1442
+ }
1443
+ permitted(c, rule, detail) {
1444
+ if (this.locked.has(c.objectNumber)) return;
1445
+ this.findings.push({
1446
+ rule,
1447
+ verdict: "permitted",
1448
+ objectNumber: c.objectNumber,
1449
+ detail
1450
+ });
1451
+ }
1452
+ /**
1453
+ * A violation is only proven on evidence: a rule condemning an object
1454
+ * whose value could not be read or inspected is reporting what it could
1455
+ * not see, and that is `incomplete`, never `forbidden`.
1456
+ */
1457
+ forbidden(c, rule, detail, edge) {
1458
+ this.findings.push({
1459
+ rule,
1460
+ verdict: c.value.truncated ? "incomplete" : "forbidden",
1461
+ objectNumber: c.objectNumber,
1462
+ edge: edge ? edgeKey(edge) : void 0,
1463
+ detail: c.value.truncated ? `${detail} (judged on missing evidence: not proven)` : detail
1464
+ });
1465
+ }
1466
+ /**
1467
+ * Claim every reference under a set of parents (an appearance subtree,
1468
+ * the DSS): objects whose references come from `parents` through labels
1469
+ * `accept`, then everything referenced from those objects, and so on.
1470
+ * Runs per side, because a replaced appearance stream exists on one
1471
+ * side only.
1472
+ */
1473
+ claimSubtree(side, roots, accept, rule) {
1474
+ const joined = /* @__PURE__ */ new Set();
1475
+ let grew = true;
1476
+ while (grew) {
1477
+ grew = false;
1478
+ for (const c of this.changes) {
1479
+ if (roots.has(c.objectNumber) || joined.has(c.objectNumber)) continue;
1480
+ if (this.locked.has(c.objectNumber)) continue;
1481
+ const edges = c.usage[side];
1482
+ if (edges.length === 0) continue;
1483
+ const inside = edges.filter((e) => roots.has(e.parent) && accept(e.label) || joined.has(e.parent));
1484
+ if (inside.length === 0) continue;
1485
+ for (const e of inside) this.claimEdge(c, side, e, rule);
1486
+ if (edges.every((e) => inside.includes(e) || this.isAllowed(c, side, e))) {
1487
+ joined.add(c.objectNumber);
1488
+ grew = true;
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ isIdentical(c) {
1494
+ return sameEffectiveValue(c);
1495
+ }
1496
+ /** Signatures signed in the newer revision but not the older: this step's signing events. */
1497
+ newlySigned() {
1498
+ const out = [];
1499
+ for (const sig of this.input.after.signatures) {
1500
+ if (!sig.signed || sig.field.kind !== "objectNumber") continue;
1501
+ if (this.before.signedFields.has(sig.field.fieldObjectNumber)) continue;
1502
+ out.push({
1503
+ fieldObjectNumber: sig.field.fieldObjectNumber,
1504
+ sig
1505
+ });
1506
+ }
1507
+ return out;
1508
+ }
1509
+ };
1510
+ function ruleXrefContainer(ctx) {
1511
+ for (const c of ctx.changes) if (c.kind === "xref" || c.kind === "objstm") {
1512
+ ctx.claimAll(c, "xref-container");
1513
+ ctx.permitted(c, "xref-container", "cross-reference container; members are judged on their own");
1514
+ }
1515
+ }
1516
+ /**
1517
+ * An object written again with the value the sealed revision holds is not
1518
+ * a modification: Acrobat compares objects by value and reports such a
1519
+ * revision as "not modified" for every role tried - document information,
1520
+ * a custom catalog entry, a page, the signed signature's own widget, a text
1521
+ * field, an annotation, the encryption dictionary, reserialised with keys
1522
+ * reordered, under a certification, under a field lock (corpus v1/02-07,
1523
+ * 23, v2/47, v3/54-62, 70, 71, 77). Under P=1 nothing is established and
1524
+ * the rewrite stays forbidden. Claimed here so the object-kind rules skip
1525
+ * it; the verdict treats it as no effective change.
1526
+ */
1527
+ function ruleIdenticalRewrite(ctx) {
1528
+ for (const c of ctx.changes) {
1529
+ if (!ctx.isIdentical(c)) continue;
1530
+ if (ctx.allows("fill")) {
1531
+ ctx.claimAll(c, "identical-rewrite");
1532
+ ctx.permitted(c, "identical-rewrite", "written again with the sealed value");
1533
+ } else {
1534
+ ctx.claimAll(c, "identical-rewrite", false);
1535
+ ctx.forbidden(c, "identical-rewrite", `written again with the sealed value under level '${ctx.level}' (not established for this level)`);
1536
+ }
1537
+ }
1538
+ }
1539
+ function ruleTrailer(ctx) {
1540
+ const c = ctx.byNum.get(0);
1541
+ if (!c) return;
1542
+ const changed = changedKeys(c.value.old, c.value.new);
1543
+ const bad = [...changed].filter((k) => !TRAILER_KEYS.has(k));
1544
+ const oldRoot = dictEntries(c.value.old)?.Root;
1545
+ const newRoot = dictEntries(c.value.new)?.Root;
1546
+ if (bad.length > 0) {
1547
+ ctx.forbidden(c, "trailer", `trailer keys changed: ${bad.join(", ")}`);
1548
+ return;
1549
+ }
1550
+ if (changed.has("Root") && !pdfValueEquals(oldRoot ?? null, newRoot ?? null)) {
1551
+ ctx.forbidden(c, "trailer", "the trailer points at a different catalog");
1552
+ return;
1553
+ }
1554
+ if (changed.has("Encrypt") && !pdfValueEquals(dictEntries(c.value.old)?.Encrypt ?? null, dictEntries(c.value.new)?.Encrypt ?? null)) {
1555
+ ctx.forbidden(c, "trailer", "the trailer points at a different encryption dictionary");
1556
+ return;
1557
+ }
1558
+ ctx.claimAll(c, "trailer");
1559
+ ctx.permitted(c, "trailer");
1560
+ }
1561
+ function onlyEdges(c, test) {
1562
+ const all = [...c.usage.old, ...c.usage.new];
1563
+ return all.length > 0 && all.every(test);
1564
+ }
1565
+ function ruleInfo(ctx) {
1566
+ for (const c of ctx.changes) {
1567
+ if (ctx.isIdentical(c)) continue;
1568
+ if (c.objectNumber !== 0 && onlyEdges(c, (e) => e.parent === 0 && e.label === "Info")) {
1569
+ ctx.claimAll(c, "info");
1570
+ ctx.permitted(c, "info", "document information dictionary");
1571
+ }
1572
+ }
1573
+ }
1574
+ function ruleMetadata(ctx) {
1575
+ const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
1576
+ for (const c of ctx.changes) {
1577
+ if (ctx.isIdentical(c)) continue;
1578
+ if (onlyEdges(c, (e) => roots.has(e.parent) && e.label === "Metadata")) {
1579
+ ctx.claimAll(c, "metadata");
1580
+ ctx.permitted(c, "metadata", "XMP metadata stream");
1581
+ }
1582
+ }
1583
+ }
1584
+ function ruleCatalog(ctx) {
1585
+ const root = ctx.input.after.root;
1586
+ const c = ctx.byNum.get(root);
1587
+ if (!c || ctx.isIdentical(c)) return;
1588
+ const changed = changedKeys(c.value.old, c.value.new);
1589
+ const bad = [...changed].filter((k) => !CATALOG_KEYS.has(k));
1590
+ if (bad.length > 0) {
1591
+ ctx.forbidden(c, "catalog-housekeeping", `catalog keys changed: ${bad.join(", ")}`);
1592
+ return;
1593
+ }
1594
+ if (changed.has("AcroForm") && !ctx.input.after.acroForm && !ctx.input.before.acroForm) {
1595
+ const problem = acroFormProblem(ctx, dictEntries(c.value.old)?.AcroForm ?? null, dictEntries(c.value.new)?.AcroForm ?? null);
1596
+ if (problem) {
1597
+ ctx.forbidden(c, "acroform-housekeeping", problem);
1598
+ return;
1599
+ }
1600
+ for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([root]), (label) => label.startsWith("AcroForm/DR/"), "acroform-housekeeping");
1601
+ }
1602
+ if (changed.has("Perms")) {
1603
+ const after = require_resolver.deriveProtection(ctx.input.after.signatures);
1604
+ const before = require_resolver.deriveProtection(ctx.input.before.signatures);
1605
+ if (!after.certification || before.judged !== null) {
1606
+ ctx.forbidden(c, "catalog-housekeeping", "/Perms may only appear with the certification signature, in the first signed revision");
1607
+ return;
1608
+ }
1609
+ }
1610
+ ctx.claimAll(c, "catalog-housekeeping");
1611
+ ctx.permitted(c, "catalog-housekeeping");
1612
+ }
1613
+ function ruleAcroForm(ctx) {
1614
+ const acro = ctx.input.after.acroForm || ctx.input.before.acroForm;
1615
+ if (!acro) return;
1616
+ const c = ctx.byNum.get(acro);
1617
+ if (!c || ctx.isIdentical(c)) return;
1618
+ const problem = acroFormProblem(ctx, c.value.old, c.value.new);
1619
+ if (problem) {
1620
+ ctx.forbidden(c, "acroform-housekeeping", problem);
1621
+ return;
1622
+ }
1623
+ ctx.claimAll(c, "acroform-housekeeping");
1624
+ ctx.permitted(c, "acroform-housekeeping");
1625
+ for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([acro]), (label) => label.startsWith("DR/"), "acroform-housekeeping");
1626
+ }
1627
+ /** What is wrong with an AcroForm dictionary change, or null when it is housekeeping. */
1628
+ function acroFormProblem(ctx, oldValue, newValue) {
1629
+ const changed = changedKeys(oldValue, newValue);
1630
+ const bad = [...changed].filter((k) => !ACROFORM_KEYS.has(k));
1631
+ if (bad.length > 0) return `AcroForm keys changed: ${bad.join(", ")}`;
1632
+ if (changed.has("Fields")) {
1633
+ const oldRefs = refsOf(dictEntries(oldValue)?.Fields);
1634
+ const newRefs = refsOf(dictEntries(newValue)?.Fields);
1635
+ if (oldRefs.length > newRefs.length || oldRefs.some((n, i) => newRefs[i] !== n)) return "/AcroForm /Fields is not append-only";
1636
+ const newSigFields = newSignatureFields(ctx);
1637
+ const strangers = newRefs.slice(oldRefs.length).filter((n) => !newSigFields.has(n));
1638
+ if (strangers.length > 0) return `/AcroForm /Fields gained non-signature fields: ${strangers.join(", ")}`;
1639
+ }
1640
+ return null;
1641
+ }
1642
+ /** The refs appended to an array-valued key when the old array is a prefix of the new one; null otherwise. */
1643
+ function appendedRefs(c, key) {
1644
+ const oldItems = dictEntries(c.value.old)?.[key];
1645
+ const newItems = dictEntries(c.value.new)?.[key];
1646
+ const oldRefs = refsOf(oldItems);
1647
+ const newRefs = refsOf(newItems);
1648
+ if (oldItems && oldItems.t !== "array") return null;
1649
+ if (!newItems || newItems.t !== "array") return null;
1650
+ if (oldRefs.length > newRefs.length) return null;
1651
+ for (let i = 0; i < oldRefs.length; i++) if (oldRefs[i] !== newRefs[i]) return null;
1652
+ return newRefs.slice(oldRefs.length);
1653
+ }
1654
+ function newSignatureFields(ctx) {
1655
+ const out = /* @__PURE__ */ new Set();
1656
+ for (const n of ctx.after.sigFields) if (!ctx.before.fieldByObj.has(n)) out.add(n);
1657
+ return out;
1658
+ }
1659
+ function ruleDss(ctx) {
1660
+ const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
1661
+ for (const side of SIDES) ctx.claimSubtree(side, roots, (label) => label === "DSS" || label.startsWith("DSS/"), "dss");
1662
+ 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");
1663
+ }
1664
+ function ruleSignatureFieldAdded(ctx) {
1665
+ const added = newSignatureFields(ctx);
1666
+ if (added.size === 0) return;
1667
+ const rule = "signature-field-added";
1668
+ if (!ctx.allows("fill")) {
1669
+ for (const n of added) {
1670
+ const c = ctx.byNum.get(n);
1671
+ if (c) ctx.forbidden(c, rule, `a signature field was added under level '${ctx.level}'`);
1672
+ }
1673
+ return;
1674
+ }
1675
+ const newWidgets = /* @__PURE__ */ new Set();
1676
+ for (const n of added) {
1677
+ const field = ctx.after.fieldByObj.get(n);
1678
+ const c = ctx.byNum.get(n);
1679
+ if (c) {
1680
+ ctx.claimAll(c, rule);
1681
+ ctx.permitted(c, rule, `new signature field "${field.name}"`);
1682
+ }
1683
+ for (const w of field.widgets) {
1684
+ newWidgets.add(w);
1685
+ if (w === n) continue;
1686
+ const wc = ctx.byNum.get(w);
1687
+ if (wc) {
1688
+ ctx.claimAll(wc, rule);
1689
+ ctx.permitted(wc, rule, `widget of new signature field "${field.name}"`);
1690
+ }
1691
+ }
1692
+ }
1693
+ for (const p of ctx.after.pages) {
1694
+ const c = ctx.byNum.get(p);
1695
+ if (!c || ctx.isIdentical(c)) continue;
1696
+ const changed = changedKeys(c.value.old, c.value.new);
1697
+ if (changed.size !== 1 || !changed.has("Annots")) continue;
1698
+ const appended = appendedRefs(c, "Annots");
1699
+ if (appended && appended.every((n) => newWidgets.has(n))) {
1700
+ ctx.claimAll(c, rule);
1701
+ ctx.permitted(c, rule, "page /Annots gained the new signature widget");
1702
+ }
1703
+ }
1704
+ for (const c of ctx.changes) {
1705
+ if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
1706
+ const changed = changedKeys(c.value.old, c.value.new);
1707
+ if (changed.size !== 1 || !changed.has("Kids")) continue;
1708
+ const appended = appendedRefs(c, "Kids");
1709
+ if (appended && appended.length > 0 && appended.every((n) => added.has(n) || newWidgets.has(n))) {
1710
+ ctx.claimAll(c, rule);
1711
+ ctx.permitted(c, rule, "field /Kids gained the new signature field");
1712
+ }
1713
+ }
1714
+ }
1715
+ function ruleSignatureAdded(ctx) {
1716
+ const rule = "signature-added";
1717
+ const events = ctx.newlySigned();
1718
+ if (events.length === 0) return;
1719
+ const addedThisStep = newSignatureFields(ctx);
1720
+ const beforeProtection = require_resolver.deriveProtection(ctx.input.before.signatures);
1721
+ const afterProtection = require_resolver.deriveProtection(ctx.input.after.signatures);
1722
+ const parents = /* @__PURE__ */ new Set();
1723
+ const values = /* @__PURE__ */ new Set();
1724
+ for (const { fieldObjectNumber, sig } of events) {
1725
+ const needed = sig.kind === "timestamp" ? "lta" : "fill";
1726
+ if (!ctx.allows(needed)) {
1727
+ const c = ctx.byNum.get(fieldObjectNumber);
1728
+ if (c) ctx.forbidden(c, rule, `a ${sig.kind} was added under level '${ctx.level}'`);
1729
+ continue;
1730
+ }
1731
+ if (sig.catalogCertification && beforeProtection.judged !== null) {
1732
+ const c = ctx.byNum.get(fieldObjectNumber);
1733
+ if (c) ctx.forbidden(c, rule, "a certification signature after another signature");
1734
+ continue;
1735
+ }
1736
+ const field = ctx.after.fieldByObj.get(fieldObjectNumber);
1737
+ parents.add(fieldObjectNumber);
1738
+ for (const w of field?.widgets ?? []) parents.add(w);
1739
+ const fieldIsNew = addedThisStep.has(fieldObjectNumber);
1740
+ const allowedFieldKeys = beforeProtection.judged === null ? /* @__PURE__ */ new Set([...signedFieldKeysFor(ctx.level), "Lock"]) : signedFieldKeysFor(ctx.level);
1741
+ const fc = ctx.byNum.get(fieldObjectNumber);
1742
+ if (fc && fieldIsNew) ctx.permitted(fc, rule, `new field "${sig.fieldName}" signed`);
1743
+ else if (fc && !ctx.isIdentical(fc)) {
1744
+ const changed = changedKeys(fc.value.old, fc.value.new);
1745
+ const bad = [...changed].filter((k) => !allowedFieldKeys.has(k));
1746
+ if (bad.length > 0) {
1747
+ ctx.forbidden(fc, rule, `signature field keys changed: ${bad.join(", ")}`);
1748
+ ctx.claimStable(fc, rule, false);
1749
+ } else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(fieldObjectNumber)?.flags)) {
1750
+ ctx.forbidden(fc, rule, "signature field flags changed beyond ReadOnly");
1751
+ ctx.claimStable(fc, rule, false);
1752
+ } else {
1753
+ ctx.claimStable(fc, rule);
1754
+ ctx.permitted(fc, rule, `field "${sig.fieldName}" signed`);
1755
+ }
1756
+ }
1757
+ for (const w of field?.widgets ?? []) {
1758
+ if (w === fieldObjectNumber || fieldIsNew) continue;
1759
+ const wc = ctx.byNum.get(w);
1760
+ if (!wc || ctx.isIdentical(wc)) continue;
1761
+ const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !SIGNED_WIDGET_KEYS.has(k));
1762
+ if (bad.length > 0) {
1763
+ ctx.forbidden(wc, rule, `signature widget keys changed: ${bad.join(", ")}`);
1764
+ ctx.claimStable(wc, rule, false);
1765
+ continue;
1766
+ }
1767
+ ctx.claimStable(wc, rule);
1768
+ ctx.permitted(wc, rule, "signature widget appearance");
1769
+ }
1770
+ for (const c of ctx.changes) {
1771
+ if (c.change !== "added") continue;
1772
+ const asValue = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "V");
1773
+ const asLock = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "Lock");
1774
+ const type = dictEntries(c.value.new)?.Type;
1775
+ if (asValue.length > 0) {
1776
+ if (!(type?.t === "name" && (type.v === "Sig" || type.v === "DocTimeStamp")) && type !== void 0) continue;
1777
+ for (const e of c.usage.new) if (e.parent === fieldObjectNumber && e.label === "V" || e.label === "DocMDP") ctx.claimEdge(c, "new", e, rule);
1778
+ ctx.permitted(c, rule, "signature value");
1779
+ values.add(c.objectNumber);
1780
+ } else if (asLock.length > 0) {
1781
+ if (!(type === void 0 || type.t === "name" && type.v === "SigFieldLock")) continue;
1782
+ for (const e of asLock) ctx.claimEdge(c, "new", e, rule);
1783
+ ctx.permitted(c, rule, "field lock installed with the signature");
1784
+ }
1785
+ }
1786
+ }
1787
+ if (afterProtection.certification && beforeProtection.judged === null) {
1788
+ const root = ctx.input.after.root;
1789
+ for (const c of ctx.changes) if (c.change === "added" && onlyEdges(c, (e) => e.parent === root && e.label === "Perms")) {
1790
+ ctx.claimAll(c, rule);
1791
+ ctx.permitted(c, rule, "certification permissions");
1792
+ }
1793
+ }
1794
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
1795
+ if (values.size > 0) ctx.claimSubtree("new", values, () => true, rule);
1796
+ }
1797
+ /**
1798
+ * Did `/Ff` change by gaining ReadOnly and nothing else? `/Ff` is
1799
+ * inheritable, and a lock writes `effective | ReadOnly` into the terminal
1800
+ * dictionary, so a terminal that carried no `/Ff` of its own is judged
1801
+ * against the effective flags the older revision's form model reported.
1802
+ */
1803
+ function readOnlyOnlyChange(c, effectiveBefore) {
1804
+ const oldFf = dictEntries(c.value.old)?.Ff;
1805
+ const newFf = dictEntries(c.value.new)?.Ff;
1806
+ const before = oldFf?.t === "number" ? oldFf.v : effectiveBefore ?? 0;
1807
+ const after = newFf?.t === "number" ? newFf.v : 0;
1808
+ return (before | FF_READ_ONLY) === after;
1809
+ }
1810
+ function ruleFormFill(ctx) {
1811
+ const rule = "form-fill";
1812
+ const fieldKeys = fieldKeysFor(ctx.level);
1813
+ const widgetKeys = widgetKeysFor(ctx.level);
1814
+ const parents = /* @__PURE__ */ new Set();
1815
+ const fields = new Map([...ctx.before.fieldByObj, ...ctx.after.fieldByObj]);
1816
+ for (const [num, field] of fields) {
1817
+ if (field.family === "signature") continue;
1818
+ const fc = ctx.byNum.get(num);
1819
+ const widgetChanges = field.widgets.filter((w) => w !== num).map((w) => ctx.byNum.get(w)).filter(Boolean);
1820
+ const owners = /* @__PURE__ */ new Set([num, ...field.widgets]);
1821
+ const isAp = (label) => label === "AP" || label.startsWith("AP/");
1822
+ 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)))) : [];
1823
+ if (!fc && widgetChanges.length === 0 && appearanceOnly.length === 0) continue;
1824
+ if (!ctx.allows("fill")) {
1825
+ for (const c of [
1826
+ fc,
1827
+ ...widgetChanges,
1828
+ ...appearanceOnly
1829
+ ]) if (c && !ctx.isIdentical(c)) ctx.forbidden(c, rule, `form fill under level '${ctx.level}'`);
1830
+ continue;
1831
+ }
1832
+ if (appearanceOnly.length > 0) {
1833
+ for (const c of appearanceOnly) ctx.permitted(c, rule, `appearance of field "${field.name}" regenerated`);
1834
+ parents.add(num);
1835
+ for (const w of field.widgets) parents.add(w);
1836
+ continue;
1837
+ }
1838
+ let ok = true;
1839
+ if (fc && !ctx.isIdentical(fc)) {
1840
+ const changed = changedKeys(fc.value.old, fc.value.new);
1841
+ const bad = [...changed].filter((k) => !fieldKeys.has(k));
1842
+ if (bad.length > 0) {
1843
+ ctx.forbidden(fc, rule, `field "${field.name}" keys changed: ${bad.join(", ")}`);
1844
+ ctx.claimStable(fc, rule, false);
1845
+ ok = false;
1846
+ } else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(num)?.flags)) {
1847
+ ctx.forbidden(fc, rule, `field "${field.name}" flags changed beyond adding ReadOnly`);
1848
+ ctx.claimStable(fc, rule, false);
1849
+ ok = false;
1850
+ } else {
1851
+ ctx.claimStable(fc, rule);
1852
+ const properties = [...changed].filter((k) => k !== "V" && k !== "AP" && k !== "AS" && k !== "I" && k !== "RV");
1853
+ 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`);
1854
+ }
1855
+ }
1856
+ for (const wc of widgetChanges) {
1857
+ if (ctx.isIdentical(wc)) continue;
1858
+ const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !widgetKeys.has(k));
1859
+ if (bad.length > 0) {
1860
+ ctx.forbidden(wc, rule, `widget of "${field.name}" keys changed: ${bad.join(", ")}`);
1861
+ ctx.claimStable(wc, rule, false);
1862
+ ok = false;
1863
+ continue;
1864
+ }
1865
+ ctx.claimStable(wc, rule);
1866
+ ctx.permitted(wc, rule, `widget of "${field.name}" appearance`);
1867
+ }
1868
+ if (ok) {
1869
+ parents.add(num);
1870
+ for (const w of field.widgets) parents.add(w);
1871
+ }
1872
+ }
1873
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
1874
+ }
1875
+ function ruleAnnotation(ctx) {
1876
+ const rule = "annotation";
1877
+ const pages = /* @__PURE__ */ new Set([...ctx.before.pages, ...ctx.after.pages]);
1878
+ const widgets = /* @__PURE__ */ new Set([...ctx.before.widgetToField.keys(), ...ctx.after.widgetToField.keys()]);
1879
+ const isWidget = (c) => {
1880
+ if (widgets.has(c.objectNumber)) return true;
1881
+ const sub = dictEntries(c.value.new ?? c.value.old)?.Subtype;
1882
+ return sub?.t === "name" && sub.v === "Widget";
1883
+ };
1884
+ const annots = [];
1885
+ for (const c of ctx.changes) {
1886
+ if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
1887
+ if ([...c.usage.old, ...c.usage.new].some((e) => pages.has(e.parent) && e.label.startsWith("Annots/")) && !isWidget(c)) annots.push(c);
1888
+ }
1889
+ if (annots.length === 0) {}
1890
+ if (!ctx.allows("annotate")) {
1891
+ for (const c of annots) ctx.forbidden(c, rule, `annotation change under level '${ctx.level}'`);
1892
+ return;
1893
+ }
1894
+ const parents = /* @__PURE__ */ new Set();
1895
+ for (const c of annots) {
1896
+ ctx.claimAll(c, rule);
1897
+ ctx.permitted(c, rule, `${c.change} annotation`);
1898
+ parents.add(c.objectNumber);
1899
+ }
1900
+ for (const p of pages) {
1901
+ const c = ctx.byNum.get(p);
1902
+ if (!c || ctx.isIdentical(c)) continue;
1903
+ const changed = changedKeys(c.value.old, c.value.new);
1904
+ if (changed.size === 1 && changed.has("Annots")) {
1905
+ const removed = refsOf(dictEntries(c.value.old)?.Annots).filter((n) => !refsOf(dictEntries(c.value.new)?.Annots).includes(n));
1906
+ const added = refsOf(dictEntries(c.value.new)?.Annots).filter((n) => !refsOf(dictEntries(c.value.old)?.Annots).includes(n));
1907
+ if ([...removed, ...added].some((n) => widgets.has(n) && !ctx.findings.some((f) => f.objectNumber === n && f.verdict === "permitted"))) {
1908
+ ctx.forbidden(c, rule, "page /Annots added or removed a form widget");
1909
+ continue;
1910
+ }
1911
+ ctx.claimAll(c, rule);
1912
+ ctx.permitted(c, rule, "page /Annots changed");
1913
+ }
1914
+ }
1915
+ for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/") || label === "Popup" || label === "IRT", rule);
1916
+ }
1917
+ function ruleFieldLock(ctx) {
1918
+ if (ctx.locks.length === 0) return;
1919
+ const lockedFields = ctx.input.before.fields.filter((f) => ctx.locks.some((l) => require_resolver.lockCovers(l.spec, f.name)));
1920
+ const lockedObjects = /* @__PURE__ */ new Set();
1921
+ for (const f of lockedFields) {
1922
+ lockedObjects.add(f.objectNumber);
1923
+ for (const w of f.widgets) lockedObjects.add(w);
1924
+ }
1925
+ const frontier = new Set(lockedObjects);
1926
+ let grew = true;
1927
+ while (grew) {
1928
+ grew = false;
1929
+ for (const c of ctx.changes) {
1930
+ if (frontier.has(c.objectNumber)) continue;
1931
+ if (c.usage.old.length > 0 && c.usage.old.every((e) => frontier.has(e.parent))) {
1932
+ frontier.add(c.objectNumber);
1933
+ grew = true;
1934
+ }
1935
+ }
1936
+ }
1937
+ for (const c of ctx.changes) {
1938
+ if (!frontier.has(c.objectNumber) || ctx.isIdentical(c)) continue;
1939
+ const field = lockedFields.find((f) => f.objectNumber === c.objectNumber || f.widgets.includes(c.objectNumber));
1940
+ if (field && field.objectNumber === c.objectNumber) {
1941
+ const changed = changedKeys(c.value.old, c.value.new);
1942
+ if (changed.size === 1 && changed.has("Ff") && readOnlyOnlyChange(c, ctx.before.fieldByObj.get(c.objectNumber)?.flags)) {
1943
+ ctx.claimStable(c, "field-lock");
1944
+ ctx.permitted(c, "field-lock", `ReadOnly set on locked field "${field.name}"`);
1945
+ continue;
1946
+ }
1947
+ }
1948
+ ctx.locked.add(c.objectNumber);
1949
+ ctx.forbidden(c, "field-lock", field ? `field "${field.name}" is locked by the signature` : "part of a locked field changed");
1950
+ }
1951
+ }
1952
+ function ruleUnexplained(ctx) {
1953
+ for (const c of ctx.changes) {
1954
+ if (c.value.truncated) {
1955
+ ctx.findings.push({
1956
+ rule: "unexplained",
1957
+ verdict: "incomplete",
1958
+ objectNumber: c.objectNumber,
1959
+ detail: "value too large to inspect"
1960
+ });
1961
+ continue;
1962
+ }
1963
+ if (c.usageIncomplete) {
1964
+ ctx.findings.push({
1965
+ rule: "unexplained",
1966
+ verdict: "incomplete",
1967
+ objectNumber: c.objectNumber,
1968
+ detail: "references could not be resolved within budget (depth, fan-out or total reads)"
1969
+ });
1970
+ continue;
1971
+ }
1972
+ if (ctx.locked.has(c.objectNumber)) continue;
1973
+ 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);
1974
+ 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");
1975
+ }
1976
+ }
1977
+ //#endregion
1978
+ //#region src/signature/analysis/restrictions.ts
1979
+ /**
1980
+ * The restrictions a window is judged under, read from the SEALED
1981
+ * revision's structure (what that revision declared, not what the final
1982
+ * file says).
1983
+ *
1984
+ * - `judged` given (a signature-anchored analysis): its own FieldMDP and
1985
+ * /Lock bind the window; other signatures' locks do not (corpus v3/85:
1986
+ * a second signature is valid with a fill of a field the first locked).
1987
+ * Every certification's level, own or earlier, applies (inherited:
1988
+ * conservative until observed otherwise), as does a /Lock's /P.
1989
+ * - no `judged` (a revision-anchored analysis): every signed signature's
1990
+ * locks and levels, as before.
1991
+ */
1992
+ function restrictionsFor(before, judged) {
1993
+ const anchors = [];
1994
+ const locks = [];
1995
+ let enforced = null;
1996
+ let signed = false;
1997
+ for (const sig of before.signatures) {
1998
+ if (!sig.signed || sig.revisionIndex === null) continue;
1999
+ signed = true;
2000
+ const own = judged !== null && sig.index === judged.index;
2001
+ if (sig.catalogCertification && sig.docMdp !== null) {
2002
+ anchors.push({
2003
+ signatureIndex: sig.index,
2004
+ revisionIndex: sig.revisionIndex,
2005
+ source: "docmdp",
2006
+ own,
2007
+ permission: sig.docMdp
2008
+ });
2009
+ enforced = require_resolver.minLevel(enforced ?? "annotate", require_resolver.levelFromPermission(sig.docMdp));
2010
+ }
2011
+ if (sig.lock?.permission !== void 0) enforced = require_resolver.minLevel(enforced ?? "annotate", require_resolver.levelFromPermission(sig.lock.permission));
2012
+ if (judged !== null && !own) continue;
2013
+ if (sig.fieldMdp) {
2014
+ anchors.push({
2015
+ signatureIndex: sig.index,
2016
+ revisionIndex: sig.revisionIndex,
2017
+ source: "fieldmdp",
2018
+ own,
2019
+ fields: sig.fieldMdp
2020
+ });
2021
+ locks.push({
2022
+ signatureIndex: sig.index,
2023
+ source: "fieldmdp",
2024
+ spec: sig.fieldMdp
2025
+ });
2026
+ }
2027
+ if (sig.lock) {
2028
+ anchors.push({
2029
+ signatureIndex: sig.index,
2030
+ revisionIndex: sig.revisionIndex,
2031
+ source: "lock",
2032
+ own,
2033
+ fields: sig.lock
2034
+ });
2035
+ locks.push({
2036
+ signatureIndex: sig.index,
2037
+ source: "lock",
2038
+ spec: sig.lock
2039
+ });
2040
+ }
2041
+ }
2042
+ return {
2043
+ anchors,
2044
+ level: signed ? enforced ?? "annotate" : "annotate",
2045
+ locks
2046
+ };
2047
+ }
2048
+ //#endregion
2049
+ //#region src/signature/analysis/resolve.ts
2050
+ const DEFAULT_EDGE_RESOLVER_BUDGET = {
2051
+ maxDepth: 16,
2052
+ maxEdgesPerObject: 256,
2053
+ maxReads: 2e4
2054
+ };
2055
+ const USAGE_INCOMPLETE = "incomplete";
2056
+ /**
2057
+ * Resolves the raw inbound edges of an object (from the fork's referrer
2058
+ * index, which covers every reachable object of a revision) into edges
2059
+ * from *anchors*: the changed objects and the structural objects of the
2060
+ * revision (trailer, catalog, AcroForm, page tree root, pages, fields,
2061
+ * widgets). An edge from an unchanged, non-structural parent is replaced by
2062
+ * that parent's own anchored edges with the labels joined, so an
2063
+ * appearance stream under an unchanged indirect `/AP` dictionary reads
2064
+ * `widget:AP/N` rather than `apDict:N`.
2065
+ *
2066
+ * A graph traversal, not a path enumeration: results are memoised per
2067
+ * object, so a lattice of shared unchanged objects costs its size, not the
2068
+ * number of paths through it. Cycle edges contribute no anchors and are
2069
+ * skipped; a result computed while skipping one is not memoised.
2070
+ */
2071
+ var EdgeResolver = class {
2072
+ constructor(read, anchors, budget = DEFAULT_EDGE_RESOLVER_BUDGET) {
2073
+ this.read = read;
2074
+ this.anchors = anchors;
2075
+ this.budget = budget;
2076
+ require_derive._defineProperty(this, "memo", /* @__PURE__ */ new Map());
2077
+ require_derive._defineProperty(this, "reads", 0);
2078
+ require_derive._defineProperty(this, "exhausted", false);
2079
+ }
2080
+ /** Anchored edges of `objectNumber`, or `incomplete` when a budget was exceeded. */
2081
+ resolve(objectNumber) {
2082
+ if (this.exhausted) return USAGE_INCOMPLETE;
2083
+ const result = this.resolveInner(objectNumber, /* @__PURE__ */ new Set());
2084
+ return result === null ? USAGE_INCOMPLETE : result.edges;
2085
+ }
2086
+ resolveInner(n, active) {
2087
+ const hit = this.memo.get(n);
2088
+ if (hit) return hit;
2089
+ if (active.size >= this.budget.maxDepth) return null;
2090
+ if (this.reads >= this.budget.maxReads) {
2091
+ this.exhausted = true;
2092
+ return null;
2093
+ }
2094
+ this.reads += 1;
2095
+ const raw = this.read(n);
2096
+ active.add(n);
2097
+ const edges = [];
2098
+ let cyclic = false;
2099
+ try {
2100
+ for (const e of raw) {
2101
+ if (this.anchors.has(e.parent)) edges.push(e);
2102
+ else if (active.has(e.parent)) {
2103
+ cyclic = true;
2104
+ continue;
2105
+ } else {
2106
+ const up = this.resolveInner(e.parent, active);
2107
+ if (up === null) return null;
2108
+ cyclic || (cyclic = up.cyclic);
2109
+ for (const u of up.edges) edges.push({
2110
+ parent: u.parent,
2111
+ label: `${u.label}/${e.label}`,
2112
+ via: [...u.via ?? [], e.parent]
2113
+ });
2114
+ }
2115
+ if (edges.length > this.budget.maxEdgesPerObject) return null;
2116
+ }
2117
+ } finally {
2118
+ active.delete(n);
2119
+ }
2120
+ const resolution = {
2121
+ edges,
2122
+ cyclic
2123
+ };
2124
+ if (!cyclic) this.memo.set(n, resolution);
2125
+ return resolution;
2126
+ }
2127
+ };
2128
+ //#endregion
858
2129
  //#region src/search/literal.ts
859
2130
  /**
860
2131
  * The fold flavor a literal query needs. Haystack and needle MUST be
@@ -865,7 +2136,8 @@ function decodeFieldRefKey(key) {
865
2136
  function foldOptionsFor(query) {
866
2137
  return {
867
2138
  keepCase: !!query.matchCase,
868
- keepMarks: !!query.matchDiacritics
2139
+ keepMarks: !!query.matchDiacritics,
2140
+ dropWhitespace: !!query.ignoreWhitespace
869
2141
  };
870
2142
  }
871
2143
  const WORD_UNIT = /[\p{L}\p{N}_]/u;
@@ -883,18 +2155,29 @@ function wordAt(text, index) {
883
2155
  return WORD_UNIT.test(String.fromCodePoint(text.codePointAt(index)));
884
2156
  }
885
2157
  /**
2158
+ * Whether the folded hit at `at` sits on word boundaries. Checked on the
2159
+ * folded plane, except under `ignoreWhitespace`: dropping whitespace glues
2160
+ * neighbouring words together ("i n v o i c e 42" folds to "invoice42"), so there the
2161
+ * boundaries are read off the ORIGINAL text around the mapped range.
2162
+ */
2163
+ function isWholeWordHit(haystack, at, needleLength, query) {
2164
+ if (!query.ignoreWhitespace) return !wordBefore(haystack.folded, at) && !wordAt(haystack.folded, at + needleLength);
2165
+ const { start, length } = require_derive.toOriginalRange(haystack, at, needleLength);
2166
+ return !wordBefore(haystack.original, start) && !wordAt(haystack.original, start + length);
2167
+ }
2168
+ /**
886
2169
  * All non-overlapping literal matches, in original code-unit space.
887
2170
  * `haystack` must have been folded with `foldOptionsFor(query)`.
888
2171
  */
889
2172
  function matchLiteral(haystack, query) {
890
- const needle = require_fold.foldText(query.text, foldOptionsFor(query)).folded;
2173
+ const needle = require_derive.foldText(query.text, foldOptionsFor(query)).folded;
891
2174
  if (needle.trim().length === 0) return [];
892
2175
  const out = [];
893
2176
  let from = 0;
894
2177
  while (from <= haystack.folded.length - needle.length) {
895
2178
  const at = haystack.folded.indexOf(needle, from);
896
2179
  if (at < 0) break;
897
- if (query.wholeWord && (wordBefore(haystack.folded, at) || wordAt(haystack.folded, at + needle.length))) {
2180
+ if (query.wholeWord && !isWholeWordHit(haystack, at, needle.length, query)) {
898
2181
  from = at + 1;
899
2182
  continue;
900
2183
  }
@@ -905,7 +2188,7 @@ function matchLiteral(haystack, query) {
905
2188
  continue;
906
2189
  }
907
2190
  }
908
- out.push(require_fold.toOriginalRange(haystack, at, needle.length));
2191
+ out.push(require_derive.toOriginalRange(haystack, at, needle.length));
909
2192
  from = at + needle.length;
910
2193
  }
911
2194
  return out;
@@ -995,6 +2278,11 @@ function validateSearchQuery(query) {
995
2278
  issue: "diacritics-with-regex",
996
2279
  message: "Diacritic-sensitive matching is not available for regex patterns (regex runs on the raw text plane)."
997
2280
  };
2281
+ if (query.ignoreWhitespace) return {
2282
+ ok: false,
2283
+ issue: "ignore-whitespace-with-regex",
2284
+ message: "Whitespace-insensitive matching is not available for regex patterns (regex runs on the raw text plane; use \\s* in the pattern)."
2285
+ };
998
2286
  return validateSearchRegex(query.text);
999
2287
  }
1000
2288
  /**
@@ -1042,7 +2330,7 @@ function matchRegex(text, query) {
1042
2330
  */
1043
2331
  function matchPageText(text, query) {
1044
2332
  if (query.regex) return matchRegex(text, query);
1045
- return matchLiteral(require_fold.foldText(text, foldOptionsFor(query)), query);
2333
+ return matchLiteral(require_derive.foldText(text, foldOptionsFor(query)), query);
1046
2334
  }
1047
2335
  //#endregion
1048
2336
  //#region src/search/snippet.ts
@@ -1182,7 +2470,7 @@ function buildPageTextLayout(snapshot) {
1182
2470
  const runs = [];
1183
2471
  const frames = [IDENTITY_FRAME];
1184
2472
  for (const run of snapshot.runs) {
1185
- if (!require_fold.isRotatedGeometryRun(run)) {
2473
+ if (!require_derive.isRotatedGeometryRun(run)) {
1186
2474
  for (const g of run.glyphs) glyphs.push({
1187
2475
  loose: g.looseBox,
1188
2476
  tight: g.tightBox,
@@ -1578,8 +2866,8 @@ function fnv1a64(input) {
1578
2866
  * untouched (their raw form IS the query).
1579
2867
  */
1580
2868
  function canonicalSearchQuery(query) {
1581
- if (query.regex || query.matchCase || query.matchDiacritics) return query;
1582
- const canonical = { text: require_fold.foldText(query.text).folded };
2869
+ if (query.regex || query.matchCase || query.matchDiacritics || query.ignoreWhitespace) return query;
2870
+ const canonical = { text: require_derive.foldText(query.text).folded };
1583
2871
  if (query.wholeWord) canonical.wholeWord = true;
1584
2872
  return canonical;
1585
2873
  }
@@ -1765,17 +3053,38 @@ function materializePdfPermissions(b) {
1765
3053
  }
1766
3054
  if (b.bit11) out.add("doc.pages.assemble");
1767
3055
  if (b.bit6) out.add("doc.annotate.modify");
1768
- if (b.bit6 || b.bit9) out.add("doc.forms.fill");
3056
+ if (b.bit6 || b.bit9) {
3057
+ out.add("doc.forms.fill");
3058
+ out.add("doc.sign");
3059
+ }
1769
3060
  if (b.bit6 && b.bit4) out.add("doc.forms.modify");
1770
3061
  return [...out];
1771
3062
  }
1772
3063
  //#endregion
3064
+ //#region src/measure/viewport.ts
3065
+ /** Last containing viewport wins. A foreign measure still wins; never guess a fallback. */
3066
+ function viewportForPoint(viewports, point) {
3067
+ return [...viewports].reverse().find(({ bbox: b }) => point.x >= b.left && point.x <= b.right && point.y >= b.bottom && point.y <= b.top);
3068
+ }
3069
+ //#endregion
3070
+ Object.defineProperty(exports, "DEFAULT_EDGE_RESOLVER_BUDGET", {
3071
+ enumerable: true,
3072
+ get: function() {
3073
+ return DEFAULT_EDGE_RESOLVER_BUDGET;
3074
+ }
3075
+ });
1773
3076
  Object.defineProperty(exports, "DEFAULT_PDF_SAVE_MODE", {
1774
3077
  enumerable: true,
1775
3078
  get: function() {
1776
3079
  return DEFAULT_PDF_SAVE_MODE;
1777
3080
  }
1778
3081
  });
3082
+ Object.defineProperty(exports, "EdgeResolver", {
3083
+ enumerable: true,
3084
+ get: function() {
3085
+ return EdgeResolver;
3086
+ }
3087
+ });
1779
3088
  Object.defineProperty(exports, "NO_ANNOTATION_FLAGS", {
1780
3089
  enumerable: true,
1781
3090
  get: function() {
@@ -1806,12 +3115,24 @@ Object.defineProperty(exports, "UNKNOWN_WEAK_ANNOTATION_STATE", {
1806
3115
  return UNKNOWN_WEAK_ANNOTATION_STATE;
1807
3116
  }
1808
3117
  });
3118
+ Object.defineProperty(exports, "USAGE_INCOMPLETE", {
3119
+ enumerable: true,
3120
+ get: function() {
3121
+ return USAGE_INCOMPLETE;
3122
+ }
3123
+ });
1809
3124
  Object.defineProperty(exports, "appearanceImpactOf", {
1810
3125
  enumerable: true,
1811
3126
  get: function() {
1812
3127
  return appearanceImpactOf;
1813
3128
  }
1814
3129
  });
3130
+ Object.defineProperty(exports, "assessmentOf", {
3131
+ enumerable: true,
3132
+ get: function() {
3133
+ return assessmentOf;
3134
+ }
3135
+ });
1815
3136
  Object.defineProperty(exports, "buildCommentThreads", {
1816
3137
  enumerable: true,
1817
3138
  get: function() {
@@ -1830,12 +3151,6 @@ Object.defineProperty(exports, "buildSnippet", {
1830
3151
  return buildSnippet;
1831
3152
  }
1832
3153
  });
1833
- Object.defineProperty(exports, "buildThreads", {
1834
- enumerable: true,
1835
- get: function() {
1836
- return buildThreads;
1837
- }
1838
- });
1839
3154
  Object.defineProperty(exports, "canonicalSearchQuery", {
1840
3155
  enumerable: true,
1841
3156
  get: function() {
@@ -1848,16 +3163,16 @@ Object.defineProperty(exports, "caps", {
1848
3163
  return caps;
1849
3164
  }
1850
3165
  });
1851
- Object.defineProperty(exports, "changesAnnotationList", {
3166
+ Object.defineProperty(exports, "changedKeys", {
1852
3167
  enumerable: true,
1853
3168
  get: function() {
1854
- return changesAnnotationList;
3169
+ return changedKeys;
1855
3170
  }
1856
3171
  });
1857
- Object.defineProperty(exports, "classifyRelation", {
3172
+ Object.defineProperty(exports, "changesAnnotationList", {
1858
3173
  enumerable: true,
1859
3174
  get: function() {
1860
- return classifyRelation;
3175
+ return changesAnnotationList;
1861
3176
  }
1862
3177
  });
1863
3178
  Object.defineProperty(exports, "collab", {
@@ -1866,6 +3181,18 @@ Object.defineProperty(exports, "collab", {
1866
3181
  return collab;
1867
3182
  }
1868
3183
  });
3184
+ Object.defineProperty(exports, "combine", {
3185
+ enumerable: true,
3186
+ get: function() {
3187
+ return combine;
3188
+ }
3189
+ });
3190
+ Object.defineProperty(exports, "conclude", {
3191
+ enumerable: true,
3192
+ get: function() {
3193
+ return conclude;
3194
+ }
3195
+ });
1869
3196
  Object.defineProperty(exports, "createPageImageHandle", {
1870
3197
  enumerable: true,
1871
3198
  get: function() {
@@ -1896,6 +3223,12 @@ Object.defineProperty(exports, "decodeSubmitFormFlags", {
1896
3223
  return decodeSubmitFormFlags;
1897
3224
  }
1898
3225
  });
3226
+ Object.defineProperty(exports, "dictEntries", {
3227
+ enumerable: true,
3228
+ get: function() {
3229
+ return dictEntries;
3230
+ }
3231
+ });
1899
3232
  Object.defineProperty(exports, "encodeFieldRefKey", {
1900
3233
  enumerable: true,
1901
3234
  get: function() {
@@ -1908,6 +3241,12 @@ Object.defineProperty(exports, "encodeStableIdKey", {
1908
3241
  return encodeStableIdKey;
1909
3242
  }
1910
3243
  });
3244
+ Object.defineProperty(exports, "evaluateStep", {
3245
+ enumerable: true,
3246
+ get: function() {
3247
+ return evaluateStep;
3248
+ }
3249
+ });
1911
3250
  Object.defineProperty(exports, "expandTextRangeToLine", {
1912
3251
  enumerable: true,
1913
3252
  get: function() {
@@ -1992,16 +3331,34 @@ Object.defineProperty(exports, "normalizeAttachmentFileSource", {
1992
3331
  return normalizeAttachmentFileSource;
1993
3332
  }
1994
3333
  });
3334
+ Object.defineProperty(exports, "parsePdfValue", {
3335
+ enumerable: true,
3336
+ get: function() {
3337
+ return parsePdfValue;
3338
+ }
3339
+ });
1995
3340
  Object.defineProperty(exports, "pdfPermissions", {
1996
3341
  enumerable: true,
1997
3342
  get: function() {
1998
3343
  return pdfPermissions;
1999
3344
  }
2000
3345
  });
2001
- Object.defineProperty(exports, "refKey", {
3346
+ Object.defineProperty(exports, "pdfValueEquals", {
3347
+ enumerable: true,
3348
+ get: function() {
3349
+ return pdfValueEquals;
3350
+ }
3351
+ });
3352
+ Object.defineProperty(exports, "primaryFinding", {
3353
+ enumerable: true,
3354
+ get: function() {
3355
+ return primaryFinding;
3356
+ }
3357
+ });
3358
+ Object.defineProperty(exports, "refsOf", {
2002
3359
  enumerable: true,
2003
3360
  get: function() {
2004
- return refKey;
3361
+ return refsOf;
2005
3362
  }
2006
3363
  });
2007
3364
  Object.defineProperty(exports, "resolveBinarySource", {
@@ -2010,12 +3367,42 @@ Object.defineProperty(exports, "resolveBinarySource", {
2010
3367
  return resolveBinarySource;
2011
3368
  }
2012
3369
  });
3370
+ Object.defineProperty(exports, "restrictionsFor", {
3371
+ enumerable: true,
3372
+ get: function() {
3373
+ return restrictionsFor;
3374
+ }
3375
+ });
3376
+ Object.defineProperty(exports, "restrictionsOf", {
3377
+ enumerable: true,
3378
+ get: function() {
3379
+ return restrictionsOf;
3380
+ }
3381
+ });
2013
3382
  Object.defineProperty(exports, "revisionTokensEqual", {
2014
3383
  enumerable: true,
2015
3384
  get: function() {
2016
3385
  return revisionTokensEqual;
2017
3386
  }
2018
3387
  });
3388
+ Object.defineProperty(exports, "richTextParagraphsFromPlainText", {
3389
+ enumerable: true,
3390
+ get: function() {
3391
+ return richTextParagraphsFromPlainText;
3392
+ }
3393
+ });
3394
+ Object.defineProperty(exports, "richTextPlainText", {
3395
+ enumerable: true,
3396
+ get: function() {
3397
+ return richTextPlainText;
3398
+ }
3399
+ });
3400
+ Object.defineProperty(exports, "sameEffectiveValue", {
3401
+ enumerable: true,
3402
+ get: function() {
3403
+ return sameEffectiveValue;
3404
+ }
3405
+ });
2019
3406
  Object.defineProperty(exports, "searchContentEpoch", {
2020
3407
  enumerable: true,
2021
3408
  get: function() {
@@ -2052,6 +3439,12 @@ Object.defineProperty(exports, "sniffBinaryMetadata", {
2052
3439
  return sniffBinaryMetadata;
2053
3440
  }
2054
3441
  });
3442
+ Object.defineProperty(exports, "stableStringify", {
3443
+ enumerable: true,
3444
+ get: function() {
3445
+ return stableStringify;
3446
+ }
3447
+ });
2055
3448
  Object.defineProperty(exports, "textGlyphAt", {
2056
3449
  enumerable: true,
2057
3450
  get: function() {
@@ -2082,6 +3475,12 @@ Object.defineProperty(exports, "validateSearchRegex", {
2082
3475
  return validateSearchRegex;
2083
3476
  }
2084
3477
  });
3478
+ Object.defineProperty(exports, "viewportForPoint", {
3479
+ enumerable: true,
3480
+ get: function() {
3481
+ return viewportForPoint;
3482
+ }
3483
+ });
2085
3484
  Object.defineProperty(exports, "wordAt", {
2086
3485
  enumerable: true,
2087
3486
  get: function() {
@@ -2094,5 +3493,11 @@ Object.defineProperty(exports, "wordBefore", {
2094
3493
  return wordBefore;
2095
3494
  }
2096
3495
  });
3496
+ Object.defineProperty(exports, "worstVerdict", {
3497
+ enumerable: true,
3498
+ get: function() {
3499
+ return worstVerdict;
3500
+ }
3501
+ });
2097
3502
 
2098
- //# sourceMappingURL=builders-8Hl1AYNU.cjs.map
3503
+ //# sourceMappingURL=viewport-BWeNHZmb.cjs.map