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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/{AbortError-8-9JAX1Y.js → AbortError-DwuHPK_I.js} +2 -2
  2. package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DwuHPK_I.js.map} +1 -1
  3. package/dist/{AbortError-CRSooKsk.cjs → AbortError-P0RU0Mgg.cjs} +4 -4
  4. package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-P0RU0Mgg.cjs.map} +1 -1
  5. package/dist/{AnnotationListSnapshot-x6tKulNV.d.cts → AnnotationListSnapshot-D9R5n2Kf.d.cts} +1390 -176
  6. package/dist/{AnnotationListSnapshot-x6tKulNV.d.ts → AnnotationListSnapshot-D9R5n2Kf.d.ts} +1390 -176
  7. package/dist/{DocumentSecurityService-CK0pejAK.d.cts → DocumentSecurityService-CIx1MT0T.d.cts} +2 -2
  8. package/dist/{DocumentSecurityService-9MawYxP-.d.ts → DocumentSecurityService-Ca6EK3-3.d.ts} +2 -2
  9. package/dist/{Engine--0XF0UAK.d.cts → Engine-EWWHhEIi.d.ts} +179 -11
  10. package/dist/{Engine-Ds0ytbl6.d.ts → Engine-FNaAEjfb.d.cts} +179 -11
  11. package/dist/conformance.cjs +1441 -88
  12. package/dist/conformance.cjs.map +1 -1
  13. package/dist/conformance.d.cts +74 -3
  14. package/dist/conformance.d.ts +74 -3
  15. package/dist/conformance.js +1376 -25
  16. package/dist/conformance.js.map +1 -1
  17. package/dist/derive-BbsDk_Kz.cjs +880 -0
  18. package/dist/derive-BbsDk_Kz.cjs.map +1 -0
  19. package/dist/derive-DAFJolWu.js +647 -0
  20. package/dist/derive-DAFJolWu.js.map +1 -0
  21. package/dist/{PageInsertBlankInput-WKY0tr6S.js → format-Bbphf4zR.js} +95 -3
  22. package/dist/format-Bbphf4zR.js.map +1 -0
  23. package/dist/{PageInsertBlankInput-CzShuSJI.cjs → format-D_epWlob.cjs} +106 -2
  24. package/dist/format-D_epWlob.cjs.map +1 -0
  25. package/dist/index.cjs +137 -88
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +5 -5
  29. package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
  30. package/dist/resolver-CYVNV4hn.cjs.map +1 -0
  31. package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
  32. package/dist/resolver-inAVzsWt.js.map +1 -0
  33. package/dist/runtime.cjs +147 -98
  34. package/dist/runtime.cjs.map +1 -1
  35. package/dist/runtime.d.cts +216 -5
  36. package/dist/runtime.d.ts +216 -5
  37. package/dist/runtime.js +6 -6
  38. package/dist/{schemas-DOyVEPk7.js → schemas-CFQGvWiu.js} +551 -5
  39. package/dist/schemas-CFQGvWiu.js.map +1 -0
  40. package/dist/{schemas-RChsAMWw.cjs → schemas-D0g6tKa9.cjs} +829 -25
  41. package/dist/schemas-D0g6tKa9.cjs.map +1 -0
  42. package/dist/{builders-8Hl1AYNU.cjs → viewport-BWeNHZmb.cjs} +1522 -117
  43. package/dist/viewport-BWeNHZmb.cjs.map +1 -0
  44. package/dist/{builders-DOjcKDWQ.js → viewport-CvMcadfQ.js} +1386 -89
  45. package/dist/viewport-CvMcadfQ.js.map +1 -0
  46. package/dist/wire.cjs +252 -13
  47. package/dist/wire.cjs.map +1 -1
  48. package/dist/wire.d.cts +3147 -5
  49. package/dist/wire.d.ts +3147 -5
  50. package/dist/wire.js +207 -14
  51. package/dist/wire.js.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
  54. package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
  55. package/dist/builders-8Hl1AYNU.cjs.map +0 -1
  56. package/dist/builders-DOjcKDWQ.js.map +0 -1
  57. package/dist/fold-BfyxxwS6.js +0 -334
  58. package/dist/fold-BfyxxwS6.js.map +0 -1
  59. package/dist/fold-C7I5GpBz.cjs +0 -447
  60. package/dist/fold-C7I5GpBz.cjs.map +0 -1
  61. package/dist/resolver-CHG4waXL.js.map +0 -1
  62. package/dist/resolver-D-jumvgM.cjs.map +0 -1
  63. package/dist/schemas-DOyVEPk7.js.map +0 -1
  64. package/dist/schemas-RChsAMWw.cjs.map +0 -1
@@ -1,7 +1,7 @@
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 { $ as PageListSnapshotSchema, Ft as FormSnapshotSchema, J as PageGeometrySnapshotSchema, M as FormRepairResultSchema, N as FormSetValueResultSchema, Vt as AnnotationDTOSchema, W as PageDeleteResultSchema, br as DocumentActionsSnapshotSchema, d as AnnotationListPageSnapshotSchema, f as AnnotationListSnapshotAllPagesSchema, ft as PageTextSnapshotSchema, j as FormImportResultSchema, l as AnnotationFlattenResultSchema, lt as PageRotateResultSchema, m as AnnotationUpdateResultSchema, mt as RedactionApplyResultSchema, o as AnnotationCreateResultSchema, p as AnnotationMoveResultSchema, q as PageFlattenResultSchema, rt as PageNameResultSchema, s as AnnotationDeleteResultSchema, tt as PageMoveResultSchema, xt as SearchSliceSchema } from "./schemas-DOyVEPk7.js";
1
+ import { A as isRotatedGeometryRun, C as refKey, M as pdfQuadBounds, i as measurementReadout, u as measureFromKnownLength, w as EngineError, y as foldText } from "./derive-DAFJolWu.js";
2
+ import { c as sliceTextByChars, m as EngineErrorCode } from "./format-Bbphf4zR.js";
3
+ import { t as AbortError } from "./AbortError-DwuHPK_I.js";
4
+ import { B as FormImportResultSchema, Dt as RedactionApplyResultSchema, H as FormSetValueResultSchema, Pt as SearchSliceSchema, V as FormRepairResultSchema, at as PageGeometrySnapshotSchema, bt as PageRotateResultSchema, d as AnnotationListPageSnapshotSchema, f as AnnotationListSnapshotAllPagesSchema, ft as PageMoveResultSchema, hn as AnnotationDTOSchema, it as PageFlattenResultSchema, l as AnnotationFlattenResultSchema, li as DocumentActionsSnapshotSchema, ln as FormSnapshotSchema, m as AnnotationUpdateResultSchema, mt as PageNameResultSchema, o as AnnotationCreateResultSchema, p as AnnotationMoveResultSchema, s as AnnotationDeleteResultSchema, tt as PageDeleteResultSchema, ut as PageListSnapshotSchema, wt as PageTextSnapshotSchema } from "./schemas-CFQGvWiu.js";
5
5
  //#region src/conformance/runMetadataConformance.ts
