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