@embedpdf/engine-services 3.0.0-next.12 → 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/index.cjs +3194 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +632 -63
- package/dist/index.d.ts +632 -63
- package/dist/index.js +3185 -247
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -271,8 +271,108 @@ var SessionEventPublisher = class {
|
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
273
|
//#endregion
|
|
274
|
+
//#region src/runtime/memory/scratch.ts
|
|
275
|
+
/**
|
|
276
|
+
* Allocate a scratch buffer, run `body` with it, and always free it.
|
|
277
|
+
*
|
|
278
|
+
* Every PDFium out-parameter call follows the same shape: malloc a
|
|
279
|
+
* buffer, hand its pointer to the native function, read the result back,
|
|
280
|
+
* free the buffer. Hand-rolling that `try/finally` at each call site is
|
|
281
|
+
* how leaks and copy-paste bugs creep in, so it lives here once.
|
|
282
|
+
*/
|
|
283
|
+
function withScratch(mem, bytes, body) {
|
|
284
|
+
const ptr = mem.alloc(bytes);
|
|
285
|
+
try {
|
|
286
|
+
return body(ptr);
|
|
287
|
+
} finally {
|
|
288
|
+
mem.free(ptr);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Like {@link withScratch} but for native calls that take several
|
|
293
|
+
* out-parameters at once (e.g. `FPDFAnnot_GetColor` with one pointer per
|
|
294
|
+
* channel). Buffers are freed in reverse allocation order.
|
|
295
|
+
*/
|
|
296
|
+
function withScratchN(mem, sizes, body) {
|
|
297
|
+
const ptrs = sizes.map((size) => mem.alloc(size));
|
|
298
|
+
try {
|
|
299
|
+
return body(ptrs);
|
|
300
|
+
} finally {
|
|
301
|
+
for (let i = ptrs.length - 1; i >= 0; i--) mem.free(ptrs[i]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/runtime/memory/strings.ts
|
|
306
|
+
/**
|
|
307
|
+
* Read a PDFium UTF-16LE string getter that uses the standard
|
|
308
|
+
* (probe length, allocate, read) ABI. `call(buf, capacity)` must invoke
|
|
309
|
+
* the native function and return the length in BYTES including the
|
|
310
|
+
* trailing NUL (the value PDFium reports when `capacity` is 0).
|
|
311
|
+
*
|
|
312
|
+
* `emptyAs` decides what a present-but-empty value (`len === 2`, a lone
|
|
313
|
+
* UTF-16 NUL) decodes to. This is the one knob different callers actually
|
|
314
|
+
* disagree on: document Info strings treat `''` as a real value, whereas
|
|
315
|
+
* a page label of `''` is indistinguishable from "absent" and reads as
|
|
316
|
+
* `null`. Making it a parameter keeps that decision explicit instead of
|
|
317
|
+
* forking the buffer dance.
|
|
318
|
+
*
|
|
319
|
+
* Returns `null` when the value is absent (`len <= 0`).
|
|
320
|
+
*/
|
|
321
|
+
function readUtf16String(mem, call, emptyAs = "") {
|
|
322
|
+
const len = call(_embedpdf_engine_runtime.NULL_PTR, 0);
|
|
323
|
+
if (len <= 0) return null;
|
|
324
|
+
if (len === 2) return emptyAs;
|
|
325
|
+
return withScratch(mem, len, (buf) => {
|
|
326
|
+
if (call(buf, len) <= 0) return null;
|
|
327
|
+
return mem.readU16String(buf);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* UTF-8 sibling of {@link readUtf16String} for the handful of native
|
|
332
|
+
* getters that return UTF-8 (e.g. `EPDF_GetMetaKeyName`). UTF-8 has no
|
|
333
|
+
* fixed empty-NUL width to special-case, so an empty value decodes to
|
|
334
|
+
* `''`; absent (`len <= 0`) is `null`.
|
|
335
|
+
*/
|
|
336
|
+
function readUtf8String(mem, call) {
|
|
337
|
+
const len = call(_embedpdf_engine_runtime.NULL_PTR, 0);
|
|
338
|
+
if (len <= 0) return null;
|
|
339
|
+
return withScratch(mem, len, (buf) => {
|
|
340
|
+
if (call(buf, len) <= 0) return null;
|
|
341
|
+
return mem.readU8String(buf);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Write side of the UTF-16 convention: encode `value` into a scratch
|
|
346
|
+
* buffer, hand the pointer to `set`, and always free it. `set` returns
|
|
347
|
+
* the native call's success flag, which is propagated.
|
|
348
|
+
*/
|
|
349
|
+
function writeUtf16String(mem, value, set) {
|
|
350
|
+
const ptr = mem.writeU16String(value);
|
|
351
|
+
try {
|
|
352
|
+
return set(ptr);
|
|
353
|
+
} finally {
|
|
354
|
+
mem.free(ptr);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* A NUL-terminated UTF-8 copy of `value` in runtime memory for the duration
|
|
359
|
+
* of `fn` (a `FPDF_STRING` parameter: a file system path, say).
|
|
360
|
+
*/
|
|
361
|
+
function withUtf8CString(mem, value, fn) {
|
|
362
|
+
const encoded = new TextEncoder().encode(value);
|
|
363
|
+
const bytes = new Uint8Array(encoded.byteLength + 1);
|
|
364
|
+
bytes.set(encoded);
|
|
365
|
+
const ptr = mem.alloc(bytes.byteLength);
|
|
366
|
+
try {
|
|
367
|
+
mem.writeBytes(ptr, bytes);
|
|
368
|
+
return fn(ptr);
|
|
369
|
+
} finally {
|
|
370
|
+
mem.free(ptr);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
274
374
|
//#region src/document-session/lifecycle/PdfDocumentOpener.ts
|
|
275
|
-
const FPDF_ERR_PASSWORD$
|
|
375
|
+
const FPDF_ERR_PASSWORD$2 = 4;
|
|
276
376
|
var CloseStack = class {
|
|
277
377
|
constructor() {
|
|
278
378
|
_defineProperty(this, "actions", []);
|
|
@@ -307,7 +407,7 @@ function openFatMemoryDocument(runtime, bytes, password = null) {
|
|
|
307
407
|
mem.writeBytes(dataPtr, bytes);
|
|
308
408
|
const docPtr = fn.FPDF_LoadMemDocument64(dataPtr, bytes.byteLength, password ?? "");
|
|
309
409
|
if (!docPtr) {
|
|
310
|
-
if (fn.FPDF_GetLastError() === FPDF_ERR_PASSWORD$
|
|
410
|
+
if (fn.FPDF_GetLastError() === FPDF_ERR_PASSWORD$2) throw password ? new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordIncorrect, "incorrect document password") : new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordRequired, "document requires a password");
|
|
311
411
|
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to open document");
|
|
312
412
|
}
|
|
313
413
|
setRuntimeOwnerPermissionsIfEncrypted(runtime, docPtr);
|
|
@@ -315,6 +415,11 @@ function openFatMemoryDocument(runtime, bytes, password = null) {
|
|
|
315
415
|
return {
|
|
316
416
|
kind: "fat-memory",
|
|
317
417
|
docPtr,
|
|
418
|
+
source: {
|
|
419
|
+
base: null,
|
|
420
|
+
layer: null,
|
|
421
|
+
password
|
|
422
|
+
},
|
|
318
423
|
close: () => stack.close()
|
|
319
424
|
};
|
|
320
425
|
} catch (error) {
|
|
@@ -332,6 +437,7 @@ function openLayerDocument(runtime, base, layer = { kind: "fresh" }, password =
|
|
|
332
437
|
mem.poke(statusPtr, "i32", -1);
|
|
333
438
|
let docPtr;
|
|
334
439
|
if (layer.kind === "fresh") docPtr = fn.EPDFLayer_OpenLayer(base.basePtr, _embedpdf_engine_runtime.NULL_PTR, password ?? "", statusPtr);
|
|
440
|
+
else if (layer.kind === "artifact-file" && runtime.kind === "native") docPtr = withUtf8CString(mem, layer.path, (pathPtr) => fn.EPDFLayer_OpenLayerArtifactFromPath(base.basePtr, pathPtr, password ?? "", statusPtr));
|
|
335
441
|
else {
|
|
336
442
|
layerAccess = layer.kind === "artifact-file" ? runtime.fileAccess.fromNodeFile(layer.path) : runtime.fileAccess.fromMemory(layer.bytes);
|
|
337
443
|
stack.push(() => layerAccess?.close());
|
|
@@ -341,9 +447,22 @@ function openLayerDocument(runtime, base, layer = { kind: "fresh" }, password =
|
|
|
341
447
|
if (!docPtr || status !== 0) throw layerOpenError(status);
|
|
342
448
|
setRuntimeOwnerPermissionsIfEncrypted(runtime, docPtr);
|
|
343
449
|
stack.push(() => fn.FPDF_CloseDocument(docPtr));
|
|
450
|
+
const baseSource = base.kind === "file" && base.path !== void 0 ? {
|
|
451
|
+
kind: "file",
|
|
452
|
+
key: base.key,
|
|
453
|
+
path: base.path
|
|
454
|
+
} : {
|
|
455
|
+
kind: "memory",
|
|
456
|
+
key: base.key
|
|
457
|
+
};
|
|
344
458
|
return {
|
|
345
459
|
kind: "layer",
|
|
346
460
|
docPtr,
|
|
461
|
+
source: {
|
|
462
|
+
base: baseSource,
|
|
463
|
+
layer,
|
|
464
|
+
password
|
|
465
|
+
},
|
|
347
466
|
close: () => stack.close()
|
|
348
467
|
};
|
|
349
468
|
} catch (error) {
|
|
@@ -496,22 +615,13 @@ var LocalRevisionAuthority = class {
|
|
|
496
615
|
};
|
|
497
616
|
//#endregion
|
|
498
617
|
//#region src/document-session/DocumentSession.ts
|
|
499
|
-
/**
|
|
500
|
-
* Owns the lifecycle of a single open PDFium document and the v3
|
|
501
|
-
* identity machinery: page registry (pageObjectNumber <-> pageIndex),
|
|
502
|
-
* `RevisionAuthority` (per-page generation counters), and `PagePtrPool`
|
|
503
|
-
* (refcounted pagePtr access).
|
|
504
|
-
*
|
|
505
|
-
* Both the local browser Worker and the server worker_thread instantiate
|
|
506
|
-
* this exactly the same way; the only thing that differs is the
|
|
507
|
-
* underlying PdfRuntimeModule (WASM vs native).
|
|
508
|
-
*/
|
|
509
618
|
var DocumentSession = class {
|
|
510
619
|
constructor(runtime, sessionId) {
|
|
511
620
|
this.runtime = runtime;
|
|
512
621
|
_defineProperty(this, "docPtr", null);
|
|
513
622
|
_defineProperty(this, "closeDocument", null);
|
|
514
623
|
_defineProperty(this, "_kind", null);
|
|
624
|
+
_defineProperty(this, "_source", null);
|
|
515
625
|
_defineProperty(this, "_sessionId", void 0);
|
|
516
626
|
_defineProperty(this, "recordsByObjectNumber", /* @__PURE__ */ new Map());
|
|
517
627
|
_defineProperty(this, "recordsByIndex", /* @__PURE__ */ new Map());
|
|
@@ -519,6 +629,59 @@ var DocumentSession = class {
|
|
|
519
629
|
_defineProperty(this, "revisions", null);
|
|
520
630
|
_defineProperty(this, "mutationSeqCounter", 0);
|
|
521
631
|
_defineProperty(this, "pages", null);
|
|
632
|
+
_defineProperty(
|
|
633
|
+
this,
|
|
634
|
+
/**
|
|
635
|
+
* Whether writers honour what the document's signatures forbid (locked
|
|
636
|
+
* fields, structural edits). Set at open from the engine option; the
|
|
637
|
+
* main-thread guard subtracts the matching capabilities.
|
|
638
|
+
*/
|
|
639
|
+
"signedDocumentPolicy",
|
|
640
|
+
"protect"
|
|
641
|
+
);
|
|
642
|
+
_defineProperty(
|
|
643
|
+
this,
|
|
644
|
+
/**
|
|
645
|
+
* The shape the caller asked for when opening plain bytes. `layer` (the
|
|
646
|
+
* default) opens them as an immutable base with a fresh layer; `plain`
|
|
647
|
+
* keeps one in-memory document unless the bytes already carry a
|
|
648
|
+
* signature, which always opens as a layer.
|
|
649
|
+
*/
|
|
650
|
+
"sessionKind",
|
|
651
|
+
"layer"
|
|
652
|
+
);
|
|
653
|
+
_defineProperty(
|
|
654
|
+
this,
|
|
655
|
+
/** The password the document was opened with; signing candidates open with the same one. */
|
|
656
|
+
"password",
|
|
657
|
+
null
|
|
658
|
+
);
|
|
659
|
+
_defineProperty(
|
|
660
|
+
this,
|
|
661
|
+
/**
|
|
662
|
+
* Whether mutations on a layer session also serialize the layer artifact
|
|
663
|
+
* into their response (what a server persists). False for a session that
|
|
664
|
+
* became a layer only because its document is signed (see
|
|
665
|
+
* `WorkerHost.openSignedAware`): the caller opened plain bytes and never
|
|
666
|
+
* asked for artifacts.
|
|
667
|
+
*/
|
|
668
|
+
"persistLayerArtifact",
|
|
669
|
+
true
|
|
670
|
+
);
|
|
671
|
+
_defineProperty(this, "loadedSeq", 0);
|
|
672
|
+
_defineProperty(this, "plainSha256", null);
|
|
673
|
+
_defineProperty(
|
|
674
|
+
this,
|
|
675
|
+
/** The signing candidate parked by `signatures.prepare`, if any. */
|
|
676
|
+
"pendingSigning",
|
|
677
|
+
null
|
|
678
|
+
);
|
|
679
|
+
_defineProperty(
|
|
680
|
+
this,
|
|
681
|
+
/** The last completed signing, so a replayed `complete` answers `already-completed`. */
|
|
682
|
+
"lastCompletion",
|
|
683
|
+
null
|
|
684
|
+
);
|
|
522
685
|
_defineProperty(this, "parkedBytes", null);
|
|
523
686
|
_defineProperty(this, "retained", []);
|
|
524
687
|
this._sessionId = sessionId ?? generateSessionId();
|
|
@@ -529,6 +692,15 @@ var DocumentSession = class {
|
|
|
529
692
|
get kind() {
|
|
530
693
|
return this._kind;
|
|
531
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Where the current bytes come from: the immutable base (registry key,
|
|
697
|
+
* file path for file bases) and the layer the session was opened with.
|
|
698
|
+
* One interpretation for signing candidates, overlays and verbatim reads.
|
|
699
|
+
*/
|
|
700
|
+
get source() {
|
|
701
|
+
if (!this._source) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "document is not open");
|
|
702
|
+
return this._source;
|
|
703
|
+
}
|
|
532
704
|
isOpen() {
|
|
533
705
|
return this.docPtr !== null;
|
|
534
706
|
}
|
|
@@ -543,9 +715,76 @@ var DocumentSession = class {
|
|
|
543
715
|
this.docPtr = handle.docPtr;
|
|
544
716
|
this.closeDocument = () => handle.close();
|
|
545
717
|
this._kind = handle.kind;
|
|
718
|
+
this._source = handle.source;
|
|
546
719
|
this.revisions = new LocalRevisionAuthority(this._sessionId);
|
|
547
720
|
this.pages = new PagePtrPool(this.runtime, handle.docPtr);
|
|
548
721
|
this.parkedBytes = null;
|
|
722
|
+
this.loadedSeq = this.mutationSeqCounter;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Whether anything was mutated since the current bytes were loaded. When
|
|
726
|
+
* false, the loaded bytes ARE the document: a save returns them verbatim
|
|
727
|
+
* and a signing candidate is built straight on them.
|
|
728
|
+
*/
|
|
729
|
+
hasUnsavedEdits() {
|
|
730
|
+
return this.mutationSeqCounter !== this.loadedSeq;
|
|
731
|
+
}
|
|
732
|
+
/** Cached hash of the current loaded bytes, or `null` when not computed since the last load. */
|
|
733
|
+
cachedPlainSha256() {
|
|
734
|
+
return this.plainSha256 && this.plainSha256.loadedSeq === this.loadedSeq ? this.plainSha256.sha256 : null;
|
|
735
|
+
}
|
|
736
|
+
rememberPlainSha256(sha256) {
|
|
737
|
+
this.plainSha256 = {
|
|
738
|
+
loadedSeq: this.loadedSeq,
|
|
739
|
+
sha256
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Re-point this session at new bytes — the one operation that changes
|
|
744
|
+
* what an open session is backed by, used when a completed signature
|
|
745
|
+
* installs its sealed file. The session id, its page object numbers
|
|
746
|
+
* (an incremental save never renumbers) and its retained resources
|
|
747
|
+
* survive; the old document is closed, every page is re-pinned (its
|
|
748
|
+
* revision bumped, its cached pointer dropped), and the mutation
|
|
749
|
+
* sequence advances so every version-keyed cache rebuilds.
|
|
750
|
+
*/
|
|
751
|
+
install(handle) {
|
|
752
|
+
if (!this.docPtr) {
|
|
753
|
+
handle.close();
|
|
754
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "no document to replace");
|
|
755
|
+
}
|
|
756
|
+
const previousPages = Array.from(this.recordsByObjectNumber.keys());
|
|
757
|
+
let firstError = null;
|
|
758
|
+
try {
|
|
759
|
+
this.pages?.closeAll();
|
|
760
|
+
} catch (error) {
|
|
761
|
+
firstError = error;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
this.closeDocument?.();
|
|
765
|
+
} catch (error) {
|
|
766
|
+
firstError ?? (firstError = error);
|
|
767
|
+
}
|
|
768
|
+
this.docPtr = handle.docPtr;
|
|
769
|
+
this.closeDocument = () => handle.close();
|
|
770
|
+
this._kind = handle.kind;
|
|
771
|
+
this._source = handle.source;
|
|
772
|
+
this.pages = new PagePtrPool(this.runtime, handle.docPtr);
|
|
773
|
+
this.recordsByIndex.clear();
|
|
774
|
+
this.recordsByObjectNumber.clear();
|
|
775
|
+
this.fullyEnumerated = false;
|
|
776
|
+
for (const pon of previousPages) this.requireRevisions().bump(pon);
|
|
777
|
+
this.mutationSeqCounter++;
|
|
778
|
+
this.loadedSeq = this.mutationSeqCounter;
|
|
779
|
+
this.pendingSigning = null;
|
|
780
|
+
if (firstError) throw firstError;
|
|
781
|
+
}
|
|
782
|
+
/** The two publish fences a candidate is built on. */
|
|
783
|
+
versionRef(baseSha256) {
|
|
784
|
+
return {
|
|
785
|
+
baseSha256,
|
|
786
|
+
editsVersion: this.mutationSeqCounter
|
|
787
|
+
};
|
|
549
788
|
}
|
|
550
789
|
/**
|
|
551
790
|
* Park this session in the password-locked state: the document could not
|
|
@@ -705,6 +944,11 @@ var DocumentSession = class {
|
|
|
705
944
|
} finally {
|
|
706
945
|
this.pages = null;
|
|
707
946
|
}
|
|
947
|
+
if (this.pendingSigning?.saved.kind === "file") try {
|
|
948
|
+
this.runtime.fileWrite.removeFile(this.pendingSigning.saved.path);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
firstError ?? (firstError = error);
|
|
951
|
+
}
|
|
708
952
|
try {
|
|
709
953
|
this.closeDocument?.();
|
|
710
954
|
} catch (error) {
|
|
@@ -713,7 +957,10 @@ var DocumentSession = class {
|
|
|
713
957
|
this.closeDocument = null;
|
|
714
958
|
this.docPtr = null;
|
|
715
959
|
this._kind = null;
|
|
960
|
+
this._source = null;
|
|
716
961
|
this.parkedBytes = null;
|
|
962
|
+
this.pendingSigning = null;
|
|
963
|
+
this.lastCompletion = null;
|
|
717
964
|
this.revisions?.clear();
|
|
718
965
|
this.revisions = null;
|
|
719
966
|
this.recordsByIndex.clear();
|
|
@@ -739,6 +986,17 @@ function generateSessionId() {
|
|
|
739
986
|
}
|
|
740
987
|
//#endregion
|
|
741
988
|
//#region src/document-session/lifecycle/BaseDocumentRegistry.ts
|
|
989
|
+
const FPDF_ERR_PASSWORD$1 = 4;
|
|
990
|
+
/**
|
|
991
|
+
* A password failure is a recoverable STATE the caller can act on (prompt,
|
|
992
|
+
* unlock), never a generic open failure — the same distinction
|
|
993
|
+
* `openFatMemoryDocument` draws, so a plain-bytes open that becomes a base
|
|
994
|
+
* parks and unlocks exactly like it always did.
|
|
995
|
+
*/
|
|
996
|
+
function baseOpenError(runtime, password) {
|
|
997
|
+
if (runtime.fn.FPDF_GetLastError() === FPDF_ERR_PASSWORD$1) return password ? new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordIncorrect, "incorrect document password") : new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordRequired, "document requires a password");
|
|
998
|
+
return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to open base document");
|
|
999
|
+
}
|
|
742
1000
|
var BaseDocumentRegistry = class {
|
|
743
1001
|
constructor(runtime) {
|
|
744
1002
|
this.runtime = runtime;
|
|
@@ -754,10 +1012,17 @@ var BaseDocumentRegistry = class {
|
|
|
754
1012
|
try {
|
|
755
1013
|
mem.writeBytes(dataPtr, opts.bytes);
|
|
756
1014
|
const basePtr = fn.EPDF_LoadMemBaseDocument64(dataPtr, opts.bytes.byteLength, opts.password ?? "");
|
|
757
|
-
if (!basePtr) throw
|
|
1015
|
+
if (!basePtr) throw baseOpenError(this.runtime, opts.password);
|
|
758
1016
|
setRuntimeOwnerPermissionsIfEncrypted(this.runtime, basePtr);
|
|
759
1017
|
stack.push(() => fn.EPDF_ReleaseBaseDocument(basePtr));
|
|
760
|
-
|
|
1018
|
+
this.supplyKnownSha(basePtr, opts.knownSha256);
|
|
1019
|
+
return this.insert({
|
|
1020
|
+
key: opts.key,
|
|
1021
|
+
kind: "memory",
|
|
1022
|
+
basePtr,
|
|
1023
|
+
refs: 1,
|
|
1024
|
+
close: () => stack.close()
|
|
1025
|
+
});
|
|
761
1026
|
} catch (error) {
|
|
762
1027
|
stack.close();
|
|
763
1028
|
throw error;
|
|
@@ -773,15 +1038,50 @@ var BaseDocumentRegistry = class {
|
|
|
773
1038
|
access = this.runtime.fileAccess.fromNodeFile(opts.path);
|
|
774
1039
|
stack.push(() => access?.close());
|
|
775
1040
|
const basePtr = fn.EPDF_LoadBaseDocument(access.ptr, opts.password ?? "");
|
|
776
|
-
if (!basePtr) throw
|
|
1041
|
+
if (!basePtr) throw baseOpenError(this.runtime, opts.password);
|
|
777
1042
|
setRuntimeOwnerPermissionsIfEncrypted(this.runtime, basePtr);
|
|
778
1043
|
stack.push(() => fn.EPDF_ReleaseBaseDocument(basePtr));
|
|
779
|
-
|
|
1044
|
+
this.supplyKnownSha(basePtr, opts.knownSha256);
|
|
1045
|
+
return this.insert({
|
|
1046
|
+
key: opts.key,
|
|
1047
|
+
kind: "file",
|
|
1048
|
+
path: opts.path,
|
|
1049
|
+
basePtr,
|
|
1050
|
+
refs: 1,
|
|
1051
|
+
close: () => stack.close()
|
|
1052
|
+
});
|
|
780
1053
|
} catch (error) {
|
|
781
1054
|
stack.close();
|
|
782
1055
|
throw error;
|
|
783
1056
|
}
|
|
784
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* One more retain on a base already in the registry (a session holds one
|
|
1060
|
+
* for its lifetime, so its key is live while it is open), or null. The
|
|
1061
|
+
* way a signing candidate reopens over its session's own base without
|
|
1062
|
+
* copying a byte.
|
|
1063
|
+
*/
|
|
1064
|
+
retainByKey(key) {
|
|
1065
|
+
return this.retain(key);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Hand the runtime a hash the host already verified for these exact
|
|
1069
|
+
* bytes, so it never hashes them itself. Malformed values are ignored:
|
|
1070
|
+
* the runtime then hashes lazily on first use, which is always correct.
|
|
1071
|
+
*/
|
|
1072
|
+
supplyKnownSha(basePtr, sha256Hex) {
|
|
1073
|
+
if (!sha256Hex || !/^[0-9a-fA-F]{64}$/.test(sha256Hex)) return;
|
|
1074
|
+
const { mem, fn } = this.runtime;
|
|
1075
|
+
const bytes = /* @__PURE__ */ new Uint8Array(32);
|
|
1076
|
+
for (let i = 0; i < 32; i++) bytes[i] = parseInt(sha256Hex.slice(i * 2, i * 2 + 2), 16);
|
|
1077
|
+
const ptr = mem.alloc(32);
|
|
1078
|
+
try {
|
|
1079
|
+
mem.writeBytes(ptr, bytes);
|
|
1080
|
+
fn.EPDF_SetBaseDocumentSha256(basePtr, ptr);
|
|
1081
|
+
} finally {
|
|
1082
|
+
mem.free(ptr);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
785
1085
|
releaseAll() {
|
|
786
1086
|
const entries = Array.from(this.entries.values());
|
|
787
1087
|
this.entries.clear();
|
|
@@ -803,20 +1103,16 @@ var BaseDocumentRegistry = class {
|
|
|
803
1103
|
entry.refs++;
|
|
804
1104
|
return this.handleFor(entry);
|
|
805
1105
|
}
|
|
806
|
-
insert(
|
|
807
|
-
|
|
808
|
-
key,
|
|
809
|
-
basePtr,
|
|
810
|
-
refs: 1,
|
|
811
|
-
close
|
|
812
|
-
};
|
|
813
|
-
this.entries.set(key, entry);
|
|
1106
|
+
insert(entry) {
|
|
1107
|
+
this.entries.set(entry.key, entry);
|
|
814
1108
|
return this.handleFor(entry);
|
|
815
1109
|
}
|
|
816
1110
|
handleFor(entry) {
|
|
817
1111
|
let released = false;
|
|
818
1112
|
return {
|
|
819
1113
|
key: entry.key,
|
|
1114
|
+
kind: entry.kind,
|
|
1115
|
+
...entry.path !== void 0 ? { path: entry.path } : {},
|
|
820
1116
|
basePtr: entry.basePtr,
|
|
821
1117
|
release: () => {
|
|
822
1118
|
if (released) return;
|
|
@@ -832,90 +1128,6 @@ var BaseDocumentRegistry = class {
|
|
|
832
1128
|
}
|
|
833
1129
|
};
|
|
834
1130
|
//#endregion
|
|
835
|
-
//#region src/runtime/memory/scratch.ts
|
|
836
|
-
/**
|
|
837
|
-
* Allocate a scratch buffer, run `body` with it, and always free it.
|
|
838
|
-
*
|
|
839
|
-
* Every PDFium out-parameter call follows the same shape: malloc a
|
|
840
|
-
* buffer, hand its pointer to the native function, read the result back,
|
|
841
|
-
* free the buffer. Hand-rolling that `try/finally` at each call site is
|
|
842
|
-
* how leaks and copy-paste bugs creep in, so it lives here once.
|
|
843
|
-
*/
|
|
844
|
-
function withScratch(mem, bytes, body) {
|
|
845
|
-
const ptr = mem.alloc(bytes);
|
|
846
|
-
try {
|
|
847
|
-
return body(ptr);
|
|
848
|
-
} finally {
|
|
849
|
-
mem.free(ptr);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Like {@link withScratch} but for native calls that take several
|
|
854
|
-
* out-parameters at once (e.g. `FPDFAnnot_GetColor` with one pointer per
|
|
855
|
-
* channel). Buffers are freed in reverse allocation order.
|
|
856
|
-
*/
|
|
857
|
-
function withScratchN(mem, sizes, body) {
|
|
858
|
-
const ptrs = sizes.map((size) => mem.alloc(size));
|
|
859
|
-
try {
|
|
860
|
-
return body(ptrs);
|
|
861
|
-
} finally {
|
|
862
|
-
for (let i = ptrs.length - 1; i >= 0; i--) mem.free(ptrs[i]);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
//#endregion
|
|
866
|
-
//#region src/runtime/memory/strings.ts
|
|
867
|
-
/**
|
|
868
|
-
* Read a PDFium UTF-16LE string getter that uses the standard
|
|
869
|
-
* (probe length, allocate, read) ABI. `call(buf, capacity)` must invoke
|
|
870
|
-
* the native function and return the length in BYTES including the
|
|
871
|
-
* trailing NUL (the value PDFium reports when `capacity` is 0).
|
|
872
|
-
*
|
|
873
|
-
* `emptyAs` decides what a present-but-empty value (`len === 2`, a lone
|
|
874
|
-
* UTF-16 NUL) decodes to. This is the one knob different callers actually
|
|
875
|
-
* disagree on: document Info strings treat `''` as a real value, whereas
|
|
876
|
-
* a page label of `''` is indistinguishable from "absent" and reads as
|
|
877
|
-
* `null`. Making it a parameter keeps that decision explicit instead of
|
|
878
|
-
* forking the buffer dance.
|
|
879
|
-
*
|
|
880
|
-
* Returns `null` when the value is absent (`len <= 0`).
|
|
881
|
-
*/
|
|
882
|
-
function readUtf16String(mem, call, emptyAs = "") {
|
|
883
|
-
const len = call(_embedpdf_engine_runtime.NULL_PTR, 0);
|
|
884
|
-
if (len <= 0) return null;
|
|
885
|
-
if (len === 2) return emptyAs;
|
|
886
|
-
return withScratch(mem, len, (buf) => {
|
|
887
|
-
if (call(buf, len) <= 0) return null;
|
|
888
|
-
return mem.readU16String(buf);
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* UTF-8 sibling of {@link readUtf16String} for the handful of native
|
|
893
|
-
* getters that return UTF-8 (e.g. `EPDF_GetMetaKeyName`). UTF-8 has no
|
|
894
|
-
* fixed empty-NUL width to special-case, so an empty value decodes to
|
|
895
|
-
* `''`; absent (`len <= 0`) is `null`.
|
|
896
|
-
*/
|
|
897
|
-
function readUtf8String(mem, call) {
|
|
898
|
-
const len = call(_embedpdf_engine_runtime.NULL_PTR, 0);
|
|
899
|
-
if (len <= 0) return null;
|
|
900
|
-
return withScratch(mem, len, (buf) => {
|
|
901
|
-
if (call(buf, len) <= 0) return null;
|
|
902
|
-
return mem.readU8String(buf);
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Write side of the UTF-16 convention: encode `value` into a scratch
|
|
907
|
-
* buffer, hand the pointer to `set`, and always free it. `set` returns
|
|
908
|
-
* the native call's success flag, which is propagated.
|
|
909
|
-
*/
|
|
910
|
-
function writeUtf16String(mem, value, set) {
|
|
911
|
-
const ptr = mem.writeU16String(value);
|
|
912
|
-
try {
|
|
913
|
-
return set(ptr);
|
|
914
|
-
} finally {
|
|
915
|
-
mem.free(ptr);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
//#endregion
|
|
919
1131
|
//#region src/features/metadata/internal/readMetadataText.ts
|
|
920
1132
|
/**
|
|
921
1133
|
* Read a key from the document's Info dictionary. Returns null if the key
|
|
@@ -2172,8 +2384,11 @@ var PagesInserter = class {
|
|
|
2172
2384
|
//#endregion
|
|
2173
2385
|
//#region src/features/annotations/internal/read/annotationReadContext.ts
|
|
2174
2386
|
/** Build the read context both read paths (list loop, mutator read-back) use. */
|
|
2175
|
-
function readContextFor(session) {
|
|
2176
|
-
return {
|
|
2387
|
+
function readContextFor(session, fonts) {
|
|
2388
|
+
return {
|
|
2389
|
+
docPtr: session.requireDocPtr(),
|
|
2390
|
+
...fonts ? { fonts } : {}
|
|
2391
|
+
};
|
|
2177
2392
|
}
|
|
2178
2393
|
//#endregion
|
|
2179
2394
|
//#region src/features/annotations/internal/annotationFlagBits.ts
|
|
@@ -3014,18 +3229,218 @@ function standardFontFromCode(code) {
|
|
|
3014
3229
|
return CODE_TO_FONT[code] ?? "helvetica";
|
|
3015
3230
|
}
|
|
3016
3231
|
//#endregion
|
|
3017
|
-
//#region src/features/annotations/internal/
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3232
|
+
//#region src/features/annotations/internal/richTextWire.ts
|
|
3233
|
+
const STANDARD_FACES = {
|
|
3234
|
+
courier: {
|
|
3235
|
+
family: "Courier",
|
|
3236
|
+
weight: 400,
|
|
3237
|
+
italic: false
|
|
3238
|
+
},
|
|
3239
|
+
"courier-bold": {
|
|
3240
|
+
family: "Courier",
|
|
3241
|
+
weight: 700,
|
|
3242
|
+
italic: false
|
|
3243
|
+
},
|
|
3244
|
+
"courier-bold-oblique": {
|
|
3245
|
+
family: "Courier",
|
|
3246
|
+
weight: 700,
|
|
3247
|
+
italic: true
|
|
3248
|
+
},
|
|
3249
|
+
"courier-oblique": {
|
|
3250
|
+
family: "Courier",
|
|
3251
|
+
weight: 400,
|
|
3252
|
+
italic: true
|
|
3253
|
+
},
|
|
3254
|
+
helvetica: {
|
|
3255
|
+
family: "Helvetica",
|
|
3256
|
+
weight: 400,
|
|
3257
|
+
italic: false
|
|
3258
|
+
},
|
|
3259
|
+
"helvetica-bold": {
|
|
3260
|
+
family: "Helvetica",
|
|
3261
|
+
weight: 700,
|
|
3262
|
+
italic: false
|
|
3263
|
+
},
|
|
3264
|
+
"helvetica-bold-oblique": {
|
|
3265
|
+
family: "Helvetica",
|
|
3266
|
+
weight: 700,
|
|
3267
|
+
italic: true
|
|
3268
|
+
},
|
|
3269
|
+
"helvetica-oblique": {
|
|
3270
|
+
family: "Helvetica",
|
|
3271
|
+
weight: 400,
|
|
3272
|
+
italic: true
|
|
3273
|
+
},
|
|
3274
|
+
"times-roman": {
|
|
3275
|
+
family: "Times",
|
|
3276
|
+
weight: 400,
|
|
3277
|
+
italic: false
|
|
3278
|
+
},
|
|
3279
|
+
"times-bold": {
|
|
3280
|
+
family: "Times",
|
|
3281
|
+
weight: 700,
|
|
3282
|
+
italic: false
|
|
3283
|
+
},
|
|
3284
|
+
"times-bold-italic": {
|
|
3285
|
+
family: "Times",
|
|
3286
|
+
weight: 700,
|
|
3287
|
+
italic: true
|
|
3288
|
+
},
|
|
3289
|
+
"times-italic": {
|
|
3290
|
+
family: "Times",
|
|
3291
|
+
weight: 400,
|
|
3292
|
+
italic: true
|
|
3293
|
+
},
|
|
3294
|
+
symbol: {
|
|
3295
|
+
family: "Symbol",
|
|
3296
|
+
weight: 400,
|
|
3297
|
+
italic: false
|
|
3298
|
+
},
|
|
3299
|
+
"zapf-dingbats": {
|
|
3300
|
+
family: "ZapfDingbats",
|
|
3301
|
+
weight: 400,
|
|
3302
|
+
italic: false
|
|
3303
|
+
}
|
|
3304
|
+
};
|
|
3305
|
+
/** Family names compare without case, spaces, hyphens and underscores
|
|
3306
|
+
* (the engine's own rule): "Noto Sans" == "NotoSans" == "noto-sans". */
|
|
3307
|
+
function familyKey$1(family) {
|
|
3308
|
+
return family.toLowerCase().replace(/[\s\-_'"]/g, "");
|
|
3309
|
+
}
|
|
3310
|
+
const STANDARD_FAMILY_KEYS = {
|
|
3311
|
+
helvetica: "Helvetica",
|
|
3312
|
+
arial: "Helvetica",
|
|
3313
|
+
arialmt: "Helvetica",
|
|
3314
|
+
sansserif: "Helvetica",
|
|
3315
|
+
times: "Times",
|
|
3316
|
+
timesroman: "Times",
|
|
3317
|
+
timesnewroman: "Times",
|
|
3318
|
+
timesnewromanpsmt: "Times",
|
|
3319
|
+
serif: "Times",
|
|
3320
|
+
courier: "Courier",
|
|
3321
|
+
couriernew: "Courier",
|
|
3322
|
+
couriernewpsmt: "Courier",
|
|
3323
|
+
monospace: "Courier",
|
|
3324
|
+
symbol: "Symbol",
|
|
3325
|
+
zapfdingbats: "ZapfDingbats",
|
|
3326
|
+
dingbats: "ZapfDingbats"
|
|
3327
|
+
};
|
|
3328
|
+
/**
|
|
3329
|
+
* The face a DTO `fontFamily` (or a rich run's `family`) asks for: a
|
|
3330
|
+
* standard font's family/weight/italic, a registered key's resolved
|
|
3331
|
+
* identity, or — for anything else — the string itself as a family name
|
|
3332
|
+
* (a face the document embeds, or one the engine will substitute).
|
|
3333
|
+
*/
|
|
3334
|
+
function faceForFreeTextFont(font, describe) {
|
|
3335
|
+
if (isStandardFont(font)) {
|
|
3336
|
+
const face = STANDARD_FACES[font];
|
|
3337
|
+
return {
|
|
3338
|
+
family: face.family,
|
|
3339
|
+
weight: face.weight,
|
|
3340
|
+
italic: face.italic
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
const registered = describe?.(font);
|
|
3344
|
+
if (registered) return {
|
|
3345
|
+
family: registered.familyName,
|
|
3346
|
+
weight: registered.weight,
|
|
3347
|
+
italic: registered.italic
|
|
3348
|
+
};
|
|
3349
|
+
return { family: font };
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* The DTO `fontFamily` for a body face: the registered `key` whose identity
|
|
3353
|
+
* matches, else the standard font's kebab name, else the family itself.
|
|
3354
|
+
*/
|
|
3355
|
+
function freeTextFontForFace(face, keyForFace) {
|
|
3356
|
+
const key = keyForFace?.(face.family, face.weight, face.italic);
|
|
3357
|
+
if (key !== void 0) return key;
|
|
3358
|
+
const standard = STANDARD_FAMILY_KEYS[familyKey$1(face.family)];
|
|
3359
|
+
if (standard) {
|
|
3360
|
+
const bold = face.weight >= 600;
|
|
3361
|
+
for (const [name, spec] of Object.entries(STANDARD_FACES)) if (spec.family === standard && spec.weight >= 600 === bold && spec.italic === face.italic) return name;
|
|
3362
|
+
for (const [name, spec] of Object.entries(STANDARD_FACES)) if (spec.family === standard) return name;
|
|
3363
|
+
}
|
|
3364
|
+
return face.family;
|
|
3365
|
+
}
|
|
3366
|
+
/** A run style or body with its `family` (and the weight/italic a key or
|
|
3367
|
+
* standard name implies) rewritten to what the engine resolves by. */
|
|
3368
|
+
function withEngineFace(style, describe) {
|
|
3369
|
+
if (style.family === void 0) return style;
|
|
3370
|
+
const face = faceForFreeTextFont(style.family, describe);
|
|
3371
|
+
const out = {
|
|
3372
|
+
...style,
|
|
3373
|
+
family: face.family
|
|
3374
|
+
};
|
|
3375
|
+
if (face.weight !== void 0 && style.weight === void 0) out.weight = face.weight;
|
|
3376
|
+
if (face.italic !== void 0 && style.italic === void 0) out.italic = face.italic;
|
|
3377
|
+
return out;
|
|
3378
|
+
}
|
|
3379
|
+
/** The engine's JSON for a write: the input shape with faces resolved. */
|
|
3380
|
+
function engineRichTextJson(input, describe) {
|
|
3381
|
+
const body = input.body ? withEngineFace(input.body, describe) : void 0;
|
|
3382
|
+
const paragraphs = input.paragraphs.map((p) => ({
|
|
3383
|
+
...p,
|
|
3384
|
+
runs: p.runs.map((r) => r.style ? {
|
|
3385
|
+
...r,
|
|
3386
|
+
style: withEngineFace(r.style, describe)
|
|
3387
|
+
} : r)
|
|
3388
|
+
}));
|
|
3389
|
+
return JSON.stringify(body ? {
|
|
3390
|
+
body,
|
|
3391
|
+
paragraphs
|
|
3392
|
+
} : { paragraphs });
|
|
3393
|
+
}
|
|
3394
|
+
/** The engine's JSON, parsed into the DTO shape (its `source` and
|
|
3395
|
+
* `diagnostics` dropped). `null` when it is not what the engine writes. */
|
|
3396
|
+
function parseEngineRichText(json) {
|
|
3397
|
+
let raw;
|
|
3398
|
+
try {
|
|
3399
|
+
raw = JSON.parse(json);
|
|
3400
|
+
} catch {
|
|
3401
|
+
return null;
|
|
3402
|
+
}
|
|
3403
|
+
if (!raw || typeof raw !== "object" || !raw.body || !Array.isArray(raw.paragraphs)) return null;
|
|
3404
|
+
const { source: _source, ...bodyRest } = raw.body;
|
|
3405
|
+
return {
|
|
3406
|
+
body: bodyRest,
|
|
3407
|
+
paragraphs: raw.paragraphs.map((p) => {
|
|
3408
|
+
const { runs, ...props } = p;
|
|
3409
|
+
return {
|
|
3410
|
+
...props,
|
|
3411
|
+
runs: (runs ?? []).map((r) => r)
|
|
3412
|
+
};
|
|
3413
|
+
})
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
/** Read an annotation's rich text through `EPDFAnnot_GetRichTextJSON`. */
|
|
3417
|
+
function readEngineRichText(fn, mem, annotPtr) {
|
|
3418
|
+
const json = readUtf8String(mem, (buf, cap) => fn.EPDFAnnot_GetRichTextJSON(annotPtr, buf, cap));
|
|
3419
|
+
return json ? parseEngineRichText(json) : null;
|
|
3420
|
+
}
|
|
3421
|
+
/**
|
|
3422
|
+
* A draft or patch that carries both `contents` and `richText` must agree:
|
|
3423
|
+
* `contents` is the plain projection of the rich text (plan §4.7). A stale
|
|
3424
|
+
* DTO echo fails loud here instead of quietly restoring old text.
|
|
3425
|
+
*/
|
|
3426
|
+
function assertRichTextAgreement(input) {
|
|
3427
|
+
if (input.subtype !== "free-text" || input.richText === void 0) return;
|
|
3428
|
+
if (input.contents === void 0 || input.contents === null) return;
|
|
3429
|
+
if (input.contents !== (0, _embedpdf_engine_core_runtime.richTextPlainText)(input.richText)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "free-text: `contents` must equal the plain projection of `richText` when both are given");
|
|
3430
|
+
}
|
|
3431
|
+
//#endregion
|
|
3432
|
+
//#region src/features/annotations/internal/textAlignment.ts
|
|
3433
|
+
/**
|
|
3434
|
+
* `/Q` quadding codes (ISO 32000 §12.7.3.3) mapped to the wire-stable
|
|
3435
|
+
* `TextAlignment` strings. Kept in engine-services so engine-core stays
|
|
3436
|
+
* PDFium-free (mirrors `lineEnding.ts`).
|
|
3437
|
+
*
|
|
3438
|
+
* Left=0, Center=1, Right=2
|
|
3439
|
+
*/
|
|
3440
|
+
const Q_LEFT = 0;
|
|
3441
|
+
const Q_CENTER = 1;
|
|
3442
|
+
const Q_RIGHT = 2;
|
|
3443
|
+
function textAlignmentToCode(align) {
|
|
3029
3444
|
switch (align) {
|
|
3030
3445
|
case "center": return Q_CENTER;
|
|
3031
3446
|
case "right": return Q_RIGHT;
|
|
@@ -3140,17 +3555,36 @@ const DEFAULT_FONT_SIZE$2 = 12;
|
|
|
3140
3555
|
function colorsEqual(a, b) {
|
|
3141
3556
|
return a.r === b.r && a.g === b.g && a.b === b.b;
|
|
3142
3557
|
}
|
|
3143
|
-
function readFreeText(fn, mem, annotPtr, base) {
|
|
3558
|
+
function readFreeText(fn, mem, annotPtr, base, _subtypeCode, ctx) {
|
|
3144
3559
|
const da = readDefaultAppearance(fn, mem, annotPtr);
|
|
3145
3560
|
const color = da?.color ?? { ...DEFAULT_FREETEXT_COLOR$1 };
|
|
3146
|
-
const
|
|
3147
|
-
const
|
|
3561
|
+
const rich = readEngineRichText(fn, mem, annotPtr);
|
|
3562
|
+
const fonts = ctx?.fonts;
|
|
3563
|
+
const fontFamily = rich ? freeTextFontForFace(rich.body, fonts ? (family, weight, italic) => fonts.keyForFace(family, weight, italic) : void 0) : da ? standardFontFromCode(da.fontCode) : DEFAULT_STANDARD_FONT;
|
|
3564
|
+
const fontSize = rich && rich.body.size > 0 ? rich.body.size : da && da.fontSize > 0 ? da.fontSize : DEFAULT_FONT_SIZE$2;
|
|
3565
|
+
const richText = rich ?? {
|
|
3566
|
+
body: {
|
|
3567
|
+
family: "Helvetica",
|
|
3568
|
+
weight: 400,
|
|
3569
|
+
italic: false,
|
|
3570
|
+
size: fontSize,
|
|
3571
|
+
color: "#000000",
|
|
3572
|
+
decoration: [],
|
|
3573
|
+
script: "normal",
|
|
3574
|
+
letterSpacing: 0,
|
|
3575
|
+
horizontalScale: 1,
|
|
3576
|
+
align: "left",
|
|
3577
|
+
dir: "ltr"
|
|
3578
|
+
},
|
|
3579
|
+
paragraphs: [{ runs: [{ text: base.contents ?? "" }] }]
|
|
3580
|
+
};
|
|
3148
3581
|
const textColor = readAnnotColor(fn, mem, annotPtr, FPDFANNOT_COLORTYPE.TextColor);
|
|
3149
3582
|
const fontColor = textColor && !colorsEqual(textColor, color) ? textColor : void 0;
|
|
3150
3583
|
const background = readAnnotColor(fn, mem, annotPtr, FPDFANNOT_COLORTYPE.Color);
|
|
3151
3584
|
const ca = readAnnotOpacity(fn, mem, annotPtr);
|
|
3152
3585
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
3153
|
-
const
|
|
3586
|
+
const quadding = textAlignmentFromCode(readTextAlignment(fn, annotPtr));
|
|
3587
|
+
const textAlign = rich && rich.body.align !== "justify" ? rich.body.align : quadding;
|
|
3154
3588
|
const intent = freeTextIntentFromName(readIntent(fn, mem, annotPtr));
|
|
3155
3589
|
const points = readCalloutLine(fn, mem, annotPtr);
|
|
3156
3590
|
const calloutLine = points.length === 2 ? [points[0], points[1]] : points.length === 3 ? [
|
|
@@ -3169,6 +3603,7 @@ function readFreeText(fn, mem, annotPtr, base) {
|
|
|
3169
3603
|
fontFamily,
|
|
3170
3604
|
fontSize,
|
|
3171
3605
|
textAlign,
|
|
3606
|
+
richText,
|
|
3172
3607
|
color,
|
|
3173
3608
|
...fontColor !== void 0 ? { fontColor } : {},
|
|
3174
3609
|
interiorColor: background ?? null,
|
|
@@ -3681,18 +4116,18 @@ function pickReader(rawSubtypeCode) {
|
|
|
3681
4116
|
* per session, closed before replacement and on session teardown via
|
|
3682
4117
|
* {@link disposeFormModel}.
|
|
3683
4118
|
*/
|
|
3684
|
-
const cache$
|
|
4119
|
+
const cache$2 = /* @__PURE__ */ new WeakMap();
|
|
3685
4120
|
function acquireFormModel(runtime, session) {
|
|
3686
4121
|
const seq = session.mutationSeq();
|
|
3687
|
-
const hit = cache$
|
|
4122
|
+
const hit = cache$2.get(session);
|
|
3688
4123
|
if (hit && hit.seq === seq) return hit.ptr;
|
|
3689
4124
|
if (hit) {
|
|
3690
4125
|
runtime.fn.EPDFForm_CloseModel(hit.ptr);
|
|
3691
|
-
cache$
|
|
4126
|
+
cache$2.delete(session);
|
|
3692
4127
|
}
|
|
3693
4128
|
const ptr = runtime.fn.EPDFForm_LoadModel(session.requireDocPtr());
|
|
3694
4129
|
if (ptr === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to build form model");
|
|
3695
|
-
cache$
|
|
4130
|
+
cache$2.set(session, {
|
|
3696
4131
|
seq,
|
|
3697
4132
|
ptr
|
|
3698
4133
|
});
|
|
@@ -3700,10 +4135,10 @@ function acquireFormModel(runtime, session) {
|
|
|
3700
4135
|
}
|
|
3701
4136
|
/** Close and forget the session's cached model, if any. */
|
|
3702
4137
|
function disposeFormModel(runtime, session) {
|
|
3703
|
-
const hit = cache$
|
|
4138
|
+
const hit = cache$2.get(session);
|
|
3704
4139
|
if (!hit) return;
|
|
3705
4140
|
runtime.fn.EPDFForm_CloseModel(hit.ptr);
|
|
3706
|
-
cache$
|
|
4141
|
+
cache$2.delete(session);
|
|
3707
4142
|
}
|
|
3708
4143
|
//#endregion
|
|
3709
4144
|
//#region src/features/forms/internal/readFormSnapshot.ts
|
|
@@ -3732,7 +4167,7 @@ const FF_EDIT = 1 << 18;
|
|
|
3732
4167
|
const FF_MULTI_SELECT = 1 << 21;
|
|
3733
4168
|
const FF_COMB = 1 << 24;
|
|
3734
4169
|
const FF_RADIOS_IN_UNISON = 1 << 25;
|
|
3735
|
-
function readWide(runtime, call) {
|
|
4170
|
+
function readWide$1(runtime, call) {
|
|
3736
4171
|
return readUtf16String(runtime.mem, call) ?? "";
|
|
3737
4172
|
}
|
|
3738
4173
|
function readWideOrNull(runtime, call) {
|
|
@@ -3746,7 +4181,7 @@ function readToggleWidgets(runtime, model, fieldIndex) {
|
|
|
3746
4181
|
annotObjectNumber: fn.EPDFForm_GetFieldWidgetObjNum(model, fieldIndex, w),
|
|
3747
4182
|
pageObjectNumber: fn.EPDFForm_GetFieldWidgetPageObjNum(model, fieldIndex, w),
|
|
3748
4183
|
onState: readUtf8String(runtime.mem, (buf, cap) => fn.EPDFForm_GetFieldWidgetOnState(model, fieldIndex, w, buf, cap)) ?? "",
|
|
3749
|
-
exportValue: readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldWidgetExportValue(model, fieldIndex, w, buf, cap)),
|
|
4184
|
+
exportValue: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldWidgetExportValue(model, fieldIndex, w, buf, cap)),
|
|
3750
4185
|
checked: fn.EPDFForm_IsFieldWidgetChecked(model, fieldIndex, w)
|
|
3751
4186
|
});
|
|
3752
4187
|
return widgets;
|
|
@@ -3766,8 +4201,8 @@ function readOptions(runtime, model, fieldIndex) {
|
|
|
3766
4201
|
const count = fn.EPDFForm_CountFieldOptions(model, fieldIndex);
|
|
3767
4202
|
const options = [];
|
|
3768
4203
|
for (let o = 0; o < count; o++) options.push({
|
|
3769
|
-
label: readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionLabel(model, fieldIndex, o, buf, cap)),
|
|
3770
|
-
value: readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionValue(model, fieldIndex, o, buf, cap)),
|
|
4204
|
+
label: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionLabel(model, fieldIndex, o, buf, cap)),
|
|
4205
|
+
value: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionValue(model, fieldIndex, o, buf, cap)),
|
|
3771
4206
|
selected: fn.EPDFForm_IsFieldOptionSelected(model, fieldIndex, o)
|
|
3772
4207
|
});
|
|
3773
4208
|
return options;
|
|
@@ -3776,7 +4211,7 @@ function readOptions(runtime, model, fieldIndex) {
|
|
|
3776
4211
|
function readFieldAt(runtime, model, fieldIndex, docPtr, actionBudget = new ActionReadBudgetTracker()) {
|
|
3777
4212
|
const { fn } = runtime;
|
|
3778
4213
|
const fieldObjectNumber = fn.EPDFForm_GetFieldObjNum(model, fieldIndex);
|
|
3779
|
-
const name = readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
|
|
4214
|
+
const name = readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
|
|
3780
4215
|
const rawFlags = fn.EPDFForm_GetFieldFlags(model, fieldIndex);
|
|
3781
4216
|
const family = FAMILY_BY_CODE[fn.EPDFForm_GetFieldFamily(model, fieldIndex)] ?? "unknown";
|
|
3782
4217
|
const valueEntry = readValueEntry(runtime, model, fieldIndex, false);
|
|
@@ -3907,7 +4342,7 @@ function readValueEntry(runtime, model, fieldIndex, isDefault) {
|
|
|
3907
4342
|
if (kind !== 1 && kind !== 2) return { kind: "unsupported" };
|
|
3908
4343
|
const count = isDefault ? fn.EPDFForm_CountFieldDefaultValues(model, fieldIndex) : fn.EPDFForm_CountFieldValues(model, fieldIndex);
|
|
3909
4344
|
const values = [];
|
|
3910
|
-
for (let index = 0; index < count; index++) values.push(readWide(runtime, (buf, capacity) => isDefault ? fn.EPDFForm_GetFieldDefaultValueAt(model, fieldIndex, index, buf, capacity) : fn.EPDFForm_GetFieldValueAt(model, fieldIndex, index, buf, capacity)));
|
|
4345
|
+
for (let index = 0; index < count; index++) values.push(readWide$1(runtime, (buf, capacity) => isDefault ? fn.EPDFForm_GetFieldDefaultValueAt(model, fieldIndex, index, buf, capacity) : fn.EPDFForm_GetFieldValueAt(model, fieldIndex, index, buf, capacity)));
|
|
3911
4346
|
return kind === 1 ? {
|
|
3912
4347
|
kind: "scalar",
|
|
3913
4348
|
value: values[0] ?? ""
|
|
@@ -4248,13 +4683,13 @@ function readAnnotationActions(fn, mem, docPtr, annotPtr, budget) {
|
|
|
4248
4683
|
* releasing any enclosing `pagePtr`.
|
|
4249
4684
|
*/
|
|
4250
4685
|
function collectPageAnnotations(input) {
|
|
4251
|
-
const { runtime, session, pageObjectNumber, count, getAnnotPtrAt, signal } = input;
|
|
4686
|
+
const { runtime, session, pageObjectNumber, count, getAnnotPtrAt, signal, fonts } = input;
|
|
4252
4687
|
const { fn, mem } = runtime;
|
|
4253
4688
|
const annotations = [];
|
|
4254
4689
|
let hasWeak = false;
|
|
4255
4690
|
const revision = session.pageState(pageObjectNumber).revision;
|
|
4256
4691
|
const actionBudget = new ActionReadBudgetTracker();
|
|
4257
|
-
const readCtx = readContextFor(session);
|
|
4692
|
+
const readCtx = readContextFor(session, fonts);
|
|
4258
4693
|
for (let i = 0; i < count; i++) {
|
|
4259
4694
|
throwIfAborted(signal);
|
|
4260
4695
|
const annotPtr = getAnnotPtrAt(i);
|
|
@@ -4290,9 +4725,10 @@ function collectPageAnnotations(input) {
|
|
|
4290
4725
|
* as they land - the public DTO surface stays the same.
|
|
4291
4726
|
*/
|
|
4292
4727
|
var AnnotationReader = class {
|
|
4293
|
-
constructor(runtime, session) {
|
|
4728
|
+
constructor(runtime, session, fonts) {
|
|
4294
4729
|
this.runtime = runtime;
|
|
4295
4730
|
this.session = session;
|
|
4731
|
+
this.fonts = fonts;
|
|
4296
4732
|
}
|
|
4297
4733
|
list(pageObjectNumber, signal) {
|
|
4298
4734
|
throwIfAborted(signal);
|
|
@@ -4307,7 +4743,8 @@ var AnnotationReader = class {
|
|
|
4307
4743
|
pageObjectNumber,
|
|
4308
4744
|
count,
|
|
4309
4745
|
getAnnotPtrAt: (i) => fn.FPDFPage_GetAnnot(pagePtr, i),
|
|
4310
|
-
signal
|
|
4746
|
+
signal,
|
|
4747
|
+
...this.fonts ? { fonts: this.fonts } : {}
|
|
4311
4748
|
});
|
|
4312
4749
|
} finally {
|
|
4313
4750
|
pool.release(pageObjectNumber);
|
|
@@ -4636,9 +5073,10 @@ function normalizeScale(scale) {
|
|
|
4636
5073
|
* actually need a pagePtr to materialise their fields.
|
|
4637
5074
|
*/
|
|
4638
5075
|
var RawAnnotationReader = class {
|
|
4639
|
-
constructor(runtime, session) {
|
|
5076
|
+
constructor(runtime, session, fonts) {
|
|
4640
5077
|
this.runtime = runtime;
|
|
4641
5078
|
this.session = session;
|
|
5079
|
+
this.fonts = fonts;
|
|
4642
5080
|
}
|
|
4643
5081
|
listAll(signal) {
|
|
4644
5082
|
throwIfAborted(signal);
|
|
@@ -4664,7 +5102,8 @@ var RawAnnotationReader = class {
|
|
|
4664
5102
|
pageObjectNumber,
|
|
4665
5103
|
count,
|
|
4666
5104
|
getAnnotPtrAt: (i) => fn.EPDFPage_GetAnnotRaw(docPtr, record.pageIndex, i),
|
|
4667
|
-
signal
|
|
5105
|
+
signal,
|
|
5106
|
+
...this.fonts ? { fonts: this.fonts } : {}
|
|
4668
5107
|
});
|
|
4669
5108
|
}
|
|
4670
5109
|
};
|
|
@@ -5605,6 +6044,20 @@ const DEFAULT_FREETEXT_COLOR = {
|
|
|
5605
6044
|
b: 0
|
|
5606
6045
|
};
|
|
5607
6046
|
/**
|
|
6047
|
+
* Write rich text through the engine's rich writer: `/RC`, `/DS`, `/DA`,
|
|
6048
|
+
* `/Contents` and the appearance, all or nothing. Faces in the input are
|
|
6049
|
+
* resolved from keys / standard names to the identities the engine names.
|
|
6050
|
+
*/
|
|
6051
|
+
function writeRichText(fn, annotPtr, input, ctx) {
|
|
6052
|
+
const json = engineRichTextJson(input, ctx?.describeRegisteredFont);
|
|
6053
|
+
if (!fn.EPDFAnnot_SetRichTextJSON(annotPtr, json)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "rich text could not be written (empty rect, or a font resource could not be built)");
|
|
6054
|
+
}
|
|
6055
|
+
/** `#RRGGBB` for a rich body colour. */
|
|
6056
|
+
function hexColor(color) {
|
|
6057
|
+
const hex = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
6058
|
+
return `#${hex(color.r)}${hex(color.g)}${hex(color.b)}`.toUpperCase();
|
|
6059
|
+
}
|
|
6060
|
+
/**
|
|
5608
6061
|
* Apply a free-text draft to a freshly-created annotation. Colour model:
|
|
5609
6062
|
* - `color` -> `/DA` colour = border + default text colour.
|
|
5610
6063
|
* - `fontColor` (optional) -> `TextColor` channel, overriding text only;
|
|
@@ -5646,6 +6099,7 @@ function applyFreeTextDraft(fn, mem, annotPtr, draft, ctx) {
|
|
|
5646
6099
|
rotation: draft.rotation,
|
|
5647
6100
|
unrotatedRect: draft.unrotatedRect
|
|
5648
6101
|
});
|
|
6102
|
+
writeRichText(fn, annotPtr, draft.richText ?? { paragraphs: (0, _embedpdf_engine_core_runtime.richTextParagraphsFromPlainText)(draft.contents ?? "") }, ctx);
|
|
5649
6103
|
}
|
|
5650
6104
|
/**
|
|
5651
6105
|
* Apply a free-text patch to an existing annotation. Only present fields are
|
|
@@ -5672,11 +6126,30 @@ function applyFreeTextPatch(fn, mem, annotPtr, patch, ctx) {
|
|
|
5672
6126
|
else setAnnotColor(fn, annotPtr, patch.interiorColor, FPDFANNOT_COLORTYPE.Color);
|
|
5673
6127
|
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5674
6128
|
applyBorderPatch(fn, mem, annotPtr, patch);
|
|
5675
|
-
|
|
6129
|
+
const current = readEngineRichText(fn, mem, annotPtr);
|
|
6130
|
+
if (patch.color !== void 0) {
|
|
5676
6131
|
const cur = readDefaultAppearance(fn, mem, annotPtr);
|
|
5677
|
-
applyDefaultAppearance(fn, annotPtr,
|
|
6132
|
+
applyDefaultAppearance(fn, annotPtr, cur ? standardFontFromCode(cur.fontCode) : DEFAULT_STANDARD_FONT, cur && cur.fontSize > 0 ? cur.fontSize : 12, patch.color, ctx);
|
|
5678
6133
|
}
|
|
5679
6134
|
if (patch.fontColor !== void 0) setAnnotColor(fn, annotPtr, patch.fontColor, FPDFANNOT_COLORTYPE.TextColor);
|
|
6135
|
+
if (patch.richText !== void 0) writeRichText(fn, annotPtr, patch.richText, ctx);
|
|
6136
|
+
else if (patch.contents !== void 0) writeRichText(fn, annotPtr, { paragraphs: (0, _embedpdf_engine_core_runtime.richTextParagraphsFromPlainText)(patch.contents ?? "") }, ctx);
|
|
6137
|
+
else if (current && (patch.fontFamily !== void 0 || patch.fontSize !== void 0 || patch.fontColor !== void 0 || patch.textAlign !== void 0)) {
|
|
6138
|
+
const body = { ...current.body };
|
|
6139
|
+
if (patch.fontFamily !== void 0) {
|
|
6140
|
+
const face = faceForFreeTextFont(patch.fontFamily, ctx?.describeRegisteredFont);
|
|
6141
|
+
body.family = face.family;
|
|
6142
|
+
if (face.weight !== void 0) body.weight = face.weight;
|
|
6143
|
+
if (face.italic !== void 0) body.italic = face.italic;
|
|
6144
|
+
}
|
|
6145
|
+
if (patch.fontSize !== void 0) body.size = patch.fontSize;
|
|
6146
|
+
if (patch.fontColor !== void 0) body.color = hexColor(patch.fontColor);
|
|
6147
|
+
if (patch.textAlign !== void 0) body.align = patch.textAlign;
|
|
6148
|
+
writeRichText(fn, annotPtr, {
|
|
6149
|
+
body,
|
|
6150
|
+
paragraphs: current.paragraphs
|
|
6151
|
+
}, ctx);
|
|
6152
|
+
}
|
|
5680
6153
|
if (patch.textAlign !== void 0) setTextAlignment(fn, annotPtr, textAlignmentToCode(patch.textAlign));
|
|
5681
6154
|
if (patch.intent !== void 0) setIntent(fn, annotPtr, freeTextIntentToName(patch.intent));
|
|
5682
6155
|
if (patch.rectDifferences === null) clearRectangleDifferences(fn, annotPtr);
|
|
@@ -6726,7 +7199,10 @@ var AnnotationMutator = class {
|
|
|
6726
7199
|
writeContext(pagePtr, resources) {
|
|
6727
7200
|
const fonts = this.fonts;
|
|
6728
7201
|
return {
|
|
6729
|
-
...fonts ? {
|
|
7202
|
+
...fonts ? {
|
|
7203
|
+
resolveRegisteredFontId: (key) => fonts.idFor(key),
|
|
7204
|
+
describeRegisteredFont: (key) => fonts.describeOrUndefined(key)
|
|
7205
|
+
} : {},
|
|
6730
7206
|
docPtr: this.session.requireDocPtr(),
|
|
6731
7207
|
pagePtr,
|
|
6732
7208
|
...resources ? { resources } : {}
|
|
@@ -6742,6 +7218,7 @@ var AnnotationMutator = class {
|
|
|
6742
7218
|
this.ensureKnownWeakStateFromPage(pageObjectNumber, pagePtr);
|
|
6743
7219
|
const writeCtx = this.writeContext(pagePtr, resources);
|
|
6744
7220
|
preflightDraft(draft, writeCtx);
|
|
7221
|
+
assertRichTextAgreement(draft);
|
|
6745
7222
|
const pageStateBefore = this.session.pageState(pageObjectNumber);
|
|
6746
7223
|
throwIfAborted(signal);
|
|
6747
7224
|
const annotPtr = fn.EPDFPage_CreateAnnot(pagePtr, subtypeCode);
|
|
@@ -6764,7 +7241,7 @@ var AnnotationMutator = class {
|
|
|
6764
7241
|
if (newObjNum <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `EPDFPage_CreateAnnot produced a direct object (no objectNumber); fork helper invariant broken`);
|
|
6765
7242
|
newIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
|
|
6766
7243
|
if (newIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_GetAnnotIndex returned ${newIndex} for freshly created annotation`);
|
|
6767
|
-
dto = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session));
|
|
7244
|
+
dto = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
|
|
6768
7245
|
joinWidgetFieldNumbers(this.runtime, this.session, [dto]);
|
|
6769
7246
|
} finally {
|
|
6770
7247
|
fn.FPDFPage_CloseAnnot(annotPtr);
|
|
@@ -6799,6 +7276,7 @@ var AnnotationMutator = class {
|
|
|
6799
7276
|
throwIfAborted(signal);
|
|
6800
7277
|
const writeCtx = this.writeContext(pagePtr, resources);
|
|
6801
7278
|
preflightPatch(patch, writeCtx);
|
|
7279
|
+
assertRichTextAgreement(patch);
|
|
6802
7280
|
this.ensureKnownWeakStateFromPage(ref.pageObjectNumber, pagePtr);
|
|
6803
7281
|
const pageStateBefore = this.session.pageState(ref.pageObjectNumber);
|
|
6804
7282
|
throwIfAborted(signal);
|
|
@@ -6806,7 +7284,7 @@ var AnnotationMutator = class {
|
|
|
6806
7284
|
const previousBlendMode = blendModeFromCode(fn.EPDFAnnot_GetBlendMode(annotPtr));
|
|
6807
7285
|
const preIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
|
|
6808
7286
|
if (preIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_GetAnnotIndex returned ${preIndex} before update`);
|
|
6809
|
-
const currentDto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, preIndex, pageStateBefore.revision, readContextFor(this.session));
|
|
7287
|
+
const currentDto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, preIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
|
|
6810
7288
|
applyPatch(fn, mem, annotPtr, patch, writeCtx);
|
|
6811
7289
|
let linkedParentId = null;
|
|
6812
7290
|
if (patch.inReplyTo !== void 0 || patch.replyType !== void 0) linkedParentId = writeAnnotationRelationship(this.runtime, this.session, pagePtr, annotPtr, ref.pageObjectNumber, {
|
|
@@ -6833,7 +7311,7 @@ var AnnotationMutator = class {
|
|
|
6833
7311
|
annotPtr = resolveAnnotPtr(this.runtime, this.session, pagePtr, ref);
|
|
6834
7312
|
const newIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
|
|
6835
7313
|
if (newIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_GetAnnotIndex returned ${newIndex} after update`);
|
|
6836
|
-
const dto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session));
|
|
7314
|
+
const dto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
|
|
6837
7315
|
joinWidgetFieldNumbers(this.runtime, this.session, [dto]);
|
|
6838
7316
|
this.recordWeakStateFromPage(ref.pageObjectNumber, pagePtr);
|
|
6839
7317
|
const meta = computeMutationImpact({
|
|
@@ -7046,7 +7524,7 @@ var AnnotationMutator = class {
|
|
|
7046
7524
|
const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, newIdx);
|
|
7047
7525
|
if (!annotPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `failed to re-read moved annotation at index ${newIdx}`);
|
|
7048
7526
|
try {
|
|
7049
|
-
moved[i] = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIdx, bumpedRev, readContextFor(this.session));
|
|
7527
|
+
moved[i] = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIdx, bumpedRev, readContextFor(this.session, this.fonts));
|
|
7050
7528
|
} finally {
|
|
7051
7529
|
fn.FPDFPage_CloseAnnot(annotPtr);
|
|
7052
7530
|
}
|
|
@@ -8308,7 +8786,480 @@ function layerSaveError(status) {
|
|
|
8308
8786
|
return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save layer artifact");
|
|
8309
8787
|
}
|
|
8310
8788
|
//#endregion
|
|
8789
|
+
//#region src/runtime/memory/u64.ts
|
|
8790
|
+
/**
|
|
8791
|
+
* 64-bit out-params as two little-endian 32-bit halves: the wasm memory
|
|
8792
|
+
* helpers have no 64-bit accessors (Emscripten's `setValue(i64)` needs
|
|
8793
|
+
* WASM_BIGINT), and the native runtime reads the same layout.
|
|
8794
|
+
*/
|
|
8795
|
+
function pokeU64(mem, ptr, value, byteOffset = 0) {
|
|
8796
|
+
const big = BigInt(value);
|
|
8797
|
+
mem.poke(ptr, "i32", Number(big & 4294967295n) | 0, byteOffset);
|
|
8798
|
+
mem.poke(ptr, "i32", Number(big >> 32n & 4294967295n) | 0, byteOffset + 4);
|
|
8799
|
+
}
|
|
8800
|
+
function peekU64(mem, ptr, byteOffset = 0) {
|
|
8801
|
+
const lo = Number(mem.peek(ptr, "i32", byteOffset)) >>> 0;
|
|
8802
|
+
return (Number(mem.peek(ptr, "i32", byteOffset + 4)) >>> 0) * 4294967296 + lo;
|
|
8803
|
+
}
|
|
8804
|
+
//#endregion
|
|
8805
|
+
//#region src/features/signature/internal/readSignatureModel.ts
|
|
8806
|
+
const KIND_DOC_TIMESTAMP = 1;
|
|
8807
|
+
const COVERAGE_BY_CODE = {
|
|
8808
|
+
0: "whole-revision",
|
|
8809
|
+
1: "partial",
|
|
8810
|
+
2: "malformed"
|
|
8811
|
+
};
|
|
8812
|
+
const STRING_FILTER = 0;
|
|
8813
|
+
const STRING_SUBFILTER = 1;
|
|
8814
|
+
const STRING_NAME = 2;
|
|
8815
|
+
const STRING_REASON = 3;
|
|
8816
|
+
const STRING_LOCATION = 4;
|
|
8817
|
+
const STRING_CONTACT_INFO = 5;
|
|
8818
|
+
const STRING_M = 6;
|
|
8819
|
+
const FIELD_ACTION_BY_CODE = {
|
|
8820
|
+
1: "all",
|
|
8821
|
+
2: "include",
|
|
8822
|
+
3: "exclude"
|
|
8823
|
+
};
|
|
8824
|
+
const FIELDS_FIELDMDP = 0;
|
|
8825
|
+
const FIELDS_LOCK = 1;
|
|
8826
|
+
const SV_V = 4;
|
|
8827
|
+
const SV_MDP = 1 << 17;
|
|
8828
|
+
const SV_LIST_SUBFILTER = 0;
|
|
8829
|
+
const SV_LIST_DIGEST_METHOD = 1;
|
|
8830
|
+
const SV_LIST_REASONS = 2;
|
|
8831
|
+
/** The PDF 1.7 seed-value entry set this engine implements. */
|
|
8832
|
+
const SUPPORTED_SEED_VALUE_VERSION = 2;
|
|
8833
|
+
const SV_UNSUPPORTED_REQUIRED = 432;
|
|
8834
|
+
function readWide(runtime, call) {
|
|
8835
|
+
return readUtf16String(runtime.mem, call, "");
|
|
8836
|
+
}
|
|
8837
|
+
function isPermission(value) {
|
|
8838
|
+
return value === 1 || value === 2 || value === 3;
|
|
8839
|
+
}
|
|
8840
|
+
function readByteRange(runtime, model, index) {
|
|
8841
|
+
const { fn, mem } = runtime;
|
|
8842
|
+
return withScratch(mem, 32, (ptr) => {
|
|
8843
|
+
for (let k = 0; k < 4; k++) pokeU64(mem, ptr, 0, k * 8);
|
|
8844
|
+
if (!fn.EPDFSig_GetByteRange(model, index, ptr)) return null;
|
|
8845
|
+
return [
|
|
8846
|
+
peekU64(mem, ptr, 0),
|
|
8847
|
+
peekU64(mem, ptr, 8),
|
|
8848
|
+
peekU64(mem, ptr, 16),
|
|
8849
|
+
peekU64(mem, ptr, 24)
|
|
8850
|
+
];
|
|
8851
|
+
});
|
|
8852
|
+
}
|
|
8853
|
+
function readLock(runtime, model, index, which) {
|
|
8854
|
+
const { fn } = runtime;
|
|
8855
|
+
const code = which === FIELDS_LOCK ? fn.EPDFSig_GetLockAction(model, index) : fn.EPDFSig_GetFieldMDPAction(model, index);
|
|
8856
|
+
const action = FIELD_ACTION_BY_CODE[code];
|
|
8857
|
+
if (!action) return null;
|
|
8858
|
+
const fields = [];
|
|
8859
|
+
const count = fn.EPDFSig_GetFieldNameCount(model, index, which);
|
|
8860
|
+
for (let n = 0; n < count; n++) fields.push(readWide(runtime, (buf, cap) => fn.EPDFSig_GetFieldNameAt(model, index, which, n, buf, cap)) ?? "");
|
|
8861
|
+
const spec = {
|
|
8862
|
+
action,
|
|
8863
|
+
fields
|
|
8864
|
+
};
|
|
8865
|
+
if (which === FIELDS_LOCK) {
|
|
8866
|
+
const permission = fn.EPDFSig_GetLockPermission(model, index);
|
|
8867
|
+
if (isPermission(permission)) spec.permission = permission;
|
|
8868
|
+
}
|
|
8869
|
+
return spec;
|
|
8870
|
+
}
|
|
8871
|
+
function readSeedValue(runtime, model, index) {
|
|
8872
|
+
const { fn } = runtime;
|
|
8873
|
+
if (!fn.EPDFSig_HasSeedValue(model, index)) return null;
|
|
8874
|
+
const requiredFlags = fn.EPDFSig_GetSeedValueRequiredFlags(model, index) >>> 0;
|
|
8875
|
+
const presentFlags = fn.EPDFSig_GetSeedValuePresentFlags(model, index) >>> 0;
|
|
8876
|
+
const version = fn.EPDFSig_GetSeedValueVersion(model, index);
|
|
8877
|
+
const mdp = fn.EPDFSig_GetSeedValueMDP(model, index);
|
|
8878
|
+
const list = (which) => {
|
|
8879
|
+
const count = fn.EPDFSig_GetSeedValueListCount(model, index, which);
|
|
8880
|
+
const items = [];
|
|
8881
|
+
for (let n = 0; n < count; n++) items.push(readWide(runtime, (buf, cap) => fn.EPDFSig_GetSeedValueListAt(model, index, which, n, buf, cap)) ?? "");
|
|
8882
|
+
return items;
|
|
8883
|
+
};
|
|
8884
|
+
return {
|
|
8885
|
+
requiredFlags,
|
|
8886
|
+
presentFlags,
|
|
8887
|
+
version: version > 0 ? version : null,
|
|
8888
|
+
mdp: (presentFlags & SV_MDP) !== 0 && mdp >= 0 && mdp <= 3 ? mdp : null,
|
|
8889
|
+
filter: readWide(runtime, (buf, cap) => fn.EPDFSig_GetSeedValueFilter(model, index, buf, cap)),
|
|
8890
|
+
subFilters: list(SV_LIST_SUBFILTER),
|
|
8891
|
+
digestMethods: list(SV_LIST_DIGEST_METHOD),
|
|
8892
|
+
reasons: list(SV_LIST_REASONS),
|
|
8893
|
+
unsupportedRequired: (requiredFlags & SV_UNSUPPORTED_REQUIRED) !== 0 || (requiredFlags & SV_V) !== 0 && version > SUPPORTED_SEED_VALUE_VERSION
|
|
8894
|
+
};
|
|
8895
|
+
}
|
|
8896
|
+
/** Every signature field of a native signature model, in model order. */
|
|
8897
|
+
function readSignaturesFromModel(runtime, model) {
|
|
8898
|
+
const { fn } = runtime;
|
|
8899
|
+
const count = fn.EPDFSig_Count(model);
|
|
8900
|
+
const out = [];
|
|
8901
|
+
const str = (i, key) => readWide(runtime, (buf, cap) => fn.EPDFSig_GetString(model, i, key, buf, cap));
|
|
8902
|
+
for (let i = 0; i < count; i++) {
|
|
8903
|
+
const signed = fn.EPDFSig_IsSigned(model, i);
|
|
8904
|
+
const widgetObjNum = fn.EPDFSig_GetWidgetObjNum(model, i);
|
|
8905
|
+
const revisionIndex = fn.EPDFSig_GetRevisionIndex(model, i);
|
|
8906
|
+
const docMdp = fn.EPDFSig_GetDocMDPPermission(model, i);
|
|
8907
|
+
out.push({
|
|
8908
|
+
index: i,
|
|
8909
|
+
field: {
|
|
8910
|
+
kind: "objectNumber",
|
|
8911
|
+
fieldObjectNumber: fn.EPDFSig_GetFieldObjNum(model, i)
|
|
8912
|
+
},
|
|
8913
|
+
fieldName: readWide(runtime, (buf, cap) => fn.EPDFSig_GetFieldName(model, i, buf, cap)) ?? "",
|
|
8914
|
+
widget: widgetObjNum > 0 ? {
|
|
8915
|
+
annotObjectNumber: widgetObjNum,
|
|
8916
|
+
pageObjectNumber: fn.EPDFSig_GetWidgetPageObjNum(model, i)
|
|
8917
|
+
} : null,
|
|
8918
|
+
signed,
|
|
8919
|
+
kind: fn.EPDFSig_GetKind(model, i) === KIND_DOC_TIMESTAMP ? "timestamp" : "signature",
|
|
8920
|
+
filter: str(i, STRING_FILTER),
|
|
8921
|
+
subFilter: str(i, STRING_SUBFILTER),
|
|
8922
|
+
byteRange: signed ? readByteRange(runtime, model, i) : null,
|
|
8923
|
+
contentsSize: signed ? Math.max(0, fn.EPDFSig_GetContents(model, i, _embedpdf_engine_runtime.NULL_PTR, 0)) : 0,
|
|
8924
|
+
coverage: signed ? COVERAGE_BY_CODE[fn.EPDFSig_GetCoverage(model, i)] ?? "malformed" : null,
|
|
8925
|
+
revisionIndex: signed && revisionIndex >= 0 ? revisionIndex : null,
|
|
8926
|
+
signer: {
|
|
8927
|
+
name: str(i, STRING_NAME),
|
|
8928
|
+
reason: str(i, STRING_REASON),
|
|
8929
|
+
location: str(i, STRING_LOCATION),
|
|
8930
|
+
contactInfo: str(i, STRING_CONTACT_INFO),
|
|
8931
|
+
claimedTime: str(i, STRING_M)
|
|
8932
|
+
},
|
|
8933
|
+
docMdp: isPermission(docMdp) ? docMdp : null,
|
|
8934
|
+
catalogCertification: fn.EPDFSig_IsCatalogCertification(model, i),
|
|
8935
|
+
fieldMdp: readLock(runtime, model, i, FIELDS_FIELDMDP),
|
|
8936
|
+
lock: readLock(runtime, model, i, FIELDS_LOCK),
|
|
8937
|
+
seedValue: readSeedValue(runtime, model, i)
|
|
8938
|
+
});
|
|
8939
|
+
}
|
|
8940
|
+
return out;
|
|
8941
|
+
}
|
|
8942
|
+
/** The DER `/Contents` of signature `index`, copied out; `NotFound` when unsigned or malformed. */
|
|
8943
|
+
function readContentsAt(runtime, model, index) {
|
|
8944
|
+
const { fn, mem } = runtime;
|
|
8945
|
+
const length = fn.EPDFSig_GetContents(model, index, _embedpdf_engine_runtime.NULL_PTR, 0);
|
|
8946
|
+
if (length <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature has no usable /Contents (malformed encoding)");
|
|
8947
|
+
return withScratch(mem, length, (buf) => {
|
|
8948
|
+
if (fn.EPDFSig_GetContents(model, index, buf, length) !== length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to read signature contents");
|
|
8949
|
+
const out = new Uint8Array(length);
|
|
8950
|
+
out.set(mem.readBytes(buf, length));
|
|
8951
|
+
return out;
|
|
8952
|
+
});
|
|
8953
|
+
}
|
|
8954
|
+
/** The chained revisions of a document, oldest first; empty when the chain is not valid. */
|
|
8955
|
+
function readRevisions(runtime, docPtr, signatures) {
|
|
8956
|
+
const { fn, mem } = runtime;
|
|
8957
|
+
const count = fn.EPDFDoc_GetRevisionCount(docPtr);
|
|
8958
|
+
const revisions = [];
|
|
8959
|
+
if (count <= 0) return revisions;
|
|
8960
|
+
withScratchN(mem, [8, 8], ([endPtr, xrefPtr]) => {
|
|
8961
|
+
for (let i = 0; i < count; i++) {
|
|
8962
|
+
pokeU64(mem, endPtr, 0);
|
|
8963
|
+
pokeU64(mem, xrefPtr, 0);
|
|
8964
|
+
if (!fn.EPDFDoc_GetRevision(docPtr, i, endPtr, xrefPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `failed to read revision ${i}`);
|
|
8965
|
+
const sealedBy = signatures.find((s) => s.revisionIndex === i);
|
|
8966
|
+
revisions.push({
|
|
8967
|
+
index: i,
|
|
8968
|
+
end: peekU64(mem, endPtr),
|
|
8969
|
+
xrefOffset: peekU64(mem, xrefPtr),
|
|
8970
|
+
signatureIndex: sealedBy ? sealedBy.index : null
|
|
8971
|
+
});
|
|
8972
|
+
}
|
|
8973
|
+
});
|
|
8974
|
+
return revisions;
|
|
8975
|
+
}
|
|
8976
|
+
/** Whether any signature field of the document carries a signed value (fields alone do not count). */
|
|
8977
|
+
function hasSignedSignature(runtime, docPtr) {
|
|
8978
|
+
const { fn } = runtime;
|
|
8979
|
+
return withSignatureModel(runtime, docPtr, (model) => {
|
|
8980
|
+
const count = fn.EPDFSig_Count(model);
|
|
8981
|
+
for (let i = 0; i < count; i++) if (fn.EPDFSig_IsSigned(model, i)) return true;
|
|
8982
|
+
return false;
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8985
|
+
/** Run `body` with a fresh signature model of `docPtr`, closing it afterwards. */
|
|
8986
|
+
function withSignatureModel(runtime, docPtr, body) {
|
|
8987
|
+
const model = runtime.fn.EPDFSig_LoadModel(docPtr);
|
|
8988
|
+
if (model === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to build signature model");
|
|
8989
|
+
try {
|
|
8990
|
+
return body(model);
|
|
8991
|
+
} finally {
|
|
8992
|
+
runtime.fn.EPDFSig_CloseModel(model);
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
/**
|
|
8996
|
+
* The structure of one document (a revision prefix, usually): catalog,
|
|
8997
|
+
* AcroForm and page-tree object numbers, the pages, the terminal fields
|
|
8998
|
+
* with their widgets, and the signatures. What the rule engine needs to
|
|
8999
|
+
* give every changed object a role.
|
|
9000
|
+
*/
|
|
9001
|
+
function readStructure(runtime, docPtr) {
|
|
9002
|
+
const { fn, mem } = runtime;
|
|
9003
|
+
const [root, acroForm, pagesRoot] = withScratchN(mem, [
|
|
9004
|
+
4,
|
|
9005
|
+
4,
|
|
9006
|
+
4
|
|
9007
|
+
], ([r, a, p]) => {
|
|
9008
|
+
mem.poke(r, "i32", 0);
|
|
9009
|
+
mem.poke(a, "i32", 0);
|
|
9010
|
+
mem.poke(p, "i32", 0);
|
|
9011
|
+
fn.EPDFDoc_GetStructureObjectNumbers(docPtr, r, a, p);
|
|
9012
|
+
return [
|
|
9013
|
+
Number(mem.peek(r, "i32")) >>> 0,
|
|
9014
|
+
Number(mem.peek(a, "i32")) >>> 0,
|
|
9015
|
+
Number(mem.peek(p, "i32")) >>> 0
|
|
9016
|
+
];
|
|
9017
|
+
});
|
|
9018
|
+
const pages = [];
|
|
9019
|
+
const pageCount = fn.FPDF_GetPageCount(docPtr);
|
|
9020
|
+
for (let i = 0; i < pageCount; i++) pages.push(fn.EPDFDoc_GetPageObjectNumberByIndex(docPtr, i));
|
|
9021
|
+
const fields = [];
|
|
9022
|
+
const formModel = fn.EPDFForm_LoadModel(docPtr);
|
|
9023
|
+
if (formModel !== _embedpdf_engine_runtime.NULL_PTR) try {
|
|
9024
|
+
const snapshot = readFormSnapshot(runtime, formModel, docPtr);
|
|
9025
|
+
for (const f of snapshot.fields) fields.push({
|
|
9026
|
+
objectNumber: f.fieldObjectNumber,
|
|
9027
|
+
name: f.name,
|
|
9028
|
+
family: f.family,
|
|
9029
|
+
widgets: f.widgets.map((w) => w.annotObjectNumber),
|
|
9030
|
+
flags: f.flags.raw
|
|
9031
|
+
});
|
|
9032
|
+
} finally {
|
|
9033
|
+
fn.EPDFForm_CloseModel(formModel);
|
|
9034
|
+
}
|
|
9035
|
+
return {
|
|
9036
|
+
root,
|
|
9037
|
+
acroForm,
|
|
9038
|
+
pagesRoot,
|
|
9039
|
+
pages,
|
|
9040
|
+
fields,
|
|
9041
|
+
signatures: withSignatureModel(runtime, docPtr, (model) => readSignaturesFromModel(runtime, model))
|
|
9042
|
+
};
|
|
9043
|
+
}
|
|
9044
|
+
//#endregion
|
|
9045
|
+
//#region src/features/signature/internal/signatureModelCache.ts
|
|
9046
|
+
/**
|
|
9047
|
+
* Per-session cache of the native `EPDFSig_LoadModel` snapshot, keyed on
|
|
9048
|
+
* the session's mutation sequence exactly like the form model: the model
|
|
9049
|
+
* walks the revision chain, the field tree and every widget, so it is
|
|
9050
|
+
* built once per document state and rebuilt after any mutation.
|
|
9051
|
+
*/
|
|
9052
|
+
const cache$1 = /* @__PURE__ */ new WeakMap();
|
|
9053
|
+
function acquireSignatureModel(runtime, session) {
|
|
9054
|
+
const seq = session.mutationSeq();
|
|
9055
|
+
const hit = cache$1.get(session);
|
|
9056
|
+
if (hit && hit.seq === seq) return hit.ptr;
|
|
9057
|
+
if (hit) {
|
|
9058
|
+
runtime.fn.EPDFSig_CloseModel(hit.ptr);
|
|
9059
|
+
cache$1.delete(session);
|
|
9060
|
+
}
|
|
9061
|
+
const ptr = runtime.fn.EPDFSig_LoadModel(session.requireDocPtr());
|
|
9062
|
+
if (ptr === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to build signature model");
|
|
9063
|
+
cache$1.set(session, {
|
|
9064
|
+
seq,
|
|
9065
|
+
ptr
|
|
9066
|
+
});
|
|
9067
|
+
return ptr;
|
|
9068
|
+
}
|
|
9069
|
+
function disposeSignatureModel(runtime, session) {
|
|
9070
|
+
const hit = cache$1.get(session);
|
|
9071
|
+
if (!hit) return;
|
|
9072
|
+
runtime.fn.EPDFSig_CloseModel(hit.ptr);
|
|
9073
|
+
cache$1.delete(session);
|
|
9074
|
+
}
|
|
9075
|
+
//#endregion
|
|
9076
|
+
//#region src/features/signature/SignatureReader.ts
|
|
9077
|
+
const DIGEST_CODE = {
|
|
9078
|
+
sha1: 0,
|
|
9079
|
+
sha256: 1,
|
|
9080
|
+
sha384: 2,
|
|
9081
|
+
sha512: 3
|
|
9082
|
+
};
|
|
9083
|
+
const DIGEST_LENGTH = {
|
|
9084
|
+
sha1: 20,
|
|
9085
|
+
sha256: 32,
|
|
9086
|
+
sha384: 48,
|
|
9087
|
+
sha512: 64
|
|
9088
|
+
};
|
|
9089
|
+
/**
|
|
9090
|
+
* Reads the signature model of a session: revisions, every signature
|
|
9091
|
+
* field with its signed state, and the protection those signatures
|
|
9092
|
+
* impose. Byte facts (revisions, coverage, digests, loaded bytes) come
|
|
9093
|
+
* from the bytes the document was loaded from — for a layer session the
|
|
9094
|
+
* base plus the delta it was opened with — never from unsaved edits.
|
|
9095
|
+
*/
|
|
9096
|
+
var SignatureReader = class {
|
|
9097
|
+
constructor(runtime, session) {
|
|
9098
|
+
this.runtime = runtime;
|
|
9099
|
+
this.session = session;
|
|
9100
|
+
}
|
|
9101
|
+
readSnapshot() {
|
|
9102
|
+
const model = acquireSignatureModel(this.runtime, this.session);
|
|
9103
|
+
const chainValid = this.runtime.fn.EPDFSig_IsRevisionChainValid(model);
|
|
9104
|
+
const signatures = readSignaturesFromModel(this.runtime, model);
|
|
9105
|
+
return {
|
|
9106
|
+
chainValid,
|
|
9107
|
+
revisions: chainValid ? readRevisions(this.runtime, this.session.requireDocPtr(), signatures) : [],
|
|
9108
|
+
signatures,
|
|
9109
|
+
protection: (0, _embedpdf_engine_core_runtime.deriveProtection)(signatures)
|
|
9110
|
+
};
|
|
9111
|
+
}
|
|
9112
|
+
/** The protection alone, for the open probe and the form write guard. */
|
|
9113
|
+
readProtection() {
|
|
9114
|
+
const model = acquireSignatureModel(this.runtime, this.session);
|
|
9115
|
+
return (0, _embedpdf_engine_core_runtime.deriveProtection)(readSignaturesFromModel(this.runtime, model));
|
|
9116
|
+
}
|
|
9117
|
+
/** The DER `/Contents` of a signed field, padding stripped. */
|
|
9118
|
+
readContents(ref) {
|
|
9119
|
+
const model = acquireSignatureModel(this.runtime, this.session);
|
|
9120
|
+
const index = this.requireSignedIndex(model, ref);
|
|
9121
|
+
return readContentsAt(this.runtime, model, index).buffer;
|
|
9122
|
+
}
|
|
9123
|
+
/** Hash a signed field's `/ByteRange` straight from the loaded bytes. */
|
|
9124
|
+
digest(ref, algorithm) {
|
|
9125
|
+
const model = acquireSignatureModel(this.runtime, this.session);
|
|
9126
|
+
const index = this.requireSignedIndex(model, ref);
|
|
9127
|
+
const range = readByteRange(this.runtime, model, index);
|
|
9128
|
+
if (!range) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature has no usable /ByteRange");
|
|
9129
|
+
return this.digestRange(range, algorithm);
|
|
9130
|
+
}
|
|
9131
|
+
/** The exact bytes of one revision. */
|
|
9132
|
+
revisionBytes(revisionIndex) {
|
|
9133
|
+
const snapshot = this.readSnapshot();
|
|
9134
|
+
if (!snapshot.chainValid) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "revision chain is not valid");
|
|
9135
|
+
const revision = snapshot.revisions[revisionIndex];
|
|
9136
|
+
if (!revision) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no revision ${revisionIndex}`);
|
|
9137
|
+
return this.readLoadedBytes(0, revision.end);
|
|
9138
|
+
}
|
|
9139
|
+
/** Resolve a signature-field ref to its identity in the current model. */
|
|
9140
|
+
resolveField(ref) {
|
|
9141
|
+
const { fn } = this.runtime;
|
|
9142
|
+
const model = acquireSignatureModel(this.runtime, this.session);
|
|
9143
|
+
const index = this.indexOf(model, ref);
|
|
9144
|
+
if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, ref.kind === "objectNumber" ? `signature field not found: object ${ref.fieldObjectNumber}` : `signature field not found: "${ref.name}"`);
|
|
9145
|
+
const widgetObjNum = fn.EPDFSig_GetWidgetObjNum(model, index);
|
|
9146
|
+
return {
|
|
9147
|
+
index,
|
|
9148
|
+
fieldObjectNumber: fn.EPDFSig_GetFieldObjNum(model, index),
|
|
9149
|
+
signed: fn.EPDFSig_IsSigned(model, index),
|
|
9150
|
+
widget: widgetObjNum > 0 ? {
|
|
9151
|
+
annotObjectNumber: widgetObjNum,
|
|
9152
|
+
pageObjectNumber: fn.EPDFSig_GetWidgetPageObjNum(model, index)
|
|
9153
|
+
} : null
|
|
9154
|
+
};
|
|
9155
|
+
}
|
|
9156
|
+
/** One signature by field object number, from the current model. */
|
|
9157
|
+
readSignatureByObjectNumber(fieldObjectNumber) {
|
|
9158
|
+
const found = this.readSnapshot().signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === fieldObjectNumber);
|
|
9159
|
+
if (!found) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `signature field not found: object ${fieldObjectNumber}`);
|
|
9160
|
+
return found;
|
|
9161
|
+
}
|
|
9162
|
+
/** The complete loaded bytes (a plain file, or a layer's base + delta). */
|
|
9163
|
+
loadedBytes() {
|
|
9164
|
+
const size = Number(this.runtime.fn.EPDFDoc_GetLoadedBytesSize(this.session.requireDocPtr()));
|
|
9165
|
+
return this.readLoadedBytes(0, size);
|
|
9166
|
+
}
|
|
9167
|
+
/**
|
|
9168
|
+
* SHA-256 and length of the base the session is on. A layer session
|
|
9169
|
+
* reports its base's hash (supplied by the host or computed once by the
|
|
9170
|
+
* runtime); a plain session hashes its loaded bytes once per load.
|
|
9171
|
+
*/
|
|
9172
|
+
version() {
|
|
9173
|
+
const { fn, mem } = this.runtime;
|
|
9174
|
+
const docPtr = this.session.requireDocPtr();
|
|
9175
|
+
const byteLength = Number(fn.EPDFDoc_GetBaseBytesSize(docPtr));
|
|
9176
|
+
if (byteLength <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "document has no loaded bytes");
|
|
9177
|
+
if (this.session.kind === "layer") return {
|
|
9178
|
+
sha256: withScratch(mem, 32, (out) => {
|
|
9179
|
+
if (!fn.EPDFLayer_GetBaseSha256(docPtr, out)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to read the base hash");
|
|
9180
|
+
return toHex(mem.readBytes(out, 32));
|
|
9181
|
+
}),
|
|
9182
|
+
byteLength
|
|
9183
|
+
};
|
|
9184
|
+
const cached = this.session.cachedPlainSha256();
|
|
9185
|
+
if (cached) return {
|
|
9186
|
+
sha256: cached,
|
|
9187
|
+
byteLength
|
|
9188
|
+
};
|
|
9189
|
+
const sha256 = toHex(new Uint8Array(this.digestRange([
|
|
9190
|
+
0,
|
|
9191
|
+
byteLength,
|
|
9192
|
+
byteLength,
|
|
9193
|
+
0
|
|
9194
|
+
], "sha256")));
|
|
9195
|
+
this.session.rememberPlainSha256(sha256);
|
|
9196
|
+
return {
|
|
9197
|
+
sha256,
|
|
9198
|
+
byteLength
|
|
9199
|
+
};
|
|
9200
|
+
}
|
|
9201
|
+
digestRange(range, algorithm) {
|
|
9202
|
+
const { fn, mem } = this.runtime;
|
|
9203
|
+
const docPtr = this.session.requireDocPtr();
|
|
9204
|
+
const outLength = DIGEST_LENGTH[algorithm];
|
|
9205
|
+
return withScratchN(mem, [
|
|
9206
|
+
32,
|
|
9207
|
+
outLength,
|
|
9208
|
+
8
|
|
9209
|
+
], ([rangePtr, outPtr, lenPtr]) => {
|
|
9210
|
+
for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, range[k], k * 8);
|
|
9211
|
+
pokeU64(mem, lenPtr, outLength);
|
|
9212
|
+
if (!fn.EPDFSig_DigestByteRange(docPtr, rangePtr, DIGEST_CODE[algorithm], outPtr, lenPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "byte range is not within the loaded bytes");
|
|
9213
|
+
const written = Number(mem.peek(lenPtr, "i32"));
|
|
9214
|
+
return copyOut$1(mem.readBytes(outPtr, written));
|
|
9215
|
+
});
|
|
9216
|
+
}
|
|
9217
|
+
/** `[offset, offset + length)` of the loaded bytes (base + loaded delta for a layer), copied out. */
|
|
9218
|
+
readLoadedBytes(offset, length) {
|
|
9219
|
+
const { fn, mem } = this.runtime;
|
|
9220
|
+
const docPtr = this.session.requireDocPtr();
|
|
9221
|
+
if (length === 0) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
9222
|
+
return withScratch(mem, length, (ptr) => {
|
|
9223
|
+
if (fn.EPDFDoc_ReadLoadedBytes(docPtr, BigInt(offset), ptr, length) !== length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to read the loaded bytes");
|
|
9224
|
+
return copyOut$1(mem.readBytes(ptr, length));
|
|
9225
|
+
});
|
|
9226
|
+
}
|
|
9227
|
+
indexOf(model, ref) {
|
|
9228
|
+
const { fn } = this.runtime;
|
|
9229
|
+
if (ref.kind === "objectNumber") return fn.EPDFSig_GetIndexByFieldObjNum(model, ref.fieldObjectNumber);
|
|
9230
|
+
const count = fn.EPDFSig_Count(model);
|
|
9231
|
+
for (let i = 0; i < count; i++) if (this.readWide((buf, cap) => fn.EPDFSig_GetFieldName(model, i, buf, cap)) === ref.name) return i;
|
|
9232
|
+
return -1;
|
|
9233
|
+
}
|
|
9234
|
+
requireSignedIndex(model, ref) {
|
|
9235
|
+
const { fn } = this.runtime;
|
|
9236
|
+
const index = this.indexOf(model, ref);
|
|
9237
|
+
if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, ref.kind === "objectNumber" ? `signature field not found: object ${ref.fieldObjectNumber}` : `signature field not found: "${ref.name}"`);
|
|
9238
|
+
if (!fn.EPDFSig_IsSigned(model, index)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature field is not signed");
|
|
9239
|
+
return index;
|
|
9240
|
+
}
|
|
9241
|
+
readString(model, index, key) {
|
|
9242
|
+
return this.readWide((buf, cap) => this.runtime.fn.EPDFSig_GetString(model, index, key, buf, cap));
|
|
9243
|
+
}
|
|
9244
|
+
readWide(call) {
|
|
9245
|
+
return readUtf16String(this.runtime.mem, call, "");
|
|
9246
|
+
}
|
|
9247
|
+
};
|
|
9248
|
+
function copyOut$1(view) {
|
|
9249
|
+
const buffer = new ArrayBuffer(view.byteLength);
|
|
9250
|
+
new Uint8Array(buffer).set(view);
|
|
9251
|
+
return buffer;
|
|
9252
|
+
}
|
|
9253
|
+
function toHex(bytes) {
|
|
9254
|
+
let out = "";
|
|
9255
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
9256
|
+
return out;
|
|
9257
|
+
}
|
|
9258
|
+
//#endregion
|
|
8311
9259
|
//#region src/features/save/DocumentSaver.ts
|
|
9260
|
+
/** The fork's `EPDFSaveStatus`. */
|
|
9261
|
+
const EPDF_SAVE_WRITTEN = 1;
|
|
9262
|
+
const EPDF_SAVE_UNCHANGED_SINCE_LOAD = 2;
|
|
8312
9263
|
/**
|
|
8313
9264
|
* Owns every "serialize the open document" path for a `DocumentSession`.
|
|
8314
9265
|
* Two distinct outputs:
|
|
@@ -8328,84 +9279,1681 @@ var DocumentSaver = class {
|
|
|
8328
9279
|
this.runtime = runtime;
|
|
8329
9280
|
this.session = session;
|
|
8330
9281
|
}
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
9282
|
+
/**
|
|
9283
|
+
* The bytes a save would produce, and whether they are the loaded bytes.
|
|
9284
|
+
* Three answers, cheapest first: nothing was mutated since load (the
|
|
9285
|
+
* session's counter); something was mutated but the document is still
|
|
9286
|
+
* the one it was opened with — the fork's save pass found no reachable
|
|
9287
|
+
* object that differs from its loaded version, an annotation added and
|
|
9288
|
+
* removed again being the common case; or a real delta. The one pass
|
|
9289
|
+
* answers the second and produces the third, comparing only the objects
|
|
9290
|
+
* that were touched. A signed file must come back exactly as sealed, so
|
|
9291
|
+
* the loaded bytes are returned verbatim in the first two cases.
|
|
9292
|
+
*/
|
|
9293
|
+
snapshot() {
|
|
9294
|
+
if (!this.session.hasUnsavedEdits()) return {
|
|
9295
|
+
bytes: this.loadedBytes(),
|
|
9296
|
+
source: "loaded"
|
|
9297
|
+
};
|
|
9298
|
+
const saved = this.saveStandaloneToBufferEx("incremental");
|
|
9299
|
+
if (saved.unchangedSinceLoad) return {
|
|
9300
|
+
bytes: this.loadedBytes(),
|
|
9301
|
+
source: "loaded"
|
|
9302
|
+
};
|
|
9303
|
+
return {
|
|
9304
|
+
bytes: saved.bytes,
|
|
9305
|
+
source: "working-copy"
|
|
9306
|
+
};
|
|
9307
|
+
}
|
|
9308
|
+
/** The complete loaded bytes: a plain file, or a layer's base plus the delta it was opened with. */
|
|
9309
|
+
loadedBytes() {
|
|
9310
|
+
return new SignatureReader(this.runtime, this.session).loadedBytes();
|
|
9311
|
+
}
|
|
9312
|
+
/**
|
|
9313
|
+
* The loaded bytes streamed to `path` in chunks: a plain file, or a
|
|
9314
|
+
* layer's base plus the delta it was opened with, never the base alone.
|
|
9315
|
+
* Native only. Replaces whatever `path` held.
|
|
9316
|
+
*/
|
|
9317
|
+
copyLoadedBytesToFile(path) {
|
|
9318
|
+
const reader = new SignatureReader(this.runtime, this.session);
|
|
9319
|
+
const size = Number(this.runtime.fn.EPDFDoc_GetLoadedBytesSize(this.session.requireDocPtr()));
|
|
9320
|
+
const chunk = 4 * 1024 * 1024;
|
|
9321
|
+
this.runtime.fileWrite.removeFile(path);
|
|
9322
|
+
for (let offset = 0; offset < size; offset += chunk) {
|
|
9323
|
+
const length = Math.min(chunk, size - offset);
|
|
9324
|
+
this.runtime.fileWrite.appendBytes(path, new Uint8Array(reader.readLoadedBytes(offset, length)));
|
|
9325
|
+
}
|
|
9326
|
+
if (size === 0) this.runtime.fileWrite.appendBytes(path, /* @__PURE__ */ new Uint8Array(0));
|
|
9327
|
+
}
|
|
9328
|
+
/**
|
|
9329
|
+
* `snapshot()` onto a file: `path` ends up holding the document a save
|
|
9330
|
+
* would write — the loaded bytes when nothing changed, else the standalone
|
|
9331
|
+
* save — without the document ever being an in-memory buffer. Native only.
|
|
9332
|
+
*/
|
|
9333
|
+
snapshotToFile(path) {
|
|
9334
|
+
if (!this.session.hasUnsavedEdits()) {
|
|
9335
|
+
this.copyLoadedBytesToFile(path);
|
|
8347
9336
|
return {
|
|
8348
|
-
|
|
8349
|
-
|
|
9337
|
+
path,
|
|
9338
|
+
source: "loaded"
|
|
9339
|
+
};
|
|
9340
|
+
}
|
|
9341
|
+
if (this.saveStandaloneToFileEx(path, "incremental").unchangedSinceLoad) {
|
|
9342
|
+
this.copyLoadedBytesToFile(path);
|
|
9343
|
+
return {
|
|
9344
|
+
path,
|
|
9345
|
+
source: "loaded"
|
|
8350
9346
|
};
|
|
8351
|
-
}
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
9347
|
+
}
|
|
9348
|
+
return {
|
|
9349
|
+
path,
|
|
9350
|
+
source: "working-copy"
|
|
9351
|
+
};
|
|
9352
|
+
}
|
|
9353
|
+
/** Whether this runtime and session can put scratch output beside the base file. */
|
|
9354
|
+
canWriteScratchFiles() {
|
|
9355
|
+
return this.runtime.kind === "native" && this.session.source.base?.kind === "file";
|
|
9356
|
+
}
|
|
9357
|
+
saveLayerArtifact() {
|
|
9358
|
+
const saved = this.saveLayerArtifactImpl(false);
|
|
9359
|
+
if (saved.size <= 0) throw layerSaveError(-1);
|
|
9360
|
+
return {
|
|
9361
|
+
bytes: saved.bytes,
|
|
9362
|
+
size: saved.size
|
|
9363
|
+
};
|
|
9364
|
+
}
|
|
9365
|
+
/**
|
|
9366
|
+
* `saveLayerArtifact` that says whether anything reachable changed since
|
|
9367
|
+
* the layer was opened. When nothing did, `bytes` is empty and the
|
|
9368
|
+
* artifact the session was opened with stands. The artifact written
|
|
9369
|
+
* otherwise is cumulative against the base (empty delta when the layer
|
|
9370
|
+
* came to equal its base).
|
|
9371
|
+
*/
|
|
9372
|
+
saveLayerArtifactEx() {
|
|
9373
|
+
return this.saveLayerArtifactImpl(true);
|
|
9374
|
+
}
|
|
9375
|
+
/**
|
|
9376
|
+
* `saveLayerArtifactEx` onto a file: nothing reaches `path` when nothing
|
|
9377
|
+
* changed since load. The delta inside is streamed, never held whole.
|
|
9378
|
+
* Native only.
|
|
9379
|
+
*/
|
|
9380
|
+
saveLayerArtifactToFileEx(path) {
|
|
9381
|
+
this.requireLayer();
|
|
9382
|
+
return this.saveLayerToFileImpl(path, "artifact");
|
|
9383
|
+
}
|
|
9384
|
+
/** `saveLayerDeltaEx` onto a file: the cumulative delta, or nothing when nothing changed since load. Native only. */
|
|
9385
|
+
saveLayerDeltaToFileEx(path) {
|
|
9386
|
+
this.requireLayer();
|
|
9387
|
+
return this.saveLayerToFileImpl(path, "delta");
|
|
9388
|
+
}
|
|
9389
|
+
saveLayerToFileImpl(path, what) {
|
|
9390
|
+
const { mem, fn } = this.runtime;
|
|
9391
|
+
const statusPtr = mem.alloc(4);
|
|
9392
|
+
const changedPtr = mem.alloc(4);
|
|
9393
|
+
this.runtime.fileWrite.removeFile(path);
|
|
9394
|
+
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9395
|
+
try {
|
|
9396
|
+
mem.poke(statusPtr, "i32", -1);
|
|
9397
|
+
mem.poke(changedPtr, "i32", 1);
|
|
9398
|
+
const ok = what === "artifact" ? fn.EPDFLayer_SaveLayerArtifactEx(this.session.requireDocPtr(), writer.ptr, statusPtr, changedPtr) : fn.EPDFLayer_SaveDeltaEx(this.session.requireDocPtr(), writer.ptr, statusPtr, changedPtr);
|
|
9399
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9400
|
+
if (!ok || status !== 0) throw layerSaveError(status);
|
|
9401
|
+
return {
|
|
9402
|
+
path,
|
|
9403
|
+
changedSinceLoad: Number(mem.peek(changedPtr, "i32")) !== 0
|
|
9404
|
+
};
|
|
9405
|
+
} finally {
|
|
9406
|
+
writer.close();
|
|
9407
|
+
mem.free(changedPtr);
|
|
9408
|
+
mem.free(statusPtr);
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
9411
|
+
saveLayerArtifactImpl(reporting) {
|
|
9412
|
+
this.requireLayer();
|
|
9413
|
+
const { mem, fn } = this.runtime;
|
|
9414
|
+
const sizePtr = mem.alloc(4);
|
|
9415
|
+
const statusPtr = mem.alloc(4);
|
|
9416
|
+
const changedPtr = mem.alloc(4);
|
|
9417
|
+
let artifactPtr = null;
|
|
9418
|
+
try {
|
|
9419
|
+
mem.poke(sizePtr, "i32", 0);
|
|
9420
|
+
mem.poke(statusPtr, "i32", -1);
|
|
9421
|
+
mem.poke(changedPtr, "i32", 1);
|
|
9422
|
+
artifactPtr = reporting ? fn.EPDFLayer_SaveLayerArtifactToOwnedBufferEx(this.session.requireDocPtr(), sizePtr, statusPtr, changedPtr) : fn.EPDFLayer_SaveLayerArtifactToOwnedBuffer(this.session.requireDocPtr(), sizePtr, statusPtr);
|
|
9423
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9424
|
+
const size = Number(mem.peek(sizePtr, "i32"));
|
|
9425
|
+
const changedSinceLoad = reporting ? Number(mem.peek(changedPtr, "i32")) !== 0 : true;
|
|
9426
|
+
if (status !== 0) throw layerSaveError(status);
|
|
9427
|
+
if (reporting && !changedSinceLoad) return {
|
|
9428
|
+
bytes: /* @__PURE__ */ new ArrayBuffer(0),
|
|
9429
|
+
size: 0,
|
|
9430
|
+
changedSinceLoad: false
|
|
9431
|
+
};
|
|
9432
|
+
if (!artifactPtr || size <= 0) throw layerSaveError(status);
|
|
9433
|
+
const bytes = mem.readBytes(artifactPtr, size);
|
|
9434
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
9435
|
+
new Uint8Array(buffer).set(bytes);
|
|
9436
|
+
return {
|
|
9437
|
+
bytes: buffer,
|
|
9438
|
+
size,
|
|
9439
|
+
changedSinceLoad
|
|
9440
|
+
};
|
|
9441
|
+
} finally {
|
|
9442
|
+
if (artifactPtr) fn.EPDF_FreeBuffer(artifactPtr);
|
|
9443
|
+
mem.free(changedPtr);
|
|
9444
|
+
mem.free(statusPtr);
|
|
9445
|
+
mem.free(sizePtr);
|
|
9446
|
+
}
|
|
9447
|
+
}
|
|
9448
|
+
/**
|
|
9449
|
+
* The raw cumulative delta a layer save writes (every promoted object,
|
|
9450
|
+
* offsets notional from the base's append offset), without the artifact
|
|
9451
|
+
* header: what `EPDFDoc_OpenBaseOverlay` composes with the base. Empty
|
|
9452
|
+
* when nothing was promoted.
|
|
9453
|
+
*/
|
|
9454
|
+
saveLayerDelta() {
|
|
9455
|
+
const saved = this.saveLayerDeltaImpl(false);
|
|
9456
|
+
return {
|
|
9457
|
+
bytes: saved.bytes,
|
|
9458
|
+
size: saved.size
|
|
9459
|
+
};
|
|
9460
|
+
}
|
|
9461
|
+
/**
|
|
9462
|
+
* `saveLayerDelta` that says whether anything reachable changed since the
|
|
9463
|
+
* layer was opened; empty bytes when nothing did (keep what you have), or
|
|
9464
|
+
* when the layer came to equal its base (then `changedSinceLoad` is true
|
|
9465
|
+
* and the document IS the base).
|
|
9466
|
+
*/
|
|
9467
|
+
saveLayerDeltaEx() {
|
|
9468
|
+
return this.saveLayerDeltaImpl(true);
|
|
9469
|
+
}
|
|
9470
|
+
saveLayerDeltaImpl(reporting) {
|
|
9471
|
+
this.requireLayer();
|
|
9472
|
+
const { mem, fn } = this.runtime;
|
|
9473
|
+
const sizePtr = mem.alloc(4);
|
|
9474
|
+
const statusPtr = mem.alloc(4);
|
|
9475
|
+
const changedPtr = mem.alloc(4);
|
|
9476
|
+
let deltaPtr = null;
|
|
9477
|
+
try {
|
|
9478
|
+
mem.poke(sizePtr, "i32", 0);
|
|
9479
|
+
mem.poke(statusPtr, "i32", -1);
|
|
9480
|
+
mem.poke(changedPtr, "i32", 1);
|
|
9481
|
+
deltaPtr = reporting ? fn.EPDFLayer_SaveDeltaToOwnedBufferEx(this.session.requireDocPtr(), sizePtr, statusPtr, changedPtr) : fn.EPDFLayer_SaveDeltaToOwnedBuffer(this.session.requireDocPtr(), sizePtr, statusPtr);
|
|
9482
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9483
|
+
const size = Number(mem.peek(sizePtr, "i32"));
|
|
9484
|
+
const changedSinceLoad = reporting ? Number(mem.peek(changedPtr, "i32")) !== 0 : true;
|
|
9485
|
+
if (status !== 0) throw layerSaveError(status);
|
|
9486
|
+
if (!deltaPtr || size <= 0) return {
|
|
9487
|
+
bytes: /* @__PURE__ */ new ArrayBuffer(0),
|
|
9488
|
+
size: 0,
|
|
9489
|
+
changedSinceLoad
|
|
9490
|
+
};
|
|
9491
|
+
const bytes = mem.readBytes(deltaPtr, size);
|
|
9492
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
9493
|
+
new Uint8Array(buffer).set(bytes);
|
|
9494
|
+
return {
|
|
9495
|
+
bytes: buffer,
|
|
9496
|
+
size,
|
|
9497
|
+
changedSinceLoad
|
|
9498
|
+
};
|
|
9499
|
+
} finally {
|
|
9500
|
+
if (deltaPtr) fn.EPDF_FreeBuffer(deltaPtr);
|
|
9501
|
+
mem.free(changedPtr);
|
|
9502
|
+
mem.free(statusPtr);
|
|
9503
|
+
mem.free(sizePtr);
|
|
8355
9504
|
}
|
|
8356
9505
|
}
|
|
8357
9506
|
saveLayerArtifactToFile(path) {
|
|
8358
9507
|
this.requireLayer();
|
|
8359
9508
|
const { mem, fn } = this.runtime;
|
|
8360
9509
|
const statusPtr = mem.alloc(4);
|
|
8361
|
-
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9510
|
+
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9511
|
+
try {
|
|
9512
|
+
mem.poke(statusPtr, "i32", -1);
|
|
9513
|
+
const ok = fn.EPDFLayer_SaveLayerArtifact(this.session.requireDocPtr(), writer.ptr, statusPtr);
|
|
9514
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9515
|
+
if (!ok || status !== 0) throw layerSaveError(status);
|
|
9516
|
+
return { path };
|
|
9517
|
+
} finally {
|
|
9518
|
+
writer.close();
|
|
9519
|
+
mem.free(statusPtr);
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9522
|
+
saveStandaloneToBuffer(mode) {
|
|
9523
|
+
const { mem, fn } = this.runtime;
|
|
9524
|
+
const sizePtr = mem.alloc(4);
|
|
9525
|
+
let pdfPtr = null;
|
|
9526
|
+
try {
|
|
9527
|
+
mem.poke(sizePtr, "i32", 0);
|
|
9528
|
+
pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(this.session.requireDocPtr(), pdfSaveModeFlags(mode), sizePtr);
|
|
9529
|
+
const size = Number(mem.peek(sizePtr, "i32"));
|
|
9530
|
+
if (!pdfPtr || size <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
|
|
9531
|
+
const bytes = mem.readBytes(pdfPtr, size);
|
|
9532
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
9533
|
+
new Uint8Array(buffer).set(bytes);
|
|
9534
|
+
return {
|
|
9535
|
+
bytes: buffer,
|
|
9536
|
+
size
|
|
9537
|
+
};
|
|
9538
|
+
} finally {
|
|
9539
|
+
if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
|
|
9540
|
+
mem.free(sizePtr);
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
/**
|
|
9544
|
+
* `saveStandaloneToBuffer` that says whether the save wrote anything. An
|
|
9545
|
+
* incremental save of a layer never rewrites an object equal to its base
|
|
9546
|
+
* twin, and when no reachable object differs from the document the layer
|
|
9547
|
+
* was opened with it writes nothing at all: `unchangedSinceLoad`, empty
|
|
9548
|
+
* bytes — the loaded bytes are the document.
|
|
9549
|
+
*/
|
|
9550
|
+
saveStandaloneToBufferEx(mode) {
|
|
9551
|
+
const { mem, fn } = this.runtime;
|
|
9552
|
+
const sizePtr = mem.alloc(4);
|
|
9553
|
+
const statusPtr = mem.alloc(4);
|
|
9554
|
+
let pdfPtr = null;
|
|
9555
|
+
try {
|
|
9556
|
+
mem.poke(sizePtr, "i32", 0);
|
|
9557
|
+
mem.poke(statusPtr, "i32", 0);
|
|
9558
|
+
pdfPtr = fn.EPDF_SaveDocumentToOwnedBufferEx(this.session.requireDocPtr(), pdfSaveModeFlags(mode), 0, sizePtr, statusPtr);
|
|
9559
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9560
|
+
if (status === EPDF_SAVE_UNCHANGED_SINCE_LOAD) return {
|
|
9561
|
+
bytes: /* @__PURE__ */ new ArrayBuffer(0),
|
|
9562
|
+
size: 0,
|
|
9563
|
+
unchangedSinceLoad: true
|
|
9564
|
+
};
|
|
9565
|
+
const size = Number(mem.peek(sizePtr, "i32"));
|
|
9566
|
+
if (!pdfPtr || size <= 0 || status !== EPDF_SAVE_WRITTEN) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
|
|
9567
|
+
const bytes = mem.readBytes(pdfPtr, size);
|
|
9568
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
9569
|
+
new Uint8Array(buffer).set(bytes);
|
|
9570
|
+
return {
|
|
9571
|
+
bytes: buffer,
|
|
9572
|
+
size,
|
|
9573
|
+
unchangedSinceLoad: false
|
|
9574
|
+
};
|
|
9575
|
+
} finally {
|
|
9576
|
+
if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
|
|
9577
|
+
mem.free(statusPtr);
|
|
9578
|
+
mem.free(sizePtr);
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
saveStandaloneToFile(path, mode) {
|
|
9582
|
+
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9583
|
+
try {
|
|
9584
|
+
if (!this.runtime.fn.FPDF_SaveAsCopy(this.session.requireDocPtr(), writer.ptr, pdfSaveModeFlags(mode))) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
|
|
9585
|
+
return { path };
|
|
9586
|
+
} finally {
|
|
9587
|
+
writer.close();
|
|
9588
|
+
}
|
|
9589
|
+
}
|
|
9590
|
+
/**
|
|
9591
|
+
* `saveStandaloneToFile` with the report of `saveStandaloneToBufferEx`.
|
|
9592
|
+
* On `unchangedSinceLoad` no byte reached the file: the caller streams
|
|
9593
|
+
* the loaded bytes instead.
|
|
9594
|
+
*/
|
|
9595
|
+
saveStandaloneToFileEx(path, mode) {
|
|
9596
|
+
const { mem, fn } = this.runtime;
|
|
9597
|
+
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9598
|
+
const statusPtr = mem.alloc(4);
|
|
9599
|
+
try {
|
|
9600
|
+
mem.poke(statusPtr, "i32", 0);
|
|
9601
|
+
const ok = fn.EPDF_SaveAsCopyEx(this.session.requireDocPtr(), writer.ptr, pdfSaveModeFlags(mode), statusPtr);
|
|
9602
|
+
const status = Number(mem.peek(statusPtr, "i32"));
|
|
9603
|
+
if (!ok || status === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
|
|
9604
|
+
return {
|
|
9605
|
+
path,
|
|
9606
|
+
unchangedSinceLoad: status === EPDF_SAVE_UNCHANGED_SINCE_LOAD
|
|
9607
|
+
};
|
|
9608
|
+
} finally {
|
|
9609
|
+
mem.free(statusPtr);
|
|
9610
|
+
writer.close();
|
|
9611
|
+
}
|
|
9612
|
+
}
|
|
9613
|
+
requireLayer() {
|
|
9614
|
+
if (this.session.kind !== "layer") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "document session is not a layer");
|
|
9615
|
+
}
|
|
9616
|
+
};
|
|
9617
|
+
//#endregion
|
|
9618
|
+
//#region src/features/signature/internal/candidateStore.ts
|
|
9619
|
+
function copyOut(view) {
|
|
9620
|
+
const out = new Uint8Array(view.byteLength);
|
|
9621
|
+
out.set(view);
|
|
9622
|
+
return out;
|
|
9623
|
+
}
|
|
9624
|
+
/** Marshal `EPDFSig_SealSpan` over a span held in the runtime heap. */
|
|
9625
|
+
function sealSpanInHeap(runtime, spanPtr, spanLength, objectOffset, fileLength) {
|
|
9626
|
+
const { fn, mem } = runtime;
|
|
9627
|
+
return withScratchN(mem, [
|
|
9628
|
+
32,
|
|
9629
|
+
8,
|
|
9630
|
+
8
|
|
9631
|
+
], ([rangePtr, coPtr, chPtr]) => {
|
|
9632
|
+
for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, 0, k * 8);
|
|
9633
|
+
pokeU64(mem, coPtr, 0);
|
|
9634
|
+
pokeU64(mem, chPtr, 0);
|
|
9635
|
+
if (!fn.EPDFSig_SealSpan(spanPtr, BigInt(spanLength), BigInt(objectOffset), BigInt(fileLength), rangePtr, coPtr, chPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to seal the signing candidate");
|
|
9636
|
+
return {
|
|
9637
|
+
byteRange: [
|
|
9638
|
+
peekU64(mem, rangePtr, 0),
|
|
9639
|
+
peekU64(mem, rangePtr, 8),
|
|
9640
|
+
peekU64(mem, rangePtr, 16),
|
|
9641
|
+
peekU64(mem, rangePtr, 24)
|
|
9642
|
+
],
|
|
9643
|
+
contentsOffset: peekU64(mem, coPtr),
|
|
9644
|
+
contentsHexLength: peekU64(mem, chPtr)
|
|
9645
|
+
};
|
|
9646
|
+
});
|
|
9647
|
+
}
|
|
9648
|
+
/** Marshal `EPDFSig_WriteContents` over a buffer in the heap whose first byte is file offset `bufferOffset`. */
|
|
9649
|
+
function writeContentsInHeap(runtime, bufPtr, bufferLength, bufferOffset, sealed, cms) {
|
|
9650
|
+
const { fn, mem } = runtime;
|
|
9651
|
+
const cmsPtr = mem.alloc(cms.byteLength);
|
|
9652
|
+
try {
|
|
9653
|
+
mem.writeBytes(cmsPtr, cms);
|
|
9654
|
+
if (!fn.EPDFSig_WriteContents(bufPtr, BigInt(bufferLength), BigInt(sealed.contentsOffset - bufferOffset), BigInt(sealed.contentsHexLength), cmsPtr, cms.byteLength)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the CMS is not one DER object that fits the reserved /Contents");
|
|
9655
|
+
} finally {
|
|
9656
|
+
mem.free(cmsPtr);
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9659
|
+
/**
|
|
9660
|
+
* Hex-encode `cms` into the /Contents hole of a candidate FILE, in place.
|
|
9661
|
+
* Only the hole (`<`, the hex digits, `>`) is read and written back; the
|
|
9662
|
+
* delimiters are checked so a wrong offset patches nothing.
|
|
9663
|
+
*/
|
|
9664
|
+
function writeContentsIntoFile(runtime, path, hole, cms) {
|
|
9665
|
+
const { mem } = runtime;
|
|
9666
|
+
const span = runtime.fileAccess.readRange(path, hole.holeOffset, hole.hexLength + 2);
|
|
9667
|
+
if (span.byteLength !== hole.hexLength + 2 || span[0] !== 60 || span[span.byteLength - 1] !== 62) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "the byteRange gap is not a <…> /Contents hole of the candidate");
|
|
9668
|
+
const spanPtr = mem.alloc(span.byteLength);
|
|
9669
|
+
try {
|
|
9670
|
+
mem.writeBytes(spanPtr, span);
|
|
9671
|
+
writeContentsInHeap(runtime, spanPtr, span.byteLength, hole.holeOffset, {
|
|
9672
|
+
byteRange: [
|
|
9673
|
+
0,
|
|
9674
|
+
0,
|
|
9675
|
+
0,
|
|
9676
|
+
0
|
|
9677
|
+
],
|
|
9678
|
+
contentsOffset: hole.holeOffset + 1,
|
|
9679
|
+
contentsHexLength: hole.hexLength,
|
|
9680
|
+
digest: /* @__PURE__ */ new Uint8Array()
|
|
9681
|
+
}, cms);
|
|
9682
|
+
runtime.fileWrite.writeRange(path, hole.holeOffset, mem.readBytes(spanPtr, span.byteLength));
|
|
9683
|
+
} finally {
|
|
9684
|
+
mem.free(spanPtr);
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9687
|
+
var MemoryCandidateStore = class {
|
|
9688
|
+
constructor(runtime, baseDocuments) {
|
|
9689
|
+
this.runtime = runtime;
|
|
9690
|
+
this.baseDocuments = baseDocuments;
|
|
9691
|
+
}
|
|
9692
|
+
save(candidateDocPtr, sigObjNum) {
|
|
9693
|
+
const { mem, fn } = this.runtime;
|
|
9694
|
+
return withScratchN(mem, [
|
|
9695
|
+
8,
|
|
9696
|
+
8,
|
|
9697
|
+
8
|
|
9698
|
+
], ([sizePtr, offPtr, lenPtr]) => {
|
|
9699
|
+
pokeU64(mem, sizePtr, 0);
|
|
9700
|
+
pokeU64(mem, offPtr, 0);
|
|
9701
|
+
pokeU64(mem, lenPtr, 0);
|
|
9702
|
+
const bufPtr = fn.EPDFSig_SaveCandidateToOwnedBuffer(candidateDocPtr, sigObjNum, sizePtr, offPtr, lenPtr);
|
|
9703
|
+
if (!bufPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to save the signing candidate");
|
|
9704
|
+
try {
|
|
9705
|
+
const size = peekU64(mem, sizePtr);
|
|
9706
|
+
return {
|
|
9707
|
+
kind: "memory",
|
|
9708
|
+
bytes: copyOut(mem.readBytes(bufPtr, size)),
|
|
9709
|
+
size,
|
|
9710
|
+
objectOffset: peekU64(mem, offPtr),
|
|
9711
|
+
objectLength: peekU64(mem, lenPtr)
|
|
9712
|
+
};
|
|
9713
|
+
} finally {
|
|
9714
|
+
fn.EPDF_FreeBuffer(bufPtr);
|
|
9715
|
+
}
|
|
9716
|
+
});
|
|
9717
|
+
}
|
|
9718
|
+
seal(saved, algorithm) {
|
|
9719
|
+
if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
|
|
9720
|
+
const { mem, fn } = this.runtime;
|
|
9721
|
+
const bufPtr = mem.alloc(saved.bytes.byteLength);
|
|
9722
|
+
try {
|
|
9723
|
+
mem.writeBytes(bufPtr, saved.bytes);
|
|
9724
|
+
return withScratchN(mem, [
|
|
9725
|
+
32,
|
|
9726
|
+
8,
|
|
9727
|
+
8,
|
|
9728
|
+
64,
|
|
9729
|
+
8
|
|
9730
|
+
], ([rangePtr, coPtr, chPtr, digestPtr, lenPtr]) => {
|
|
9731
|
+
for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, 0, k * 8);
|
|
9732
|
+
pokeU64(mem, coPtr, 0);
|
|
9733
|
+
pokeU64(mem, chPtr, 0);
|
|
9734
|
+
pokeU64(mem, lenPtr, 64);
|
|
9735
|
+
if (!fn.EPDFSig_Seal(bufPtr, BigInt(saved.bytes.byteLength), BigInt(saved.objectOffset), BigInt(saved.objectLength), DIGEST_CODE[algorithm], rangePtr, coPtr, chPtr, digestPtr, lenPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to seal the signing candidate");
|
|
9736
|
+
saved.bytes = copyOut(mem.readBytes(bufPtr, saved.bytes.byteLength));
|
|
9737
|
+
const digestLength = Number(mem.peek(lenPtr, "i32"));
|
|
9738
|
+
return {
|
|
9739
|
+
byteRange: [
|
|
9740
|
+
peekU64(mem, rangePtr, 0),
|
|
9741
|
+
peekU64(mem, rangePtr, 8),
|
|
9742
|
+
peekU64(mem, rangePtr, 16),
|
|
9743
|
+
peekU64(mem, rangePtr, 24)
|
|
9744
|
+
],
|
|
9745
|
+
contentsOffset: peekU64(mem, coPtr),
|
|
9746
|
+
contentsHexLength: peekU64(mem, chPtr),
|
|
9747
|
+
digest: copyOut(mem.readBytes(digestPtr, digestLength))
|
|
9748
|
+
};
|
|
9749
|
+
});
|
|
9750
|
+
} finally {
|
|
9751
|
+
mem.free(bufPtr);
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
writeContents(saved, sealed, cms) {
|
|
9755
|
+
if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
|
|
9756
|
+
const { mem } = this.runtime;
|
|
9757
|
+
const bufPtr = mem.alloc(saved.bytes.byteLength);
|
|
9758
|
+
try {
|
|
9759
|
+
mem.writeBytes(bufPtr, saved.bytes);
|
|
9760
|
+
writeContentsInHeap(this.runtime, bufPtr, saved.bytes.byteLength, 0, sealed, cms);
|
|
9761
|
+
saved.bytes = copyOut(mem.readBytes(bufPtr, saved.bytes.byteLength));
|
|
9762
|
+
} finally {
|
|
9763
|
+
mem.free(bufPtr);
|
|
9764
|
+
}
|
|
9765
|
+
}
|
|
9766
|
+
openSealed(saved, password) {
|
|
9767
|
+
if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
|
|
9768
|
+
const base = this.baseDocuments.acquireMemoryBase({
|
|
9769
|
+
key: `signed:${generateUuid()}`,
|
|
9770
|
+
bytes: saved.bytes,
|
|
9771
|
+
password
|
|
9772
|
+
});
|
|
9773
|
+
return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
|
|
9774
|
+
}
|
|
9775
|
+
discard() {}
|
|
9776
|
+
};
|
|
9777
|
+
/**
|
|
9778
|
+
* A candidate written next to the session's base file, through the
|
|
9779
|
+
* runtime's local-file surface: the base streams through the writer, the
|
|
9780
|
+
* seal patches the object span in place, the digest streams the file, and
|
|
9781
|
+
* the sealed file becomes a file base. Only the object span and the CMS
|
|
9782
|
+
* are ever held in memory. Node runtimes only.
|
|
9783
|
+
*/
|
|
9784
|
+
var FileCandidateStore = class {
|
|
9785
|
+
constructor(runtime, baseDocuments, basePath, candidatePath = defaultCandidatePath) {
|
|
9786
|
+
this.runtime = runtime;
|
|
9787
|
+
this.baseDocuments = baseDocuments;
|
|
9788
|
+
this.basePath = basePath;
|
|
9789
|
+
this.candidatePath = candidatePath;
|
|
9790
|
+
}
|
|
9791
|
+
save(candidateDocPtr, sigObjNum, signingId) {
|
|
9792
|
+
const { mem, fn } = this.runtime;
|
|
9793
|
+
const path = this.candidatePath(this.basePath, signingId);
|
|
9794
|
+
this.runtime.fileWrite.removeFile(path);
|
|
9795
|
+
const writer = this.runtime.fileWrite.toNodeFile(path);
|
|
9796
|
+
try {
|
|
9797
|
+
return withScratchN(mem, [
|
|
9798
|
+
8,
|
|
9799
|
+
8,
|
|
9800
|
+
8
|
|
9801
|
+
], ([sizePtr, offPtr, lenPtr]) => {
|
|
9802
|
+
pokeU64(mem, sizePtr, 0);
|
|
9803
|
+
pokeU64(mem, offPtr, 0);
|
|
9804
|
+
pokeU64(mem, lenPtr, 0);
|
|
9805
|
+
if (!fn.EPDFSig_SaveCandidate(candidateDocPtr, sigObjNum, writer.ptr, sizePtr, offPtr, lenPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to save the signing candidate");
|
|
9806
|
+
return {
|
|
9807
|
+
kind: "file",
|
|
9808
|
+
path,
|
|
9809
|
+
size: peekU64(mem, sizePtr),
|
|
9810
|
+
objectOffset: peekU64(mem, offPtr),
|
|
9811
|
+
objectLength: peekU64(mem, lenPtr)
|
|
9812
|
+
};
|
|
9813
|
+
});
|
|
9814
|
+
} catch (error) {
|
|
9815
|
+
writer.close();
|
|
9816
|
+
this.runtime.fileWrite.removeFile(path);
|
|
9817
|
+
throw error;
|
|
9818
|
+
} finally {
|
|
9819
|
+
writer.close();
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
seal(saved, algorithm) {
|
|
9823
|
+
if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
|
|
9824
|
+
const { mem, fn } = this.runtime;
|
|
9825
|
+
const span = this.runtime.fileAccess.readRange(saved.path, saved.objectOffset, saved.objectLength);
|
|
9826
|
+
if (span.byteLength !== saved.objectLength) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the signing candidate file is shorter than its object span");
|
|
9827
|
+
const spanPtr = mem.alloc(span.byteLength);
|
|
9828
|
+
let plan;
|
|
9829
|
+
try {
|
|
9830
|
+
mem.writeBytes(spanPtr, span);
|
|
9831
|
+
plan = sealSpanInHeap(this.runtime, spanPtr, span.byteLength, saved.objectOffset, saved.size);
|
|
9832
|
+
this.runtime.fileWrite.writeRange(saved.path, saved.objectOffset, mem.readBytes(spanPtr, span.byteLength));
|
|
9833
|
+
} finally {
|
|
9834
|
+
mem.free(spanPtr);
|
|
9835
|
+
}
|
|
9836
|
+
const access = this.runtime.fileAccess.fromNodeFile(saved.path);
|
|
9837
|
+
try {
|
|
9838
|
+
const digest = withScratchN(mem, [
|
|
9839
|
+
32,
|
|
9840
|
+
64,
|
|
9841
|
+
8
|
|
9842
|
+
], ([rangePtr, digestPtr, lenPtr]) => {
|
|
9843
|
+
for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, plan.byteRange[k], k * 8);
|
|
9844
|
+
pokeU64(mem, lenPtr, 64);
|
|
9845
|
+
if (!fn.EPDFSig_DigestFileRange(access.ptr, rangePtr, DIGEST_CODE[algorithm], digestPtr, lenPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to digest the signing candidate file");
|
|
9846
|
+
return copyOut(mem.readBytes(digestPtr, Number(mem.peek(lenPtr, "i32"))));
|
|
9847
|
+
});
|
|
9848
|
+
return {
|
|
9849
|
+
...plan,
|
|
9850
|
+
digest
|
|
9851
|
+
};
|
|
9852
|
+
} finally {
|
|
9853
|
+
access.close();
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
writeContents(saved, sealed, cms) {
|
|
9857
|
+
if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
|
|
9858
|
+
const { mem } = this.runtime;
|
|
9859
|
+
const span = this.runtime.fileAccess.readRange(saved.path, saved.objectOffset, saved.objectLength);
|
|
9860
|
+
const spanPtr = mem.alloc(span.byteLength);
|
|
9861
|
+
try {
|
|
9862
|
+
mem.writeBytes(spanPtr, span);
|
|
9863
|
+
writeContentsInHeap(this.runtime, spanPtr, span.byteLength, saved.objectOffset, sealed, cms);
|
|
9864
|
+
this.runtime.fileWrite.writeRange(saved.path, saved.objectOffset, mem.readBytes(spanPtr, span.byteLength));
|
|
9865
|
+
} finally {
|
|
9866
|
+
mem.free(spanPtr);
|
|
9867
|
+
}
|
|
9868
|
+
}
|
|
9869
|
+
openSealed(saved, password) {
|
|
9870
|
+
if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
|
|
9871
|
+
const base = this.baseDocuments.acquireFileBase({
|
|
9872
|
+
key: `signed:${saved.path}`,
|
|
9873
|
+
path: saved.path,
|
|
9874
|
+
password
|
|
9875
|
+
});
|
|
9876
|
+
return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
|
|
9877
|
+
}
|
|
9878
|
+
discard(saved) {
|
|
9879
|
+
if (saved.kind === "file") this.runtime.fileWrite.removeFile(saved.path);
|
|
9880
|
+
}
|
|
9881
|
+
};
|
|
9882
|
+
/** Beside the base file, named by the signing; the host may override this (server storage layout). */
|
|
9883
|
+
function defaultCandidatePath(basePath, signingId) {
|
|
9884
|
+
return `${basePath}.signing-${signingId}.pdf`;
|
|
9885
|
+
}
|
|
9886
|
+
/**
|
|
9887
|
+
* A scratch file beside the base: `<base>.<tag>-<id>.<ext>`. Unique per
|
|
9888
|
+
* id, so a file a document holds open is never rewritten; the caller that
|
|
9889
|
+
* names it registers its removal before anything writes it.
|
|
9890
|
+
*/
|
|
9891
|
+
function scratchPath(basePath, tag, id, ext) {
|
|
9892
|
+
return `${basePath}.${tag}-${id}.${ext}`;
|
|
9893
|
+
}
|
|
9894
|
+
//#endregion
|
|
9895
|
+
//#region src/features/signature/internal/appearance.ts
|
|
9896
|
+
/** `EPDF_STAMP_FIT_CONTAIN`: uniform scale, letterboxed and centred in the rect. */
|
|
9897
|
+
const FIT_CONTAIN = 0;
|
|
9898
|
+
/**
|
|
9899
|
+
* Draw one page of a PDF into a widget annotation's normal appearance —
|
|
9900
|
+
* what a signature's mark is, whether the field is being signed
|
|
9901
|
+
* (`SignatureMutator`) or only filled visually (`FormMutator`).
|
|
9902
|
+
*
|
|
9903
|
+
* Two fork calls, the same pair the stamp path makes: `SetAppearanceFromPage`
|
|
9904
|
+
* clones the page into the document as a wrapped Form XObject (page content
|
|
9905
|
+
* and resources in a child form, the outer stream a placement matrix), then
|
|
9906
|
+
* `UpdateAppearanceToRect` writes that placement — the page fitted uniformly
|
|
9907
|
+
* into the widget's rect. Without the second call the page sits in its own
|
|
9908
|
+
* box and the viewer's BBox→/Rect mapping stretches it to the rect's aspect.
|
|
9909
|
+
*/
|
|
9910
|
+
function bakeWidgetAppearance(runtime, docPtr, widget, pdf, pageIndex) {
|
|
9911
|
+
const { mem, fn } = runtime;
|
|
9912
|
+
const stack = new CloseStack();
|
|
9913
|
+
try {
|
|
9914
|
+
const pagePtr = fn.EPDFDoc_LoadPageByObjectNumber(docPtr, widget.pageObjectNumber);
|
|
9915
|
+
if (!pagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "the widget page could not be loaded");
|
|
9916
|
+
stack.push(() => fn.FPDF_ClosePage(pagePtr));
|
|
9917
|
+
const annotPtr = fn.EPDFPage_GetAnnotByObjectNumber(pagePtr, widget.annotObjectNumber);
|
|
9918
|
+
if (!annotPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "the signature widget could not be loaded");
|
|
9919
|
+
stack.push(() => fn.FPDFPage_CloseAnnot(annotPtr));
|
|
9920
|
+
const dataPtr = mem.alloc(pdf.byteLength);
|
|
9921
|
+
stack.push(() => mem.free(dataPtr));
|
|
9922
|
+
mem.writeBytes(dataPtr, pdf);
|
|
9923
|
+
const artworkPtr = fn.FPDF_LoadMemDocument64(dataPtr, pdf.byteLength, "");
|
|
9924
|
+
if (!artworkPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the appearance PDF could not be opened");
|
|
9925
|
+
stack.push(() => fn.FPDF_CloseDocument(artworkPtr));
|
|
9926
|
+
if (!fn.EPDFAnnot_SetAppearanceFromPage(annotPtr, artworkPtr, pageIndex)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "the appearance page could not be drawn into the widget");
|
|
9927
|
+
if (!fn.EPDFAnnot_UpdateAppearanceToRect(annotPtr, FIT_CONTAIN)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the appearance page could not be fitted into the widget");
|
|
9928
|
+
} finally {
|
|
9929
|
+
stack.close();
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
//#endregion
|
|
9933
|
+
//#region src/features/signature/internal/sealCheck.ts
|
|
9934
|
+
/**
|
|
9935
|
+
* The one check both signing paths make before sealed bytes become a
|
|
9936
|
+
* version — the in-session complete and the server's session-less
|
|
9937
|
+
* finalize. Reading the result back through the ordinary signature model
|
|
9938
|
+
* proves the bytes, not the writer: the field is signed, its signature
|
|
9939
|
+
* covers the whole LAST revision (the candidate's own, never an earlier
|
|
9940
|
+
* one), its /ByteRange is the one the digest was computed over, and the
|
|
9941
|
+
* /Contents decode to exactly the CMS installed. Anything else is refused
|
|
9942
|
+
* and the bytes are never installed.
|
|
9943
|
+
*/
|
|
9944
|
+
function assertSealedSignature(runtime, docPtr, model, expected) {
|
|
9945
|
+
const refuse = (why) => new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `the sealed bytes ${why}`);
|
|
9946
|
+
const signatures = readSignaturesFromModel(runtime, model);
|
|
9947
|
+
const signature = signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === expected.fieldObjectNumber);
|
|
9948
|
+
if (!signature) throw refuse("lost the signature field");
|
|
9949
|
+
if (!signature.signed || signature.coverage !== "whole-revision" || !signature.byteRange) throw refuse("do not carry a whole-revision signature on the field");
|
|
9950
|
+
if (!sameRange(signature.byteRange, expected.byteRange)) throw refuse(`carry /ByteRange [${signature.byteRange.join(" ")}] where [${expected.byteRange.join(" ")}] was sealed`);
|
|
9951
|
+
if (!runtime.fn.EPDFSig_IsRevisionChainValid(model)) throw refuse("have a broken cross-reference chain");
|
|
9952
|
+
const revisions = readRevisions(runtime, docPtr, signatures);
|
|
9953
|
+
if (signature.revisionIndex !== revisions.length - 1) throw refuse("seal an earlier revision than the candidate");
|
|
9954
|
+
if (!bytesEqual(readContentsAt(runtime, model, signature.index), expected.cms)) throw refuse("carry /Contents that are not the CMS installed");
|
|
9955
|
+
return signature;
|
|
9956
|
+
}
|
|
9957
|
+
function sameRange(a, b) {
|
|
9958
|
+
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
|
|
9959
|
+
}
|
|
9960
|
+
function bytesEqual(a, b) {
|
|
9961
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
9962
|
+
for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
|
|
9963
|
+
return true;
|
|
9964
|
+
}
|
|
9965
|
+
//#endregion
|
|
9966
|
+
//#region src/features/forms/internal/wideStringArray.ts
|
|
9967
|
+
/**
|
|
9968
|
+
* Marshal a `const FPDF_WIDESTRING*` array: encode every string into the
|
|
9969
|
+
* runtime heap, write the pointer table with the runtime's pointer width
|
|
9970
|
+
* (bigint pointers cannot be poked into the wasm 32-bit heap), run |body|,
|
|
9971
|
+
* free everything.
|
|
9972
|
+
*/
|
|
9973
|
+
function withWideStringArray(runtime, values, body) {
|
|
9974
|
+
const { mem } = runtime;
|
|
9975
|
+
const stringPtrs = values.map((value) => mem.writeU16String(value));
|
|
9976
|
+
const isWasm = runtime.kind === "wasm";
|
|
9977
|
+
const ptrSize = isWasm ? 4 : 8;
|
|
9978
|
+
try {
|
|
9979
|
+
return withScratch(mem, Math.max(1, stringPtrs.length * ptrSize), (arrayPtr) => {
|
|
9980
|
+
stringPtrs.forEach((ptr, i) => isWasm ? mem.poke(arrayPtr, "i32", Number(ptr), i * 4) : mem.poke(arrayPtr, "i64", ptr, i * 8));
|
|
9981
|
+
return body(arrayPtr, stringPtrs.length);
|
|
9982
|
+
});
|
|
9983
|
+
} finally {
|
|
9984
|
+
for (let i = stringPtrs.length - 1; i >= 0; i--) mem.free(stringPtrs[i]);
|
|
9985
|
+
}
|
|
9986
|
+
}
|
|
9987
|
+
//#endregion
|
|
9988
|
+
//#region src/features/signature/SignatureMutator.ts
|
|
9989
|
+
const SUBFILTER_CODE = {
|
|
9990
|
+
"adbe.pkcs7.detached": 0,
|
|
9991
|
+
"ETSI.CAdES.detached": 1,
|
|
9992
|
+
"ETSI.RFC3161": 2
|
|
9993
|
+
};
|
|
9994
|
+
const FIELD_ACTION_CODE = {
|
|
9995
|
+
all: 1,
|
|
9996
|
+
include: 2,
|
|
9997
|
+
exclude: 3
|
|
9998
|
+
};
|
|
9999
|
+
const DEFAULT_CONTENTS_SIZE = 8192;
|
|
10000
|
+
const MIN_CONTENTS_SIZE = 256;
|
|
10001
|
+
const MAX_CONTENTS_SIZE = 4 * 1024 * 1024;
|
|
10002
|
+
const PREPARE_ILP32 = {
|
|
10003
|
+
bytes: 52,
|
|
10004
|
+
ptrBytes: 4,
|
|
10005
|
+
subfilter: 0,
|
|
10006
|
+
digest: 4,
|
|
10007
|
+
contentsSize: 8,
|
|
10008
|
+
name: 12,
|
|
10009
|
+
reason: 16,
|
|
10010
|
+
location: 20,
|
|
10011
|
+
contactInfo: 24,
|
|
10012
|
+
signingTime: 28,
|
|
10013
|
+
docmdpPermission: 32,
|
|
10014
|
+
fieldmdpAction: 36,
|
|
10015
|
+
fieldmdpFields: 40,
|
|
10016
|
+
fieldmdpFieldCount: 44,
|
|
10017
|
+
lockPermission: 48
|
|
10018
|
+
};
|
|
10019
|
+
const PREPARE_LP64 = {
|
|
10020
|
+
bytes: 80,
|
|
10021
|
+
ptrBytes: 8,
|
|
10022
|
+
subfilter: 0,
|
|
10023
|
+
digest: 4,
|
|
10024
|
+
contentsSize: 8,
|
|
10025
|
+
name: 16,
|
|
10026
|
+
reason: 24,
|
|
10027
|
+
location: 32,
|
|
10028
|
+
contactInfo: 40,
|
|
10029
|
+
signingTime: 48,
|
|
10030
|
+
docmdpPermission: 56,
|
|
10031
|
+
fieldmdpAction: 60,
|
|
10032
|
+
fieldmdpFields: 64,
|
|
10033
|
+
fieldmdpFieldCount: 72,
|
|
10034
|
+
lockPermission: 76
|
|
10035
|
+
};
|
|
10036
|
+
/**
|
|
10037
|
+
* The two-phase signing protocol on a session, local and native alike.
|
|
10038
|
+
*
|
|
10039
|
+
* `prepare` never touches the live document: it builds a CANDIDATE — a
|
|
10040
|
+
* fresh layer over the session's own immutable base (see `openCandidate`)
|
|
10041
|
+
* — writes the signature there, saves it through a `CandidateStore`
|
|
10042
|
+
* (memory locally, a file beside the base on file-backed sessions), seals
|
|
10043
|
+
* it, and parks the saved candidate on the session. `complete` writes the
|
|
10044
|
+
* CMS into it, opens it as a new base with a fresh layer, proves the new
|
|
10045
|
+
* signature seals a whole revision, and installs it as the session's
|
|
10046
|
+
* document. `abort` discards the candidate.
|
|
10047
|
+
*/
|
|
10048
|
+
var SignatureMutator = class {
|
|
10049
|
+
constructor(runtime, session, baseDocuments, candidatePath) {
|
|
10050
|
+
this.runtime = runtime;
|
|
10051
|
+
this.session = session;
|
|
10052
|
+
this.baseDocuments = baseDocuments;
|
|
10053
|
+
this.candidatePath = candidatePath;
|
|
10054
|
+
}
|
|
10055
|
+
prepare(input) {
|
|
10056
|
+
if (this.session.pendingSigning) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SigningPending, `a signing is pending (${this.session.pendingSigning.prepared.signingId}); complete or abort it first`);
|
|
10057
|
+
const reader = new SignatureReader(this.runtime, this.session);
|
|
10058
|
+
const field = reader.resolveField(input.field);
|
|
10059
|
+
if (field.signed) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the signature field is already signed");
|
|
10060
|
+
if (input.appearance && !field.widget) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the signature field has no widget to carry an appearance");
|
|
10061
|
+
const algorithm = input.digest ?? "sha256";
|
|
10062
|
+
if (!(algorithm in DIGEST_CODE) || algorithm === "sha1") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `unsupported digest '${algorithm}'`);
|
|
10063
|
+
const subFilter = (input.kind ?? "signature") === "timestamp" ? "ETSI.RFC3161" : input.subFilter ?? "ETSI.CAdES.detached";
|
|
10064
|
+
if (!(subFilter in SUBFILTER_CODE)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `unsupported subFilter '${subFilter}'`);
|
|
10065
|
+
const contentsSize = input.contentsSize ?? DEFAULT_CONTENTS_SIZE;
|
|
10066
|
+
if (!Number.isInteger(contentsSize) || contentsSize < MIN_CONTENTS_SIZE || contentsSize > MAX_CONTENTS_SIZE) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `contentsSize must be an integer in [${MIN_CONTENTS_SIZE}, ${MAX_CONTENTS_SIZE}]`);
|
|
10067
|
+
const expectedVersion = this.session.versionRef(reader.version().sha256);
|
|
10068
|
+
const signingId = generateUuid();
|
|
10069
|
+
const stack = new CloseStack();
|
|
10070
|
+
try {
|
|
10071
|
+
const candidate = this.openCandidate(signingId, stack);
|
|
10072
|
+
stack.push(() => candidate.close());
|
|
10073
|
+
const attribution = input.attribution ?? input.signer;
|
|
10074
|
+
const valueObjNum = this.callPrepare(candidate.docPtr, field.fieldObjectNumber, {
|
|
10075
|
+
subfilter: SUBFILTER_CODE[subFilter],
|
|
10076
|
+
digest: DIGEST_CODE[algorithm],
|
|
10077
|
+
contentsSize,
|
|
10078
|
+
name: attribution?.name ?? null,
|
|
10079
|
+
reason: attribution?.reason ?? null,
|
|
10080
|
+
location: attribution?.location ?? null,
|
|
10081
|
+
contactInfo: attribution?.contactInfo ?? null,
|
|
10082
|
+
signingTime: input.signingTime ?? null,
|
|
10083
|
+
docmdpPermission: input.certify?.permission ?? 0,
|
|
10084
|
+
fieldmdpAction: input.lock ? FIELD_ACTION_CODE[input.lock.action] : 0,
|
|
10085
|
+
fieldmdpFields: input.lock?.action === "all" ? [] : input.lock?.fields ?? [],
|
|
10086
|
+
lockPermission: input.lock?.permission ?? 0
|
|
10087
|
+
});
|
|
10088
|
+
if (valueObjNum === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the engine refused to author the signature (the field is signed, read-only or locked, a certification is not allowed here, the seed value requires what is not implemented, or the request is inconsistent)");
|
|
10089
|
+
if (input.appearance && field.widget) bakeWidgetAppearance(this.runtime, candidate.docPtr, field.widget, input.appearance.pdf, input.appearance.pageIndex ?? 0);
|
|
10090
|
+
const store = this.storeFor();
|
|
10091
|
+
const saved = store.save(candidate.docPtr, valueObjNum, signingId);
|
|
10092
|
+
let sealed;
|
|
10093
|
+
try {
|
|
10094
|
+
sealed = store.seal(saved, algorithm);
|
|
10095
|
+
} catch (error) {
|
|
10096
|
+
store.discard(saved);
|
|
10097
|
+
throw error;
|
|
10098
|
+
}
|
|
10099
|
+
const prepared = {
|
|
10100
|
+
signingId,
|
|
10101
|
+
digest: sealed.digest,
|
|
10102
|
+
algorithm,
|
|
10103
|
+
byteRange: sealed.byteRange,
|
|
10104
|
+
contentsSize,
|
|
10105
|
+
subFilter,
|
|
10106
|
+
expectedVersion,
|
|
10107
|
+
expiresAt: null
|
|
10108
|
+
};
|
|
10109
|
+
this.session.pendingSigning = {
|
|
10110
|
+
prepared,
|
|
10111
|
+
fieldObjectNumber: field.fieldObjectNumber,
|
|
10112
|
+
saved,
|
|
10113
|
+
contentsOffset: sealed.contentsOffset,
|
|
10114
|
+
contentsHexLength: sealed.contentsHexLength
|
|
10115
|
+
};
|
|
10116
|
+
return prepared;
|
|
10117
|
+
} finally {
|
|
10118
|
+
stack.close();
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
complete(input) {
|
|
10122
|
+
const pending = this.session.pendingSigning;
|
|
10123
|
+
if (!pending || pending.prepared.signingId !== input.signingId) {
|
|
10124
|
+
const last = this.session.lastCompletion;
|
|
10125
|
+
if (last && last.signingId === input.signingId) {
|
|
10126
|
+
if (!bytesEqual(last.cms, input.cms)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "this signing already completed with a different CMS");
|
|
10127
|
+
return {
|
|
10128
|
+
...last.result,
|
|
10129
|
+
status: "already-completed"
|
|
10130
|
+
};
|
|
10131
|
+
}
|
|
10132
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no pending signing '${input.signingId}'`);
|
|
10133
|
+
}
|
|
10134
|
+
if (!sameVersion(pending.prepared.expectedVersion, input.expectedVersion)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SigningVersionMismatch, "expectedVersion is not the version the candidate was prepared on");
|
|
10135
|
+
if (input.cms.byteLength === 0 || input.cms[0] !== 48) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the CMS is not a DER SEQUENCE");
|
|
10136
|
+
if (input.cms.byteLength > pending.prepared.contentsSize) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `the CMS (${input.cms.byteLength} bytes) does not fit the reserved ${pending.prepared.contentsSize} bytes`);
|
|
10137
|
+
const store = this.storeFor();
|
|
10138
|
+
const sealed = {
|
|
10139
|
+
byteRange: pending.prepared.byteRange,
|
|
10140
|
+
contentsOffset: pending.contentsOffset,
|
|
10141
|
+
contentsHexLength: pending.contentsHexLength,
|
|
10142
|
+
digest: pending.prepared.digest
|
|
10143
|
+
};
|
|
10144
|
+
store.writeContents(pending.saved, sealed, input.cms);
|
|
10145
|
+
const handle = store.openSealed(pending.saved, this.session.password);
|
|
10146
|
+
this.verifySealed(handle, {
|
|
10147
|
+
fieldObjectNumber: pending.fieldObjectNumber,
|
|
10148
|
+
byteRange: pending.prepared.byteRange,
|
|
10149
|
+
cms: input.cms
|
|
10150
|
+
});
|
|
10151
|
+
this.session.install(handle);
|
|
10152
|
+
disposeFormModel(this.runtime, this.session);
|
|
10153
|
+
disposeSignatureModel(this.runtime, this.session);
|
|
10154
|
+
const reader = new SignatureReader(this.runtime, this.session);
|
|
10155
|
+
const snapshot = reader.readSnapshot();
|
|
10156
|
+
const signature = snapshot.signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === pending.fieldObjectNumber);
|
|
10157
|
+
if (!signature) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the installed document lost the signature field");
|
|
10158
|
+
const result = {
|
|
10159
|
+
status: "completed",
|
|
10160
|
+
signature,
|
|
10161
|
+
version: reader.version(),
|
|
10162
|
+
previous: pending.prepared.expectedVersion,
|
|
10163
|
+
protection: snapshot.protection,
|
|
10164
|
+
meta: {
|
|
10165
|
+
affectedPages: this.session.allRecords().map((r) => this.session.pageState(r.pageObjectNumber)),
|
|
10166
|
+
cacheDelta: null
|
|
10167
|
+
}
|
|
10168
|
+
};
|
|
10169
|
+
this.session.lastCompletion = {
|
|
10170
|
+
signingId: input.signingId,
|
|
10171
|
+
cms: input.cms.slice(),
|
|
10172
|
+
result
|
|
10173
|
+
};
|
|
10174
|
+
return result;
|
|
10175
|
+
}
|
|
10176
|
+
abort(signingId) {
|
|
10177
|
+
const pending = this.session.pendingSigning;
|
|
10178
|
+
if (pending && pending.prepared.signingId === signingId) {
|
|
10179
|
+
this.session.pendingSigning = null;
|
|
10180
|
+
this.storeFor().discard(pending.saved);
|
|
10181
|
+
return { status: "aborted" };
|
|
10182
|
+
}
|
|
10183
|
+
if (this.session.lastCompletion?.signingId === signingId) return { status: "already-completed" };
|
|
10184
|
+
return { status: "unknown" };
|
|
10185
|
+
}
|
|
10186
|
+
callPrepare(docPtr, fieldObjectNumber, opts) {
|
|
10187
|
+
const { mem, fn } = this.runtime;
|
|
10188
|
+
const layout = this.runtime.kind === "wasm" ? PREPARE_ILP32 : PREPARE_LP64;
|
|
10189
|
+
const owned = [];
|
|
10190
|
+
const wide = (value) => {
|
|
10191
|
+
if (value === null) return _embedpdf_engine_runtime.NULL_PTR;
|
|
10192
|
+
const ptr = mem.writeU16String(value);
|
|
10193
|
+
owned.push(ptr);
|
|
10194
|
+
return ptr;
|
|
10195
|
+
};
|
|
10196
|
+
const utf8 = (value) => {
|
|
10197
|
+
if (value === null) return _embedpdf_engine_runtime.NULL_PTR;
|
|
10198
|
+
const ptr = mem.writeU8String(value);
|
|
10199
|
+
owned.push(ptr);
|
|
10200
|
+
return ptr;
|
|
10201
|
+
};
|
|
10202
|
+
try {
|
|
10203
|
+
return withWideStringArray(this.runtime, opts.fieldmdpFields, (fieldsPtr, fieldCount) => withScratch(mem, layout.bytes, (structPtr) => {
|
|
10204
|
+
for (let off = 0; off < layout.bytes; off += 4) mem.poke(structPtr, "i32", 0, off);
|
|
10205
|
+
const pokePtr = (offset, ptr) => {
|
|
10206
|
+
if (layout.ptrBytes === 4) mem.poke(structPtr, "i32", Number(ptr), offset);
|
|
10207
|
+
else mem.poke(structPtr, "i64", ptr, offset);
|
|
10208
|
+
};
|
|
10209
|
+
mem.poke(structPtr, "i32", opts.subfilter, layout.subfilter);
|
|
10210
|
+
mem.poke(structPtr, "i32", opts.digest, layout.digest);
|
|
10211
|
+
mem.poke(structPtr, "i32", opts.contentsSize, layout.contentsSize);
|
|
10212
|
+
pokePtr(layout.name, wide(opts.name));
|
|
10213
|
+
pokePtr(layout.reason, wide(opts.reason));
|
|
10214
|
+
pokePtr(layout.location, wide(opts.location));
|
|
10215
|
+
pokePtr(layout.contactInfo, wide(opts.contactInfo));
|
|
10216
|
+
pokePtr(layout.signingTime, utf8(opts.signingTime));
|
|
10217
|
+
mem.poke(structPtr, "i32", opts.docmdpPermission, layout.docmdpPermission);
|
|
10218
|
+
mem.poke(structPtr, "i32", opts.fieldmdpAction, layout.fieldmdpAction);
|
|
10219
|
+
pokePtr(layout.fieldmdpFields, fieldCount > 0 ? fieldsPtr : _embedpdf_engine_runtime.NULL_PTR);
|
|
10220
|
+
mem.poke(structPtr, "i32", fieldCount, layout.fieldmdpFieldCount);
|
|
10221
|
+
mem.poke(structPtr, "i32", opts.lockPermission, layout.lockPermission);
|
|
10222
|
+
return fn.EPDFSig_Prepare(docPtr, fieldObjectNumber, structPtr);
|
|
10223
|
+
}));
|
|
10224
|
+
} finally {
|
|
10225
|
+
for (let i = owned.length - 1; i >= 0; i--) mem.free(owned[i]);
|
|
10226
|
+
}
|
|
10227
|
+
}
|
|
10228
|
+
/** Draw a page of `pdf` into the widget's appearance stream on the candidate. */
|
|
10229
|
+
/**
|
|
10230
|
+
* File-backed sessions (a file base in the registry: the server) persist
|
|
10231
|
+
* the candidate as a file beside the base; everything else keeps it in
|
|
10232
|
+
* memory. The choice follows the SESSION's base, not the candidate's.
|
|
10233
|
+
*/
|
|
10234
|
+
storeFor() {
|
|
10235
|
+
const base = this.session.source.base;
|
|
10236
|
+
if (base?.kind === "file") return new FileCandidateStore(this.runtime, this.baseDocuments, base.path, this.candidatePath);
|
|
10237
|
+
return new MemoryCandidateStore(this.runtime, this.baseDocuments);
|
|
10238
|
+
}
|
|
10239
|
+
/**
|
|
10240
|
+
* Prove the sealed bytes carry a whole-revision signature on the field
|
|
10241
|
+
* before anything is installed; closes the handle on failure.
|
|
10242
|
+
*/
|
|
10243
|
+
/**
|
|
10244
|
+
* Read the sealed candidate back before it is installed: the checks in
|
|
10245
|
+
* `assertSealedSignature` are made on a model loaded from the sealed
|
|
10246
|
+
* bytes themselves, so a candidate the writer got wrong never becomes
|
|
10247
|
+
* the session's bytes.
|
|
10248
|
+
*/
|
|
10249
|
+
verifySealed(handle, expected) {
|
|
10250
|
+
const { fn } = this.runtime;
|
|
10251
|
+
try {
|
|
10252
|
+
const model = fn.EPDFSig_LoadModel(handle.docPtr);
|
|
10253
|
+
if (!model) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the sealed bytes have no signature model");
|
|
10254
|
+
try {
|
|
10255
|
+
assertSealedSignature(this.runtime, handle.docPtr, model, expected);
|
|
10256
|
+
} finally {
|
|
10257
|
+
fn.EPDFSig_CloseModel(model);
|
|
10258
|
+
}
|
|
10259
|
+
if (this.session.kind !== "layer") this.session.persistLayerArtifact = false;
|
|
10260
|
+
} catch (error) {
|
|
10261
|
+
handle.close();
|
|
10262
|
+
throw error;
|
|
10263
|
+
}
|
|
10264
|
+
}
|
|
10265
|
+
/**
|
|
10266
|
+
* The candidate the signature is authored on. A layer session reopens a
|
|
10267
|
+
* fresh layer over ITS OWN immutable base (a registry retain, no copy of
|
|
10268
|
+
* the file), fed the layer it was opened with — or, with unsaved edits,
|
|
10269
|
+
* the artifact a save would write, which is cumulative: the edits and
|
|
10270
|
+
* the signature then share one revision, as Acrobat saves a
|
|
10271
|
+
* fill-and-sign. The fallback freezes the complete loaded bytes as a new
|
|
10272
|
+
* memory base: for a plain session, and for a layer whose loaded delta
|
|
10273
|
+
* already holds signed bytes (a layer save rewrites the delta and would
|
|
10274
|
+
* drop them; the fork refuses to prepare such a candidate).
|
|
10275
|
+
*/
|
|
10276
|
+
openCandidate(signingId, stack) {
|
|
10277
|
+
const password = this.session.password;
|
|
10278
|
+
const source = this.session.source;
|
|
10279
|
+
const saver = new DocumentSaver(this.runtime, this.session);
|
|
10280
|
+
const scratch = saver.canWriteScratchFiles() && source.base?.kind === "file" ? source.base.path : null;
|
|
10281
|
+
if (this.session.kind === "layer" && source.base && !this.loadedDeltaHoldsSignedBytes()) {
|
|
10282
|
+
const base = this.baseDocuments.retainByKey(source.base.key);
|
|
10283
|
+
if (base) {
|
|
10284
|
+
let layer = source.layer ?? { kind: "fresh" };
|
|
10285
|
+
if (this.session.hasUnsavedEdits()) if (scratch) {
|
|
10286
|
+
const path = scratchPath(scratch, "signing", signingId, "layer");
|
|
10287
|
+
stack.push(() => this.runtime.fileWrite.removeFile(path));
|
|
10288
|
+
if (saver.saveLayerArtifactToFileEx(path).changedSinceLoad) layer = {
|
|
10289
|
+
kind: "artifact-file",
|
|
10290
|
+
path
|
|
10291
|
+
};
|
|
10292
|
+
} else {
|
|
10293
|
+
const artifact = saver.saveLayerArtifactEx();
|
|
10294
|
+
if (artifact.changedSinceLoad) layer = {
|
|
10295
|
+
kind: "artifact",
|
|
10296
|
+
bytes: artifact.bytes
|
|
10297
|
+
};
|
|
10298
|
+
}
|
|
10299
|
+
return openLayerDocument(this.runtime, base, layer, password);
|
|
10300
|
+
}
|
|
10301
|
+
}
|
|
10302
|
+
if (scratch) {
|
|
10303
|
+
const path = scratchPath(scratch, "signing", signingId, "base.pdf");
|
|
10304
|
+
stack.push(() => this.runtime.fileWrite.removeFile(path));
|
|
10305
|
+
saver.snapshotToFile(path);
|
|
10306
|
+
const base = this.baseDocuments.acquireFileBase({
|
|
10307
|
+
key: `candidate:${signingId}`,
|
|
10308
|
+
path,
|
|
10309
|
+
password
|
|
10310
|
+
});
|
|
10311
|
+
return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
|
|
10312
|
+
}
|
|
10313
|
+
const candidateBytes = saver.snapshot().bytes;
|
|
10314
|
+
const base = this.baseDocuments.acquireMemoryBase({
|
|
10315
|
+
key: `candidate:${signingId}`,
|
|
10316
|
+
bytes: new Uint8Array(candidateBytes),
|
|
10317
|
+
password
|
|
10318
|
+
});
|
|
10319
|
+
return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
|
|
10320
|
+
}
|
|
10321
|
+
/** Whether a signature's byte range reaches into the layer's loaded delta (past the base). */
|
|
10322
|
+
loadedDeltaHoldsSignedBytes() {
|
|
10323
|
+
const layer = this.session.source.layer;
|
|
10324
|
+
if (!layer || layer.kind === "fresh") return false;
|
|
10325
|
+
const baseSize = Number(this.runtime.fn.EPDFDoc_GetBaseBytesSize(this.session.requireDocPtr()));
|
|
10326
|
+
return new SignatureReader(this.runtime, this.session).readSnapshot().signatures.some((s) => s.signed && s.byteRange !== null && s.byteRange[2] + s.byteRange[3] > baseSize);
|
|
10327
|
+
}
|
|
10328
|
+
};
|
|
10329
|
+
function sameVersion(a, b) {
|
|
10330
|
+
return a.baseSha256 === b.baseSha256 && a.editsVersion === b.editsVersion;
|
|
10331
|
+
}
|
|
10332
|
+
//#endregion
|
|
10333
|
+
//#region src/features/signature/SignatureAnalyzer.ts
|
|
10334
|
+
const CHANGE_BY_CODE = {
|
|
10335
|
+
0: "added",
|
|
10336
|
+
1: "modified",
|
|
10337
|
+
2: "freed"
|
|
10338
|
+
};
|
|
10339
|
+
const KIND_BY_CODE = {
|
|
10340
|
+
0: "dictionary",
|
|
10341
|
+
1: "stream",
|
|
10342
|
+
2: "array",
|
|
10343
|
+
3: "scalar",
|
|
10344
|
+
4: "xref",
|
|
10345
|
+
5: "objstm",
|
|
10346
|
+
6: "trailer"
|
|
10347
|
+
};
|
|
10348
|
+
const DIFF_OLD = 0;
|
|
10349
|
+
const DIFF_NEW = 1;
|
|
10350
|
+
/** EPDF_DIFF_READ_OK / FAILED / ABSENT. */
|
|
10351
|
+
const READ_BY_CODE = {
|
|
10352
|
+
0: "ok",
|
|
10353
|
+
1: "failed",
|
|
10354
|
+
2: "absent"
|
|
10355
|
+
};
|
|
10356
|
+
/** The objects an edge may be anchored at: the trailer, the structure of the revision, and every changed object. */
|
|
10357
|
+
function anchorSet(s, changed) {
|
|
10358
|
+
const out = /* @__PURE__ */ new Set([
|
|
10359
|
+
0,
|
|
10360
|
+
s.root,
|
|
10361
|
+
s.pagesRoot,
|
|
10362
|
+
...s.pages,
|
|
10363
|
+
...changed
|
|
10364
|
+
]);
|
|
10365
|
+
if (s.acroForm) out.add(s.acroForm);
|
|
10366
|
+
for (const f of s.fields) {
|
|
10367
|
+
out.add(f.objectNumber);
|
|
10368
|
+
for (const w of f.widgets) out.add(w);
|
|
10369
|
+
}
|
|
10370
|
+
return out;
|
|
10371
|
+
}
|
|
10372
|
+
/**
|
|
10373
|
+
* Modification analysis over a session: which revisions to compare, the
|
|
10374
|
+
* fork's exact object diff for each adjacent pair, each revision's
|
|
10375
|
+
* structure, and the pure rule engine's verdict per step. Every revision
|
|
10376
|
+
* is opened as its own prefix document from the target's loaded bytes;
|
|
10377
|
+
* `working-copy` first snapshots the session the way a save would.
|
|
10378
|
+
*/
|
|
10379
|
+
var SignatureAnalyzer = class {
|
|
10380
|
+
constructor(runtime, session) {
|
|
10381
|
+
this.runtime = runtime;
|
|
10382
|
+
this.session = session;
|
|
10383
|
+
}
|
|
10384
|
+
/**
|
|
10385
|
+
* The signature snapshot of the session's working copy: the unsaved
|
|
10386
|
+
* state snapshotted the way a save would write it, as one more revision
|
|
10387
|
+
* over the loaded bytes. Identical to the loaded-bytes snapshot when
|
|
10388
|
+
* nothing is unsaved.
|
|
10389
|
+
*/
|
|
10390
|
+
readWorkingCopySnapshot() {
|
|
10391
|
+
const stack = new CloseStack();
|
|
10392
|
+
try {
|
|
10393
|
+
const copy = this.openWorkingCopy(stack);
|
|
10394
|
+
if (copy.source === "loaded") return new SignatureReader(this.runtime, this.session).readSnapshot();
|
|
10395
|
+
const target = copy.target;
|
|
10396
|
+
return withSignatureModel(this.runtime, target, (model) => {
|
|
10397
|
+
const chainValid = this.runtime.fn.EPDFSig_IsRevisionChainValid(model);
|
|
10398
|
+
const signatures = readSignaturesFromModel(this.runtime, model);
|
|
10399
|
+
return {
|
|
10400
|
+
chainValid,
|
|
10401
|
+
revisions: chainValid ? readRevisions(this.runtime, target, signatures) : [],
|
|
10402
|
+
signatures,
|
|
10403
|
+
protection: (0, _embedpdf_engine_core_runtime.deriveProtection)(signatures)
|
|
10404
|
+
};
|
|
10405
|
+
});
|
|
10406
|
+
} finally {
|
|
10407
|
+
stack.close();
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
analyze(input) {
|
|
10411
|
+
const reader = new SignatureReader(this.runtime, this.session);
|
|
10412
|
+
const snapshot = reader.readSnapshot();
|
|
10413
|
+
const version = reader.version();
|
|
10414
|
+
const mode = input.exploratoryLevel ? "exploratory" : "authoritative";
|
|
10415
|
+
const detail = input.detail ?? "summary";
|
|
10416
|
+
let basisSource = "persisted";
|
|
10417
|
+
const editsVersion = this.session.mutationSeq();
|
|
10418
|
+
let sinceRevision;
|
|
10419
|
+
let sinceSignature = null;
|
|
10420
|
+
if ("signatureIndex" in input.since) {
|
|
10421
|
+
const sig = snapshot.signatures[input.since.signatureIndex];
|
|
10422
|
+
if (!sig) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature ${input.since.signatureIndex}`);
|
|
10423
|
+
if (!sig.signed || sig.revisionIndex === null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `signature ${input.since.signatureIndex} seals no whole revision; nothing to analyze from`);
|
|
10424
|
+
sinceRevision = sig.revisionIndex;
|
|
10425
|
+
sinceSignature = sig.index;
|
|
10426
|
+
} else sinceRevision = input.since.revisionIndex;
|
|
10427
|
+
if (!snapshot.chainValid) {
|
|
10428
|
+
const current = {
|
|
10429
|
+
verdict: "indeterminate",
|
|
10430
|
+
complete: false,
|
|
10431
|
+
findings: [{
|
|
10432
|
+
rule: "revision-chain",
|
|
10433
|
+
verdict: "incomplete",
|
|
10434
|
+
objectNumber: 0,
|
|
10435
|
+
detail: "the cross-reference chain is broken or was rebuilt: no revision can be told from another"
|
|
10436
|
+
}],
|
|
10437
|
+
method: "net-state"
|
|
10438
|
+
};
|
|
10439
|
+
return {
|
|
10440
|
+
mode,
|
|
10441
|
+
policyVersion: _embedpdf_engine_core_runtime.SIGNATURE_POLICY_VERSION,
|
|
10442
|
+
basis: {
|
|
10443
|
+
version,
|
|
10444
|
+
editsVersion,
|
|
10445
|
+
source: "persisted"
|
|
10446
|
+
},
|
|
10447
|
+
since: {
|
|
10448
|
+
revisionIndex: sinceRevision,
|
|
10449
|
+
signatureIndex: sinceSignature
|
|
10450
|
+
},
|
|
10451
|
+
until: { revisionIndex: sinceRevision },
|
|
10452
|
+
restrictions: [],
|
|
10453
|
+
current: {
|
|
10454
|
+
...current,
|
|
10455
|
+
primary: current.findings[0]
|
|
10456
|
+
},
|
|
10457
|
+
later: {
|
|
10458
|
+
revisionCount: 0,
|
|
10459
|
+
undoneObjectNumbers: []
|
|
10460
|
+
},
|
|
10461
|
+
verdict: "indeterminate",
|
|
10462
|
+
steps: []
|
|
10463
|
+
};
|
|
10464
|
+
}
|
|
10465
|
+
if (!Number.isInteger(sinceRevision) || sinceRevision < 0 || sinceRevision >= snapshot.revisions.length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `no revision ${sinceRevision}`);
|
|
10466
|
+
const stack = new CloseStack();
|
|
10467
|
+
try {
|
|
10468
|
+
let target = this.session.requireDocPtr();
|
|
10469
|
+
let revisions = snapshot.revisions;
|
|
10470
|
+
const copy = input.until === "working-copy" ? this.openWorkingCopy(stack) : null;
|
|
10471
|
+
if (copy && copy.source === "working-copy") {
|
|
10472
|
+
basisSource = "working-copy";
|
|
10473
|
+
target = copy.target;
|
|
10474
|
+
const signatures = withSignatureModel(this.runtime, target, (m) => readSignaturesFromModel(this.runtime, m));
|
|
10475
|
+
revisions = readRevisions(this.runtime, target, signatures);
|
|
10476
|
+
if (revisions.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the working copy has no valid revision chain");
|
|
10477
|
+
}
|
|
10478
|
+
let untilRevision = revisions.length - 1;
|
|
10479
|
+
if (typeof input.until === "object") {
|
|
10480
|
+
untilRevision = input.until.revisionIndex;
|
|
10481
|
+
if (!Number.isInteger(untilRevision) || untilRevision < sinceRevision || untilRevision >= revisions.length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `no revision ${untilRevision} at or after ${sinceRevision}`);
|
|
10482
|
+
}
|
|
10483
|
+
const pair = new CloseStack();
|
|
10484
|
+
let before;
|
|
10485
|
+
let after;
|
|
10486
|
+
let changes = [];
|
|
10487
|
+
let health;
|
|
10488
|
+
try {
|
|
10489
|
+
const sealed = this.openPrefix(target, revisions, sinceRevision, pair);
|
|
10490
|
+
const judged = untilRevision === revisions.length - 1 ? target : this.openPrefix(target, revisions, untilRevision, pair);
|
|
10491
|
+
before = readStructure(this.runtime, sealed);
|
|
10492
|
+
after = untilRevision === sinceRevision ? before : readStructure(this.runtime, judged);
|
|
10493
|
+
if (untilRevision > sinceRevision) ({changes, health} = this.compare(sealed, judged, before, after));
|
|
10494
|
+
} finally {
|
|
10495
|
+
pair.close();
|
|
10496
|
+
}
|
|
10497
|
+
const judgedSignature = sinceSignature === null ? null : before.signatures[sinceSignature] ?? null;
|
|
10498
|
+
const restrictions = (0, _embedpdf_engine_core_runtime.restrictionsFor)(before, judgedSignature);
|
|
10499
|
+
const levelOverride = input.exploratoryLevel;
|
|
10500
|
+
const net = (0, _embedpdf_engine_core_runtime.evaluateStep)({
|
|
10501
|
+
older: sinceRevision,
|
|
10502
|
+
newer: untilRevision,
|
|
10503
|
+
changes,
|
|
10504
|
+
before,
|
|
10505
|
+
after,
|
|
10506
|
+
health,
|
|
10507
|
+
restrictions: {
|
|
10508
|
+
level: restrictions.level,
|
|
10509
|
+
locks: restrictions.locks
|
|
10510
|
+
},
|
|
10511
|
+
levelOverride
|
|
10512
|
+
});
|
|
10513
|
+
const certifies = restrictions.anchors.some((r) => r.source === "docmdp" && r.own);
|
|
10514
|
+
const intermediate = untilRevision - sinceRevision;
|
|
10515
|
+
let steps;
|
|
10516
|
+
if (intermediate <= 1) steps = intermediate === 1 ? [net] : [];
|
|
10517
|
+
else if (certifies || detail === "full") steps = this.walkSteps(target, revisions, sinceRevision, untilRevision, before, {
|
|
10518
|
+
level: restrictions.level,
|
|
10519
|
+
locks: restrictions.locks,
|
|
10520
|
+
levelOverride
|
|
10521
|
+
});
|
|
10522
|
+
else steps = [];
|
|
10523
|
+
const current = certifies ? {
|
|
10524
|
+
...(0, _embedpdf_engine_core_runtime.combine)(net, steps),
|
|
10525
|
+
method: "net-state+replay"
|
|
10526
|
+
} : {
|
|
10527
|
+
...(0, _embedpdf_engine_core_runtime.assessmentOf)(net),
|
|
10528
|
+
method: "net-state"
|
|
10529
|
+
};
|
|
10530
|
+
return {
|
|
10531
|
+
mode,
|
|
10532
|
+
policyVersion: _embedpdf_engine_core_runtime.SIGNATURE_POLICY_VERSION,
|
|
10533
|
+
basis: {
|
|
10534
|
+
version,
|
|
10535
|
+
editsVersion,
|
|
10536
|
+
source: basisSource
|
|
10537
|
+
},
|
|
10538
|
+
since: {
|
|
10539
|
+
revisionIndex: sinceRevision,
|
|
10540
|
+
signatureIndex: sinceSignature
|
|
10541
|
+
},
|
|
10542
|
+
until: { revisionIndex: untilRevision },
|
|
10543
|
+
restrictions: restrictions.anchors,
|
|
10544
|
+
current,
|
|
10545
|
+
later: {
|
|
10546
|
+
revisionCount: intermediate,
|
|
10547
|
+
undoneObjectNumbers: changes.filter(_embedpdf_engine_core_runtime.sameEffectiveValue).map((c) => c.objectNumber)
|
|
10548
|
+
},
|
|
10549
|
+
verdict: current.verdict,
|
|
10550
|
+
steps
|
|
10551
|
+
};
|
|
10552
|
+
} finally {
|
|
10553
|
+
stack.close();
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
/** Revision `index` of `target`'s bytes as its own document, owned by `stack`. */
|
|
10557
|
+
openPrefix(target, revisions, index, stack) {
|
|
10558
|
+
const prefix = this.runtime.fn.EPDFDoc_OpenRevision(target, BigInt(revisions[index].end));
|
|
10559
|
+
if (prefix === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, `revision ${index} does not open as a document`);
|
|
10560
|
+
stack.push(() => this.runtime.fn.FPDF_CloseDocument(prefix));
|
|
10561
|
+
return prefix;
|
|
10562
|
+
}
|
|
10563
|
+
/**
|
|
10564
|
+
* Every pairwise step between `since` and `until`, two prefix documents
|
|
10565
|
+
* open at a time whatever the revision count. `sealedStructure` is the
|
|
10566
|
+
* structure of `since` (already read); every later structure is read once
|
|
10567
|
+
* and dropped with its document.
|
|
10568
|
+
*/
|
|
10569
|
+
walkSteps(target, revisions, since, until, sealedStructure, restrictions) {
|
|
10570
|
+
const { fn } = this.runtime;
|
|
10571
|
+
const steps = [];
|
|
10572
|
+
let older = this.openPrefix(target, revisions, since, new CloseStack());
|
|
10573
|
+
let olderStructure = sealedStructure;
|
|
10574
|
+
try {
|
|
10575
|
+
for (let r = since + 1; r <= until; r++) {
|
|
10576
|
+
const newer = r === revisions.length - 1 ? target : this.openPrefix(target, revisions, r, new CloseStack());
|
|
10577
|
+
try {
|
|
10578
|
+
const newerStructure = readStructure(this.runtime, newer);
|
|
10579
|
+
const { changes, health } = this.compare(older, newer, olderStructure, newerStructure);
|
|
10580
|
+
steps.push((0, _embedpdf_engine_core_runtime.evaluateStep)({
|
|
10581
|
+
older: r - 1,
|
|
10582
|
+
newer: r,
|
|
10583
|
+
changes,
|
|
10584
|
+
before: olderStructure,
|
|
10585
|
+
after: newerStructure,
|
|
10586
|
+
health,
|
|
10587
|
+
restrictions: {
|
|
10588
|
+
level: restrictions.level,
|
|
10589
|
+
locks: restrictions.locks
|
|
10590
|
+
},
|
|
10591
|
+
levelOverride: restrictions.levelOverride
|
|
10592
|
+
}));
|
|
10593
|
+
olderStructure = newerStructure;
|
|
10594
|
+
} finally {
|
|
10595
|
+
fn.FPDF_CloseDocument(older);
|
|
10596
|
+
older = newer;
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10599
|
+
} finally {
|
|
10600
|
+
if (older !== target) fn.FPDF_CloseDocument(older);
|
|
10601
|
+
}
|
|
10602
|
+
return steps;
|
|
10603
|
+
}
|
|
10604
|
+
/**
|
|
10605
|
+
* The document as a save would write it, opened read-only, and whose
|
|
10606
|
+
* bytes that is. `loaded`: nothing was mutated since load, or the saver's
|
|
10607
|
+
* pass found no reachable object that differs from its loaded version —
|
|
10608
|
+
* the session's own document is the working copy. `working-copy`: a
|
|
10609
|
+
* layer session composes its immutable base with the cumulative delta
|
|
10610
|
+
* the pass emits (`EPDFDoc_OpenBaseOverlay`: no copy of the base, one
|
|
10611
|
+
* cross-reference parse, the loaded delta's revision REPLACED, as an
|
|
10612
|
+
* artifact is), or — when every edit brought the layer back to its base
|
|
10613
|
+
* — a fresh layer over that base; a plain session (unsigned by law 9, so
|
|
10614
|
+
* this is exploratory) materialises a standalone incremental save and
|
|
10615
|
+
* opens it whole.
|
|
10616
|
+
*/
|
|
10617
|
+
openWorkingCopy(stack) {
|
|
10618
|
+
const { fn, mem } = this.runtime;
|
|
10619
|
+
const docPtr = this.session.requireDocPtr();
|
|
10620
|
+
if (!this.session.hasUnsavedEdits()) return {
|
|
10621
|
+
target: docPtr,
|
|
10622
|
+
source: "loaded"
|
|
10623
|
+
};
|
|
10624
|
+
const saver = new DocumentSaver(this.runtime, this.session);
|
|
10625
|
+
if (this.session.kind === "layer") {
|
|
10626
|
+
const base = this.session.source.base;
|
|
10627
|
+
if (saver.canWriteScratchFiles() && base?.kind === "file") {
|
|
10628
|
+
const path = scratchPath(base.path, "working-copy", generateUuid(), "delta");
|
|
10629
|
+
stack.push(() => this.runtime.fileWrite.removeFile(path));
|
|
10630
|
+
if (!saver.saveLayerDeltaToFileEx(path).changedSinceLoad) return {
|
|
10631
|
+
target: docPtr,
|
|
10632
|
+
source: "loaded"
|
|
10633
|
+
};
|
|
10634
|
+
if (this.runtime.fileAccess.sizeOf(path) === 0) return this.freshLayerOverBase(stack);
|
|
10635
|
+
const overlay = withUtf8CString(mem, path, (pathPtr) => fn.EPDFDoc_OpenBaseOverlayFromPath(docPtr, pathPtr));
|
|
10636
|
+
if (overlay === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the working copy does not compose with its base");
|
|
10637
|
+
stack.push(() => fn.FPDF_CloseDocument(overlay));
|
|
10638
|
+
return {
|
|
10639
|
+
target: overlay,
|
|
10640
|
+
source: "working-copy"
|
|
10641
|
+
};
|
|
10642
|
+
}
|
|
10643
|
+
const delta = saver.saveLayerDeltaEx();
|
|
10644
|
+
if (!delta.changedSinceLoad) return {
|
|
10645
|
+
target: docPtr,
|
|
10646
|
+
source: "loaded"
|
|
10647
|
+
};
|
|
10648
|
+
if (delta.size === 0) return this.freshLayerOverBase(stack);
|
|
10649
|
+
const bytes = new Uint8Array(delta.bytes);
|
|
10650
|
+
const ptr = mem.alloc(bytes.byteLength);
|
|
10651
|
+
try {
|
|
10652
|
+
mem.writeBytes(ptr, bytes);
|
|
10653
|
+
const overlay = fn.EPDFDoc_OpenBaseOverlay(docPtr, ptr, bytes.byteLength);
|
|
10654
|
+
if (overlay === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the working copy does not compose with its base");
|
|
10655
|
+
stack.push(() => fn.FPDF_CloseDocument(overlay));
|
|
10656
|
+
return {
|
|
10657
|
+
target: overlay,
|
|
10658
|
+
source: "working-copy"
|
|
10659
|
+
};
|
|
10660
|
+
} finally {
|
|
10661
|
+
mem.free(ptr);
|
|
10662
|
+
}
|
|
10663
|
+
}
|
|
10664
|
+
const saved = saver.saveStandaloneToBufferEx("incremental");
|
|
10665
|
+
if (saved.unchangedSinceLoad) return {
|
|
10666
|
+
target: docPtr,
|
|
10667
|
+
source: "loaded"
|
|
10668
|
+
};
|
|
10669
|
+
const opened = openFatMemoryDocument(this.runtime, new Uint8Array(saved.bytes), this.session.password);
|
|
10670
|
+
stack.push(() => opened.close());
|
|
10671
|
+
return {
|
|
10672
|
+
target: opened.docPtr,
|
|
10673
|
+
source: "working-copy"
|
|
10674
|
+
};
|
|
10675
|
+
}
|
|
10676
|
+
/** Changed since load, nothing to write: the layer equals its base, and a fresh layer over it IS that document. */
|
|
10677
|
+
freshLayerOverBase(stack) {
|
|
10678
|
+
const { fn, mem } = this.runtime;
|
|
10679
|
+
const base = fn.EPDFLayer_GetBaseDocument(this.session.requireDocPtr());
|
|
10680
|
+
const statusPtr = mem.alloc(4);
|
|
8362
10681
|
try {
|
|
8363
10682
|
mem.poke(statusPtr, "i32", -1);
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
return {
|
|
10683
|
+
const fresh = fn.EPDFLayer_OpenLayer(base, _embedpdf_engine_runtime.NULL_PTR, this.session.password ?? "", statusPtr);
|
|
10684
|
+
if (fresh === _embedpdf_engine_runtime.NULL_PTR || Number(mem.peek(statusPtr, "i32")) !== 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the working copy could not be opened over its base");
|
|
10685
|
+
stack.push(() => fn.FPDF_CloseDocument(fresh));
|
|
10686
|
+
return {
|
|
10687
|
+
target: fresh,
|
|
10688
|
+
source: "working-copy"
|
|
10689
|
+
};
|
|
8368
10690
|
} finally {
|
|
8369
|
-
writer.close();
|
|
8370
10691
|
mem.free(statusPtr);
|
|
8371
10692
|
}
|
|
8372
10693
|
}
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
10694
|
+
/**
|
|
10695
|
+
* The fork's exact object diff between two revision documents,
|
|
10696
|
+
* marshalled: values in pass one, anchored edges in pass two (the anchors
|
|
10697
|
+
* are the changed objects plus each side's structural objects).
|
|
10698
|
+
*/
|
|
10699
|
+
compare(older, newer, before, after) {
|
|
10700
|
+
const { fn, mem } = this.runtime;
|
|
10701
|
+
const diff = fn.EPDFDoc_CompareRevisions(older, newer);
|
|
10702
|
+
if (diff === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the revisions do not share a byte history");
|
|
8377
10703
|
try {
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
10704
|
+
const count = fn.EPDFObjectDiff_GetCount(diff);
|
|
10705
|
+
const changes = [];
|
|
10706
|
+
withScratchN(mem, [
|
|
10707
|
+
4,
|
|
10708
|
+
4,
|
|
10709
|
+
4,
|
|
10710
|
+
4,
|
|
10711
|
+
4,
|
|
10712
|
+
4
|
|
10713
|
+
], ([numPtr, changePtr, kindPtr, oldGenPtr, newGenPtr, streamPtr]) => {
|
|
10714
|
+
for (let i = 0; i < count; i++) {
|
|
10715
|
+
for (const p of [
|
|
10716
|
+
numPtr,
|
|
10717
|
+
changePtr,
|
|
10718
|
+
kindPtr,
|
|
10719
|
+
oldGenPtr,
|
|
10720
|
+
newGenPtr,
|
|
10721
|
+
streamPtr
|
|
10722
|
+
]) mem.poke(p, "i32", 0);
|
|
10723
|
+
if (!fn.EPDFObjectDiff_GetEntry(diff, i, numPtr, changePtr, kindPtr, oldGenPtr, newGenPtr, streamPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `failed to read diff entry ${i}`);
|
|
10724
|
+
const objectNumber = Number(mem.peek(numPtr, "i32")) >>> 0;
|
|
10725
|
+
const oldGen = Number(mem.peek(oldGenPtr, "i32"));
|
|
10726
|
+
const newGen = Number(mem.peek(newGenPtr, "i32"));
|
|
10727
|
+
const oldValue = this.readValue(diff, i, DIFF_OLD);
|
|
10728
|
+
const newValue = this.readValue(diff, i, DIFF_NEW);
|
|
10729
|
+
let truncated = oldValue.truncated || newValue.truncated;
|
|
10730
|
+
let oldParsed = null;
|
|
10731
|
+
let newParsed = null;
|
|
10732
|
+
try {
|
|
10733
|
+
oldParsed = oldValue.text === null || oldValue.truncated ? null : (0, _embedpdf_engine_core_runtime.parsePdfValue)(oldValue.text);
|
|
10734
|
+
newParsed = newValue.text === null || newValue.truncated ? null : (0, _embedpdf_engine_core_runtime.parsePdfValue)(newValue.text);
|
|
10735
|
+
} catch {
|
|
10736
|
+
truncated = true;
|
|
10737
|
+
}
|
|
10738
|
+
const change = CHANGE_BY_CODE[Number(mem.peek(changePtr, "i32"))] ?? "modified";
|
|
10739
|
+
changes.push({
|
|
10740
|
+
objectNumber,
|
|
10741
|
+
change,
|
|
10742
|
+
kind: KIND_BY_CODE[Number(mem.peek(kindPtr, "i32"))] ?? "scalar",
|
|
10743
|
+
present: {
|
|
10744
|
+
old: change !== "added",
|
|
10745
|
+
new: change !== "freed"
|
|
10746
|
+
},
|
|
10747
|
+
generation: {
|
|
10748
|
+
old: oldGen < 0 ? null : oldGen,
|
|
10749
|
+
new: newGen < 0 ? null : newGen
|
|
10750
|
+
},
|
|
10751
|
+
value: {
|
|
10752
|
+
old: oldParsed,
|
|
10753
|
+
new: newParsed,
|
|
10754
|
+
truncated
|
|
10755
|
+
},
|
|
10756
|
+
raw: {
|
|
10757
|
+
old: oldValue.text,
|
|
10758
|
+
new: newValue.text
|
|
10759
|
+
},
|
|
10760
|
+
read: {
|
|
10761
|
+
old: READ_BY_CODE[fn.EPDFObjectDiff_GetReadStatus(diff, i, DIFF_OLD)] ?? "failed",
|
|
10762
|
+
new: READ_BY_CODE[fn.EPDFObjectDiff_GetReadStatus(diff, i, DIFF_NEW)] ?? "failed"
|
|
10763
|
+
},
|
|
10764
|
+
streamDataChanged: Number(mem.peek(streamPtr, "i32")) !== 0,
|
|
10765
|
+
usage: {
|
|
10766
|
+
old: [],
|
|
10767
|
+
new: []
|
|
10768
|
+
}
|
|
10769
|
+
});
|
|
10770
|
+
}
|
|
10771
|
+
});
|
|
8385
10772
|
return {
|
|
8386
|
-
|
|
8387
|
-
|
|
10773
|
+
changes: this.withAnchoredUsage(diff, changes, before, after),
|
|
10774
|
+
health: {
|
|
10775
|
+
old: this.readHealth(diff, DIFF_OLD),
|
|
10776
|
+
new: this.readHealth(diff, DIFF_NEW)
|
|
10777
|
+
}
|
|
8388
10778
|
};
|
|
8389
10779
|
} finally {
|
|
8390
|
-
|
|
8391
|
-
mem.free(sizePtr);
|
|
10780
|
+
fn.EPDFObjectDiff_Close(diff);
|
|
8392
10781
|
}
|
|
8393
10782
|
}
|
|
8394
|
-
|
|
8395
|
-
|
|
10783
|
+
/** What the fork learned about one revision as a whole (see `RevisionHealth`). */
|
|
10784
|
+
readHealth(diff, which) {
|
|
10785
|
+
const { fn, mem } = this.runtime;
|
|
10786
|
+
return withScratchN(mem, [
|
|
10787
|
+
4,
|
|
10788
|
+
4,
|
|
10789
|
+
4
|
|
10790
|
+
], ([sparsePtr, barePtr, completePtr]) => {
|
|
10791
|
+
for (const p of [
|
|
10792
|
+
sparsePtr,
|
|
10793
|
+
barePtr,
|
|
10794
|
+
completePtr
|
|
10795
|
+
]) mem.poke(p, "i32", 0);
|
|
10796
|
+
if (!fn.EPDFObjectDiff_GetRevisionHealth(diff, which, sparsePtr, barePtr, completePtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to read revision health");
|
|
10797
|
+
return {
|
|
10798
|
+
sparseXref: Number(mem.peek(sparsePtr, "i32")) !== 0,
|
|
10799
|
+
bareReferences: Number(mem.peek(barePtr, "i32")) >>> 0,
|
|
10800
|
+
referrersComplete: Number(mem.peek(completePtr, "i32")) !== 0
|
|
10801
|
+
};
|
|
10802
|
+
});
|
|
10803
|
+
}
|
|
10804
|
+
/**
|
|
10805
|
+
* Pass two: every changed object's inbound edges, resolved through
|
|
10806
|
+
* unchanged non-structural objects to the nearest anchor (the fork's
|
|
10807
|
+
* referrer index covers every reachable object, so the walk never leaves
|
|
10808
|
+
* the diff). An object whose edges exceed the resolver's budget is
|
|
10809
|
+
* marked `usageIncomplete` and the evaluator refuses to permit it.
|
|
10810
|
+
*/
|
|
10811
|
+
withAnchoredUsage(diff, changes, before, after) {
|
|
10812
|
+
const changed = new Set(changes.map((c) => c.objectNumber));
|
|
10813
|
+
const resolvers = {
|
|
10814
|
+
old: new _embedpdf_engine_core_runtime.EdgeResolver((n) => this.readReferrers(diff, DIFF_OLD, n), anchorSet(before, changed)),
|
|
10815
|
+
new: new _embedpdf_engine_core_runtime.EdgeResolver((n) => this.readReferrers(diff, DIFF_NEW, n), anchorSet(after, changed))
|
|
10816
|
+
};
|
|
10817
|
+
return changes.map((c) => {
|
|
10818
|
+
const old = c.present.old ? resolvers.old.resolve(c.objectNumber) : [];
|
|
10819
|
+
const now = c.present.new ? resolvers.new.resolve(c.objectNumber) : [];
|
|
10820
|
+
const incomplete = old === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE || now === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE;
|
|
10821
|
+
return {
|
|
10822
|
+
...c,
|
|
10823
|
+
usage: {
|
|
10824
|
+
old: old === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE ? [] : old,
|
|
10825
|
+
new: now === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE ? [] : now
|
|
10826
|
+
},
|
|
10827
|
+
...incomplete ? { usageIncomplete: true } : {}
|
|
10828
|
+
};
|
|
10829
|
+
});
|
|
10830
|
+
}
|
|
10831
|
+
readValue(diff, index, which) {
|
|
10832
|
+
const { fn, mem } = this.runtime;
|
|
10833
|
+
return withScratch(mem, 4, (truncPtr) => {
|
|
10834
|
+
mem.poke(truncPtr, "i32", 0);
|
|
10835
|
+
const length = fn.EPDFObjectDiff_GetValue(diff, index, which, _embedpdf_engine_runtime.NULL_PTR, 0, truncPtr);
|
|
10836
|
+
const truncatedProbe = Number(mem.peek(truncPtr, "i32")) !== 0;
|
|
10837
|
+
if (length <= 0) return {
|
|
10838
|
+
text: null,
|
|
10839
|
+
truncated: truncatedProbe
|
|
10840
|
+
};
|
|
10841
|
+
return withScratch(mem, length, (buf) => {
|
|
10842
|
+
mem.poke(truncPtr, "i32", 0);
|
|
10843
|
+
const written = fn.EPDFObjectDiff_GetValue(diff, index, which, buf, length, truncPtr);
|
|
10844
|
+
const truncated = Number(mem.peek(truncPtr, "i32")) !== 0;
|
|
10845
|
+
if (written <= 0) return {
|
|
10846
|
+
text: null,
|
|
10847
|
+
truncated
|
|
10848
|
+
};
|
|
10849
|
+
const bytes = mem.readBytes(buf, written - 1);
|
|
10850
|
+
let text = "";
|
|
10851
|
+
for (const b of bytes) text += String.fromCharCode(b);
|
|
10852
|
+
return {
|
|
10853
|
+
text,
|
|
10854
|
+
truncated
|
|
10855
|
+
};
|
|
10856
|
+
});
|
|
10857
|
+
});
|
|
10858
|
+
}
|
|
10859
|
+
readReferrers(diff, which, objectNumber) {
|
|
10860
|
+
const { fn, mem } = this.runtime;
|
|
10861
|
+
const count = fn.EPDFObjectDiff_GetReferrerCount(diff, which, objectNumber);
|
|
10862
|
+
const out = [];
|
|
10863
|
+
if (count <= 0) return out;
|
|
10864
|
+
withScratch(mem, 4, (parentPtr) => {
|
|
10865
|
+
for (let k = 0; k < count; k++) {
|
|
10866
|
+
mem.poke(parentPtr, "i32", 0);
|
|
10867
|
+
const length = fn.EPDFObjectDiff_GetReferrer(diff, which, objectNumber, k, parentPtr, _embedpdf_engine_runtime.NULL_PTR, 0);
|
|
10868
|
+
const label = length <= 0 ? "" : withScratch(mem, length, (buf) => {
|
|
10869
|
+
const written = fn.EPDFObjectDiff_GetReferrer(diff, which, objectNumber, k, parentPtr, buf, length);
|
|
10870
|
+
if (written <= 0) return "";
|
|
10871
|
+
const bytes = mem.readBytes(buf, written - 1);
|
|
10872
|
+
let text = "";
|
|
10873
|
+
for (const b of bytes) text += String.fromCharCode(b);
|
|
10874
|
+
return text;
|
|
10875
|
+
});
|
|
10876
|
+
out.push({
|
|
10877
|
+
parent: Number(mem.peek(parentPtr, "i32")) >>> 0,
|
|
10878
|
+
label
|
|
10879
|
+
});
|
|
10880
|
+
}
|
|
10881
|
+
});
|
|
10882
|
+
return out;
|
|
10883
|
+
}
|
|
10884
|
+
};
|
|
10885
|
+
//#endregion
|
|
10886
|
+
//#region src/features/signature/CandidateFinalizer.ts
|
|
10887
|
+
/**
|
|
10888
|
+
* The session-less half of a durable signing. A server prepares a
|
|
10889
|
+
* candidate on one replica, keeps only its tail, and may complete it on
|
|
10890
|
+
* another: the completing replica rebuilds base ⊕ tail into a private
|
|
10891
|
+
* file and hands it here with what the prepare reported. The CMS is
|
|
10892
|
+
* hex-encoded into the /Contents hole in place — never more than the
|
|
10893
|
+
* hole itself is held in memory — and the file is then opened like any
|
|
10894
|
+
* other base and read back through the signature model, so the version
|
|
10895
|
+
* returned is the file's own hash and the signature is what the file
|
|
10896
|
+
* says, not what the writer meant. Nothing here touches a live session.
|
|
10897
|
+
*/
|
|
10898
|
+
var CandidateFinalizer = class {
|
|
10899
|
+
constructor(runtime, baseDocuments) {
|
|
10900
|
+
this.runtime = runtime;
|
|
10901
|
+
this.baseDocuments = baseDocuments;
|
|
10902
|
+
}
|
|
10903
|
+
finalize(input) {
|
|
10904
|
+
const [r0, r1, r2, r3] = input.byteRange;
|
|
10905
|
+
const size = this.runtime.fileAccess.sizeOf(input.path);
|
|
10906
|
+
const isOffset = (n) => Number.isSafeInteger(n) && n >= 0;
|
|
10907
|
+
if (!input.byteRange.every(isOffset) || r0 !== 0 || r1 >= r2 || r2 + r3 !== size) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `byteRange [${input.byteRange.join(" ")}] does not span a ${size}-byte candidate`);
|
|
10908
|
+
const hexLength = r2 - r1 - 2;
|
|
10909
|
+
if (!Number.isSafeInteger(input.contentsSize) || hexLength !== 2 * input.contentsSize) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `the /Contents hole holds ${hexLength} hex digits, not the ${2 * input.contentsSize} of contentsSize`);
|
|
10910
|
+
if (input.cms.byteLength === 0 || input.cms[0] !== 48) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the CMS is not a DER SEQUENCE");
|
|
10911
|
+
if (input.cms.byteLength > input.contentsSize) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `the CMS (${input.cms.byteLength} bytes) does not fit the reserved ${input.contentsSize} bytes`);
|
|
10912
|
+
writeContentsIntoFile(this.runtime, input.path, {
|
|
10913
|
+
holeOffset: r1,
|
|
10914
|
+
hexLength
|
|
10915
|
+
}, input.cms);
|
|
10916
|
+
const session = new DocumentSession(this.runtime);
|
|
10917
|
+
const base = this.baseDocuments.acquireFileBase({
|
|
10918
|
+
key: `finalize:${generateUuid()}`,
|
|
10919
|
+
path: input.path,
|
|
10920
|
+
password: input.password
|
|
10921
|
+
});
|
|
8396
10922
|
try {
|
|
8397
|
-
|
|
8398
|
-
|
|
10923
|
+
session.openFromHandle(openLayerDocument(this.runtime, base, { kind: "fresh" }, input.password));
|
|
10924
|
+
const model = acquireSignatureModel(this.runtime, session);
|
|
10925
|
+
assertSealedSignature(this.runtime, session.requireDocPtr(), model, {
|
|
10926
|
+
fieldObjectNumber: input.fieldObjectNumber,
|
|
10927
|
+
byteRange: input.byteRange,
|
|
10928
|
+
cms: input.cms
|
|
10929
|
+
});
|
|
10930
|
+
const reader = new SignatureReader(this.runtime, session);
|
|
10931
|
+
const snapshot = reader.readSnapshot();
|
|
10932
|
+
return {
|
|
10933
|
+
signature: snapshot.signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === input.fieldObjectNumber),
|
|
10934
|
+
protection: snapshot.protection,
|
|
10935
|
+
version: reader.version()
|
|
10936
|
+
};
|
|
8399
10937
|
} finally {
|
|
8400
|
-
|
|
10938
|
+
session.close();
|
|
8401
10939
|
}
|
|
8402
10940
|
}
|
|
8403
|
-
requireLayer() {
|
|
8404
|
-
if (this.session.kind !== "layer") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "document session is not a layer");
|
|
8405
|
-
}
|
|
8406
10941
|
};
|
|
8407
10942
|
//#endregion
|
|
8408
10943
|
//#region src/features/fonts/FontRegistrar.ts
|
|
10944
|
+
/** `EPDF_FONT_EMBEDDING_*` codes. Restricted (3) and bitmap-only (4) are
|
|
10945
|
+
* refused at registration, so they never reach a handle. */
|
|
10946
|
+
function permissionFromCode(code) {
|
|
10947
|
+
switch (code) {
|
|
10948
|
+
case 1: return "editable";
|
|
10949
|
+
case 2: return "preview-and-print";
|
|
10950
|
+
default: return "installable";
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
/** The engine's family comparison: no case, spaces, hyphens, underscores. */
|
|
10954
|
+
function familyKey(family) {
|
|
10955
|
+
return family.toLowerCase().replace(/[\s\-_'"]/g, "");
|
|
10956
|
+
}
|
|
8409
10957
|
/**
|
|
8410
10958
|
* Thread-confined runtime font registry binding.
|
|
8411
10959
|
*
|
|
@@ -8429,6 +10977,7 @@ var FontRegistrar = class {
|
|
|
8429
10977
|
this.runtime = runtime;
|
|
8430
10978
|
this.ids = ids;
|
|
8431
10979
|
_defineProperty(this, "fileHandles", []);
|
|
10980
|
+
_defineProperty(this, "identities", /* @__PURE__ */ new Map());
|
|
8432
10981
|
}
|
|
8433
10982
|
/**
|
|
8434
10983
|
* Register a font from its bytes. Copies into the WASM heap, hands the
|
|
@@ -8447,6 +10996,11 @@ var FontRegistrar = class {
|
|
|
8447
10996
|
const id = fn.EPDFFont_RegisterMemFont64(familyName, weight, italic, ptr, bytes.byteLength);
|
|
8448
10997
|
if (id === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `font registration failed (unloadable or no glyphs): ${fontKey}`);
|
|
8449
10998
|
this.ids.set(fontKey, id);
|
|
10999
|
+
this.identities.set(fontKey, this.readIdentity(id, {
|
|
11000
|
+
familyName,
|
|
11001
|
+
weight,
|
|
11002
|
+
italic
|
|
11003
|
+
}));
|
|
8450
11004
|
} finally {
|
|
8451
11005
|
mem.free(ptr);
|
|
8452
11006
|
}
|
|
@@ -8472,8 +11026,72 @@ var FontRegistrar = class {
|
|
|
8472
11026
|
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `font registration failed (unloadable or no glyphs): ${fontKey} (${path})`);
|
|
8473
11027
|
}
|
|
8474
11028
|
this.ids.set(fontKey, id);
|
|
11029
|
+
this.identities.set(fontKey, this.readIdentity(id, {
|
|
11030
|
+
familyName,
|
|
11031
|
+
weight,
|
|
11032
|
+
italic
|
|
11033
|
+
}));
|
|
8475
11034
|
this.fileHandles.push(access);
|
|
8476
11035
|
}
|
|
11036
|
+
/** The identity and licence the runtime resolved for a key. Throws if unknown. */
|
|
11037
|
+
describe(fontKey) {
|
|
11038
|
+
const identity = this.identities.get(fontKey);
|
|
11039
|
+
if (!identity) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `font not registered on this thread: ${fontKey}`);
|
|
11040
|
+
return identity;
|
|
11041
|
+
}
|
|
11042
|
+
/** {@link describe} for callers that treat an unknown key as "not a key". */
|
|
11043
|
+
describeOrUndefined(fontKey) {
|
|
11044
|
+
return this.identities.get(fontKey);
|
|
11045
|
+
}
|
|
11046
|
+
/**
|
|
11047
|
+
* The key of the registered font whose identity matches a face (family
|
|
11048
|
+
* compared the engine's way; the closest weight, italic first), or
|
|
11049
|
+
* undefined when no registered family matches.
|
|
11050
|
+
*/
|
|
11051
|
+
keyForFace(family, weight, italic) {
|
|
11052
|
+
const wanted = familyKey(family);
|
|
11053
|
+
let best;
|
|
11054
|
+
for (const [key, identity] of this.identities) {
|
|
11055
|
+
if (familyKey(identity.familyName) !== wanted) continue;
|
|
11056
|
+
const score = Math.abs(identity.weight - weight) + (identity.italic === italic ? 0 : 1e3);
|
|
11057
|
+
if (!best || score < best.score) best = {
|
|
11058
|
+
key,
|
|
11059
|
+
score
|
|
11060
|
+
};
|
|
11061
|
+
}
|
|
11062
|
+
return best?.key;
|
|
11063
|
+
}
|
|
11064
|
+
/** The application asserts a licence permitting editing with the font. */
|
|
11065
|
+
authorizeEditing(fontKey) {
|
|
11066
|
+
const id = this.requireId(fontKey);
|
|
11067
|
+
if (!this.runtime.fn.EPDFFont_AuthorizeEditing(id)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `authorizeEditing failed: ${fontKey}`);
|
|
11068
|
+
const previous = this.describe(fontKey);
|
|
11069
|
+
const identity = this.readIdentity(id, {
|
|
11070
|
+
familyName: previous.familyName,
|
|
11071
|
+
weight: previous.weight,
|
|
11072
|
+
italic: previous.italic ? 1 : 0
|
|
11073
|
+
});
|
|
11074
|
+
this.identities.set(fontKey, identity);
|
|
11075
|
+
return identity;
|
|
11076
|
+
}
|
|
11077
|
+
readIdentity(id, given) {
|
|
11078
|
+
const { fn, mem } = this.runtime;
|
|
11079
|
+
const attempt = (read, fallback) => {
|
|
11080
|
+
try {
|
|
11081
|
+
return read();
|
|
11082
|
+
} catch {
|
|
11083
|
+
return fallback;
|
|
11084
|
+
}
|
|
11085
|
+
};
|
|
11086
|
+
return {
|
|
11087
|
+
familyName: attempt(() => readUtf8String(mem, (buf, cap) => fn.EPDFFont_GetFamilyName(id, buf, cap)) ?? given.familyName, given.familyName),
|
|
11088
|
+
weight: attempt(() => fn.EPDFFont_GetWeight(id), given.weight || 400),
|
|
11089
|
+
italic: attempt(() => fn.EPDFFont_IsItalic(id), given.italic === 1),
|
|
11090
|
+
embeddingPermission: attempt(() => permissionFromCode(fn.EPDFFont_GetEmbeddingPermission(id)), "installable"),
|
|
11091
|
+
editingAuthorized: attempt(() => fn.EPDFFont_IsEditingAuthorized(id), true),
|
|
11092
|
+
instanced: attempt(() => fn.EPDFFont_IsInstanced(id), false)
|
|
11093
|
+
};
|
|
11094
|
+
}
|
|
8477
11095
|
/**
|
|
8478
11096
|
* Register host-owned startup fonts on this thread, in order, optionally
|
|
8479
11097
|
* adding each to the fallback chain. Order matters: it fixes both the native
|
|
@@ -8499,6 +11117,7 @@ var FontRegistrar = class {
|
|
|
8499
11117
|
clear() {
|
|
8500
11118
|
this.runtime.fn.EPDFFont_ClearRegisteredFonts();
|
|
8501
11119
|
this.ids.clear();
|
|
11120
|
+
this.identities.clear();
|
|
8502
11121
|
for (const access of this.fileHandles) access.close();
|
|
8503
11122
|
this.fileHandles.length = 0;
|
|
8504
11123
|
}
|
|
@@ -8651,35 +11270,14 @@ function flagMasks(flags) {
|
|
|
8651
11270
|
};
|
|
8652
11271
|
}
|
|
8653
11272
|
//#endregion
|
|
8654
|
-
//#region src/features/forms/internal/wideStringArray.ts
|
|
8655
|
-
/**
|
|
8656
|
-
* Marshal a `const FPDF_WIDESTRING*` array: encode every string into the
|
|
8657
|
-
* runtime heap, write the pointer table with the runtime's pointer width
|
|
8658
|
-
* (bigint pointers cannot be poked into the wasm 32-bit heap), run |body|,
|
|
8659
|
-
* free everything.
|
|
8660
|
-
*/
|
|
8661
|
-
function withWideStringArray(runtime, values, body) {
|
|
8662
|
-
const { mem } = runtime;
|
|
8663
|
-
const stringPtrs = values.map((value) => mem.writeU16String(value));
|
|
8664
|
-
const isWasm = runtime.kind === "wasm";
|
|
8665
|
-
const ptrSize = isWasm ? 4 : 8;
|
|
8666
|
-
try {
|
|
8667
|
-
return withScratch(mem, Math.max(1, stringPtrs.length * ptrSize), (arrayPtr) => {
|
|
8668
|
-
stringPtrs.forEach((ptr, i) => isWasm ? mem.poke(arrayPtr, "i32", Number(ptr), i * 4) : mem.poke(arrayPtr, "i64", ptr, i * 8));
|
|
8669
|
-
return body(arrayPtr, stringPtrs.length);
|
|
8670
|
-
});
|
|
8671
|
-
} finally {
|
|
8672
|
-
for (let i = stringPtrs.length - 1; i >= 0; i--) mem.free(stringPtrs[i]);
|
|
8673
|
-
}
|
|
8674
|
-
}
|
|
8675
|
-
//#endregion
|
|
8676
11273
|
//#region src/features/forms/FormMutator.ts
|
|
8677
11274
|
const FAMILY_CODE = {
|
|
8678
11275
|
checkbox: 2,
|
|
8679
11276
|
radio: 3,
|
|
8680
11277
|
text: 4,
|
|
8681
11278
|
combobox: 5,
|
|
8682
|
-
listbox: 6
|
|
11279
|
+
listbox: 6,
|
|
11280
|
+
signature: 7
|
|
8683
11281
|
};
|
|
8684
11282
|
/** Widgets a single value write can touch; far above any real form. */
|
|
8685
11283
|
const CHANGED_WIDGETS_CAPACITY$1 = 256;
|
|
@@ -8712,7 +11310,7 @@ var FormMutator = class {
|
|
|
8712
11310
|
throwIfAborted(signal);
|
|
8713
11311
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8714
11312
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8715
|
-
this.assertWritable(resolved
|
|
11313
|
+
this.assertWritable(resolved);
|
|
8716
11314
|
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8717
11315
|
if (!FAMILY_BY_VALUE_TYPE[value.type].includes(before.family)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `value type '${value.type}' does not apply to a '${before.family}' field`);
|
|
8718
11316
|
const changed = this.applyWrite(resolved.fieldObjectNumber, value);
|
|
@@ -8722,7 +11320,7 @@ var FormMutator = class {
|
|
|
8722
11320
|
throwIfAborted(signal);
|
|
8723
11321
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8724
11322
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8725
|
-
this.assertWritable(resolved
|
|
11323
|
+
this.assertWritable(resolved);
|
|
8726
11324
|
const changed = this.withChangedWidgets((buf, cap, countPtr) => this.runtime.fn.EPDFForm_ResetField(this.session.requireDocPtr(), resolved.fieldObjectNumber, buf, cap, countPtr));
|
|
8727
11325
|
if (changed === null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "form field cannot be reset");
|
|
8728
11326
|
return this.readBack(resolved.fieldObjectNumber, changed);
|
|
@@ -8815,13 +11413,39 @@ var FormMutator = class {
|
|
|
8815
11413
|
this.session.noteMutation();
|
|
8816
11414
|
return { field: this.readBackField(fieldObjectNumber) };
|
|
8817
11415
|
}
|
|
11416
|
+
/**
|
|
11417
|
+
* Draw a PDF page into every widget of an UNSIGNED signature field: the
|
|
11418
|
+
* visual "sign" of a viewer without a signer. The field's value stays
|
|
11419
|
+
* empty and nothing is sealed; a signed field is refused (its appearance
|
|
11420
|
+
* is part of what the signature covers). Pages whose widgets changed are
|
|
11421
|
+
* reported so their renders re-pin.
|
|
11422
|
+
*/
|
|
11423
|
+
setSignatureAppearance(ref, pdf, pageIndex, signal) {
|
|
11424
|
+
throwIfAborted(signal);
|
|
11425
|
+
const docPtr = this.session.requireDocPtr();
|
|
11426
|
+
const model = acquireFormModel(this.runtime, this.session);
|
|
11427
|
+
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
11428
|
+
this.assertWritable(resolved);
|
|
11429
|
+
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, docPtr);
|
|
11430
|
+
if (before.family !== "signature") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `'${before.name}' is a ${before.family} field, not a signature field`);
|
|
11431
|
+
if (withSignatureModel(this.runtime, docPtr, (signatures) => readSignaturesFromModel(this.runtime, signatures).some((s) => s.signed && s.field.kind === "objectNumber" && s.field.fieldObjectNumber === resolved.fieldObjectNumber))) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `'${before.name}' is signed; its appearance is sealed with the signature`);
|
|
11432
|
+
if (before.widgets.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `'${before.name}' has no widget to draw into`);
|
|
11433
|
+
for (const widget of before.widgets) bakeWidgetAppearance(this.runtime, docPtr, widget, pdf, pageIndex);
|
|
11434
|
+
this.session.noteMutation();
|
|
11435
|
+
const pages = [...new Set(before.widgets.map((w) => w.pageObjectNumber))];
|
|
11436
|
+
for (const pon of pages) this.session.bumpRevision(pon);
|
|
11437
|
+
return {
|
|
11438
|
+
field: this.readBackField(resolved.fieldObjectNumber),
|
|
11439
|
+
pages
|
|
11440
|
+
};
|
|
11441
|
+
}
|
|
8818
11442
|
updateField(ref, patch, signal) {
|
|
8819
11443
|
throwIfAborted(signal);
|
|
8820
11444
|
const { fn } = this.runtime;
|
|
8821
11445
|
const docPtr = this.session.requireDocPtr();
|
|
8822
11446
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8823
11447
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8824
|
-
this.assertWritable(resolved
|
|
11448
|
+
this.assertWritable(resolved);
|
|
8825
11449
|
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8826
11450
|
if (before.family !== patch.family) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `patch family '${patch.family}' does not match field family '${before.family}'`);
|
|
8827
11451
|
const fieldObjectNumber = resolved.fieldObjectNumber;
|
|
@@ -8852,7 +11476,7 @@ var FormMutator = class {
|
|
|
8852
11476
|
const { fn, mem } = this.runtime;
|
|
8853
11477
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8854
11478
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8855
|
-
this.assertWritable(resolved
|
|
11479
|
+
this.assertWritable(resolved);
|
|
8856
11480
|
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8857
11481
|
if (!withScratchN(mem, [256 * 4, 4], ([buf, countPtr]) => {
|
|
8858
11482
|
mem.poke(countPtr, "i32", 0);
|
|
@@ -8872,7 +11496,7 @@ var FormMutator = class {
|
|
|
8872
11496
|
const { fn } = this.runtime;
|
|
8873
11497
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8874
11498
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8875
|
-
this.assertWritable(resolved
|
|
11499
|
+
this.assertWritable(resolved);
|
|
8876
11500
|
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8877
11501
|
const toggle = before.family === "checkbox" || before.family === "radio";
|
|
8878
11502
|
const state = toggle ? onState ?? (before.family === "checkbox" ? "Yes" : "") : "";
|
|
@@ -8886,7 +11510,7 @@ var FormMutator = class {
|
|
|
8886
11510
|
const { fn } = this.runtime;
|
|
8887
11511
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8888
11512
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8889
|
-
this.assertWritable(resolved
|
|
11513
|
+
this.assertWritable(resolved);
|
|
8890
11514
|
if (!fn.EPDFForm_DetachWidget(this.session.requireDocPtr(), resolved.fieldObjectNumber, widget.annotObjectNumber)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "widget is not attached to this field");
|
|
8891
11515
|
this.session.noteMutation();
|
|
8892
11516
|
return { field: this.readBackField(resolved.fieldObjectNumber) };
|
|
@@ -8921,8 +11545,30 @@ var FormMutator = class {
|
|
|
8921
11545
|
if (fieldIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "form field vanished after write");
|
|
8922
11546
|
return readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
|
|
8923
11547
|
}
|
|
8924
|
-
assertWritable(
|
|
8925
|
-
if (fieldObjectNumber === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "form field is stored as a direct object and cannot be written");
|
|
11548
|
+
assertWritable(resolved) {
|
|
11549
|
+
if (resolved.fieldObjectNumber === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "form field is stored as a direct object and cannot be written");
|
|
11550
|
+
this.assertNotLockedBySignature(resolved);
|
|
11551
|
+
}
|
|
11552
|
+
/**
|
|
11553
|
+
* A field an earlier signature froze (its FieldMDP, or the /Lock of a
|
|
11554
|
+
* signed field) refuses every write: document-derived authority, the
|
|
11555
|
+
* same way encryption bits are. Off under `signedDocumentPolicy:
|
|
11556
|
+
* 'permit'`. A document whose signature model cannot be built is not
|
|
11557
|
+
* known to be locked.
|
|
11558
|
+
*/
|
|
11559
|
+
assertNotLockedBySignature(resolved) {
|
|
11560
|
+
if (this.session.signedDocumentPolicy !== "protect") return;
|
|
11561
|
+
let protection;
|
|
11562
|
+
try {
|
|
11563
|
+
protection = new SignatureReader(this.runtime, this.session).readProtection();
|
|
11564
|
+
} catch {
|
|
11565
|
+
return;
|
|
11566
|
+
}
|
|
11567
|
+
if (protection.fieldLocks.length === 0) return;
|
|
11568
|
+
const model = acquireFormModel(this.runtime, this.session);
|
|
11569
|
+
const name = readUtf16String(this.runtime.mem, (buf, cap) => this.runtime.fn.EPDFForm_GetFieldName(model, resolved.fieldIndex, buf, cap)) ?? "";
|
|
11570
|
+
const lock = (0, _embedpdf_engine_core_runtime.fieldLockFor)(protection, name);
|
|
11571
|
+
if (lock) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.ProtectedDocument, `form field "${name}" is locked by signature ${lock.signatureIndex} (${lock.source === "fieldmdp" ? "FieldMDP" : "/Lock"})`);
|
|
8926
11572
|
}
|
|
8927
11573
|
/** Dispatch the typed native write. Returns the changed widget objnums. */
|
|
8928
11574
|
applyWrite(fieldObjectNumber, value) {
|
|
@@ -9769,6 +12415,7 @@ var WorkerHost = class {
|
|
|
9769
12415
|
this.aborts.set(msg.jobId, ctrl);
|
|
9770
12416
|
let resultPack;
|
|
9771
12417
|
try {
|
|
12418
|
+
this.assertNoPendingSigning(msg);
|
|
9772
12419
|
switch (msg.kind) {
|
|
9773
12420
|
case "open.fatMem":
|
|
9774
12421
|
case "open.layerMemBase":
|
|
@@ -9808,6 +12455,36 @@ var WorkerHost = class {
|
|
|
9808
12455
|
case "annotations.move":
|
|
9809
12456
|
resultPack = this.handleAnnotationsMove(msg, ctrl.signal);
|
|
9810
12457
|
break;
|
|
12458
|
+
case "signatures.list":
|
|
12459
|
+
resultPack = this.handleSignaturesList(msg);
|
|
12460
|
+
break;
|
|
12461
|
+
case "signatures.contents":
|
|
12462
|
+
resultPack = this.handleSignaturesContents(msg);
|
|
12463
|
+
break;
|
|
12464
|
+
case "signatures.digest":
|
|
12465
|
+
resultPack = this.handleSignaturesDigest(msg);
|
|
12466
|
+
break;
|
|
12467
|
+
case "signatures.revisionBytes":
|
|
12468
|
+
resultPack = this.handleSignaturesRevisionBytes(msg);
|
|
12469
|
+
break;
|
|
12470
|
+
case "document.version":
|
|
12471
|
+
resultPack = this.handleDocumentVersion(msg);
|
|
12472
|
+
break;
|
|
12473
|
+
case "signatures.prepare":
|
|
12474
|
+
resultPack = this.handleSignaturesPrepare(msg);
|
|
12475
|
+
break;
|
|
12476
|
+
case "signatures.complete":
|
|
12477
|
+
resultPack = this.handleSignaturesComplete(msg);
|
|
12478
|
+
break;
|
|
12479
|
+
case "signatures.abort":
|
|
12480
|
+
resultPack = this.handleSignaturesAbort(msg);
|
|
12481
|
+
break;
|
|
12482
|
+
case "signatures.analyze":
|
|
12483
|
+
resultPack = this.handleSignaturesAnalyze(msg);
|
|
12484
|
+
break;
|
|
12485
|
+
case "signatures.finalizeCandidate":
|
|
12486
|
+
resultPack = this.handleSignaturesFinalizeCandidate(msg);
|
|
12487
|
+
break;
|
|
9811
12488
|
case "forms.list":
|
|
9812
12489
|
resultPack = this.handleFormsList(msg, ctrl.signal);
|
|
9813
12490
|
break;
|
|
@@ -9835,6 +12512,9 @@ var WorkerHost = class {
|
|
|
9835
12512
|
case "forms.updateField":
|
|
9836
12513
|
resultPack = this.handleFormsUpdateField(msg, ctrl.signal);
|
|
9837
12514
|
break;
|
|
12515
|
+
case "forms.setSignatureAppearance":
|
|
12516
|
+
resultPack = this.handleFormsSetSignatureAppearance(msg, ctrl.signal);
|
|
12517
|
+
break;
|
|
9838
12518
|
case "forms.deleteField":
|
|
9839
12519
|
resultPack = this.handleFormsDeleteField(msg, ctrl.signal);
|
|
9840
12520
|
break;
|
|
@@ -9952,6 +12632,12 @@ var WorkerHost = class {
|
|
|
9952
12632
|
case "fonts.clear":
|
|
9953
12633
|
resultPack = this.handleFontsClear(msg);
|
|
9954
12634
|
break;
|
|
12635
|
+
case "fonts.authorizeEditing":
|
|
12636
|
+
resultPack = this.handleFontsAuthorizeEditing(msg);
|
|
12637
|
+
break;
|
|
12638
|
+
case "document.setFontSettings":
|
|
12639
|
+
resultPack = this.handleDocumentSetFontSettings(msg);
|
|
12640
|
+
break;
|
|
9955
12641
|
case "layer.close":
|
|
9956
12642
|
resultPack = this.handleLayerClose(msg);
|
|
9957
12643
|
break;
|
|
@@ -9983,10 +12669,13 @@ var WorkerHost = class {
|
|
|
9983
12669
|
const key = sessionKey(req.docId, req.kind === "open.fatMem" ? void 0 : req.layerName);
|
|
9984
12670
|
if (this.sessions.has(key)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `document session already open: ${key}`);
|
|
9985
12671
|
const session = new DocumentSession(this.runtime);
|
|
12672
|
+
session.signedDocumentPolicy = req.signedDocumentPolicy ?? "protect";
|
|
12673
|
+
session.password = req.password;
|
|
9986
12674
|
if (req.kind === "open.fatMem") {
|
|
12675
|
+
session.sessionKind = req.sessionKind ?? "layer";
|
|
9987
12676
|
const bytes = new Uint8Array(req.bytes);
|
|
9988
12677
|
try {
|
|
9989
|
-
|
|
12678
|
+
this.openSignedAware(session, bytes, req.password);
|
|
9990
12679
|
} catch (error) {
|
|
9991
12680
|
if (!isPasswordOpenError(error)) throw error;
|
|
9992
12681
|
session.parkLocked(bytes);
|
|
@@ -10001,14 +12690,16 @@ var WorkerHost = class {
|
|
|
10001
12690
|
const base = this.baseDocuments.acquireMemoryBase({
|
|
10002
12691
|
key: req.baseKey,
|
|
10003
12692
|
bytes: new Uint8Array(req.baseBytes),
|
|
10004
|
-
password: req.password
|
|
12693
|
+
password: req.password,
|
|
12694
|
+
knownSha256: req.baseSha256
|
|
10005
12695
|
});
|
|
10006
12696
|
session.openFromHandle(openLayerDocument(this.runtime, base, req.layer, req.password));
|
|
10007
12697
|
} else {
|
|
10008
12698
|
const base = this.baseDocuments.acquireFileBase({
|
|
10009
12699
|
key: req.baseKey,
|
|
10010
12700
|
path: req.basePath,
|
|
10011
|
-
password: req.password
|
|
12701
|
+
password: req.password,
|
|
12702
|
+
knownSha256: req.baseSha256
|
|
10012
12703
|
});
|
|
10013
12704
|
session.openFromHandle(openLayerDocument(this.runtime, base, req.layer, req.password));
|
|
10014
12705
|
}
|
|
@@ -10016,9 +12707,160 @@ var WorkerHost = class {
|
|
|
10016
12707
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10017
12708
|
tag: "open",
|
|
10018
12709
|
docId: req.docId,
|
|
10019
|
-
security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password ?? "")
|
|
12710
|
+
security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password ?? ""),
|
|
12711
|
+
protection: this.probeProtection(session)
|
|
12712
|
+
});
|
|
12713
|
+
}
|
|
12714
|
+
/**
|
|
12715
|
+
* Open plain bytes into `session` in the shape it asked for. The default,
|
|
12716
|
+
* `layer`, makes the bytes an immutable base with a fresh layer on top:
|
|
12717
|
+
* reads fall through to the base, a write promotes only what it touches,
|
|
12718
|
+
* a save appends exactly that, and a signature keeps its validity across
|
|
12719
|
+
* later edits because the signature dictionary is never rewritten. The
|
|
12720
|
+
* `plain` shape keeps one in-memory document — unless the bytes already
|
|
12721
|
+
* carry a signature value, which always opens as a layer, because a plain
|
|
12722
|
+
* save rewrites every loaded object into the new revision and strict
|
|
12723
|
+
* validators reject that. Auto-layered sessions serialize no artifact per
|
|
12724
|
+
* mutation: the caller never asked for one.
|
|
12725
|
+
*/
|
|
12726
|
+
openSignedAware(session, bytes, password) {
|
|
12727
|
+
if (session.sessionKind === "plain") {
|
|
12728
|
+
const plain = openFatMemoryDocument(this.runtime, bytes, password);
|
|
12729
|
+
let signed = false;
|
|
12730
|
+
try {
|
|
12731
|
+
signed = hasSignedSignature(this.runtime, plain.docPtr);
|
|
12732
|
+
} catch {
|
|
12733
|
+
signed = false;
|
|
12734
|
+
}
|
|
12735
|
+
if (!signed) {
|
|
12736
|
+
session.openFromHandle(plain);
|
|
12737
|
+
return;
|
|
12738
|
+
}
|
|
12739
|
+
plain.close();
|
|
12740
|
+
}
|
|
12741
|
+
const base = this.baseDocuments.acquireMemoryBase({
|
|
12742
|
+
key: `signed-open:${generateUuid()}`,
|
|
12743
|
+
bytes,
|
|
12744
|
+
password
|
|
12745
|
+
});
|
|
12746
|
+
session.openFromHandle(openLayerDocument(this.runtime, base, { kind: "fresh" }, password));
|
|
12747
|
+
session.persistLayerArtifact = false;
|
|
12748
|
+
}
|
|
12749
|
+
/**
|
|
12750
|
+
* What the document's signatures forbid, read at open and after an
|
|
12751
|
+
* unlock so the main-thread guard can subtract capabilities before the
|
|
12752
|
+
* first mutation. A document whose signature model cannot be built
|
|
12753
|
+
* still opens: protection is then `null` (nothing is known to be
|
|
12754
|
+
* signed), and the reads report the failure on their own.
|
|
12755
|
+
*/
|
|
12756
|
+
probeProtection(session) {
|
|
12757
|
+
try {
|
|
12758
|
+
return new SignatureReader(this.runtime, session).readProtection();
|
|
12759
|
+
} catch {
|
|
12760
|
+
return null;
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12763
|
+
handleSignaturesList(req) {
|
|
12764
|
+
const session = this.requireSession(req);
|
|
12765
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12766
|
+
tag: "signatures.list",
|
|
12767
|
+
snapshot: req.workingCopy ? new SignatureAnalyzer(this.runtime, session).readWorkingCopySnapshot() : new SignatureReader(this.runtime, session).readSnapshot()
|
|
12768
|
+
});
|
|
12769
|
+
}
|
|
12770
|
+
handleSignaturesContents(req) {
|
|
12771
|
+
const session = this.requireSession(req);
|
|
12772
|
+
const bytes = new SignatureReader(this.runtime, session).readContents(req.ref);
|
|
12773
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12774
|
+
tag: "signatures.contents",
|
|
12775
|
+
bytes
|
|
12776
|
+
}, [bytes]);
|
|
12777
|
+
}
|
|
12778
|
+
handleSignaturesDigest(req) {
|
|
12779
|
+
const session = this.requireSession(req);
|
|
12780
|
+
const digest = new SignatureReader(this.runtime, session).digest(req.ref, req.algorithm);
|
|
12781
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12782
|
+
tag: "signatures.digest",
|
|
12783
|
+
digest
|
|
12784
|
+
}, [digest]);
|
|
12785
|
+
}
|
|
12786
|
+
handleSignaturesRevisionBytes(req) {
|
|
12787
|
+
const session = this.requireSession(req);
|
|
12788
|
+
const bytes = new SignatureReader(this.runtime, session).revisionBytes(req.revisionIndex);
|
|
12789
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12790
|
+
tag: "signatures.revisionBytes",
|
|
12791
|
+
bytes,
|
|
12792
|
+
size: bytes.byteLength
|
|
12793
|
+
}, [bytes]);
|
|
12794
|
+
}
|
|
12795
|
+
handleDocumentVersion(req) {
|
|
12796
|
+
const session = this.requireSession(req);
|
|
12797
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12798
|
+
tag: "document.version",
|
|
12799
|
+
version: new SignatureReader(this.runtime, session).version()
|
|
12800
|
+
});
|
|
12801
|
+
}
|
|
12802
|
+
handleSignaturesPrepare(req) {
|
|
12803
|
+
const session = this.requireSession(req);
|
|
12804
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12805
|
+
tag: "signatures.prepare",
|
|
12806
|
+
result: new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).prepare(req.input)
|
|
12807
|
+
});
|
|
12808
|
+
}
|
|
12809
|
+
handleSignaturesComplete(req) {
|
|
12810
|
+
const session = this.requireSession(req);
|
|
12811
|
+
const result = new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).complete(req.input);
|
|
12812
|
+
if (result.status === "already-completed") return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12813
|
+
tag: "signatures.complete",
|
|
12814
|
+
result
|
|
12815
|
+
});
|
|
12816
|
+
return this.finishMutation(session, {
|
|
12817
|
+
tag: "signatures.complete",
|
|
12818
|
+
result
|
|
12819
|
+
}, req.artifactPath);
|
|
12820
|
+
}
|
|
12821
|
+
handleSignaturesAnalyze(req) {
|
|
12822
|
+
const session = this.requireSession(req);
|
|
12823
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12824
|
+
tag: "signatures.analyze",
|
|
12825
|
+
analysis: new SignatureAnalyzer(this.runtime, session).analyze(req.input)
|
|
12826
|
+
});
|
|
12827
|
+
}
|
|
12828
|
+
/**
|
|
12829
|
+
* Session-less, like `document.renderPageFile`: the candidate file is
|
|
12830
|
+
* the caller's, patched in place and read back through a transient
|
|
12831
|
+
* session that is never stored in `this.sessions`.
|
|
12832
|
+
*/
|
|
12833
|
+
handleSignaturesFinalizeCandidate(req) {
|
|
12834
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12835
|
+
tag: "signatures.finalizeCandidate",
|
|
12836
|
+
...new CandidateFinalizer(this.runtime, this.baseDocuments).finalize({
|
|
12837
|
+
path: req.path,
|
|
12838
|
+
byteRange: req.byteRange,
|
|
12839
|
+
contentsSize: req.contentsSize,
|
|
12840
|
+
fieldObjectNumber: req.fieldObjectNumber,
|
|
12841
|
+
cms: new Uint8Array(req.cms),
|
|
12842
|
+
password: req.password ?? null
|
|
12843
|
+
})
|
|
12844
|
+
});
|
|
12845
|
+
}
|
|
12846
|
+
handleSignaturesAbort(req) {
|
|
12847
|
+
const session = this.requireSession(req);
|
|
12848
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
12849
|
+
tag: "signatures.abort",
|
|
12850
|
+
result: new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).abort(req.signingId)
|
|
10020
12851
|
});
|
|
10021
12852
|
}
|
|
12853
|
+
/**
|
|
12854
|
+
* The dispatch fence for a parked signing candidate. Only mutating kinds
|
|
12855
|
+
* are refused; a request for a session that is not open falls through
|
|
12856
|
+
* to its handler's own `DocNotOpen`.
|
|
12857
|
+
*/
|
|
12858
|
+
assertNoPendingSigning(msg) {
|
|
12859
|
+
if (!MUTATING_KINDS.has(msg.kind) || !("docId" in msg)) return;
|
|
12860
|
+
const layerName = "layerName" in msg ? msg.layerName : void 0;
|
|
12861
|
+
const session = this.sessions.get(sessionKey(msg.docId, layerName));
|
|
12862
|
+
if (session?.pendingSigning) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SigningPending, `a signing is pending (${session.pendingSigning.prepared.signingId}); complete or abort it before mutating the document`);
|
|
12863
|
+
}
|
|
10022
12864
|
handleMetadataRead(req, signal) {
|
|
10023
12865
|
const session = this.requireSession(req);
|
|
10024
12866
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
@@ -10045,21 +12887,21 @@ var WorkerHost = class {
|
|
|
10045
12887
|
const session = this.requireSession(req);
|
|
10046
12888
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10047
12889
|
tag: "annotations.listRawAll",
|
|
10048
|
-
snapshot: new RawAnnotationReader(this.runtime, session).listAll(signal)
|
|
12890
|
+
snapshot: new RawAnnotationReader(this.runtime, session, this.fonts).listAll(signal)
|
|
10049
12891
|
});
|
|
10050
12892
|
}
|
|
10051
12893
|
handleAnnotationsListRawPage(req, signal) {
|
|
10052
12894
|
const session = this.requireSession(req);
|
|
10053
12895
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10054
12896
|
tag: "annotations.listRawPage",
|
|
10055
|
-
snapshot: new RawAnnotationReader(this.runtime, session).listOne(req.pageObjectNumber, signal)
|
|
12897
|
+
snapshot: new RawAnnotationReader(this.runtime, session, this.fonts).listOne(req.pageObjectNumber, signal)
|
|
10056
12898
|
});
|
|
10057
12899
|
}
|
|
10058
12900
|
handleAnnotationsListFullPage(req, signal) {
|
|
10059
12901
|
const session = this.requireSession(req);
|
|
10060
12902
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10061
12903
|
tag: "annotations.listFullPage",
|
|
10062
|
-
snapshot: new AnnotationReader(this.runtime, session).list(req.pageObjectNumber, signal)
|
|
12904
|
+
snapshot: new AnnotationReader(this.runtime, session, this.fonts).list(req.pageObjectNumber, signal)
|
|
10063
12905
|
});
|
|
10064
12906
|
}
|
|
10065
12907
|
handleAnnotationsRenderAppearances(req, signal) {
|
|
@@ -10420,6 +13262,14 @@ var WorkerHost = class {
|
|
|
10420
13262
|
}
|
|
10421
13263
|
handleDocumentSaveBuffer(req) {
|
|
10422
13264
|
const session = this.requireSession(req);
|
|
13265
|
+
if (req.mode === "incremental") {
|
|
13266
|
+
const snap = new DocumentSaver(this.runtime, session).snapshot();
|
|
13267
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
13268
|
+
tag: "document.saveBuffer",
|
|
13269
|
+
bytes: snap.bytes,
|
|
13270
|
+
size: snap.bytes.byteLength
|
|
13271
|
+
}, [snap.bytes]);
|
|
13272
|
+
}
|
|
10423
13273
|
const saved = new DocumentSaver(this.runtime, session).saveStandaloneToBuffer(req.mode);
|
|
10424
13274
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10425
13275
|
tag: "document.saveBuffer",
|
|
@@ -10429,6 +13279,17 @@ var WorkerHost = class {
|
|
|
10429
13279
|
}
|
|
10430
13280
|
handleDocumentSaveFile(req) {
|
|
10431
13281
|
const session = this.requireSession(req);
|
|
13282
|
+
if (req.mode === "incremental" && (!session.hasUnsavedEdits() || new DocumentSaver(this.runtime, session).saveStandaloneToFileEx(req.path, req.mode).unchangedSinceLoad)) {
|
|
13283
|
+
new DocumentSaver(this.runtime, session).copyLoadedBytesToFile(req.path);
|
|
13284
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
13285
|
+
tag: "document.saveFile",
|
|
13286
|
+
path: req.path
|
|
13287
|
+
});
|
|
13288
|
+
}
|
|
13289
|
+
if (req.mode === "incremental") return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
13290
|
+
tag: "document.saveFile",
|
|
13291
|
+
path: req.path
|
|
13292
|
+
});
|
|
10432
13293
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10433
13294
|
tag: "document.saveFile",
|
|
10434
13295
|
path: new DocumentSaver(this.runtime, session).saveStandaloneToFile(req.path, req.mode).path
|
|
@@ -10485,21 +13346,47 @@ var WorkerHost = class {
|
|
|
10485
13346
|
const parked = this.sessions.get(sessionKey(req.docId));
|
|
10486
13347
|
let session;
|
|
10487
13348
|
if (parked?.isLocked()) {
|
|
10488
|
-
|
|
13349
|
+
this.openSignedAware(parked, parked.lockedBytes(), req.password);
|
|
13350
|
+
parked.password = req.password;
|
|
10489
13351
|
session = parked;
|
|
10490
13352
|
} else session = this.requireSession(req);
|
|
10491
13353
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10492
13354
|
tag: "document.checkPasswordPermissions",
|
|
10493
|
-
security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password, req.mode ?? "any")
|
|
13355
|
+
security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password, req.mode ?? "any"),
|
|
13356
|
+
protection: this.probeProtection(session)
|
|
10494
13357
|
});
|
|
10495
13358
|
}
|
|
10496
13359
|
handleFontsRegister(req) {
|
|
10497
13360
|
this.fonts.register(req.fontKey, req.familyName, req.weight, req.italic, new Uint8Array(req.data));
|
|
10498
13361
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10499
13362
|
tag: "fonts.register",
|
|
10500
|
-
fontKey: req.fontKey
|
|
13363
|
+
fontKey: req.fontKey,
|
|
13364
|
+
identity: this.fonts.describe(req.fontKey)
|
|
13365
|
+
});
|
|
13366
|
+
}
|
|
13367
|
+
handleFontsAuthorizeEditing(req) {
|
|
13368
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
13369
|
+
tag: "fonts.authorizeEditing",
|
|
13370
|
+
identity: this.fonts.authorizeEditing(req.fontKey)
|
|
10501
13371
|
});
|
|
10502
13372
|
}
|
|
13373
|
+
/** Per-document font and text-layout settings (session state). */
|
|
13374
|
+
handleDocumentSetFontSettings(req) {
|
|
13375
|
+
const docPtr = this.requireSession(req).requireDocPtr();
|
|
13376
|
+
const { fn } = this.runtime;
|
|
13377
|
+
if (req.embeddingPolicy !== void 0) {
|
|
13378
|
+
const code = {
|
|
13379
|
+
default: 0,
|
|
13380
|
+
subset: 1,
|
|
13381
|
+
full: 2
|
|
13382
|
+
}[req.embeddingPolicy];
|
|
13383
|
+
if (!fn.EPDFDoc_SetFontEmbeddingPolicy(docPtr, code)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "EPDFDoc_SetFontEmbeddingPolicy failed");
|
|
13384
|
+
}
|
|
13385
|
+
if (req.typographicFeatures !== void 0) {
|
|
13386
|
+
if (!fn.EPDFDoc_SetTypographicFeatures(docPtr, req.typographicFeatures)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "EPDFDoc_SetTypographicFeatures failed");
|
|
13387
|
+
}
|
|
13388
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({ tag: "document.setFontSettings" });
|
|
13389
|
+
}
|
|
10503
13390
|
handleFontsAddFallback(req) {
|
|
10504
13391
|
this.fonts.addFallback(req.fontKey);
|
|
10505
13392
|
return (0, _embedpdf_engine_core_runtime.wirePack)({ tag: "fonts.addFallback" });
|
|
@@ -10525,6 +13412,7 @@ var WorkerHost = class {
|
|
|
10525
13412
|
const session = this.sessions.get(key);
|
|
10526
13413
|
if (session) {
|
|
10527
13414
|
disposeFormModel(this.runtime, session);
|
|
13415
|
+
disposeSignatureModel(this.runtime, session);
|
|
10528
13416
|
session.close();
|
|
10529
13417
|
this.sessions.delete(key);
|
|
10530
13418
|
}
|
|
@@ -10534,6 +13422,7 @@ var WorkerHost = class {
|
|
|
10534
13422
|
for (const [key, session] of Array.from(this.sessions.entries())) {
|
|
10535
13423
|
if (!sessionKeyBelongsToDoc(key, req.docId)) continue;
|
|
10536
13424
|
disposeFormModel(this.runtime, session);
|
|
13425
|
+
disposeSignatureModel(this.runtime, session);
|
|
10537
13426
|
session.close();
|
|
10538
13427
|
this.sessions.delete(key);
|
|
10539
13428
|
}
|
|
@@ -10544,6 +13433,7 @@ var WorkerHost = class {
|
|
|
10544
13433
|
this.destroyed = true;
|
|
10545
13434
|
for (const session of this.sessions.values()) {
|
|
10546
13435
|
disposeFormModel(this.runtime, session);
|
|
13436
|
+
disposeSignatureModel(this.runtime, session);
|
|
10547
13437
|
session.close();
|
|
10548
13438
|
}
|
|
10549
13439
|
this.sessions.clear();
|
|
@@ -10649,6 +13539,21 @@ var WorkerHost = class {
|
|
|
10649
13539
|
}
|
|
10650
13540
|
}, req.artifactPath);
|
|
10651
13541
|
}
|
|
13542
|
+
handleFormsSetSignatureAppearance(req, signal) {
|
|
13543
|
+
const session = this.requireSession(req);
|
|
13544
|
+
const { field, pages } = new FormMutator(this.runtime, session).setSignatureAppearance(req.ref, new Uint8Array(req.pdf), req.pageIndex, signal);
|
|
13545
|
+
const meta = {
|
|
13546
|
+
affectedPages: pages.map((pon) => session.pageState(pon)),
|
|
13547
|
+
cacheDelta: null
|
|
13548
|
+
};
|
|
13549
|
+
return this.finishMutation(session, {
|
|
13550
|
+
tag: "forms.setSignatureAppearance",
|
|
13551
|
+
result: {
|
|
13552
|
+
field,
|
|
13553
|
+
meta
|
|
13554
|
+
}
|
|
13555
|
+
}, req.artifactPath);
|
|
13556
|
+
}
|
|
10652
13557
|
handleFormsDeleteField(req, signal) {
|
|
10653
13558
|
const session = this.requireSession(req);
|
|
10654
13559
|
const { deletedFieldObjectNumber, detachedWidgets } = new FormMutator(this.runtime, session).deleteField(req.ref, signal);
|
|
@@ -10694,8 +13599,8 @@ var WorkerHost = class {
|
|
|
10694
13599
|
}, req.artifactPath);
|
|
10695
13600
|
}
|
|
10696
13601
|
finishMutation(session, payload, artifactPath) {
|
|
10697
|
-
if (!payload.tag.startsWith("forms.") && payload.tag !== "pages.flatten" && payload.tag !== "redaction.apply") session.noteMutation();
|
|
10698
|
-
if (session.kind !== "layer") return (0, _embedpdf_engine_core_runtime.wirePack)(payload);
|
|
13602
|
+
if (!payload.tag.startsWith("forms.") && payload.tag !== "pages.flatten" && payload.tag !== "redaction.apply" && payload.tag !== "signatures.complete") session.noteMutation();
|
|
13603
|
+
if (session.kind !== "layer" || !session.persistLayerArtifact) return (0, _embedpdf_engine_core_runtime.wirePack)(payload);
|
|
10699
13604
|
const saved = this.saveLayerArtifact(session, artifactPath);
|
|
10700
13605
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10701
13606
|
...payload,
|
|
@@ -10721,6 +13626,39 @@ const EMPTY_FORM_META = {
|
|
|
10721
13626
|
cacheDelta: null
|
|
10722
13627
|
};
|
|
10723
13628
|
const BASE_SESSION_SUFFIX = "__base__";
|
|
13629
|
+
/** Every request kind that writes to a session's document. */
|
|
13630
|
+
const MUTATING_KINDS = /* @__PURE__ */ new Set([
|
|
13631
|
+
"metadata.update",
|
|
13632
|
+
"annotations.create",
|
|
13633
|
+
"annotations.update",
|
|
13634
|
+
"annotations.delete",
|
|
13635
|
+
"annotations.move",
|
|
13636
|
+
"annotations.flatten",
|
|
13637
|
+
"forms.setValue",
|
|
13638
|
+
"forms.reset",
|
|
13639
|
+
"forms.applyEffects",
|
|
13640
|
+
"forms.import",
|
|
13641
|
+
"forms.repair",
|
|
13642
|
+
"forms.createField",
|
|
13643
|
+
"forms.updateField",
|
|
13644
|
+
"forms.setSignatureAppearance",
|
|
13645
|
+
"forms.deleteField",
|
|
13646
|
+
"forms.attachWidget",
|
|
13647
|
+
"forms.detachWidget",
|
|
13648
|
+
"pages.move",
|
|
13649
|
+
"pages.rotate",
|
|
13650
|
+
"pages.delete",
|
|
13651
|
+
"pages.setName",
|
|
13652
|
+
"pages.removeName",
|
|
13653
|
+
"pages.flatten",
|
|
13654
|
+
"pages.insert",
|
|
13655
|
+
"pages.insertBlank",
|
|
13656
|
+
"redaction.apply",
|
|
13657
|
+
"attachments.create",
|
|
13658
|
+
"attachments.delete",
|
|
13659
|
+
"pieceInfo.update",
|
|
13660
|
+
"pieceInfo.clear"
|
|
13661
|
+
]);
|
|
10724
13662
|
function sessionKey(docId, layerName) {
|
|
10725
13663
|
return `${docId}::${layerName ? `layer:${layerName}` : BASE_SESSION_SUFFIX}`;
|
|
10726
13664
|
}
|
|
@@ -10758,7 +13696,9 @@ exports.AnnotationReader = AnnotationReader;
|
|
|
10758
13696
|
exports.AttachmentMutator = AttachmentMutator;
|
|
10759
13697
|
exports.AttachmentReader = AttachmentReader;
|
|
10760
13698
|
exports.BaseDocumentRegistry = BaseDocumentRegistry;
|
|
13699
|
+
exports.CandidateFinalizer = CandidateFinalizer;
|
|
10761
13700
|
exports.CloseStack = CloseStack;
|
|
13701
|
+
exports.DIGEST_CODE = DIGEST_CODE;
|
|
10762
13702
|
exports.DocumentActionsReader = DocumentActionsReader;
|
|
10763
13703
|
exports.DocumentPagesMutator = PagesMutator;
|
|
10764
13704
|
exports.DocumentSaver = DocumentSaver;
|
|
@@ -10788,21 +13728,30 @@ exports.RedactionApplier = RedactionApplier;
|
|
|
10788
13728
|
exports.SearchReader = SearchReader;
|
|
10789
13729
|
exports.SecurityReader = SecurityReader;
|
|
10790
13730
|
exports.SessionEventPublisher = SessionEventPublisher;
|
|
13731
|
+
exports.SignatureAnalyzer = SignatureAnalyzer;
|
|
13732
|
+
exports.SignatureMutator = SignatureMutator;
|
|
13733
|
+
exports.SignatureReader = SignatureReader;
|
|
10791
13734
|
exports.WorkerHost = WorkerHost;
|
|
13735
|
+
exports.acquireSignatureModel = acquireSignatureModel;
|
|
10792
13736
|
exports.buildRunsFromRawGlyphs = buildRunsFromRawGlyphs;
|
|
10793
13737
|
exports.computeMutationImpact = computeMutationImpact;
|
|
10794
13738
|
exports.destroyLibrary = destroyLibrary;
|
|
10795
13739
|
exports.disposeFormModel = disposeFormModel;
|
|
13740
|
+
exports.disposeSignatureModel = disposeSignatureModel;
|
|
10796
13741
|
exports.ensureInitialized = ensureInitialized;
|
|
10797
13742
|
exports.formatPdfDate = formatPdfDate;
|
|
10798
13743
|
exports.generateUuid = generateUuid;
|
|
10799
13744
|
exports.hasAllStandardSecurityPermissions = hasAllStandardSecurityPermissions;
|
|
13745
|
+
exports.hasSignedSignature = hasSignedSignature;
|
|
10800
13746
|
exports.normalizeU32 = normalizeU32;
|
|
10801
13747
|
exports.openFatMemoryDocument = openFatMemoryDocument;
|
|
10802
13748
|
exports.openLayerDocument = openLayerDocument;
|
|
10803
13749
|
exports.pdfDateToIso = pdfDateToIso;
|
|
10804
13750
|
exports.readActionModel = readActionModel;
|
|
10805
13751
|
exports.readAttachmentFileInfo = readAttachmentFileInfo;
|
|
13752
|
+
exports.readRevisions = readRevisions;
|
|
13753
|
+
exports.readSignaturesFromModel = readSignaturesFromModel;
|
|
13754
|
+
exports.readStructure = readStructure;
|
|
10806
13755
|
exports.throwIfAborted = throwIfAborted;
|
|
10807
13756
|
exports.writeAttachmentFilePayload = writeAttachmentFilePayload;
|
|
10808
13757
|
|