6
6
  /**
7
7
  * The conformance harness is transport-agnostic. It takes the test runner as
@@ -122,6 +122,270 @@ async function openFixture$20(engine, opts) {
122
122
  });
123
123
  }
124
124
  //#endregion
125
+ //#region src/conformance/runMeasurementConformance.ts
126
+ /** Shared numerical, persistence and calibration contract for local and HTTP engines. */
127
+ function runMeasurementConformance(runner, opts) {
128
+ const { describe, test, beforeAll, afterAll, expect } = runner;
129
+ describe(`measurement conformance: ${opts.label}`, () => {
130
+ let engine;
131
+ beforeAll(async () => {
132
+ engine = await opts.makeEngine();
133
+ });
134
+ afterAll(async () => {
135
+ await engine?.destroy();
136
+ });
137
+ const open = async () => opts.openKind === "bytes" ? engine.open({
138
+ kind: "bytes",
139
+ id: opts.fixture.id,
140
+ bytes: await opts.fixture.bytes()
141
+ }) : engine.open({
142
+ kind: "id",
143
+ id: opts.fixture.cloudId ?? opts.fixture.id
144
+ });
145
+ const scale = measureFromKnownLength(100, {
146
+ value: 3,
147
+ unit: "m"
148
+ });
149
+ const rect = {
150
+ left: 0,
151
+ bottom: 0,
152
+ right: 100,
153
+ top: 100
154
+ };
155
+ const vertices = [
156
+ {
157
+ x: 0,
158
+ y: 0
159
+ },
160
+ {
161
+ x: 100,
162
+ y: 0
163
+ },
164
+ {
165
+ x: 100,
166
+ y: 100
167
+ }
168
+ ];
169
+ test("geometry derives contents while caption patches retain placement", async () => {
170
+ const doc = await open();
171
+ try {
172
+ const page = doc.page((await doc.pages.list()).pages[0].pageObjectNumber);
173
+ const created = (await page.annotations.create({
174
+ subtype: "line",
175
+ intent: "LineDimension",
176
+ rect,
177
+ measure: scale,
178
+ linePoints: {
179
+ start: vertices[0],
180
+ end: vertices[1]
181
+ },
182
+ caption: {
183
+ enabled: true,
184
+ offset: {
185
+ along: 10,
186
+ perpendicular: 20
187
+ }
188
+ }
189
+ })).created;
190
+ const inert = await page.annotations.update(created.ref, {
191
+ subtype: "line",
192
+ contents: "999 m"
193
+ });
194
+ expect(inert.updated.contents).toBe("3 m");
195
+ expect(inert.appearance).toEqual({
196
+ action: "preserved",
197
+ changed: false
198
+ });
199
+ const changed = await page.annotations.update(created.ref, {
200
+ subtype: "line",
201
+ linePoints: {
202
+ start: vertices[0],
203
+ end: {
204
+ x: 200,
205
+ y: 0
206
+ }
207
+ }
208
+ });
209
+ expect(changed.updated.contents).toBe("6 m");
210
+ expect(changed.appearance.changed).toBe(true);
211
+ const hidden = await page.annotations.update(created.ref, {
212
+ subtype: "line",
213
+ caption: { enabled: false }
214
+ });
215
+ expect(hidden.updated).toMatchObject({
216
+ contents: "6 m",
217
+ caption: {
218
+ enabled: false,
219
+ offset: {
220
+ along: 10,
221
+ perpendicular: 20
222
+ }
223
+ }
224
+ });
225
+ expect(hidden.appearance.changed).toBe(true);
226
+ const reset = await page.annotations.update(created.ref, {
227
+ subtype: "line",
228
+ caption: {
229
+ enabled: true,
230
+ offset: null
231
+ },
232
+ measure: null
233
+ });
234
+ expect(reset.updated.contents).toBe("6 m");
235
+ expect(reset.updated.subtype === "line" && reset.updated.caption?.offset === void 0).toBe(true);
236
+ expect(measurementReadout(reset.updated)).toEqual({ unavailable: "no-measure" });
237
+ } finally {
238
+ await doc.close();
239
+ }
240
+ });
241
+ test("all three kinds derive labels, save, reopen and retain captions", async () => {
242
+ let doc = await open();
243
+ try {
244
+ const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
245
+ const drafts = [
246
+ {
247
+ subtype: "line",
248
+ rect,
249
+ intent: "LineDimension",
250
+ measure: measureFromKnownLength(1, {
251
+ value: 1.00000001,
252
+ unit: "m"
253
+ }),
254
+ contents: "wrong",
255
+ linePoints: {
256
+ start: vertices[0],
257
+ end: {
258
+ x: 3.4450000001,
259
+ y: 0
260
+ }
261
+ },
262
+ caption: {
263
+ enabled: true,
264
+ offset: {
265
+ along: 10,
266
+ perpendicular: 20
267
+ }
268
+ }
269
+ },
270
+ {
271
+ subtype: "polyline",
272
+ rect,
273
+ intent: "PolyLineDimension",
274
+ measure: scale,
275
+ contents: "wrong",
276
+ vertices,
277
+ caption: {
278
+ enabled: true,
279
+ center: {
280
+ x: 0,
281
+ y: 0
282
+ }
283
+ }
284
+ },
285
+ {
286
+ subtype: "polygon",
287
+ rect,
288
+ intent: "PolygonDimension",
289
+ measure: scale,
290
+ contents: "wrong",
291
+ vertices,
292
+ caption: {
293
+ enabled: true,
294
+ center: {
295
+ x: 50,
296
+ y: 30
297
+ }
298
+ }
299
+ }
300
+ ];
301
+ const saved = [];
302
+ for (const draft of drafts) {
303
+ const preview = measurementReadout(draft);
304
+ const a = (await doc.page(pon).annotations.create(draft)).created;
305
+ expect(a.contents).toBe("label" in preview ? preview.label : void 0);
306
+ saved.push(a);
307
+ }
308
+ const bytes = opts.openKind === "bytes" ? await doc.download() : void 0;
309
+ await doc.close();
310
+ doc = bytes ? await engine.open({
311
+ kind: "bytes",
312
+ id: `${opts.fixture.id}-saved`,
313
+ bytes
314
+ }) : await open();
315
+ const page = doc.page((await doc.pages.list()).pages[0].pageObjectNumber);
316
+ const list = (await page.annotations.list()).annotations;
317
+ for (const before of saved) {
318
+ const a = list.find((a) => refKey(a.ref) === refKey(before.ref));
319
+ expect(a.contents).toBe(before.contents);
320
+ if (a.subtype !== "line" && a.subtype !== "polygon" && a.subtype !== "polyline") throw new Error("Missing dimension");
321
+ expect(a.caption).toEqual("caption" in before ? before.caption : void 0);
322
+ const result = await page.annotations.update(a.ref, {
323
+ subtype: a.subtype,
324
+ color: {
325
+ r: 0,
326
+ g: 0,
327
+ b: 255
328
+ }
329
+ });
330
+ expect(result.updated.contents).toBe(before.contents);
331
+ }
332
+ } finally {
333
+ await doc.close();
334
+ }
335
+ });
336
+ test("calibration updates only viewports and emits one event per write", async () => {
337
+ const doc = await open();
338
+ try {
339
+ const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
340
+ const page = doc.page(pon);
341
+ if (!page.measure) throw new Error("Measurement service is required");
342
+ const foreign = (await page.measure.viewports()).filter((v) => !v.owned);
343
+ const annotations = (await page.annotations.list()).annotations;
344
+ const events = [];
345
+ const off = doc.events.subscribe((event) => events.push(event));
346
+ try {
347
+ await page.measure.setScale(scale);
348
+ expect((await page.measure.viewports()).filter((v) => v.owned)).toHaveLength(1);
349
+ expect((await page.annotations.list()).annotations).toEqual(annotations);
350
+ await page.measure.setScale(null);
351
+ expect(await page.measure.viewports()).toEqual(foreign);
352
+ const changes = events.filter((e) => e.type === "page.viewportsChanged");
353
+ expect(changes).toHaveLength(2);
354
+ for (const event of changes) expect(event).toMatchObject({
355
+ pageObjectNumber: pon,
356
+ meta: { affectedPages: [] }
357
+ });
358
+ } finally {
359
+ off();
360
+ }
361
+ } finally {
362
+ await doc.close();
363
+ }
364
+ });
365
+ test("invalid and immediately aborted calibration leaves the previous scale intact", async () => {
366
+ const doc = await open();
367
+ try {
368
+ const page = doc.page((await doc.pages.list()).pages[0].pageObjectNumber);
369
+ await page.measure.setScale(scale);
370
+ const before = await page.measure.viewports();
371
+ await expect(page.measure.setScale({
372
+ ...scale,
373
+ x: [{
374
+ unit: "m",
375
+ conversion: 0
376
+ }]
377
+ })).rejects.toMatchObject({ code: EngineErrorCode.InvalidArg });
378
+ const pending = page.measure.setScale(null);
379
+ pending.abort("conformance");
380
+ await expect(pending).rejects.toBeInstanceOf(AbortError);
381
+ expect(await page.measure.viewports()).toEqual(before);
382
+ } finally {
383
+ await doc.close();
384
+ }
385
+ });
386
+ });
387
+ }
388
+ //#endregion
125
389
  //#region src/conformance/runAnnotationReadConformance.ts
126
390
  function runAnnotationReadConformance(runner, opts) {
127
391
  const { describe, test, beforeAll, afterAll, expect } = runner;
@@ -388,7 +652,7 @@ function runActionsConformance(runner, opts) {
388
652
  if (engine) await engine.destroy();
389
653
  });
