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

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 (59) hide show
  1. package/dist/{AbortError-CRSooKsk.cjs → AbortError-Cq2igpZ6.cjs} +2 -2
  2. package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-Cq2igpZ6.cjs.map} +1 -1
  3. package/dist/{AbortError-8-9JAX1Y.js → AbortError-DHqPBnYc.js} +2 -2
  4. package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DHqPBnYc.js.map} +1 -1
  5. package/dist/{AnnotationListSnapshot-C1U3XUxP.d.cts → AnnotationListSnapshot-D3mFut1e.d.cts} +1323 -214
  6. package/dist/{AnnotationListSnapshot-C1U3XUxP.d.ts → AnnotationListSnapshot-D3mFut1e.d.ts} +1323 -214
  7. package/dist/{DocumentSecurityService-6rhztZlz.d.cts → DocumentSecurityService-ClTZc0zO.d.cts} +2 -2
  8. package/dist/{DocumentSecurityService-7nL9aY75.d.ts → DocumentSecurityService-_JRjqvoI.d.ts} +2 -2
  9. package/dist/{Engine-D3868xIU.d.cts → Engine-Bx0NOfmd.d.cts} +219 -11
  10. package/dist/{Engine-CWnAKccD.d.ts → Engine-DxtpCYcP.d.ts} +219 -11
  11. package/dist/{PageInsertBlankInput-CzShuSJI.cjs → PageInsertBlankInput-CqXBmFhN.cjs} +37 -2
  12. package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +1 -0
  13. package/dist/{PageInsertBlankInput-WKY0tr6S.js → PageInsertBlankInput-cHq9-i-z.js} +37 -2
  14. package/dist/PageInsertBlankInput-cHq9-i-z.js.map +1 -0
  15. package/dist/{builders-Do6_4KnS.cjs → builders-Bqm8yTpi.cjs} +1483 -11
  16. package/dist/builders-Bqm8yTpi.cjs.map +1 -0
  17. package/dist/{builders-Cdz34nbF.js → builders-Dqo2X-dt.js} +1364 -12
  18. package/dist/builders-Dqo2X-dt.js.map +1 -0
  19. package/dist/conformance.cjs +1630 -107
  20. package/dist/conformance.cjs.map +1 -1
  21. package/dist/conformance.d.cts +117 -3
  22. package/dist/conformance.d.ts +117 -3
  23. package/dist/conformance.js +1627 -108
  24. package/dist/conformance.js.map +1 -1
  25. package/dist/{fold-C7I5GpBz.cjs → fold-2V29cxCV.cjs} +2 -2
  26. package/dist/{fold-C7I5GpBz.cjs.map → fold-2V29cxCV.cjs.map} +1 -1
  27. package/dist/{fold-BfyxxwS6.js → fold-CdEJTBTn.js} +2 -2
  28. package/dist/{fold-BfyxxwS6.js.map → fold-CdEJTBTn.js.map} +1 -1
  29. package/dist/index.cjs +33 -4
  30. package/dist/index.d.cts +2 -2
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +5 -5
  33. package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
  34. package/dist/resolver-CYVNV4hn.cjs.map +1 -0
  35. package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
  36. package/dist/resolver-inAVzsWt.js.map +1 -0
  37. package/dist/runtime.cjs +34 -5
  38. package/dist/runtime.d.cts +253 -5
  39. package/dist/runtime.d.ts +253 -5
  40. package/dist/runtime.js +6 -6
  41. package/dist/{schemas-XGrNVMDd.js → schemas-BLARNCgx.js} +469 -7
  42. package/dist/schemas-BLARNCgx.js.map +1 -0
  43. package/dist/{schemas-CS5iPpX5.cjs → schemas-DIqFMmrC.cjs} +672 -6
  44. package/dist/schemas-DIqFMmrC.cjs.map +1 -0
  45. package/dist/wire.cjs +250 -12
  46. package/dist/wire.cjs.map +1 -1
  47. package/dist/wire.d.cts +444 -4
  48. package/dist/wire.d.ts +444 -4
  49. package/dist/wire.js +214 -13
  50. package/dist/wire.js.map +1 -1
  51. package/package.json +1 -1
  52. package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
  53. package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
  54. package/dist/builders-Cdz34nbF.js.map +0 -1
  55. package/dist/builders-Do6_4KnS.cjs.map +0 -1
  56. package/dist/resolver-CHG4waXL.js.map +0 -1
  57. package/dist/resolver-D-jumvgM.cjs.map +0 -1
  58. package/dist/schemas-CS5iPpX5.cjs.map +0 -1
  59. package/dist/schemas-XGrNVMDd.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_fold = require("./fold-C7I5GpBz.cjs");
3
- const require_PageInsertBlankInput = require("./PageInsertBlankInput-CzShuSJI.cjs");
4
- const require_AbortError = require("./AbortError-CRSooKsk.cjs");
5
- const require_schemas = require("./schemas-CS5iPpX5.cjs");
2
+ const require_fold = require("./fold-2V29cxCV.cjs");
3
+ const require_PageInsertBlankInput = require("./PageInsertBlankInput-CqXBmFhN.cjs");
4
+ const require_AbortError = require("./AbortError-Cq2igpZ6.cjs");
5
+ const require_schemas = require("./schemas-DIqFMmrC.cjs");
6
6
  //#region src/conformance/runMetadataConformance.ts
