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

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 (62) hide show
  1. package/dist/{AbortError-CjSasIlL.js → AbortError-8-9JAX1Y.js} +2 -2
  2. package/dist/{AbortError-CjSasIlL.js.map → AbortError-8-9JAX1Y.js.map} +1 -1
  3. package/dist/{AbortError-ByBflvEl.cjs → AbortError-CRSooKsk.cjs} +2 -2
  4. package/dist/{AbortError-ByBflvEl.cjs.map → AbortError-CRSooKsk.cjs.map} +1 -1
  5. package/dist/{AnnotationListSnapshot-W0RCsyDE.d.cts → AnnotationListSnapshot-C1U3XUxP.d.cts} +846 -127
  6. package/dist/{AnnotationListSnapshot-W0RCsyDE.d.ts → AnnotationListSnapshot-C1U3XUxP.d.ts} +846 -127
  7. package/dist/{DocumentSecurityService-Df79fgpe.d.cts → DocumentSecurityService-6rhztZlz.d.cts} +33 -2
  8. package/dist/{DocumentSecurityService-B-tULKNY.d.ts → DocumentSecurityService-7nL9aY75.d.ts} +33 -2
  9. package/dist/{Engine-B7Oe7f5i.d.ts → Engine-CWnAKccD.d.ts} +62 -19
  10. package/dist/{Engine-D9loFZc1.d.cts → Engine-D3868xIU.d.cts} +62 -19
  11. package/dist/{subtype-qejjVr6h.cjs → PageInsertBlankInput-CzShuSJI.cjs} +184 -1
  12. package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +1 -0
  13. package/dist/{subtype-BvAzLCGC.js → PageInsertBlankInput-WKY0tr6S.js} +149 -2
  14. package/dist/PageInsertBlankInput-WKY0tr6S.js.map +1 -0
  15. package/dist/{builders-C548U47M.js → builders-Cdz34nbF.js} +566 -164
  16. package/dist/builders-Cdz34nbF.js.map +1 -0
  17. package/dist/{builders-BvikWRuO.cjs → builders-Do6_4KnS.cjs} +628 -220
  18. package/dist/builders-Do6_4KnS.cjs.map +1 -0
  19. package/dist/conformance.cjs +1039 -204
  20. package/dist/conformance.cjs.map +1 -1
  21. package/dist/conformance.d.cts +130 -6
  22. package/dist/conformance.d.ts +130 -6
  23. package/dist/conformance.js +994 -163
  24. package/dist/conformance.js.map +1 -1
  25. package/dist/{fold-CB4fAOOy.js → fold-BfyxxwS6.js} +162 -3
  26. package/dist/fold-BfyxxwS6.js.map +1 -0
  27. package/dist/{fold-IaMph3CO.cjs → fold-C7I5GpBz.cjs} +235 -4
  28. package/dist/fold-C7I5GpBz.cjs.map +1 -0
  29. package/dist/index.cjs +38 -19
  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-DiPMTfcy.js → resolver-CHG4waXL.js} +2 -1
  34. package/dist/resolver-CHG4waXL.js.map +1 -0
  35. package/dist/{resolver-DAZWVoJL.cjs → resolver-D-jumvgM.cjs} +2 -1
  36. package/dist/resolver-D-jumvgM.cjs.map +1 -0
  37. package/dist/runtime.cjs +43 -24
  38. package/dist/runtime.cjs.map +1 -1
  39. package/dist/runtime.d.cts +103 -5
  40. package/dist/runtime.d.ts +103 -5
  41. package/dist/runtime.js +6 -6
  42. package/dist/{schemas-B_1VyFsm.cjs → schemas-CS5iPpX5.cjs} +305 -111
  43. package/dist/schemas-CS5iPpX5.cjs.map +1 -0
  44. package/dist/{schemas-9OpSSeM6.js → schemas-XGrNVMDd.js} +250 -92
  45. package/dist/schemas-XGrNVMDd.js.map +1 -0
  46. package/dist/wire.cjs +131 -4
  47. package/dist/wire.cjs.map +1 -1
  48. package/dist/wire.d.cts +277 -20
  49. package/dist/wire.d.ts +277 -20
  50. package/dist/wire.js +120 -5
  51. package/dist/wire.js.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/builders-BvikWRuO.cjs.map +0 -1
  54. package/dist/builders-C548U47M.js.map +0 -1
  55. package/dist/fold-CB4fAOOy.js.map +0 -1
  56. package/dist/fold-IaMph3CO.cjs.map +0 -1
  57. package/dist/resolver-DAZWVoJL.cjs.map +0 -1
  58. package/dist/resolver-DiPMTfcy.js.map +0 -1
  59. package/dist/schemas-9OpSSeM6.js.map +0 -1
  60. package/dist/schemas-B_1VyFsm.cjs.map +0 -1
  61. package/dist/subtype-BvAzLCGC.js.map +0 -1
  62. package/dist/subtype-qejjVr6h.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { o as EngineErrorCode } from "./subtype-BvAzLCGC.js";
2
- import { i as EngineError, n as foldText } from "./fold-CB4fAOOy.js";
3
- import { t as AbortError } from "./AbortError-CjSasIlL.js";
4
- import { A as FormSetValueResultSchema, At as AnnotationDTOSchema, B as PageDeleteResultSchema, G as PageListSnapshotSchema, H as PageFlattenResultSchema, O as FormImportResultSchema, Q as PageRotateResultSchema, a as AnnotationCreateResultSchema, c as AnnotationListPageSnapshotSchema, d as AnnotationUpdateResultSchema, k as FormRepairResultSchema, l as AnnotationListSnapshotAllPagesSchema, o as AnnotationDeleteResultSchema, q as PageMoveResultSchema, rt as RedactionApplyResultSchema, tt as PageTextSnapshotSchema, u as AnnotationMoveResultSchema, ur as DocumentActionsSnapshotSchema, ut as SearchSliceSchema, wt as FormSnapshotSchema } from "./schemas-9OpSSeM6.js";
1
+ import { f as pdfQuadBounds, i as EngineError, n as foldText, u as isRotatedGeometryRun } from "./fold-BfyxxwS6.js";
2
+ import { f as EngineErrorCode, o as sliceTextByChars } from "./PageInsertBlankInput-WKY0tr6S.js";
3
+ import { t as AbortError } from "./AbortError-8-9JAX1Y.js";
4
+ import { A as FormSetValueResultSchema, B as PageDeleteResultSchema, O as FormImportResultSchema, Ot as FormSnapshotSchema, Pt as AnnotationDTOSchema, U as PageFlattenResultSchema, W as PageGeometrySnapshotSchema, Y as PageListSnapshotSchema, Z as PageMoveResultSchema, a as AnnotationCreateResultSchema, at as PageTextSnapshotSchema, c as AnnotationListPageSnapshotSchema, d as AnnotationUpdateResultSchema, k as FormRepairResultSchema, l as AnnotationListSnapshotAllPagesSchema, mt as SearchSliceSchema, nt as PageRotateResultSchema, o as AnnotationDeleteResultSchema, pr as DocumentActionsSnapshotSchema, st as RedactionApplyResultSchema, u as AnnotationMoveResultSchema } from "./schemas-XGrNVMDd.js";
5
5
  //#region src/conformance/runMetadataConformance.ts
6
6
  /**
7
7
  * The conformance harness is transport-agnostic. It takes the test runner as
@@ -18,7 +18,7 @@ function runMetadataConformance(runner, opts) {
18
18
  if (engine) await engine.destroy();
19
19
  });
20
20
  test("reads metadata from sample fixture", async () => {
21
- const doc = await openFixture$14(engine, opts);
21
+ const doc = await openFixture$17(engine, opts);
22
22
  try {
23
23
  const meta = await doc.metadata.read();
24
24
  expect(meta).toMatchObject(opts.fixture.expected);
@@ -28,7 +28,7 @@ function runMetadataConformance(runner, opts) {
28
28
  }
29
29
  });
30
30
  test("abort() before completion rejects with AbortError", async () => {
31
- const doc = await openFixture$14(engine, opts);
31
+ const doc = await openFixture$17(engine, opts);
32
32
  try {
33
33
  const p = doc.metadata.read();
34
34
  p.abort("test");
@@ -38,7 +38,7 @@ function runMetadataConformance(runner, opts) {
38
38
  }
39
39
  });
40
40
  test("aborting one read does not affect a concurrent one", async () => {
41
- const doc = await openFixture$14(engine, opts);
41
+ const doc = await openFixture$17(engine, opts);
42
42
  try {
43
43
  const a = doc.metadata.read();
44
44
  const b = doc.metadata.read();
@@ -50,7 +50,7 @@ function runMetadataConformance(runner, opts) {
50
50
  }
51
51
  });
52
52
  test("metadata after close throws DocNotOpen", async () => {
53
- const doc = await openFixture$14(engine, opts);
53
+ const doc = await openFixture$17(engine, opts);
54
54
  await doc.close();
55
55
  let caught;
56
56
  try {
@@ -62,7 +62,7 @@ function runMetadataConformance(runner, opts) {
62
62
  expect(EngineError.is(caught, EngineErrorCode.DocNotOpen)).toBe(true);
63
63
  });
64
64
  test("update() sets standard + custom fields and a subsequent read observes them", async () => {
65
- const doc = await openFixture$14(engine, opts);
65
+ const doc = await openFixture$17(engine, opts);
66
66
  try {
67
67
  const result = await doc.metadata.update({
68
68
  title: "Conformance Title",
@@ -81,7 +81,7 @@ function runMetadataConformance(runner, opts) {
81
81
  }
82
82
  });
83
83
  test("update() clears a field with null", async () => {
84
- const doc = await openFixture$14(engine, opts);
84
+ const doc = await openFixture$17(engine, opts);
85
85
  try {
86
86
  await doc.metadata.update({ title: "To Be Cleared" });
87
87
  const set = await doc.metadata.read();
@@ -94,7 +94,7 @@ function runMetadataConformance(runner, opts) {
94
94
  }
95
95
  });
96
96
  test("update() after close throws DocNotOpen", async () => {
97
- const doc = await openFixture$14(engine, opts);
97
+ const doc = await openFixture$17(engine, opts);
98
98
  await doc.close();
99
99
  let caught;
100
100
  try {
@@ -107,7 +107,7 @@ function runMetadataConformance(runner, opts) {
107
107
  });
108
108
  });
109
109
  }
110
- async function openFixture$14(engine, opts) {
110
+ async function openFixture$17(engine, opts) {
111
111
  if (opts.openKind === "bytes") {
112
112
  const bytes = await opts.fixture.bytes();
113
113
  return engine.open({
@@ -134,11 +134,12 @@ function runAnnotationReadConformance(runner, opts) {
134
134
  if (engine) await engine.destroy();
135
135
  });
136
136
  test("listRawAll returns one entry per page with valid PageState", async () => {
137
- const doc = await openFixture$13(engine, opts);
137
+ const doc = await openFixture$16(engine, opts);
138
138
  try {
139
139
  const snap = await doc.annotations.listRawAll();
140
140
  expect(AnnotationListSnapshotAllPagesSchema.safeParse(snap).success).toBe(true);
141
141
  expect(snap.pages.length >= 1).toBe(true);
142
+ if (snap.auditHead !== void 0) expect(Number.isInteger(snap.auditHead) && snap.auditHead >= 0).toBe(true);
142
143
  const target = snap.pages.find((p) => p.pageState.pageObjectNumber === opts.fixture.pageObjectNumber);
143
144
  expect(target !== void 0).toBe(true);
144
145
  expect(target.annotations.length).toBe(opts.fixture.expectedAnnotationCount);
@@ -147,7 +148,7 @@ function runAnnotationReadConformance(runner, opts) {
147
148
  }
148
149
  });
149
150
  test("listRaw on the test page returns the expected counts and DTO shape", async () => {
150
- const doc = await openFixture$13(engine, opts);
151
+ const doc = await openFixture$16(engine, opts);
151
152
  try {
152
153
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
153
154
  expect(AnnotationListPageSnapshotSchema.safeParse(snap).success).toBe(true);
@@ -177,7 +178,7 @@ function runAnnotationReadConformance(runner, opts) {
177
178
  }
178
179
  });
179
180
  test("shape/vertex/line annotations expose their family fields", async () => {
180
- const doc = await openFixture$13(engine, opts);
181
+ const doc = await openFixture$16(engine, opts);
181
182
  try {
182
183
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
183
184
  for (const a of snap.annotations) {
@@ -236,7 +237,7 @@ function runAnnotationReadConformance(runner, opts) {
236
237
  }
237
238
  });
238
239
  test("full page list dispatches per-subtype and matches raw counts", async () => {
239
- const doc = await openFixture$13(engine, opts);
240
+ const doc = await openFixture$16(engine, opts);
240
241
  try {
241
242
  const full = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
242
243
  expect(AnnotationListPageSnapshotSchema.safeParse(full).success).toBe(true);
@@ -246,7 +247,7 @@ function runAnnotationReadConformance(runner, opts) {
246
247
  }
247
248
  });
248
249
  test("every annotation DTO satisfies AnnotationDTOSchema (discriminated union)", async () => {
249
- const doc = await openFixture$13(engine, opts);
250
+ const doc = await openFixture$16(engine, opts);
250
251
  try {
251
252
  const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
252
253
  for (const a of snap.annotations) {
@@ -286,7 +287,7 @@ function runAnnotationReadConformance(runner, opts) {
286
287
  });
287
288
  if (opts.fixture.expectsWeakAnnotation) {
288
289
  test("weak annotations carry identityQuality: weak and ref.kind: index", async () => {
289
- const doc = await openFixture$13(engine, opts);
290
+ const doc = await openFixture$16(engine, opts);
290
291
  try {
291
292
  const weak = (await doc.annotations.listRaw(opts.fixture.pageObjectNumber)).annotations.find((a) => a.identityQuality === "weak");
292
293
  expect(weak !== void 0).toBe(true);
@@ -296,7 +297,7 @@ function runAnnotationReadConformance(runner, opts) {
296
297
  }
297
298
  });
298
299
  test("a stale revision token throws InvalidReference", async () => {
299
- const doc = await openFixture$13(engine, opts);
300
+ const doc = await openFixture$16(engine, opts);
300
301
  try {
301
302
  const snap = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
302
303
  const weak = snap.annotations.find((a) => a.identityQuality === "weak");
@@ -312,7 +313,7 @@ function runAnnotationReadConformance(runner, opts) {
312
313
  });
313
314
  }
314
315
  test("abort() on listRaw rejects with AbortError", async () => {
315
- const doc = await openFixture$13(engine, opts);
316
+ const doc = await openFixture$16(engine, opts);
316
317
  try {
317
318
  const p = doc.annotations.listRaw(opts.fixture.pageObjectNumber);
318
319
  p.abort("test");
@@ -322,7 +323,7 @@ function runAnnotationReadConformance(runner, opts) {
322
323
  }
323
324
  });
324
325
  test("listRaw on an unknown pageObjectNumber throws NotFound", async () => {
325
- const doc = await openFixture$13(engine, opts);
326
+ const doc = await openFixture$16(engine, opts);
326
327
  try {
327
328
  let caught;
328
329
  try {
@@ -338,7 +339,7 @@ function runAnnotationReadConformance(runner, opts) {
338
339
  });
339
340
  });
340
341
  }
341
- async function openFixture$13(engine, opts) {
342
+ async function openFixture$16(engine, opts) {
342
343
  if (opts.openKind === "bytes") {
343
344
  const bytes = await opts.fixture.bytes();
344
345
  return engine.open({
@@ -368,6 +369,13 @@ function emptyFlags() {
368
369
  }
369
370
  //#endregion
370
371
  //#region src/conformance/runActionsConformance.ts
372
+ /** The script payload rides only the javascript/rendition arms. */
373
+ function scriptOf(node) {
374
+ if (!node) return "";
375
+ if (node.type === "javascript") return node.script;
376
+ if (node.type === "rendition") return node.script ?? "";
377
+ return "";
378
+ }
371
379
  /** Action extraction parity. These tests assert data only; no script executes. */