390
654
  test("reads catalog additional actions and preserves /CO identity", async () => {
391
- const doc = await open(engine, opts, opts.fixtures.document);
655
+ const doc = await open$1(engine, opts, opts.fixtures.document);
392
656
  try {
393
657
  expect(Boolean(doc.actions)).toBe(true);
394
658
  const snapshot = await doc.actions.read();
@@ -409,7 +673,7 @@ function runActionsConformance(runner, opts) {
409
673
  }
410
674
  });
411
675
  test("attaches page /AA to PageLayout", async () => {
412
- const doc = await open(engine, opts, opts.fixtures.page);
676
+ const doc = await open$1(engine, opts, opts.fixtures.page);
413
677
  try {
414
678
  const pages = await doc.pages.list();
415
679
  expect(pages.pages[0].actions?.open?.root?.type).toBe("goto-embedded");
@@ -421,7 +685,7 @@ function runActionsConformance(runner, opts) {
421
685
  }
422
686
  });
423
687
  test("attaches activation actions to AnnotationBase, including links", async () => {
424
- const doc = await open(engine, opts, opts.fixtures.annotation);
688
+ const doc = await open$1(engine, opts, opts.fixtures.annotation);
425
689
  try {
426
690
  const firstPage = (await doc.pages.list()).pages[0];
427
691
  const snapshot = await doc.page(firstPage.pageObjectNumber).annotations.list();
@@ -438,7 +702,7 @@ function runActionsConformance(runner, opts) {
438
702
  test("classifies a /S /JavaScript link target, script staying on base.actions", async () => {
439
703
  const fixture = opts.fixtures.javascriptLink;
440
704
  if (!fixture) return;
441
- const doc = await open(engine, opts, fixture);
705
+ const doc = await open$1(engine, opts, fixture);
442
706
  try {
443
707
  const firstPage = (await doc.pages.list()).pages[0];
444
708
  const link = (await doc.page(firstPage.pageObjectNumber).annotations.list()).annotations.find((annotation) => annotation.subtype === "link");
@@ -451,7 +715,7 @@ function runActionsConformance(runner, opts) {
451
715
  }
452
716
  });
453
717
  test("keeps field actions separate from merged widget events", async () => {
454
- const doc = await open(engine, opts, opts.fixtures.field);
718
+ const doc = await open$1(engine, opts, opts.fixtures.field);
455
719
  try {
456
720
  const form = await doc.forms.list();
457
721
  expect(form.fields).toHaveLength(1);
@@ -465,7 +729,7 @@ function runActionsConformance(runner, opts) {
465
729
  }
466
730
  });
467
731
  test("carries interpreter payloads on every executable node", async () => {
468
- const doc = await open(engine, opts, opts.fixtures.payloads);
732
+ const doc = await open$1(engine, opts, opts.fixtures.payloads);
469
733
  try {
470
734
  const page = (await doc.pages.list()).pages[0];
471
735
  const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
@@ -626,7 +890,7 @@ function runActionsConformance(runner, opts) {
626
890
  }
627
891
  });
628
892
  test("degrades unreadable payloads to unknown with payload-dropped", async () => {
629
- const doc = await open(engine, opts, opts.fixtures.payloads);
893
+ const doc = await open$1(engine, opts, opts.fixtures.payloads);
630
894
  try {
631
895
  const page = (await doc.pages.list()).pages[0];
632
896
  const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
@@ -650,7 +914,7 @@ function runActionsConformance(runner, opts) {
650
914
  }
651
915
  });
652
916
  test("reads a destination-form /OpenAction as openDestination", async () => {
653
- const doc = await open(engine, opts, opts.fixtures.openDestination);
917
+ const doc = await open$1(engine, opts, opts.fixtures.openDestination);
654
918
  try {
655
919
  expect(Boolean(doc.actions)).toBe(true);
656
920
  const snapshot = await doc.actions.read();
@@ -670,7 +934,7 @@ function runActionsConformance(runner, opts) {
670
934
  });
671
935
  });
672
936
  }
673
- async function open(engine, opts, fixture) {
937
+ async function open$1(engine, opts, fixture) {
674
938
  if (opts.openKind === "bytes") return engine.open({
675
939
  kind: "bytes",
676
940
  id: fixture.id,
@@ -1518,6 +1782,287 @@ function runAnnotationMutationConformance(runner, opts) {
1518
1782
  await doc.close();
1519
1783
  }
1520
1784
  });
1785
+ test("rich text: a plain draft reads back body-style paragraphs and a body from its font", async () => {
1786
+ const doc = await openFixture$18(engine, opts);
1787
+ try {
1788
+ const created = await doc.page(fix.pageObjectNumber).annotations.create({
1789
+ subtype: "free-text",
1790
+ intent: "free-text",
1791
+ contents: "Plain\rtext",
1792
+ rect: shapeRect,
1793
+ fontFamily: "helvetica-bold",
1794
+ fontSize: 18,
1795
+ textAlign: "center",
1796
+ color: {
1797
+ r: 0,
1798
+ g: 0,
1799
+ b: 0
1800
+ }
1801
+ });
1802
+ expect(created.created.subtype).toBe("free-text");
1803
+ if (created.created.subtype === "free-text") {
1804
+ const rich = created.created.richText;
1805
+ expect(created.created.fontFamily).toBe("helvetica-bold");
1806
+ expect(rich.body.family).toBe("Helvetica");
1807
+ expect(rich.body.weight).toBe(700);
1808
+ expect(rich.body.italic).toBe(false);
1809
+ expect(rich.body.size).toBe(18);
1810
+ expect(rich.body.align).toBe("center");
1811
+ expect(rich.paragraphs).toEqual([{ runs: [{ text: "Plain" }] }, { runs: [{ text: "text" }] }]);
1812
+ }
1813
+ } finally {
1814
+ await doc.close();
1815
+ }
1816
+ });
1817
+ test("rich text: a draft with runs round-trips the body, the runs and the plain projection", async () => {
1818
+ const doc = await openFixture$18(engine, opts);
1819
+ try {
1820
+ const page = doc.page(fix.pageObjectNumber);
1821
+ const created = await page.annotations.create({
1822
+ subtype: "free-text",
1823
+ intent: "free-text",
1824
+ rect: shapeRect,
1825
+ fontFamily: "helvetica",
1826
+ fontSize: 12,
1827
+ textAlign: "left",
1828
+ color: {
1829
+ r: 0,
1830
+ g: 0,
1831
+ b: 255
1832
+ },
1833
+ richText: {
1834
+ body: {
1835
+ family: "Helvetica",
1836
+ size: 18,
1837
+ color: "#102030"
1838
+ },
1839
+ paragraphs: [{ runs: [
1840
+ { text: "Hello " },
1841
+ {
1842
+ text: "bold",
1843
+ style: { weight: 700 }
1844
+ },
1845
+ {
1846
+ text: " red",
1847
+ style: { color: "#FF0000" }
1848
+ }
1849
+ ] }, {
1850
+ align: "center",
1851
+ runs: [{ text: "H" }, {
1852
+ text: "2",
1853
+ style: { script: "sub" }
1854
+ }]
1855
+ }]
1856
+ }
1857
+ });
1858
+ expect(AnnotationCreateResultSchema.safeParse(created).success).toBe(true);
1859
+ expect(created.created.subtype).toBe("free-text");
1860
+ if (created.created.subtype === "free-text") {
1861
+ const dto = created.created;
1862
+ expect(dto.contents).toBe("Hello bold red\rH2");
1863
+ expect(dto.fontFamily).toBe("helvetica");
1864
+ expect(dto.fontSize).toBe(18);
1865
+ expect(dto.color).toMatchObject({
1866
+ r: 0,
1867
+ g: 0,
1868
+ b: 255
1869
+ });
1870
+ expect(dto.richText.body.color).toBe("#102030");
1871
+ expect(dto.richText.paragraphs[0].runs).toEqual([
1872
+ { text: "Hello " },
1873
+ {
1874
+ text: "bold",
1875
+ style: { weight: 700 }
1876
+ },
1877
+ {
1878
+ text: " red",
1879
+ style: { color: "#FF0000" }
1880
+ }
1881
+ ]);
1882
+ expect(dto.richText.paragraphs[1].align).toBe("center");
1883
+ expect(dto.richText.paragraphs[1].runs[1]).toEqual({
1884
+ text: "2",
1885
+ style: { script: "sub" }
1886
+ });
1887
+ }
1888
+ const listed = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
1889
+ expect(listed?.subtype).toBe("free-text");
1890
+ if (listed?.subtype === "free-text") expect(listed.richText.paragraphs[0].runs[1]).toEqual({
1891
+ text: "bold",
1892
+ style: { weight: 700 }
1893
+ });
1894
+ } finally {
1895
+ await doc.close();
1896
+ }
1897
+ });
1898
+ test("rich text: paragraphs-only patch keeps the body; contents-only rewrites body-style paragraphs", async () => {
1899
+ const doc = await openFixture$18(engine, opts);
1900
+ try {
1901
+ const page = doc.page(fix.pageObjectNumber);
1902
+ const ref = (await page.annotations.create({
1903
+ subtype: "free-text",
1904
+ intent: "free-text",
1905
+ rect: shapeRect,
1906
+ fontFamily: "helvetica",
1907
+ fontSize: 14,
1908
+ textAlign: "center",
1909
+ color: {
1910
+ r: 0,
1911
+ g: 0,
1912
+ b: 0
1913
+ },
1914
+ richText: {
1915
+ body: {
1916
+ family: "Helvetica",
1917
+ size: 14
1918
+ },
1919
+ paragraphs: [{ runs: [{ text: "a" }, {
1920
+ text: "b",
1921
+ style: { weight: 700 }
1922
+ }] }]
1923
+ }
1924
+ })).created.ref;
1925
+ const restyled = await page.annotations.update(ref, {
1926
+ subtype: "free-text",
1927
+ richText: { paragraphs: [{ runs: [{
1928
+ text: "ab",
1929
+ style: { italic: true }
1930
+ }, { text: "c" }] }] }
1931
+ });
1932
+ expect(AnnotationUpdateResultSchema.safeParse(restyled).success).toBe(true);
1933
+ expect(restyled.updated.subtype).toBe("free-text");
1934
+ if (restyled.updated.subtype === "free-text") {
1935
+ expect(restyled.updated.contents).toBe("abc");
1936
+ expect(restyled.updated.richText.body.size).toBe(14);
1937
+ expect(restyled.updated.richText.body.align).toBe("center");
1938
+ expect(restyled.updated.textAlign).toBe("center");
1939
+ expect(restyled.updated.richText.paragraphs[0].runs[0]).toEqual({
1940
+ text: "ab",
1941
+ style: { italic: true }
1942
+ });
1943
+ }
1944
+ const rewritten = await page.annotations.update(ref, {
1945
+ subtype: "free-text",
1946
+ contents: "one\rtwo"
1947
+ });
1948
+ expect(rewritten.updated.subtype).toBe("free-text");
1949
+ if (rewritten.updated.subtype === "free-text") {
1950
+ expect(rewritten.updated.contents).toBe("one\rtwo");
1951
+ expect(rewritten.updated.richText.paragraphs).toEqual([{ runs: [{ text: "one" }] }, { runs: [{ text: "two" }] }]);
1952
+ expect(rewritten.updated.richText.body.size).toBe(14);
1953
+ }
1954
+ expect(rewritten.meta.weakRefsInvalidated).toBe(false);
1955
+ } finally {
1956
+ await doc.close();
1957
+ }
1958
+ });
1959
+ test("rich text: fontSize / fontColor / fontFamily move the body while runs keep their deltas", async () => {
1960
+ const doc = await openFixture$18(engine, opts);
1961
+ try {
1962
+ const page = doc.page(fix.pageObjectNumber);
1963
+ const created = await page.annotations.create({
1964
+ subtype: "free-text",
1965
+ intent: "free-text",
1966
+ rect: shapeRect,
1967
+ fontFamily: "helvetica",
1968
+ fontSize: 14,
1969
+ textAlign: "left",
1970
+ color: {
1971
+ r: 0,
1972
+ g: 0,
1973
+ b: 0
1974
+ },
1975
+ richText: {
1976
+ body: {
1977
+ family: "Helvetica",
1978
+ size: 14
1979
+ },
1980
+ paragraphs: [{ runs: [{ text: "a" }, {
1981
+ text: "b",
1982
+ style: { size: 30 }
1983
+ }] }]
1984
+ }
1985
+ });
1986
+ const updated = await page.annotations.update(created.created.ref, {
1987
+ subtype: "free-text",
1988
+ fontSize: 20,
1989
+ fontColor: {
1990
+ r: 255,
1991
+ g: 0,
1992
+ b: 0
1993
+ },
1994
+ fontFamily: "times-bold"
1995
+ });
1996
+ expect(updated.updated.subtype).toBe("free-text");
1997
+ if (updated.updated.subtype === "free-text") {
1998
+ const dto = updated.updated;
1999
+ expect(dto.fontSize).toBe(20);
2000
+ expect(dto.fontFamily).toBe("times-bold");
2001
+ expect(dto.richText.body.size).toBe(20);
2002
+ expect(dto.richText.body.color).toBe("#FF0000");
2003
+ expect(dto.richText.body.family).toBe("Times");
2004
+ expect(dto.richText.body.weight).toBe(700);
2005
+ expect(dto.richText.paragraphs[0].runs[1]).toEqual({
2006
+ text: "b",
2007
+ style: { size: 30 }
2008
+ });
2009
+ }
2010
+ } finally {
2011
+ await doc.close();
2012
+ }
2013
+ });
2014
+ test("rich text: contents and richText that disagree reject with InvalidArg; agreeing writes the runs", async () => {
2015
+ const doc = await openFixture$18(engine, opts);
2016
+ try {
2017
+ const page = doc.page(fix.pageObjectNumber);
2018
+ const created = await page.annotations.create({
2019
+ subtype: "free-text",
2020
+ intent: "free-text",
2021
+ contents: "x",
2022
+ rect: shapeRect,
2023
+ fontFamily: "helvetica",
2024
+ fontSize: 14,
2025
+ textAlign: "left",
2026
+ color: {
2027
+ r: 0,
2028
+ g: 0,
2029
+ b: 0
2030
+ }
2031
+ });
2032
+ const ref = created.created.ref;
2033
+ let caught;
2034
+ try {
2035
+ await page.annotations.update(ref, {
2036
+ subtype: "free-text",
2037
+ contents: "stale",
2038
+ richText: { paragraphs: [{ runs: [{ text: "fresh" }] }] }
2039
+ });
2040
+ } catch (err) {
2041
+ caught = err;
2042
+ }
2043
+ expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
2044
+ const after = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
2045
+ expect(after?.contents).toBe("x");
2046
+ const agreed = await page.annotations.update(ref, {
2047
+ subtype: "free-text",
2048
+ contents: "fresh",
2049
+ richText: { paragraphs: [{ runs: [{ text: "fr" }, {
2050
+ text: "esh",
2051
+ style: { weight: 700 }
2052
+ }] }] }
2053
+ });
2054
+ expect(agreed.updated.subtype).toBe("free-text");
2055
+ if (agreed.updated.subtype === "free-text") {
2056
+ expect(agreed.updated.contents).toBe("fresh");
2057
+ expect(agreed.updated.richText.paragraphs[0].runs[1]).toEqual({
2058
+ text: "esh",
2059
+ style: { weight: 700 }
2060
+ });
2061
+ }
2062
+ } finally {
2063
+ await doc.close();
2064
+ }
2065
+ });
1521
2066
  test("create redact (area + text) round-trips label + colour fields", async () => {
1522
2067
  const doc = await openFixture$18(engine, opts);
1523
2068
  try {
@@ -6535,20 +7080,42 @@ function runSearchConformance(runner, opts) {
6535
7080
  await doc.close();
6536
7081
  }
6537
7082
  });
6538
- test("regex + matchDiacritics is rejected with InvalidArg", async () => {
7083
+ test("ignoreWhitespace keeps every default hit and finds the space-free needle", async () => {
6539
7084
  const doc = await openFixture(engine, opts);
6540
7085
  try {
6541
- let caught;
6542
- try {
6543
- await doc.search.query({ query: {
6544
- text: fixture.presentRegex,
6545
- regex: true,
6546
- matchDiacritics: true
6547
- } });
6548
- } catch (err) {
6549
- caught = err;
7086
+ const key = (m) => `${m.pageObjectNumber}:${m.charStart}:${m.charCount}`;
7087
+ const plain = await collectAll(doc, { query: { text: fixture.presentLiteral } });
7088
+ const relaxed = await collectAll(doc, { query: {
7089
+ text: fixture.presentLiteral,
7090
+ ignoreWhitespace: true
7091
+ } });
7092
+ const relaxedKeys = new Set(relaxed.matches.map(key));
7093
+ for (const m of plain.matches) expect(relaxedKeys.has(key(m))).toBe(true);
7094
+ const squashed = await collectAll(doc, { query: {
7095
+ text: fixture.presentLiteral.replace(/\s+/g, ""),
7096
+ ignoreWhitespace: true
7097
+ } });
7098
+ expect(squashed.matches.map(key)).toEqual(relaxed.matches.map(key));
7099
+ } finally {
7100
+ await doc.close();
7101
+ }
7102
+ });
7103
+ test("regex + matchDiacritics / ignoreWhitespace are rejected with InvalidArg", async () => {
7104
+ const doc = await openFixture(engine, opts);
7105
+ try {
7106
+ for (const flags of [{ matchDiacritics: true }, { ignoreWhitespace: true }]) {
7107
+ let caught;
7108
+ try {
7109
+ await doc.search.query({ query: {
7110
+ text: fixture.presentRegex,
7111
+ regex: true,
7112
+ ...flags
7113
+ } });
7114
+ } catch (err) {
7115
+ caught = err;
7116
+ }
7117
+ expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
6550
7118
  }
6551
- expect(EngineError.is(caught, EngineErrorCode.InvalidArg)).toBe(true);
6552
7119
  } finally {
6553
7120
  await doc.close();
6554
7121
  }
@@ -6674,6 +7241,790 @@ function diffAnnotation(i, a, b, errs) {
6674
7241
  if (a.nm !== b.nm) errs.push(`annotations[${i}].nm mismatch: ${a.nm} vs ${b.nm}`);
6675
7242
  }
6676
7243
  //#endregion
6677
- export { TEXT_DIVERGENCE_CASES, diffAnnotationListSnapshot, diffAnnotationListSnapshotAll, runActionsConformance, runAnnotationAppearanceConformance, runAnnotationAppearanceExportConformance, runAnnotationFlattenConformance, runAnnotationMutationConformance, runAnnotationReadConformance, runAttachmentConformance, runDocumentEventsConformance, runFormConformance, runMetadataConformance, runNamedPagesConformance, runPageDeleteConformance, runPageExtractConformance, runPageFlattenConformance, runPageGeometryOrientationConformance, runPageInsertBlankConformance, runPageInsertConformance, runPageReorderConformance, runPageRotateConformance, runPageTextConformance, runPieceInfoConformance, runRedactionApplyConformance, runSearchConformance, runTextDivergenceConformance };
7244
+ //#region src/conformance/runSignatureConformance.ts
7245
+ /**
7246
+ * Digital signatures, read side. The service is OPTIONAL on the contract
7247
+ * (`downloadLayer?` pattern) — the suite runs only where the engine
7248
+ * provides it.
7249
+ *
7250
+ * Invariants:
7251
+ * 1. Byte facts describe the LOADED bytes: a revision is a byte prefix,
7252
+ * a whole-revision signature seals exactly one, the digest over its
7253
+ * `/ByteRange` is what the file's bytes hash to, and an unsaved edit
7254
+ * changes none of it.
7255
+ * 2. Protection is derived from the signatures in the file and applied
7256
+ * as document-derived capabilities: a certification with permission 2
7257
+ * takes page assembly and annotation writes away, a FieldMDP freezes
7258
+ * the fields it names, a rewrite save is refused. Under
7259
+ * `signedDocumentPolicy: 'permit'` none of that applies and the
7260
+ * analysis is unchanged.
7261
+ * 3. The same answers come from a plain session and from a layer
7262
+ * session over the same bytes.
7263
+ */
7264
+ function runSignatureConformance(runner, opts) {
7265
+ const { describe, test, beforeAll, afterAll, expect } = runner;
7266
+ describe(`signatures conformance: ${opts.label}`, () => {
7267
+ let engine;
7268
+ let permitEngine = null;
7269
+ let supported = false;
7270
+ beforeAll(async () => {
7271
+ engine = await opts.makeEngine();
7272
+ const probe = await open(engine, opts, opts.fixtures.unsignedForm);
7273
+ supported = probe.signatures !== void 0;
7274
+ await probe.close();
7275
+ if (supported && opts.makePermitEngine) permitEngine = await opts.makePermitEngine();
7276
+ });
7277
+ afterAll(async () => {
7278
+ if (permitEngine) await permitEngine.destroy();
7279
+ if (engine) await engine.destroy();
7280
+ });
7281
+ test("an unsigned form has no signatures and no protection; version() hashes the loaded bytes", async () => {
7282
+ if (!supported) return;
7283
+ const bytes = await opts.fixtures.unsignedForm.bytes();
7284
+ const doc = await open(engine, opts, opts.fixtures.unsignedForm);
7285
+ try {
7286
+ const snapshot = await doc.signatures.list();
7287
+ expect(snapshot.chainValid).toBe(true);
7288
+ expect(snapshot.signatures).toEqual([]);
7289
+ expect(snapshot.revisions.length >= 1).toBe(true);
7290
+ expect(snapshot.protection.judged).toBeNull();
7291
+ expect(snapshot.protection.enforced).toBeNull();
7292
+ expect(snapshot.protection.certification).toBeNull();
7293
+ expect(snapshot.protection.fieldLocks).toEqual([]);
7294
+ expect(doc.security.allows("doc.pages.assemble")).toBe(true);
7295
+ if (doc.version) {
7296
+ const version = await doc.version();
7297
+ expect(version.byteLength).toBe(bytes.byteLength);
7298
+ expect(version.sha256).toBe(await sha256Hex(bytes));
7299
+ }
7300
+ let caught;
7301
+ try {
7302
+ await doc.signatures.contents({
7303
+ kind: "fqn",
7304
+ name: "no-such-field"
7305
+ });
7306
+ } catch (err) {
7307
+ caught = err;
7308
+ }
7309
+ expect(EngineError.is(caught, EngineErrorCode.NotFound)).toBe(true);
7310
+ } finally {
7311
+ await doc.close();
7312
+ }
7313
+ });
7314
+ test("two approval signatures: revisions, coverage, digests, contents, revision bytes", async () => {
7315
+ if (!supported) return;
7316
+ const fixture = opts.fixtures.twoApprovals;
7317
+ const bytes = await fixture.bytes();
7318
+ const doc = await open(engine, opts, fixture);
7319
+ try {
7320
+ const snapshot = await doc.signatures.list();
7321
+ expect(snapshot.chainValid).toBe(true);
7322
+ expect(snapshot.revisions).toHaveLength(3);
7323
+ expect(snapshot.signatures).toHaveLength(2);
7324
+ expect(snapshot.revisions[2].end).toBe(bytes.byteLength);
7325
+ for (const [i, sig] of snapshot.signatures.entries()) {
7326
+ expect(sig.index).toBe(i);
7327
+ expect(sig.signed).toBe(true);
7328
+ expect(sig.kind).toBe("signature");
7329
+ expect(sig.field.kind).toBe("objectNumber");
7330
+ expect(sig.fieldName).toBe(fixture.fieldNames[i]);
7331
+ expect(sig.coverage).toBe("whole-revision");
7332
+ expect(sig.revisionIndex).toBe(i + 1);
7333
+ expect(snapshot.revisions[i + 1].signatureIndex).toBe(i);
7334
+ expect(sig.subFilter).toBe("ETSI.CAdES.detached");
7335
+ expect(typeof sig.signer.claimedTime).toBe("string");
7336
+ expect(sig.docMdp).toBeNull();
7337
+ expect(sig.catalogCertification).toBe(false);
7338
+ expect(sig.contentsSize > 0).toBe(true);
7339
+ expect(sig.byteRange[0]).toBe(0);
7340
+ expect(sig.byteRange[2] + sig.byteRange[3]).toBe(snapshot.revisions[i + 1].end);
7341
+ }
7342
+ expect(snapshot.protection.judged).toBe("annotate");
7343
+ expect(snapshot.protection.enforced).toBeNull();
7344
+ expect(snapshot.protection.certification).toBeNull();
7345
+ expect(snapshot.protection.fieldLocks).toEqual([]);
7346
+ const first = snapshot.signatures[0];
7347
+ const contents = await doc.signatures.contents(first.field);
7348
+ expect(contents.byteLength).toBe(first.contentsSize);
7349
+ expect(contents[0]).toBe(48);
7350
+ const [a, b, c, d] = first.byteRange;
7351
+ const sealed = await doc.signatures.revisionBytes(1);
7352
+ expect(sealed.byteLength).toBe(snapshot.revisions[1].end);
7353
+ expect(bytesEqual(sealed, bytes.subarray(0, sealed.byteLength))).toBe(true);
7354
+ const signedBytes = concat(sealed.subarray(a, a + b), sealed.subarray(c, c + d));
7355
+ const digest = await doc.signatures.digest(first.field, "sha256");
7356
+ expect(digest.byteLength).toBe(32);
7357
+ expect(toHex(digest)).toBe(await sha256Hex(signedBytes));
7358
+ expect((await doc.signatures.digest(first.field, "sha512")).byteLength).toBe(64);
7359
+ const byName = await doc.signatures.digest({
7360
+ kind: "fqn",
7361
+ name: first.fieldName
7362
+ }, "sha256");
7363
+ expect(toHex(byName)).toBe(toHex(digest));
7364
+ const text = (await doc.forms.list()).fields.find((f) => f.name === fixture.textField);
7365
+ expect(text).toBeTruthy();
7366
+ await doc.forms.setValue({
7367
+ kind: "objectNumber",
7368
+ fieldObjectNumber: text.fieldObjectNumber
7369
+ }, {
7370
+ type: "text",
7371
+ value: "unsaved"
7372
+ });
7373
+ const again = await doc.signatures.list();
7374
+ expect(again.revisions).toEqual(snapshot.revisions);
7375
+ expect(again.signatures.map((s) => [
7376
+ s.coverage,
7377
+ s.revisionIndex,
7378
+ s.byteRange
7379
+ ])).toEqual(snapshot.signatures.map((s) => [
7380
+ s.coverage,
7381
+ s.revisionIndex,
7382
+ s.byteRange
7383
+ ]));
7384
+ expect(toHex(await doc.signatures.digest(first.field, "sha256"))).toBe(toHex(digest));
7385
+ if (doc.version) expect((await doc.version()).sha256).toBe(await sha256Hex(bytes));
7386
+ } finally {
7387
+ await doc.close();
7388
+ }
7389
+ });
7390
+ test("a certification takes structural capabilities away; permit lifts it", async () => {
7391
+ if (!supported) return;
7392
+ const fixture = opts.fixtures.certified;
7393
+ const doc = await open(engine, opts, fixture);
7394
+ try {
7395
+ const snapshot = await doc.signatures.list();
7396
+ const signed = snapshot.signatures.filter((s) => s.signed);
7397
+ expect(signed).toHaveLength(1);
7398
+ const sig = signed[0];
7399
+ expect(sig.fieldName).toBe(fixture.fieldName);
7400
+ expect(sig.coverage).toBe("whole-revision");
7401
+ expect(sig.docMdp).toBe(2);
7402
+ expect(sig.catalogCertification).toBe(true);
7403
+ expect(snapshot.protection.judged).toBe("fill");
7404
+ expect(snapshot.protection.enforced).toBe("fill");
7405
+ expect(snapshot.protection.certification).toEqual({
7406
+ signatureIndex: sig.index,
7407
+ permission: 2
7408
+ });
7409
+ expect(doc.security.allows("doc.forms.read")).toBe(true);
7410
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7411
+ expect(doc.security.allows("doc.annotate.modify")).toBe(false);
7412
+ expect(doc.security.allows("doc.pages.assemble")).toBe(false);
7413
+ expect(doc.security.allows("doc.redact")).toBe(false);
7414
+ const list = await doc.pages.list();
7415
+ let caught;
7416
+ try {
7417
+ await doc.pages.delete([list.pages[0].pageObjectNumber]);
7418
+ } catch (err) {
7419
+ caught = err;
7420
+ }
7421
+ expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
7422
+ caught = void 0;
7423
+ try {
7424
+ await doc.download({ mode: "rewrite" });
7425
+ } catch (err) {
7426
+ caught = err;
7427
+ }
7428
+ expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
7429
+ const saved = await doc.download();
7430
+ expect(saved.byteLength >= snapshot.revisions[1].end).toBe(true);
7431
+ } finally {
7432
+ await doc.close();
7433
+ }
7434
+ if (!permitEngine) return;
7435
+ const permitted = await open(permitEngine, opts, fixture);
7436
+ try {
7437
+ const snapshot = await permitted.signatures.list();
7438
+ expect(snapshot.protection.judged).toBe("fill");
7439
+ expect(snapshot.protection.enforced).toBe("fill");
7440
+ expect(permitted.security.allows("doc.pages.assemble")).toBe(true);
7441
+ const rewritten = await permitted.download({ mode: "rewrite" });
7442
+ expect(rewritten.byteLength > 0).toBe(true);
7443
+ } finally {
7444
+ await permitted.close();
7445
+ }
7446
+ });
7447
+ test("a FieldMDP freezes the fields it names; permit lifts it", async () => {
7448
+ if (!supported) return;
7449
+ const fixture = opts.fixtures.fieldMdp;
7450
+ const doc = await open(engine, opts, fixture);
7451
+ const lockedRef = {
7452
+ kind: "fqn",
7453
+ name: fixture.lockedField
7454
+ };
7455
+ try {
7456
+ const snapshot = await doc.signatures.list();
7457
+ const sig = snapshot.signatures[0];
7458
+ expect(sig.fieldName).toBe(fixture.fieldName);
7459
+ expect(sig.fieldMdp).toEqual({
7460
+ action: "include",
7461
+ fields: [fixture.lockedField]
7462
+ });
7463
+ expect(snapshot.protection.judged).toBe("annotate");
7464
+ expect(snapshot.protection.enforced).toBeNull();
7465
+ expect(snapshot.protection.fieldLocks[0]).toEqual({
7466
+ signatureIndex: 0,
7467
+ source: "fieldmdp",
7468
+ spec: {
7469
+ action: "include",
7470
+ fields: [fixture.lockedField]
7471
+ }
7472
+ });
7473
+ for (const lock of snapshot.protection.fieldLocks) {
7474
+ expect(lock.signatureIndex).toBe(0);
7475
+ expect(lock.spec).toEqual({
7476
+ action: "include",
7477
+ fields: [fixture.lockedField]
7478
+ });
7479
+ }
7480
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7481
+ let caught;
7482
+ try {
7483
+ await doc.forms.setValue(lockedRef, {
7484
+ type: "text",
7485
+ value: "changed"
7486
+ });
7487
+ } catch (err) {
7488
+ caught = err;
7489
+ }
7490
+ expect(EngineError.is(caught, EngineErrorCode.ProtectedDocument)).toBe(true);
7491
+ const field = await doc.forms.get(lockedRef);
7492
+ expect(field.family).toBe("text");
7493
+ expect(field.value === "changed").toBe(false);
7494
+ } finally {
7495
+ await doc.close();
7496
+ }
7497
+ if (!permitEngine) return;
7498
+ const permitted = await open(permitEngine, opts, fixture);
7499
+ try {
7500
+ const result = await permitted.forms.setValue(lockedRef, {
7501
+ type: "text",
7502
+ value: "changed"
7503
+ });
7504
+ expect(result.field.name).toBe(fixture.lockedField);
7505
+ } finally {
7506
+ await permitted.close();
7507
+ }
7508
+ });
7509
+ test("well-formed ranges that seal no revision are partial, with no revision index", async () => {
7510
+ if (!supported) return;
7511
+ const doc = await open(engine, opts, opts.fixtures.partialChain);
7512
+ try {
7513
+ const snapshot = await doc.signatures.list();
7514
+ expect(snapshot.chainValid).toBe(true);
7515
+ expect(snapshot.revisions).toHaveLength(3);
7516
+ expect(snapshot.signatures).toHaveLength(2);
7517
+ for (const sig of snapshot.signatures) {
7518
+ expect(sig.signed).toBe(true);
7519
+ expect(sig.coverage).toBe("partial");
7520
+ expect(sig.revisionIndex).toBeNull();
7521
+ expect(sig.contentsSize).toBe(13);
7522
+ expect((await doc.signatures.contents(sig.field)).byteLength).toBe(13);
7523
+ expect((await doc.signatures.digest(sig.field, "sha256")).byteLength).toBe(32);
7524
+ }
7525
+ expect(snapshot.revisions.every((r) => r.signatureIndex === null)).toBe(true);
7526
+ expect(snapshot.protection.judged).toBe("annotate");
7527
+ expect(snapshot.protection.enforced).toBeNull();
7528
+ let caught;
7529
+ try {
7530
+ await doc.signatures.revisionBytes(3);
7531
+ } catch (err) {
7532
+ caught = err;
7533
+ }
7534
+ expect(EngineError.is(caught, EngineErrorCode.NotFound)).toBe(true);
7535
+ } finally {
7536
+ await doc.close();
7537
+ }
7538
+ });
7539
+ if (opts.openKind !== "id") {
7540
+ runSigningTests(runner, opts, () => engine, () => supported);
7541
+ runAnalysisTests(runner, opts, () => engine, () => permitEngine, () => supported);
7542
+ }
7543
+ });
7544
+ }
7545
+ function runAnalysisTests(runner, opts, engineOf, permitEngineOf, supported) {
7546
+ const { test, expect } = runner;
7547
+ test("analyze: a form fill and a second signature after an approval are permitted", async () => {
7548
+ if (!supported()) return;
7549
+ const doc = await open(engineOf(), opts, opts.fixtures.twoApprovals);
7550
+ try {
7551
+ const analysis = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
7552
+ expect(analysis.mode).toBe("authoritative");
7553
+ expect(analysis.basis.source).toBe("persisted");
7554
+ expect(analysis.since).toEqual({
7555
+ revisionIndex: 1,
7556
+ signatureIndex: 0
7557
+ });
7558
+ expect(analysis.until.revisionIndex).toBe(2);
7559
+ expect(analysis.steps).toHaveLength(1);
7560
+ const [step] = analysis.steps;
7561
+ expect(step.levelInForce).toBe("annotate");
7562
+ expect(step.verdict).toBe("permitted");
7563
+ expect(step.changes.length > 0).toBe(true);
7564
+ const rules = new Set(step.findings.filter((f) => f.verdict === "permitted").map((f) => f.rule));
7565
+ expect(rules.has("form-fill")).toBe(true);
7566
+ expect(rules.has("signature-added")).toBe(true);
7567
+ expect(step.findings.filter((f) => f.verdict === "forbidden")).toEqual([]);
7568
+ expect(analysis.verdict).toBe("permitted");
7569
+ const latest = await doc.signatures.analyze({ since: { signatureIndex: 1 } });
7570
+ expect(latest.steps).toHaveLength(0);
7571
+ expect(latest.verdict).toBe("unchanged");
7572
+ const historic = await doc.signatures.analyze({
7573
+ since: { revisionIndex: 0 },
7574
+ until: { revisionIndex: 1 }
7575
+ });
7576
+ expect(historic.steps).toHaveLength(1);
7577
+ expect(historic.verdict).toBe("permitted");
7578
+ } finally {
7579
+ await doc.close();
7580
+ }
7581
+ });
7582
+ test("analyze: the working copy is one more revision; a partial signature cannot anchor", async () => {
7583
+ if (!supported()) return;
7584
+ const fx = opts.fixtures.unsignedSigField;
7585
+ const doc = await open(engineOf(), opts, fx);
7586
+ try {
7587
+ const prepared = await doc.signatures.prepare({
7588
+ field: {
7589
+ kind: "fqn",
7590
+ name: fx.fieldName
7591
+ },
7592
+ certify: { permission: 2 }
7593
+ });
7594
+ await doc.signatures.complete({
7595
+ signingId: prepared.signingId,
7596
+ cms: FAKE_CMS,
7597
+ expectedVersion: prepared.expectedVersion
7598
+ });
7599
+ const clean = await doc.signatures.analyze({
7600
+ since: { signatureIndex: 0 },
7601
+ until: "working-copy"
7602
+ });
7603
+ expect(clean.steps).toHaveLength(0);
7604
+ expect(clean.verdict).toBe("unchanged");
7605
+ await doc.forms.setValue({
7606
+ kind: "fqn",
7607
+ name: fx.textField
7608
+ }, {
7609
+ type: "text",
7610
+ value: "draft"
7611
+ });
7612
+ const persisted = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
7613
+ expect(persisted.steps).toHaveLength(0);
7614
+ const working = await doc.signatures.analyze({
7615
+ since: { signatureIndex: 0 },
7616
+ until: "working-copy"
7617
+ });
7618
+ expect(working.basis.source).toBe("working-copy");
7619
+ expect(working.steps).toHaveLength(1);
7620
+ expect(working.steps[0].levelInForce).toBe("fill");
7621
+ expect(working.verdict).toBe("permitted");
7622
+ expect(working.steps[0].findings.some((f) => f.rule === "form-fill" && f.verdict === "permitted")).toBe(true);
7623
+ } finally {
7624
+ await doc.close();
7625
+ }
7626
+ const partial = await open(engineOf(), opts, opts.fixtures.partialChain);
7627
+ try {
7628
+ expect(await caughtCode(() => partial.signatures.analyze({ since: { signatureIndex: 0 } }))).toBe(EngineErrorCode.InvalidArg);
7629
+ const byRevision = await partial.signatures.analyze({ since: { revisionIndex: 0 } });
7630
+ expect(byRevision.later.revisionCount).toBe(2);
7631
+ expect(byRevision.steps).toHaveLength(0);
7632
+ const inFull = await partial.signatures.analyze({
7633
+ since: { revisionIndex: 0 },
7634
+ detail: "full"
7635
+ });
7636
+ expect(inFull.steps).toHaveLength(2);
7637
+ expect(inFull.current.verdict).toBe(byRevision.current.verdict);
7638
+ } finally {
7639
+ await partial.close();
7640
+ }
7641
+ });
7642
+ test("analyze: a page edit under a certification and a locked field edit are forbidden", async () => {
7643
+ if (!supported()) return;
7644
+ const permit = permitEngineOf();
7645
+ if (!permit) return;
7646
+ const certified = await open(permit, opts, opts.fixtures.certified);
7647
+ let reopened = null;
7648
+ try {
7649
+ const list = await certified.pages.list();
7650
+ await certified.pages.rotate([list.pages[0].pageObjectNumber], 90);
7651
+ const working = await certified.signatures.analyze({
7652
+ since: { signatureIndex: 0 },
7653
+ until: "working-copy"
7654
+ });
7655
+ expect(working.verdict).toBe("forbidden");
7656
+ const bytes = await certified.download();
7657
+ reopened = await reopen(permit, opts, `${opts.fixtures.certified.id}-tampered`, bytes);
7658
+ const analysis = await reopened.signatures.analyze({ since: { signatureIndex: 0 } });
7659
+ expect(analysis.verdict).toBe("forbidden");
7660
+ expect(analysis.steps[0].findings.some((f) => f.verdict === "forbidden")).toBe(true);
7661
+ } finally {
7662
+ if (reopened) await reopened.close();
7663
+ await certified.close();
7664
+ }
7665
+ const locked = await open(permit, opts, opts.fixtures.fieldMdp);
7666
+ try {
7667
+ await locked.forms.setValue({
7668
+ kind: "fqn",
7669
+ name: opts.fixtures.fieldMdp.lockedField
7670
+ }, {
7671
+ type: "text",
7672
+ value: "tampered"
7673
+ });
7674
+ const working = await locked.signatures.analyze({
7675
+ since: { signatureIndex: 0 },
7676
+ until: "working-copy"
7677
+ });
7678
+ expect(working.verdict).toBe("forbidden");
7679
+ expect(working.steps[0].findings.some((f) => f.rule === "field-lock")).toBe(true);
7680
+ } finally {
7681
+ await locked.close();
7682
+ }
7683
+ });
7684
+ }
7685
+ /** A minimal DER SEQUENCE standing in for a CMS: the engine's job ends at the bytes. */
7686
+ const FAKE_CMS = new Uint8Array([
7687
+ 48,
7688
+ 6,
7689
+ 2,
7690
+ 1,
7691
+ 1,
7692
+ 2,
7693
+ 1,
7694
+ 2
7695
+ ]);
7696
+ async function caughtCode(run) {
7697
+ try {
7698
+ await run();
7699
+ return null;
7700
+ } catch (err) {
7701
+ return err instanceof EngineError ? err.code : "not-an-engine-error";
7702
+ }
7703
+ }
7704
+ function runSigningTests(runner, opts, engineOf, supported) {
7705
+ const { test, expect } = runner;
7706
+ const fx = () => opts.fixtures.unsignedSigField;
7707
+ const sigRef = () => ({
7708
+ kind: "fqn",
7709
+ name: fx().fieldName
7710
+ });
7711
+ const textRef = () => ({
7712
+ kind: "fqn",
7713
+ name: fx().textField
7714
+ });
7715
+ test("sign: prepare seals a candidate, complete installs it, download returns it verbatim", async () => {
7716
+ if (!supported()) return;
7717
+ const engine = engineOf();
7718
+ const doc = await open(engine, opts, fx());
7719
+ const events = [];
7720
+ const unsubscribe = doc.events.subscribe((event) => events.push(event.type));
7721
+ let reopened = null;
7722
+ try {
7723
+ const v0 = await doc.version();
7724
+ const before = await doc.signatures.list();
7725
+ expect(before.revisions).toHaveLength(1);
7726
+ expect(before.signatures).toHaveLength(1);
7727
+ expect(before.signatures[0].signed).toBe(false);
7728
+ expect(before.protection.judged).toBeNull();
7729
+ const prepared = await doc.signatures.prepare({
7730
+ field: sigRef(),
7731
+ subFilter: "ETSI.CAdES.detached",
7732
+ digest: "sha256",
7733
+ contentsSize: 1024,
7734
+ attribution: { reason: "conformance" }
7735
+ });
7736
+ expect(prepared.digest.byteLength).toBe(32);
7737
+ expect(prepared.algorithm).toBe("sha256");
7738
+ expect(prepared.subFilter).toBe("ETSI.CAdES.detached");
7739
+ expect(prepared.contentsSize).toBe(1024);
7740
+ expect(prepared.byteRange[0]).toBe(0);
7741
+ expect(prepared.byteRange[2] > prepared.byteRange[1]).toBe(true);
7742
+ expect(prepared.expectedVersion.baseSha256).toBe(v0.sha256);
7743
+ expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(EngineErrorCode.SigningPending);
7744
+ expect(await caughtCode(() => doc.forms.setValue(textRef(), {
7745
+ type: "text",
7746
+ value: "x"
7747
+ }))).toBe(EngineErrorCode.SigningPending);
7748
+ const during = await doc.signatures.list();
7749
+ expect(during.signatures[0].signed).toBe(false);
7750
+ expect((await doc.version()).sha256).toBe(v0.sha256);
7751
+ expect(await caughtCode(() => doc.signatures.complete({
7752
+ signingId: prepared.signingId,
7753
+ cms: FAKE_CMS,
7754
+ expectedVersion: {
7755
+ baseSha256: prepared.expectedVersion.baseSha256,
7756
+ editsVersion: prepared.expectedVersion.editsVersion + 1
7757
+ }
7758
+ }))).toBe(EngineErrorCode.SigningVersionMismatch);
7759
+ const result = await doc.signatures.complete({
7760
+ signingId: prepared.signingId,
7761
+ cms: FAKE_CMS,
7762
+ expectedVersion: prepared.expectedVersion
7763
+ });
7764
+ expect(result.status).toBe("completed");
7765
+ expect(result.signature.signed).toBe(true);
7766
+ expect(result.signature.fieldName).toBe(fx().fieldName);
7767
+ expect(result.signature.coverage).toBe("whole-revision");
7768
+ expect(result.signature.revisionIndex).toBe(1);
7769
+ expect(result.signature.subFilter).toBe("ETSI.CAdES.detached");
7770
+ expect(result.signature.signer.reason).toBe("conformance");
7771
+ expect(result.signature.contentsSize).toBe(FAKE_CMS.byteLength);
7772
+ expect(result.previous).toEqual(prepared.expectedVersion);
7773
+ expect(result.version.sha256 === v0.sha256).toBe(false);
7774
+ expect(result.protection.judged).toBe("annotate");
7775
+ expect(result.protection.enforced).toBeNull();
7776
+ expect(result.meta.affectedPages.length >= 1).toBe(true);
7777
+ expect(await doc.version()).toEqual(result.version);
7778
+ const after = await doc.signatures.list();
7779
+ expect(after.revisions).toHaveLength(2);
7780
+ expect(after.signatures[0].signed).toBe(true);
7781
+ expect(after.signatures[0].revisionIndex).toBe(1);
7782
+ expect(after.revisions[1].signatureIndex).toBe(0);
7783
+ expect(toHex(await doc.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
7784
+ expect(bytesEqual(await doc.signatures.contents(sigRef()), FAKE_CMS)).toBe(true);
7785
+ const bytes = await doc.download();
7786
+ expect(bytes.byteLength).toBe(result.version.byteLength);
7787
+ expect(await sha256Hex(bytes)).toBe(result.version.sha256);
7788
+ reopened = await reopen(engine, opts, `${fx().id}-signed`, bytes);
7789
+ const fresh = await reopened.signatures.list();
7790
+ expect(fresh.revisions).toHaveLength(2);
7791
+ expect(fresh.signatures[0].coverage).toBe("whole-revision");
7792
+ expect(toHex(await reopened.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
7793
+ const replay = await doc.signatures.complete({
7794
+ signingId: prepared.signingId,
7795
+ cms: FAKE_CMS,
7796
+ expectedVersion: prepared.expectedVersion
7797
+ });
7798
+ expect(replay.status).toBe("already-completed");
7799
+ expect(replay.version).toEqual(result.version);
7800
+ expect((await doc.signatures.abort(prepared.signingId)).status).toBe("already-completed");
7801
+ expect((await doc.signatures.abort("never-prepared")).status).toBe("unknown");
7802
+ await doc.forms.setValue(textRef(), {
7803
+ type: "text",
7804
+ value: "after"
7805
+ });
7806
+ expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(EngineErrorCode.SignatureRefused);
7807
+ expect(events.filter((t) => t === "signature.prepared")).toHaveLength(1);
7808
+ expect(events.filter((t) => t === "signature.completed")).toHaveLength(1);
7809
+ expect(events.filter((t) => t === "document.versioned")).toHaveLength(1);
7810
+ } finally {
7811
+ unsubscribe();
7812
+ if (reopened) await reopened.close();
7813
+ await doc.close();
7814
+ }
7815
+ });
7816
+ test("sign: unsaved edits are sealed in the signature's own revision (layer session)", async () => {
7817
+ if (!supported()) return;
7818
+ const doc = await open(engineOf(), opts, fx());
7819
+ try {
7820
+ await doc.forms.setValue(textRef(), {
7821
+ type: "text",
7822
+ value: "unsaved"
7823
+ });
7824
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7825
+ const result = await doc.signatures.complete({
7826
+ signingId: prepared.signingId,
7827
+ cms: FAKE_CMS,
7828
+ expectedVersion: prepared.expectedVersion
7829
+ });
7830
+ expect(result.signature.revisionIndex).toBe(1);
7831
+ const after = await doc.signatures.list();
7832
+ expect(after.revisions).toHaveLength(2);
7833
+ const text = await doc.forms.get(textRef());
7834
+ expect(text.value).toBe("unsaved");
7835
+ const bytes = await doc.download();
7836
+ expect(await sha256Hex(bytes)).toBe(result.version.sha256);
7837
+ } finally {
7838
+ await doc.close();
7839
+ }
7840
+ });
7841
+ test("sign: a certification protects the document from the moment it completes", async () => {
7842
+ if (!supported()) return;
7843
+ const doc = await open(engineOf(), opts, fx());
7844
+ try {
7845
+ expect(doc.security.allows("doc.pages.assemble")).toBe(true);
7846
+ const prepared = await doc.signatures.prepare({
7847
+ field: sigRef(),
7848
+ certify: { permission: 2 },
7849
+ subFilter: "adbe.pkcs7.detached"
7850
+ });
7851
+ const result = await doc.signatures.complete({
7852
+ signingId: prepared.signingId,
7853
+ cms: FAKE_CMS,
7854
+ expectedVersion: prepared.expectedVersion
7855
+ });
7856
+ expect(result.signature.docMdp).toBe(2);
7857
+ expect(result.signature.catalogCertification).toBe(true);
7858
+ expect(result.protection.judged).toBe("fill");
7859
+ expect(result.protection.enforced).toBe("fill");
7860
+ expect(result.protection.certification).toEqual({
7861
+ signatureIndex: result.signature.index,
7862
+ permission: 2
7863
+ });
7864
+ expect(doc.security.allows("doc.pages.assemble")).toBe(false);
7865
+ expect(doc.security.allows("doc.forms.fill")).toBe(true);
7866
+ const list = await doc.pages.list();
7867
+ expect(await caughtCode(() => doc.pages.delete([list.pages[0].pageObjectNumber]))).toBe(EngineErrorCode.ProtectedDocument);
7868
+ expect(await caughtCode(() => doc.download({ mode: "rewrite" }))).toBe(EngineErrorCode.ProtectedDocument);
7869
+ await doc.forms.setValue(textRef(), {
7870
+ type: "text",
7871
+ value: "filled"
7872
+ });
7873
+ const after = await doc.signatures.list();
7874
+ expect(after.signatures[0].coverage).toBe("whole-revision");
7875
+ } finally {
7876
+ await doc.close();
7877
+ }
7878
+ });
7879
+ test("sign: a lock freezes the named fields and is mirrored on the field", async () => {
7880
+ if (!supported()) return;
7881
+ const doc = await open(engineOf(), opts, fx());
7882
+ try {
7883
+ const prepared = await doc.signatures.prepare({
7884
+ field: sigRef(),
7885
+ lock: {
7886
+ action: "include",
7887
+ fields: [fx().textField]
7888
+ }
7889
+ });
7890
+ const result = await doc.signatures.complete({
7891
+ signingId: prepared.signingId,
7892
+ cms: FAKE_CMS,
7893
+ expectedVersion: prepared.expectedVersion
7894
+ });
7895
+ expect(result.signature.fieldMdp).toEqual({
7896
+ action: "include",
7897
+ fields: [fx().textField]
7898
+ });
7899
+ expect(result.signature.lock).toEqual({
7900
+ action: "include",
7901
+ fields: [fx().textField]
7902
+ });
7903
+ expect(result.protection.fieldLocks.length >= 1).toBe(true);
7904
+ expect(await caughtCode(() => doc.forms.setValue(textRef(), {
7905
+ type: "text",
7906
+ value: "nope"
7907
+ }))).toBe(EngineErrorCode.ProtectedDocument);
7908
+ } finally {
7909
+ await doc.close();
7910
+ }
7911
+ });
7912
+ test("sign: abort discards the candidate and lifts the fence", async () => {
7913
+ if (!supported()) return;
7914
+ const doc = await open(engineOf(), opts, fx());
7915
+ try {
7916
+ const v0 = await doc.version();
7917
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7918
+ expect((await doc.signatures.abort(prepared.signingId)).status).toBe("aborted");
7919
+ expect((await doc.signatures.list()).signatures[0].signed).toBe(false);
7920
+ expect((await doc.version()).sha256).toBe(v0.sha256);
7921
+ await doc.forms.setValue(textRef(), {
7922
+ type: "text",
7923
+ value: "free again"
7924
+ });
7925
+ expect(await caughtCode(() => doc.signatures.complete({
7926
+ signingId: prepared.signingId,
7927
+ cms: FAKE_CMS,
7928
+ expectedVersion: prepared.expectedVersion
7929
+ }))).toBe(EngineErrorCode.NotFound);
7930
+ } finally {
7931
+ await doc.close();
7932
+ }
7933
+ });
7934
+ test("sign: artwork from a PDF page lands on the widget", async () => {
7935
+ if (!supported()) return;
7936
+ const doc = await open(engineOf(), opts, fx());
7937
+ try {
7938
+ const artwork = await opts.fixtures.artwork.bytes();
7939
+ const prepared = await doc.signatures.prepare({
7940
+ field: sigRef(),
7941
+ appearance: {
7942
+ pdf: artwork,
7943
+ pageIndex: 0
7944
+ }
7945
+ });
7946
+ const result = await doc.signatures.complete({
7947
+ signingId: prepared.signingId,
7948
+ cms: FAKE_CMS,
7949
+ expectedVersion: prepared.expectedVersion
7950
+ });
7951
+ expect(result.signature.coverage).toBe("whole-revision");
7952
+ expect(result.signature.widget).toBeTruthy();
7953
+ const widget = (await doc.page(result.signature.widget.pageObjectNumber).annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && a.ref.annotObjectNumber === result.signature.widget.annotObjectNumber);
7954
+ expect(widget).toBeTruthy();
7955
+ } finally {
7956
+ await doc.close();
7957
+ }
7958
+ });
7959
+ if (opts.openKind === "layerBytes") test("sign: a layer session keeps its kind, with an empty layer over the new base", async () => {
7960
+ if (!supported()) return;
7961
+ const doc = await open(engineOf(), opts, fx());
7962
+ try {
7963
+ const prepared = await doc.signatures.prepare({ field: sigRef() });
7964
+ const result = await doc.signatures.complete({
7965
+ signingId: prepared.signingId,
7966
+ cms: FAKE_CMS,
7967
+ expectedVersion: prepared.expectedVersion
7968
+ });
7969
+ const layer = await doc.downloadLayer();
7970
+ expect(layer.byteLength > 0).toBe(true);
7971
+ expect(await sha256Hex(await doc.download())).toBe(result.version.sha256);
7972
+ } finally {
7973
+ await doc.close();
7974
+ }
7975
+ });
7976
+ }
7977
+ async function reopen(engine, opts, id, bytes) {
7978
+ if (opts.openKind === "layerBytes") return engine.open({
7979
+ kind: "layerBytes",
7980
+ id,
7981
+ baseBytes: bytes
7982
+ });
7983
+ return engine.open({
7984
+ kind: "bytes",
7985
+ id,
7986
+ bytes
7987
+ });
7988
+ }
7989
+ async function open(engine, opts, fixture) {
7990
+ if (opts.openKind === "bytes") return engine.open({
7991
+ kind: "bytes",
7992
+ id: fixture.id,
7993
+ bytes: await fixture.bytes()
7994
+ });
7995
+ if (opts.openKind === "layerBytes") return engine.open({
7996
+ kind: "layerBytes",
7997
+ id: `${fixture.id}-layer`,
7998
+ baseBytes: await fixture.bytes()
7999
+ });
8000
+ return engine.open({
8001
+ kind: "id",
8002
+ id: fixture.cloudId ?? fixture.id
8003
+ });
8004
+ }
8005
+ function concat(a, b) {
8006
+ const out = new Uint8Array(a.byteLength + b.byteLength);
8007
+ out.set(a, 0);
8008
+ out.set(b, a.byteLength);
8009
+ return out;
8010
+ }
8011
+ function bytesEqual(a, b) {
8012
+ if (a.byteLength !== b.byteLength) return false;
8013
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
8014
+ return true;
8015
+ }
8016
+ function toHex(bytes) {
8017
+ let out = "";
8018
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
8019
+ return out;
8020
+ }
8021
+ async function sha256Hex(bytes) {
8022
+ const copy = new Uint8Array(bytes.byteLength);
8023
+ copy.set(bytes);
8024
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", copy);
8025
+ return toHex(new Uint8Array(digest));
8026
+ }
8027
+ //#endregion
8028
+ export { TEXT_DIVERGENCE_CASES, diffAnnotationListSnapshot, diffAnnotationListSnapshotAll, runActionsConformance, runAnnotationAppearanceConformance, runAnnotationAppearanceExportConformance, runAnnotationFlattenConformance, runAnnotationMutationConformance, runAnnotationReadConformance, runAttachmentConformance, runDocumentEventsConformance, runFormConformance, runMeasurementConformance, runMetadataConformance, runNamedPagesConformance, runPageDeleteConformance, runPageExtractConformance, runPageFlattenConformance, runPageGeometryOrientationConformance, runPageInsertBlankConformance, runPageInsertConformance, runPageReorderConformance, runPageRotateConformance, runPageTextConformance, runPieceInfoConformance, runRedactionApplyConformance, runSearchConformance, runSignatureConformance, runTextDivergenceConformance };
6678
8029
 
6679
8030
  //# sourceMappingURL=conformance.js.map