7
7
  /**
8
8
  * The conformance harness is transport-agnostic. It takes the test runner as
@@ -19,7 +19,7 @@ function runMetadataConformance(runner, opts) {
19
19
  if (engine) await engine.destroy();
20
20
  });
21
21
  test("reads metadata from sample fixture", async () => {
22
- const doc = await openFixture$17(engine, opts);
22
+ const doc = await openFixture$20(engine, opts);
23
23
  try {
24
24
  const meta = await doc.metadata.read();
25
25
  expect(meta).toMatchObject(opts.fixture.expected);
@@ -29,7 +29,7 @@ function runMetadataConformance(runner, opts) {
29
29
  }
30
30
  });
31
31
  test("abort() before completion rejects with AbortError", async () => {
32
- const doc = await openFixture$17(engine, opts);
32
+ const doc = await openFixture$20(engine, opts);
33
33
  try {
34
34
  const p = doc.metadata.read();
35
35
  p.abort("test");
@@ -39,7 +39,7 @@ function runMetadataConformance(runner, opts) {
39
39
  }
40
40
  });
41
41
  test("aborting one read does not affect a concurrent one", async () => {
42
- const doc = await openFixture$17(engine, opts);
42
+ const doc = await openFixture$20(engine, opts);
43
43
  try {
44
44
  const a = doc.metadata.read();
45
45
  const b = doc.metadata.read();
@@ -51,7 +51,7 @@ function runMetadataConformance(runner, opts) {
51
51
  }
52
52
  });
53
53
  test("metadata after close throws DocNotOpen", async () => {
54
- const doc = await openFixture$17(engine, opts);
54
+ const doc = await openFixture$20(engine, opts);
55
55
  await doc.close();
56
56
  let caught;
57
57
  try {
@@ -63,7 +63,7 @@ function runMetadataConformance(runner, opts) {
63
63
  expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.DocNotOpen)).toBe(true);
64
64
  });
65
65
  test("update() sets standard + custom fields and a subsequent read observes them", async () => {
66
- const doc = await openFixture$17(engine, opts);
66
+ const doc = await openFixture$20(engine, opts);
67
67
  try {
68
68
  const result = await doc.metadata.update({
69
69
  title: "Conformance Title",
@@ -82,7 +82,7 @@ function runMetadataConformance(runner, opts) {
82
82
  }
83
83
  });
84
84
  test("update() clears a field with null", async () => {
85
- const doc = await openFixture$17(engine, opts);
85
+ const doc = await openFixture$20(engine, opts);
86
86
  try {
87
87
  await doc.metadata.update({ title: "To Be Cleared" });
88
88
  const set = await doc.metadata.read();
@@ -95,7 +95,7 @@ function runMetadataConformance(runner, opts) {
95
95
  }
96
96
  });
97
97
  test("update() after close throws DocNotOpen", async () => {
98
- const doc = await openFixture$17(engine, opts);
98
+ const doc = await openFixture$20(engine, opts);
99
99
  await doc.close();
100
100
  let caught;
101
101
  try {
@@ -108,7 +108,7 @@ function runMetadataConformance(runner, opts) {
108
108
  });
109
109
  });
110
110
  }
111
- async function openFixture$17(engine, opts) {
111
+ async function openFixture$20(engine, opts) {
112
112
  if (opts.openKind === "bytes") {
113
113
  const bytes = await opts.fixture.bytes();
114
114
  return engine.open({
@@ -135,7 +135,7 @@ function runAnnotationReadConformance(runner, opts) {
135
135
  if (engine) await engine.destroy();
136
136
  });
137
137
  test("listRawAll returns one entry per page with valid PageState", async () => {
138
- const doc = await openFixture$16(engine, opts);
138
+ const doc = await openFixture$19(engine, opts);
139
139
  try {
140
140
  const snap = await doc.annotations.listRawAll();
141
141
  expect(require_schemas.AnnotationListSnapshotAllPagesSchema.safeParse(snap).success).toBe(true);
@@ -149,7 +149,7 @@ function runAnnotationReadConformance(runner, opts) {
149
149
  }
150
150
  });
151
151
  test("listRaw on the test page returns the expected counts and DTO shape", async () => {
152
- const doc = await openFixture$16(engine, opts);
152
+ const doc = await openFixture$19(engine, opts);
153
153
  try {
154
154
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
155
155
  expect(require_schemas.AnnotationListPageSnapshotSchema.safeParse(snap).success).toBe(true);
@@ -179,7 +179,7 @@ function runAnnotationReadConformance(runner, opts) {
179
179
  }
180
180
  });
181
181
  test("shape/vertex/line annotations expose their family fields", async () => {
182
- const doc = await openFixture$16(engine, opts);
182
+ const doc = await openFixture$19(engine, opts);
183
183
  try {
184
184
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
185
185
  for (const a of snap.annotations) {
@@ -238,7 +238,7 @@ function runAnnotationReadConformance(runner, opts) {
238
238
  }
239
239
  });
240
240
  test("full page list dispatches per-subtype and matches raw counts", async () => {
241
- const doc = await openFixture$16(engine, opts);
241
+ const doc = await openFixture$19(engine, opts);
242
242
  try {
243
243
  const full = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
244
244
  expect(require_schemas.AnnotationListPageSnapshotSchema.safeParse(full).success).toBe(true);
@@ -248,7 +248,7 @@ function runAnnotationReadConformance(runner, opts) {
248
248
  }
249
249
  });
250
250
  test("every annotation DTO satisfies AnnotationDTOSchema (discriminated union)", async () => {
251
- const doc = await openFixture$16(engine, opts);
251
+ const doc = await openFixture$19(engine, opts);
252
252
  try {
253
253
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
254
254
  for (const a of snap.annotations) {
@@ -288,7 +288,7 @@ function runAnnotationReadConformance(runner, opts) {
288
288
  });
289
289
  if (opts.fixture.expectsWeakAnnotation) {
290
290
  test("weak annotations carry identityQuality: weak and ref.kind: index", async () => {
291
- const doc = await openFixture$16(engine, opts);
291
+ const doc = await openFixture$19(engine, opts);
292
292
  try {
293
293
  const weak = (await doc.annotations.listRaw(opts.fixture.pageObjectNumber)).annotations.find((a) => a.identityQuality === "weak");
294
294
  expect(weak !== void 0).toBe(true);
@@ -298,7 +298,7 @@ function runAnnotationReadConformance(runner, opts) {
298
298
  }
299
299
  });
300
300
  test("a stale revision token throws InvalidReference", async () => {
301
- const doc = await openFixture$16(engine, opts);
301
+ const doc = await openFixture$19(engine, opts);
302
302
  try {
303
303
  const snap = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
304
304
  const weak = snap.annotations.find((a) => a.identityQuality === "weak");
@@ -314,7 +314,7 @@ function runAnnotationReadConformance(runner, opts) {
314
314
  });
315
315
  }
316
316
  test("abort() on listRaw rejects with AbortError", async () => {
317
- const doc = await openFixture$16(engine, opts);
317
+ const doc = await openFixture$19(engine, opts);
318
318
  try {
319
319
  const p = doc.annotations.listRaw(opts.fixture.pageObjectNumber);
320
320
  p.abort("test");
@@ -324,7 +324,7 @@ function runAnnotationReadConformance(runner, opts) {
324
324
  }
325
325
  });
326
326
  test("listRaw on an unknown pageObjectNumber throws NotFound", async () => {
327
- const doc = await openFixture$16(engine, opts);
327
+ const doc = await openFixture$19(engine, opts);
328
328
  try {
329
329
  let caught;
330
330
  try {
@@ -340,7 +340,7 @@ function runAnnotationReadConformance(runner, opts) {
340
340
  });
341
341
  });
342
342
  }
343
- async function openFixture$16(engine, opts) {
343
+ async function openFixture$19(engine, opts) {
344
344
  if (opts.openKind === "bytes") {
345
345
  const bytes = await opts.fixture.bytes();
346
346
  return engine.open({
@@ -389,7 +389,7 @@ function runActionsConformance(runner, opts) {
389
389
  if (engine) await engine.destroy();
390
390
  });
391
391
  test("reads catalog additional actions and preserves /CO identity", async () => {
392
- const doc = await open(engine, opts, opts.fixtures.document);
392
+ const doc = await open$1(engine, opts, opts.fixtures.document);
393
393
  try {
394
394
  expect(Boolean(doc.actions)).toBe(true);
395
395
  const snapshot = await doc.actions.read();
@@ -410,7 +410,7 @@ function runActionsConformance(runner, opts) {
410
410
  }
411
411
  });
412
412
  test("attaches page /AA to PageLayout", async () => {
413
- const doc = await open(engine, opts, opts.fixtures.page);
413
+ const doc = await open$1(engine, opts, opts.fixtures.page);
414
414
  try {
415
415
  const pages = await doc.pages.list();
416
416
  expect(pages.pages[0].actions?.open?.root?.type).toBe("goto-embedded");
@@ -422,7 +422,7 @@ function runActionsConformance(runner, opts) {
422
422
  }
423
423
  });
424
424
  test("attaches activation actions to AnnotationBase, including links", async () => {
425
- const doc = await open(engine, opts, opts.fixtures.annotation);
425
+ const doc = await open$1(engine, opts, opts.fixtures.annotation);
426
426
  try {
427
427
  const firstPage = (await doc.pages.list()).pages[0];
428
428
  const snapshot = await doc.page(firstPage.pageObjectNumber).annotations.list();
@@ -439,7 +439,7 @@ function runActionsConformance(runner, opts) {
439
439
  test("classifies a /S /JavaScript link target, script staying on base.actions", async () => {
440
440
  const fixture = opts.fixtures.javascriptLink;
441
441
  if (!fixture) return;
442
- const doc = await open(engine, opts, fixture);
442
+ const doc = await open$1(engine, opts, fixture);
443
443
  try {
444
444
  const firstPage = (await doc.pages.list()).pages[0];
445
445
  const link = (await doc.page(firstPage.pageObjectNumber).annotations.list()).annotations.find((annotation) => annotation.subtype === "link");
@@ -452,7 +452,7 @@ function runActionsConformance(runner, opts) {
452
452
  }
453
453
  });
454
454
  test("keeps field actions separate from merged widget events", async () => {
455
- const doc = await open(engine, opts, opts.fixtures.field);
455
+ const doc = await open$1(engine, opts, opts.fixtures.field);
456
456
  try {
457
457
  const form = await doc.forms.list();
458
458
  expect(form.fields).toHaveLength(1);
@@ -466,7 +466,7 @@ function runActionsConformance(runner, opts) {
466
466
  }
467
467
  });
468
468
  test("carries interpreter payloads on every executable node", async () => {
469
- const doc = await open(engine, opts, opts.fixtures.payloads);
469
+ const doc = await open$1(engine, opts, opts.fixtures.payloads);
470
470
  try {
471
471
  const page = (await doc.pages.list()).pages[0];
472
472
  const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
@@ -627,7 +627,7 @@ function runActionsConformance(runner, opts) {
627
627
  }
628
628
  });
629
629
  test("degrades unreadable payloads to unknown with payload-dropped", async () => {
630
- const doc = await open(engine, opts, opts.fixtures.payloads);
630
+ const doc = await open$1(engine, opts, opts.fixtures.payloads);
631
631
  try {
632
632
  const page = (await doc.pages.list()).pages[0];
633
633
  const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
@@ -651,7 +651,7 @@ function runActionsConformance(runner, opts) {
651
651
  }
652
652
  });
653
653
  test("reads a destination-form /OpenAction as openDestination", async () => {
654
- const doc = await open(engine, opts, opts.fixtures.openDestination);
654
+ const doc = await open$1(engine, opts, opts.fixtures.openDestination);
655
655
  try {
656
656
  expect(Boolean(doc.actions)).toBe(true);
657
657
  const snapshot = await doc.actions.read();
@@ -671,7 +671,7 @@ function runActionsConformance(runner, opts) {
671
671
  });
672
672
  });
673
673
  }
674
- async function open(engine, opts, fixture) {
674
+ async function open$1(engine, opts, fixture) {
675
675
  if (opts.openKind === "bytes") return engine.open({
676
676
  kind: "bytes",
677
677
  id: fixture.id,
@@ -809,7 +809,7 @@ function runAnnotationMutationConformance(runner, opts) {
809
809
  if (engine) await engine.destroy();
810
810
  });
811
811
  test("create appends without shifting indices and leaves weak refs valid", async () => {
812
- const doc = await openFixture$15(engine, opts);
812
+ const doc = await openFixture$18(engine, opts);
813
813
  try {
814
814
  const page = doc.page(fix.pageObjectNumber);
815
815
  const before = await page.annotations.list();
@@ -845,7 +845,7 @@ function runAnnotationMutationConformance(runner, opts) {
845
845
  }
846
846
  });
847
847
  test("create + update honor annotation flags (set on create, merge on patch)", async () => {
848
- const doc = await openFixture$15(engine, opts);
848
+ const doc = await openFixture$18(engine, opts);
849
849
  try {
850
850
  const page = doc.page(fix.pageObjectNumber);
851
851
  const draft = {
@@ -880,7 +880,7 @@ function runAnnotationMutationConformance(runner, opts) {
880
880
  }
881
881
  });
882
882
  test("create shape annotations (circle + square) round-trip shape fields", async () => {
883
- const doc = await openFixture$15(engine, opts);
883
+ const doc = await openFixture$18(engine, opts);
884
884
  try {
885
885
  const page = doc.page(fix.pageObjectNumber);
886
886
  const circleDraft = {
@@ -961,7 +961,7 @@ function runAnnotationMutationConformance(runner, opts) {
961
961
  }
962
962
  });
963
963
  test("cloudy border (/BE) + rect differences (/RD) are tri-state", async () => {
964
- const doc = await openFixture$15(engine, opts);
964
+ const doc = await openFixture$18(engine, opts);
965
965
  try {
966
966
  const page = doc.page(fix.pageObjectNumber);
967
967
  const plainDraft = {
@@ -1040,7 +1040,7 @@ function runAnnotationMutationConformance(runner, opts) {
1040
1040
  }
1041
1041
  });
1042
1042
  test("update echoes the appearance verdict: moves preserve /AP, restyles re-bake", async () => {
1043
- const doc = await openFixture$15(engine, opts);
1043
+ const doc = await openFixture$18(engine, opts);
1044
1044
  try {
1045
1045
  const page = doc.page(fix.pageObjectNumber);
1046
1046
  const draft = {
@@ -1115,7 +1115,7 @@ function runAnnotationMutationConformance(runner, opts) {
1115
1115
  }
1116
1116
  });
1117
1117
  test("a partial /DA patch preserves the unpatched font members", async () => {
1118
- const doc = await openFixture$15(engine, opts);
1118
+ const doc = await openFixture$18(engine, opts);
1119
1119
  try {
1120
1120
  const page = doc.page(fix.pageObjectNumber);
1121
1121
  const draft = {
@@ -1171,7 +1171,7 @@ function runAnnotationMutationConformance(runner, opts) {
1171
1171
  }
1172
1172
  });
1173
1173
  test("box transform is tri-state: rect-only moves keep rotation, null flattens", async () => {
1174
- const doc = await openFixture$15(engine, opts);
1174
+ const doc = await openFixture$18(engine, opts);
1175
1175
  try {
1176
1176
  const page = doc.page(fix.pageObjectNumber);
1177
1177
  const draft = {
@@ -1232,7 +1232,7 @@ function runAnnotationMutationConformance(runner, opts) {
1232
1232
  }
1233
1233
  });
1234
1234
  test("create vertex + line annotations (polygon/polyline/line) round-trip geometry", async () => {
1235
- const doc = await openFixture$15(engine, opts);
1235
+ const doc = await openFixture$18(engine, opts);
1236
1236
  try {
1237
1237
  const page = doc.page(fix.pageObjectNumber);
1238
1238
  const polygonDraft = {
@@ -1363,7 +1363,7 @@ function runAnnotationMutationConformance(runner, opts) {
1363
1363
  }
1364
1364
  });
1365
1365
  test("create free-text + callout round-trip text/colour/intent fields", async () => {
1366
- const doc = await openFixture$15(engine, opts);
1366
+ const doc = await openFixture$18(engine, opts);
1367
1367
  try {
1368
1368
  const page = doc.page(fix.pageObjectNumber);
1369
1369
  const freeTextDraft = {
@@ -1459,7 +1459,7 @@ function runAnnotationMutationConformance(runner, opts) {
1459
1459
  }
1460
1460
  });
1461
1461
  test("update a free-text patches alignment + colours and is non-structural", async () => {
1462
- const doc = await openFixture$15(engine, opts);
1462
+ const doc = await openFixture$18(engine, opts);
1463
1463
  try {
1464
1464
  const page = doc.page(fix.pageObjectNumber);
1465
1465
  const created = await page.annotations.create({
@@ -1519,8 +1519,289 @@ function runAnnotationMutationConformance(runner, opts) {
1519
1519
  await doc.close();
1520
1520
  }
1521
1521
  });
1522
+ test("rich text: a plain draft reads back body-style paragraphs and a body from its font", async () => {
1523
+ const doc = await openFixture$18(engine, opts);
1524
+ try {
1525
+ const created = await doc.page(fix.pageObjectNumber).annotations.create({
1526
+ subtype: "free-text",
1527
+ intent: "free-text",
1528
+ contents: "Plain\rtext",
1529
+ rect: shapeRect,
1530
+ fontFamily: "helvetica-bold",
1531
+ fontSize: 18,
1532
+ textAlign: "center",
1533
+ color: {
1534
+ r: 0,
1535
+ g: 0,
1536
+ b: 0
1537
+ }
1538
+ });
1539
+ expect(created.created.subtype).toBe("free-text");
1540
+ if (created.created.subtype === "free-text") {
1541
+ const rich = created.created.richText;
1542
+ expect(created.created.fontFamily).toBe("helvetica-bold");
1543
+ expect(rich.body.family).toBe("Helvetica");
1544
+ expect(rich.body.weight).toBe(700);
1545
+ expect(rich.body.italic).toBe(false);
1546
+ expect(rich.body.size).toBe(18);
1547
+ expect(rich.body.align).toBe("center");
1548
+ expect(rich.paragraphs).toEqual([{ runs: [{ text: "Plain" }] }, { runs: [{ text: "text" }] }]);
1549
+ }
1550
+ } finally {
1551
+ await doc.close();
1552
+ }
1553
+ });
1554
+ test("rich text: a draft with runs round-trips the body, the runs and the plain projection", async () => {
1555
+ const doc = await openFixture$18(engine, opts);
1556
+ try {
1557
+ const page = doc.page(fix.pageObjectNumber);
1558
+ const created = await page.annotations.create({
1559
+ subtype: "free-text",
1560
+ intent: "free-text",
1561
+ rect: shapeRect,
1562
+ fontFamily: "helvetica",
1563
+ fontSize: 12,
1564
+ textAlign: "left",
1565
+ color: {
1566
+ r: 0,
1567
+ g: 0,
1568
+ b: 255
1569
+ },
1570
+ richText: {
1571
+ body: {
1572
+ family: "Helvetica",
1573
+ size: 18,
1574
+ color: "#102030"
1575
+ },
1576
+ paragraphs: [{ runs: [
1577
+ { text: "Hello " },
1578
+ {
1579
+ text: "bold",
1580
+ style: { weight: 700 }
1581
+ },
1582
+ {
1583
+ text: " red",
1584
+ style: { color: "#FF0000" }
1585
+ }
1586
+ ] }, {
1587
+ align: "center",
1588
+ runs: [{ text: "H" }, {
1589
+ text: "2",
1590
+ style: { script: "sub" }
1591
+ }]
1592
+ }]
1593
+ }
1594
+ });
1595
+ expect(require_schemas.AnnotationCreateResultSchema.safeParse(created).success).toBe(true);
1596
+ expect(created.created.subtype).toBe("free-text");
1597
+ if (created.created.subtype === "free-text") {
1598
+ const dto = created.created;
1599
+ expect(dto.contents).toBe("Hello bold red\rH2");
1600
+ expect(dto.fontFamily).toBe("helvetica");
1601
+ expect(dto.fontSize).toBe(18);
1602
+ expect(dto.color).toMatchObject({
1603
+ r: 0,
1604
+ g: 0,
1605
+ b: 255
1606
+ });
1607
+ expect(dto.richText.body.color).toBe("#102030");
1608
+ expect(dto.richText.paragraphs[0].runs).toEqual([
1609
+ { text: "Hello " },
1610
+ {
1611
+ text: "bold",
1612
+ style: { weight: 700 }
1613
+ },
1614
+ {
1615
+ text: " red",
1616
+ style: { color: "#FF0000" }
1617
+ }
1618
+ ]);
1619
+ expect(dto.richText.paragraphs[1].align).toBe("center");
1620
+ expect(dto.richText.paragraphs[1].runs[1]).toEqual({
1621
+ text: "2",
1622
+ style: { script: "sub" }
1623
+ });
1624
+ }
1625
+ const listed = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
1626
+ expect(listed?.subtype).toBe("free-text");
1627
+ if (listed?.subtype === "free-text") expect(listed.richText.paragraphs[0].runs[1]).toEqual({
1628
+ text: "bold",
1629
+ style: { weight: 700 }
1630
+ });
1631
+ } finally {
1632
+ await doc.close();
1633
+ }
1634
+ });
1635
+ test("rich text: paragraphs-only patch keeps the body; contents-only rewrites body-style paragraphs", async () => {
1636
+ const doc = await openFixture$18(engine, opts);
1637
+ try {
1638
+ const page = doc.page(fix.pageObjectNumber);
1639
+ const ref = (await page.annotations.create({
1640
+ subtype: "free-text",
1641
+ intent: "free-text",
1642
+ rect: shapeRect,
1643
+ fontFamily: "helvetica",
1644
+ fontSize: 14,
1645
+ textAlign: "center",
1646
+ color: {
1647
+ r: 0,
1648
+ g: 0,
1649
+ b: 0
1650
+ },
1651
+ richText: {
1652
+ body: {
1653
+ family: "Helvetica",
1654
+ size: 14
1655
+ },
1656
+ paragraphs: [{ runs: [{ text: "a" }, {
1657
+ text: "b",
1658
+ style: { weight: 700 }
1659
+ }] }]
1660
+ }
1661
+ })).created.ref;
1662
+ const restyled = await page.annotations.update(ref, {
1663
+ subtype: "free-text",
1664
+ richText: { paragraphs: [{ runs: [{
1665
+ text: "ab",
1666
+ style: { italic: true }
1667
+ }, { text: "c" }] }] }
1668
+ });
1669
+ expect(require_schemas.AnnotationUpdateResultSchema.safeParse(restyled).success).toBe(true);
1670
+ expect(restyled.updated.subtype).toBe("free-text");
1671
+ if (restyled.updated.subtype === "free-text") {
1672
+ expect(restyled.updated.contents).toBe("abc");
1673
+ expect(restyled.updated.richText.body.size).toBe(14);
1674
+ expect(restyled.updated.richText.body.align).toBe("center");
1675
+ expect(restyled.updated.textAlign).toBe("center");
1676
+ expect(restyled.updated.richText.paragraphs[0].runs[0]).toEqual({
1677
+ text: "ab",
1678
+ style: { italic: true }
1679
+ });
1680
+ }
1681
+ const rewritten = await page.annotations.update(ref, {
1682
+ subtype: "free-text",
1683
+ contents: "one\rtwo"
1684
+ });
1685
+ expect(rewritten.updated.subtype).toBe("free-text");
1686
+ if (rewritten.updated.subtype === "free-text") {
1687
+ expect(rewritten.updated.contents).toBe("one\rtwo");
1688
+ expect(rewritten.updated.richText.paragraphs).toEqual([{ runs: [{ text: "one" }] }, { runs: [{ text: "two" }] }]);
1689
+ expect(rewritten.updated.richText.body.size).toBe(14);
1690
+ }
1691
+ expect(rewritten.meta.weakRefsInvalidated).toBe(false);
1692
+ } finally {
1693
+ await doc.close();
1694
+ }
1695
+ });
1696
+ test("rich text: fontSize / fontColor / fontFamily move the body while runs keep their deltas", async () => {
1697
+ const doc = await openFixture$18(engine, opts);
1698
+ try {
1699
+ const page = doc.page(fix.pageObjectNumber);
1700
+ const created = await page.annotations.create({
1701
+ subtype: "free-text",
1702
+ intent: "free-text",
1703
+ rect: shapeRect,
1704
+ fontFamily: "helvetica",
1705
+ fontSize: 14,
1706
+ textAlign: "left",
1707
+ color: {
1708
+ r: 0,
1709
+ g: 0,
1710
+ b: 0
1711
+ },
1712
+ richText: {
1713
+ body: {
1714
+ family: "Helvetica",
1715
+ size: 14
1716
+ },
1717
+ paragraphs: [{ runs: [{ text: "a" }, {
1718
+ text: "b",
1719
+ style: { size: 30 }
1720
+ }] }]
1721
+ }
1722
+ });
1723
+ const updated = await page.annotations.update(created.created.ref, {
1724
+ subtype: "free-text",
1725
+ fontSize: 20,
1726
+ fontColor: {
1727
+ r: 255,
1728
+ g: 0,
1729
+ b: 0
1730
+ },
1731
+ fontFamily: "times-bold"
1732
+ });
1733
+ expect(updated.updated.subtype).toBe("free-text");
1734
+ if (updated.updated.subtype === "free-text") {
1735
+ const dto = updated.updated;
1736
+ expect(dto.fontSize).toBe(20);
1737
+ expect(dto.fontFamily).toBe("times-bold");
1738
+ expect(dto.richText.body.size).toBe(20);
1739
+ expect(dto.richText.body.color).toBe("#FF0000");
1740
+ expect(dto.richText.body.family).toBe("Times");
1741
+ expect(dto.richText.body.weight).toBe(700);
1742
+ expect(dto.richText.paragraphs[0].runs[1]).toEqual({
1743
+ text: "b",
1744
+ style: { size: 30 }
1745
+ });
1746
+ }
1747
+ } finally {
1748
+ await doc.close();
1749
+ }
1750
+ });
1751
+ test("rich text: contents and richText that disagree reject with InvalidArg; agreeing writes the runs", async () => {
1752
+ const doc = await openFixture$18(engine, opts);
1753
+ try {
1754
+ const page = doc.page(fix.pageObjectNumber);
1755
+ const created = await page.annotations.create({
1756
+ subtype: "free-text",
1757
+ intent: "free-text",
1758
+ contents: "x",
1759
+ rect: shapeRect,
1760
+ fontFamily: "helvetica",
1761
+ fontSize: 14,
1762
+ textAlign: "left",
1763
+ color: {
1764
+ r: 0,
1765
+ g: 0,
1766
+ b: 0
1767
+ }
1768
+ });
1769
+ const ref = created.created.ref;
1770
+ let caught;
1771
+ try {
1772
+ await page.annotations.update(ref, {
1773
+ subtype: "free-text",
1774
+ contents: "stale",
1775
+ richText: { paragraphs: [{ runs: [{ text: "fresh" }] }] }
1776
+ });
1777
+ } catch (err) {
1778
+ caught = err;
1779
+ }
1780
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.InvalidArg)).toBe(true);
1781
+ const after = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
1782
+ expect(after?.contents).toBe("x");
1783
+ const agreed = await page.annotations.update(ref, {
1784
+ subtype: "free-text",
1785
+ contents: "fresh",
1786
+ richText: { paragraphs: [{ runs: [{ text: "fr" }, {
1787
+ text: "esh",
1788
+ style: { weight: 700 }
1789
+ }] }] }
1790
+ });
1791
+ expect(agreed.updated.subtype).toBe("free-text");
1792
+ if (agreed.updated.subtype === "free-text") {
1793
+ expect(agreed.updated.contents).toBe("fresh");
1794
+ expect(agreed.updated.richText.paragraphs[0].runs[1]).toEqual({
1795
+ text: "esh",
1796
+ style: { weight: 700 }
1797
+ });
1798
+ }
1799
+ } finally {
1800
+ await doc.close();
1801
+ }
1802
+ });
1522
1803
  test("create redact (area + text) round-trips label + colour fields", async () => {
1523
- const doc = await openFixture$15(engine, opts);
1804
+ const doc = await openFixture$18(engine, opts);
1524
1805
  try {
1525
1806
  const page = doc.page(fix.pageObjectNumber);
1526
1807
  const areaDraft = {
@@ -1600,7 +1881,7 @@ function runAnnotationMutationConformance(runner, opts) {
1600
1881
  }
1601
1882
  });
1602
1883
  test("update a redact patches the label and clears it, non-structurally", async () => {
1603
- const doc = await openFixture$15(engine, opts);
1884
+ const doc = await openFixture$18(engine, opts);
1604
1885
  try {
1605
1886
  const page = doc.page(fix.pageObjectNumber);
1606
1887
  const created = await page.annotations.create({
@@ -1677,7 +1958,7 @@ function runAnnotationMutationConformance(runner, opts) {
1677
1958
  }
1678
1959
  });
1679
1960
  test("create + update a caret round-trips color/opacity/rect differences", async () => {
1680
- const doc = await openFixture$15(engine, opts);
1961
+ const doc = await openFixture$18(engine, opts);
1681
1962
  try {
1682
1963
  const page = doc.page(fix.pageObjectNumber);
1683
1964
  const caretDraft = {
@@ -1757,7 +2038,7 @@ function runAnnotationMutationConformance(runner, opts) {
1757
2038
  }
1758
2039
  });
1759
2040
  test("update an ink annotation patches strokes + color and is non-structural", async () => {
1760
- const doc = await openFixture$15(engine, opts);
2041
+ const doc = await openFixture$18(engine, opts);
1761
2042
  try {
1762
2043
  const page = doc.page(fix.pageObjectNumber);
1763
2044
  const created = await page.annotations.create({
@@ -1808,7 +2089,7 @@ function runAnnotationMutationConformance(runner, opts) {
1808
2089
  }
1809
2090
  });
1810
2091
  test("ink highlight intent + blend round-trip and unrelated patches preserve blend", async () => {
1811
- const doc = await openFixture$15(engine, opts);
2092
+ const doc = await openFixture$18(engine, opts);
1812
2093
  try {
1813
2094
  const page = doc.page(fix.pageObjectNumber);
1814
2095
  const created = await page.annotations.create({
@@ -1851,7 +2132,7 @@ function runAnnotationMutationConformance(runner, opts) {
1851
2132
  }
1852
2133
  });
1853
2134
  test("update a polyline patches vertices + line endings and is non-structural", async () => {
1854
- const doc = await openFixture$15(engine, opts);
2135
+ const doc = await openFixture$18(engine, opts);
1855
2136
  try {
1856
2137
  const page = doc.page(fix.pageObjectNumber);
1857
2138
  const created = await page.annotations.create({
@@ -1900,7 +2181,7 @@ function runAnnotationMutationConformance(runner, opts) {
1900
2181
  }
1901
2182
  });
1902
2183
  test("update a shape annotation patches color and is non-structural", async () => {
1903
- const doc = await openFixture$15(engine, opts);
2184
+ const doc = await openFixture$18(engine, opts);
1904
2185
  try {
1905
2186
  const page = doc.page(fix.pageObjectNumber);
1906
2187
  const created = await page.annotations.create({
@@ -1949,7 +2230,7 @@ function runAnnotationMutationConformance(runner, opts) {
1949
2230
  }
1950
2231
  });
1951
2232
  if (opts.supportsAppearanceRasters) test("creating a shape bakes an /AP appearance the reader can rasterize", async () => {
1952
- const doc = await openFixture$15(engine, opts);
2233
+ const doc = await openFixture$18(engine, opts);
1953
2234
  try {
1954
2235
  const page = doc.page(fix.pageObjectNumber);
1955
2236
  const created = await page.annotations.create({
@@ -1979,7 +2260,7 @@ function runAnnotationMutationConformance(runner, opts) {
1979
2260
  }
1980
2261
  });
1981
2262
  test("update on a durable annotation is non-structural and never touches /NM", async () => {
1982
- const doc = await openFixture$15(engine, opts);
2263
+ const doc = await openFixture$18(engine, opts);
1983
2264
  try {
1984
2265
  const page = doc.page(fix.pageObjectNumber);
1985
2266
  const before = await page.annotations.list();
@@ -2005,7 +2286,7 @@ function runAnnotationMutationConformance(runner, opts) {
2005
2286
  }
2006
2287
  });
2007
2288
  if (fix.expectsWeakAnnotation) test("update on a weak annotation stamps a UUID v4 /NM and upgrades the ref", async () => {
2008
- const doc = await openFixture$15(engine, opts);
2289
+ const doc = await openFixture$18(engine, opts);
2009
2290
  try {
2010
2291
  const page = doc.page(fix.pageObjectNumber);
2011
2292
  const before = await page.annotations.list();
@@ -2035,7 +2316,7 @@ function runAnnotationMutationConformance(runner, opts) {
2035
2316
  }
2036
2317
  });
2037
2318
  test("delete by objectNumber removes the annotation and reports a stable id", async () => {
2038
- const doc = await openFixture$15(engine, opts);
2319
+ const doc = await openFixture$18(engine, opts);
2039
2320
  try {
2040
2321
  const page = doc.page(fix.pageObjectNumber);
2041
2322
  const draft = {
@@ -2065,7 +2346,7 @@ function runAnnotationMutationConformance(runner, opts) {
2065
2346
  }
2066
2347
  });
2067
2348
  if (fix.expectsWeakAnnotation) test("delete by index of a weak annotation reports deleted: null and refetch reason", async () => {
2068
- const doc = await openFixture$15(engine, opts);
2349
+ const doc = await openFixture$18(engine, opts);
2069
2350
  try {
2070
2351
  const page = doc.page(fix.pageObjectNumber);
2071
2352
  const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
@@ -2088,7 +2369,7 @@ function runAnnotationMutationConformance(runner, opts) {
2088
2369
  }
2089
2370
  });
2090
2371
  test("abort on create rejects with AbortError", async () => {
2091
- const doc = await openFixture$15(engine, opts);
2372
+ const doc = await openFixture$18(engine, opts);
2092
2373
  try {
2093
2374
  const page = doc.page(fix.pageObjectNumber);
2094
2375
  const draft = {
@@ -2104,7 +2385,7 @@ function runAnnotationMutationConformance(runner, opts) {
2104
2385
  }
2105
2386
  });
2106
2387
  test("update with a stale index revision throws InvalidReference", async () => {
2107
- const doc = await openFixture$15(engine, opts);
2388
+ const doc = await openFixture$18(engine, opts);
2108
2389
  try {
2109
2390
  const page = doc.page(fix.pageObjectNumber);
2110
2391
  const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
@@ -2131,7 +2412,7 @@ function runAnnotationMutationConformance(runner, opts) {
2131
2412
  }
2132
2413
  });
2133
2414
  test("move single durable annotation reorders within the page (single-as-batch)", async () => {
2134
- const doc = await openFixture$15(engine, opts);
2415
+ const doc = await openFixture$18(engine, opts);
2135
2416
  try {
2136
2417
  const page = doc.page(fix.pageObjectNumber);
2137
2418
  const aDraft = {
@@ -2175,7 +2456,7 @@ function runAnnotationMutationConformance(runner, opts) {
2175
2456
  }
2176
2457
  });
2177
2458
  test("move multi-block preserves caller-supplied order at the destination", async () => {
2178
- const doc = await openFixture$15(engine, opts);
2459
+ const doc = await openFixture$18(engine, opts);
2179
2460
  try {
2180
2461
  const page = doc.page(fix.pageObjectNumber);
2181
2462
  const ids = await Promise.all([
@@ -2214,7 +2495,7 @@ function runAnnotationMutationConformance(runner, opts) {
2214
2495
  }
2215
2496
  });
2216
2497
  if (fix.expectsWeakAnnotation) test("move on a weak annotation upgrades it to durable /NM (one rev bump for batch)", async () => {
2217
- const doc = await openFixture$15(engine, opts);
2498
+ const doc = await openFixture$18(engine, opts);
2218
2499
  try {
2219
2500
  const page = doc.page(fix.pageObjectNumber);
2220
2501
  const before = await page.annotations.list();
@@ -2239,7 +2520,7 @@ function runAnnotationMutationConformance(runner, opts) {
2239
2520
  }
2240
2521
  });
2241
2522
  test("move with a stale index revision rejects (locked rev-token guard)", async () => {
2242
- const doc = await openFixture$15(engine, opts);
2523
+ const doc = await openFixture$18(engine, opts);
2243
2524
  try {
2244
2525
  const page = doc.page(fix.pageObjectNumber);
2245
2526
  const a = await page.annotations.create({
@@ -2277,7 +2558,7 @@ function runAnnotationMutationConformance(runner, opts) {
2277
2558
  }
2278
2559
  });
2279
2560
  test("move with out-of-range toIndex rejects with InvalidArg", async () => {
2280
- const doc = await openFixture$15(engine, opts);
2561
+ const doc = await openFixture$18(engine, opts);
2281
2562
  try {
2282
2563
  const page = doc.page(fix.pageObjectNumber);
2283
2564
  const a = await page.annotations.create({
@@ -2301,7 +2582,7 @@ function runAnnotationMutationConformance(runner, opts) {
2301
2582
  }
2302
2583
  });
2303
2584
  test("move with duplicate refs rejects with InvalidArg", async () => {
2304
- const doc = await openFixture$15(engine, opts);
2585
+ const doc = await openFixture$18(engine, opts);
2305
2586
  try {
2306
2587
  const page = doc.page(fix.pageObjectNumber);
2307
2588
  const a = await page.annotations.create({
@@ -2324,7 +2605,7 @@ function runAnnotationMutationConformance(runner, opts) {
2324
2605
  }
2325
2606
  });
2326
2607
  test("abort on move rejects with AbortError", async () => {
2327
- const doc = await openFixture$15(engine, opts);
2608
+ const doc = await openFixture$18(engine, opts);
2328
2609
  try {
2329
2610
  const page = doc.page(fix.pageObjectNumber);
2330
2611
  const a = await page.annotations.create({
@@ -2345,7 +2626,7 @@ function runAnnotationMutationConformance(runner, opts) {
2345
2626
  }
2346
2627
  });
2347
2628
  test("a page move does NOT bump per-page RevisionTokens (weak refs survive reorder)", async () => {
2348
- const doc = await openFixture$15(engine, opts);
2629
+ const doc = await openFixture$18(engine, opts);
2349
2630
  try {
2350
2631
  const list = await doc.pages.list();
2351
2632
  if (list.pages.length < 1) return;
@@ -2360,7 +2641,7 @@ function runAnnotationMutationConformance(runner, opts) {
2360
2641
  }
2361
2642
  });
2362
2643
  test("create a reply links /IRT and defaults /RT to \"reply\", reporting the parent", async () => {
2363
- const doc = await openFixture$15(engine, opts);
2644
+ const doc = await openFixture$18(engine, opts);
2364
2645
  try {
2365
2646
  const page = doc.page(fix.pageObjectNumber);
2366
2647
  const parent = await page.annotations.create({
@@ -2394,7 +2675,7 @@ function runAnnotationMutationConformance(runner, opts) {
2394
2675
  }
2395
2676
  });
2396
2677
  test("create a grouped subordinate writes /RT /Group", async () => {
2397
- const doc = await openFixture$15(engine, opts);
2678
+ const doc = await openFixture$18(engine, opts);
2398
2679
  try {
2399
2680
  const page = doc.page(fix.pageObjectNumber);
2400
2681
  const primary = await page.annotations.create({
@@ -2425,7 +2706,7 @@ function runAnnotationMutationConformance(runner, opts) {
2425
2706
  }
2426
2707
  });
2427
2708
  test("create link annotations round-trip uri, goto/xyz, goto/fitH, and dead targets", async () => {
2428
- const doc = await openFixture$15(engine, opts);
2709
+ const doc = await openFixture$18(engine, opts);
2429
2710
  try {
2430
2711
  const page = doc.page(fix.pageObjectNumber);
2431
2712
  const uri = await page.annotations.create({
@@ -2502,7 +2783,7 @@ function runAnnotationMutationConformance(runner, opts) {
2502
2783
  }
2503
2784
  });
2504
2785
  test("a link patch retargets in both directions (uri→goto, goto→uri) and moves the rect", async () => {
2505
- const doc = await openFixture$15(engine, opts);
2786
+ const doc = await openFixture$18(engine, opts);
2506
2787
  try {
2507
2788
  const page = doc.page(fix.pageObjectNumber);
2508
2789
  const created = await page.annotations.create({
@@ -2557,7 +2838,7 @@ function runAnnotationMutationConformance(runner, opts) {
2557
2838
  }
2558
2839
  });
2559
2840
  test("a link patch clears the target with null — a dead link on re-read", async () => {
2560
- const doc = await openFixture$15(engine, opts);
2841
+ const doc = await openFixture$18(engine, opts);
2561
2842
  try {
2562
2843
  const page = doc.page(fix.pageObjectNumber);
2563
2844
  const created = await page.annotations.create({
@@ -2596,7 +2877,7 @@ function runAnnotationMutationConformance(runner, opts) {
2596
2877
  }
2597
2878
  });
2598
2879
  test("a link grouped to a highlight round-trips /IRT + /RT /Group with its target", async () => {
2599
- const doc = await openFixture$15(engine, opts);
2880
+ const doc = await openFixture$18(engine, opts);
2600
2881
  try {
2601
2882
  const page = doc.page(fix.pageObjectNumber);
2602
2883
  const parent = await page.annotations.create({
@@ -2630,7 +2911,7 @@ function runAnnotationMutationConformance(runner, opts) {
2630
2911
  }
2631
2912
  });
2632
2913
  test("replace-text round-trips /IT and groups StrikeOut under its Caret", async () => {
2633
- const doc = await openFixture$15(engine, opts);
2914
+ const doc = await openFixture$18(engine, opts);
2634
2915
  try {
2635
2916
  const page = doc.page(fix.pageObjectNumber);
2636
2917
  const caret = await page.annotations.create({
@@ -2682,7 +2963,7 @@ function runAnnotationMutationConformance(runner, opts) {
2682
2963
  }
2683
2964
  });
2684
2965
  test("patch inReplyTo: null clears /IRT and /RT (back to top-level)", async () => {
2685
- const doc = await openFixture$15(engine, opts);
2966
+ const doc = await openFixture$18(engine, opts);
2686
2967
  try {
2687
2968
  const page = doc.page(fix.pageObjectNumber);
2688
2969
  const parent = await page.annotations.create({
@@ -2709,7 +2990,7 @@ function runAnnotationMutationConformance(runner, opts) {
2709
2990
  }
2710
2991
  });
2711
2992
  test("create with a cross-page /IRT parent throws InvalidArg", async () => {
2712
- const doc = await openFixture$15(engine, opts);
2993
+ const doc = await openFixture$18(engine, opts);
2713
2994
  try {
2714
2995
  const page = doc.page(fix.pageObjectNumber);
2715
2996
  const parent = await page.annotations.create({
@@ -2740,7 +3021,7 @@ function runAnnotationMutationConformance(runner, opts) {
2740
3021
  }
2741
3022
  });
2742
3023
  test("a review-status reply round-trips /State + /StateModel + /Subj", async () => {
2743
- const doc = await openFixture$15(engine, opts);
3024
+ const doc = await openFixture$18(engine, opts);
2744
3025
  try {
2745
3026
  const page = doc.page(fix.pageObjectNumber);
2746
3027
  const target = await page.annotations.create({
@@ -2774,7 +3055,7 @@ function runAnnotationMutationConformance(runner, opts) {
2774
3055
  }
2775
3056
  });
2776
3057
  test("a draft /State without /StateModel is rejected with InvalidArg", async () => {
2777
- const doc = await openFixture$15(engine, opts);
3058
+ const doc = await openFixture$18(engine, opts);
2778
3059
  try {
2779
3060
  const page = doc.page(fix.pageObjectNumber);
2780
3061
  let caught;
@@ -2793,7 +3074,7 @@ function runAnnotationMutationConformance(runner, opts) {
2793
3074
  }
2794
3075
  });
2795
3076
  test("null-clear patches truly remove /State, /StateModel, /Subj and /Contents", async () => {
2796
- const doc = await openFixture$15(engine, opts);
3077
+ const doc = await openFixture$18(engine, opts);
2797
3078
  try {
2798
3079
  const page = doc.page(fix.pageObjectNumber);
2799
3080
  const note = await page.annotations.create({
@@ -2840,7 +3121,7 @@ function runAnnotationMutationConformance(runner, opts) {
2840
3121
  }
2841
3122
  });
2842
3123
  test("custom state models round-trip verbatim", async () => {
2843
- const doc = await openFixture$15(engine, opts);
3124
+ const doc = await openFixture$18(engine, opts);
2844
3125
  try {
2845
3126
  const page = doc.page(fix.pageObjectNumber);
2846
3127
  const custom = await page.annotations.create({
@@ -2868,7 +3149,7 @@ async function beginWeakEditIfRequired(doc, pageObjectNumber, fix) {
2868
3149
  if (doc.capabilities.weakAnnotationEditSessions !== "required" || !fix.expectsWeakAnnotation) return null;
2869
3150
  return doc.annotations.beginWeakEdit([pageObjectNumber]);
2870
3151
  }
2871
- async function openFixture$15(engine, opts) {
3152
+ async function openFixture$18(engine, opts) {
2872
3153
  if (opts.openKind === "bytes") {
2873
3154
  const bytes = await opts.fixture.bytes();
2874
3155
  return engine.open({
@@ -3458,7 +3739,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3458
3739
  if (engine) await engine.destroy();
3459
3740
  });
3460
3741
  test("renders the expected set of appearances with valid output", async () => {
3461
- const doc = await openFixture$14(engine, opts);
3742
+ const doc = await openFixture$17(engine, opts);
3462
3743
  try {
3463
3744
  const { pageState, appearances } = await collect(doc, opts, useRaw);
3464
3745
  expect(pageState.pageObjectNumber).toBe(opts.fixture.pageObjectNumber);
@@ -3485,7 +3766,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3485
3766
  }
3486
3767
  });
3487
3768
  if (opts.fixture.expectsWeakAppearance) test("weak (index-only) annotations are emitted, not dropped", async () => {
3488
- const doc = await openFixture$14(engine, opts);
3769
+ const doc = await openFixture$17(engine, opts);
3489
3770
  try {
3490
3771
  const { appearances } = await collect(doc, opts, useRaw);
3491
3772
  const weak = appearances.find((a) => a.ref.kind === "index");
@@ -3495,7 +3776,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3495
3776
  }
3496
3777
  });
3497
3778
  if (useRaw) test("rendered appearances are not blank (non-zero alpha)", async () => {
3498
- const doc = await openFixture$14(engine, opts);
3779
+ const doc = await openFixture$17(engine, opts);
3499
3780
  try {
3500
3781
  const { appearances } = await collect(doc, opts, useRaw);
3501
3782
  const anyVisible = appearances.some((a) => a.raster !== null && hasOpaquePixel(a.raster));
@@ -3505,7 +3786,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3505
3786
  }
3506
3787
  });
3507
3788
  test("abort() rejects with AbortError", async () => {
3508
- const doc = await openFixture$14(engine, opts);
3789
+ const doc = await openFixture$17(engine, opts);
3509
3790
  try {
3510
3791
  const page = doc.page(opts.fixture.pageObjectNumber);
3511
3792
  const p = useRaw ? page.annotations.renderAppearances({ scale: 1 }) : page.annotations.renderAppearanceImages({
@@ -3560,7 +3841,7 @@ function hasOpaquePixel(raster) {
3560
3841
  }
3561
3842
  return false;
3562
3843
  }
3563
- async function openFixture$14(engine, opts) {
3844
+ async function openFixture$17(engine, opts) {
3564
3845
  if (opts.openKind === "bytes") {
3565
3846
  const bytes = await opts.fixture.bytes();
3566
3847
  return engine.open({
@@ -3629,7 +3910,7 @@ function runPageReorderConformance(runner, opts) {
3629
3910
  if (engine) await engine.destroy();
3630
3911
  });
3631
3912
  test("pages.list() returns every page in display order, durable PONs only", async () => {
3632
- const doc = await openFixture$13(engine, opts);
3913
+ const doc = await openFixture$16(engine, opts);
3633
3914
  try {
3634
3915
  const list = await doc.pages.list();
3635
3916
  expect(require_schemas.PageListSnapshotSchema.safeParse(list).success).toBe(true);
@@ -3648,7 +3929,7 @@ function runPageReorderConformance(runner, opts) {
3648
3929
  }
3649
3930
  });
3650
3931
  test("pages.move() reorders pages and returns the full post-move order", async () => {
3651
- const doc = await openFixture$13(engine, opts);
3932
+ const doc = await openFixture$16(engine, opts);
3652
3933
  try {
3653
3934
  const before = await doc.pages.list();
3654
3935
  if (before.pages.length < 3) return;
@@ -3672,7 +3953,7 @@ function runPageReorderConformance(runner, opts) {
3672
3953
  }
3673
3954
  });
3674
3955
  test("weak index-based annotation refs survive a page reorder", async () => {
3675
- const doc = await openFixture$13(engine, opts);
3956
+ const doc = await openFixture$16(engine, opts);
3676
3957
  try {
3677
3958
  const list = await doc.pages.list();
3678
3959
  if (list.pages.length < 2) return;
@@ -3709,7 +3990,7 @@ function runPageReorderConformance(runner, opts) {
3709
3990
  }
3710
3991
  });
3711
3992
  test("pages.move() rejects duplicate PONs with InvalidArg", async () => {
3712
- const doc = await openFixture$13(engine, opts);
3993
+ const doc = await openFixture$16(engine, opts);
3713
3994
  try {
3714
3995
  const list = await doc.pages.list();
3715
3996
  if (list.pages.length < 1) return;
@@ -3726,7 +4007,7 @@ function runPageReorderConformance(runner, opts) {
3726
4007
  }
3727
4008
  });
3728
4009
  test("pages.move() rejects out-of-range destIndex with InvalidArg", async () => {
3729
- const doc = await openFixture$13(engine, opts);
4010
+ const doc = await openFixture$16(engine, opts);
3730
4011
  try {
3731
4012
  const list = await doc.pages.list();
3732
4013
  if (list.pages.length < 1) return;
@@ -3743,7 +4024,7 @@ function runPageReorderConformance(runner, opts) {
3743
4024
  }
3744
4025
  });
3745
4026
  test("pages.move() rejects unknown PON with NotFound or InvalidArg", async () => {
3746
- const doc = await openFixture$13(engine, opts);
4027
+ const doc = await openFixture$16(engine, opts);
3747
4028
  try {
3748
4029
  const list = await doc.pages.list();
3749
4030
  let bogus = 0;
@@ -3761,7 +4042,7 @@ function runPageReorderConformance(runner, opts) {
3761
4042
  }
3762
4043
  });
3763
4044
  test("abort on pages.move() rejects with AbortError", async () => {
3764
- const doc = await openFixture$13(engine, opts);
4045
+ const doc = await openFixture$16(engine, opts);
3765
4046
  try {
3766
4047
  const list = await doc.pages.list();
3767
4048
  if (list.pages.length < 1) return;
@@ -3775,7 +4056,7 @@ function runPageReorderConformance(runner, opts) {
3775
4056
  });
3776
4057
  });
3777
4058
  }
3778
- async function openFixture$13(engine, opts) {
4059
+ async function openFixture$16(engine, opts) {
3779
4060
  if (opts.openKind === "bytes") {
3780
4061
  const bytes = await opts.fixture.bytes();
3781
4062
  return engine.open({
@@ -3849,7 +4130,7 @@ function runPageRotateConformance(runner, opts) {
3849
4130
  if (engine) await engine.destroy();
3850
4131
  });
3851
4132
  test("pages.rotate() sets the absolute rotation and returns the full layout", async () => {
3852
- const doc = await openFixture$12(engine, opts);
4133
+ const doc = await openFixture$15(engine, opts);
3853
4134
  try {
3854
4135
  const before = await doc.pages.list();
3855
4136
  const target = before.pages[0];
@@ -3868,7 +4149,7 @@ function runPageRotateConformance(runner, opts) {
3868
4149
  }
3869
4150
  });
3870
4151
  test("absolute rotation is idempotent: setting the same value twice is a no-op", async () => {
3871
- const doc = await openFixture$12(engine, opts);
4152
+ const doc = await openFixture$15(engine, opts);
3872
4153
  try {
3873
4154
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3874
4155
  const first = await doc.pages.rotate([pon], 180);
@@ -3879,7 +4160,7 @@ function runPageRotateConformance(runner, opts) {
3879
4160
  }
3880
4161
  });
3881
4162
  test("one rotation applies to EVERY listed page (the multi-select gesture)", async () => {
3882
- const doc = await openFixture$12(engine, opts);
4163
+ const doc = await openFixture$15(engine, opts);
3883
4164
  try {
3884
4165
  const list = await doc.pages.list();
3885
4166
  if (list.pages.length < 2) return;
@@ -3891,7 +4172,7 @@ function runPageRotateConformance(runner, opts) {
3891
4172
  }
3892
4173
  });
3893
4174
  test("weak index-based annotation refs survive a rotate (no revision bump)", async () => {
3894
- const doc = await openFixture$12(engine, opts);
4175
+ const doc = await openFixture$15(engine, opts);
3895
4176
  try {
3896
4177
  const hostPon = (await doc.pages.list()).pages[0].pageObjectNumber;
3897
4178
  const hostPage = doc.page(hostPon);
@@ -3921,7 +4202,7 @@ function runPageRotateConformance(runner, opts) {
3921
4202
  }
3922
4203
  });
3923
4204
  test("pages.rotate() rejects an invalid rotation value with InvalidArg", async () => {
3924
- const doc = await openFixture$12(engine, opts);
4205
+ const doc = await openFixture$15(engine, opts);
3925
4206
  try {
3926
4207
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3927
4208
  let caught;
@@ -3936,7 +4217,7 @@ function runPageRotateConformance(runner, opts) {
3936
4217
  }
3937
4218
  });
3938
4219
  test("pages.rotate() rejects duplicate PONs with InvalidArg", async () => {
3939
- const doc = await openFixture$12(engine, opts);
4220
+ const doc = await openFixture$15(engine, opts);
3940
4221
  try {
3941
4222
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3942
4223
  let caught;
@@ -3951,7 +4232,7 @@ function runPageRotateConformance(runner, opts) {
3951
4232
  }
3952
4233
  });
3953
4234
  test("pages.rotate() rejects unknown PON with NotFound or InvalidArg", async () => {
3954
- const doc = await openFixture$12(engine, opts);
4235
+ const doc = await openFixture$15(engine, opts);
3955
4236
  try {
3956
4237
  const list = await doc.pages.list();
3957
4238
  let bogus = 0;
@@ -3969,7 +4250,7 @@ function runPageRotateConformance(runner, opts) {
3969
4250
  }
3970
4251
  });
3971
4252
  test("abort on pages.rotate() rejects with AbortError", async () => {
3972
- const doc = await openFixture$12(engine, opts);
4253
+ const doc = await openFixture$15(engine, opts);
3973
4254
  try {
3974
4255
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3975
4256
  const p = doc.pages.rotate([pon], 90);
@@ -3981,7 +4262,7 @@ function runPageRotateConformance(runner, opts) {
3981
4262
  });
3982
4263
  });
3983
4264
  }
3984
- async function openFixture$12(engine, opts) {
4265
+ async function openFixture$15(engine, opts) {
3985
4266
  if (opts.openKind === "bytes") {
3986
4267
  const bytes = await opts.fixture.bytes();
3987
4268
  return engine.open({
@@ -4044,7 +4325,7 @@ function runPageDeleteConformance(runner, opts) {
4044
4325
  if (engine) await engine.destroy();
4045
4326
  });
4046
4327
  test("pages.delete() removes the page and returns the surviving layout", async () => {
4047
- const doc = await openFixture$11(engine, opts);
4328
+ const doc = await openFixture$14(engine, opts);
4048
4329
  try {
4049
4330
  const before = await doc.pages.list();
4050
4331
  if (before.pages.length < 2) return;
@@ -4065,7 +4346,7 @@ function runPageDeleteConformance(runner, opts) {
4065
4346
  }
4066
4347
  });
4067
4348
  test("addressing a deleted page afterwards is a clean NotFound", async () => {
4068
- const doc = await openFixture$11(engine, opts);
4349
+ const doc = await openFixture$14(engine, opts);
4069
4350
  try {
4070
4351
  const before = await doc.pages.list();
4071
4352
  if (before.pages.length < 2) return;
@@ -4083,7 +4364,7 @@ function runPageDeleteConformance(runner, opts) {
4083
4364
  }
4084
4365
  });
4085
4366
  test("surviving pages keep their weak index-based annotation refs", async () => {
4086
- const doc = await openFixture$11(engine, opts);
4367
+ const doc = await openFixture$14(engine, opts);
4087
4368
  try {
4088
4369
  const list = await doc.pages.list();
4089
4370
  if (list.pages.length < 2) return;
@@ -4116,7 +4397,7 @@ function runPageDeleteConformance(runner, opts) {
4116
4397
  }
4117
4398
  });
4118
4399
  test("deleting EVERY page is rejected with InvalidArg", async () => {
4119
- const doc = await openFixture$11(engine, opts);
4400
+ const doc = await openFixture$14(engine, opts);
4120
4401
  try {
4121
4402
  const list = await doc.pages.list();
4122
4403
  let caught;
@@ -4131,7 +4412,7 @@ function runPageDeleteConformance(runner, opts) {
4131
4412
  }
4132
4413
  });
4133
4414
  test("pages.delete() rejects duplicate PONs with InvalidArg", async () => {
4134
- const doc = await openFixture$11(engine, opts);
4415
+ const doc = await openFixture$14(engine, opts);
4135
4416
  try {
4136
4417
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
4137
4418
  let caught;
@@ -4146,7 +4427,7 @@ function runPageDeleteConformance(runner, opts) {
4146
4427
  }
4147
4428
  });
4148
4429
  test("pages.delete() rejects unknown PON with NotFound or InvalidArg", async () => {
4149
- const doc = await openFixture$11(engine, opts);
4430
+ const doc = await openFixture$14(engine, opts);
4150
4431
  try {
4151
4432
  const list = await doc.pages.list();
4152
4433
  let bogus = 0;
@@ -4164,7 +4445,7 @@ function runPageDeleteConformance(runner, opts) {
4164
4445
  }
4165
4446
  });
4166
4447
  test("abort on pages.delete() rejects with AbortError", async () => {
4167
- const doc = await openFixture$11(engine, opts);
4448
+ const doc = await openFixture$14(engine, opts);
4168
4449
  try {
4169
4450
  const list = await doc.pages.list();
4170
4451
  if (list.pages.length < 2) return;
@@ -4178,7 +4459,224 @@ function runPageDeleteConformance(runner, opts) {
4178
4459
  });
4179
4460
  });
4180
4461
  }
4181
- async function openFixture$11(engine, opts) {
4462
+ async function openFixture$14(engine, opts) {
4463
+ if (opts.openKind === "bytes") {
4464
+ const bytes = await opts.fixture.bytes();
4465
+ return engine.open({
4466
+ kind: "bytes",
4467
+ id: opts.fixture.id,
4468
+ bytes
4469
+ });
4470
+ }
4471
+ return engine.open({
4472
+ kind: "id",
4473
+ id: opts.fixture.cloudId ?? opts.fixture.id
4474
+ });
4475
+ }
4476
+ //#endregion
4477
+ //#region src/conformance/runNamedPagesConformance.ts
4478
+ /**
4479
+ * Named-pages conformance: the catalog's `/Names /Pages` registry as LAYOUT
4480
+ * data. Locks the invariants both engines must share — do not loosen
4481
+ * without re-reading `NamedPageEntry` and `PageNameResult`:
4482
+ *
4483
+ * 1. `pages.list().namedPages` is present (possibly empty) and every
4484
+ * registration resolves to a page in `pages`, a template, or is
4485
+ * reported dangling — never silently dropped.
4486
+ * 2. `pages.setName()` creates or replaces by decoded key (idempotent),
4487
+ * `replace` renames in one job, and the result IS the new layout.
4488
+ * 3. `pages.removeName()` drops the registration and nothing else.
4489
+ * 4. Deleting a page removes every registration pointing at it — inside
4490
+ * the delete, with no plugin in the loop.
4491
+ * 5. Non-ASCII keys round-trip unchanged (Acrobat writes UTF-16 + BOM).
4492
+ *
4493
+ * Skips cleanly when the engine does not implement the optional members.
4494
+ */
4495
+ function runNamedPagesConformance(runner, opts) {
4496
+ const { describe, test, beforeAll, afterAll, expect } = runner;
4497
+ describe(`named pages conformance: ${opts.label}`, () => {
4498
+ let engine;
4499
+ beforeAll(async () => {
4500
+ engine = await opts.makeEngine();
4501
+ });
4502
+ afterAll(async () => {
4503
+ if (engine) await engine.destroy();
4504
+ });
4505
+ test("pages.list() carries namedPages and every entry resolves", async () => {
4506
+ const doc = await openFixture$13(engine, opts);
4507
+ try {
4508
+ const layout = await doc.pages.list();
4509
+ require_schemas.PageListSnapshotSchema.parse(layout);
4510
+ expect(Array.isArray(layout.namedPages)).toBe(true);
4511
+ const pons = new Set(layout.pages.map((page) => page.pageObjectNumber));
4512
+ for (const entry of layout.namedPages ?? []) {
4513
+ expect(entry.name.length > 0).toBe(true);
4514
+ if (entry.target.kind === "page") expect(pons.has(entry.target.pageObjectNumber)).toBe(true);
4515
+ }
4516
+ } finally {
4517
+ await doc.close();
4518
+ }
4519
+ });
4520
+ test("pages.setName() registers, replaces by key, and renames via `replace`", async () => {
4521
+ const doc = await openFixture$13(engine, opts);
4522
+ try {
4523
+ if (!doc.pages.setName) return;
4524
+ const before = await doc.pages.list();
4525
+ const [first, second] = before.pages;
4526
+ const baseline = (before.namedPages ?? []).length;
4527
+ const created = await doc.pages.setName({
4528
+ name: "Approved=Goedgekeurd",
4529
+ pageObjectNumber: first.pageObjectNumber
4530
+ });
4531
+ require_schemas.PageNameResultSchema.parse(created);
4532
+ expect(created.layout.pages.length).toBe(before.pages.length);
4533
+ expect(find(created, "Approved=Goedgekeurd")).toEqual({
4534
+ kind: "page",
4535
+ pageObjectNumber: first.pageObjectNumber
4536
+ });
4537
+ expect((created.layout.namedPages ?? []).length).toBe(baseline + 1);
4538
+ if (created.cache) {
4539
+ expect(created.cache.docVersion > created.cache.previousDocVersion).toBe(true);
4540
+ expect(created.cache.layoutVersion > 0).toBe(true);
4541
+ }
4542
+ const listed = await doc.pages.list();
4543
+ expect(listed.namedPages).toEqual(created.layout.namedPages);
4544
+ const replaced = await doc.pages.setName({
4545
+ name: "Approved=Goedgekeurd",
4546
+ pageObjectNumber: second.pageObjectNumber
4547
+ });
4548
+ expect(find(replaced, "Approved=Goedgekeurd")).toEqual({
4549
+ kind: "page",
4550
+ pageObjectNumber: second.pageObjectNumber
4551
+ });
4552
+ expect((replaced.layout.namedPages ?? []).length).toBe(baseline + 1);
4553
+ const renamed = await doc.pages.setName({
4554
+ name: "Approved=Approved",
4555
+ pageObjectNumber: second.pageObjectNumber,
4556
+ replace: "Approved=Goedgekeurd"
4557
+ });
4558
+ expect(find(renamed, "Approved=Goedgekeurd")).toBe(void 0);
4559
+ expect(find(renamed, "Approved=Approved")).toEqual({
4560
+ kind: "page",
4561
+ pageObjectNumber: second.pageObjectNumber
4562
+ });
4563
+ expect((renamed.layout.namedPages ?? []).length).toBe(baseline + 1);
4564
+ } finally {
4565
+ await doc.close();
4566
+ }
4567
+ });
4568
+ test("non-ASCII keys round-trip through set → list", async () => {
4569
+ const doc = await openFixture$13(engine, opts);
4570
+ try {
4571
+ if (!doc.pages.setName) return;
4572
+ const before = await doc.pages.list();
4573
+ const key = "Stämpel=Stämpel 日本";
4574
+ const result = await doc.pages.setName({
4575
+ name: key,
4576
+ pageObjectNumber: before.pages[0].pageObjectNumber
4577
+ });
4578
+ expect(find(result, key)).toEqual({
4579
+ kind: "page",
4580
+ pageObjectNumber: before.pages[0].pageObjectNumber
4581
+ });
4582
+ const listed = await doc.pages.list();
4583
+ expect((listed.namedPages ?? []).some((entry) => entry.name === key)).toBe(true);
4584
+ } finally {
4585
+ await doc.close();
4586
+ }
4587
+ });
4588
+ test("pages.removeName() drops the registration and keeps the page", async () => {
4589
+ const doc = await openFixture$13(engine, opts);
4590
+ try {
4591
+ if (!doc.pages.setName || !doc.pages.removeName) return;
4592
+ const before = await doc.pages.list();
4593
+ await doc.pages.setName({
4594
+ name: "Draft=Concept",
4595
+ pageObjectNumber: before.pages[0].pageObjectNumber
4596
+ });
4597
+ const removed = await doc.pages.removeName({ name: "Draft=Concept" });
4598
+ require_schemas.PageNameResultSchema.parse(removed);
4599
+ expect(find(removed, "Draft=Concept")).toBe(void 0);
4600
+ expect(removed.layout.pages.length).toBe(before.pages.length);
4601
+ await expect(doc.pages.removeName({ name: "Draft=Concept" })).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.NotFound });
4602
+ } finally {
4603
+ await doc.close();
4604
+ }
4605
+ });
4606
+ test("pages.setName() rejects an empty name and an unknown page", async () => {
4607
+ const doc = await openFixture$13(engine, opts);
4608
+ try {
4609
+ if (!doc.pages.setName) return;
4610
+ const before = await doc.pages.list();
4611
+ await expect(doc.pages.setName({
4612
+ name: "",
4613
+ pageObjectNumber: before.pages[0].pageObjectNumber
4614
+ })).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
4615
+ let unknownPage = null;
4616
+ try {
4617
+ await doc.pages.setName({
4618
+ name: "Ghost=Ghost",
4619
+ pageObjectNumber: 987654321
4620
+ });
4621
+ } catch (error) {
4622
+ unknownPage = error;
4623
+ }
4624
+ expect(require_fold.EngineError.is(unknownPage, require_PageInsertBlankInput.EngineErrorCode.NotFound) || require_fold.EngineError.is(unknownPage, require_PageInsertBlankInput.EngineErrorCode.InvalidArg)).toBe(true);
4625
+ } finally {
4626
+ await doc.close();
4627
+ }
4628
+ });
4629
+ test("deleting a page removes every registration pointing at it", async () => {
4630
+ const doc = await openFixture$13(engine, opts);
4631
+ try {
4632
+ if (!doc.pages.setName) return;
4633
+ const before = await doc.pages.list();
4634
+ if (before.pages.length < 2) return;
4635
+ const victim = before.pages[1].pageObjectNumber;
4636
+ const survivor = before.pages[0].pageObjectNumber;
4637
+ await doc.pages.setName({
4638
+ name: "Victim=One",
4639
+ pageObjectNumber: victim
4640
+ });
4641
+ await doc.pages.setName({
4642
+ name: "Victim=Two",
4643
+ pageObjectNumber: victim
4644
+ });
4645
+ await doc.pages.setName({
4646
+ name: "Survivor",
4647
+ pageObjectNumber: survivor
4648
+ });
4649
+ const deleted = await doc.pages.delete([victim]);
4650
+ const names = (deleted.layout.namedPages ?? []).map((entry) => entry.name);
4651
+ expect(names.includes("Victim=One")).toBe(false);
4652
+ expect(names.includes("Victim=Two")).toBe(false);
4653
+ expect(names).toContain("Survivor");
4654
+ expect((deleted.layout.namedPages ?? []).some((entry) => entry.target.kind === "dangling")).toBe(false);
4655
+ } finally {
4656
+ await doc.close();
4657
+ }
4658
+ });
4659
+ test("abort on pages.setName() rejects with AbortError", async () => {
4660
+ const doc = await openFixture$13(engine, opts);
4661
+ try {
4662
+ if (!doc.pages.setName) return;
4663
+ const before = await doc.pages.list();
4664
+ const promise = doc.pages.setName({
4665
+ name: "Aborted=Aborted",
4666
+ pageObjectNumber: before.pages[0].pageObjectNumber
4667
+ });
4668
+ promise.abort();
4669
+ await expect(promise).rejects.toBeInstanceOf(require_AbortError.AbortError);
4670
+ } finally {
4671
+ await doc.close();
4672
+ }
4673
+ });
4674
+ });
4675
+ }
4676
+ function find(result, name) {
4677
+ return (result.layout.namedPages ?? []).find((entry) => entry.name === name)?.target;
4678
+ }
4679
+ async function openFixture$13(engine, opts) {
4182
4680
  if (opts.openKind === "bytes") {
4183
4681
  const bytes = await opts.fixture.bytes();
4184
4682
  return engine.open({
@@ -4193,6 +4691,243 @@ async function openFixture$11(engine, opts) {
4193
4691
  });
4194
4692
  }
4195
4693
  //#endregion
4694
+ //#region src/conformance/runAnnotationFlattenConformance.ts
4695
+ const square$1 = (left, bottom) => ({
4696
+ subtype: "square",
4697
+ rect: {
4698
+ left,
4699
+ bottom,
4700
+ right: left + 40,
4701
+ top: bottom + 40
4702
+ },
4703
+ color: {
4704
+ r: 20,
4705
+ g: 40,
4706
+ b: 220
4707
+ },
4708
+ strokeWidth: 2
4709
+ });
4710
+ /**
4711
+ * Selective annotation flatten: `page(pon).annotations.flatten(refs)` is
4712
+ * `pages.flatten` for a chosen set. Locks, on every engine:
4713
+ * 1. only the given refs are painted and removed; the rest of the page's
4714
+ * annotations are untouched;
4715
+ * 2. per-ref status (`applied` / `skipped`), one `annotations.flattened`
4716
+ * event, a `meta` with that page's new pins, layout unchanged;
4717
+ * 3. a hidden ref is `skipped`; a ref on another page rejects `InvalidArg`
4718
+ * with nothing mutated.
4719
+ * Skips cleanly when the engine does not implement the optional member.
4720
+ */
4721
+ function runAnnotationFlattenConformance(runner, opts) {
4722
+ const { describe, test, beforeAll, afterAll, expect } = runner;
4723
+ describe(`annotation flatten conformance: ${opts.label}`, () => {
4724
+ let engine;
4725
+ beforeAll(async () => {
4726
+ engine = await opts.makeEngine();
4727
+ });
4728
+ afterAll(async () => {
4729
+ if (engine) await engine.destroy();
4730
+ });
4731
+ test("flattens exactly the given refs and reports per-ref status", async () => {
4732
+ const doc = await openFixture$12(engine, opts);
4733
+ try {
4734
+ const layoutBefore = await doc.pages.list();
4735
+ const pon = layoutBefore.pages[0].pageObjectNumber;
4736
+ const page = doc.page(pon);
4737
+ if (!page.annotations.flatten) return;
4738
+ const a = (await page.annotations.create(square$1(20, 20))).created.ref;
4739
+ const b = (await page.annotations.create(square$1(80, 20))).created.ref;
4740
+ const c = (await page.annotations.create(square$1(140, 20))).created.ref;
4741
+ await page.annotations.update(c, {
4742
+ subtype: "square",
4743
+ flags: { hidden: true }
4744
+ });
4745
+ const before = await page.annotations.list();
4746
+ const events = [];
4747
+ const unsubscribe = doc.events.subscribe((event) => {
4748
+ if (event.type === "annotations.flattened") events.push(event);
4749
+ });
4750
+ const result = await page.annotations.flatten([a, c], "display");
4751
+ unsubscribe();
4752
+ expect(require_schemas.AnnotationFlattenResultSchema.safeParse(result).success).toBe(true);
4753
+ expect(result.pageObjectNumber).toBe(pon);
4754
+ expect(result.usage).toBe("display");
4755
+ expect(result.results.map((item) => item.status)).toEqual(["applied", "skipped"]);
4756
+ expect(result.meta === null).toBe(false);
4757
+ expect(events).toHaveLength(1);
4758
+ expect(await doc.pages.list()).toEqual(layoutBefore);
4759
+ const after = await page.annotations.list();
4760
+ expect(after.annotations.length).toBe(before.annotations.length - 1);
4761
+ expect(after.annotations.some((dto) => sameRef(dto.ref, b))).toBe(true);
4762
+ expect(after.annotations.some((dto) => sameRef(dto.ref, c))).toBe(true);
4763
+ expect(after.annotations.some((dto) => sameRef(dto.ref, a))).toBe(false);
4764
+ expect(after.pageState.revision.generation > before.pageState.revision.generation).toBe(true);
4765
+ } finally {
4766
+ await doc.close();
4767
+ }
4768
+ });
4769
+ test("a ref on another page rejects InvalidArg and mutates nothing", async () => {
4770
+ const doc = await openFixture$12(engine, opts);
4771
+ try {
4772
+ const layout = await doc.pages.list();
4773
+ if (layout.pages.length < 2) return;
4774
+ const page0 = doc.page(layout.pages[0].pageObjectNumber);
4775
+ const page1 = doc.page(layout.pages[1].pageObjectNumber);
4776
+ if (!page0.annotations.flatten) return;
4777
+ const own = (await page0.annotations.create(square$1(20, 100))).created.ref;
4778
+ const foreign = (await page1.annotations.create(square$1(20, 100))).created.ref;
4779
+ const countBefore = (await page0.annotations.list()).annotations.length;
4780
+ await expect(page0.annotations.flatten([own, foreign])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
4781
+ expect((await page0.annotations.list()).annotations.length).toBe(countBefore);
4782
+ } finally {
4783
+ await doc.close();
4784
+ }
4785
+ });
4786
+ test("an empty ref list rejects InvalidArg; an unknown ref rejects", async () => {
4787
+ const doc = await openFixture$12(engine, opts);
4788
+ try {
4789
+ const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
4790
+ const page = doc.page(pon);
4791
+ if (!page.annotations.flatten) return;
4792
+ await expect(page.annotations.flatten([])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
4793
+ let unknown = null;
4794
+ try {
4795
+ await page.annotations.flatten([{
4796
+ kind: "objectNumber",
4797
+ pageObjectNumber: pon,
4798
+ annotObjectNumber: 987654321
4799
+ }]);
4800
+ } catch (error) {
4801
+ unknown = error;
4802
+ }
4803
+ expect(require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.NotFound) || require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.InvalidArg) || require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.InvalidReference)).toBe(true);
4804
+ } finally {
4805
+ await doc.close();
4806
+ }
4807
+ });
4808
+ });
4809
+ }
4810
+ function sameRef(left, right) {
4811
+ if (left.kind === "objectNumber" && right.kind === "objectNumber") return left.annotObjectNumber === right.annotObjectNumber;
4812
+ return JSON.stringify(left) === JSON.stringify(right);
4813
+ }
4814
+ async function openFixture$12(engine, opts) {
4815
+ if (opts.openKind === "bytes") return engine.open({
4816
+ kind: "bytes",
4817
+ id: opts.fixture.id,
4818
+ bytes: await opts.fixture.bytes()
4819
+ });
4820
+ return engine.open({
4821
+ kind: "id",
4822
+ id: opts.fixture.cloudId ?? opts.fixture.id
4823
+ });
4824
+ }
4825
+ //#endregion
4826
+ //#region src/conformance/runAnnotationAppearanceExportConformance.ts
4827
+ const PDF_MAGIC$1 = [
4828
+ 37,
4829
+ 80,
4830
+ 68,
4831
+ 70
4832
+ ];
4833
+ const square = (left, bottom, size = 40) => ({
4834
+ subtype: "square",
4835
+ rect: {
4836
+ left,
4837
+ bottom,
4838
+ right: left + size,
4839
+ top: bottom + size
4840
+ },
4841
+ color: {
4842
+ r: 220,
4843
+ g: 20,
4844
+ b: 20
4845
+ },
4846
+ strokeWidth: 2
4847
+ });
4848
+ /**
4849
+ * `page(pon).annotations.exportAppearance(refs)`: the chosen annotations'
4850
+ * appearances as ONE single-page PDF sized to their union /Rect. Locks:
4851
+ * 1. bytes are a PDF; the source page is untouched (no mutation, no
4852
+ * revision bump);
4853
+ * 2. re-opened (local engines), the page is exactly the union size and the
4854
+ * requested count of annotations is gone — they are content now;
4855
+ * 3. a ref on another page rejects `InvalidArg`; an empty list rejects.
4856
+ * Skips cleanly when the engine does not implement the optional member.
4857
+ */
4858
+ function runAnnotationAppearanceExportConformance(runner, opts) {
4859
+ const { describe, test, beforeAll, afterAll, expect } = runner;
4860
+ describe(`annotation appearance export conformance: ${opts.label}`, () => {
4861
+ let engine;
4862
+ beforeAll(async () => {
4863
+ engine = await opts.makeEngine();
4864
+ });
4865
+ afterAll(async () => {
4866
+ if (engine) await engine.destroy();
4867
+ });
4868
+ test("returns a single-page PDF sized to the union rect; source untouched", async () => {
4869
+ const doc = await openFixture$11(engine, opts);
4870
+ let exported = null;
4871
+ try {
4872
+ const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
4873
+ const page = doc.page(pon);
4874
+ if (!page.annotations.exportAppearance) return;
4875
+ const a = (await page.annotations.create(square(20, 20))).created.ref;
4876
+ const b = (await page.annotations.create(square(100, 60, 30))).created.ref;
4877
+ const before = await page.annotations.list();
4878
+ const bytes = await page.annotations.exportAppearance([a, b]);
4879
+ expect(bytes.length > 4).toBe(true);
4880
+ expect([...bytes.subarray(0, 4)]).toEqual(PDF_MAGIC$1);
4881
+ const after = await page.annotations.list();
4882
+ expect(after.annotations.length).toBe(before.annotations.length);
4883
+ expect(after.pageState.revision.generation).toBe(before.pageState.revision.generation);
4884
+ if (opts.openKind !== "bytes") return;
4885
+ exported = await engine.open({
4886
+ kind: "bytes",
4887
+ id: `${opts.fixture.id}-appearance`,
4888
+ bytes
4889
+ });
4890
+ const layout = await exported.pages.list();
4891
+ expect(layout.pageCount).toBe(1);
4892
+ expect(Math.round(layout.pages[0].size.width)).toBe(110);
4893
+ expect(Math.round(layout.pages[0].size.height)).toBe(70);
4894
+ const exportedPage = exported.page(layout.pages[0].pageObjectNumber);
4895
+ expect((await exportedPage.annotations.list()).annotations).toHaveLength(0);
4896
+ } finally {
4897
+ if (exported) await exported.close();
4898
+ await doc.close();
4899
+ }
4900
+ });
4901
+ test("a ref on another page and an empty list reject InvalidArg", async () => {
4902
+ const doc = await openFixture$11(engine, opts);
4903
+ try {
4904
+ const layout = await doc.pages.list();
4905
+ const page0 = doc.page(layout.pages[0].pageObjectNumber);
4906
+ if (!page0.annotations.exportAppearance) return;
4907
+ await expect(page0.annotations.exportAppearance([])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
4908
+ if (layout.pages.length < 2) return;
4909
+ const page1 = doc.page(layout.pages[1].pageObjectNumber);
4910
+ const own = (await page0.annotations.create(square(20, 100))).created.ref;
4911
+ const foreign = (await page1.annotations.create(square(20, 100))).created.ref;
4912
+ await expect(page0.annotations.exportAppearance([own, foreign])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
4913
+ } finally {
4914
+ await doc.close();
4915
+ }
4916
+ });
4917
+ });
4918
+ }
4919
+ async function openFixture$11(engine, opts) {
4920
+ if (opts.openKind === "bytes") return engine.open({
4921
+ kind: "bytes",
4922
+ id: opts.fixture.id,
4923
+ bytes: await opts.fixture.bytes()
4924
+ });
4925
+ return engine.open({
4926
+ kind: "id",
4927
+ id: opts.fixture.cloudId ?? opts.fixture.id
4928
+ });
4929
+ }
4930
+ //#endregion
4196
4931
  //#region src/conformance/runPageFlattenConformance.ts
4197
4932
  /** Transport-neutral coverage for the content + annotation flatten rail. */
4198
4933
  function runPageFlattenConformance(runner, opts) {
@@ -6221,17 +6956,804 @@ function diffAnnotation(i, a, b, errs) {
6221
6956
  if (a.nm !== b.nm) errs.push(`annotations[${i}].nm mismatch: ${a.nm} vs ${b.nm}`);
6222
6957
  }
6223
6958
  //#endregion
6959
+ //#region src/conformance/runSignatureConformance.ts
6960
+ /**
6961
+ * Digital signatures, read side. The service is OPTIONAL on the contract
6962
+ * (`downloadLayer?` pattern) — the suite runs only where the engine
6963
+ * provides it.
6964
+ *
6965
+ * Invariants:
6966
+ * 1. Byte facts describe the LOADED bytes: a revision is a byte prefix,
6967
+ * a whole-revision signature seals exactly one, the digest over its
6968
+ * `/ByteRange` is what the file's bytes hash to, and an unsaved edit
6969
+ * changes none of it.
6970
+ * 2. Protection is derived from the signatures in the file and applied
6971
+ * as document-derived capabilities: a certification with permission 2
6972
+ * takes page assembly and annotation writes away, a FieldMDP freezes
6973
+ * the fields it names, a rewrite save is refused. Under
6974
+ * `signedDocumentPolicy: 'permit'` none of that applies and the
6975
+ * analysis is unchanged.
6976
+ * 3. The same answers come from a plain session and from a layer
6977
+ * session over the same bytes.
6978
+ */
6979
+ function runSignatureConformance(runner, opts) {
6980
+ const { describe, test, beforeAll, afterAll, expect } = runner;
6981
+ describe(`signatures conformance: ${opts.label}`, () => {
6982
+ let engine;
6983
+ let permitEngine = null;
6984
+ let supported = false;
6985
+ beforeAll(async () => {
6986
+ engine = await opts.makeEngine();
6987
+ const probe = await open(engine, opts, opts.fixtures.unsignedForm);
6988
+ supported = probe.signatures !== void 0;
6989
+ await probe.close();
6990
+ if (supported && opts.makePermitEngine) permitEngine = await opts.makePermitEngine();
6991
+ });
6992
+ afterAll(async () => {
6993
+ if (permitEngine) await permitEngine.destroy();
6994
+ if (engine) await engine.destroy();
6995
+ });
6996
+ test("an unsigned form has no signatures and no protection; version() hashes the loaded bytes", async () => {
6997
+ if (!supported) return;
6998
+ const bytes = await opts.fixtures.unsignedForm.bytes();
6999
+ const doc = await open(engine, opts, opts.fixtures.unsignedForm);
7000
+ try {
7001
+ const snapshot = await doc.signatures.list();
7002
+ expect(snapshot.chainValid).toBe(true);
7003
+ expect(snapshot.signatures).toEqual([]);
7004
+ expect(snapshot.revisions.length >= 1).toBe(true);
7005
+ expect(snapshot.protection.judged).toBeNull();
7006
+ expect(snapshot.protection.enforced).toBeNull();
7007
+ expect(snapshot.protection.certification).toBeNull();
7008
+ expect(snapshot.protection.fieldLocks).toEqual([]);
7009
+ expect(doc.security.allows("doc.pages.assemble")).toBe(true);
7010
+ if (doc.version) {
7011
+ const version = await doc.version();
7012
+ expect(version.byteLength).toBe(bytes.byteLength);
7013
+ expect(version.sha256).toBe(await sha256Hex(bytes));
7014
+ }
7015
+ let caught;
7016
+ try {
7017
+ await doc.signatures.contents({
7018
+ kind: "fqn",
7019
+ name: "no-such-field"
7020
+ });
7021
+ } catch (err) {
7022
+ caught = err;
7023
+ }
7024
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.NotFound)).toBe(true);
7025
+ } finally {
7026
+ await doc.close();
7027
+ }
7028
+ });
7029
+ test("two approval signatures: revisions, coverage, digests, contents, revision bytes", async () => {
7030
+ if (!supported) return;
7031
+ const fixture = opts.fixtures.twoApprovals;
7032
+ const bytes = await fixture.bytes();
7033
+ const doc = await open(engine, opts, fixture);
7034
+ try {
7035
+ const snapshot = await doc.signatures.list();
7036
+ expect(snapshot.chainValid).toBe(true);
7037
+ expect(snapshot.revisions).toHaveLength(3);
7038
+ expect(snapshot.signatures).toHaveLength(2);
7039
+ expect(snapshot.revisions[2].end).toBe(bytes.byteLength);
7040
+ for (const [i, sig] of snapshot.signatures.entries()) {
7041
+ expect(sig.index).toBe(i);
7042
+ expect(sig.signed).toBe(true);
7043
+ expect(sig.kind).toBe("signature");
7044
+ expect(sig.field.kind).toBe("objectNumber");
7045
+ expect(sig.fieldName).toBe(fixture.fieldNames[i]);
7046
+ expect(sig.coverage).toBe("whole-revision");
7047
+ expect(sig.revisionIndex).toBe(i + 1);
7048
+ expect(snapshot.revisions[i + 1].signatureIndex).toBe(i);
7049
+ expect(sig.subFilter).toBe("ETSI.CAdES.detached");
7050
+ expect(typeof sig.signer.claimedTime).toBe("string");
7051
+ expect(sig.docMdp).toBeNull();
7052
+ expect(sig.catalogCertification).toBe(false);
7053
+ expect(sig.contentsSize > 0).toBe(true);
7054
+ expect(sig.byteRange[0]).toBe(0);
7055
+ expect(sig.byteRange[2] + sig.byteRange[3]).toBe(snapshot.revisions[i + 1].end);
7056
+ }
7057
+ expect(snapshot.protection.judged).toBe("annotate");
7058
+ expect(snapshot.protection.enforced).toBeNull();
7059
+ expect(snapshot.protection.certification).toBeNull();
7060
+ expect(snapshot.protection.fieldLocks).toEqual([]);
7061
+ const first = snapshot.signatures[0];
7062
+ const contents = await doc.signatures.contents(first.field);
7063
+ expect(contents.byteLength).toBe(first.contentsSize);
7064
+ expect(contents[0]).toBe(48);
7065
+ const [a, b, c, d] = first.byteRange;
7066
+ const sealed = await doc.signatures.revisionBytes(1);
7067
+ expect(sealed.byteLength).toBe(snapshot.revisions[1].end);
7068
+ expect(bytesEqual(sealed, bytes.subarray(0, sealed.byteLength))).toBe(true);
7069
+ const signedBytes = concat(sealed.subarray(a, a + b), sealed.subarray(c, c + d));
7070
+ const digest = await doc.signatures.digest(first.field, "sha256");
7071
+ expect(digest.byteLength).toBe(32);
7072
+ expect(toHex(digest)).toBe(await sha256Hex(signedBytes));
7073
+ expect((await doc.signatures.digest(first.field, "sha512")).byteLength).toBe(64);
7074
+ const byName = await doc.signatures.digest({
7075
+ kind: "fqn",
7076
+ name: first.fieldName
7077
+ }, "sha256");
7078
+ expect(toHex(byName)).toBe(toHex(digest));
7079
+ const text = (await doc.forms.list()).fields.find((f) => f.name === fixture.textField);
7080
+ expect(text).toBeTruthy();
7081
+ await doc.forms.setValue({
7082
+ kind: "objectNumber",
7083
+ fieldObjectNumber: text.fieldObjectNumber
7084
+ }, {
7085
+ type: "text",
7086
+ value: "unsaved"
7087
+ });
7088
+ const again = await doc.signatures.list();
7089
+ expect(again.revisions).toEqual(snapshot.revisions);
7090
+ expect(again.signatures.map((s) => [
7091
+ s.coverage,
7092
+ s.revisionIndex,
7093
+ s.byteRange
7094
+ ])).toEqual(snapshot.signatures.map((s) => [
7095
+ s.coverage,
7096
+ s.revisionIndex,
7097
+ s.byteRange
7098
+ ]));
7099
+ expect(toHex(await doc.signatures.digest(first.field, "sha256"))).toBe(toHex(digest));
7100
+ if (doc.version) expect((await doc.version()).sha256).toBe(await sha256Hex(bytes));
7101
+ } finally {
7102
+ await doc.close();
7103
+ }
7104
+ });
7105
+ test("a certification takes structural capabilities away; permit lifts it", async () => {
7106
+ if (!supported) return;
7107
+ const fixture = opts.fixtures.certified;
7108
+ const doc = await open(engine, opts, fixture);
7109
+ try {
7110
+ const snapshot = await doc.signatures.list();
7111
+ const signed = snapshot.signatures.filter((s) => s.signed);
7112
+ expect(signed).toHaveLength(1);
7113
+ const sig = signed[0];
7114
+ expect(sig.fieldName).toBe(fixture.fieldName);
7115
+ expect(sig.coverage).toBe("whole-revision");
7116
+ expect(sig.docMdp).toBe(2);
7117
+ expect(sig.catalogCertification).toBe(true);
7118
+ expect(snapshot.protection.judged).toBe("fill");
7119
+ expect(snapshot.protection.enforced).toBe("fill");
7120
+ expect(snapshot.protection.certification).toEqual({
7121
+ signatureIndex: sig.index,
7122
+ permission: 2
7123
+ });
7124
+ expect(doc.security.allows("doc.forms.read")).toBe(true);
7125
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7126
+ expect(doc.security.allows("doc.annotate.modify")).toBe(false);
7127
+ expect(doc.security.allows("doc.pages.assemble")).toBe(false);
7128
+ expect(doc.security.allows("doc.redact")).toBe(false);
7129
+ const list = await doc.pages.list();
7130
+ let caught;
7131
+ try {
7132
+ await doc.pages.delete([list.pages[0].pageObjectNumber]);
7133
+ } catch (err) {
7134
+ caught = err;
7135
+ }
7136
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
7137
+ caught = void 0;
7138
+ try {
7139
+ await doc.download({ mode: "rewrite" });
7140
+ } catch (err) {
7141
+ caught = err;
7142
+ }
7143
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
7144
+ const saved = await doc.download();
7145
+ expect(saved.byteLength >= snapshot.revisions[1].end).toBe(true);
7146
+ } finally {
7147
+ await doc.close();
7148
+ }
7149
+ if (!permitEngine) return;
7150
+ const permitted = await open(permitEngine, opts, fixture);
7151
+ try {
7152
+ const snapshot = await permitted.signatures.list();
7153
+ expect(snapshot.protection.judged).toBe("fill");
7154
+ expect(snapshot.protection.enforced).toBe("fill");
7155
+ expect(permitted.security.allows("doc.pages.assemble")).toBe(true);
7156
+ const rewritten = await permitted.download({ mode: "rewrite" });
7157
+ expect(rewritten.byteLength > 0).toBe(true);
7158
+ } finally {
7159
+ await permitted.close();
7160
+ }
7161
+ });
7162
+ test("a FieldMDP freezes the fields it names; permit lifts it", async () => {
7163
+ if (!supported) return;
7164
+ const fixture = opts.fixtures.fieldMdp;
7165
+ const doc = await open(engine, opts, fixture);
7166
+ const lockedRef = {
7167
+ kind: "fqn",
7168
+ name: fixture.lockedField
7169
+ };
7170
+ try {
7171
+ const snapshot = await doc.signatures.list();
7172
+ const sig = snapshot.signatures[0];
7173
+ expect(sig.fieldName).toBe(fixture.fieldName);
7174
+ expect(sig.fieldMdp).toEqual({
7175
+ action: "include",
7176
+ fields: [fixture.lockedField]
7177
+ });
7178
+ expect(snapshot.protection.judged).toBe("annotate");
7179
+ expect(snapshot.protection.enforced).toBeNull();
7180
+ expect(snapshot.protection.fieldLocks[0]).toEqual({
7181
+ signatureIndex: 0,
7182
+ source: "fieldmdp",
7183
+ spec: {
7184
+ action: "include",
7185
+ fields: [fixture.lockedField]
7186
+ }
7187
+ });
7188
+ for (const lock of snapshot.protection.fieldLocks) {
7189
+ expect(lock.signatureIndex).toBe(0);
7190
+ expect(lock.spec).toEqual({
7191
+ action: "include",
7192
+ fields: [fixture.lockedField]
7193
+ });
7194
+ }
7195
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7196
+ let caught;
7197
+ try {
7198
+ await doc.forms.setValue(lockedRef, {
7199
+ type: "text",
7200
+ value: "changed"
7201
+ });
7202
+ } catch (err) {
7203
+ caught = err;
7204
+ }
7205
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
7206
+ const field = await doc.forms.get(lockedRef);
7207
+ expect(field.family).toBe("text");
7208
+ expect(field.value === "changed").toBe(false);
7209
+ } finally {
7210
+ await doc.close();
7211
+ }
7212
+ if (!permitEngine) return;
7213
+ const permitted = await open(permitEngine, opts, fixture);
7214
+ try {
7215
+ const result = await permitted.forms.setValue(lockedRef, {
7216
+ type: "text",
7217
+ value: "changed"
7218
+ });
7219
+ expect(result.field.name).toBe(fixture.lockedField);
7220
+ } finally {
7221
+ await permitted.close();
7222
+ }
7223
+ });
7224
+ test("well-formed ranges that seal no revision are partial, with no revision index", async () => {
7225
+ if (!supported) return;
7226
+ const doc = await open(engine, opts, opts.fixtures.partialChain);
7227
+ try {
7228
+ const snapshot = await doc.signatures.list();
7229
+ expect(snapshot.chainValid).toBe(true);
7230
+ expect(snapshot.revisions).toHaveLength(3);
7231
+ expect(snapshot.signatures).toHaveLength(2);
7232
+ for (const sig of snapshot.signatures) {
7233
+ expect(sig.signed).toBe(true);
7234
+ expect(sig.coverage).toBe("partial");
7235
+ expect(sig.revisionIndex).toBeNull();
7236
+ expect(sig.contentsSize).toBe(13);
7237
+ expect((await doc.signatures.contents(sig.field)).byteLength).toBe(13);
7238
+ expect((await doc.signatures.digest(sig.field, "sha256")).byteLength).toBe(32);
7239
+ }
7240
+ expect(snapshot.revisions.every((r) => r.signatureIndex === null)).toBe(true);
7241
+ expect(snapshot.protection.judged).toBe("annotate");
7242
+ expect(snapshot.protection.enforced).toBeNull();
7243
+ let caught;
7244
+ try {
7245
+ await doc.signatures.revisionBytes(3);
7246
+ } catch (err) {
7247
+ caught = err;
7248
+ }
7249
+ expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.NotFound)).toBe(true);
7250
+ } finally {
7251
+ await doc.close();
7252
+ }
7253
+ });
7254
+ if (opts.openKind !== "id") {
7255
+ runSigningTests(runner, opts, () => engine, () => supported);
7256
+ runAnalysisTests(runner, opts, () => engine, () => permitEngine, () => supported);
7257
+ }
7258
+ });
7259
+ }
7260
+ function runAnalysisTests(runner, opts, engineOf, permitEngineOf, supported) {
7261
+ const { test, expect } = runner;
7262
+ test("analyze: a form fill and a second signature after an approval are permitted", async () => {
7263
+ if (!supported()) return;
7264
+ const doc = await open(engineOf(), opts, opts.fixtures.twoApprovals);
7265
+ try {
7266
+ const analysis = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
7267
+ expect(analysis.mode).toBe("authoritative");
7268
+ expect(analysis.basis.source).toBe("persisted");
7269
+ expect(analysis.since).toEqual({
7270
+ revisionIndex: 1,
7271
+ signatureIndex: 0
7272
+ });
7273
+ expect(analysis.until.revisionIndex).toBe(2);
7274
+ expect(analysis.steps).toHaveLength(1);
7275
+ const [step] = analysis.steps;
7276
+ expect(step.levelInForce).toBe("annotate");
7277
+ expect(step.verdict).toBe("permitted");
7278
+ expect(step.changes.length > 0).toBe(true);
7279
+ const rules = new Set(step.findings.filter((f) => f.verdict === "permitted").map((f) => f.rule));
7280
+ expect(rules.has("form-fill")).toBe(true);
7281
+ expect(rules.has("signature-added")).toBe(true);
7282
+ expect(step.findings.filter((f) => f.verdict === "forbidden")).toEqual([]);
7283
+ expect(analysis.verdict).toBe("permitted");
7284
+ const latest = await doc.signatures.analyze({ since: { signatureIndex: 1 } });
7285
+ expect(latest.steps).toHaveLength(0);
7286
+ expect(latest.verdict).toBe("unchanged");
7287
+ const historic = await doc.signatures.analyze({
7288
+ since: { revisionIndex: 0 },
7289
+ until: { revisionIndex: 1 }
7290
+ });
7291
+ expect(historic.steps).toHaveLength(1);
7292
+ expect(historic.verdict).toBe("permitted");
7293
+ } finally {
7294
+ await doc.close();
7295
+ }
7296
+ });
7297
+ test("analyze: the working copy is one more revision; a partial signature cannot anchor", async () => {
7298
+ if (!supported()) return;
7299
+ const fx = opts.fixtures.unsignedSigField;
7300
+ const doc = await open(engineOf(), opts, fx);
7301
+ try {
7302
+ const prepared = await doc.signatures.prepare({
7303
+ field: {
7304
+ kind: "fqn",
7305
+ name: fx.fieldName
7306
+ },
7307
+ certify: { permission: 2 }
7308
+ });
7309
+ await doc.signatures.complete({
7310
+ signingId: prepared.signingId,
7311
+ cms: FAKE_CMS,
7312
+ expectedVersion: prepared.expectedVersion
7313
+ });
7314
+ const clean = await doc.signatures.analyze({
7315
+ since: { signatureIndex: 0 },
7316
+ until: "working-copy"
7317
+ });
7318
+ expect(clean.steps).toHaveLength(0);
7319
+ expect(clean.verdict).toBe("unchanged");
7320
+ await doc.forms.setValue({
7321
+ kind: "fqn",
7322
+ name: fx.textField
7323
+ }, {
7324
+ type: "text",
7325
+ value: "draft"
7326
+ });
7327
+ const persisted = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
7328
+ expect(persisted.steps).toHaveLength(0);
7329
+ const working = await doc.signatures.analyze({
7330
+ since: { signatureIndex: 0 },
7331
+ until: "working-copy"
7332
+ });
7333
+ expect(working.basis.source).toBe("working-copy");
7334
+ expect(working.steps).toHaveLength(1);
7335
+ expect(working.steps[0].levelInForce).toBe("fill");
7336
+ expect(working.verdict).toBe("permitted");
7337
+ expect(working.steps[0].findings.some((f) => f.rule === "form-fill" && f.verdict === "permitted")).toBe(true);
7338
+ } finally {
7339
+ await doc.close();
7340
+ }
7341
+ const partial = await open(engineOf(), opts, opts.fixtures.partialChain);
7342
+ try {
7343
+ expect(await caughtCode(() => partial.signatures.analyze({ since: { signatureIndex: 0 } }))).toBe(require_PageInsertBlankInput.EngineErrorCode.InvalidArg);
7344
+ const byRevision = await partial.signatures.analyze({ since: { revisionIndex: 0 } });
7345
+ expect(byRevision.later.revisionCount).toBe(2);
7346
+ expect(byRevision.steps).toHaveLength(0);
7347
+ const inFull = await partial.signatures.analyze({
7348
+ since: { revisionIndex: 0 },
7349
+ detail: "full"
7350
+ });
7351
+ expect(inFull.steps).toHaveLength(2);
7352
+ expect(inFull.current.verdict).toBe(byRevision.current.verdict);
7353
+ } finally {
7354
+ await partial.close();
7355
+ }
7356
+ });
7357
+ test("analyze: a page edit under a certification and a locked field edit are forbidden", async () => {
7358
+ if (!supported()) return;
7359
+ const permit = permitEngineOf();
7360
+ if (!permit) return;
7361
+ const certified = await open(permit, opts, opts.fixtures.certified);
7362
+ let reopened = null;
7363
+ try {
7364
+ const list = await certified.pages.list();
7365
+ await certified.pages.rotate([list.pages[0].pageObjectNumber], 90);
7366
+ const working = await certified.signatures.analyze({
7367
+ since: { signatureIndex: 0 },
7368
+ until: "working-copy"
7369
+ });
7370
+ expect(working.verdict).toBe("forbidden");
7371
+ const bytes = await certified.download();
7372
+ reopened = await reopen(permit, opts, `${opts.fixtures.certified.id}-tampered`, bytes);
7373
+ const analysis = await reopened.signatures.analyze({ since: { signatureIndex: 0 } });
7374
+ expect(analysis.verdict).toBe("forbidden");
7375
+ expect(analysis.steps[0].findings.some((f) => f.verdict === "forbidden")).toBe(true);
7376
+ } finally {
7377
+ if (reopened) await reopened.close();
7378
+ await certified.close();
7379
+ }
7380
+ const locked = await open(permit, opts, opts.fixtures.fieldMdp);
7381
+ try {
7382
+ await locked.forms.setValue({
7383
+ kind: "fqn",
7384
+ name: opts.fixtures.fieldMdp.lockedField
7385
+ }, {
7386
+ type: "text",
7387
+ value: "tampered"
7388
+ });
7389
+ const working = await locked.signatures.analyze({
7390
+ since: { signatureIndex: 0 },
7391
+ until: "working-copy"
7392
+ });
7393
+ expect(working.verdict).toBe("forbidden");
7394
+ expect(working.steps[0].findings.some((f) => f.rule === "field-lock")).toBe(true);
7395
+ } finally {
7396
+ await locked.close();
7397
+ }
7398
+ });
7399
+ }
7400
+ /** A minimal DER SEQUENCE standing in for a CMS: the engine's job ends at the bytes. */
7401
+ const FAKE_CMS = new Uint8Array([
7402
+ 48,
7403
+ 6,
7404
+ 2,
7405
+ 1,
7406
+ 1,
7407
+ 2,
7408
+ 1,
7409
+ 2
7410
+ ]);
7411
+ async function caughtCode(run) {
7412
+ try {
7413
+ await run();
7414
+ return null;
7415
+ } catch (err) {
7416
+ return err instanceof require_fold.EngineError ? err.code : "not-an-engine-error";
7417
+ }
7418
+ }
7419
+ function runSigningTests(runner, opts, engineOf, supported) {
7420
+ const { test, expect } = runner;
7421
+ const fx = () => opts.fixtures.unsignedSigField;
7422
+ const sigRef = () => ({
7423
+ kind: "fqn",
7424
+ name: fx().fieldName
7425
+ });
7426
+ const textRef = () => ({
7427
+ kind: "fqn",
7428
+ name: fx().textField
7429
+ });
7430
+ test("sign: prepare seals a candidate, complete installs it, download returns it verbatim", async () => {
7431
+ if (!supported()) return;
7432
+ const engine = engineOf();
7433
+ const doc = await open(engine, opts, fx());
7434
+ const events = [];
7435
+ const unsubscribe = doc.events.subscribe((event) => events.push(event.type));
7436
+ let reopened = null;
7437
+ try {
7438
+ const v0 = await doc.version();
7439
+ const before = await doc.signatures.list();
7440
+ expect(before.revisions).toHaveLength(1);
7441
+ expect(before.signatures).toHaveLength(1);
7442
+ expect(before.signatures[0].signed).toBe(false);
7443
+ expect(before.protection.judged).toBeNull();
7444
+ const prepared = await doc.signatures.prepare({
7445
+ field: sigRef(),
7446
+ subFilter: "ETSI.CAdES.detached",
7447
+ digest: "sha256",
7448
+ contentsSize: 1024,
7449
+ attribution: { reason: "conformance" }
7450
+ });
7451
+ expect(prepared.digest.byteLength).toBe(32);
7452
+ expect(prepared.algorithm).toBe("sha256");
7453
+ expect(prepared.subFilter).toBe("ETSI.CAdES.detached");
7454
+ expect(prepared.contentsSize).toBe(1024);
7455
+ expect(prepared.byteRange[0]).toBe(0);
7456
+ expect(prepared.byteRange[2] > prepared.byteRange[1]).toBe(true);
7457
+ expect(prepared.expectedVersion.baseSha256).toBe(v0.sha256);
7458
+ expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningPending);
7459
+ expect(await caughtCode(() => doc.forms.setValue(textRef(), {
7460
+ type: "text",
7461
+ value: "x"
7462
+ }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningPending);
7463
+ const during = await doc.signatures.list();
7464
+ expect(during.signatures[0].signed).toBe(false);
7465
+ expect((await doc.version()).sha256).toBe(v0.sha256);
7466
+ expect(await caughtCode(() => doc.signatures.complete({
7467
+ signingId: prepared.signingId,
7468
+ cms: FAKE_CMS,
7469
+ expectedVersion: {
7470
+ baseSha256: prepared.expectedVersion.baseSha256,
7471
+ editsVersion: prepared.expectedVersion.editsVersion + 1
7472
+ }
7473
+ }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningVersionMismatch);
7474
+ const result = await doc.signatures.complete({
7475
+ signingId: prepared.signingId,
7476
+ cms: FAKE_CMS,
7477
+ expectedVersion: prepared.expectedVersion
7478
+ });
7479
+ expect(result.status).toBe("completed");
7480
+ expect(result.signature.signed).toBe(true);
7481
+ expect(result.signature.fieldName).toBe(fx().fieldName);
7482
+ expect(result.signature.coverage).toBe("whole-revision");
7483
+ expect(result.signature.revisionIndex).toBe(1);
7484
+ expect(result.signature.subFilter).toBe("ETSI.CAdES.detached");
7485
+ expect(result.signature.signer.reason).toBe("conformance");
7486
+ expect(result.signature.contentsSize).toBe(FAKE_CMS.byteLength);
7487
+ expect(result.previous).toEqual(prepared.expectedVersion);
7488
+ expect(result.version.sha256 === v0.sha256).toBe(false);
7489
+ expect(result.protection.judged).toBe("annotate");
7490
+ expect(result.protection.enforced).toBeNull();
7491
+ expect(result.meta.affectedPages.length >= 1).toBe(true);
7492
+ expect(await doc.version()).toEqual(result.version);
7493
+ const after = await doc.signatures.list();
7494
+ expect(after.revisions).toHaveLength(2);
7495
+ expect(after.signatures[0].signed).toBe(true);
7496
+ expect(after.signatures[0].revisionIndex).toBe(1);
7497
+ expect(after.revisions[1].signatureIndex).toBe(0);
7498
+ expect(toHex(await doc.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
7499
+ expect(bytesEqual(await doc.signatures.contents(sigRef()), FAKE_CMS)).toBe(true);
7500
+ const bytes = await doc.download();
7501
+ expect(bytes.byteLength).toBe(result.version.byteLength);
7502
+ expect(await sha256Hex(bytes)).toBe(result.version.sha256);
7503
+ reopened = await reopen(engine, opts, `${fx().id}-signed`, bytes);
7504
+ const fresh = await reopened.signatures.list();
7505
+ expect(fresh.revisions).toHaveLength(2);
7506
+ expect(fresh.signatures[0].coverage).toBe("whole-revision");
7507
+ expect(toHex(await reopened.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
7508
+ const replay = await doc.signatures.complete({
7509
+ signingId: prepared.signingId,
7510
+ cms: FAKE_CMS,
7511
+ expectedVersion: prepared.expectedVersion
7512
+ });
7513
+ expect(replay.status).toBe("already-completed");
7514
+ expect(replay.version).toEqual(result.version);
7515
+ expect((await doc.signatures.abort(prepared.signingId)).status).toBe("already-completed");
7516
+ expect((await doc.signatures.abort("never-prepared")).status).toBe("unknown");
7517
+ await doc.forms.setValue(textRef(), {
7518
+ type: "text",
7519
+ value: "after"
7520
+ });
7521
+ expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SignatureRefused);
7522
+ expect(events.filter((t) => t === "signature.prepared")).toHaveLength(1);
7523
+ expect(events.filter((t) => t === "signature.completed")).toHaveLength(1);
7524
+ expect(events.filter((t) => t === "document.versioned")).toHaveLength(1);
7525
+ } finally {
7526
+ unsubscribe();
7527
+ if (reopened) await reopened.close();
7528
+ await doc.close();
7529
+ }
7530
+ });
7531
+ test("sign: unsaved edits are sealed in the signature's own revision (layer session)", async () => {
7532
+ if (!supported()) return;
7533
+ const doc = await open(engineOf(), opts, fx());
7534
+ try {
7535
+ await doc.forms.setValue(textRef(), {
7536
+ type: "text",
7537
+ value: "unsaved"
7538
+ });
7539
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7540
+ const result = await doc.signatures.complete({
7541
+ signingId: prepared.signingId,
7542
+ cms: FAKE_CMS,
7543
+ expectedVersion: prepared.expectedVersion
7544
+ });
7545
+ expect(result.signature.revisionIndex).toBe(1);
7546
+ const after = await doc.signatures.list();
7547
+ expect(after.revisions).toHaveLength(2);
7548
+ const text = await doc.forms.get(textRef());
7549
+ expect(text.value).toBe("unsaved");
7550
+ const bytes = await doc.download();
7551
+ expect(await sha256Hex(bytes)).toBe(result.version.sha256);
7552
+ } finally {
7553
+ await doc.close();
7554
+ }
7555
+ });
7556
+ test("sign: a certification protects the document from the moment it completes", async () => {
7557
+ if (!supported()) return;
7558
+ const doc = await open(engineOf(), opts, fx());
7559
+ try {
7560
+ expect(doc.security.allows("doc.pages.assemble")).toBe(true);
7561
+ const prepared = await doc.signatures.prepare({
7562
+ field: sigRef(),
7563
+ certify: { permission: 2 },
7564
+ subFilter: "adbe.pkcs7.detached"
7565
+ });
7566
+ const result = await doc.signatures.complete({
7567
+ signingId: prepared.signingId,
7568
+ cms: FAKE_CMS,
7569
+ expectedVersion: prepared.expectedVersion
7570
+ });
7571
+ expect(result.signature.docMdp).toBe(2);
7572
+ expect(result.signature.catalogCertification).toBe(true);
7573
+ expect(result.protection.judged).toBe("fill");
7574
+ expect(result.protection.enforced).toBe("fill");
7575
+ expect(result.protection.certification).toEqual({
7576
+ signatureIndex: result.signature.index,
7577
+ permission: 2
7578
+ });
7579
+ expect(doc.security.allows("doc.pages.assemble")).toBe(false);
7580
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7581
+ const list = await doc.pages.list();
7582
+ expect(await caughtCode(() => doc.pages.delete([list.pages[0].pageObjectNumber]))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
7583
+ expect(await caughtCode(() => doc.download({ mode: "rewrite" }))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
7584
+ await doc.forms.setValue(textRef(), {
7585
+ type: "text",
7586
+ value: "filled"
7587
+ });
7588
+ const after = await doc.signatures.list();
7589
+ expect(after.signatures[0].coverage).toBe("whole-revision");
7590
+ } finally {
7591
+ await doc.close();
7592
+ }
7593
+ });
7594
+ test("sign: a lock freezes the named fields and is mirrored on the field", async () => {
7595
+ if (!supported()) return;
7596
+ const doc = await open(engineOf(), opts, fx());
7597
+ try {
7598
+ const prepared = await doc.signatures.prepare({
7599
+ field: sigRef(),
7600
+ lock: {
7601
+ action: "include",
7602
+ fields: [fx().textField]
7603
+ }
7604
+ });
7605
+ const result = await doc.signatures.complete({
7606
+ signingId: prepared.signingId,
7607
+ cms: FAKE_CMS,
7608
+ expectedVersion: prepared.expectedVersion
7609
+ });
7610
+ expect(result.signature.fieldMdp).toEqual({
7611
+ action: "include",
7612
+ fields: [fx().textField]
7613
+ });
7614
+ expect(result.signature.lock).toEqual({
7615
+ action: "include",
7616
+ fields: [fx().textField]
7617
+ });
7618
+ expect(result.protection.fieldLocks.length >= 1).toBe(true);
7619
+ expect(await caughtCode(() => doc.forms.setValue(textRef(), {
7620
+ type: "text",
7621
+ value: "nope"
7622
+ }))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
7623
+ } finally {
7624
+ await doc.close();
7625
+ }
7626
+ });
7627
+ test("sign: abort discards the candidate and lifts the fence", async () => {
7628
+ if (!supported()) return;
7629
+ const doc = await open(engineOf(), opts, fx());
7630
+ try {
7631
+ const v0 = await doc.version();
7632
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7633
+ expect((await doc.signatures.abort(prepared.signingId)).status).toBe("aborted");
7634
+ expect((await doc.signatures.list()).signatures[0].signed).toBe(false);
7635
+ expect((await doc.version()).sha256).toBe(v0.sha256);
7636
+ await doc.forms.setValue(textRef(), {
7637
+ type: "text",
7638
+ value: "free again"
7639
+ });
7640
+ expect(await caughtCode(() => doc.signatures.complete({
7641
+ signingId: prepared.signingId,
7642
+ cms: FAKE_CMS,
7643
+ expectedVersion: prepared.expectedVersion
7644
+ }))).toBe(require_PageInsertBlankInput.EngineErrorCode.NotFound);
7645
+ } finally {
7646
+ await doc.close();
7647
+ }
7648
+ });
7649
+ test("sign: artwork from a PDF page lands on the widget", async () => {
7650
+ if (!supported()) return;
7651
+ const doc = await open(engineOf(), opts, fx());
7652
+ try {
7653
+ const artwork = await opts.fixtures.artwork.bytes();
7654
+ const prepared = await doc.signatures.prepare({
7655
+ field: sigRef(),
7656
+ appearance: {
7657
+ pdf: artwork,
7658
+ pageIndex: 0
7659
+ }
7660
+ });
7661
+ const result = await doc.signatures.complete({
7662
+ signingId: prepared.signingId,
7663
+ cms: FAKE_CMS,
7664
+ expectedVersion: prepared.expectedVersion
7665
+ });
7666
+ expect(result.signature.coverage).toBe("whole-revision");
7667
+ expect(result.signature.widget).toBeTruthy();
7668
+ const widget = (await doc.page(result.signature.widget.pageObjectNumber).annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && a.ref.annotObjectNumber === result.signature.widget.annotObjectNumber);
7669
+ expect(widget).toBeTruthy();
7670
+ } finally {
7671
+ await doc.close();
7672
+ }
7673
+ });
7674
+ if (opts.openKind === "layerBytes") test("sign: a layer session keeps its kind, with an empty layer over the new base", async () => {
7675
+ if (!supported()) return;
7676
+ const doc = await open(engineOf(), opts, fx());
7677
+ try {
7678
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7679
+ const result = await doc.signatures.complete({
7680
+ signingId: prepared.signingId,
7681
+ cms: FAKE_CMS,
7682
+ expectedVersion: prepared.expectedVersion
7683
+ });
7684
+ const layer = await doc.downloadLayer();
7685
+ expect(layer.byteLength > 0).toBe(true);
7686
+ expect(await sha256Hex(await doc.download())).toBe(result.version.sha256);
7687
+ } finally {
7688
+ await doc.close();
7689
+ }
7690
+ });
7691
+ }
7692
+ async function reopen(engine, opts, id, bytes) {
7693
+ if (opts.openKind === "layerBytes") return engine.open({
7694
+ kind: "layerBytes",
7695
+ id,
7696
+ baseBytes: bytes
7697
+ });
7698
+ return engine.open({
7699
+ kind: "bytes",
7700
+ id,
7701
+ bytes
7702
+ });
7703
+ }
7704
+ async function open(engine, opts, fixture) {
7705
+ if (opts.openKind === "bytes") return engine.open({
7706
+ kind: "bytes",
7707
+ id: fixture.id,
7708
+ bytes: await fixture.bytes()
7709
+ });
7710
+ if (opts.openKind === "layerBytes") return engine.open({
7711
+ kind: "layerBytes",
7712
+ id: `${fixture.id}-layer`,
7713
+ baseBytes: await fixture.bytes()
7714
+ });
7715
+ return engine.open({
7716
+ kind: "id",
7717
+ id: fixture.cloudId ?? fixture.id
7718
+ });
7719
+ }
7720
+ function concat(a, b) {
7721
+ const out = new Uint8Array(a.byteLength + b.byteLength);
7722
+ out.set(a, 0);
7723
+ out.set(b, a.byteLength);
7724
+ return out;
7725
+ }
7726
+ function bytesEqual(a, b) {
7727
+ if (a.byteLength !== b.byteLength) return false;
7728
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
7729
+ return true;
7730
+ }
7731
+ function toHex(bytes) {
7732
+ let out = "";
7733
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
7734
+ return out;
7735
+ }
7736
+ async function sha256Hex(bytes) {
7737
+ const copy = new Uint8Array(bytes.byteLength);
7738
+ copy.set(bytes);
7739
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", copy);
7740
+ return toHex(new Uint8Array(digest));
7741
+ }
7742
+ //#endregion
6224
7743
  exports.TEXT_DIVERGENCE_CASES = TEXT_DIVERGENCE_CASES;
6225
7744
  exports.diffAnnotationListSnapshot = diffAnnotationListSnapshot;
6226
7745
  exports.diffAnnotationListSnapshotAll = diffAnnotationListSnapshotAll;
6227
7746
  exports.runActionsConformance = runActionsConformance;
6228
7747
  exports.runAnnotationAppearanceConformance = runAnnotationAppearanceConformance;
7748
+ exports.runAnnotationAppearanceExportConformance = runAnnotationAppearanceExportConformance;
7749
+ exports.runAnnotationFlattenConformance = runAnnotationFlattenConformance;
6229
7750
  exports.runAnnotationMutationConformance = runAnnotationMutationConformance;
6230
7751
  exports.runAnnotationReadConformance = runAnnotationReadConformance;
6231
7752
  exports.runAttachmentConformance = runAttachmentConformance;
6232
7753
  exports.runDocumentEventsConformance = runDocumentEventsConformance;
6233
7754
  exports.runFormConformance = runFormConformance;
6234
7755
  exports.runMetadataConformance = runMetadataConformance;
7756
+ exports.runNamedPagesConformance = runNamedPagesConformance;
6235
7757
  exports.runPageDeleteConformance = runPageDeleteConformance;
6236
7758
  exports.runPageExtractConformance = runPageExtractConformance;
6237
7759
  exports.runPageFlattenConformance = runPageFlattenConformance;
@@ -6244,6 +7766,7 @@ exports.runPageTextConformance = runPageTextConformance;
6244
7766
  exports.runPieceInfoConformance = runPieceInfoConformance;
6245
7767
  exports.runRedactionApplyConformance = runRedactionApplyConformance;
6246
7768
  exports.runSearchConformance = runSearchConformance;
7769
+ exports.runSignatureConformance = runSignatureConformance;
6247
7770
  exports.runTextDivergenceConformance = runTextDivergenceConformance;
6248
7771
 
6249
7772
  //# sourceMappingURL=conformance.cjs.map