372
380
  function runActionsConformance(runner, opts) {
373
381
  const { describe, test, beforeAll, afterAll, expect } = runner;
@@ -387,10 +395,10 @@ function runActionsConformance(runner, opts) {
387
395
  expect(DocumentActionsSnapshotSchema.safeParse(snapshot).success).toBe(true);
388
396
  expect(snapshot.openAction).toBeNull();
389
397
  expect(snapshot.willSave?.root?.type).toBe("javascript");
390
- expect(snapshot.willSave?.root?.script ?? "").toMatch(/Will Save/);
391
- expect(snapshot.didSave?.root?.script ?? "").toMatch(/Did Save/);
392
- expect(snapshot.willPrint?.root?.script ?? "").toMatch(/Will Print/);
393
- expect(snapshot.didPrint?.root?.script ?? "").toMatch(/Did Print/);
398
+ expect(scriptOf(snapshot.willSave?.root)).toMatch(/Will Save/);
399
+ expect(scriptOf(snapshot.didSave?.root)).toMatch(/Did Save/);
400
+ expect(scriptOf(snapshot.willPrint?.root)).toMatch(/Will Print/);
401
+ expect(scriptOf(snapshot.didPrint?.root)).toMatch(/Did Print/);
394
402
  const form = await doc.forms.list();
395
403
  expect(form.calculationOrder).toEqual([{
396
404
  kind: "objectNumber",
@@ -437,7 +445,7 @@ function runActionsConformance(runner, opts) {
437
445
  expect(link?.subtype).toBe("link");
438
446
  if (link?.subtype === "link") expect(link.target).toEqual({ kind: "javascript" });
439
447
  expect(link?.actions?.activate?.root?.type).toBe("javascript");
440
- expect(link?.actions?.activate?.root?.script ?? "").toMatch(/app\.alert/);
448
+ expect(scriptOf(link?.actions?.activate?.root)).toMatch(/app\.alert/);
441
449
  } finally {
442
450
  await doc.close();
443
451
  }
@@ -448,7 +456,7 @@ function runActionsConformance(runner, opts) {
448
456
  const form = await doc.forms.list();
449
457
  expect(form.fields).toHaveLength(1);
450
458
  expect(form.fields[0].actions?.format?.root?.type).toBe("javascript");
451
- expect(form.fields[0].actions?.format?.root?.script ?? "").toMatch(/AFDate_FormatEx/);
459
+ expect(scriptOf(form.fields[0].actions?.format?.root)).toMatch(/AFDate_FormatEx/);
452
460
  const page = (await doc.pages.list()).pages[0];
453
461
  const widget = (await doc.page(page.pageObjectNumber).annotations.list()).annotations[0];
454
462
  expect(widget.actions).toBe(void 0);
@@ -456,6 +464,210 @@ function runActionsConformance(runner, opts) {
456
464
  await doc.close();
457
465
  }
458
466
  });
467
+ test("carries interpreter payloads on every executable node", async () => {
468
+ const doc = await open(engine, opts, opts.fixtures.payloads);
469
+ try {
470
+ const page = (await doc.pages.list()).pages[0];
471
+ const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
472
+ const pon = page.pageObjectNumber;
473
+ const rootOf = (nm) => {
474
+ const annotation = snapshot.annotations.find((candidate) => candidate.nm === nm);
475
+ expect(Boolean(annotation)).toBe(true);
476
+ return annotation?.actions?.activate?.root ?? null;
477
+ };
478
+ const targetOf = (nm) => {
479
+ const annotation = snapshot.annotations.find((candidate) => candidate.nm === nm);
480
+ return annotation?.subtype === "link" ? annotation.target : null;
481
+ };
482
+ expect(rootOf("goto-fitr")).toMatchObject({
483
+ type: "goto",
484
+ destination: {
485
+ kind: "fitR",
486
+ pageObjectNumber: pon,
487
+ left: 10,
488
+ bottom: 20,
489
+ right: 300,
490
+ top: 400
491
+ }
492
+ });
493
+ expect(targetOf("goto-fitr")).toMatchObject({
494
+ kind: "goto",
495
+ destination: { kind: "fitR" }
496
+ });
497
+ expect(rootOf("uri-map")).toMatchObject({
498
+ type: "uri",
499
+ uri: "https://example.test/map",
500
+ isMap: true
501
+ });
502
+ expect(rootOf("named-next")).toMatchObject({
503
+ type: "named",
504
+ name: "NextPage"
505
+ });
506
+ const mixed = rootOf("hide-mixed");
507
+ expect(mixed).toMatchObject({
508
+ type: "hide",
509
+ hide: false
510
+ });
511
+ const mixedTargets = mixed?.type === "hide" ? mixed.targets : [];
512
+ expect(mixedTargets).toHaveLength(2);
513
+ expect(mixedTargets[0]).toEqual({
514
+ kind: "name",
515
+ name: "note1"
516
+ });
517
+ expect(mixedTargets[1]?.kind === "objectNumber" && mixedTargets[1].objectNumber > 0).toBe(true);
518
+ expect(rootOf("hide-scalar")).toMatchObject({
519
+ type: "hide",
520
+ targets: [{
521
+ kind: "name",
522
+ name: "fieldB"
523
+ }],
524
+ hide: true
525
+ });
526
+ expect(rootOf("reset-include")).toMatchObject({
527
+ type: "reset-form",
528
+ fields: [{
529
+ kind: "name",
530
+ name: "calc1"
531
+ }],
532
+ exclude: true
533
+ });
534
+ expect(rootOf("reset-absent")).toMatchObject({
535
+ type: "reset-form",
536
+ fields: null,
537
+ exclude: true
538
+ });
539
+ expect(rootOf("reset-empty")).toMatchObject({
540
+ type: "reset-form",
541
+ fields: [],
542
+ exclude: false
543
+ });
544
+ expect(rootOf("submit-urlspec")).toMatchObject({
545
+ type: "submit-form",
546
+ payload: {
547
+ url: "https://uf.example.test/submit",
548
+ fields: [{
549
+ kind: "name",
550
+ name: "parent"
551
+ }],
552
+ flags: {
553
+ raw: 0,
554
+ exclude: false,
555
+ format: "fdf",
556
+ method: "post"
557
+ }
558
+ }
559
+ });
560
+ expect(rootOf("submit-compat-xfdf")).toMatchObject({
561
+ type: "submit-form",
562
+ payload: {
563
+ url: "https://example.test/xfdf",
564
+ fields: [{
565
+ kind: "name",
566
+ name: "noexport"
567
+ }, {
568
+ kind: "name",
569
+ name: "plain"
570
+ }],
571
+ flags: {
572
+ raw: 35,
573
+ exclude: true,
574
+ includeNoValueFields: true,
575
+ format: "xfdf",
576
+ method: "post"
577
+ },
578
+ charSet: "utf-8"
579
+ }
580
+ });
581
+ expect(rootOf("submit-pdf-get")).toMatchObject({
582
+ type: "submit-form",
583
+ payload: {
584
+ url: "https://example.test/pdf",
585
+ fields: null,
586
+ flags: {
587
+ raw: 264,
588
+ format: "pdf",
589
+ method: "get"
590
+ }
591
+ }
592
+ });
593
+ expect(rootOf("launch-app")).toMatchObject({
594
+ type: "launch",
595
+ filePath: "app.exe"
596
+ });
597
+ expect(rootOf("gotor-file")).toMatchObject({
598
+ type: "goto-remote",
599
+ filePath: "other.pdf"
600
+ });
601
+ const chain = rootOf("chain-js-goto-hide");
602
+ expect(chain).toMatchObject({
603
+ type: "javascript",
604
+ script: "app.alert('chain');"
605
+ });
606
+ expect(chain?.next[0]).toMatchObject({
607
+ type: "goto",
608
+ destination: {
609
+ kind: "xyz",
610
+ pageObjectNumber: pon,
611
+ left: 5,
612
+ top: 10,
613
+ zoom: 1.25
614
+ }
615
+ });
616
+ expect(chain?.next[0]?.next[0]).toMatchObject({
617
+ type: "hide",
618
+ targets: [{
619
+ kind: "name",
620
+ name: "note1"
621
+ }],
622
+ hide: true
623
+ });
624
+ } finally {
625
+ await doc.close();
626
+ }
627
+ });
628
+ test("degrades unreadable payloads to unknown with payload-dropped", async () => {
629
+ const doc = await open(engine, opts, opts.fixtures.payloads);
630
+ try {
631
+ const page = (await doc.pages.list()).pages[0];
632
+ const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
633
+ for (const [nm, subtype] of [
634
+ ["goto-malformed", "GoTo"],
635
+ ["hide-partial", "Hide"],
636
+ ["submit-not-url", "SubmitForm"],
637
+ ["submit-no-f", "SubmitForm"]
638
+ ]) {
639
+ const annotation = snapshot.annotations.find((candidate) => candidate.nm === nm);
640
+ const tree = annotation?.actions?.activate;
641
+ expect(tree?.root).toMatchObject({
642
+ type: "unknown",
643
+ subtype
644
+ });
645
+ expect(tree?.warnings ?? []).toContain("payload-dropped");
646
+ if (annotation?.subtype === "link") expect(annotation.target).toEqual({ kind: "unsupported" });
647
+ }
648
+ } finally {
649
+ await doc.close();
650
+ }
651
+ });
652
+ test("reads a destination-form /OpenAction as openDestination", async () => {
653
+ const doc = await open(engine, opts, opts.fixtures.openDestination);
654
+ try {
655
+ expect(Boolean(doc.actions)).toBe(true);
656
+ const snapshot = await doc.actions.read();
657
+ expect(DocumentActionsSnapshotSchema.safeParse(snapshot).success).toBe(true);
658
+ expect(snapshot.openAction).toBeNull();
659
+ const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
660
+ expect(snapshot.openDestination).toEqual({
661
+ kind: "xyz",
662
+ pageObjectNumber: pon,
663
+ left: 10,
664
+ top: 700,
665
+ zoom: 1.5
666
+ });
667
+ } finally {
668
+ await doc.close();
669
+ }
670
+ });
459
671
  });
460
672
  }
461
673
  async function open(engine, opts, fixture) {
@@ -596,7 +808,7 @@ function runAnnotationMutationConformance(runner, opts) {
596
808
  if (engine) await engine.destroy();
597
809
  });
598
810
  test("create appends without shifting indices and leaves weak refs valid", async () => {
599
- const doc = await openFixture$12(engine, opts);
811
+ const doc = await openFixture$15(engine, opts);
600
812
  try {
601
813
  const page = doc.page(fix.pageObjectNumber);
602
814
  const before = await page.annotations.list();
@@ -632,7 +844,7 @@ function runAnnotationMutationConformance(runner, opts) {
632
844
  }
633
845
  });
634
846
  test("create + update honor annotation flags (set on create, merge on patch)", async () => {
635
- const doc = await openFixture$12(engine, opts);
847
+ const doc = await openFixture$15(engine, opts);
636
848
  try {
637
849
  const page = doc.page(fix.pageObjectNumber);
638
850
  const draft = {
@@ -667,7 +879,7 @@ function runAnnotationMutationConformance(runner, opts) {
667
879
  }
668
880
  });
669
881
  test("create shape annotations (circle + square) round-trip shape fields", async () => {
670
- const doc = await openFixture$12(engine, opts);
882
+ const doc = await openFixture$15(engine, opts);
671
883
  try {
672
884
  const page = doc.page(fix.pageObjectNumber);
673
885
  const circleDraft = {
@@ -748,7 +960,7 @@ function runAnnotationMutationConformance(runner, opts) {
748
960
  }
749
961
  });
750
962
  test("cloudy border (/BE) + rect differences (/RD) are tri-state", async () => {
751
- const doc = await openFixture$12(engine, opts);
963
+ const doc = await openFixture$15(engine, opts);
752
964
  try {
753
965
  const page = doc.page(fix.pageObjectNumber);
754
966
  const plainDraft = {
@@ -827,7 +1039,7 @@ function runAnnotationMutationConformance(runner, opts) {
827
1039
  }
828
1040
  });
829
1041
  test("update echoes the appearance verdict: moves preserve /AP, restyles re-bake", async () => {
830
- const doc = await openFixture$12(engine, opts);
1042
+ const doc = await openFixture$15(engine, opts);
831
1043
  try {
832
1044
  const page = doc.page(fix.pageObjectNumber);
833
1045
  const draft = {
@@ -902,7 +1114,7 @@ function runAnnotationMutationConformance(runner, opts) {
902
1114
  }
903
1115
  });
904
1116
  test("a partial /DA patch preserves the unpatched font members", async () => {
905
- const doc = await openFixture$12(engine, opts);
1117
+ const doc = await openFixture$15(engine, opts);
906
1118
  try {
907
1119
  const page = doc.page(fix.pageObjectNumber);
908
1120
  const draft = {
@@ -958,7 +1170,7 @@ function runAnnotationMutationConformance(runner, opts) {
958
1170
  }
959
1171
  });
960
1172
  test("box transform is tri-state: rect-only moves keep rotation, null flattens", async () => {
961
- const doc = await openFixture$12(engine, opts);
1173
+ const doc = await openFixture$15(engine, opts);
962
1174
  try {
963
1175
  const page = doc.page(fix.pageObjectNumber);
964
1176
  const draft = {
@@ -1019,7 +1231,7 @@ function runAnnotationMutationConformance(runner, opts) {
1019
1231
  }
1020
1232
  });
1021
1233
  test("create vertex + line annotations (polygon/polyline/line) round-trip geometry", async () => {
1022
- const doc = await openFixture$12(engine, opts);
1234
+ const doc = await openFixture$15(engine, opts);
1023
1235
  try {
1024
1236
  const page = doc.page(fix.pageObjectNumber);
1025
1237
  const polygonDraft = {
@@ -1150,7 +1362,7 @@ function runAnnotationMutationConformance(runner, opts) {
1150
1362
  }
1151
1363
  });
1152
1364
  test("create free-text + callout round-trip text/colour/intent fields", async () => {
1153
- const doc = await openFixture$12(engine, opts);
1365
+ const doc = await openFixture$15(engine, opts);
1154
1366
  try {
1155
1367
  const page = doc.page(fix.pageObjectNumber);
1156
1368
  const freeTextDraft = {
@@ -1246,7 +1458,7 @@ function runAnnotationMutationConformance(runner, opts) {
1246
1458
  }
1247
1459
  });
1248
1460
  test("update a free-text patches alignment + colours and is non-structural", async () => {
1249
- const doc = await openFixture$12(engine, opts);
1461
+ const doc = await openFixture$15(engine, opts);
1250
1462
  try {
1251
1463
  const page = doc.page(fix.pageObjectNumber);
1252
1464
  const created = await page.annotations.create({
@@ -1307,7 +1519,7 @@ function runAnnotationMutationConformance(runner, opts) {
1307
1519
  }
1308
1520
  });
1309
1521
  test("create redact (area + text) round-trips label + colour fields", async () => {
1310
- const doc = await openFixture$12(engine, opts);
1522
+ const doc = await openFixture$15(engine, opts);
1311
1523
  try {
1312
1524
  const page = doc.page(fix.pageObjectNumber);
1313
1525
  const areaDraft = {
@@ -1387,7 +1599,7 @@ function runAnnotationMutationConformance(runner, opts) {
1387
1599
  }
1388
1600
  });
1389
1601
  test("update a redact patches the label and clears it, non-structurally", async () => {
1390
- const doc = await openFixture$12(engine, opts);
1602
+ const doc = await openFixture$15(engine, opts);
1391
1603
  try {
1392
1604
  const page = doc.page(fix.pageObjectNumber);
1393
1605
  const created = await page.annotations.create({
@@ -1464,7 +1676,7 @@ function runAnnotationMutationConformance(runner, opts) {
1464
1676
  }
1465
1677
  });
1466
1678
  test("create + update a caret round-trips color/opacity/rect differences", async () => {
1467
- const doc = await openFixture$12(engine, opts);
1679
+ const doc = await openFixture$15(engine, opts);
1468
1680
  try {
1469
1681
  const page = doc.page(fix.pageObjectNumber);
1470
1682
  const caretDraft = {
@@ -1544,7 +1756,7 @@ function runAnnotationMutationConformance(runner, opts) {
1544
1756
  }
1545
1757
  });
1546
1758
  test("update an ink annotation patches strokes + color and is non-structural", async () => {
1547
- const doc = await openFixture$12(engine, opts);
1759
+ const doc = await openFixture$15(engine, opts);
1548
1760
  try {
1549
1761
  const page = doc.page(fix.pageObjectNumber);
1550
1762
  const created = await page.annotations.create({
@@ -1595,7 +1807,7 @@ function runAnnotationMutationConformance(runner, opts) {
1595
1807
  }
1596
1808
  });
1597
1809
  test("ink highlight intent + blend round-trip and unrelated patches preserve blend", async () => {
1598
- const doc = await openFixture$12(engine, opts);
1810
+ const doc = await openFixture$15(engine, opts);
1599
1811
  try {
1600
1812
  const page = doc.page(fix.pageObjectNumber);
1601
1813
  const created = await page.annotations.create({
@@ -1638,7 +1850,7 @@ function runAnnotationMutationConformance(runner, opts) {
1638
1850
  }
1639
1851
  });
1640
1852
  test("update a polyline patches vertices + line endings and is non-structural", async () => {
1641
- const doc = await openFixture$12(engine, opts);
1853
+ const doc = await openFixture$15(engine, opts);
1642
1854
  try {
1643
1855
  const page = doc.page(fix.pageObjectNumber);
1644
1856
  const created = await page.annotations.create({
@@ -1687,7 +1899,7 @@ function runAnnotationMutationConformance(runner, opts) {
1687
1899
  }
1688
1900
  });
1689
1901
  test("update a shape annotation patches color and is non-structural", async () => {
1690
- const doc = await openFixture$12(engine, opts);
1902
+ const doc = await openFixture$15(engine, opts);
1691
1903
  try {
1692
1904
  const page = doc.page(fix.pageObjectNumber);
1693
1905
  const created = await page.annotations.create({
@@ -1736,7 +1948,7 @@ function runAnnotationMutationConformance(runner, opts) {
1736
1948
  }
1737
1949
  });
1738
1950
  if (opts.supportsAppearanceRasters) test("creating a shape bakes an /AP appearance the reader can rasterize", async () => {
1739
- const doc = await openFixture$12(engine, opts);
1951
+ const doc = await openFixture$15(engine, opts);
1740
1952
  try {
1741
1953
  const page = doc.page(fix.pageObjectNumber);
1742
1954
  const created = await page.annotations.create({
@@ -1766,7 +1978,7 @@ function runAnnotationMutationConformance(runner, opts) {
1766
1978
  }
1767
1979
  });
1768
1980
  test("update on a durable annotation is non-structural and never touches /NM", async () => {
1769
- const doc = await openFixture$12(engine, opts);
1981
+ const doc = await openFixture$15(engine, opts);
1770
1982
  try {
1771
1983
  const page = doc.page(fix.pageObjectNumber);
1772
1984
  const before = await page.annotations.list();
@@ -1792,7 +2004,7 @@ function runAnnotationMutationConformance(runner, opts) {
1792
2004
  }
1793
2005
  });
1794
2006
  if (fix.expectsWeakAnnotation) test("update on a weak annotation stamps a UUID v4 /NM and upgrades the ref", async () => {
1795
- const doc = await openFixture$12(engine, opts);
2007
+ const doc = await openFixture$15(engine, opts);
1796
2008
  try {
1797
2009
  const page = doc.page(fix.pageObjectNumber);
1798
2010
  const before = await page.annotations.list();
@@ -1822,7 +2034,7 @@ function runAnnotationMutationConformance(runner, opts) {
1822
2034
  }
1823
2035
  });
1824
2036
  test("delete by objectNumber removes the annotation and reports a stable id", async () => {
1825
- const doc = await openFixture$12(engine, opts);
2037
+ const doc = await openFixture$15(engine, opts);
1826
2038
  try {
1827
2039
  const page = doc.page(fix.pageObjectNumber);
1828
2040
  const draft = {
@@ -1852,7 +2064,7 @@ function runAnnotationMutationConformance(runner, opts) {
1852
2064
  }
1853
2065
  });
1854
2066
  if (fix.expectsWeakAnnotation) test("delete by index of a weak annotation reports deleted: null and refetch reason", async () => {
1855
- const doc = await openFixture$12(engine, opts);
2067
+ const doc = await openFixture$15(engine, opts);
1856
2068
  try {
1857
2069
  const page = doc.page(fix.pageObjectNumber);
1858
2070
  const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
@@ -1875,7 +2087,7 @@ function runAnnotationMutationConformance(runner, opts) {
1875
2087
  }
1876
2088
  });
1877
2089
  test("abort on create rejects with AbortError", async () => {
1878
- const doc = await openFixture$12(engine, opts);
2090
+ const doc = await openFixture$15(engine, opts);
1879
2091
  try {
1880
2092
  const page = doc.page(fix.pageObjectNumber);
1881
2093
  const draft = {
@@ -1891,7 +2103,7 @@ function runAnnotationMutationConformance(runner, opts) {
1891
2103
  }
1892
2104
  });
1893
2105
  test("update with a stale index revision throws InvalidReference", async () => {
1894
- const doc = await openFixture$12(engine, opts);
2106
+ const doc = await openFixture$15(engine, opts);
1895
2107
  try {
1896
2108
  const page = doc.page(fix.pageObjectNumber);
1897
2109
  const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
@@ -1918,7 +2130,7 @@ function runAnnotationMutationConformance(runner, opts) {
1918
2130
  }
1919
2131
  });
1920
2132
  test("move single durable annotation reorders within the page (single-as-batch)", async () => {
1921
- const doc = await openFixture$12(engine, opts);
2133
+ const doc = await openFixture$15(engine, opts);
1922
2134
  try {
1923
2135
  const page = doc.page(fix.pageObjectNumber);
1924
2136
  const aDraft = {
@@ -1962,7 +2174,7 @@ function runAnnotationMutationConformance(runner, opts) {
1962
2174
  }
1963
2175
  });
1964
2176
  test("move multi-block preserves caller-supplied order at the destination", async () => {
1965
- const doc = await openFixture$12(engine, opts);
2177
+ const doc = await openFixture$15(engine, opts);
1966
2178
  try {
1967
2179
  const page = doc.page(fix.pageObjectNumber);
1968
2180
  const ids = await Promise.all([
@@ -2001,7 +2213,7 @@ function runAnnotationMutationConformance(runner, opts) {
2001
2213
  }
2002
2214
  });
2003
2215
  if (fix.expectsWeakAnnotation) test("move on a weak annotation upgrades it to durable /NM (one rev bump for batch)", async () => {
2004
- const doc = await openFixture$12(engine, opts);
2216
+ const doc = await openFixture$15(engine, opts);
2005
2217
  try {
2006
2218
  const page = doc.page(fix.pageObjectNumber);
2007
2219
  const before = await page.annotations.list();
@@ -2026,7 +2238,7 @@ function runAnnotationMutationConformance(runner, opts) {
2026
2238
  }
2027
2239
  });
2028
2240
  test("move with a stale index revision rejects (locked rev-token guard)", async () => {
2029
- const doc = await openFixture$12(engine, opts);
2241
+ const doc = await openFixture$15(engine, opts);
2030
2242
  try {
2031
2243
  const page = doc.page(fix.pageObjectNumber);
2032
2244
  const a = await page.annotations.create({
@@ -2064,7 +2276,7 @@ function runAnnotationMutationConformance(runner, opts) {
2064
2276
  }
2065
2277
  });
2066
2278
  test("move with out-of-range toIndex rejects with InvalidArg", async () => {
2067
- const doc = await openFixture$12(engine, opts);
2279
+ const doc = await openFixture$15(engine, opts);
2068
2280
  try {
2069
2281
  const page = doc.page(fix.pageObjectNumber);
2070
2282
  const a = await page.annotations.create({
@@ -2088,7 +2300,7 @@ function runAnnotationMutationConformance(runner, opts) {
2088
2300
  }
2089
2301
  });
2090
2302
  test("move with duplicate refs rejects with InvalidArg", async () => {
2091
- const doc = await openFixture$12(engine, opts);
2303
+ const doc = await openFixture$15(engine, opts);
2092
2304
  try {
2093
2305
  const page = doc.page(fix.pageObjectNumber);
2094
2306
  const a = await page.annotations.create({
@@ -2111,7 +2323,7 @@ function runAnnotationMutationConformance(runner, opts) {
2111
2323
  }
2112
2324
  });
2113
2325
  test("abort on move rejects with AbortError", async () => {
2114
- const doc = await openFixture$12(engine, opts);
2326
+ const doc = await openFixture$15(engine, opts);
2115
2327
  try {
2116
2328
  const page = doc.page(fix.pageObjectNumber);
2117
2329
  const a = await page.annotations.create({
@@ -2132,7 +2344,7 @@ function runAnnotationMutationConformance(runner, opts) {
2132
2344
  }
2133
2345
  });
2134
2346
  test("a page move does NOT bump per-page RevisionTokens (weak refs survive reorder)", async () => {
2135
- const doc = await openFixture$12(engine, opts);
2347
+ const doc = await openFixture$15(engine, opts);
2136
2348
  try {
2137
2349
  const list = await doc.pages.list();
2138
2350
  if (list.pages.length < 1) return;
@@ -2147,7 +2359,7 @@ function runAnnotationMutationConformance(runner, opts) {
2147
2359
  }
2148
2360
  });
2149
2361
  test("create a reply links /IRT and defaults /RT to \"reply\", reporting the parent", async () => {
2150
- const doc = await openFixture$12(engine, opts);
2362
+ const doc = await openFixture$15(engine, opts);
2151
2363
  try {
2152
2364
  const page = doc.page(fix.pageObjectNumber);
2153
2365
  const parent = await page.annotations.create({
@@ -2181,7 +2393,7 @@ function runAnnotationMutationConformance(runner, opts) {
2181
2393
  }
2182
2394
  });
2183
2395
  test("create a grouped subordinate writes /RT /Group", async () => {
2184
- const doc = await openFixture$12(engine, opts);
2396
+ const doc = await openFixture$15(engine, opts);
2185
2397
  try {
2186
2398
  const page = doc.page(fix.pageObjectNumber);
2187
2399
  const primary = await page.annotations.create({
@@ -2212,7 +2424,7 @@ function runAnnotationMutationConformance(runner, opts) {
2212
2424
  }
2213
2425
  });
2214
2426
  test("create link annotations round-trip uri, goto/xyz, goto/fitH, and dead targets", async () => {
2215
- const doc = await openFixture$12(engine, opts);
2427
+ const doc = await openFixture$15(engine, opts);
2216
2428
  try {
2217
2429
  const page = doc.page(fix.pageObjectNumber);
2218
2430
  const uri = await page.annotations.create({
@@ -2289,7 +2501,7 @@ function runAnnotationMutationConformance(runner, opts) {
2289
2501
  }
2290
2502
  });
2291
2503
  test("a link patch retargets in both directions (uri→goto, goto→uri) and moves the rect", async () => {
2292
- const doc = await openFixture$12(engine, opts);
2504
+ const doc = await openFixture$15(engine, opts);
2293
2505
  try {
2294
2506
  const page = doc.page(fix.pageObjectNumber);
2295
2507
  const created = await page.annotations.create({
@@ -2344,7 +2556,7 @@ function runAnnotationMutationConformance(runner, opts) {
2344
2556
  }
2345
2557
  });
2346
2558
  test("a link patch clears the target with null — a dead link on re-read", async () => {
2347
- const doc = await openFixture$12(engine, opts);
2559
+ const doc = await openFixture$15(engine, opts);
2348
2560
  try {
2349
2561
  const page = doc.page(fix.pageObjectNumber);
2350
2562
  const created = await page.annotations.create({
@@ -2383,7 +2595,7 @@ function runAnnotationMutationConformance(runner, opts) {
2383
2595
  }
2384
2596
  });
2385
2597
  test("a link grouped to a highlight round-trips /IRT + /RT /Group with its target", async () => {
2386
- const doc = await openFixture$12(engine, opts);
2598
+ const doc = await openFixture$15(engine, opts);
2387
2599
  try {
2388
2600
  const page = doc.page(fix.pageObjectNumber);
2389
2601
  const parent = await page.annotations.create({
@@ -2417,7 +2629,7 @@ function runAnnotationMutationConformance(runner, opts) {
2417
2629
  }
2418
2630
  });
2419
2631
  test("replace-text round-trips /IT and groups StrikeOut under its Caret", async () => {
2420
- const doc = await openFixture$12(engine, opts);
2632
+ const doc = await openFixture$15(engine, opts);
2421
2633
  try {
2422
2634
  const page = doc.page(fix.pageObjectNumber);
2423
2635
  const caret = await page.annotations.create({
@@ -2469,7 +2681,7 @@ function runAnnotationMutationConformance(runner, opts) {
2469
2681
  }
2470
2682
  });
2471
2683
  test("patch inReplyTo: null clears /IRT and /RT (back to top-level)", async () => {
2472
- const doc = await openFixture$12(engine, opts);
2684
+ const doc = await openFixture$15(engine, opts);
2473
2685
  try {
2474
2686
  const page = doc.page(fix.pageObjectNumber);
2475
2687
  const parent = await page.annotations.create({
@@ -2496,7 +2708,7 @@ function runAnnotationMutationConformance(runner, opts) {
2496
2708
  }
2497
2709
  });
2498
2710
  test("create with a cross-page /IRT parent throws InvalidArg", async () => {
2499
- const doc = await openFixture$12(engine, opts);
2711
+ const doc = await openFixture$15(engine, opts);
2500
2712
  try {
2501
2713
  const page = doc.page(fix.pageObjectNumber);
2502
2714
  const parent = await page.annotations.create({
@@ -2526,13 +2738,136 @@ function runAnnotationMutationConformance(runner, opts) {
2526
2738
  await doc.close();
2527
2739
  }
2528
2740
  });
2741
+ test("a review-status reply round-trips /State + /StateModel + /Subj", async () => {
2742
+ const doc = await openFixture$15(engine, opts);
2743
+ try {
2744
+ const page = doc.page(fix.pageObjectNumber);
2745
+ const target = await page.annotations.create({
2746
+ subtype: "highlight",
2747
+ contents: "status target",
2748
+ subject: "Pricing question",
2749
+ quadPoints: quad
2750
+ });
2751
+ expect(target.created.subject).toBe("Pricing question");
2752
+ const status = await page.annotations.create({
2753
+ subtype: "text",
2754
+ rect: shapeRect,
2755
+ inReplyTo: target.created.ref,
2756
+ state: "accepted",
2757
+ stateModel: "review"
2758
+ });
2759
+ expect(AnnotationCreateResultSchema.safeParse(status).success).toBe(true);
2760
+ expect(status.created.subtype).toBe("text");
2761
+ if (status.created.subtype !== "text") return;
2762
+ expect(status.created.state).toBe("accepted");
2763
+ expect(status.created.stateModel).toBe("review");
2764
+ expect(status.created.replyType).toBe("reply");
2765
+ const read = (await page.annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && status.created.ref.kind === "objectNumber" && a.ref.annotObjectNumber === status.created.ref.annotObjectNumber);
2766
+ expect(read?.subtype).toBe("text");
2767
+ if (read?.subtype === "text") {
2768
+ expect(read.state).toBe("accepted");
2769
+ expect(read.stateModel).toBe("review");
2770
+ }
2771
+ } finally {
2772
+ await doc.close();
2773
+ }
2774
+ });
2775
+ test("a draft /State without /StateModel is rejected with InvalidArg", async () => {
2776
+ const doc = await openFixture$15(engine, opts);
2777
+ try {
2778
+ const page = doc.page(fix.pageObjectNumber);
2779
+ let caught;
2780
+ try {
2781
+ await page.annotations.create({
2782
+ subtype: "text",
2783
+ rect: shapeRect,
2784
+ state: "accepted"
2785
+ });
2786
+ } catch (err) {
2787
+ caught = err;
2788
+ }
2789
+ expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
2790
+ } finally {
2791
+ await doc.close();
2792
+ }
2793
+ });
2794
+ test("null-clear patches truly remove /State, /StateModel, /Subj and /Contents", async () => {
2795
+ const doc = await openFixture$15(engine, opts);
2796
+ try {
2797
+ const page = doc.page(fix.pageObjectNumber);
2798
+ const note = await page.annotations.create({
2799
+ subtype: "text",
2800
+ rect: shapeRect,
2801
+ contents: "work in progress",
2802
+ subject: "Draft",
2803
+ state: "none",
2804
+ stateModel: "review"
2805
+ });
2806
+ const cycled = await page.annotations.update(note.created.ref, {
2807
+ subtype: "text",
2808
+ state: "rejected"
2809
+ });
2810
+ expect(cycled.appearance.changed).toBe(false);
2811
+ expect(cycled.updated.subtype).toBe("text");
2812
+ if (cycled.updated.subtype === "text") {
2813
+ expect(cycled.updated.state).toBe("rejected");
2814
+ expect(cycled.updated.stateModel).toBe("review");
2815
+ }
2816
+ const cleared = await page.annotations.update(note.created.ref, {
2817
+ subtype: "text",
2818
+ contents: null,
2819
+ subject: null,
2820
+ state: null,
2821
+ stateModel: null
2822
+ });
2823
+ expect(cleared.appearance.changed).toBe(false);
2824
+ expect(cleared.updated.contents).toBe(null);
2825
+ expect(cleared.updated.subject).toBe(null);
2826
+ if (cleared.updated.subtype === "text") {
2827
+ expect(cleared.updated.state).toBe(null);
2828
+ expect(cleared.updated.stateModel).toBe(null);
2829
+ }
2830
+ const read = (await page.annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && note.created.ref.kind === "objectNumber" && a.ref.annotObjectNumber === note.created.ref.annotObjectNumber);
2831
+ expect(read?.contents).toBe(null);
2832
+ expect(read?.subject).toBe(null);
2833
+ if (read?.subtype === "text") {
2834
+ expect(read.state).toBe(null);
2835
+ expect(read.stateModel).toBe(null);
2836
+ }
2837
+ } finally {
2838
+ await doc.close();
2839
+ }
2840
+ });
2841
+ test("custom state models round-trip verbatim", async () => {
2842
+ const doc = await openFixture$15(engine, opts);
2843
+ try {
2844
+ const page = doc.page(fix.pageObjectNumber);
2845
+ const custom = await page.annotations.create({
2846
+ subtype: "text",
2847
+ rect: shapeRect,
2848
+ state: "in-progress",
2849
+ stateModel: "X-ReviewWorkflow"
2850
+ });
2851
+ expect(custom.created.subtype).toBe("text");
2852
+ if (custom.created.subtype !== "text") return;
2853
+ expect(custom.created.state).toBe("in-progress");
2854
+ expect(custom.created.stateModel).toBe("X-ReviewWorkflow");
2855
+ const read = (await page.annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && custom.created.ref.kind === "objectNumber" && a.ref.annotObjectNumber === custom.created.ref.annotObjectNumber);
2856
+ if (read?.subtype === "text") {
2857
+ expect(read.state).toBe("in-progress");
2858
+ expect(read.stateModel).toBe("X-ReviewWorkflow");
2859
+ }
2860
+ } finally {
2861
+ await doc.close();
2862
+ }
2863
+ });
2529
2864
  });
2530
2865
  }
2531
2866
  async function beginWeakEditIfRequired(doc, pageObjectNumber, fix) {
2532
2867
  if (doc.capabilities.weakAnnotationEditSessions !== "required" || !fix.expectsWeakAnnotation) return null;
2533
2868
  return doc.annotations.beginWeakEdit([pageObjectNumber]);
2534
2869
  }
2535
- async function openFixture$12(engine, opts) {
2870
+ async function openFixture$15(engine, opts) {
2536
2871
  if (opts.openKind === "bytes") {
2537
2872
  const bytes = await opts.fixture.bytes();
2538
2873
  return engine.open({
@@ -3122,7 +3457,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3122
3457
  if (engine) await engine.destroy();
3123
3458
  });
3124
3459
  test("renders the expected set of appearances with valid output", async () => {
3125
- const doc = await openFixture$11(engine, opts);
3460
+ const doc = await openFixture$14(engine, opts);
3126
3461
  try {
3127
3462
  const { pageState, appearances } = await collect(doc, opts, useRaw);
3128
3463
  expect(pageState.pageObjectNumber).toBe(opts.fixture.pageObjectNumber);
@@ -3149,7 +3484,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3149
3484
  }
3150
3485
  });
3151
3486
  if (opts.fixture.expectsWeakAppearance) test("weak (index-only) annotations are emitted, not dropped", async () => {
3152
- const doc = await openFixture$11(engine, opts);
3487
+ const doc = await openFixture$14(engine, opts);
3153
3488
  try {
3154
3489
  const { appearances } = await collect(doc, opts, useRaw);
3155
3490
  const weak = appearances.find((a) => a.ref.kind === "index");
@@ -3159,7 +3494,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3159
3494
  }
3160
3495
  });
3161
3496
  if (useRaw) test("rendered appearances are not blank (non-zero alpha)", async () => {
3162
- const doc = await openFixture$11(engine, opts);
3497
+ const doc = await openFixture$14(engine, opts);
3163
3498
  try {
3164
3499
  const { appearances } = await collect(doc, opts, useRaw);
3165
3500
  const anyVisible = appearances.some((a) => a.raster !== null && hasOpaquePixel(a.raster));
@@ -3169,7 +3504,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
3169
3504
  }
3170
3505
  });
3171
3506
  test("abort() rejects with AbortError", async () => {
3172
- const doc = await openFixture$11(engine, opts);
3507
+ const doc = await openFixture$14(engine, opts);
3173
3508
  try {
3174
3509
  const page = doc.page(opts.fixture.pageObjectNumber);
3175
3510
  const p = useRaw ? page.annotations.renderAppearances({ scale: 1 }) : page.annotations.renderAppearanceImages({
@@ -3224,7 +3559,7 @@ function hasOpaquePixel(raster) {
3224
3559
  }
3225
3560
  return false;
3226
3561
  }
3227
- async function openFixture$11(engine, opts) {
3562
+ async function openFixture$14(engine, opts) {
3228
3563
  if (opts.openKind === "bytes") {
3229
3564
  const bytes = await opts.fixture.bytes();
3230
3565
  return engine.open({
@@ -3293,7 +3628,7 @@ function runPageReorderConformance(runner, opts) {
3293
3628
  if (engine) await engine.destroy();
3294
3629
  });
3295
3630
  test("pages.list() returns every page in display order, durable PONs only", async () => {
3296
- const doc = await openFixture$10(engine, opts);
3631
+ const doc = await openFixture$13(engine, opts);
3297
3632
  try {
3298
3633
  const list = await doc.pages.list();
3299
3634
  expect(PageListSnapshotSchema.safeParse(list).success).toBe(true);
@@ -3312,7 +3647,7 @@ function runPageReorderConformance(runner, opts) {
3312
3647
  }
3313
3648
  });
3314
3649
  test("pages.move() reorders pages and returns the full post-move order", async () => {
3315
- const doc = await openFixture$10(engine, opts);
3650
+ const doc = await openFixture$13(engine, opts);
3316
3651
  try {
3317
3652
  const before = await doc.pages.list();
3318
3653
  if (before.pages.length < 3) return;
@@ -3336,7 +3671,7 @@ function runPageReorderConformance(runner, opts) {
3336
3671
  }
3337
3672
  });
3338
3673
  test("weak index-based annotation refs survive a page reorder", async () => {
3339
- const doc = await openFixture$10(engine, opts);
3674
+ const doc = await openFixture$13(engine, opts);
3340
3675
  try {
3341
3676
  const list = await doc.pages.list();
3342
3677
  if (list.pages.length < 2) return;
@@ -3373,7 +3708,7 @@ function runPageReorderConformance(runner, opts) {
3373
3708
  }
3374
3709
  });
3375
3710
  test("pages.move() rejects duplicate PONs with InvalidArg", async () => {
3376
- const doc = await openFixture$10(engine, opts);
3711
+ const doc = await openFixture$13(engine, opts);
3377
3712
  try {
3378
3713
  const list = await doc.pages.list();
3379
3714
  if (list.pages.length < 1) return;
@@ -3390,7 +3725,7 @@ function runPageReorderConformance(runner, opts) {
3390
3725
  }
3391
3726
  });
3392
3727
  test("pages.move() rejects out-of-range destIndex with InvalidArg", async () => {
3393
- const doc = await openFixture$10(engine, opts);
3728
+ const doc = await openFixture$13(engine, opts);
3394
3729
  try {
3395
3730
  const list = await doc.pages.list();
3396
3731
  if (list.pages.length < 1) return;
@@ -3407,7 +3742,7 @@ function runPageReorderConformance(runner, opts) {
3407
3742
  }
3408
3743
  });
3409
3744
  test("pages.move() rejects unknown PON with NotFound or InvalidArg", async () => {
3410
- const doc = await openFixture$10(engine, opts);
3745
+ const doc = await openFixture$13(engine, opts);
3411
3746
  try {
3412
3747
  const list = await doc.pages.list();
3413
3748
  let bogus = 0;
@@ -3425,7 +3760,7 @@ function runPageReorderConformance(runner, opts) {
3425
3760
  }
3426
3761
  });
3427
3762
  test("abort on pages.move() rejects with AbortError", async () => {
3428
- const doc = await openFixture$10(engine, opts);
3763
+ const doc = await openFixture$13(engine, opts);
3429
3764
  try {
3430
3765
  const list = await doc.pages.list();
3431
3766
  if (list.pages.length < 1) return;
@@ -3439,7 +3774,7 @@ function runPageReorderConformance(runner, opts) {
3439
3774
  });
3440
3775
  });
3441
3776
  }
3442
- async function openFixture$10(engine, opts) {
3777
+ async function openFixture$13(engine, opts) {
3443
3778
  if (opts.openKind === "bytes") {
3444
3779
  const bytes = await opts.fixture.bytes();
3445
3780
  return engine.open({
@@ -3513,7 +3848,7 @@ function runPageRotateConformance(runner, opts) {
3513
3848
  if (engine) await engine.destroy();
3514
3849
  });
3515
3850
  test("pages.rotate() sets the absolute rotation and returns the full layout", async () => {
3516
- const doc = await openFixture$9(engine, opts);
3851
+ const doc = await openFixture$12(engine, opts);
3517
3852
  try {
3518
3853
  const before = await doc.pages.list();
3519
3854
  const target = before.pages[0];
@@ -3532,7 +3867,7 @@ function runPageRotateConformance(runner, opts) {
3532
3867
  }
3533
3868
  });
3534
3869
  test("absolute rotation is idempotent: setting the same value twice is a no-op", async () => {
3535
- const doc = await openFixture$9(engine, opts);
3870
+ const doc = await openFixture$12(engine, opts);
3536
3871
  try {
3537
3872
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3538
3873
  const first = await doc.pages.rotate([pon], 180);
@@ -3543,7 +3878,7 @@ function runPageRotateConformance(runner, opts) {
3543
3878
  }
3544
3879
  });
3545
3880
  test("one rotation applies to EVERY listed page (the multi-select gesture)", async () => {
3546
- const doc = await openFixture$9(engine, opts);
3881
+ const doc = await openFixture$12(engine, opts);
3547
3882
  try {
3548
3883
  const list = await doc.pages.list();
3549
3884
  if (list.pages.length < 2) return;
@@ -3555,7 +3890,7 @@ function runPageRotateConformance(runner, opts) {
3555
3890
  }
3556
3891
  });
3557
3892
  test("weak index-based annotation refs survive a rotate (no revision bump)", async () => {
3558
- const doc = await openFixture$9(engine, opts);
3893
+ const doc = await openFixture$12(engine, opts);
3559
3894
  try {
3560
3895
  const hostPon = (await doc.pages.list()).pages[0].pageObjectNumber;
3561
3896
  const hostPage = doc.page(hostPon);
@@ -3585,7 +3920,7 @@ function runPageRotateConformance(runner, opts) {
3585
3920
  }
3586
3921
  });
3587
3922
  test("pages.rotate() rejects an invalid rotation value with InvalidArg", async () => {
3588
- const doc = await openFixture$9(engine, opts);
3923
+ const doc = await openFixture$12(engine, opts);
3589
3924
  try {
3590
3925
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3591
3926
  let caught;
@@ -3600,7 +3935,7 @@ function runPageRotateConformance(runner, opts) {
3600
3935
  }
3601
3936
  });
3602
3937
  test("pages.rotate() rejects duplicate PONs with InvalidArg", async () => {
3603
- const doc = await openFixture$9(engine, opts);
3938
+ const doc = await openFixture$12(engine, opts);
3604
3939
  try {
3605
3940
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3606
3941
  let caught;
@@ -3615,7 +3950,7 @@ function runPageRotateConformance(runner, opts) {
3615
3950
  }
3616
3951
  });
3617
3952
  test("pages.rotate() rejects unknown PON with NotFound or InvalidArg", async () => {
3618
- const doc = await openFixture$9(engine, opts);
3953
+ const doc = await openFixture$12(engine, opts);
3619
3954
  try {
3620
3955
  const list = await doc.pages.list();
3621
3956
  let bogus = 0;
@@ -3633,7 +3968,7 @@ function runPageRotateConformance(runner, opts) {
3633
3968
  }
3634
3969
  });
3635
3970
  test("abort on pages.rotate() rejects with AbortError", async () => {
3636
- const doc = await openFixture$9(engine, opts);
3971
+ const doc = await openFixture$12(engine, opts);
3637
3972
  try {
3638
3973
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3639
3974
  const p = doc.pages.rotate([pon], 90);
@@ -3645,7 +3980,7 @@ function runPageRotateConformance(runner, opts) {
3645
3980
  });
3646
3981
  });
3647
3982
  }
3648
- async function openFixture$9(engine, opts) {
3983
+ async function openFixture$12(engine, opts) {
3649
3984
  if (opts.openKind === "bytes") {
3650
3985
  const bytes = await opts.fixture.bytes();
3651
3986
  return engine.open({
@@ -3708,7 +4043,7 @@ function runPageDeleteConformance(runner, opts) {
3708
4043
  if (engine) await engine.destroy();
3709
4044
  });
3710
4045
  test("pages.delete() removes the page and returns the surviving layout", async () => {
3711
- const doc = await openFixture$8(engine, opts);
4046
+ const doc = await openFixture$11(engine, opts);
3712
4047
  try {
3713
4048
  const before = await doc.pages.list();
3714
4049
  if (before.pages.length < 2) return;
@@ -3729,7 +4064,7 @@ function runPageDeleteConformance(runner, opts) {
3729
4064
  }
3730
4065
  });
3731
4066
  test("addressing a deleted page afterwards is a clean NotFound", async () => {
3732
- const doc = await openFixture$8(engine, opts);
4067
+ const doc = await openFixture$11(engine, opts);
3733
4068
  try {
3734
4069
  const before = await doc.pages.list();
3735
4070
  if (before.pages.length < 2) return;
@@ -3747,7 +4082,7 @@ function runPageDeleteConformance(runner, opts) {
3747
4082
  }
3748
4083
  });
3749
4084
  test("surviving pages keep their weak index-based annotation refs", async () => {
3750
- const doc = await openFixture$8(engine, opts);
4085
+ const doc = await openFixture$11(engine, opts);
3751
4086
  try {
3752
4087
  const list = await doc.pages.list();
3753
4088
  if (list.pages.length < 2) return;
@@ -3780,7 +4115,7 @@ function runPageDeleteConformance(runner, opts) {
3780
4115
  }
3781
4116
  });
3782
4117
  test("deleting EVERY page is rejected with InvalidArg", async () => {
3783
- const doc = await openFixture$8(engine, opts);
4118
+ const doc = await openFixture$11(engine, opts);
3784
4119
  try {
3785
4120
  const list = await doc.pages.list();
3786
4121
  let caught;
@@ -3795,7 +4130,7 @@ function runPageDeleteConformance(runner, opts) {
3795
4130
  }
3796
4131
  });
3797
4132
  test("pages.delete() rejects duplicate PONs with InvalidArg", async () => {
3798
- const doc = await openFixture$8(engine, opts);
4133
+ const doc = await openFixture$11(engine, opts);
3799
4134
  try {
3800
4135
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
3801
4136
  let caught;
@@ -3810,7 +4145,7 @@ function runPageDeleteConformance(runner, opts) {
3810
4145
  }
3811
4146
  });
3812
4147
  test("pages.delete() rejects unknown PON with NotFound or InvalidArg", async () => {
3813
- const doc = await openFixture$8(engine, opts);
4148
+ const doc = await openFixture$11(engine, opts);
3814
4149
  try {
3815
4150
  const list = await doc.pages.list();
3816
4151
  let bogus = 0;
@@ -3828,7 +4163,7 @@ function runPageDeleteConformance(runner, opts) {
3828
4163
  }
3829
4164
  });
3830
4165
  test("abort on pages.delete() rejects with AbortError", async () => {
3831
- const doc = await openFixture$8(engine, opts);
4166
+ const doc = await openFixture$11(engine, opts);
3832
4167
  try {
3833
4168
  const list = await doc.pages.list();
3834
4169
  if (list.pages.length < 2) return;
@@ -3842,7 +4177,7 @@ function runPageDeleteConformance(runner, opts) {
3842
4177
  });
3843
4178
  });
3844
4179
  }
3845
- async function openFixture$8(engine, opts) {
4180
+ async function openFixture$11(engine, opts) {
3846
4181
  if (opts.openKind === "bytes") {
3847
4182
  const bytes = await opts.fixture.bytes();
3848
4183
  return engine.open({
@@ -3870,7 +4205,7 @@ function runPageFlattenConformance(runner, opts) {
3870
4205
  if (engine) await engine.destroy();
3871
4206
  });
3872
4207
  test("flattens eligible appearances once without changing layout", async () => {
3873
- const doc = await openFixture$7(engine, opts);
4208
+ const doc = await openFixture$10(engine, opts);
3874
4209
  try {
3875
4210
  if (!doc.pages.flatten) return;
3876
4211
  const layoutBefore = await doc.pages.list();
@@ -3904,7 +4239,7 @@ function runPageFlattenConformance(runner, opts) {
3904
4239
  });
3905
4240
  });
3906
4241
  }
3907
- async function openFixture$7(engine, opts) {
4242
+ async function openFixture$10(engine, opts) {
3908
4243
  if (opts.openKind === "bytes") return engine.open({
3909
4244
  kind: "bytes",
3910
4245
  id: opts.fixture.id,
@@ -3959,7 +4294,7 @@ function runRedactionApplyConformance(runner, opts) {
3959
4294
  if (engine) await engine.destroy();
3960
4295
  });
3961
4296
  test("annotations scope applies the marked redaction and counts collateral", async () => {
3962
- const doc = await openFixture$6(engine, opts);
4297
+ const doc = await openFixture$9(engine, opts);
3963
4298
  try {
3964
4299
  if (!doc.redaction) return;
3965
4300
  const layoutBefore = await doc.pages.list();
@@ -4013,7 +4348,7 @@ function runRedactionApplyConformance(runner, opts) {
4013
4348
  }
4014
4349
  });
4015
4350
  test("pages scope applies everything once and validates its input", async () => {
4016
- const doc = await openFixture$6(engine, opts);
4351
+ const doc = await openFixture$9(engine, opts);
4017
4352
  try {
4018
4353
  if (!doc.redaction) return;
4019
4354
  const pageObjectNumber = (await doc.pages.list()).pages[0].pageObjectNumber;
@@ -4064,7 +4399,7 @@ function runRedactionApplyConformance(runner, opts) {
4064
4399
  });
4065
4400
  });
4066
4401
  }
4067
- async function openFixture$6(engine, opts) {
4402
+ async function openFixture$9(engine, opts) {
4068
4403
  if (opts.openKind === "bytes") return engine.open({
4069
4404
  kind: "bytes",
4070
4405
  id: opts.fixture.id,
@@ -4104,19 +4439,14 @@ function runPageExtractConformance(runner, opts) {
4104
4439
  const { describe, test, beforeAll, afterAll, expect } = runner;
4105
4440
  describe(`page extract conformance: ${opts.label}`, () => {
4106
4441
  let engine;
4107
- let supported = false;
4108
4442
  beforeAll(async () => {
4109
4443
  engine = await opts.makeEngine();
4110
- const probe = await openFixture$5(engine, opts);
4111
- supported = probe.pages.extract !== void 0;
4112
- await probe.close();
4113
4444
  });
4114
4445
  afterAll(async () => {
4115
4446
  if (engine) await engine.destroy();
4116
4447
  });
4117
4448
  test("pages.extract() returns standalone PDF bytes and leaves the source untouched", async () => {
4118
- if (!supported) return;
4119
- const doc = await openFixture$5(engine, opts);
4449
+ const doc = await openFixture$8(engine, opts);
4120
4450
  try {
4121
4451
  const before = await doc.pages.list();
4122
4452
  const target = before.pages[before.pages.length - 1];
@@ -4131,8 +4461,8 @@ function runPageExtractConformance(runner, opts) {
4131
4461
  }
4132
4462
  });
4133
4463
  test("extracted bytes re-open as a document with the requested pages, in caller order", async () => {
4134
- if (!supported || opts.openKind !== "bytes") return;
4135
- const doc = await openFixture$5(engine, opts);
4464
+ if (opts.openKind !== "bytes") return;
4465
+ const doc = await openFixture$8(engine, opts);
4136
4466
  let extracted = null;
4137
4467
  try {
4138
4468
  const list = await doc.pages.list();
@@ -4155,8 +4485,7 @@ function runPageExtractConformance(runner, opts) {
4155
4485
  }
4156
4486
  });
4157
4487
  test("pages.extract() rejects empty input with InvalidArg", async () => {
4158
- if (!supported) return;
4159
- const doc = await openFixture$5(engine, opts);
4488
+ const doc = await openFixture$8(engine, opts);
4160
4489
  try {
4161
4490
  let caught;
4162
4491
  try {
@@ -4170,8 +4499,7 @@ function runPageExtractConformance(runner, opts) {
4170
4499
  }
4171
4500
  });
4172
4501
  test("pages.extract() rejects duplicate PONs with InvalidArg", async () => {
4173
- if (!supported) return;
4174
- const doc = await openFixture$5(engine, opts);
4502
+ const doc = await openFixture$8(engine, opts);
4175
4503
  try {
4176
4504
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
4177
4505
  let caught;
@@ -4186,8 +4514,7 @@ function runPageExtractConformance(runner, opts) {
4186
4514
  }
4187
4515
  });
4188
4516
  test("pages.extract() rejects unknown PON with NotFound or InvalidArg", async () => {
4189
- if (!supported) return;
4190
- const doc = await openFixture$5(engine, opts);
4517
+ const doc = await openFixture$8(engine, opts);
4191
4518
  try {
4192
4519
  const list = await doc.pages.list();
4193
4520
  let bogus = 0;
@@ -4205,8 +4532,7 @@ function runPageExtractConformance(runner, opts) {
4205
4532
  }
4206
4533
  });
4207
4534
  test("abort on pages.extract() rejects with AbortError", async () => {
4208
- if (!supported) return;
4209
- const doc = await openFixture$5(engine, opts);
4535
+ const doc = await openFixture$8(engine, opts);
4210
4536
  try {
4211
4537
  const list = await doc.pages.list();
4212
4538
  const p = doc.pages.extract([list.pages[0].pageObjectNumber]);
@@ -4218,7 +4544,7 @@ function runPageExtractConformance(runner, opts) {
4218
4544
  });
4219
4545
  });
4220
4546
  }
4221
- async function openFixture$5(engine, opts) {
4547
+ async function openFixture$8(engine, opts) {
4222
4548
  if (opts.openKind === "bytes") {
4223
4549
  const bytes = await opts.fixture.bytes();
4224
4550
  return engine.open({
@@ -4480,9 +4806,9 @@ function runAttachmentConformance(runner, opts) {
4480
4806
  //#endregion
4481
4807
  //#region src/conformance/runPageInsertConformance.ts
4482
4808
  /**
4483
- * Page insert conformance. `pages.insert?` is optional only until the cloud
4484
- * endpoint ships (it is slated REQUIRED-parity — a cloud viewer must be able
4485
- * to add pages); the suite runs wherever the implementation exists.
4809
+ * Page insert conformance. `pages.insert` is a REQUIRED member — this suite
4810
+ * runs unconditionally on every engine, so an implementation that loses the
4811
+ * verb fails loudly instead of being skipped past.
4486
4812
  *
4487
4813
  * Invariants:
4488
4814
  * 1. Every page of the source bytes is COPIED in at `destIndex` (omitted →
@@ -4498,19 +4824,14 @@ function runPageInsertConformance(runner, opts) {
4498
4824
  const { describe, test, beforeAll, afterAll, expect } = runner;
4499
4825
  describe(`page insert conformance: ${opts.label}`, () => {
4500
4826
  let engine;
4501
- let supported = false;
4502
4827
  beforeAll(async () => {
4503
4828
  engine = await opts.makeEngine();
4504
- const probe = await openFixture$4(engine, opts);
4505
- supported = probe.pages.insert !== void 0 && probe.pages.extract !== void 0;
4506
- await probe.close();
4507
4829
  });
4508
4830
  afterAll(async () => {
4509
4831
  if (engine) await engine.destroy();
4510
4832
  });
4511
4833
  test("appends every source page with fresh PONs; existing pages keep identity", async () => {
4512
- if (!supported) return;
4513
- const doc = await openFixture$4(engine, opts);
4834
+ const doc = await openFixture$7(engine, opts);
4514
4835
  try {
4515
4836
  const before = await doc.pages.list();
4516
4837
  const beforePons = before.pages.map((p) => p.pageObjectNumber);
@@ -4528,8 +4849,7 @@ function runPageInsertConformance(runner, opts) {
4528
4849
  }
4529
4850
  });
4530
4851
  test("destIndex places the block mid-document, in source order", async () => {
4531
- if (!supported) return;
4532
- const doc = await openFixture$4(engine, opts);
4852
+ const doc = await openFixture$7(engine, opts);
4533
4853
  try {
4534
4854
  const before = await doc.pages.list();
4535
4855
  if (before.pages.length < 2) return;
@@ -4546,8 +4866,8 @@ function runPageInsertConformance(runner, opts) {
4546
4866
  }
4547
4867
  });
4548
4868
  test("the inserted pages persist through save → re-open", async () => {
4549
- if (!supported || opts.openKind !== "bytes") return;
4550
- const doc = await openFixture$4(engine, opts);
4869
+ if (opts.openKind !== "bytes") return;
4870
+ const doc = await openFixture$7(engine, opts);
4551
4871
  let reopened = null;
4552
4872
  try {
4553
4873
  const before = await doc.pages.list();
@@ -4567,8 +4887,7 @@ function runPageInsertConformance(runner, opts) {
4567
4887
  }
4568
4888
  });
4569
4889
  test("empty bytes reject with InvalidArg; garbage rejects with MalformedPdf", async () => {
4570
- if (!supported) return;
4571
- const doc = await openFixture$4(engine, opts);
4890
+ const doc = await openFixture$7(engine, opts);
4572
4891
  try {
4573
4892
  let caught;
4574
4893
  try {
@@ -4591,8 +4910,7 @@ function runPageInsertConformance(runner, opts) {
4591
4910
  }
4592
4911
  });
4593
4912
  test("out-of-range destIndex rejects with InvalidArg", async () => {
4594
- if (!supported) return;
4595
- const doc = await openFixture$4(engine, opts);
4913
+ const doc = await openFixture$7(engine, opts);
4596
4914
  try {
4597
4915
  const before = await doc.pages.list();
4598
4916
  const single = await doc.pages.extract([before.pages[0].pageObjectNumber]);
@@ -4609,7 +4927,160 @@ function runPageInsertConformance(runner, opts) {
4609
4927
  });
4610
4928
  });
4611
4929
  }
4612
- async function openFixture$4(engine, opts) {
4930
+ async function openFixture$7(engine, opts) {
4931
+ if (opts.openKind === "bytes") {
4932
+ const bytes = await opts.fixture.bytes();
4933
+ return engine.open({
4934
+ kind: "bytes",
4935
+ id: opts.fixture.id,
4936
+ bytes
4937
+ });
4938
+ }
4939
+ return engine.open({
4940
+ kind: "id",
4941
+ id: opts.fixture.cloudId ?? opts.fixture.id
4942
+ });
4943
+ }
4944
+ //#endregion
4945
+ //#region src/conformance/runPageInsertBlankConformance.ts
4946
+ /** An on-purpose non-default size so the assertions can't pass by accident. */
4947
+ const SIZE = {
4948
+ width: 396,
4949
+ height: 612
4950
+ };
4951
+ /**
4952
+ * Blank-page insert conformance. `pages.insertBlank` is a REQUIRED member —
4953
+ * the suite runs unconditionally on every engine (no fixture bytes are
4954
+ * needed at all, which is the point of the verb), so an implementation that
4955
+ * loses the verb fails loudly instead of being skipped past.
4956
+ *
4957
+ * Invariants:
4958
+ * 1. `count` blank pages of exactly `size` appear at `destIndex` (omitted
4959
+ * → append), rotation 0; the result lists their fresh PONs in insertion
4960
+ * order and they agree with the returned layout.
4961
+ * 2. Pre-existing pages keep their identity: same PONs before and after,
4962
+ * in the expected positions (an insert never invalidates neighbours).
4963
+ * 3. The mutation persists through save → re-open (bytes engines only).
4964
+ * 4. Non-positive size / count outside [1, PAGE_INSERT_BLANK_MAX_COUNT] /
4965
+ * out-of-range destIndex reject with InvalidArg, leaving the document
4966
+ * untouched.
4967
+ */
4968
+ function runPageInsertBlankConformance(runner, opts) {
4969
+ const { describe, test, beforeAll, afterAll, expect } = runner;
4970
+ describe(`page insert-blank conformance: ${opts.label}`, () => {
4971
+ let engine;
4972
+ beforeAll(async () => {
4973
+ engine = await opts.makeEngine();
4974
+ });
4975
+ afterAll(async () => {
4976
+ if (engine) await engine.destroy();
4977
+ });
4978
+ test("appends one blank page of the requested size with a fresh PON", async () => {
4979
+ const doc = await openFixture$6(engine, opts);
4980
+ try {
4981
+ const before = await doc.pages.list();
4982
+ const beforePons = before.pages.map((p) => p.pageObjectNumber);
4983
+ const result = await doc.pages.insertBlank({ size: SIZE });
4984
+ expect(result.insertedPageObjectNumbers.length).toBe(1);
4985
+ expect(result.layout.pageCount).toBe(before.pageCount + 1);
4986
+ expect(result.layout.pages.slice(0, before.pageCount).map((p) => p.pageObjectNumber)).toEqual(beforePons);
4987
+ const newPon = result.insertedPageObjectNumbers[0];
4988
+ expect(beforePons.includes(newPon)).toBe(false);
4989
+ const appended = result.layout.pages[before.pageCount];
4990
+ expect(appended.pageObjectNumber).toBe(newPon);
4991
+ expect(appended.size).toEqual(SIZE);
4992
+ expect(appended.rotation).toBe(0);
4993
+ } finally {
4994
+ await doc.close();
4995
+ }
4996
+ });
4997
+ test("destIndex + count places the blank block mid-document", async () => {
4998
+ const doc = await openFixture$6(engine, opts);
4999
+ try {
5000
+ const before = await doc.pages.list();
5001
+ if (before.pages.length < 2) return;
5002
+ const beforePons = before.pages.map((p) => p.pageObjectNumber);
5003
+ const result = await doc.pages.insertBlank({
5004
+ size: SIZE,
5005
+ count: 2
5006
+ }, 1);
5007
+ expect(result.insertedPageObjectNumbers.length).toBe(2);
5008
+ expect(result.insertedPageObjectNumbers[0] === result.insertedPageObjectNumbers[1]).toBe(false);
5009
+ const pons = result.layout.pages.map((p) => p.pageObjectNumber);
5010
+ expect(pons[0]).toBe(beforePons[0]);
5011
+ expect(pons.slice(1, 3)).toEqual(result.insertedPageObjectNumbers);
5012
+ expect(pons.slice(3)).toEqual(beforePons.slice(1));
5013
+ expect(result.layout.pages[1].size).toEqual(SIZE);
5014
+ expect(result.layout.pages[2].size).toEqual(SIZE);
5015
+ } finally {
5016
+ await doc.close();
5017
+ }
5018
+ });
5019
+ test("the blank pages persist through save → re-open", async () => {
5020
+ if (opts.openKind !== "bytes") return;
5021
+ const doc = await openFixture$6(engine, opts);
5022
+ let reopened = null;
5023
+ try {
5024
+ const before = await doc.pages.list();
5025
+ await doc.pages.insertBlank({ size: SIZE });
5026
+ const bytes = await doc.download();
5027
+ reopened = await engine.open({
5028
+ kind: "bytes",
5029
+ id: `${opts.fixture.id}-insert-blank-reopen`,
5030
+ bytes
5031
+ });
5032
+ const after = await reopened.pages.list();
5033
+ expect(after.pageCount).toBe(before.pageCount + 1);
5034
+ const tail = after.pages[after.pageCount - 1];
5035
+ expect(Math.round(tail.size.width)).toBe(SIZE.width);
5036
+ expect(Math.round(tail.size.height)).toBe(SIZE.height);
5037
+ } finally {
5038
+ if (reopened) await reopened.close();
5039
+ await doc.close();
5040
+ }
5041
+ });
5042
+ test("non-positive size, bad count, and out-of-range destIndex reject with InvalidArg", async () => {
5043
+ const doc = await openFixture$6(engine, opts);
5044
+ try {
5045
+ const before = await doc.pages.list();
5046
+ const rejects = [
5047
+ () => doc.pages.insertBlank({ size: {
5048
+ width: 0,
5049
+ height: 612
5050
+ } }),
5051
+ () => doc.pages.insertBlank({ size: {
5052
+ width: 396,
5053
+ height: -10
5054
+ } }),
5055
+ () => doc.pages.insertBlank({
5056
+ size: SIZE,
5057
+ count: 0
5058
+ }),
5059
+ () => doc.pages.insertBlank({
5060
+ size: SIZE,
5061
+ count: 101
5062
+ }),
5063
+ () => doc.pages.insertBlank({ size: SIZE }, before.pageCount + 1)
5064
+ ];
5065
+ for (const attempt of rejects) {
5066
+ let caught;
5067
+ try {
5068
+ await attempt();
5069
+ } catch (err) {
5070
+ caught = err;
5071
+ }
5072
+ expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
5073
+ }
5074
+ const list = await doc.pages.list();
5075
+ expect(list.pageCount).toBe(before.pageCount);
5076
+ expect(list.pages.map((p) => p.pageObjectNumber)).toEqual(before.pages.map((p) => p.pageObjectNumber));
5077
+ } finally {
5078
+ await doc.close();
5079
+ }
5080
+ });
5081
+ });
5082
+ }
5083
+ async function openFixture$6(engine, opts) {
4613
5084
  if (opts.openKind === "bytes") {
4614
5085
  const bytes = await opts.fixture.bytes();
4615
5086
  return engine.open({
@@ -4650,7 +5121,7 @@ function runPieceInfoConformance(runner, opts) {
4650
5121
  let supported = false;
4651
5122
  beforeAll(async () => {
4652
5123
  engine = await opts.makeEngine();
4653
- const probe = await openFixture$3(engine, opts);
5124
+ const probe = await openFixture$5(engine, opts);
4654
5125
  supported = probe.pieceInfo !== void 0;
4655
5126
  await probe.close();
4656
5127
  });
@@ -4659,7 +5130,7 @@ function runPieceInfoConformance(runner, opts) {
4659
5130
  });
4660
5131
  test("doc-level: the full value vocabulary round-trips with tags intact", async () => {
4661
5132
  if (!supported) return;
4662
- const doc = await openFixture$3(engine, opts);
5133
+ const doc = await openFixture$5(engine, opts);
4663
5134
  try {
4664
5135
  await doc.pieceInfo.update(APP, {
4665
5136
  name: "Standard Stamps",
@@ -4699,7 +5170,7 @@ function runPieceInfoConformance(runner, opts) {
4699
5170
  });
4700
5171
  test("page-level: the stamp schema round-trips on a page", async () => {
4701
5172
  if (!supported) return;
4702
- const doc = await openFixture$3(engine, opts);
5173
+ const doc = await openFixture$5(engine, opts);
4703
5174
  try {
4704
5175
  const list = await doc.pages.list();
4705
5176
  const page = doc.page(list.pages[0].pageObjectNumber);
@@ -4725,7 +5196,7 @@ function runPieceInfoConformance(runner, opts) {
4725
5196
  });
4726
5197
  test("writes persist through save → re-open", async () => {
4727
5198
  if (!supported || opts.openKind !== "bytes") return;
4728
- const doc = await openFixture$3(engine, opts);
5199
+ const doc = await openFixture$5(engine, opts);
4729
5200
  let reopened = null;
4730
5201
  try {
4731
5202
  const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
@@ -4758,7 +5229,7 @@ function runPieceInfoConformance(runner, opts) {
4758
5229
  });
4759
5230
  test("null deletes a key; clear removes an entry; siblings survive", async () => {
4760
5231
  if (!supported) return;
4761
- const doc = await openFixture$3(engine, opts);
5232
+ const doc = await openFixture$5(engine, opts);
4762
5233
  try {
4763
5234
  await doc.pieceInfo.update(APP, {
4764
5235
  name: "A",
@@ -4784,7 +5255,7 @@ function runPieceInfoConformance(runner, opts) {
4784
5255
  });
4785
5256
  test("an application that was never written reads as null", async () => {
4786
5257
  if (!supported) return;
4787
- const doc = await openFixture$3(engine, opts);
5258
+ const doc = await openFixture$5(engine, opts);
4788
5259
  try {
4789
5260
  expect(await doc.pieceInfo.read("EMBD_NeverWritten")).toBe(null);
4790
5261
  const list = await doc.pages.list();
@@ -4795,7 +5266,7 @@ function runPieceInfoConformance(runner, opts) {
4795
5266
  });
4796
5267
  });
4797
5268
  }
4798
- async function openFixture$3(engine, opts) {
5269
+ async function openFixture$5(engine, opts) {
4799
5270
  if (opts.openKind === "bytes") {
4800
5271
  const bytes = await opts.fixture.bytes();
4801
5272
  return engine.open({
@@ -4858,7 +5329,7 @@ function runDocumentEventsConformance(runner, opts) {
4858
5329
  if (engine) await engine.destroy();
4859
5330
  });
4860
5331
  test("every confirmed mutation publishes exactly one event, results verbatim", async () => {
4861
- const doc = await openFixture$2(engine, opts);
5332
+ const doc = await openFixture$4(engine, opts);
4862
5333
  try {
4863
5334
  const events = [];
4864
5335
  doc.events.subscribe((event) => events.push(event));
@@ -4905,19 +5376,46 @@ function runDocumentEventsConformance(runner, opts) {
4905
5376
  expect(evDeleted.layout).toEqual(deleted.layout);
4906
5377
  }
4907
5378
  if (evMeta.type === "metadata.updated") expect(evMeta.metadata).toEqual(meta.metadata);
5379
+ const first = events[0];
5380
+ expect(first !== void 0 && "origin" in first).toBe(true);
5381
+ if (first === void 0 || !("origin" in first)) return;
4908
5382
  for (const event of events) {
5383
+ expect("origin" in event).toBe(true);
5384
+ if (!("origin" in event)) continue;
4909
5385
  expect(event.origin.kind).toBe("local");
4910
5386
  expect(typeof event.origin.sessionId).toBe("string");
4911
5387
  expect(event.origin.sessionId.length > 0).toBe(true);
4912
- expect(event.origin.sessionId).toBe(events[0].origin.sessionId);
5388
+ expect(event.origin.sessionId).toBe(first.origin.sessionId);
4913
5389
  expect(typeof event.origin.ts).toBe("number");
4914
5390
  }
4915
5391
  } finally {
4916
5392
  await doc.close();
4917
5393
  }
4918
5394
  });
5395
+ test("pages.inserted rides verbatim", async () => {
5396
+ const doc = await openFixture$4(engine, opts);
5397
+ try {
5398
+ const events = [];
5399
+ doc.events.subscribe((event) => events.push(event));
5400
+ const inserted = await doc.pages.insertBlank({ size: {
5401
+ width: 396,
5402
+ height: 612
5403
+ } }, 0);
5404
+ expect(events.map((event) => event.type)).toEqual(["pages.inserted"]);
5405
+ const [evInserted] = events;
5406
+ if (evInserted.type === "pages.inserted") {
5407
+ expect(evInserted.insertedPageObjectNumbers).toEqual(inserted.insertedPageObjectNumbers);
5408
+ expect(evInserted.layout).toEqual(inserted.layout);
5409
+ expect(evInserted.cache).toEqual(inserted.cache);
5410
+ expect(evInserted.destIndex).toBe(0);
5411
+ expect(evInserted.origin.kind).toBe("local");
5412
+ }
5413
+ } finally {
5414
+ await doc.close();
5415
+ }
5416
+ });
4919
5417
  test("a FAILED mutation publishes nothing (events are ground truth)", async () => {
4920
- const doc = await openFixture$2(engine, opts);
5418
+ const doc = await openFixture$4(engine, opts);
4921
5419
  try {
4922
5420
  const events = [];
4923
5421
  doc.events.subscribe((event) => events.push(event));
@@ -4935,7 +5433,7 @@ function runDocumentEventsConformance(runner, opts) {
4935
5433
  }
4936
5434
  });
4937
5435
  test("unsubscribe stops delivery", async () => {
4938
- const doc = await openFixture$2(engine, opts);
5436
+ const doc = await openFixture$4(engine, opts);
4939
5437
  try {
4940
5438
  const events = [];
4941
5439
  doc.events.subscribe((event) => events.push(event))();
@@ -4948,7 +5446,7 @@ function runDocumentEventsConformance(runner, opts) {
4948
5446
  });
4949
5447
  });
4950
5448
  }
4951
- async function openFixture$2(engine, opts) {
5449
+ async function openFixture$4(engine, opts) {
4952
5450
  if (opts.openKind === "bytes") {
4953
5451
  const bytes = await opts.fixture.bytes();
4954
5452
  return engine.open({
@@ -4975,7 +5473,7 @@ function runPageTextConformance(runner, opts) {
4975
5473
  if (engine) await engine.destroy();
4976
5474
  });
4977
5475
  test("read() returns a PageTextSnapshot with non-empty text", async () => {
4978
- const doc = await openFixture$1(engine, opts);
5476
+ const doc = await openFixture$3(engine, opts);
4979
5477
  try {
4980
5478
  const snap = await doc.page(opts.fixture.pageObjectNumber).text.read();
4981
5479
  expect(PageTextSnapshotSchema.safeParse(snap).success).toBe(true);
@@ -4986,7 +5484,7 @@ function runPageTextConformance(runner, opts) {
4986
5484
  }
4987
5485
  });
4988
5486
  test("extracted text contains the expected substring", async () => {
4989
- const doc = await openFixture$1(engine, opts);
5487
+ const doc = await openFixture$3(engine, opts);
4990
5488
  try {
4991
5489
  const snap = await doc.page(opts.fixture.pageObjectNumber).text.read();
4992
5490
  expect(snap.text.includes(opts.fixture.expectedSubstring)).toBe(true);
@@ -4995,7 +5493,7 @@ function runPageTextConformance(runner, opts) {
4995
5493
  }
4996
5494
  });
4997
5495
  test("read() on an unknown pageObjectNumber throws NotFound", async () => {
4998
- const doc = await openFixture$1(engine, opts);
5496
+ const doc = await openFixture$3(engine, opts);
4999
5497
  try {
5000
5498
  let caught;
5001
5499
  try {
@@ -5010,7 +5508,7 @@ function runPageTextConformance(runner, opts) {
5010
5508
  }
5011
5509
  });
5012
5510
  test("abort() on read rejects with AbortError", async () => {
5013
- const doc = await openFixture$1(engine, opts);
5511
+ const doc = await openFixture$3(engine, opts);
5014
5512
  try {
5015
5513
  const p = doc.page(opts.fixture.pageObjectNumber).text.read();
5016
5514
  p.abort("test");
@@ -5020,7 +5518,7 @@ function runPageTextConformance(runner, opts) {
5020
5518
  }
5021
5519
  });
5022
5520
  test("read after close throws DocNotOpen", async () => {
5023
- const doc = await openFixture$1(engine, opts);
5521
+ const doc = await openFixture$3(engine, opts);
5024
5522
  const page = doc.page(opts.fixture.pageObjectNumber);
5025
5523
  await doc.close();
5026
5524
  let caught;
@@ -5034,6 +5532,320 @@ function runPageTextConformance(runner, opts) {
5034
5532
  });
5035
5533
  });
5036
5534
  }
5535
+ async function openFixture$3(engine, opts) {
5536
+ if (opts.openKind === "bytes") {
5537
+ const bytes = await opts.fixture.bytes();
5538
+ return engine.open({
5539
+ kind: "bytes",
5540
+ id: opts.fixture.id,
5541
+ bytes
5542
+ });
5543
+ }
5544
+ return engine.open({
5545
+ kind: "id",
5546
+ id: opts.fixture.cloudId ?? opts.fixture.id
5547
+ });
5548
+ }
5549
+ //#endregion
5550
+ //#region src/conformance/runTextDivergenceConformance.ts
5551
+ function runTextDivergenceConformance(runner, opts) {
5552
+ const { describe, test, beforeAll, afterAll, expect } = runner;
5553
+ const fixture = opts.fixture;
5554
+ describe(`text divergence conformance: ${opts.label} [${fixture.id}]`, () => {
5555
+ let engine;
5556
+ beforeAll(async () => {
5557
+ engine = await opts.makeEngine();
5558
+ });
5559
+ afterAll(async () => {
5560
+ if (engine) await engine.destroy();
5561
+ });
5562
+ test("snapshot is schema-valid with the exact text, charCount, and charMap", async () => {
5563
+ const doc = await openFixture$2(engine, opts);
5564
+ try {
5565
+ const snap = await doc.page(fixture.pageObjectNumber).text.read();
5566
+ expect(PageTextSnapshotSchema.safeParse(snap).success).toBe(true);
5567
+ expect(snap.text).toBe(fixture.exactText);
5568
+ expect(snap.charCount).toBe(fixture.expectedCharCount);
5569
+ const anchors = snap.charMap ?? null;
5570
+ if (fixture.expectedCharMap === null) expect(anchors === null || anchors.length === 0).toBe(true);
5571
+ else expect(anchors === null ? null : anchors.map((a) => [a[0], a[1]])).toEqual(fixture.expectedCharMap.map((a) => [a[0], a[1]]));
5572
+ } finally {
5573
+ await doc.close();
5574
+ }
5575
+ });
5576
+ test("char-space slicing round-trips through the map", async () => {
5577
+ const doc = await openFixture$2(engine, opts);
5578
+ try {
5579
+ const snap = await doc.page(fixture.pageObjectNumber).text.read();
5580
+ expect(sliceTextByChars(snap, 0, snap.charCount)).toBe(snap.text);
5581
+ for (const s of fixture.slices ?? []) expect(sliceTextByChars(snap, s.from, s.to)).toBe(s.text);
5582
+ } finally {
5583
+ await doc.close();
5584
+ }
5585
+ });
5586
+ if (fixture.search) {
5587
+ const probe = fixture.search;
5588
+ test("search hits address character space and join the text by slicing", async () => {
5589
+ const doc = await openFixture$2(engine, opts);
5590
+ try {
5591
+ const hit = (await doc.search.query({
5592
+ query: { text: probe.query },
5593
+ mode: "rects"
5594
+ })).matches.find((m) => m.pageObjectNumber === fixture.pageObjectNumber);
5595
+ expect(hit === void 0).toBe(false);
5596
+ expect(hit.charStart).toBe(probe.charStart);
5597
+ expect(hit.charCount).toBe(probe.charCount);
5598
+ expect(hit.segments.length > 0).toBe(true);
5599
+ const snap = await doc.page(fixture.pageObjectNumber).text.read();
5600
+ expect(sliceTextByChars(snap, hit.charStart, hit.charStart + hit.charCount)).toBe(probe.matchedText);
5601
+ } finally {
5602
+ await doc.close();
5603
+ }
5604
+ });
5605
+ }
5606
+ });
5607
+ }
5608
+ async function openFixture$2(engine, opts) {
5609
+ if (opts.openKind === "bytes") {
5610
+ const bytes = await opts.fixture.bytes();
5611
+ return engine.open({
5612
+ kind: "bytes",
5613
+ id: opts.fixture.id,
5614
+ bytes
5615
+ });
5616
+ }
5617
+ return engine.open({
5618
+ kind: "id",
5619
+ id: opts.fixture.cloudId ?? opts.fixture.id
5620
+ });
5621
+ }
5622
+ /**
5623
+ * THE pinned divergence cases — one table, consumed by both engines' suites
5624
+ * so expected values cannot drift. Values verified against the wasm runtime
5625
+ * (see each case). All fixtures are single-page; the page's indirect object
5626
+ * number is 3 in each.
5627
+ */
5628
+ const TEXT_DIVERGENCE_CASES = {
5629
+ /** Leading NON-PRINTING character: 31 character slots, 30 text units —
5630
+ * every text offset shifts by one, encoded by the single anchor [1, 0].
5631
+ * The searchex embeddertest for this fixture pins the same +1 shift. */
5632
+ bug1139: {
5633
+ resource: "bug_1139.pdf",
5634
+ pageObjectNumber: 3,
5635
+ exactText: "Hello, world!\r\nGoodbye, world!",
5636
+ expectedCharCount: 31,
5637
+ expectedCharMap: [[1, 0]],
5638
+ slices: [
5639
+ {
5640
+ from: 0,
5641
+ to: 1,
5642
+ text: ""
5643
+ },
5644
+ {
5645
+ from: 1,
5646
+ to: 7,
5647
+ text: "Hello,"
5648
+ },
5649
+ {
5650
+ from: 16,
5651
+ to: 23,
5652
+ text: "Goodbye"
5653
+ }
5654
+ ],
5655
+ search: {
5656
+ query: "Goodbye",
5657
+ charStart: 16,
5658
+ charCount: 7,
5659
+ matchedText: "Goodbye"
5660
+ }
5661
+ },
5662
+ /** `/ActualText` replacement: the span's real glyphs are REPLACED by one
5663
+ * synthetic kPiece character per ActualText character, lockstep with the
5664
+ * text (identity map), with synthesized evenly-divided boxes — so search
5665
+ * segments exist and extraction yields the ActualText, not the glyphs. */
5666
+ actualText: {
5667
+ resource: "bug_384770169.pdf",
5668
+ pageObjectNumber: 3,
5669
+ exactText: "What is my favorite food?",
5670
+ expectedCharCount: 25,
5671
+ expectedCharMap: null,
5672
+ slices: [{
5673
+ from: 11,
5674
+ to: 19,
5675
+ text: "favorite"
5676
+ }],
5677
+ search: {
5678
+ query: "favorite",
5679
+ charStart: 11,
5680
+ charCount: 8,
5681
+ matchedText: "favorite"
5682
+ }
5683
+ },
5684
+ /** Supplementary-plane text via a ToUnicode surrogate pair: PDFium stores
5685
+ * one character-list entry PER SURROGATE HALF in lockstep with the text
5686
+ * (identity map, no divergence in this representation) — and the emoji
5687
+ * SURVIVES extraction end to end. The combined-astral representation
5688
+ * (font-cmap path, one entry per code point → a [+2] anchor) is covered
5689
+ * by the charmap unit tests and the reader's divergent walk. */
5690
+ astralToUnicode: {
5691
+ resource: "embedpdf_astral_tounicode.pdf",
5692
+ pageObjectNumber: 3,
5693
+ exactText: "😀B!",
5694
+ expectedCharCount: 4,
5695
+ expectedCharMap: null,
5696
+ slices: [{
5697
+ from: 0,
5698
+ to: 2,
5699
+ text: "😀"
5700
+ }, {
5701
+ from: 2,
5702
+ to: 3,
5703
+ text: "B"
5704
+ }],
5705
+ search: {
5706
+ query: "B!",
5707
+ charStart: 2,
5708
+ charCount: 2,
5709
+ matchedText: "B!"
5710
+ }
5711
+ }
5712
+ };
5713
+ //#endregion
5714
+ //#region src/conformance/runPageGeometryOrientationConformance.ts
5715
+ const FLAG_EMPTY = 2;
5716
+ /** Absolute tolerance for coordinate assertions (PDF points). */
5717
+ const COORD_TOLERANCE = .001;
5718
+ /** Tolerance for baseline-angle assertions (radians). */
5719
+ const ANGLE_TOLERANCE = .001;
5720
+ function runPageGeometryOrientationConformance(runner, opts) {
5721
+ const { describe, test, beforeAll, afterAll, expect } = runner;
5722
+ describe(`page geometry orientation conformance: ${opts.label} [${opts.fixture.id}]`, () => {
5723
+ let engine;
5724
+ beforeAll(async () => {
5725
+ engine = await opts.makeEngine();
5726
+ });
5727
+ afterAll(async () => {
5728
+ if (engine) await engine.destroy();
5729
+ });
5730
+ const readSnapshot = async () => {
5731
+ const doc = await openFixture$1(engine, opts);
5732
+ return {
5733
+ snapshot: await doc.page(opts.fixture.pageObjectNumber).geometry.read(),
5734
+ close: () => doc.close()
5735
+ };
5736
+ };
5737
+ test("snapshot round-trips the wire schema", async () => {
5738
+ const { snapshot, close } = await readSnapshot();
5739
+ try {
5740
+ const parsed = PageGeometrySnapshotSchema.safeParse(snapshot);
5741
+ expect(parsed.success).toBe(true);
5742
+ if (parsed.success) expect(parsed.data).toEqual(snapshot);
5743
+ } finally {
5744
+ await close();
5745
+ }
5746
+ });
5747
+ test("run charStart indices tile the page glyph sequence", async () => {
5748
+ const { snapshot, close } = await readSnapshot();
5749
+ try {
5750
+ let next = 0;
5751
+ for (const run of snapshot.runs) {
5752
+ expect(run.charStart).toBe(next);
5753
+ next += run.glyphs.length;
5754
+ }
5755
+ } finally {
5756
+ await close();
5757
+ }
5758
+ });
5759
+ test("runs match the fixture orientation expectation", async () => {
5760
+ const { snapshot, close } = await readSnapshot();
5761
+ try {
5762
+ const expectation = opts.fixture.expectation;
5763
+ if (expectation.kind === "upright") {
5764
+ for (const run of snapshot.runs) {
5765
+ expect(isRotatedGeometryRun(run)).toBe(false);
5766
+ if (isRotatedGeometryRun(run)) continue;
5767
+ for (const glyph of run.glyphs) {
5768
+ expect("looseQuad" in glyph).toBe(false);
5769
+ if (glyph.flags & FLAG_EMPTY) expect(glyph.looseBox).toEqual({
5770
+ left: 0,
5771
+ bottom: 0,
5772
+ right: 0,
5773
+ top: 0
5774
+ });
5775
+ }
5776
+ }
5777
+ return;
5778
+ }
5779
+ if (expectation.kind === "empty-only") {
5780
+ for (const run of snapshot.runs) {
5781
+ expect(isRotatedGeometryRun(run)).toBe(false);
5782
+ if (isRotatedGeometryRun(run)) continue;
5783
+ for (const glyph of run.glyphs) {
5784
+ expect(glyph.flags & FLAG_EMPTY).toBe(FLAG_EMPTY);
5785
+ expect(glyph.looseBox).toEqual({
5786
+ left: 0,
5787
+ bottom: 0,
5788
+ right: 0,
5789
+ top: 0
5790
+ });
5791
+ }
5792
+ }
5793
+ return;
5794
+ }
5795
+ const rotatedRuns = snapshot.runs.filter(isRotatedGeometryRun);
5796
+ expect(rotatedRuns.length >= (expectation.minRotatedRuns ?? 1)).toBe(true);
5797
+ for (const run of rotatedRuns) {
5798
+ expect(expectation.rotations.some((angle) => angleClose(run.rotation, angle, ANGLE_TOLERANCE))).toBe(true);
5799
+ expect(run.ascentFlip).toBe(expectation.ascentFlip);
5800
+ assertRotatedRunGeometry(run);
5801
+ }
5802
+ if (expectation.sheared) {
5803
+ const shearedGlyphs = rotatedRuns.flatMap((run) => run.glyphs).filter((glyph) => (glyph.flags & FLAG_EMPTY) === 0 && Math.abs(glyph.looseQuad.p3.x - glyph.looseQuad.p1.x) > 1);
5804
+ expect(shearedGlyphs.length > 0).toBe(true);
5805
+ }
5806
+ } finally {
5807
+ await close();
5808
+ }
5809
+ });
5810
+ function assertRotatedRunGeometry(run) {
5811
+ for (const glyph of run.glyphs) {
5812
+ if (glyph.flags & FLAG_EMPTY) {
5813
+ expect(glyph.looseQuad).toEqual({
5814
+ p1: {
5815
+ x: 0,
5816
+ y: 0
5817
+ },
5818
+ p2: {
5819
+ x: 0,
5820
+ y: 0
5821
+ },
5822
+ p3: {
5823
+ x: 0,
5824
+ y: 0
5825
+ },
5826
+ p4: {
5827
+ x: 0,
5828
+ y: 0
5829
+ }
5830
+ });
5831
+ continue;
5832
+ }
5833
+ const q = glyph.looseQuad;
5834
+ expect(Math.abs(q.p2.x - q.p1.x - (q.p4.x - q.p3.x)) <= COORD_TOLERANCE).toBe(true);
5835
+ expect(Math.abs(q.p2.y - q.p1.y - (q.p4.y - q.p3.y)) <= COORD_TOLERANCE).toBe(true);
5836
+ const bounds = pdfQuadBounds(q);
5837
+ expect(bounds.left >= run.rect.left - COORD_TOLERANCE).toBe(true);
5838
+ expect(bounds.right <= run.rect.right + COORD_TOLERANCE).toBe(true);
5839
+ expect(bounds.bottom >= run.rect.bottom - COORD_TOLERANCE).toBe(true);
5840
+ expect(bounds.top <= run.rect.top + COORD_TOLERANCE).toBe(true);
5841
+ }
5842
+ }
5843
+ });
5844
+ }
5845
+ function angleClose(a, b, tolerance) {
5846
+ const delta = a - b;
5847
+ return Math.abs(Math.atan2(Math.sin(delta), Math.cos(delta))) <= tolerance;
5848
+ }
5037
5849
  async function openFixture$1(engine, opts) {
5038
5850
  if (opts.openKind === "bytes") {
5039
5851
  const bytes = await opts.fixture.bytes();
@@ -5089,10 +5901,27 @@ function runSearchConformance(runner, opts) {
5089
5901
  expect(matches.some((m) => m.pageObjectNumber === fixture.presentPageObjectNumber)).toBe(true);
5090
5902
  for (const m of matches) {
5091
5903
  expect(m.charCount > 0).toBe(true);
5092
- expect(m.rects.length > 0).toBe(true);
5093
- for (const r of m.rects) {
5094
- expect(r.right > r.left).toBe(true);
5095
- expect(r.top > r.bottom).toBe(true);
5904
+ expect(m.segments.length > 0).toBe(true);
5905
+ for (const s of m.segments) {
5906
+ expect(s.rect.right > s.rect.left).toBe(true);
5907
+ expect(s.rect.top > s.rect.bottom).toBe(true);
5908
+ const xs = [
5909
+ s.quad.p1.x,
5910
+ s.quad.p2.x,
5911
+ s.quad.p3.x,
5912
+ s.quad.p4.x
5913
+ ];
5914
+ const ys = [
5915
+ s.quad.p1.y,
5916
+ s.quad.p2.y,
5917
+ s.quad.p3.y,
5918
+ s.quad.p4.y
5919
+ ];
5920
+ expect(Math.abs(s.rect.left - Math.min(...xs)) < .001).toBe(true);
5921
+ expect(Math.abs(s.rect.right - Math.max(...xs)) < .001).toBe(true);
5922
+ expect(Math.abs(s.rect.bottom - Math.min(...ys)) < .001).toBe(true);
5923
+ expect(Math.abs(s.rect.top - Math.max(...ys)) < .001).toBe(true);
5924
+ expect(s.advance === 1 || s.advance === -1).toBe(true);
5096
5925
  }
5097
5926
  }
5098
5927
  } finally {
@@ -5224,7 +6053,7 @@ function runSearchConformance(runner, opts) {
5224
6053
  expect(matches.length > 0).toBe(true);
5225
6054
  for (const m of matches) {
5226
6055
  expect(m.charCount > 0).toBe(true);
5227
- expect(m.rects.length > 0).toBe(true);
6056
+ expect(m.segments.length > 0).toBe(true);
5228
6057
  }
5229
6058
  } finally {
5230
6059
  await doc.close();
@@ -5351,6 +6180,8 @@ async function openFixture(engine, opts) {
5351
6180
  * are disjoint. We do compare `pageObjectNumber` and `generation` (which
5352
6181
  * should be 0 for fresh reads on both sides). Page order is no longer part
5353
6182
  * of `PageState`; it lives in `PageLayout.index` (see `pages.list()`).
6183
+ * `auditHead` is likewise not compared: it is a cloud-only reconciliation
6184
+ * cursor, absent on local snapshots by design.
5354
6185
  */
5355
6186
  function diffAnnotationListSnapshot(a, b) {
5356
6187
  const errs = [];
@@ -5389,6 +6220,6 @@ function diffAnnotation(i, a, b, errs) {
5389
6220
  if (a.nm !== b.nm) errs.push(`annotations[${i}].nm mismatch: ${a.nm} vs ${b.nm}`);
5390
6221
  }
5391
6222
  //#endregion
5392
- export { diffAnnotationListSnapshot, diffAnnotationListSnapshotAll, runActionsConformance, runAnnotationAppearanceConformance, runAnnotationMutationConformance, runAnnotationReadConformance, runAttachmentConformance, runDocumentEventsConformance, runFormConformance, runMetadataConformance, runPageDeleteConformance, runPageExtractConformance, runPageFlattenConformance, runPageInsertConformance, runPageReorderConformance, runPageRotateConformance, runPageTextConformance, runPieceInfoConformance, runRedactionApplyConformance, runSearchConformance };
6223
+ export { TEXT_DIVERGENCE_CASES, diffAnnotationListSnapshot, diffAnnotationListSnapshotAll, runActionsConformance, runAnnotationAppearanceConformance, runAnnotationMutationConformance, runAnnotationReadConformance, runAttachmentConformance, runDocumentEventsConformance, runFormConformance, runMetadataConformance, runPageDeleteConformance, runPageExtractConformance, runPageFlattenConformance, runPageGeometryOrientationConformance, runPageInsertBlankConformance, runPageInsertConformance, runPageReorderConformance, runPageRotateConformance, runPageTextConformance, runPieceInfoConformance, runRedactionApplyConformance, runSearchConformance, runTextDivergenceConformance };
5393
6224
 
5394
6225
  //# sourceMappingURL=conformance.js.map