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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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$1 = 4;
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$1) 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");
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 new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to open base document");
1015
+ if (!basePtr) throw baseOpenError(this.runtime, opts.password);
758
1016
  setRuntimeOwnerPermissionsIfEncrypted(this.runtime, basePtr);
759
1017
  stack.push(() => fn.EPDF_ReleaseBaseDocument(basePtr));
760
- return this.insert(opts.key, basePtr, () => stack.close());
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 new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to open base document");
1041
+ if (!basePtr) throw baseOpenError(this.runtime, opts.password);
777
1042
  setRuntimeOwnerPermissionsIfEncrypted(this.runtime, basePtr);
778
1043
  stack.push(() => fn.EPDF_ReleaseBaseDocument(basePtr));
779
- return this.insert(opts.key, basePtr, () => stack.close());
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(key, basePtr, close) {
807
- const entry = {
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 { docPtr: session.requireDocPtr() };
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/textAlignment.ts
3018
- /**
3019
- * `/Q` quadding codes (ISO 32000 §12.7.3.3) mapped to the wire-stable
3020
- * `TextAlignment` strings. Kept in engine-services so engine-core stays
3021
- * PDFium-free (mirrors `lineEnding.ts`).
3022
- *
3023
- * Left=0, Center=1, Right=2
3024
- */
3025
- const Q_LEFT = 0;
3026
- const Q_CENTER = 1;
3027
- const Q_RIGHT = 2;
3028
- function textAlignmentToCode(align) {
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 fontFamily = da ? standardFontFromCode(da.fontCode) : DEFAULT_STANDARD_FONT;
3147
- const fontSize = da && da.fontSize > 0 ? da.fontSize : DEFAULT_FONT_SIZE$2;
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 textAlign = textAlignmentFromCode(readTextAlignment(fn, annotPtr));
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,
@@ -3209,6 +3644,181 @@ function readInk(fn, mem, annotPtr, base) {
3209
3644
  };
3210
3645
  }
3211
3646
  //#endregion
3647
+ //#region src/features/measure/internal/measureCodec.ts
3648
+ const MEASURE_AXES = {
3649
+ x: 0,
3650
+ y: 1,
3651
+ distance: 2,
3652
+ area: 3,
3653
+ angle: 4,
3654
+ slope: 5
3655
+ };
3656
+ const FRACTIONS = [
3657
+ "decimal",
3658
+ "fraction",
3659
+ "round",
3660
+ "truncate"
3661
+ ];
3662
+ const TEXT = [
3663
+ "thousands",
3664
+ "decimal",
3665
+ "prefixSpacing",
3666
+ "suffixSpacing"
3667
+ ];
3668
+ /** A native rejection after validated input is an internal invariant, never caller validation. */
3669
+ function requireMeasureWrite(ok) {
3670
+ if (!ok) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "Measurement native write failed after preflight");
3671
+ }
3672
+ function readMeasureNumber(mem, call, type = "f32") {
3673
+ return withScratch(mem, 4, (p) => call(p) ? Number(mem.peek(p, type)) : void 0);
3674
+ }
3675
+ function withMeasurePoint(mem, point, call) {
3676
+ if (!point) return call(_embedpdf_engine_runtime.NULL_PTR);
3677
+ return withScratch(mem, 8, (p) => {
3678
+ mem.poke(p, "f32", point.x);
3679
+ mem.poke(p, "f32", point.y, 4);
3680
+ return call(p);
3681
+ });
3682
+ }
3683
+ function readMeasure(fn, mem, handle) {
3684
+ if (!handle) return void 0;
3685
+ const subtype = fn.EPDFMeasure_GetSubtype(handle);
3686
+ if (subtype !== 1) return { subtype: subtype === 2 ? "GEO" : "unknown" };
3687
+ const formats = (axis) => Array.from({ length: fn.EPDFMeasure_CountFormats(handle, axis) }, (_, i) => {
3688
+ const f = fn.EPDFMeasure_GetFormat(handle, axis, i);
3689
+ const out = { unit: readUtf16String(mem, (b, n) => fn.EPDFNumberFormat_GetUnit(f, b, n)) ?? "" };
3690
+ const conversion = readMeasureNumber(mem, (p) => fn.EPDFNumberFormat_GetConversion(f, p));
3691
+ if (conversion !== void 0) out.conversion = conversion;
3692
+ const fraction = readMeasureNumber(mem, (p) => fn.EPDFNumberFormat_GetFraction(f, p), "i32");
3693
+ if (fraction !== void 0 && FRACTIONS[fraction]) out.fraction = FRACTIONS[fraction];
3694
+ const precision = readMeasureNumber(mem, (p) => fn.EPDFNumberFormat_GetPrecision(f, p), "i32");
3695
+ if (precision !== void 0) out.precision = precision;
3696
+ const fixed = readMeasureNumber(mem, (p) => fn.EPDFNumberFormat_GetFixedDenominator(f, p), "i32");
3697
+ if (fixed !== void 0) out.fixed = !!fixed;
3698
+ const position = readMeasureNumber(mem, (p) => fn.EPDFNumberFormat_GetLabelPosition(f, p), "i32");
3699
+ if (position !== void 0) out.labelPosition = position === 1 ? "prefix" : "suffix";
3700
+ for (const [code, key] of TEXT.entries()) {
3701
+ const text = readUtf16String(mem, (b, n) => fn.EPDFNumberFormat_GetText(f, code, b, n));
3702
+ if (text !== null) out[key] = text;
3703
+ }
3704
+ return out;
3705
+ });
3706
+ const ratio = readUtf16String(mem, (b, n) => fn.EPDFMeasure_GetRatio(handle, b, n));
3707
+ const origin = withScratch(mem, 8, (p) => fn.EPDFMeasure_GetOrigin(handle, p) ? {
3708
+ x: Number(mem.peek(p, "f32")),
3709
+ y: Number(mem.peek(p, "f32", 4))
3710
+ } : void 0);
3711
+ const cyx = readMeasureNumber(mem, (p) => fn.EPDFMeasure_GetCYX(handle, p));
3712
+ const result = {
3713
+ subtype: "RL",
3714
+ x: formats(0),
3715
+ distance: formats(2),
3716
+ area: formats(3),
3717
+ ...ratio !== null ? { ratio } : {},
3718
+ ...origin ? { origin } : {},
3719
+ ...cyx !== void 0 ? { cyx } : {}
3720
+ };
3721
+ for (const key of [
3722
+ "y",
3723
+ "angle",
3724
+ "slope"
3725
+ ]) {
3726
+ const list = formats(MEASURE_AXES[key]);
3727
+ if (list.length) result[key] = list;
3728
+ }
3729
+ return result;
3730
+ }
3731
+ /** The caller supplies a freshly created/reset handle and has completed preflight. */
3732
+ function writeMeasure(fn, mem, handle, value) {
3733
+ requireMeasureWrite(handle);
3734
+ if (value.ratio !== void 0) requireMeasureWrite(writeUtf16String(mem, value.ratio, (p) => fn.EPDFMeasure_SetRatio(handle, p)));
3735
+ if (value.origin) requireMeasureWrite(withMeasurePoint(mem, value.origin, (p) => fn.EPDFMeasure_SetOrigin(handle, p)));
3736
+ if (value.cyx !== void 0) withScratch(mem, 4, (p) => {
3737
+ mem.poke(p, "f32", value.cyx);
3738
+ requireMeasureWrite(fn.EPDFMeasure_SetCYX(handle, p));
3739
+ });
3740
+ for (const [key, axis] of Object.entries(MEASURE_AXES)) for (const nf of value[key] ?? []) {
3741
+ const text = mem.writeU16String(nf.unit);
3742
+ let f;
3743
+ try {
3744
+ f = fn.EPDFMeasure_AddFormat(handle, axis, text, nf.conversion);
3745
+ } finally {
3746
+ mem.free(text);
3747
+ }
3748
+ requireMeasureWrite(f);
3749
+ if (nf.fraction !== void 0) requireMeasureWrite(fn.EPDFNumberFormat_SetFraction(f, FRACTIONS.indexOf(nf.fraction)));
3750
+ if (nf.precision !== void 0) requireMeasureWrite(fn.EPDFNumberFormat_SetPrecision(f, nf.precision));
3751
+ if (nf.fixed !== void 0) requireMeasureWrite(fn.EPDFNumberFormat_SetFixedDenominator(f, nf.fixed));
3752
+ if (nf.labelPosition !== void 0) requireMeasureWrite(fn.EPDFNumberFormat_SetLabelPosition(f, nf.labelPosition === "prefix" ? 1 : 0));
3753
+ for (const [code, key] of TEXT.entries()) if (nf[key] !== void 0) requireMeasureWrite(writeUtf16String(mem, nf[key], (p) => fn.EPDFNumberFormat_SetText(f, code, p)));
3754
+ }
3755
+ }
3756
+ //#endregion
3757
+ //#region src/features/annotations/internal/read/readMeasurementFields.ts
3758
+ function readAnnotationMeasure(fn, mem, annot) {
3759
+ const measure = readMeasure(fn, mem, fn.EPDFAnnot_GetMeasure(annot));
3760
+ return measure ? { measure } : {};
3761
+ }
3762
+ function readLineCaption(fn, mem, annot) {
3763
+ if (![
3764
+ "Cap",
3765
+ "CP",
3766
+ "CO"
3767
+ ].some((key) => fn.FPDFAnnot_HasKey(annot, key))) return void 0;
3768
+ return withScratchN(mem, [
3769
+ 4,
3770
+ 4,
3771
+ 8
3772
+ ], ([enabled, position, offset]) => {
3773
+ if (!fn.EPDFAnnot_GetLineCaption(annot, enabled, position, offset)) return void 0;
3774
+ return {
3775
+ enabled: !!mem.peek(enabled, "i32"),
3776
+ position: mem.peek(position, "i32") === 1 ? "top" : "inline",
3777
+ ...fn.FPDFAnnot_HasKey(annot, "CO") ? { offset: {
3778
+ along: Number(mem.peek(offset, "f32")),
3779
+ perpendicular: Number(mem.peek(offset, "f32", 4))
3780
+ } } : {}
3781
+ };
3782
+ });
3783
+ }
3784
+ function readShapeCaption(fn, mem, annot) {
3785
+ return withScratchN(mem, [
3786
+ 4,
3787
+ 4,
3788
+ 8
3789
+ ], ([enabled, hasCenter, center]) => {
3790
+ if (!fn.EPDFAnnot_GetShapeCaption(annot, enabled, hasCenter, center)) return void 0;
3791
+ const value = !!mem.peek(enabled, "i32"), manual = !!mem.peek(hasCenter, "i32");
3792
+ if (!fn.EPDFAnnot_GetEmbedMetadataBoolean(annot, "MeasurementCaption", enabled) && !manual) return void 0;
3793
+ return {
3794
+ enabled: value,
3795
+ ...manual ? { center: {
3796
+ x: Number(mem.peek(center, "f32")),
3797
+ y: Number(mem.peek(center, "f32", 4))
3798
+ } } : {}
3799
+ };
3800
+ });
3801
+ }
3802
+ function readLineLeader(fn, mem, annot) {
3803
+ if (![
3804
+ "LL",
3805
+ "LLE",
3806
+ "LLO"
3807
+ ].some((key) => fn.FPDFAnnot_HasKey(annot, key))) return void 0;
3808
+ return withScratchN(mem, [
3809
+ 4,
3810
+ 4,
3811
+ 4
3812
+ ], ([length, extension, offset]) => {
3813
+ if (!fn.EPDFAnnot_GetLineLeader(annot, length, extension, offset)) return void 0;
3814
+ return {
3815
+ length: Number(mem.peek(length, "f32")),
3816
+ extension: Number(mem.peek(extension, "f32")),
3817
+ offset: Number(mem.peek(offset, "f32"))
3818
+ };
3819
+ });
3820
+ }
3821
+ //#endregion
3212
3822
  //#region src/features/annotations/internal/read/readLineAnnotation.ts
3213
3823
  /** Fallback `/L` when the annotation has no line geometry. */
3214
3824
  const ZERO_LINE = {
@@ -3223,8 +3833,15 @@ const ZERO_LINE = {
3223
3833
  };
3224
3834
  function readLine(fn, mem, annotPtr, base) {
3225
3835
  const rotation = readAnnotationRotation(fn, mem, annotPtr);
3836
+ const intent = readIntent(fn, mem, annotPtr);
3837
+ const caption = readLineCaption(fn, mem, annotPtr);
3838
+ const leader = readLineLeader(fn, mem, annotPtr);
3226
3839
  return {
3227
3840
  ...base,
3841
+ ...readAnnotationMeasure(fn, mem, annotPtr),
3842
+ ...intent === "LineDimension" || intent === "LineArrow" ? { intent } : {},
3843
+ ...caption ? { caption } : {},
3844
+ ...leader ? { leader } : {},
3228
3845
  subtype: "line",
3229
3846
  ...readFilledStyleExtras(fn, mem, annotPtr),
3230
3847
  linePoints: readLine$1(fn, mem, annotPtr) ?? ZERO_LINE,
@@ -3563,16 +4180,26 @@ function readVertexExtras(fn, mem, annotPtr) {
3563
4180
  };
3564
4181
  }
3565
4182
  function readPolygon(fn, mem, annotPtr, base) {
4183
+ const caption = readShapeCaption(fn, mem, annotPtr);
4184
+ const intent = readIntent(fn, mem, annotPtr);
3566
4185
  return {
3567
4186
  ...base,
4187
+ ...readAnnotationMeasure(fn, mem, annotPtr),
4188
+ ...caption ? { caption } : {},
4189
+ ...intent === "PolygonDimension" || intent === "PolygonCloud" ? { intent } : {},
3568
4190
  subtype: "polygon",
3569
4191
  ...readVertexExtras(fn, mem, annotPtr),
3570
4192
  cloudyIntensity: readBorderEffect(fn, mem, annotPtr)
3571
4193
  };
3572
4194
  }
3573
4195
  function readPolyline(fn, mem, annotPtr, base) {
4196
+ const caption = readShapeCaption(fn, mem, annotPtr);
4197
+ const intent = readIntent(fn, mem, annotPtr);
3574
4198
  return {
3575
4199
  ...base,
4200
+ ...readAnnotationMeasure(fn, mem, annotPtr),
4201
+ ...caption ? { caption } : {},
4202
+ ...intent === "PolyLineDimension" ? { intent } : {},
3576
4203
  subtype: "polyline",
3577
4204
  ...readVertexExtras(fn, mem, annotPtr),
3578
4205
  lineEndings: readLineEndings(fn, mem, annotPtr)
@@ -3681,18 +4308,18 @@ function pickReader(rawSubtypeCode) {
3681
4308
  * per session, closed before replacement and on session teardown via
3682
4309
  * {@link disposeFormModel}.
3683
4310
  */
3684
- const cache$1 = /* @__PURE__ */ new WeakMap();
4311
+ const cache$2 = /* @__PURE__ */ new WeakMap();
3685
4312
  function acquireFormModel(runtime, session) {
3686
4313
  const seq = session.mutationSeq();
3687
- const hit = cache$1.get(session);
4314
+ const hit = cache$2.get(session);
3688
4315
  if (hit && hit.seq === seq) return hit.ptr;
3689
4316
  if (hit) {
3690
4317
  runtime.fn.EPDFForm_CloseModel(hit.ptr);
3691
- cache$1.delete(session);
4318
+ cache$2.delete(session);
3692
4319
  }
3693
4320
  const ptr = runtime.fn.EPDFForm_LoadModel(session.requireDocPtr());
3694
4321
  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$1.set(session, {
4322
+ cache$2.set(session, {
3696
4323
  seq,
3697
4324
  ptr
3698
4325
  });
@@ -3700,10 +4327,10 @@ function acquireFormModel(runtime, session) {
3700
4327
  }
3701
4328
  /** Close and forget the session's cached model, if any. */
3702
4329
  function disposeFormModel(runtime, session) {
3703
- const hit = cache$1.get(session);
4330
+ const hit = cache$2.get(session);
3704
4331
  if (!hit) return;
3705
4332
  runtime.fn.EPDFForm_CloseModel(hit.ptr);
3706
- cache$1.delete(session);
4333
+ cache$2.delete(session);
3707
4334
  }
3708
4335
  //#endregion
3709
4336
  //#region src/features/forms/internal/readFormSnapshot.ts
@@ -3732,7 +4359,7 @@ const FF_EDIT = 1 << 18;
3732
4359
  const FF_MULTI_SELECT = 1 << 21;
3733
4360
  const FF_COMB = 1 << 24;
3734
4361
  const FF_RADIOS_IN_UNISON = 1 << 25;
3735
- function readWide(runtime, call) {
4362
+ function readWide$1(runtime, call) {
3736
4363
  return readUtf16String(runtime.mem, call) ?? "";
3737
4364
  }
3738
4365
  function readWideOrNull(runtime, call) {
@@ -3746,7 +4373,7 @@ function readToggleWidgets(runtime, model, fieldIndex) {
3746
4373
  annotObjectNumber: fn.EPDFForm_GetFieldWidgetObjNum(model, fieldIndex, w),
3747
4374
  pageObjectNumber: fn.EPDFForm_GetFieldWidgetPageObjNum(model, fieldIndex, w),
3748
4375
  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)),
4376
+ exportValue: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldWidgetExportValue(model, fieldIndex, w, buf, cap)),
3750
4377
  checked: fn.EPDFForm_IsFieldWidgetChecked(model, fieldIndex, w)
3751
4378
  });
3752
4379
  return widgets;
@@ -3766,8 +4393,8 @@ function readOptions(runtime, model, fieldIndex) {
3766
4393
  const count = fn.EPDFForm_CountFieldOptions(model, fieldIndex);
3767
4394
  const options = [];
3768
4395
  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)),
4396
+ label: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionLabel(model, fieldIndex, o, buf, cap)),
4397
+ value: readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldOptionValue(model, fieldIndex, o, buf, cap)),
3771
4398
  selected: fn.EPDFForm_IsFieldOptionSelected(model, fieldIndex, o)
3772
4399
  });
3773
4400
  return options;
@@ -3776,7 +4403,7 @@ function readOptions(runtime, model, fieldIndex) {
3776
4403
  function readFieldAt(runtime, model, fieldIndex, docPtr, actionBudget = new ActionReadBudgetTracker()) {
3777
4404
  const { fn } = runtime;
3778
4405
  const fieldObjectNumber = fn.EPDFForm_GetFieldObjNum(model, fieldIndex);
3779
- const name = readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
4406
+ const name = readWide$1(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
3780
4407
  const rawFlags = fn.EPDFForm_GetFieldFlags(model, fieldIndex);
3781
4408
  const family = FAMILY_BY_CODE[fn.EPDFForm_GetFieldFamily(model, fieldIndex)] ?? "unknown";
3782
4409
  const valueEntry = readValueEntry(runtime, model, fieldIndex, false);
@@ -3907,7 +4534,7 @@ function readValueEntry(runtime, model, fieldIndex, isDefault) {
3907
4534
  if (kind !== 1 && kind !== 2) return { kind: "unsupported" };
3908
4535
  const count = isDefault ? fn.EPDFForm_CountFieldDefaultValues(model, fieldIndex) : fn.EPDFForm_CountFieldValues(model, fieldIndex);
3909
4536
  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)));
4537
+ 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
4538
  return kind === 1 ? {
3912
4539
  kind: "scalar",
3913
4540
  value: values[0] ?? ""
@@ -4248,13 +4875,13 @@ function readAnnotationActions(fn, mem, docPtr, annotPtr, budget) {
4248
4875
  * releasing any enclosing `pagePtr`.
4249
4876
  */
4250
4877
  function collectPageAnnotations(input) {
4251
- const { runtime, session, pageObjectNumber, count, getAnnotPtrAt, signal } = input;
4878
+ const { runtime, session, pageObjectNumber, count, getAnnotPtrAt, signal, fonts } = input;
4252
4879
  const { fn, mem } = runtime;
4253
4880
  const annotations = [];
4254
4881
  let hasWeak = false;
4255
4882
  const revision = session.pageState(pageObjectNumber).revision;
4256
4883
  const actionBudget = new ActionReadBudgetTracker();
4257
- const readCtx = readContextFor(session);
4884
+ const readCtx = readContextFor(session, fonts);
4258
4885
  for (let i = 0; i < count; i++) {
4259
4886
  throwIfAborted(signal);
4260
4887
  const annotPtr = getAnnotPtrAt(i);
@@ -4290,9 +4917,10 @@ function collectPageAnnotations(input) {
4290
4917
  * as they land - the public DTO surface stays the same.
4291
4918
  */
4292
4919
  var AnnotationReader = class {
4293
- constructor(runtime, session) {
4920
+ constructor(runtime, session, fonts) {
4294
4921
  this.runtime = runtime;
4295
4922
  this.session = session;
4923
+ this.fonts = fonts;
4296
4924
  }
4297
4925
  list(pageObjectNumber, signal) {
4298
4926
  throwIfAborted(signal);
@@ -4307,7 +4935,8 @@ var AnnotationReader = class {
4307
4935
  pageObjectNumber,
4308
4936
  count,
4309
4937
  getAnnotPtrAt: (i) => fn.FPDFPage_GetAnnot(pagePtr, i),
4310
- signal
4938
+ signal,
4939
+ ...this.fonts ? { fonts: this.fonts } : {}
4311
4940
  });
4312
4941
  } finally {
4313
4942
  pool.release(pageObjectNumber);
@@ -4636,9 +5265,10 @@ function normalizeScale(scale) {
4636
5265
  * actually need a pagePtr to materialise their fields.
4637
5266
  */
4638
5267
  var RawAnnotationReader = class {
4639
- constructor(runtime, session) {
5268
+ constructor(runtime, session, fonts) {
4640
5269
  this.runtime = runtime;
4641
5270
  this.session = session;
5271
+ this.fonts = fonts;
4642
5272
  }
4643
5273
  listAll(signal) {
4644
5274
  throwIfAborted(signal);
@@ -4664,11 +5294,157 @@ var RawAnnotationReader = class {
4664
5294
  pageObjectNumber,
4665
5295
  count,
4666
5296
  getAnnotPtrAt: (i) => fn.EPDFPage_GetAnnotRaw(docPtr, record.pageIndex, i),
4667
- signal
5297
+ signal,
5298
+ ...this.fonts ? { fonts: this.fonts } : {}
4668
5299
  });
4669
5300
  }
4670
5301
  };
4671
5302
  //#endregion
5303
+ //#region src/features/annotations/internal/mutations/prepareMeasurementMutation.ts
5304
+ const kinds = /* @__PURE__ */ new Set([
5305
+ "line",
5306
+ "polygon",
5307
+ "polyline"
5308
+ ]);
5309
+ const inputs = [
5310
+ "linePoints",
5311
+ "vertices",
5312
+ "measure",
5313
+ "intent",
5314
+ "contents"
5315
+ ];
5316
+ function validate(value) {
5317
+ if (!kinds.has(value.subtype)) return;
5318
+ const v = value;
5319
+ try {
5320
+ if (v.measure !== void 0 && v.measure !== null) (0, _embedpdf_engine_core_runtime.assertWritableMeasure)(v.measure);
5321
+ if (v.caption != null && (typeof v.caption !== "object" || Array.isArray(v.caption))) throw new RangeError("Invalid caption");
5322
+ if (v.subtype === "line" && v.leader != null && (typeof v.leader !== "object" || Array.isArray(v.leader))) throw new RangeError("Invalid line leader");
5323
+ const intents = v.subtype === "line" ? ["LineDimension", "LineArrow"] : v.subtype === "polygon" ? ["PolygonDimension", "PolygonCloud"] : ["PolyLineDimension"];
5324
+ if (v.intent != null && !intents.includes(v.intent)) throw new RangeError("Invalid measurement intent");
5325
+ if (v.caption) {
5326
+ if (v.caption.enabled !== void 0 && typeof v.caption.enabled !== "boolean") throw new RangeError("Invalid caption visibility");
5327
+ if (v.subtype === "line") {
5328
+ if (v.caption.position != null && !["inline", "top"].includes(v.caption.position)) throw new RangeError("Invalid caption position");
5329
+ if (v.caption.offset) {
5330
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(v.caption.offset.along);
5331
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(v.caption.offset.perpendicular);
5332
+ }
5333
+ } else if (v.caption.center) {
5334
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(v.caption.center.x);
5335
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(v.caption.center.y);
5336
+ }
5337
+ }
5338
+ if (v.subtype === "line" && v.leader) {
5339
+ for (const n of [
5340
+ v.leader.length,
5341
+ v.leader.extension ?? 0,
5342
+ v.leader.offset ?? 0
5343
+ ]) (0, _embedpdf_engine_core_runtime.assertPdfFloat)(n);
5344
+ if ((v.leader.extension ?? 0) < 0 || (v.leader.offset ?? 0) < 0) throw new RangeError("Leader extension and offset must be nonnegative");
5345
+ }
5346
+ const points = v.subtype === "line" ? v.linePoints ? [v.linePoints.start, v.linePoints.end] : [] : v.vertices ?? [];
5347
+ for (const p of points) {
5348
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(p.x);
5349
+ (0, _embedpdf_engine_core_runtime.assertPdfFloat)(p.y);
5350
+ }
5351
+ } catch (error) {
5352
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, error.message);
5353
+ }
5354
+ }
5355
+ /** Infer a rigid transform only when ALL corresponding vertices agree. Vertex edits leave a manual center fixed. */
5356
+ function moveCenter(before, after, center) {
5357
+ if (before.length !== after.length || before.length < 2) return void 0;
5358
+ const a = before[0], b = after[0];
5359
+ const index = before.findIndex((p) => Math.hypot(p.x - a.x, p.y - a.y) > 1e-6);
5360
+ if (index < 0) return void 0;
5361
+ const u = {
5362
+ x: before[index].x - a.x,
5363
+ y: before[index].y - a.y
5364
+ };
5365
+ const v = {
5366
+ x: after[index].x - b.x,
5367
+ y: after[index].y - b.y
5368
+ };
5369
+ const length = u.x * u.x + u.y * u.y;
5370
+ const cos = (u.x * v.x + u.y * v.y) / length, sin = (u.x * v.y - u.y * v.x) / length;
5371
+ if (Math.abs(cos * cos + sin * sin - 1) > 1e-5) return void 0;
5372
+ const transform = (p) => ({
5373
+ x: b.x + cos * (p.x - a.x) - sin * (p.y - a.y),
5374
+ y: b.y + sin * (p.x - a.x) + cos * (p.y - a.y)
5375
+ });
5376
+ if (!before.every((p, i) => {
5377
+ const t = transform(p);
5378
+ return Math.hypot(t.x - after[i].x, t.y - after[i].y) <= .001;
5379
+ })) return void 0;
5380
+ return transform(center);
5381
+ }
5382
+ function prepareMeasurementDraft(draft) {
5383
+ validate(draft);
5384
+ if ((draft.subtype === "line" || draft.subtype === "polygon" || draft.subtype === "polyline") && draft.caption && draft.caption.enabled === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "A new caption requires enabled");
5385
+ return (0, _embedpdf_engine_core_runtime.deriveMeasurementLabel)(draft);
5386
+ }
5387
+ function prepareMeasurementPatch(fn, annot, current, patch) {
5388
+ if (patch.subtype !== current.subtype) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "Annotation subtype cannot change");
5389
+ validate(patch);
5390
+ if (patch.subtype !== "line" && patch.subtype !== "polygon" && patch.subtype !== "polyline" || current.subtype !== "line" && current.subtype !== "polygon" && current.subtype !== "polyline") return patch;
5391
+ if (patch.measure && current.measure && current.measure.subtype !== "RL") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "Remove the foreign measure explicitly before replacing it");
5392
+ if (patch.subtype !== "line" && patch.caption && fn.FPDFAnnot_HasKey(annot, "EMBD_Metadata") && fn.FPDFAnnot_GetValueType(annot, "EMBD_Metadata") !== 6) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "Malformed annotation metadata cannot store a caption");
5393
+ if (patch.subtype !== "line" && current.subtype !== "line" && patch.vertices && patch.caption !== null && patch.caption?.center === void 0 && current.caption?.center) {
5394
+ const center = moveCenter(current.vertices, patch.vertices, current.caption.center);
5395
+ if (center) patch = {
5396
+ ...patch,
5397
+ caption: {
5398
+ ...current.caption,
5399
+ ...patch.caption,
5400
+ center
5401
+ }
5402
+ };
5403
+ }
5404
+ if (patch.caption) {
5405
+ if (patch.subtype === "line" && current.subtype === "line") {
5406
+ const c = {
5407
+ enabled: false,
5408
+ ...current.caption,
5409
+ ...patch.caption
5410
+ };
5411
+ patch = {
5412
+ ...patch,
5413
+ caption: {
5414
+ enabled: c.enabled,
5415
+ position: c.position ?? "inline",
5416
+ ...c.offset ? { offset: c.offset } : {}
5417
+ }
5418
+ };
5419
+ } else if (patch.subtype !== "line" && current.subtype !== "line") {
5420
+ const c = {
5421
+ enabled: false,
5422
+ ...current.caption,
5423
+ ...patch.caption
5424
+ };
5425
+ patch = {
5426
+ ...patch,
5427
+ caption: {
5428
+ enabled: c.enabled,
5429
+ ...c.center ? { center: c.center } : {}
5430
+ }
5431
+ };
5432
+ }
5433
+ }
5434
+ if (inputs.some((key) => patch[key] !== void 0)) {
5435
+ const readout = (0, _embedpdf_engine_core_runtime.measurementReadout)({
5436
+ ...current,
5437
+ ...patch
5438
+ });
5439
+ if ((0, _embedpdf_engine_core_runtime.isReadout)(readout)) patch = {
5440
+ ...patch,
5441
+ contents: readout.label
5442
+ };
5443
+ }
5444
+ validate(patch);
5445
+ return patch;
5446
+ }
5447
+ //#endregion
4672
5448
  //#region src/features/annotations/internal/write/annotationWritePrimitives.ts
4673
5449
  /**
4674
5450
  * Write-side twin of `annotationReadPrimitives.ts`. Every annotation
@@ -5605,6 +6381,20 @@ const DEFAULT_FREETEXT_COLOR = {
5605
6381
  b: 0
5606
6382
  };
5607
6383
  /**
6384
+ * Write rich text through the engine's rich writer: `/RC`, `/DS`, `/DA`,
6385
+ * `/Contents` and the appearance, all or nothing. Faces in the input are
6386
+ * resolved from keys / standard names to the identities the engine names.
6387
+ */
6388
+ function writeRichText(fn, annotPtr, input, ctx) {
6389
+ const json = engineRichTextJson(input, ctx?.describeRegisteredFont);
6390
+ 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)");
6391
+ }
6392
+ /** `#RRGGBB` for a rich body colour. */
6393
+ function hexColor(color) {
6394
+ const hex = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
6395
+ return `#${hex(color.r)}${hex(color.g)}${hex(color.b)}`.toUpperCase();
6396
+ }
6397
+ /**
5608
6398
  * Apply a free-text draft to a freshly-created annotation. Colour model:
5609
6399
  * - `color` -> `/DA` colour = border + default text colour.
5610
6400
  * - `fontColor` (optional) -> `TextColor` channel, overriding text only;
@@ -5646,6 +6436,7 @@ function applyFreeTextDraft(fn, mem, annotPtr, draft, ctx) {
5646
6436
  rotation: draft.rotation,
5647
6437
  unrotatedRect: draft.unrotatedRect
5648
6438
  });
6439
+ writeRichText(fn, annotPtr, draft.richText ?? { paragraphs: (0, _embedpdf_engine_core_runtime.richTextParagraphsFromPlainText)(draft.contents ?? "") }, ctx);
5649
6440
  }
5650
6441
  /**
5651
6442
  * Apply a free-text patch to an existing annotation. Only present fields are
@@ -5672,11 +6463,30 @@ function applyFreeTextPatch(fn, mem, annotPtr, patch, ctx) {
5672
6463
  else setAnnotColor(fn, annotPtr, patch.interiorColor, FPDFANNOT_COLORTYPE.Color);
5673
6464
  if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
5674
6465
  applyBorderPatch(fn, mem, annotPtr, patch);
5675
- if (patch.fontFamily !== void 0 || patch.fontSize !== void 0 || patch.color !== void 0) {
6466
+ const current = readEngineRichText(fn, mem, annotPtr);
6467
+ if (patch.color !== void 0) {
5676
6468
  const cur = readDefaultAppearance(fn, mem, annotPtr);
5677
- applyDefaultAppearance(fn, annotPtr, patch.fontFamily ?? (cur ? standardFontFromCode(cur.fontCode) : "helvetica"), patch.fontSize ?? (cur && cur.fontSize > 0 ? cur.fontSize : 12), patch.color ?? cur?.color ?? DEFAULT_FREETEXT_COLOR, ctx);
6469
+ applyDefaultAppearance(fn, annotPtr, cur ? standardFontFromCode(cur.fontCode) : DEFAULT_STANDARD_FONT, cur && cur.fontSize > 0 ? cur.fontSize : 12, patch.color, ctx);
5678
6470
  }
5679
6471
  if (patch.fontColor !== void 0) setAnnotColor(fn, annotPtr, patch.fontColor, FPDFANNOT_COLORTYPE.TextColor);
6472
+ if (patch.richText !== void 0) writeRichText(fn, annotPtr, patch.richText, ctx);
6473
+ else if (patch.contents !== void 0) writeRichText(fn, annotPtr, { paragraphs: (0, _embedpdf_engine_core_runtime.richTextParagraphsFromPlainText)(patch.contents ?? "") }, ctx);
6474
+ else if (current && (patch.fontFamily !== void 0 || patch.fontSize !== void 0 || patch.fontColor !== void 0 || patch.textAlign !== void 0)) {
6475
+ const body = { ...current.body };
6476
+ if (patch.fontFamily !== void 0) {
6477
+ const face = faceForFreeTextFont(patch.fontFamily, ctx?.describeRegisteredFont);
6478
+ body.family = face.family;
6479
+ if (face.weight !== void 0) body.weight = face.weight;
6480
+ if (face.italic !== void 0) body.italic = face.italic;
6481
+ }
6482
+ if (patch.fontSize !== void 0) body.size = patch.fontSize;
6483
+ if (patch.fontColor !== void 0) body.color = hexColor(patch.fontColor);
6484
+ if (patch.textAlign !== void 0) body.align = patch.textAlign;
6485
+ writeRichText(fn, annotPtr, {
6486
+ body,
6487
+ paragraphs: current.paragraphs
6488
+ }, ctx);
6489
+ }
5680
6490
  if (patch.textAlign !== void 0) setTextAlignment(fn, annotPtr, textAlignmentToCode(patch.textAlign));
5681
6491
  if (patch.intent !== void 0) setIntent(fn, annotPtr, freeTextIntentToName(patch.intent));
5682
6492
  if (patch.rectDifferences === null) clearRectangleDifferences(fn, annotPtr);
@@ -5729,6 +6539,36 @@ function isInkSubtype(subtype) {
5729
6539
  return subtype === "ink";
5730
6540
  }
5731
6541
  //#endregion
6542
+ //#region src/features/annotations/internal/write/writeMeasurementFields.ts
6543
+ /** Receives normalized complete captions from the mutator, never partial state. */
6544
+ function writeMeasurementFields(fn, mem, annot, value) {
6545
+ if (value.intent !== void 0) if (value.intent === null) fn.EPDFAnnot_RemoveKey(annot, "IT");
6546
+ else requireMeasureWrite(fn.EPDFAnnot_SetIntent(annot, value.intent));
6547
+ if (value.measure !== void 0) if (value.measure === null) requireMeasureWrite(fn.EPDFAnnot_RemoveMeasure(annot));
6548
+ else writeMeasure(fn, mem, fn.EPDFAnnot_AddMeasure(annot), value.measure);
6549
+ if (value.subtype === "line") {
6550
+ if (value.leader !== void 0) requireMeasureWrite(fn.EPDFAnnot_SetLineLeader(annot, value.leader?.length ?? 0, value.leader?.extension ?? 0, value.leader?.offset ?? 0));
6551
+ if (value.caption !== void 0) if (value.caption === null) for (const key of [
6552
+ "Cap",
6553
+ "CP",
6554
+ "CO"
6555
+ ]) fn.EPDFAnnot_RemoveKey(annot, key);
6556
+ else {
6557
+ const c = value.caption;
6558
+ requireMeasureWrite(withMeasurePoint(mem, c.offset ? {
6559
+ x: c.offset.along,
6560
+ y: c.offset.perpendicular
6561
+ } : void 0, (p) => fn.EPDFAnnot_SetLineCaption(annot, c.enabled ?? false, c.position === "top" ? 1 : 0, p)));
6562
+ }
6563
+ } else if (value.caption !== void 0) if (value.caption === null) {
6564
+ fn.EPDFAnnot_ClearEmbedMetadataKey(annot, "MeasurementCaption");
6565
+ fn.EPDFAnnot_ClearEmbedMetadataKey(annot, "MeasurementCaptionCenter");
6566
+ } else {
6567
+ const c = value.caption;
6568
+ requireMeasureWrite(withMeasurePoint(mem, c.center ?? void 0, (p) => fn.EPDFAnnot_SetShapeCaption(annot, c.enabled ?? false, p)));
6569
+ }
6570
+ }
6571
+ //#endregion
5732
6572
  //#region src/features/annotations/internal/write/writeLineAnnotation.ts
5733
6573
  /** Default line endings when a line draft omits them. */
5734
6574
  const DEFAULT_LINE_ENDINGS$1 = {
@@ -5745,6 +6585,7 @@ const DEFAULT_LINE_ENDINGS$1 = {
5745
6585
  */
5746
6586
  function applyLineDraft(fn, mem, annotPtr, draft) {
5747
6587
  applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
6588
+ writeMeasurementFields(fn, mem, annotPtr, draft);
5748
6589
  setAnnotRect(fn, mem, annotPtr, draft.rect);
5749
6590
  applyFilledStyleDraft(fn, mem, annotPtr, draft);
5750
6591
  setLine(fn, mem, annotPtr, draft.linePoints);
@@ -5753,6 +6594,7 @@ function applyLineDraft(fn, mem, annotPtr, draft) {
5753
6594
  }
5754
6595
  function applyLinePatch(fn, mem, annotPtr, patch) {
5755
6596
  applyAnnotationBasePatch(fn, mem, annotPtr, patch);
6597
+ writeMeasurementFields(fn, mem, annotPtr, patch);
5756
6598
  if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
5757
6599
  applyFilledStylePatch(fn, mem, annotPtr, patch);
5758
6600
  if (patch.linePoints !== void 0) setLine(fn, mem, annotPtr, patch.linePoints);
@@ -6411,6 +7253,7 @@ const DEFAULT_LINE_ENDINGS = {
6411
7253
  */
6412
7254
  function applyPolygonDraft(fn, mem, annotPtr, draft) {
6413
7255
  applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
7256
+ writeMeasurementFields(fn, mem, annotPtr, draft);
6414
7257
  setAnnotRect(fn, mem, annotPtr, draft.rect);
6415
7258
  applyFilledStyleDraft(fn, mem, annotPtr, draft);
6416
7259
  setVertices(fn, mem, annotPtr, draft.vertices);
@@ -6419,6 +7262,7 @@ function applyPolygonDraft(fn, mem, annotPtr, draft) {
6419
7262
  }
6420
7263
  function applyPolygonPatch(fn, mem, annotPtr, patch) {
6421
7264
  applyAnnotationBasePatch(fn, mem, annotPtr, patch);
7265
+ writeMeasurementFields(fn, mem, annotPtr, patch);
6422
7266
  if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
6423
7267
  applyFilledStylePatch(fn, mem, annotPtr, patch);
6424
7268
  if (patch.vertices !== void 0) setVertices(fn, mem, annotPtr, patch.vertices);
@@ -6436,6 +7280,7 @@ function applyPolygonPatch(fn, mem, annotPtr, patch) {
6436
7280
  */
6437
7281
  function applyPolylineDraft(fn, mem, annotPtr, draft) {
6438
7282
  applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
7283
+ writeMeasurementFields(fn, mem, annotPtr, draft);
6439
7284
  setAnnotRect(fn, mem, annotPtr, draft.rect);
6440
7285
  applyFilledStyleDraft(fn, mem, annotPtr, draft);
6441
7286
  setVertices(fn, mem, annotPtr, draft.vertices);
@@ -6444,6 +7289,7 @@ function applyPolylineDraft(fn, mem, annotPtr, draft) {
6444
7289
  }
6445
7290
  function applyPolylinePatch(fn, mem, annotPtr, patch) {
6446
7291
  applyAnnotationBasePatch(fn, mem, annotPtr, patch);
7292
+ writeMeasurementFields(fn, mem, annotPtr, patch);
6447
7293
  if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
6448
7294
  applyFilledStylePatch(fn, mem, annotPtr, patch);
6449
7295
  if (patch.vertices !== void 0) setVertices(fn, mem, annotPtr, patch.vertices);
@@ -6726,7 +7572,10 @@ var AnnotationMutator = class {
6726
7572
  writeContext(pagePtr, resources) {
6727
7573
  const fonts = this.fonts;
6728
7574
  return {
6729
- ...fonts ? { resolveRegisteredFontId: (key) => fonts.idFor(key) } : {},
7575
+ ...fonts ? {
7576
+ resolveRegisteredFontId: (key) => fonts.idFor(key),
7577
+ describeRegisteredFont: (key) => fonts.describeOrUndefined(key)
7578
+ } : {},
6730
7579
  docPtr: this.session.requireDocPtr(),
6731
7580
  pagePtr,
6732
7581
  ...resources ? { resources } : {}
@@ -6742,6 +7591,8 @@ var AnnotationMutator = class {
6742
7591
  this.ensureKnownWeakStateFromPage(pageObjectNumber, pagePtr);
6743
7592
  const writeCtx = this.writeContext(pagePtr, resources);
6744
7593
  preflightDraft(draft, writeCtx);
7594
+ assertRichTextAgreement(draft);
7595
+ draft = prepareMeasurementDraft(draft);
6745
7596
  const pageStateBefore = this.session.pageState(pageObjectNumber);
6746
7597
  throwIfAborted(signal);
6747
7598
  const annotPtr = fn.EPDFPage_CreateAnnot(pagePtr, subtypeCode);
@@ -6764,7 +7615,7 @@ var AnnotationMutator = class {
6764
7615
  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
7616
  newIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
6766
7617
  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));
7618
+ dto = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
6768
7619
  joinWidgetFieldNumbers(this.runtime, this.session, [dto]);
6769
7620
  } finally {
6770
7621
  fn.FPDFPage_CloseAnnot(annotPtr);
@@ -6799,15 +7650,21 @@ var AnnotationMutator = class {
6799
7650
  throwIfAborted(signal);
6800
7651
  const writeCtx = this.writeContext(pagePtr, resources);
6801
7652
  preflightPatch(patch, writeCtx);
7653
+ assertRichTextAgreement(patch);
6802
7654
  this.ensureKnownWeakStateFromPage(ref.pageObjectNumber, pagePtr);
6803
7655
  const pageStateBefore = this.session.pageState(ref.pageObjectNumber);
6804
- throwIfAborted(signal);
6805
- const stableId = this.captureOrStampStableId(annotPtr);
6806
7656
  const previousBlendMode = blendModeFromCode(fn.EPDFAnnot_GetBlendMode(annotPtr));
6807
7657
  const preIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
6808
7658
  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));
7659
+ const currentDto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, preIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
7660
+ patch = prepareMeasurementPatch(fn, annotPtr, currentDto, patch);
7661
+ throwIfAborted(signal);
7662
+ const stableId = this.captureOrStampStableId(annotPtr);
6810
7663
  applyPatch(fn, mem, annotPtr, patch, writeCtx);
7664
+ if (patch.contents !== void 0 && patch.contents !== currentDto.contents && (0, _embedpdf_engine_core_runtime.isDimension)({
7665
+ ...currentDto,
7666
+ ...patch
7667
+ })) fn.EPDFAnnot_RemoveKey(annotPtr, "RC");
6811
7668
  let linkedParentId = null;
6812
7669
  if (patch.inReplyTo !== void 0 || patch.replyType !== void 0) linkedParentId = writeAnnotationRelationship(this.runtime, this.session, pagePtr, annotPtr, ref.pageObjectNumber, {
6813
7670
  inReplyTo: patch.inReplyTo,
@@ -6833,7 +7690,7 @@ var AnnotationMutator = class {
6833
7690
  annotPtr = resolveAnnotPtr(this.runtime, this.session, pagePtr, ref);
6834
7691
  const newIndex = fn.FPDFPage_GetAnnotIndex(pagePtr, annotPtr);
6835
7692
  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));
7693
+ const dto = readAnnotationFromPtr(fn, mem, annotPtr, ref.pageObjectNumber, newIndex, pageStateBefore.revision, readContextFor(this.session, this.fonts));
6837
7694
  joinWidgetFieldNumbers(this.runtime, this.session, [dto]);
6838
7695
  this.recordWeakStateFromPage(ref.pageObjectNumber, pagePtr);
6839
7696
  const meta = computeMutationImpact({
@@ -7046,7 +7903,7 @@ var AnnotationMutator = class {
7046
7903
  const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, newIdx);
7047
7904
  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
7905
  try {
7049
- moved[i] = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIdx, bumpedRev, readContextFor(this.session));
7906
+ moved[i] = readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, newIdx, bumpedRev, readContextFor(this.session, this.fonts));
7050
7907
  } finally {
7051
7908
  fn.FPDFPage_CloseAnnot(annotPtr);
7052
7909
  }
@@ -8308,104 +9165,2176 @@ function layerSaveError(status) {
8308
9165
  return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save layer artifact");
8309
9166
  }
8310
9167
  //#endregion
8311
- //#region src/features/save/DocumentSaver.ts
9168
+ //#region src/features/save/internal/readSavedBuffer.ts
9169
+ /** Keep the runtime's owned copy when it already occupies an entire ArrayBuffer. */
9170
+ function readSavedBuffer(mem, ptr, size) {
9171
+ const bytes = mem.readBytes(ptr, size);
9172
+ if (bytes.buffer instanceof ArrayBuffer && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) return bytes.buffer;
9173
+ return new Uint8Array(bytes).buffer;
9174
+ }
9175
+ //#endregion
9176
+ //#region src/runtime/memory/u64.ts
8312
9177
  /**
8313
- * Owns every "serialize the open document" path for a `DocumentSession`.
8314
- * Two distinct outputs:
8315
- *
8316
- * - Layer artifacts (`saveLayerArtifact*`) are the storage-optimized
8317
- * `.layer` delta used by server-side persistence. Only valid for a
8318
- * `layer` session.
8319
- * - Standalone saves (`saveStandalone*`) export a self-contained PDF
8320
- * view (base + applied delta) via `FPDF_SaveAsCopy` /
8321
- * `EPDF_SaveDocumentToOwnedBuffer`, valid for any session kind.
8322
- *
8323
- * Lives in `engine-services` so the browser Worker and the Node
8324
- * `worker_thread` server share the exact same save code path.
9178
+ * 64-bit out-params as two little-endian 32-bit halves: the wasm memory
9179
+ * helpers have no 64-bit accessors (Emscripten's `setValue(i64)` needs
9180
+ * WASM_BIGINT), and the native runtime reads the same layout.
8325
9181
  */
8326
- var DocumentSaver = class {
8327
- constructor(runtime, session) {
8328
- this.runtime = runtime;
8329
- this.session = session;
9182
+ function pokeU64(mem, ptr, value, byteOffset = 0) {
9183
+ const big = BigInt(value);
9184
+ mem.poke(ptr, "i32", Number(big & 4294967295n) | 0, byteOffset);
9185
+ mem.poke(ptr, "i32", Number(big >> 32n & 4294967295n) | 0, byteOffset + 4);
9186
+ }
9187
+ function peekU64(mem, ptr, byteOffset = 0) {
9188
+ const lo = Number(mem.peek(ptr, "i32", byteOffset)) >>> 0;
9189
+ return (Number(mem.peek(ptr, "i32", byteOffset + 4)) >>> 0) * 4294967296 + lo;
9190
+ }
9191
+ //#endregion
9192
+ //#region src/features/signature/internal/readSignatureModel.ts
9193
+ const KIND_DOC_TIMESTAMP = 1;
9194
+ const COVERAGE_BY_CODE = {
9195
+ 0: "whole-revision",
9196
+ 1: "partial",
9197
+ 2: "malformed"
9198
+ };
9199
+ const STRING_FILTER = 0;
9200
+ const STRING_SUBFILTER = 1;
9201
+ const STRING_NAME = 2;
9202
+ const STRING_REASON = 3;
9203
+ const STRING_LOCATION = 4;
9204
+ const STRING_CONTACT_INFO = 5;
9205
+ const STRING_M = 6;
9206
+ const FIELD_ACTION_BY_CODE = {
9207
+ 1: "all",
9208
+ 2: "include",
9209
+ 3: "exclude"
9210
+ };
9211
+ const FIELDS_FIELDMDP = 0;
9212
+ const FIELDS_LOCK = 1;
9213
+ const SV_V = 4;
9214
+ const SV_MDP = 1 << 17;
9215
+ const SV_LIST_SUBFILTER = 0;
9216
+ const SV_LIST_DIGEST_METHOD = 1;
9217
+ const SV_LIST_REASONS = 2;
9218
+ /** The PDF 1.7 seed-value entry set this engine implements. */
9219
+ const SUPPORTED_SEED_VALUE_VERSION = 2;
9220
+ const SV_UNSUPPORTED_REQUIRED = 432;
9221
+ function readWide(runtime, call) {
9222
+ return readUtf16String(runtime.mem, call, "");
9223
+ }
9224
+ function isPermission(value) {
9225
+ return value === 1 || value === 2 || value === 3;
9226
+ }
9227
+ function readByteRange(runtime, model, index) {
9228
+ const { fn, mem } = runtime;
9229
+ return withScratch(mem, 32, (ptr) => {
9230
+ for (let k = 0; k < 4; k++) pokeU64(mem, ptr, 0, k * 8);
9231
+ if (!fn.EPDFSig_GetByteRange(model, index, ptr)) return null;
9232
+ return [
9233
+ peekU64(mem, ptr, 0),
9234
+ peekU64(mem, ptr, 8),
9235
+ peekU64(mem, ptr, 16),
9236
+ peekU64(mem, ptr, 24)
9237
+ ];
9238
+ });
9239
+ }
9240
+ function readLock(runtime, model, index, which) {
9241
+ const { fn } = runtime;
9242
+ const code = which === FIELDS_LOCK ? fn.EPDFSig_GetLockAction(model, index) : fn.EPDFSig_GetFieldMDPAction(model, index);
9243
+ const action = FIELD_ACTION_BY_CODE[code];
9244
+ if (!action) return null;
9245
+ const fields = [];
9246
+ const count = fn.EPDFSig_GetFieldNameCount(model, index, which);
9247
+ for (let n = 0; n < count; n++) fields.push(readWide(runtime, (buf, cap) => fn.EPDFSig_GetFieldNameAt(model, index, which, n, buf, cap)) ?? "");
9248
+ const spec = {
9249
+ action,
9250
+ fields
9251
+ };
9252
+ if (which === FIELDS_LOCK) {
9253
+ const permission = fn.EPDFSig_GetLockPermission(model, index);
9254
+ if (isPermission(permission)) spec.permission = permission;
8330
9255
  }
8331
- saveLayerArtifact() {
8332
- this.requireLayer();
8333
- const { mem, fn } = this.runtime;
8334
- const sizePtr = mem.alloc(4);
8335
- const statusPtr = mem.alloc(4);
8336
- let artifactPtr = null;
8337
- try {
8338
- mem.poke(sizePtr, "i32", 0);
8339
- mem.poke(statusPtr, "i32", -1);
8340
- artifactPtr = fn.EPDFLayer_SaveLayerArtifactToOwnedBuffer(this.session.requireDocPtr(), sizePtr, statusPtr);
8341
- const status = Number(mem.peek(statusPtr, "i32"));
8342
- const size = Number(mem.peek(sizePtr, "i32"));
8343
- if (!artifactPtr || status !== 0 || size <= 0) throw layerSaveError(status);
8344
- const bytes = mem.readBytes(artifactPtr, size);
8345
- const buffer = new ArrayBuffer(bytes.byteLength);
8346
- new Uint8Array(buffer).set(bytes);
8347
- return {
8348
- bytes: buffer,
8349
- size
8350
- };
8351
- } finally {
8352
- if (artifactPtr) fn.EPDF_FreeBuffer(artifactPtr);
8353
- mem.free(statusPtr);
8354
- mem.free(sizePtr);
8355
- }
9256
+ return spec;
9257
+ }
9258
+ function readSeedValue(runtime, model, index) {
9259
+ const { fn } = runtime;
9260
+ if (!fn.EPDFSig_HasSeedValue(model, index)) return null;
9261
+ const requiredFlags = fn.EPDFSig_GetSeedValueRequiredFlags(model, index) >>> 0;
9262
+ const presentFlags = fn.EPDFSig_GetSeedValuePresentFlags(model, index) >>> 0;
9263
+ const version = fn.EPDFSig_GetSeedValueVersion(model, index);
9264
+ const mdp = fn.EPDFSig_GetSeedValueMDP(model, index);
9265
+ const list = (which) => {
9266
+ const count = fn.EPDFSig_GetSeedValueListCount(model, index, which);
9267
+ const items = [];
9268
+ for (let n = 0; n < count; n++) items.push(readWide(runtime, (buf, cap) => fn.EPDFSig_GetSeedValueListAt(model, index, which, n, buf, cap)) ?? "");
9269
+ return items;
9270
+ };
9271
+ return {
9272
+ requiredFlags,
9273
+ presentFlags,
9274
+ version: version > 0 ? version : null,
9275
+ mdp: (presentFlags & SV_MDP) !== 0 && mdp >= 0 && mdp <= 3 ? mdp : null,
9276
+ filter: readWide(runtime, (buf, cap) => fn.EPDFSig_GetSeedValueFilter(model, index, buf, cap)),
9277
+ subFilters: list(SV_LIST_SUBFILTER),
9278
+ digestMethods: list(SV_LIST_DIGEST_METHOD),
9279
+ reasons: list(SV_LIST_REASONS),
9280
+ unsupportedRequired: (requiredFlags & SV_UNSUPPORTED_REQUIRED) !== 0 || (requiredFlags & SV_V) !== 0 && version > SUPPORTED_SEED_VALUE_VERSION
9281
+ };
9282
+ }
9283
+ /** Every signature field of a native signature model, in model order. */
9284
+ function readSignaturesFromModel(runtime, model) {
9285
+ const { fn } = runtime;
9286
+ const count = fn.EPDFSig_Count(model);
9287
+ const out = [];
9288
+ const str = (i, key) => readWide(runtime, (buf, cap) => fn.EPDFSig_GetString(model, i, key, buf, cap));
9289
+ for (let i = 0; i < count; i++) {
9290
+ const signed = fn.EPDFSig_IsSigned(model, i);
9291
+ const widgetObjNum = fn.EPDFSig_GetWidgetObjNum(model, i);
9292
+ const revisionIndex = fn.EPDFSig_GetRevisionIndex(model, i);
9293
+ const docMdp = fn.EPDFSig_GetDocMDPPermission(model, i);
9294
+ out.push({
9295
+ index: i,
9296
+ field: {
9297
+ kind: "objectNumber",
9298
+ fieldObjectNumber: fn.EPDFSig_GetFieldObjNum(model, i)
9299
+ },
9300
+ fieldName: readWide(runtime, (buf, cap) => fn.EPDFSig_GetFieldName(model, i, buf, cap)) ?? "",
9301
+ widget: widgetObjNum > 0 ? {
9302
+ annotObjectNumber: widgetObjNum,
9303
+ pageObjectNumber: fn.EPDFSig_GetWidgetPageObjNum(model, i)
9304
+ } : null,
9305
+ signed,
9306
+ kind: fn.EPDFSig_GetKind(model, i) === KIND_DOC_TIMESTAMP ? "timestamp" : "signature",
9307
+ filter: str(i, STRING_FILTER),
9308
+ subFilter: str(i, STRING_SUBFILTER),
9309
+ byteRange: signed ? readByteRange(runtime, model, i) : null,
9310
+ contentsSize: signed ? Math.max(0, fn.EPDFSig_GetContents(model, i, _embedpdf_engine_runtime.NULL_PTR, 0)) : 0,
9311
+ coverage: signed ? COVERAGE_BY_CODE[fn.EPDFSig_GetCoverage(model, i)] ?? "malformed" : null,
9312
+ revisionIndex: signed && revisionIndex >= 0 ? revisionIndex : null,
9313
+ signer: {
9314
+ name: str(i, STRING_NAME),
9315
+ reason: str(i, STRING_REASON),
9316
+ location: str(i, STRING_LOCATION),
9317
+ contactInfo: str(i, STRING_CONTACT_INFO),
9318
+ claimedTime: str(i, STRING_M)
9319
+ },
9320
+ docMdp: isPermission(docMdp) ? docMdp : null,
9321
+ catalogCertification: fn.EPDFSig_IsCatalogCertification(model, i),
9322
+ fieldMdp: readLock(runtime, model, i, FIELDS_FIELDMDP),
9323
+ lock: readLock(runtime, model, i, FIELDS_LOCK),
9324
+ seedValue: readSeedValue(runtime, model, i)
9325
+ });
8356
9326
  }
8357
- saveLayerArtifactToFile(path) {
8358
- this.requireLayer();
8359
- const { mem, fn } = this.runtime;
8360
- const statusPtr = mem.alloc(4);
8361
- const writer = this.runtime.fileWrite.toNodeFile(path);
8362
- try {
8363
- mem.poke(statusPtr, "i32", -1);
8364
- const ok = fn.EPDFLayer_SaveLayerArtifact(this.session.requireDocPtr(), writer.ptr, statusPtr);
8365
- const status = Number(mem.peek(statusPtr, "i32"));
8366
- if (!ok || status !== 0) throw layerSaveError(status);
8367
- return { path };
8368
- } finally {
8369
- writer.close();
8370
- mem.free(statusPtr);
9327
+ return out;
9328
+ }
9329
+ /** The DER `/Contents` of signature `index`, copied out; `NotFound` when unsigned or malformed. */
9330
+ function readContentsAt(runtime, model, index) {
9331
+ const { fn, mem } = runtime;
9332
+ const length = fn.EPDFSig_GetContents(model, index, _embedpdf_engine_runtime.NULL_PTR, 0);
9333
+ if (length <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature has no usable /Contents (malformed encoding)");
9334
+ return withScratch(mem, length, (buf) => {
9335
+ 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");
9336
+ const out = new Uint8Array(length);
9337
+ out.set(mem.readBytes(buf, length));
9338
+ return out;
9339
+ });
9340
+ }
9341
+ /** The chained revisions of a document, oldest first; empty when the chain is not valid. */
9342
+ function readRevisions(runtime, docPtr, signatures) {
9343
+ const { fn, mem } = runtime;
9344
+ const count = fn.EPDFDoc_GetRevisionCount(docPtr);
9345
+ const revisions = [];
9346
+ if (count <= 0) return revisions;
9347
+ withScratchN(mem, [8, 8], ([endPtr, xrefPtr]) => {
9348
+ for (let i = 0; i < count; i++) {
9349
+ pokeU64(mem, endPtr, 0);
9350
+ pokeU64(mem, xrefPtr, 0);
9351
+ 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}`);
9352
+ const sealedBy = signatures.find((s) => s.revisionIndex === i);
9353
+ revisions.push({
9354
+ index: i,
9355
+ end: peekU64(mem, endPtr),
9356
+ xrefOffset: peekU64(mem, xrefPtr),
9357
+ signatureIndex: sealedBy ? sealedBy.index : null
9358
+ });
8371
9359
  }
9360
+ });
9361
+ return revisions;
9362
+ }
9363
+ /** Whether any signature field of the document carries a signed value (fields alone do not count). */
9364
+ function hasSignedSignature(runtime, docPtr) {
9365
+ const { fn } = runtime;
9366
+ return withSignatureModel(runtime, docPtr, (model) => {
9367
+ const count = fn.EPDFSig_Count(model);
9368
+ for (let i = 0; i < count; i++) if (fn.EPDFSig_IsSigned(model, i)) return true;
9369
+ return false;
9370
+ });
9371
+ }
9372
+ /** Run `body` with a fresh signature model of `docPtr`, closing it afterwards. */
9373
+ function withSignatureModel(runtime, docPtr, body) {
9374
+ const model = runtime.fn.EPDFSig_LoadModel(docPtr);
9375
+ 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");
9376
+ try {
9377
+ return body(model);
9378
+ } finally {
9379
+ runtime.fn.EPDFSig_CloseModel(model);
8372
9380
  }
8373
- saveStandaloneToBuffer(mode) {
8374
- const { mem, fn } = this.runtime;
8375
- const sizePtr = mem.alloc(4);
9381
+ }
9382
+ /**
9383
+ * The structure of one document (a revision prefix, usually): catalog,
9384
+ * AcroForm and page-tree object numbers, the pages, the terminal fields
9385
+ * with their widgets, and the signatures. What the rule engine needs to
9386
+ * give every changed object a role.
9387
+ */
9388
+ function readStructure(runtime, docPtr) {
9389
+ const { fn, mem } = runtime;
9390
+ const [root, acroForm, pagesRoot] = withScratchN(mem, [
9391
+ 4,
9392
+ 4,
9393
+ 4
9394
+ ], ([r, a, p]) => {
9395
+ mem.poke(r, "i32", 0);
9396
+ mem.poke(a, "i32", 0);
9397
+ mem.poke(p, "i32", 0);
9398
+ fn.EPDFDoc_GetStructureObjectNumbers(docPtr, r, a, p);
9399
+ return [
9400
+ Number(mem.peek(r, "i32")) >>> 0,
9401
+ Number(mem.peek(a, "i32")) >>> 0,
9402
+ Number(mem.peek(p, "i32")) >>> 0
9403
+ ];
9404
+ });
9405
+ const pages = [];
9406
+ const pageCount = fn.FPDF_GetPageCount(docPtr);
9407
+ for (let i = 0; i < pageCount; i++) pages.push(fn.EPDFDoc_GetPageObjectNumberByIndex(docPtr, i));
9408
+ const fields = [];
9409
+ const formModel = fn.EPDFForm_LoadModel(docPtr);
9410
+ if (formModel !== _embedpdf_engine_runtime.NULL_PTR) try {
9411
+ const snapshot = readFormSnapshot(runtime, formModel, docPtr);
9412
+ for (const f of snapshot.fields) fields.push({
9413
+ objectNumber: f.fieldObjectNumber,
9414
+ name: f.name,
9415
+ family: f.family,
9416
+ widgets: f.widgets.map((w) => w.annotObjectNumber),
9417
+ flags: f.flags.raw
9418
+ });
9419
+ } finally {
9420
+ fn.EPDFForm_CloseModel(formModel);
9421
+ }
9422
+ return {
9423
+ root,
9424
+ acroForm,
9425
+ pagesRoot,
9426
+ pages,
9427
+ fields,
9428
+ signatures: withSignatureModel(runtime, docPtr, (model) => readSignaturesFromModel(runtime, model))
9429
+ };
9430
+ }
9431
+ //#endregion
9432
+ //#region src/features/signature/internal/signatureModelCache.ts
9433
+ /**
9434
+ * Per-session cache of the native `EPDFSig_LoadModel` snapshot, keyed on
9435
+ * the session's mutation sequence exactly like the form model: the model
9436
+ * walks the revision chain, the field tree and every widget, so it is
9437
+ * built once per document state and rebuilt after any mutation.
9438
+ */
9439
+ const cache$1 = /* @__PURE__ */ new WeakMap();
9440
+ function acquireSignatureModel(runtime, session) {
9441
+ const seq = session.mutationSeq();
9442
+ const hit = cache$1.get(session);
9443
+ if (hit && hit.seq === seq) return hit.ptr;
9444
+ if (hit) {
9445
+ runtime.fn.EPDFSig_CloseModel(hit.ptr);
9446
+ cache$1.delete(session);
9447
+ }
9448
+ const ptr = runtime.fn.EPDFSig_LoadModel(session.requireDocPtr());
9449
+ 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");
9450
+ cache$1.set(session, {
9451
+ seq,
9452
+ ptr
9453
+ });
9454
+ return ptr;
9455
+ }
9456
+ function disposeSignatureModel(runtime, session) {
9457
+ const hit = cache$1.get(session);
9458
+ if (!hit) return;
9459
+ runtime.fn.EPDFSig_CloseModel(hit.ptr);
9460
+ cache$1.delete(session);
9461
+ }
9462
+ //#endregion
9463
+ //#region src/features/signature/SignatureReader.ts
9464
+ const DIGEST_CODE = {
9465
+ sha1: 0,
9466
+ sha256: 1,
9467
+ sha384: 2,
9468
+ sha512: 3
9469
+ };
9470
+ const DIGEST_LENGTH = {
9471
+ sha1: 20,
9472
+ sha256: 32,
9473
+ sha384: 48,
9474
+ sha512: 64
9475
+ };
9476
+ /**
9477
+ * Reads the signature model of a session: revisions, every signature
9478
+ * field with its signed state, and the protection those signatures
9479
+ * impose. Byte facts (revisions, coverage, digests, loaded bytes) come
9480
+ * from the bytes the document was loaded from — for a layer session the
9481
+ * base plus the delta it was opened with — never from unsaved edits.
9482
+ */
9483
+ var SignatureReader = class {
9484
+ constructor(runtime, session) {
9485
+ this.runtime = runtime;
9486
+ this.session = session;
9487
+ }
9488
+ readSnapshot() {
9489
+ const model = acquireSignatureModel(this.runtime, this.session);
9490
+ const chainValid = this.runtime.fn.EPDFSig_IsRevisionChainValid(model);
9491
+ const signatures = readSignaturesFromModel(this.runtime, model);
9492
+ return {
9493
+ chainValid,
9494
+ revisions: chainValid ? readRevisions(this.runtime, this.session.requireDocPtr(), signatures) : [],
9495
+ signatures,
9496
+ protection: (0, _embedpdf_engine_core_runtime.deriveProtection)(signatures)
9497
+ };
9498
+ }
9499
+ /** The protection alone, for the open probe and the form write guard. */
9500
+ readProtection() {
9501
+ const model = acquireSignatureModel(this.runtime, this.session);
9502
+ return (0, _embedpdf_engine_core_runtime.deriveProtection)(readSignaturesFromModel(this.runtime, model));
9503
+ }
9504
+ /** The DER `/Contents` of a signed field, padding stripped. */
9505
+ readContents(ref) {
9506
+ const model = acquireSignatureModel(this.runtime, this.session);
9507
+ const index = this.requireSignedIndex(model, ref);
9508
+ return readContentsAt(this.runtime, model, index).buffer;
9509
+ }
9510
+ /** Hash a signed field's `/ByteRange` straight from the loaded bytes. */
9511
+ digest(ref, algorithm) {
9512
+ const model = acquireSignatureModel(this.runtime, this.session);
9513
+ const index = this.requireSignedIndex(model, ref);
9514
+ const range = readByteRange(this.runtime, model, index);
9515
+ if (!range) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature has no usable /ByteRange");
9516
+ return this.digestRange(range, algorithm);
9517
+ }
9518
+ /** The exact bytes of one revision. */
9519
+ revisionBytes(revisionIndex) {
9520
+ const snapshot = this.readSnapshot();
9521
+ if (!snapshot.chainValid) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "revision chain is not valid");
9522
+ const revision = snapshot.revisions[revisionIndex];
9523
+ if (!revision) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no revision ${revisionIndex}`);
9524
+ return this.readLoadedBytes(0, revision.end);
9525
+ }
9526
+ /** Resolve a signature-field ref to its identity in the current model. */
9527
+ resolveField(ref) {
9528
+ const { fn } = this.runtime;
9529
+ const model = acquireSignatureModel(this.runtime, this.session);
9530
+ const index = this.indexOf(model, ref);
9531
+ 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}"`);
9532
+ const widgetObjNum = fn.EPDFSig_GetWidgetObjNum(model, index);
9533
+ return {
9534
+ index,
9535
+ fieldObjectNumber: fn.EPDFSig_GetFieldObjNum(model, index),
9536
+ signed: fn.EPDFSig_IsSigned(model, index),
9537
+ widget: widgetObjNum > 0 ? {
9538
+ annotObjectNumber: widgetObjNum,
9539
+ pageObjectNumber: fn.EPDFSig_GetWidgetPageObjNum(model, index)
9540
+ } : null
9541
+ };
9542
+ }
9543
+ /** One signature by field object number, from the current model. */
9544
+ readSignatureByObjectNumber(fieldObjectNumber) {
9545
+ const found = this.readSnapshot().signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === fieldObjectNumber);
9546
+ if (!found) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `signature field not found: object ${fieldObjectNumber}`);
9547
+ return found;
9548
+ }
9549
+ /** The complete loaded bytes (a plain file, or a layer's base + delta). */
9550
+ loadedBytes() {
9551
+ const size = Number(this.runtime.fn.EPDFDoc_GetLoadedBytesSize(this.session.requireDocPtr()));
9552
+ return this.readLoadedBytes(0, size);
9553
+ }
9554
+ /**
9555
+ * SHA-256 and length of the base the session is on. A layer session
9556
+ * reports its base's hash (supplied by the host or computed once by the
9557
+ * runtime); a plain session hashes its loaded bytes once per load.
9558
+ */
9559
+ version() {
9560
+ const { fn, mem } = this.runtime;
9561
+ const docPtr = this.session.requireDocPtr();
9562
+ const byteLength = Number(fn.EPDFDoc_GetBaseBytesSize(docPtr));
9563
+ if (byteLength <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "document has no loaded bytes");
9564
+ if (this.session.kind === "layer") return {
9565
+ sha256: withScratch(mem, 32, (out) => {
9566
+ 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");
9567
+ return toHex(mem.readBytes(out, 32));
9568
+ }),
9569
+ byteLength
9570
+ };
9571
+ const cached = this.session.cachedPlainSha256();
9572
+ if (cached) return {
9573
+ sha256: cached,
9574
+ byteLength
9575
+ };
9576
+ const sha256 = toHex(new Uint8Array(this.digestRange([
9577
+ 0,
9578
+ byteLength,
9579
+ byteLength,
9580
+ 0
9581
+ ], "sha256")));
9582
+ this.session.rememberPlainSha256(sha256);
9583
+ return {
9584
+ sha256,
9585
+ byteLength
9586
+ };
9587
+ }
9588
+ digestRange(range, algorithm) {
9589
+ const { fn, mem } = this.runtime;
9590
+ const docPtr = this.session.requireDocPtr();
9591
+ const outLength = DIGEST_LENGTH[algorithm];
9592
+ return withScratchN(mem, [
9593
+ 32,
9594
+ outLength,
9595
+ 8
9596
+ ], ([rangePtr, outPtr, lenPtr]) => {
9597
+ for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, range[k], k * 8);
9598
+ pokeU64(mem, lenPtr, outLength);
9599
+ 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");
9600
+ const written = Number(mem.peek(lenPtr, "i32"));
9601
+ return copyOut$1(mem.readBytes(outPtr, written));
9602
+ });
9603
+ }
9604
+ /** `[offset, offset + length)` of the loaded bytes (base + loaded delta for a layer), copied out. */
9605
+ readLoadedBytes(offset, length) {
9606
+ const { fn, mem } = this.runtime;
9607
+ const docPtr = this.session.requireDocPtr();
9608
+ if (length === 0) return /* @__PURE__ */ new ArrayBuffer(0);
9609
+ return withScratch(mem, length, (ptr) => {
9610
+ 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");
9611
+ return copyOut$1(mem.readBytes(ptr, length));
9612
+ });
9613
+ }
9614
+ indexOf(model, ref) {
9615
+ const { fn } = this.runtime;
9616
+ if (ref.kind === "objectNumber") return fn.EPDFSig_GetIndexByFieldObjNum(model, ref.fieldObjectNumber);
9617
+ const count = fn.EPDFSig_Count(model);
9618
+ for (let i = 0; i < count; i++) if (this.readWide((buf, cap) => fn.EPDFSig_GetFieldName(model, i, buf, cap)) === ref.name) return i;
9619
+ return -1;
9620
+ }
9621
+ requireSignedIndex(model, ref) {
9622
+ const { fn } = this.runtime;
9623
+ const index = this.indexOf(model, ref);
9624
+ 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}"`);
9625
+ if (!fn.EPDFSig_IsSigned(model, index)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "signature field is not signed");
9626
+ return index;
9627
+ }
9628
+ readString(model, index, key) {
9629
+ return this.readWide((buf, cap) => this.runtime.fn.EPDFSig_GetString(model, index, key, buf, cap));
9630
+ }
9631
+ readWide(call) {
9632
+ return readUtf16String(this.runtime.mem, call, "");
9633
+ }
9634
+ };
9635
+ function copyOut$1(view) {
9636
+ const buffer = new ArrayBuffer(view.byteLength);
9637
+ new Uint8Array(buffer).set(view);
9638
+ return buffer;
9639
+ }
9640
+ function toHex(bytes) {
9641
+ let out = "";
9642
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
9643
+ return out;
9644
+ }
9645
+ //#endregion
9646
+ //#region src/features/save/DocumentSaver.ts
9647
+ /** The fork's `EPDFSaveStatus`. */
9648
+ const EPDF_SAVE_WRITTEN = 1;
9649
+ const EPDF_SAVE_UNCHANGED_SINCE_LOAD = 2;
9650
+ /**
9651
+ * Owns every "serialize the open document" path for a `DocumentSession`.
9652
+ * Two distinct outputs:
9653
+ *
9654
+ * - Layer artifacts (`saveLayerArtifact*`) are the storage-optimized
9655
+ * `.layer` delta used by server-side persistence. Only valid for a
9656
+ * `layer` session.
9657
+ * - Standalone saves (`saveStandalone*`) export a self-contained PDF
9658
+ * view (base + applied delta) via `FPDF_SaveAsCopy` /
9659
+ * `EPDF_SaveDocumentToOwnedBuffer`, valid for any session kind.
9660
+ *
9661
+ * Lives in `engine-services` so the browser Worker and the Node
9662
+ * `worker_thread` server share the exact same save code path.
9663
+ */
9664
+ var DocumentSaver = class {
9665
+ constructor(runtime, session) {
9666
+ this.runtime = runtime;
9667
+ this.session = session;
9668
+ }
9669
+ /**
9670
+ * The bytes a save would produce, and whether they are the loaded bytes.
9671
+ * Three answers, cheapest first: nothing was mutated since load (the
9672
+ * session's counter); something was mutated but the document is still
9673
+ * the one it was opened with — the fork's save pass found no reachable
9674
+ * object that differs from its loaded version, an annotation added and
9675
+ * removed again being the common case; or a real delta. The one pass
9676
+ * answers the second and produces the third, comparing only the objects
9677
+ * that were touched. A signed file must come back exactly as sealed, so
9678
+ * the loaded bytes are returned verbatim in the first two cases.
9679
+ */
9680
+ snapshot() {
9681
+ if (!this.session.hasUnsavedEdits()) return {
9682
+ bytes: this.loadedBytes(),
9683
+ source: "loaded"
9684
+ };
9685
+ const saved = this.saveStandaloneToBufferEx("incremental");
9686
+ if (saved.unchangedSinceLoad) return {
9687
+ bytes: this.loadedBytes(),
9688
+ source: "loaded"
9689
+ };
9690
+ return {
9691
+ bytes: saved.bytes,
9692
+ source: "working-copy"
9693
+ };
9694
+ }
9695
+ /** The complete loaded bytes: a plain file, or a layer's base plus the delta it was opened with. */
9696
+ loadedBytes() {
9697
+ return new SignatureReader(this.runtime, this.session).loadedBytes();
9698
+ }
9699
+ /**
9700
+ * The loaded bytes streamed to `path` in chunks: a plain file, or a
9701
+ * layer's base plus the delta it was opened with, never the base alone.
9702
+ * Native only. Replaces whatever `path` held.
9703
+ */
9704
+ copyLoadedBytesToFile(path) {
9705
+ const reader = new SignatureReader(this.runtime, this.session);
9706
+ const size = Number(this.runtime.fn.EPDFDoc_GetLoadedBytesSize(this.session.requireDocPtr()));
9707
+ const chunk = 4 * 1024 * 1024;
9708
+ this.runtime.fileWrite.removeFile(path);
9709
+ for (let offset = 0; offset < size; offset += chunk) {
9710
+ const length = Math.min(chunk, size - offset);
9711
+ this.runtime.fileWrite.appendBytes(path, new Uint8Array(reader.readLoadedBytes(offset, length)));
9712
+ }
9713
+ if (size === 0) this.runtime.fileWrite.appendBytes(path, /* @__PURE__ */ new Uint8Array(0));
9714
+ }
9715
+ /**
9716
+ * `snapshot()` onto a file: `path` ends up holding the document a save
9717
+ * would write — the loaded bytes when nothing changed, else the standalone
9718
+ * save — without the document ever being an in-memory buffer. Native only.
9719
+ */
9720
+ snapshotToFile(path) {
9721
+ if (!this.session.hasUnsavedEdits()) {
9722
+ this.copyLoadedBytesToFile(path);
9723
+ return {
9724
+ path,
9725
+ source: "loaded"
9726
+ };
9727
+ }
9728
+ if (this.saveStandaloneToFileEx(path, "incremental").unchangedSinceLoad) {
9729
+ this.copyLoadedBytesToFile(path);
9730
+ return {
9731
+ path,
9732
+ source: "loaded"
9733
+ };
9734
+ }
9735
+ return {
9736
+ path,
9737
+ source: "working-copy"
9738
+ };
9739
+ }
9740
+ /** Whether this runtime and session can put scratch output beside the base file. */
9741
+ canWriteScratchFiles() {
9742
+ return this.runtime.kind === "native" && this.session.source.base?.kind === "file";
9743
+ }
9744
+ saveLayerArtifact() {
9745
+ const saved = this.saveLayerArtifactImpl(false);
9746
+ if (saved.size <= 0) throw layerSaveError(-1);
9747
+ return {
9748
+ bytes: saved.bytes,
9749
+ size: saved.size
9750
+ };
9751
+ }
9752
+ /**
9753
+ * `saveLayerArtifact` that says whether anything reachable changed since
9754
+ * the layer was opened. When nothing did, `bytes` is empty and the
9755
+ * artifact the session was opened with stands. The artifact written
9756
+ * otherwise is cumulative against the base (empty delta when the layer
9757
+ * came to equal its base).
9758
+ */
9759
+ saveLayerArtifactEx() {
9760
+ return this.saveLayerArtifactImpl(true);
9761
+ }
9762
+ /**
9763
+ * `saveLayerArtifactEx` onto a file: nothing reaches `path` when nothing
9764
+ * changed since load. The delta inside is streamed, never held whole.
9765
+ * Native only.
9766
+ */
9767
+ saveLayerArtifactToFileEx(path) {
9768
+ this.requireLayer();
9769
+ return this.saveLayerToFileImpl(path, "artifact");
9770
+ }
9771
+ /** `saveLayerDeltaEx` onto a file: the cumulative delta, or nothing when nothing changed since load. Native only. */
9772
+ saveLayerDeltaToFileEx(path) {
9773
+ this.requireLayer();
9774
+ return this.saveLayerToFileImpl(path, "delta");
9775
+ }
9776
+ saveLayerToFileImpl(path, what) {
9777
+ const { mem, fn } = this.runtime;
9778
+ const statusPtr = mem.alloc(4);
9779
+ const changedPtr = mem.alloc(4);
9780
+ this.runtime.fileWrite.removeFile(path);
9781
+ const writer = this.runtime.fileWrite.toNodeFile(path);
9782
+ try {
9783
+ mem.poke(statusPtr, "i32", -1);
9784
+ mem.poke(changedPtr, "i32", 1);
9785
+ const ok = what === "artifact" ? fn.EPDFLayer_SaveLayerArtifactEx(this.session.requireDocPtr(), writer.ptr, statusPtr, changedPtr) : fn.EPDFLayer_SaveDeltaEx(this.session.requireDocPtr(), writer.ptr, statusPtr, changedPtr);
9786
+ const status = Number(mem.peek(statusPtr, "i32"));
9787
+ if (!ok || status !== 0) throw layerSaveError(status);
9788
+ return {
9789
+ path,
9790
+ changedSinceLoad: Number(mem.peek(changedPtr, "i32")) !== 0
9791
+ };
9792
+ } finally {
9793
+ writer.close();
9794
+ mem.free(changedPtr);
9795
+ mem.free(statusPtr);
9796
+ }
9797
+ }
9798
+ saveLayerArtifactImpl(reporting) {
9799
+ this.requireLayer();
9800
+ const { mem, fn } = this.runtime;
9801
+ const sizePtr = mem.alloc(8);
9802
+ const statusPtr = mem.alloc(4);
9803
+ const changedPtr = mem.alloc(4);
9804
+ let artifactPtr = null;
9805
+ try {
9806
+ mem.poke(sizePtr, "i32", 0);
9807
+ mem.poke(statusPtr, "i32", -1);
9808
+ mem.poke(changedPtr, "i32", 1);
9809
+ artifactPtr = reporting ? fn.EPDFLayer_SaveLayerArtifactToOwnedBufferEx(this.session.requireDocPtr(), sizePtr, statusPtr, changedPtr) : fn.EPDFLayer_SaveLayerArtifactToOwnedBuffer(this.session.requireDocPtr(), sizePtr, statusPtr);
9810
+ const status = Number(mem.peek(statusPtr, "i32"));
9811
+ const size = Number(mem.peek(sizePtr, "i32"));
9812
+ const changedSinceLoad = reporting ? Number(mem.peek(changedPtr, "i32")) !== 0 : true;
9813
+ if (status !== 0) throw layerSaveError(status);
9814
+ if (reporting && !changedSinceLoad) return {
9815
+ bytes: /* @__PURE__ */ new ArrayBuffer(0),
9816
+ size: 0,
9817
+ changedSinceLoad: false
9818
+ };
9819
+ if (!artifactPtr || size <= 0) throw layerSaveError(status);
9820
+ const bytes = mem.readBytes(artifactPtr, size);
9821
+ const buffer = new ArrayBuffer(bytes.byteLength);
9822
+ new Uint8Array(buffer).set(bytes);
9823
+ return {
9824
+ bytes: buffer,
9825
+ size,
9826
+ changedSinceLoad
9827
+ };
9828
+ } finally {
9829
+ if (artifactPtr) fn.EPDF_FreeBuffer(artifactPtr);
9830
+ mem.free(changedPtr);
9831
+ mem.free(statusPtr);
9832
+ mem.free(sizePtr);
9833
+ }
9834
+ }
9835
+ /**
9836
+ * The raw cumulative delta a layer save writes (every promoted object,
9837
+ * offsets notional from the base's append offset), without the artifact
9838
+ * header: what `EPDFDoc_OpenBaseOverlay` composes with the base. Empty
9839
+ * when nothing was promoted.
9840
+ */
9841
+ saveLayerDelta() {
9842
+ const saved = this.saveLayerDeltaImpl(false);
9843
+ return {
9844
+ bytes: saved.bytes,
9845
+ size: saved.size
9846
+ };
9847
+ }
9848
+ /**
9849
+ * `saveLayerDelta` that says whether anything reachable changed since the
9850
+ * layer was opened; empty bytes when nothing did (keep what you have), or
9851
+ * when the layer came to equal its base (then `changedSinceLoad` is true
9852
+ * and the document IS the base).
9853
+ */
9854
+ saveLayerDeltaEx() {
9855
+ return this.saveLayerDeltaImpl(true);
9856
+ }
9857
+ saveLayerDeltaImpl(reporting) {
9858
+ this.requireLayer();
9859
+ const { mem, fn } = this.runtime;
9860
+ const sizePtr = mem.alloc(8);
9861
+ const statusPtr = mem.alloc(4);
9862
+ const changedPtr = mem.alloc(4);
9863
+ let deltaPtr = null;
9864
+ try {
9865
+ mem.poke(sizePtr, "i32", 0);
9866
+ mem.poke(statusPtr, "i32", -1);
9867
+ mem.poke(changedPtr, "i32", 1);
9868
+ deltaPtr = reporting ? fn.EPDFLayer_SaveDeltaToOwnedBufferEx(this.session.requireDocPtr(), sizePtr, statusPtr, changedPtr) : fn.EPDFLayer_SaveDeltaToOwnedBuffer(this.session.requireDocPtr(), sizePtr, statusPtr);
9869
+ const status = Number(mem.peek(statusPtr, "i32"));
9870
+ const size = Number(mem.peek(sizePtr, "i32"));
9871
+ const changedSinceLoad = reporting ? Number(mem.peek(changedPtr, "i32")) !== 0 : true;
9872
+ if (status !== 0) throw layerSaveError(status);
9873
+ if (!deltaPtr || size <= 0) return {
9874
+ bytes: /* @__PURE__ */ new ArrayBuffer(0),
9875
+ size: 0,
9876
+ changedSinceLoad
9877
+ };
9878
+ const bytes = mem.readBytes(deltaPtr, size);
9879
+ const buffer = new ArrayBuffer(bytes.byteLength);
9880
+ new Uint8Array(buffer).set(bytes);
9881
+ return {
9882
+ bytes: buffer,
9883
+ size,
9884
+ changedSinceLoad
9885
+ };
9886
+ } finally {
9887
+ if (deltaPtr) fn.EPDF_FreeBuffer(deltaPtr);
9888
+ mem.free(changedPtr);
9889
+ mem.free(statusPtr);
9890
+ mem.free(sizePtr);
9891
+ }
9892
+ }
9893
+ saveLayerArtifactToFile(path) {
9894
+ this.requireLayer();
9895
+ const { mem, fn } = this.runtime;
9896
+ const statusPtr = mem.alloc(4);
9897
+ const writer = this.runtime.fileWrite.toNodeFile(path);
9898
+ try {
9899
+ mem.poke(statusPtr, "i32", -1);
9900
+ const ok = fn.EPDFLayer_SaveLayerArtifact(this.session.requireDocPtr(), writer.ptr, statusPtr);
9901
+ const status = Number(mem.peek(statusPtr, "i32"));
9902
+ if (!ok || status !== 0) throw layerSaveError(status);
9903
+ return { path };
9904
+ } finally {
9905
+ writer.close();
9906
+ mem.free(statusPtr);
9907
+ }
9908
+ }
9909
+ saveStandaloneToBuffer(mode) {
9910
+ const { mem, fn } = this.runtime;
9911
+ const sizePtr = mem.alloc(8);
8376
9912
  let pdfPtr = null;
8377
9913
  try {
8378
9914
  mem.poke(sizePtr, "i32", 0);
8379
9915
  pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(this.session.requireDocPtr(), pdfSaveModeFlags(mode), sizePtr);
8380
9916
  const size = Number(mem.peek(sizePtr, "i32"));
8381
9917
  if (!pdfPtr || size <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
8382
- const bytes = mem.readBytes(pdfPtr, size);
8383
- const buffer = new ArrayBuffer(bytes.byteLength);
8384
- new Uint8Array(buffer).set(bytes);
8385
9918
  return {
8386
- bytes: buffer,
8387
- size
9919
+ bytes: readSavedBuffer(mem, pdfPtr, size),
9920
+ size
9921
+ };
9922
+ } finally {
9923
+ if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
9924
+ mem.free(sizePtr);
9925
+ }
9926
+ }
9927
+ /**
9928
+ * `saveStandaloneToBuffer` that says whether the save wrote anything. An
9929
+ * incremental save of a layer never rewrites an object equal to its base
9930
+ * twin, and when no reachable object differs from the document the layer
9931
+ * was opened with it writes nothing at all: `unchangedSinceLoad`, empty
9932
+ * bytes — the loaded bytes are the document.
9933
+ */
9934
+ saveStandaloneToBufferEx(mode) {
9935
+ const { mem, fn } = this.runtime;
9936
+ const sizePtr = mem.alloc(8);
9937
+ const statusPtr = mem.alloc(4);
9938
+ let pdfPtr = null;
9939
+ try {
9940
+ mem.poke(sizePtr, "i32", 0);
9941
+ mem.poke(statusPtr, "i32", 0);
9942
+ pdfPtr = fn.EPDF_SaveDocumentToOwnedBufferEx(this.session.requireDocPtr(), pdfSaveModeFlags(mode), 0, sizePtr, statusPtr);
9943
+ const status = Number(mem.peek(statusPtr, "i32"));
9944
+ if (status === EPDF_SAVE_UNCHANGED_SINCE_LOAD) return {
9945
+ bytes: /* @__PURE__ */ new ArrayBuffer(0),
9946
+ size: 0,
9947
+ unchangedSinceLoad: true
9948
+ };
9949
+ const size = Number(mem.peek(sizePtr, "i32"));
9950
+ 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");
9951
+ return {
9952
+ bytes: readSavedBuffer(mem, pdfPtr, size),
9953
+ size,
9954
+ unchangedSinceLoad: false
9955
+ };
9956
+ } finally {
9957
+ if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
9958
+ mem.free(statusPtr);
9959
+ mem.free(sizePtr);
9960
+ }
9961
+ }
9962
+ saveStandaloneToFile(path, mode) {
9963
+ const writer = this.runtime.fileWrite.toNodeFile(path);
9964
+ try {
9965
+ 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");
9966
+ return { path };
9967
+ } finally {
9968
+ writer.close();
9969
+ }
9970
+ }
9971
+ /**
9972
+ * `saveStandaloneToFile` with the report of `saveStandaloneToBufferEx`.
9973
+ * On `unchangedSinceLoad` no byte reached the file: the caller streams
9974
+ * the loaded bytes instead.
9975
+ */
9976
+ saveStandaloneToFileEx(path, mode) {
9977
+ const { mem, fn } = this.runtime;
9978
+ const writer = this.runtime.fileWrite.toNodeFile(path);
9979
+ const statusPtr = mem.alloc(4);
9980
+ try {
9981
+ mem.poke(statusPtr, "i32", 0);
9982
+ const ok = fn.EPDF_SaveAsCopyEx(this.session.requireDocPtr(), writer.ptr, pdfSaveModeFlags(mode), statusPtr);
9983
+ const status = Number(mem.peek(statusPtr, "i32"));
9984
+ if (!ok || status === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed, "failed to save document");
9985
+ return {
9986
+ path,
9987
+ unchangedSinceLoad: status === EPDF_SAVE_UNCHANGED_SINCE_LOAD
9988
+ };
9989
+ } finally {
9990
+ mem.free(statusPtr);
9991
+ writer.close();
9992
+ }
9993
+ }
9994
+ requireLayer() {
9995
+ if (this.session.kind !== "layer") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "document session is not a layer");
9996
+ }
9997
+ };
9998
+ //#endregion
9999
+ //#region src/features/signature/internal/candidateStore.ts
10000
+ function copyOut(view) {
10001
+ const out = new Uint8Array(view.byteLength);
10002
+ out.set(view);
10003
+ return out;
10004
+ }
10005
+ /** Marshal `EPDFSig_SealSpan` over a span held in the runtime heap. */
10006
+ function sealSpanInHeap(runtime, spanPtr, spanLength, objectOffset, fileLength) {
10007
+ const { fn, mem } = runtime;
10008
+ return withScratchN(mem, [
10009
+ 32,
10010
+ 8,
10011
+ 8
10012
+ ], ([rangePtr, coPtr, chPtr]) => {
10013
+ for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, 0, k * 8);
10014
+ pokeU64(mem, coPtr, 0);
10015
+ pokeU64(mem, chPtr, 0);
10016
+ 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");
10017
+ return {
10018
+ byteRange: [
10019
+ peekU64(mem, rangePtr, 0),
10020
+ peekU64(mem, rangePtr, 8),
10021
+ peekU64(mem, rangePtr, 16),
10022
+ peekU64(mem, rangePtr, 24)
10023
+ ],
10024
+ contentsOffset: peekU64(mem, coPtr),
10025
+ contentsHexLength: peekU64(mem, chPtr)
10026
+ };
10027
+ });
10028
+ }
10029
+ /** Marshal `EPDFSig_WriteContents` over a buffer in the heap whose first byte is file offset `bufferOffset`. */
10030
+ function writeContentsInHeap(runtime, bufPtr, bufferLength, bufferOffset, sealed, cms) {
10031
+ const { fn, mem } = runtime;
10032
+ const cmsPtr = mem.alloc(cms.byteLength);
10033
+ try {
10034
+ mem.writeBytes(cmsPtr, cms);
10035
+ 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");
10036
+ } finally {
10037
+ mem.free(cmsPtr);
10038
+ }
10039
+ }
10040
+ /**
10041
+ * Hex-encode `cms` into the /Contents hole of a candidate FILE, in place.
10042
+ * Only the hole (`<`, the hex digits, `>`) is read and written back; the
10043
+ * delimiters are checked so a wrong offset patches nothing.
10044
+ */
10045
+ function writeContentsIntoFile(runtime, path, hole, cms) {
10046
+ const { mem } = runtime;
10047
+ const span = runtime.fileAccess.readRange(path, hole.holeOffset, hole.hexLength + 2);
10048
+ 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");
10049
+ const spanPtr = mem.alloc(span.byteLength);
10050
+ try {
10051
+ mem.writeBytes(spanPtr, span);
10052
+ writeContentsInHeap(runtime, spanPtr, span.byteLength, hole.holeOffset, {
10053
+ byteRange: [
10054
+ 0,
10055
+ 0,
10056
+ 0,
10057
+ 0
10058
+ ],
10059
+ contentsOffset: hole.holeOffset + 1,
10060
+ contentsHexLength: hole.hexLength,
10061
+ digest: /* @__PURE__ */ new Uint8Array()
10062
+ }, cms);
10063
+ runtime.fileWrite.writeRange(path, hole.holeOffset, mem.readBytes(spanPtr, span.byteLength));
10064
+ } finally {
10065
+ mem.free(spanPtr);
10066
+ }
10067
+ }
10068
+ var MemoryCandidateStore = class {
10069
+ constructor(runtime, baseDocuments) {
10070
+ this.runtime = runtime;
10071
+ this.baseDocuments = baseDocuments;
10072
+ }
10073
+ save(candidateDocPtr, sigObjNum) {
10074
+ const { mem, fn } = this.runtime;
10075
+ return withScratchN(mem, [
10076
+ 8,
10077
+ 8,
10078
+ 8
10079
+ ], ([sizePtr, offPtr, lenPtr]) => {
10080
+ pokeU64(mem, sizePtr, 0);
10081
+ pokeU64(mem, offPtr, 0);
10082
+ pokeU64(mem, lenPtr, 0);
10083
+ const bufPtr = fn.EPDFSig_SaveCandidateToOwnedBuffer(candidateDocPtr, sigObjNum, sizePtr, offPtr, lenPtr);
10084
+ if (!bufPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to save the signing candidate");
10085
+ try {
10086
+ const size = peekU64(mem, sizePtr);
10087
+ return {
10088
+ kind: "memory",
10089
+ bytes: copyOut(mem.readBytes(bufPtr, size)),
10090
+ size,
10091
+ objectOffset: peekU64(mem, offPtr),
10092
+ objectLength: peekU64(mem, lenPtr)
10093
+ };
10094
+ } finally {
10095
+ fn.EPDF_FreeBuffer(bufPtr);
10096
+ }
10097
+ });
10098
+ }
10099
+ seal(saved, algorithm) {
10100
+ if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
10101
+ const { mem, fn } = this.runtime;
10102
+ const bufPtr = mem.alloc(saved.bytes.byteLength);
10103
+ try {
10104
+ mem.writeBytes(bufPtr, saved.bytes);
10105
+ return withScratchN(mem, [
10106
+ 32,
10107
+ 8,
10108
+ 8,
10109
+ 64,
10110
+ 8
10111
+ ], ([rangePtr, coPtr, chPtr, digestPtr, lenPtr]) => {
10112
+ for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, 0, k * 8);
10113
+ pokeU64(mem, coPtr, 0);
10114
+ pokeU64(mem, chPtr, 0);
10115
+ pokeU64(mem, lenPtr, 64);
10116
+ 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");
10117
+ saved.bytes = copyOut(mem.readBytes(bufPtr, saved.bytes.byteLength));
10118
+ const digestLength = Number(mem.peek(lenPtr, "i32"));
10119
+ return {
10120
+ byteRange: [
10121
+ peekU64(mem, rangePtr, 0),
10122
+ peekU64(mem, rangePtr, 8),
10123
+ peekU64(mem, rangePtr, 16),
10124
+ peekU64(mem, rangePtr, 24)
10125
+ ],
10126
+ contentsOffset: peekU64(mem, coPtr),
10127
+ contentsHexLength: peekU64(mem, chPtr),
10128
+ digest: copyOut(mem.readBytes(digestPtr, digestLength))
10129
+ };
10130
+ });
10131
+ } finally {
10132
+ mem.free(bufPtr);
10133
+ }
10134
+ }
10135
+ writeContents(saved, sealed, cms) {
10136
+ if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
10137
+ const { mem } = this.runtime;
10138
+ const bufPtr = mem.alloc(saved.bytes.byteLength);
10139
+ try {
10140
+ mem.writeBytes(bufPtr, saved.bytes);
10141
+ writeContentsInHeap(this.runtime, bufPtr, saved.bytes.byteLength, 0, sealed, cms);
10142
+ saved.bytes = copyOut(mem.readBytes(bufPtr, saved.bytes.byteLength));
10143
+ } finally {
10144
+ mem.free(bufPtr);
10145
+ }
10146
+ }
10147
+ openSealed(saved, password) {
10148
+ if (saved.kind !== "memory") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a memory candidate");
10149
+ const base = this.baseDocuments.acquireMemoryBase({
10150
+ key: `signed:${generateUuid()}`,
10151
+ bytes: saved.bytes,
10152
+ password
10153
+ });
10154
+ return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
10155
+ }
10156
+ discard() {}
10157
+ };
10158
+ /**
10159
+ * A candidate written next to the session's base file, through the
10160
+ * runtime's local-file surface: the base streams through the writer, the
10161
+ * seal patches the object span in place, the digest streams the file, and
10162
+ * the sealed file becomes a file base. Only the object span and the CMS
10163
+ * are ever held in memory. Node runtimes only.
10164
+ */
10165
+ var FileCandidateStore = class {
10166
+ constructor(runtime, baseDocuments, basePath, candidatePath = defaultCandidatePath) {
10167
+ this.runtime = runtime;
10168
+ this.baseDocuments = baseDocuments;
10169
+ this.basePath = basePath;
10170
+ this.candidatePath = candidatePath;
10171
+ }
10172
+ save(candidateDocPtr, sigObjNum, signingId) {
10173
+ const { mem, fn } = this.runtime;
10174
+ const path = this.candidatePath(this.basePath, signingId);
10175
+ this.runtime.fileWrite.removeFile(path);
10176
+ const writer = this.runtime.fileWrite.toNodeFile(path);
10177
+ try {
10178
+ return withScratchN(mem, [
10179
+ 8,
10180
+ 8,
10181
+ 8
10182
+ ], ([sizePtr, offPtr, lenPtr]) => {
10183
+ pokeU64(mem, sizePtr, 0);
10184
+ pokeU64(mem, offPtr, 0);
10185
+ pokeU64(mem, lenPtr, 0);
10186
+ 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");
10187
+ return {
10188
+ kind: "file",
10189
+ path,
10190
+ size: peekU64(mem, sizePtr),
10191
+ objectOffset: peekU64(mem, offPtr),
10192
+ objectLength: peekU64(mem, lenPtr)
10193
+ };
10194
+ });
10195
+ } catch (error) {
10196
+ writer.close();
10197
+ this.runtime.fileWrite.removeFile(path);
10198
+ throw error;
10199
+ } finally {
10200
+ writer.close();
10201
+ }
10202
+ }
10203
+ seal(saved, algorithm) {
10204
+ if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
10205
+ const { mem, fn } = this.runtime;
10206
+ const span = this.runtime.fileAccess.readRange(saved.path, saved.objectOffset, saved.objectLength);
10207
+ 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");
10208
+ const spanPtr = mem.alloc(span.byteLength);
10209
+ let plan;
10210
+ try {
10211
+ mem.writeBytes(spanPtr, span);
10212
+ plan = sealSpanInHeap(this.runtime, spanPtr, span.byteLength, saved.objectOffset, saved.size);
10213
+ this.runtime.fileWrite.writeRange(saved.path, saved.objectOffset, mem.readBytes(spanPtr, span.byteLength));
10214
+ } finally {
10215
+ mem.free(spanPtr);
10216
+ }
10217
+ const access = this.runtime.fileAccess.fromNodeFile(saved.path);
10218
+ try {
10219
+ const digest = withScratchN(mem, [
10220
+ 32,
10221
+ 64,
10222
+ 8
10223
+ ], ([rangePtr, digestPtr, lenPtr]) => {
10224
+ for (let k = 0; k < 4; k++) pokeU64(mem, rangePtr, plan.byteRange[k], k * 8);
10225
+ pokeU64(mem, lenPtr, 64);
10226
+ 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");
10227
+ return copyOut(mem.readBytes(digestPtr, Number(mem.peek(lenPtr, "i32"))));
10228
+ });
10229
+ return {
10230
+ ...plan,
10231
+ digest
10232
+ };
10233
+ } finally {
10234
+ access.close();
10235
+ }
10236
+ }
10237
+ writeContents(saved, sealed, cms) {
10238
+ if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
10239
+ const { mem } = this.runtime;
10240
+ const span = this.runtime.fileAccess.readRange(saved.path, saved.objectOffset, saved.objectLength);
10241
+ const spanPtr = mem.alloc(span.byteLength);
10242
+ try {
10243
+ mem.writeBytes(spanPtr, span);
10244
+ writeContentsInHeap(this.runtime, spanPtr, span.byteLength, saved.objectOffset, sealed, cms);
10245
+ this.runtime.fileWrite.writeRange(saved.path, saved.objectOffset, mem.readBytes(spanPtr, span.byteLength));
10246
+ } finally {
10247
+ mem.free(spanPtr);
10248
+ }
10249
+ }
10250
+ openSealed(saved, password) {
10251
+ if (saved.kind !== "file") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "not a file candidate");
10252
+ const base = this.baseDocuments.acquireFileBase({
10253
+ key: `signed:${saved.path}`,
10254
+ path: saved.path,
10255
+ password
10256
+ });
10257
+ return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
10258
+ }
10259
+ discard(saved) {
10260
+ if (saved.kind === "file") this.runtime.fileWrite.removeFile(saved.path);
10261
+ }
10262
+ };
10263
+ /** Beside the base file, named by the signing; the host may override this (server storage layout). */
10264
+ function defaultCandidatePath(basePath, signingId) {
10265
+ return `${basePath}.signing-${signingId}.pdf`;
10266
+ }
10267
+ /**
10268
+ * A scratch file beside the base: `<base>.<tag>-<id>.<ext>`. Unique per
10269
+ * id, so a file a document holds open is never rewritten; the caller that
10270
+ * names it registers its removal before anything writes it.
10271
+ */
10272
+ function scratchPath(basePath, tag, id, ext) {
10273
+ return `${basePath}.${tag}-${id}.${ext}`;
10274
+ }
10275
+ //#endregion
10276
+ //#region src/features/signature/internal/appearance.ts
10277
+ /** `EPDF_STAMP_FIT_CONTAIN`: uniform scale, letterboxed and centred in the rect. */
10278
+ const FIT_CONTAIN = 0;
10279
+ /**
10280
+ * Draw one page of a PDF into a widget annotation's normal appearance —
10281
+ * what a signature's mark is, whether the field is being signed
10282
+ * (`SignatureMutator`) or only filled visually (`FormMutator`).
10283
+ *
10284
+ * Two fork calls, the same pair the stamp path makes: `SetAppearanceFromPage`
10285
+ * clones the page into the document as a wrapped Form XObject (page content
10286
+ * and resources in a child form, the outer stream a placement matrix), then
10287
+ * `UpdateAppearanceToRect` writes that placement — the page fitted uniformly
10288
+ * into the widget's rect. Without the second call the page sits in its own
10289
+ * box and the viewer's BBox→/Rect mapping stretches it to the rect's aspect.
10290
+ */
10291
+ function bakeWidgetAppearance(runtime, docPtr, widget, pdf, pageIndex) {
10292
+ const { mem, fn } = runtime;
10293
+ const stack = new CloseStack();
10294
+ try {
10295
+ const pagePtr = fn.EPDFDoc_LoadPageByObjectNumber(docPtr, widget.pageObjectNumber);
10296
+ if (!pagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "the widget page could not be loaded");
10297
+ stack.push(() => fn.FPDF_ClosePage(pagePtr));
10298
+ const annotPtr = fn.EPDFPage_GetAnnotByObjectNumber(pagePtr, widget.annotObjectNumber);
10299
+ if (!annotPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, "the signature widget could not be loaded");
10300
+ stack.push(() => fn.FPDFPage_CloseAnnot(annotPtr));
10301
+ const dataPtr = mem.alloc(pdf.byteLength);
10302
+ stack.push(() => mem.free(dataPtr));
10303
+ mem.writeBytes(dataPtr, pdf);
10304
+ const artworkPtr = fn.FPDF_LoadMemDocument64(dataPtr, pdf.byteLength, "");
10305
+ if (!artworkPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "the appearance PDF could not be opened");
10306
+ stack.push(() => fn.FPDF_CloseDocument(artworkPtr));
10307
+ 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");
10308
+ 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");
10309
+ } finally {
10310
+ stack.close();
10311
+ }
10312
+ }
10313
+ //#endregion
10314
+ //#region src/features/signature/internal/sealCheck.ts
10315
+ /**
10316
+ * The one check both signing paths make before sealed bytes become a
10317
+ * version — the in-session complete and the server's session-less
10318
+ * finalize. Reading the result back through the ordinary signature model
10319
+ * proves the bytes, not the writer: the field is signed, its signature
10320
+ * covers the whole LAST revision (the candidate's own, never an earlier
10321
+ * one), its /ByteRange is the one the digest was computed over, and the
10322
+ * /Contents decode to exactly the CMS installed. Anything else is refused
10323
+ * and the bytes are never installed.
10324
+ */
10325
+ function assertSealedSignature(runtime, docPtr, model, expected) {
10326
+ const refuse = (why) => new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `the sealed bytes ${why}`);
10327
+ const signatures = readSignaturesFromModel(runtime, model);
10328
+ const signature = signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === expected.fieldObjectNumber);
10329
+ if (!signature) throw refuse("lost the signature field");
10330
+ if (!signature.signed || signature.coverage !== "whole-revision" || !signature.byteRange) throw refuse("do not carry a whole-revision signature on the field");
10331
+ if (!sameRange(signature.byteRange, expected.byteRange)) throw refuse(`carry /ByteRange [${signature.byteRange.join(" ")}] where [${expected.byteRange.join(" ")}] was sealed`);
10332
+ if (!runtime.fn.EPDFSig_IsRevisionChainValid(model)) throw refuse("have a broken cross-reference chain");
10333
+ const revisions = readRevisions(runtime, docPtr, signatures);
10334
+ if (signature.revisionIndex !== revisions.length - 1) throw refuse("seal an earlier revision than the candidate");
10335
+ if (!bytesEqual(readContentsAt(runtime, model, signature.index), expected.cms)) throw refuse("carry /Contents that are not the CMS installed");
10336
+ return signature;
10337
+ }
10338
+ function sameRange(a, b) {
10339
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
10340
+ }
10341
+ function bytesEqual(a, b) {
10342
+ if (a.byteLength !== b.byteLength) return false;
10343
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
10344
+ return true;
10345
+ }
10346
+ //#endregion
10347
+ //#region src/features/forms/internal/wideStringArray.ts
10348
+ /**
10349
+ * Marshal a `const FPDF_WIDESTRING*` array: encode every string into the
10350
+ * runtime heap, write the pointer table with the runtime's pointer width
10351
+ * (bigint pointers cannot be poked into the wasm 32-bit heap), run |body|,
10352
+ * free everything.
10353
+ */
10354
+ function withWideStringArray(runtime, values, body) {
10355
+ const { mem } = runtime;
10356
+ const stringPtrs = values.map((value) => mem.writeU16String(value));
10357
+ const isWasm = runtime.kind === "wasm";
10358
+ const ptrSize = isWasm ? 4 : 8;
10359
+ try {
10360
+ return withScratch(mem, Math.max(1, stringPtrs.length * ptrSize), (arrayPtr) => {
10361
+ stringPtrs.forEach((ptr, i) => isWasm ? mem.poke(arrayPtr, "i32", Number(ptr), i * 4) : mem.poke(arrayPtr, "i64", ptr, i * 8));
10362
+ return body(arrayPtr, stringPtrs.length);
10363
+ });
10364
+ } finally {
10365
+ for (let i = stringPtrs.length - 1; i >= 0; i--) mem.free(stringPtrs[i]);
10366
+ }
10367
+ }
10368
+ //#endregion
10369
+ //#region src/features/signature/SignatureMutator.ts
10370
+ const SUBFILTER_CODE = {
10371
+ "adbe.pkcs7.detached": 0,
10372
+ "ETSI.CAdES.detached": 1,
10373
+ "ETSI.RFC3161": 2
10374
+ };
10375
+ const FIELD_ACTION_CODE = {
10376
+ all: 1,
10377
+ include: 2,
10378
+ exclude: 3
10379
+ };
10380
+ const DEFAULT_CONTENTS_SIZE = 8192;
10381
+ const MIN_CONTENTS_SIZE = 256;
10382
+ const MAX_CONTENTS_SIZE = 4 * 1024 * 1024;
10383
+ const PREPARE_ILP32 = {
10384
+ bytes: 52,
10385
+ ptrBytes: 4,
10386
+ subfilter: 0,
10387
+ digest: 4,
10388
+ contentsSize: 8,
10389
+ name: 12,
10390
+ reason: 16,
10391
+ location: 20,
10392
+ contactInfo: 24,
10393
+ signingTime: 28,
10394
+ docmdpPermission: 32,
10395
+ fieldmdpAction: 36,
10396
+ fieldmdpFields: 40,
10397
+ fieldmdpFieldCount: 44,
10398
+ lockPermission: 48
10399
+ };
10400
+ const PREPARE_LP64 = {
10401
+ bytes: 80,
10402
+ ptrBytes: 8,
10403
+ subfilter: 0,
10404
+ digest: 4,
10405
+ contentsSize: 8,
10406
+ name: 16,
10407
+ reason: 24,
10408
+ location: 32,
10409
+ contactInfo: 40,
10410
+ signingTime: 48,
10411
+ docmdpPermission: 56,
10412
+ fieldmdpAction: 60,
10413
+ fieldmdpFields: 64,
10414
+ fieldmdpFieldCount: 72,
10415
+ lockPermission: 76
10416
+ };
10417
+ /**
10418
+ * The two-phase signing protocol on a session, local and native alike.
10419
+ *
10420
+ * `prepare` never touches the live document: it builds a CANDIDATE — a
10421
+ * fresh layer over the session's own immutable base (see `openCandidate`)
10422
+ * — writes the signature there, saves it through a `CandidateStore`
10423
+ * (memory locally, a file beside the base on file-backed sessions), seals
10424
+ * it, and parks the saved candidate on the session. `complete` writes the
10425
+ * CMS into it, opens it as a new base with a fresh layer, proves the new
10426
+ * signature seals a whole revision, and installs it as the session's
10427
+ * document. `abort` discards the candidate.
10428
+ */
10429
+ var SignatureMutator = class {
10430
+ constructor(runtime, session, baseDocuments, candidatePath) {
10431
+ this.runtime = runtime;
10432
+ this.session = session;
10433
+ this.baseDocuments = baseDocuments;
10434
+ this.candidatePath = candidatePath;
10435
+ }
10436
+ prepare(input) {
10437
+ 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`);
10438
+ const reader = new SignatureReader(this.runtime, this.session);
10439
+ const field = reader.resolveField(input.field);
10440
+ if (field.signed) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, "the signature field is already signed");
10441
+ 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");
10442
+ const algorithm = input.digest ?? "sha256";
10443
+ if (!(algorithm in DIGEST_CODE) || algorithm === "sha1") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `unsupported digest '${algorithm}'`);
10444
+ const subFilter = (input.kind ?? "signature") === "timestamp" ? "ETSI.RFC3161" : input.subFilter ?? "ETSI.CAdES.detached";
10445
+ if (!(subFilter in SUBFILTER_CODE)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `unsupported subFilter '${subFilter}'`);
10446
+ const contentsSize = input.contentsSize ?? DEFAULT_CONTENTS_SIZE;
10447
+ 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}]`);
10448
+ const expectedVersion = this.session.versionRef(reader.version().sha256);
10449
+ const signingId = generateUuid();
10450
+ const stack = new CloseStack();
10451
+ try {
10452
+ const candidate = this.openCandidate(signingId, stack);
10453
+ stack.push(() => candidate.close());
10454
+ const attribution = input.attribution ?? input.signer;
10455
+ const valueObjNum = this.callPrepare(candidate.docPtr, field.fieldObjectNumber, {
10456
+ subfilter: SUBFILTER_CODE[subFilter],
10457
+ digest: DIGEST_CODE[algorithm],
10458
+ contentsSize,
10459
+ name: attribution?.name ?? null,
10460
+ reason: attribution?.reason ?? null,
10461
+ location: attribution?.location ?? null,
10462
+ contactInfo: attribution?.contactInfo ?? null,
10463
+ signingTime: input.signingTime ?? null,
10464
+ docmdpPermission: input.certify?.permission ?? 0,
10465
+ fieldmdpAction: input.lock ? FIELD_ACTION_CODE[input.lock.action] : 0,
10466
+ fieldmdpFields: input.lock?.action === "all" ? [] : input.lock?.fields ?? [],
10467
+ lockPermission: input.lock?.permission ?? 0
10468
+ });
10469
+ 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)");
10470
+ if (input.appearance && field.widget) bakeWidgetAppearance(this.runtime, candidate.docPtr, field.widget, input.appearance.pdf, input.appearance.pageIndex ?? 0);
10471
+ const store = this.storeFor();
10472
+ const saved = store.save(candidate.docPtr, valueObjNum, signingId);
10473
+ let sealed;
10474
+ try {
10475
+ sealed = store.seal(saved, algorithm);
10476
+ } catch (error) {
10477
+ store.discard(saved);
10478
+ throw error;
10479
+ }
10480
+ const prepared = {
10481
+ signingId,
10482
+ digest: sealed.digest,
10483
+ algorithm,
10484
+ byteRange: sealed.byteRange,
10485
+ contentsSize,
10486
+ subFilter,
10487
+ expectedVersion,
10488
+ expiresAt: null
10489
+ };
10490
+ this.session.pendingSigning = {
10491
+ prepared,
10492
+ fieldObjectNumber: field.fieldObjectNumber,
10493
+ saved,
10494
+ contentsOffset: sealed.contentsOffset,
10495
+ contentsHexLength: sealed.contentsHexLength
10496
+ };
10497
+ return prepared;
10498
+ } finally {
10499
+ stack.close();
10500
+ }
10501
+ }
10502
+ complete(input) {
10503
+ const pending = this.session.pendingSigning;
10504
+ if (!pending || pending.prepared.signingId !== input.signingId) {
10505
+ const last = this.session.lastCompletion;
10506
+ if (last && last.signingId === input.signingId) {
10507
+ 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");
10508
+ return {
10509
+ ...last.result,
10510
+ status: "already-completed"
10511
+ };
10512
+ }
10513
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no pending signing '${input.signingId}'`);
10514
+ }
10515
+ 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");
10516
+ 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");
10517
+ 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`);
10518
+ const store = this.storeFor();
10519
+ const sealed = {
10520
+ byteRange: pending.prepared.byteRange,
10521
+ contentsOffset: pending.contentsOffset,
10522
+ contentsHexLength: pending.contentsHexLength,
10523
+ digest: pending.prepared.digest
10524
+ };
10525
+ store.writeContents(pending.saved, sealed, input.cms);
10526
+ const handle = store.openSealed(pending.saved, this.session.password);
10527
+ this.verifySealed(handle, {
10528
+ fieldObjectNumber: pending.fieldObjectNumber,
10529
+ byteRange: pending.prepared.byteRange,
10530
+ cms: input.cms
10531
+ });
10532
+ this.session.install(handle);
10533
+ disposeFormModel(this.runtime, this.session);
10534
+ disposeSignatureModel(this.runtime, this.session);
10535
+ const reader = new SignatureReader(this.runtime, this.session);
10536
+ const snapshot = reader.readSnapshot();
10537
+ const signature = snapshot.signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === pending.fieldObjectNumber);
10538
+ if (!signature) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the installed document lost the signature field");
10539
+ const result = {
10540
+ status: "completed",
10541
+ signature,
10542
+ version: reader.version(),
10543
+ previous: pending.prepared.expectedVersion,
10544
+ protection: snapshot.protection,
10545
+ meta: {
10546
+ affectedPages: this.session.allRecords().map((r) => this.session.pageState(r.pageObjectNumber)),
10547
+ cacheDelta: null
10548
+ }
10549
+ };
10550
+ this.session.lastCompletion = {
10551
+ signingId: input.signingId,
10552
+ cms: input.cms.slice(),
10553
+ result
10554
+ };
10555
+ return result;
10556
+ }
10557
+ abort(signingId) {
10558
+ const pending = this.session.pendingSigning;
10559
+ if (pending && pending.prepared.signingId === signingId) {
10560
+ this.session.pendingSigning = null;
10561
+ this.storeFor().discard(pending.saved);
10562
+ return { status: "aborted" };
10563
+ }
10564
+ if (this.session.lastCompletion?.signingId === signingId) return { status: "already-completed" };
10565
+ return { status: "unknown" };
10566
+ }
10567
+ callPrepare(docPtr, fieldObjectNumber, opts) {
10568
+ const { mem, fn } = this.runtime;
10569
+ const layout = this.runtime.kind === "wasm" ? PREPARE_ILP32 : PREPARE_LP64;
10570
+ const owned = [];
10571
+ const wide = (value) => {
10572
+ if (value === null) return _embedpdf_engine_runtime.NULL_PTR;
10573
+ const ptr = mem.writeU16String(value);
10574
+ owned.push(ptr);
10575
+ return ptr;
10576
+ };
10577
+ const utf8 = (value) => {
10578
+ if (value === null) return _embedpdf_engine_runtime.NULL_PTR;
10579
+ const ptr = mem.writeU8String(value);
10580
+ owned.push(ptr);
10581
+ return ptr;
10582
+ };
10583
+ try {
10584
+ return withWideStringArray(this.runtime, opts.fieldmdpFields, (fieldsPtr, fieldCount) => withScratch(mem, layout.bytes, (structPtr) => {
10585
+ for (let off = 0; off < layout.bytes; off += 4) mem.poke(structPtr, "i32", 0, off);
10586
+ const pokePtr = (offset, ptr) => {
10587
+ if (layout.ptrBytes === 4) mem.poke(structPtr, "i32", Number(ptr), offset);
10588
+ else mem.poke(structPtr, "i64", ptr, offset);
10589
+ };
10590
+ mem.poke(structPtr, "i32", opts.subfilter, layout.subfilter);
10591
+ mem.poke(structPtr, "i32", opts.digest, layout.digest);
10592
+ mem.poke(structPtr, "i32", opts.contentsSize, layout.contentsSize);
10593
+ pokePtr(layout.name, wide(opts.name));
10594
+ pokePtr(layout.reason, wide(opts.reason));
10595
+ pokePtr(layout.location, wide(opts.location));
10596
+ pokePtr(layout.contactInfo, wide(opts.contactInfo));
10597
+ pokePtr(layout.signingTime, utf8(opts.signingTime));
10598
+ mem.poke(structPtr, "i32", opts.docmdpPermission, layout.docmdpPermission);
10599
+ mem.poke(structPtr, "i32", opts.fieldmdpAction, layout.fieldmdpAction);
10600
+ pokePtr(layout.fieldmdpFields, fieldCount > 0 ? fieldsPtr : _embedpdf_engine_runtime.NULL_PTR);
10601
+ mem.poke(structPtr, "i32", fieldCount, layout.fieldmdpFieldCount);
10602
+ mem.poke(structPtr, "i32", opts.lockPermission, layout.lockPermission);
10603
+ return fn.EPDFSig_Prepare(docPtr, fieldObjectNumber, structPtr);
10604
+ }));
10605
+ } finally {
10606
+ for (let i = owned.length - 1; i >= 0; i--) mem.free(owned[i]);
10607
+ }
10608
+ }
10609
+ /** Draw a page of `pdf` into the widget's appearance stream on the candidate. */
10610
+ /**
10611
+ * File-backed sessions (a file base in the registry: the server) persist
10612
+ * the candidate as a file beside the base; everything else keeps it in
10613
+ * memory. The choice follows the SESSION's base, not the candidate's.
10614
+ */
10615
+ storeFor() {
10616
+ const base = this.session.source.base;
10617
+ if (base?.kind === "file") return new FileCandidateStore(this.runtime, this.baseDocuments, base.path, this.candidatePath);
10618
+ return new MemoryCandidateStore(this.runtime, this.baseDocuments);
10619
+ }
10620
+ /**
10621
+ * Prove the sealed bytes carry a whole-revision signature on the field
10622
+ * before anything is installed; closes the handle on failure.
10623
+ */
10624
+ /**
10625
+ * Read the sealed candidate back before it is installed: the checks in
10626
+ * `assertSealedSignature` are made on a model loaded from the sealed
10627
+ * bytes themselves, so a candidate the writer got wrong never becomes
10628
+ * the session's bytes.
10629
+ */
10630
+ verifySealed(handle, expected) {
10631
+ const { fn } = this.runtime;
10632
+ try {
10633
+ const model = fn.EPDFSig_LoadModel(handle.docPtr);
10634
+ if (!model) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "the sealed bytes have no signature model");
10635
+ try {
10636
+ assertSealedSignature(this.runtime, handle.docPtr, model, expected);
10637
+ } finally {
10638
+ fn.EPDFSig_CloseModel(model);
10639
+ }
10640
+ if (this.session.kind !== "layer") this.session.persistLayerArtifact = false;
10641
+ } catch (error) {
10642
+ handle.close();
10643
+ throw error;
10644
+ }
10645
+ }
10646
+ /**
10647
+ * The candidate the signature is authored on. A layer session reopens a
10648
+ * fresh layer over ITS OWN immutable base (a registry retain, no copy of
10649
+ * the file), fed the layer it was opened with — or, with unsaved edits,
10650
+ * the artifact a save would write, which is cumulative: the edits and
10651
+ * the signature then share one revision, as Acrobat saves a
10652
+ * fill-and-sign. The fallback freezes the complete loaded bytes as a new
10653
+ * memory base: for a plain session, and for a layer whose loaded delta
10654
+ * already holds signed bytes (a layer save rewrites the delta and would
10655
+ * drop them; the fork refuses to prepare such a candidate).
10656
+ */
10657
+ openCandidate(signingId, stack) {
10658
+ const password = this.session.password;
10659
+ const source = this.session.source;
10660
+ const saver = new DocumentSaver(this.runtime, this.session);
10661
+ const scratch = saver.canWriteScratchFiles() && source.base?.kind === "file" ? source.base.path : null;
10662
+ if (this.session.kind === "layer" && source.base && !this.loadedDeltaHoldsSignedBytes()) {
10663
+ const base = this.baseDocuments.retainByKey(source.base.key);
10664
+ if (base) {
10665
+ let layer = source.layer ?? { kind: "fresh" };
10666
+ if (this.session.hasUnsavedEdits()) if (scratch) {
10667
+ const path = scratchPath(scratch, "signing", signingId, "layer");
10668
+ stack.push(() => this.runtime.fileWrite.removeFile(path));
10669
+ if (saver.saveLayerArtifactToFileEx(path).changedSinceLoad) layer = {
10670
+ kind: "artifact-file",
10671
+ path
10672
+ };
10673
+ } else {
10674
+ const artifact = saver.saveLayerArtifactEx();
10675
+ if (artifact.changedSinceLoad) layer = {
10676
+ kind: "artifact",
10677
+ bytes: artifact.bytes
10678
+ };
10679
+ }
10680
+ return openLayerDocument(this.runtime, base, layer, password);
10681
+ }
10682
+ }
10683
+ if (scratch) {
10684
+ const path = scratchPath(scratch, "signing", signingId, "base.pdf");
10685
+ stack.push(() => this.runtime.fileWrite.removeFile(path));
10686
+ saver.snapshotToFile(path);
10687
+ const base = this.baseDocuments.acquireFileBase({
10688
+ key: `candidate:${signingId}`,
10689
+ path,
10690
+ password
10691
+ });
10692
+ return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
10693
+ }
10694
+ const candidateBytes = saver.snapshot().bytes;
10695
+ const base = this.baseDocuments.acquireMemoryBase({
10696
+ key: `candidate:${signingId}`,
10697
+ bytes: new Uint8Array(candidateBytes),
10698
+ password
10699
+ });
10700
+ return openLayerDocument(this.runtime, base, { kind: "fresh" }, password);
10701
+ }
10702
+ /** Whether a signature's byte range reaches into the layer's loaded delta (past the base). */
10703
+ loadedDeltaHoldsSignedBytes() {
10704
+ const layer = this.session.source.layer;
10705
+ if (!layer || layer.kind === "fresh") return false;
10706
+ const baseSize = Number(this.runtime.fn.EPDFDoc_GetBaseBytesSize(this.session.requireDocPtr()));
10707
+ return new SignatureReader(this.runtime, this.session).readSnapshot().signatures.some((s) => s.signed && s.byteRange !== null && s.byteRange[2] + s.byteRange[3] > baseSize);
10708
+ }
10709
+ };
10710
+ function sameVersion(a, b) {
10711
+ return a.baseSha256 === b.baseSha256 && a.editsVersion === b.editsVersion;
10712
+ }
10713
+ //#endregion
10714
+ //#region src/features/signature/SignatureAnalyzer.ts
10715
+ const CHANGE_BY_CODE = {
10716
+ 0: "added",
10717
+ 1: "modified",
10718
+ 2: "freed"
10719
+ };
10720
+ const KIND_BY_CODE = {
10721
+ 0: "dictionary",
10722
+ 1: "stream",
10723
+ 2: "array",
10724
+ 3: "scalar",
10725
+ 4: "xref",
10726
+ 5: "objstm",
10727
+ 6: "trailer"
10728
+ };
10729
+ const DIFF_OLD = 0;
10730
+ const DIFF_NEW = 1;
10731
+ /** EPDF_DIFF_READ_OK / FAILED / ABSENT. */
10732
+ const READ_BY_CODE = {
10733
+ 0: "ok",
10734
+ 1: "failed",
10735
+ 2: "absent"
10736
+ };
10737
+ /** The objects an edge may be anchored at: the trailer, the structure of the revision, and every changed object. */
10738
+ function anchorSet(s, changed) {
10739
+ const out = /* @__PURE__ */ new Set([
10740
+ 0,
10741
+ s.root,
10742
+ s.pagesRoot,
10743
+ ...s.pages,
10744
+ ...changed
10745
+ ]);
10746
+ if (s.acroForm) out.add(s.acroForm);
10747
+ for (const f of s.fields) {
10748
+ out.add(f.objectNumber);
10749
+ for (const w of f.widgets) out.add(w);
10750
+ }
10751
+ return out;
10752
+ }
10753
+ /**
10754
+ * Modification analysis over a session: which revisions to compare, the
10755
+ * fork's exact object diff for each adjacent pair, each revision's
10756
+ * structure, and the pure rule engine's verdict per step. Every revision
10757
+ * is opened as its own prefix document from the target's loaded bytes;
10758
+ * `working-copy` first snapshots the session the way a save would.
10759
+ */
10760
+ var SignatureAnalyzer = class {
10761
+ constructor(runtime, session) {
10762
+ this.runtime = runtime;
10763
+ this.session = session;
10764
+ }
10765
+ /**
10766
+ * The signature snapshot of the session's working copy: the unsaved
10767
+ * state snapshotted the way a save would write it, as one more revision
10768
+ * over the loaded bytes. Identical to the loaded-bytes snapshot when
10769
+ * nothing is unsaved.
10770
+ */
10771
+ readWorkingCopySnapshot() {
10772
+ const stack = new CloseStack();
10773
+ try {
10774
+ const copy = this.openWorkingCopy(stack);
10775
+ if (copy.source === "loaded") return new SignatureReader(this.runtime, this.session).readSnapshot();
10776
+ const target = copy.target;
10777
+ return withSignatureModel(this.runtime, target, (model) => {
10778
+ const chainValid = this.runtime.fn.EPDFSig_IsRevisionChainValid(model);
10779
+ const signatures = readSignaturesFromModel(this.runtime, model);
10780
+ return {
10781
+ chainValid,
10782
+ revisions: chainValid ? readRevisions(this.runtime, target, signatures) : [],
10783
+ signatures,
10784
+ protection: (0, _embedpdf_engine_core_runtime.deriveProtection)(signatures)
10785
+ };
10786
+ });
10787
+ } finally {
10788
+ stack.close();
10789
+ }
10790
+ }
10791
+ analyze(input) {
10792
+ const reader = new SignatureReader(this.runtime, this.session);
10793
+ const snapshot = reader.readSnapshot();
10794
+ const version = reader.version();
10795
+ const mode = input.exploratoryLevel ? "exploratory" : "authoritative";
10796
+ const detail = input.detail ?? "summary";
10797
+ let basisSource = "persisted";
10798
+ const editsVersion = this.session.mutationSeq();
10799
+ let sinceRevision;
10800
+ let sinceSignature = null;
10801
+ if ("signatureIndex" in input.since) {
10802
+ const sig = snapshot.signatures[input.since.signatureIndex];
10803
+ if (!sig) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature ${input.since.signatureIndex}`);
10804
+ 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`);
10805
+ sinceRevision = sig.revisionIndex;
10806
+ sinceSignature = sig.index;
10807
+ } else sinceRevision = input.since.revisionIndex;
10808
+ if (!snapshot.chainValid) {
10809
+ const current = {
10810
+ verdict: "indeterminate",
10811
+ complete: false,
10812
+ findings: [{
10813
+ rule: "revision-chain",
10814
+ verdict: "incomplete",
10815
+ objectNumber: 0,
10816
+ detail: "the cross-reference chain is broken or was rebuilt: no revision can be told from another"
10817
+ }],
10818
+ method: "net-state"
10819
+ };
10820
+ return {
10821
+ mode,
10822
+ policyVersion: _embedpdf_engine_core_runtime.SIGNATURE_POLICY_VERSION,
10823
+ basis: {
10824
+ version,
10825
+ editsVersion,
10826
+ source: "persisted"
10827
+ },
10828
+ since: {
10829
+ revisionIndex: sinceRevision,
10830
+ signatureIndex: sinceSignature
10831
+ },
10832
+ until: { revisionIndex: sinceRevision },
10833
+ restrictions: [],
10834
+ current: {
10835
+ ...current,
10836
+ primary: current.findings[0]
10837
+ },
10838
+ later: {
10839
+ revisionCount: 0,
10840
+ undoneObjectNumbers: []
10841
+ },
10842
+ verdict: "indeterminate",
10843
+ steps: []
10844
+ };
10845
+ }
10846
+ 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}`);
10847
+ const stack = new CloseStack();
10848
+ try {
10849
+ let target = this.session.requireDocPtr();
10850
+ let revisions = snapshot.revisions;
10851
+ const copy = input.until === "working-copy" ? this.openWorkingCopy(stack) : null;
10852
+ if (copy && copy.source === "working-copy") {
10853
+ basisSource = "working-copy";
10854
+ target = copy.target;
10855
+ const signatures = withSignatureModel(this.runtime, target, (m) => readSignaturesFromModel(this.runtime, m));
10856
+ revisions = readRevisions(this.runtime, target, signatures);
10857
+ 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");
10858
+ }
10859
+ let untilRevision = revisions.length - 1;
10860
+ if (typeof input.until === "object") {
10861
+ untilRevision = input.until.revisionIndex;
10862
+ 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}`);
10863
+ }
10864
+ const pair = new CloseStack();
10865
+ let before;
10866
+ let after;
10867
+ let changes = [];
10868
+ let health;
10869
+ try {
10870
+ const sealed = this.openPrefix(target, revisions, sinceRevision, pair);
10871
+ const judged = untilRevision === revisions.length - 1 ? target : this.openPrefix(target, revisions, untilRevision, pair);
10872
+ before = readStructure(this.runtime, sealed);
10873
+ after = untilRevision === sinceRevision ? before : readStructure(this.runtime, judged);
10874
+ if (untilRevision > sinceRevision) ({changes, health} = this.compare(sealed, judged, before, after));
10875
+ } finally {
10876
+ pair.close();
10877
+ }
10878
+ const judgedSignature = sinceSignature === null ? null : before.signatures[sinceSignature] ?? null;
10879
+ const restrictions = (0, _embedpdf_engine_core_runtime.restrictionsFor)(before, judgedSignature);
10880
+ const levelOverride = input.exploratoryLevel;
10881
+ const net = (0, _embedpdf_engine_core_runtime.evaluateStep)({
10882
+ older: sinceRevision,
10883
+ newer: untilRevision,
10884
+ changes,
10885
+ before,
10886
+ after,
10887
+ health,
10888
+ restrictions: {
10889
+ level: restrictions.level,
10890
+ locks: restrictions.locks
10891
+ },
10892
+ levelOverride
10893
+ });
10894
+ const certifies = restrictions.anchors.some((r) => r.source === "docmdp" && r.own);
10895
+ const intermediate = untilRevision - sinceRevision;
10896
+ let steps;
10897
+ if (intermediate <= 1) steps = intermediate === 1 ? [net] : [];
10898
+ else if (certifies || detail === "full") steps = this.walkSteps(target, revisions, sinceRevision, untilRevision, before, {
10899
+ level: restrictions.level,
10900
+ locks: restrictions.locks,
10901
+ levelOverride
10902
+ });
10903
+ else steps = [];
10904
+ const current = certifies ? {
10905
+ ...(0, _embedpdf_engine_core_runtime.combine)(net, steps),
10906
+ method: "net-state+replay"
10907
+ } : {
10908
+ ...(0, _embedpdf_engine_core_runtime.assessmentOf)(net),
10909
+ method: "net-state"
10910
+ };
10911
+ return {
10912
+ mode,
10913
+ policyVersion: _embedpdf_engine_core_runtime.SIGNATURE_POLICY_VERSION,
10914
+ basis: {
10915
+ version,
10916
+ editsVersion,
10917
+ source: basisSource
10918
+ },
10919
+ since: {
10920
+ revisionIndex: sinceRevision,
10921
+ signatureIndex: sinceSignature
10922
+ },
10923
+ until: { revisionIndex: untilRevision },
10924
+ restrictions: restrictions.anchors,
10925
+ current,
10926
+ later: {
10927
+ revisionCount: intermediate,
10928
+ undoneObjectNumbers: changes.filter(_embedpdf_engine_core_runtime.sameEffectiveValue).map((c) => c.objectNumber)
10929
+ },
10930
+ verdict: current.verdict,
10931
+ steps
10932
+ };
10933
+ } finally {
10934
+ stack.close();
10935
+ }
10936
+ }
10937
+ /** Revision `index` of `target`'s bytes as its own document, owned by `stack`. */
10938
+ openPrefix(target, revisions, index, stack) {
10939
+ const prefix = this.runtime.fn.EPDFDoc_OpenRevision(target, BigInt(revisions[index].end));
10940
+ 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`);
10941
+ stack.push(() => this.runtime.fn.FPDF_CloseDocument(prefix));
10942
+ return prefix;
10943
+ }
10944
+ /**
10945
+ * Every pairwise step between `since` and `until`, two prefix documents
10946
+ * open at a time whatever the revision count. `sealedStructure` is the
10947
+ * structure of `since` (already read); every later structure is read once
10948
+ * and dropped with its document.
10949
+ */
10950
+ walkSteps(target, revisions, since, until, sealedStructure, restrictions) {
10951
+ const { fn } = this.runtime;
10952
+ const steps = [];
10953
+ let older = this.openPrefix(target, revisions, since, new CloseStack());
10954
+ let olderStructure = sealedStructure;
10955
+ try {
10956
+ for (let r = since + 1; r <= until; r++) {
10957
+ const newer = r === revisions.length - 1 ? target : this.openPrefix(target, revisions, r, new CloseStack());
10958
+ try {
10959
+ const newerStructure = readStructure(this.runtime, newer);
10960
+ const { changes, health } = this.compare(older, newer, olderStructure, newerStructure);
10961
+ steps.push((0, _embedpdf_engine_core_runtime.evaluateStep)({
10962
+ older: r - 1,
10963
+ newer: r,
10964
+ changes,
10965
+ before: olderStructure,
10966
+ after: newerStructure,
10967
+ health,
10968
+ restrictions: {
10969
+ level: restrictions.level,
10970
+ locks: restrictions.locks
10971
+ },
10972
+ levelOverride: restrictions.levelOverride
10973
+ }));
10974
+ olderStructure = newerStructure;
10975
+ } finally {
10976
+ fn.FPDF_CloseDocument(older);
10977
+ older = newer;
10978
+ }
10979
+ }
10980
+ } finally {
10981
+ if (older !== target) fn.FPDF_CloseDocument(older);
10982
+ }
10983
+ return steps;
10984
+ }
10985
+ /**
10986
+ * The document as a save would write it, opened read-only, and whose
10987
+ * bytes that is. `loaded`: nothing was mutated since load, or the saver's
10988
+ * pass found no reachable object that differs from its loaded version —
10989
+ * the session's own document is the working copy. `working-copy`: a
10990
+ * layer session composes its immutable base with the cumulative delta
10991
+ * the pass emits (`EPDFDoc_OpenBaseOverlay`: no copy of the base, one
10992
+ * cross-reference parse, the loaded delta's revision REPLACED, as an
10993
+ * artifact is), or — when every edit brought the layer back to its base
10994
+ * — a fresh layer over that base; a plain session (unsigned by law 9, so
10995
+ * this is exploratory) materialises a standalone incremental save and
10996
+ * opens it whole.
10997
+ */
10998
+ openWorkingCopy(stack) {
10999
+ const { fn, mem } = this.runtime;
11000
+ const docPtr = this.session.requireDocPtr();
11001
+ if (!this.session.hasUnsavedEdits()) return {
11002
+ target: docPtr,
11003
+ source: "loaded"
11004
+ };
11005
+ const saver = new DocumentSaver(this.runtime, this.session);
11006
+ if (this.session.kind === "layer") {
11007
+ const base = this.session.source.base;
11008
+ if (saver.canWriteScratchFiles() && base?.kind === "file") {
11009
+ const path = scratchPath(base.path, "working-copy", generateUuid(), "delta");
11010
+ stack.push(() => this.runtime.fileWrite.removeFile(path));
11011
+ if (!saver.saveLayerDeltaToFileEx(path).changedSinceLoad) return {
11012
+ target: docPtr,
11013
+ source: "loaded"
11014
+ };
11015
+ if (this.runtime.fileAccess.sizeOf(path) === 0) return this.freshLayerOverBase(stack);
11016
+ const overlay = withUtf8CString(mem, path, (pathPtr) => fn.EPDFDoc_OpenBaseOverlayFromPath(docPtr, pathPtr));
11017
+ 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");
11018
+ stack.push(() => fn.FPDF_CloseDocument(overlay));
11019
+ return {
11020
+ target: overlay,
11021
+ source: "working-copy"
11022
+ };
11023
+ }
11024
+ const delta = saver.saveLayerDeltaEx();
11025
+ if (!delta.changedSinceLoad) return {
11026
+ target: docPtr,
11027
+ source: "loaded"
11028
+ };
11029
+ if (delta.size === 0) return this.freshLayerOverBase(stack);
11030
+ const bytes = new Uint8Array(delta.bytes);
11031
+ const ptr = mem.alloc(bytes.byteLength);
11032
+ try {
11033
+ mem.writeBytes(ptr, bytes);
11034
+ const overlay = fn.EPDFDoc_OpenBaseOverlay(docPtr, ptr, bytes.byteLength);
11035
+ 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");
11036
+ stack.push(() => fn.FPDF_CloseDocument(overlay));
11037
+ return {
11038
+ target: overlay,
11039
+ source: "working-copy"
11040
+ };
11041
+ } finally {
11042
+ mem.free(ptr);
11043
+ }
11044
+ }
11045
+ const saved = saver.saveStandaloneToBufferEx("incremental");
11046
+ if (saved.unchangedSinceLoad) return {
11047
+ target: docPtr,
11048
+ source: "loaded"
11049
+ };
11050
+ const opened = openFatMemoryDocument(this.runtime, new Uint8Array(saved.bytes), this.session.password);
11051
+ stack.push(() => opened.close());
11052
+ return {
11053
+ target: opened.docPtr,
11054
+ source: "working-copy"
11055
+ };
11056
+ }
11057
+ /** Changed since load, nothing to write: the layer equals its base, and a fresh layer over it IS that document. */
11058
+ freshLayerOverBase(stack) {
11059
+ const { fn, mem } = this.runtime;
11060
+ const base = fn.EPDFLayer_GetBaseDocument(this.session.requireDocPtr());
11061
+ const statusPtr = mem.alloc(4);
11062
+ try {
11063
+ mem.poke(statusPtr, "i32", -1);
11064
+ const fresh = fn.EPDFLayer_OpenLayer(base, _embedpdf_engine_runtime.NULL_PTR, this.session.password ?? "", statusPtr);
11065
+ 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");
11066
+ stack.push(() => fn.FPDF_CloseDocument(fresh));
11067
+ return {
11068
+ target: fresh,
11069
+ source: "working-copy"
11070
+ };
11071
+ } finally {
11072
+ mem.free(statusPtr);
11073
+ }
11074
+ }
11075
+ /**
11076
+ * The fork's exact object diff between two revision documents,
11077
+ * marshalled: values in pass one, anchored edges in pass two (the anchors
11078
+ * are the changed objects plus each side's structural objects).
11079
+ */
11080
+ compare(older, newer, before, after) {
11081
+ const { fn, mem } = this.runtime;
11082
+ const diff = fn.EPDFDoc_CompareRevisions(older, newer);
11083
+ 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");
11084
+ try {
11085
+ const count = fn.EPDFObjectDiff_GetCount(diff);
11086
+ const changes = [];
11087
+ withScratchN(mem, [
11088
+ 4,
11089
+ 4,
11090
+ 4,
11091
+ 4,
11092
+ 4,
11093
+ 4
11094
+ ], ([numPtr, changePtr, kindPtr, oldGenPtr, newGenPtr, streamPtr]) => {
11095
+ for (let i = 0; i < count; i++) {
11096
+ for (const p of [
11097
+ numPtr,
11098
+ changePtr,
11099
+ kindPtr,
11100
+ oldGenPtr,
11101
+ newGenPtr,
11102
+ streamPtr
11103
+ ]) mem.poke(p, "i32", 0);
11104
+ 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}`);
11105
+ const objectNumber = Number(mem.peek(numPtr, "i32")) >>> 0;
11106
+ const oldGen = Number(mem.peek(oldGenPtr, "i32"));
11107
+ const newGen = Number(mem.peek(newGenPtr, "i32"));
11108
+ const oldValue = this.readValue(diff, i, DIFF_OLD);
11109
+ const newValue = this.readValue(diff, i, DIFF_NEW);
11110
+ let truncated = oldValue.truncated || newValue.truncated;
11111
+ let oldParsed = null;
11112
+ let newParsed = null;
11113
+ try {
11114
+ oldParsed = oldValue.text === null || oldValue.truncated ? null : (0, _embedpdf_engine_core_runtime.parsePdfValue)(oldValue.text);
11115
+ newParsed = newValue.text === null || newValue.truncated ? null : (0, _embedpdf_engine_core_runtime.parsePdfValue)(newValue.text);
11116
+ } catch {
11117
+ truncated = true;
11118
+ }
11119
+ const change = CHANGE_BY_CODE[Number(mem.peek(changePtr, "i32"))] ?? "modified";
11120
+ changes.push({
11121
+ objectNumber,
11122
+ change,
11123
+ kind: KIND_BY_CODE[Number(mem.peek(kindPtr, "i32"))] ?? "scalar",
11124
+ present: {
11125
+ old: change !== "added",
11126
+ new: change !== "freed"
11127
+ },
11128
+ generation: {
11129
+ old: oldGen < 0 ? null : oldGen,
11130
+ new: newGen < 0 ? null : newGen
11131
+ },
11132
+ value: {
11133
+ old: oldParsed,
11134
+ new: newParsed,
11135
+ truncated
11136
+ },
11137
+ raw: {
11138
+ old: oldValue.text,
11139
+ new: newValue.text
11140
+ },
11141
+ read: {
11142
+ old: READ_BY_CODE[fn.EPDFObjectDiff_GetReadStatus(diff, i, DIFF_OLD)] ?? "failed",
11143
+ new: READ_BY_CODE[fn.EPDFObjectDiff_GetReadStatus(diff, i, DIFF_NEW)] ?? "failed"
11144
+ },
11145
+ streamDataChanged: Number(mem.peek(streamPtr, "i32")) !== 0,
11146
+ usage: {
11147
+ old: [],
11148
+ new: []
11149
+ }
11150
+ });
11151
+ }
11152
+ });
11153
+ return {
11154
+ changes: this.withAnchoredUsage(diff, changes, before, after),
11155
+ health: {
11156
+ old: this.readHealth(diff, DIFF_OLD),
11157
+ new: this.readHealth(diff, DIFF_NEW)
11158
+ }
11159
+ };
11160
+ } finally {
11161
+ fn.EPDFObjectDiff_Close(diff);
11162
+ }
11163
+ }
11164
+ /** What the fork learned about one revision as a whole (see `RevisionHealth`). */
11165
+ readHealth(diff, which) {
11166
+ const { fn, mem } = this.runtime;
11167
+ return withScratchN(mem, [
11168
+ 4,
11169
+ 4,
11170
+ 4
11171
+ ], ([sparsePtr, barePtr, completePtr]) => {
11172
+ for (const p of [
11173
+ sparsePtr,
11174
+ barePtr,
11175
+ completePtr
11176
+ ]) mem.poke(p, "i32", 0);
11177
+ 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");
11178
+ return {
11179
+ sparseXref: Number(mem.peek(sparsePtr, "i32")) !== 0,
11180
+ bareReferences: Number(mem.peek(barePtr, "i32")) >>> 0,
11181
+ referrersComplete: Number(mem.peek(completePtr, "i32")) !== 0
11182
+ };
11183
+ });
11184
+ }
11185
+ /**
11186
+ * Pass two: every changed object's inbound edges, resolved through
11187
+ * unchanged non-structural objects to the nearest anchor (the fork's
11188
+ * referrer index covers every reachable object, so the walk never leaves
11189
+ * the diff). An object whose edges exceed the resolver's budget is
11190
+ * marked `usageIncomplete` and the evaluator refuses to permit it.
11191
+ */
11192
+ withAnchoredUsage(diff, changes, before, after) {
11193
+ const changed = new Set(changes.map((c) => c.objectNumber));
11194
+ const resolvers = {
11195
+ old: new _embedpdf_engine_core_runtime.EdgeResolver((n) => this.readReferrers(diff, DIFF_OLD, n), anchorSet(before, changed)),
11196
+ new: new _embedpdf_engine_core_runtime.EdgeResolver((n) => this.readReferrers(diff, DIFF_NEW, n), anchorSet(after, changed))
11197
+ };
11198
+ return changes.map((c) => {
11199
+ const old = c.present.old ? resolvers.old.resolve(c.objectNumber) : [];
11200
+ const now = c.present.new ? resolvers.new.resolve(c.objectNumber) : [];
11201
+ const incomplete = old === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE || now === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE;
11202
+ return {
11203
+ ...c,
11204
+ usage: {
11205
+ old: old === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE ? [] : old,
11206
+ new: now === _embedpdf_engine_core_runtime.USAGE_INCOMPLETE ? [] : now
11207
+ },
11208
+ ...incomplete ? { usageIncomplete: true } : {}
8388
11209
  };
8389
- } finally {
8390
- if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
8391
- mem.free(sizePtr);
8392
- }
11210
+ });
8393
11211
  }
8394
- saveStandaloneToFile(path, mode) {
8395
- const writer = this.runtime.fileWrite.toNodeFile(path);
11212
+ readValue(diff, index, which) {
11213
+ const { fn, mem } = this.runtime;
11214
+ return withScratch(mem, 4, (truncPtr) => {
11215
+ mem.poke(truncPtr, "i32", 0);
11216
+ const length = fn.EPDFObjectDiff_GetValue(diff, index, which, _embedpdf_engine_runtime.NULL_PTR, 0, truncPtr);
11217
+ const truncatedProbe = Number(mem.peek(truncPtr, "i32")) !== 0;
11218
+ if (length <= 0) return {
11219
+ text: null,
11220
+ truncated: truncatedProbe
11221
+ };
11222
+ return withScratch(mem, length, (buf) => {
11223
+ mem.poke(truncPtr, "i32", 0);
11224
+ const written = fn.EPDFObjectDiff_GetValue(diff, index, which, buf, length, truncPtr);
11225
+ const truncated = Number(mem.peek(truncPtr, "i32")) !== 0;
11226
+ if (written <= 0) return {
11227
+ text: null,
11228
+ truncated
11229
+ };
11230
+ const bytes = mem.readBytes(buf, written - 1);
11231
+ let text = "";
11232
+ for (const b of bytes) text += String.fromCharCode(b);
11233
+ return {
11234
+ text,
11235
+ truncated
11236
+ };
11237
+ });
11238
+ });
11239
+ }
11240
+ readReferrers(diff, which, objectNumber) {
11241
+ const { fn, mem } = this.runtime;
11242
+ const count = fn.EPDFObjectDiff_GetReferrerCount(diff, which, objectNumber);
11243
+ const out = [];
11244
+ if (count <= 0) return out;
11245
+ withScratch(mem, 4, (parentPtr) => {
11246
+ for (let k = 0; k < count; k++) {
11247
+ mem.poke(parentPtr, "i32", 0);
11248
+ const length = fn.EPDFObjectDiff_GetReferrer(diff, which, objectNumber, k, parentPtr, _embedpdf_engine_runtime.NULL_PTR, 0);
11249
+ const label = length <= 0 ? "" : withScratch(mem, length, (buf) => {
11250
+ const written = fn.EPDFObjectDiff_GetReferrer(diff, which, objectNumber, k, parentPtr, buf, length);
11251
+ if (written <= 0) return "";
11252
+ const bytes = mem.readBytes(buf, written - 1);
11253
+ let text = "";
11254
+ for (const b of bytes) text += String.fromCharCode(b);
11255
+ return text;
11256
+ });
11257
+ out.push({
11258
+ parent: Number(mem.peek(parentPtr, "i32")) >>> 0,
11259
+ label
11260
+ });
11261
+ }
11262
+ });
11263
+ return out;
11264
+ }
11265
+ };
11266
+ //#endregion
11267
+ //#region src/features/signature/CandidateFinalizer.ts
11268
+ /**
11269
+ * The session-less half of a durable signing. A server prepares a
11270
+ * candidate on one replica, keeps only its tail, and may complete it on
11271
+ * another: the completing replica rebuilds base ⊕ tail into a private
11272
+ * file and hands it here with what the prepare reported. The CMS is
11273
+ * hex-encoded into the /Contents hole in place — never more than the
11274
+ * hole itself is held in memory — and the file is then opened like any
11275
+ * other base and read back through the signature model, so the version
11276
+ * returned is the file's own hash and the signature is what the file
11277
+ * says, not what the writer meant. Nothing here touches a live session.
11278
+ */
11279
+ var CandidateFinalizer = class {
11280
+ constructor(runtime, baseDocuments) {
11281
+ this.runtime = runtime;
11282
+ this.baseDocuments = baseDocuments;
11283
+ }
11284
+ finalize(input) {
11285
+ const [r0, r1, r2, r3] = input.byteRange;
11286
+ const size = this.runtime.fileAccess.sizeOf(input.path);
11287
+ const isOffset = (n) => Number.isSafeInteger(n) && n >= 0;
11288
+ 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`);
11289
+ const hexLength = r2 - r1 - 2;
11290
+ 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`);
11291
+ 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");
11292
+ 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`);
11293
+ writeContentsIntoFile(this.runtime, input.path, {
11294
+ holeOffset: r1,
11295
+ hexLength
11296
+ }, input.cms);
11297
+ const session = new DocumentSession(this.runtime);
11298
+ const base = this.baseDocuments.acquireFileBase({
11299
+ key: `finalize:${generateUuid()}`,
11300
+ path: input.path,
11301
+ password: input.password
11302
+ });
8396
11303
  try {
8397
- 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");
8398
- return { path };
11304
+ session.openFromHandle(openLayerDocument(this.runtime, base, { kind: "fresh" }, input.password));
11305
+ const model = acquireSignatureModel(this.runtime, session);
11306
+ assertSealedSignature(this.runtime, session.requireDocPtr(), model, {
11307
+ fieldObjectNumber: input.fieldObjectNumber,
11308
+ byteRange: input.byteRange,
11309
+ cms: input.cms
11310
+ });
11311
+ const reader = new SignatureReader(this.runtime, session);
11312
+ const snapshot = reader.readSnapshot();
11313
+ return {
11314
+ signature: snapshot.signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === input.fieldObjectNumber),
11315
+ protection: snapshot.protection,
11316
+ version: reader.version()
11317
+ };
8399
11318
  } finally {
8400
- writer.close();
11319
+ session.close();
8401
11320
  }
8402
11321
  }
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
11322
  };
8407
11323
  //#endregion
8408
11324
  //#region src/features/fonts/FontRegistrar.ts
11325
+ /** `EPDF_FONT_EMBEDDING_*` codes. Restricted (3) and bitmap-only (4) are
11326
+ * refused at registration, so they never reach a handle. */
11327
+ function permissionFromCode(code) {
11328
+ switch (code) {
11329
+ case 1: return "editable";
11330
+ case 2: return "preview-and-print";
11331
+ default: return "installable";
11332
+ }
11333
+ }
11334
+ /** The engine's family comparison: no case, spaces, hyphens, underscores. */
11335
+ function familyKey(family) {
11336
+ return family.toLowerCase().replace(/[\s\-_'"]/g, "");
11337
+ }
8409
11338
  /**
8410
11339
  * Thread-confined runtime font registry binding.
8411
11340
  *
@@ -8429,6 +11358,7 @@ var FontRegistrar = class {
8429
11358
  this.runtime = runtime;
8430
11359
  this.ids = ids;
8431
11360
  _defineProperty(this, "fileHandles", []);
11361
+ _defineProperty(this, "identities", /* @__PURE__ */ new Map());
8432
11362
  }
8433
11363
  /**
8434
11364
  * Register a font from its bytes. Copies into the WASM heap, hands the
@@ -8447,6 +11377,11 @@ var FontRegistrar = class {
8447
11377
  const id = fn.EPDFFont_RegisterMemFont64(familyName, weight, italic, ptr, bytes.byteLength);
8448
11378
  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
11379
  this.ids.set(fontKey, id);
11380
+ this.identities.set(fontKey, this.readIdentity(id, {
11381
+ familyName,
11382
+ weight,
11383
+ italic
11384
+ }));
8450
11385
  } finally {
8451
11386
  mem.free(ptr);
8452
11387
  }
@@ -8472,8 +11407,72 @@ var FontRegistrar = class {
8472
11407
  throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `font registration failed (unloadable or no glyphs): ${fontKey} (${path})`);
8473
11408
  }
8474
11409
  this.ids.set(fontKey, id);
11410
+ this.identities.set(fontKey, this.readIdentity(id, {
11411
+ familyName,
11412
+ weight,
11413
+ italic
11414
+ }));
8475
11415
  this.fileHandles.push(access);
8476
11416
  }
11417
+ /** The identity and licence the runtime resolved for a key. Throws if unknown. */
11418
+ describe(fontKey) {
11419
+ const identity = this.identities.get(fontKey);
11420
+ if (!identity) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `font not registered on this thread: ${fontKey}`);
11421
+ return identity;
11422
+ }
11423
+ /** {@link describe} for callers that treat an unknown key as "not a key". */
11424
+ describeOrUndefined(fontKey) {
11425
+ return this.identities.get(fontKey);
11426
+ }
11427
+ /**
11428
+ * The key of the registered font whose identity matches a face (family
11429
+ * compared the engine's way; the closest weight, italic first), or
11430
+ * undefined when no registered family matches.
11431
+ */
11432
+ keyForFace(family, weight, italic) {
11433
+ const wanted = familyKey(family);
11434
+ let best;
11435
+ for (const [key, identity] of this.identities) {
11436
+ if (familyKey(identity.familyName) !== wanted) continue;
11437
+ const score = Math.abs(identity.weight - weight) + (identity.italic === italic ? 0 : 1e3);
11438
+ if (!best || score < best.score) best = {
11439
+ key,
11440
+ score
11441
+ };
11442
+ }
11443
+ return best?.key;
11444
+ }
11445
+ /** The application asserts a licence permitting editing with the font. */
11446
+ authorizeEditing(fontKey) {
11447
+ const id = this.requireId(fontKey);
11448
+ if (!this.runtime.fn.EPDFFont_AuthorizeEditing(id)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `authorizeEditing failed: ${fontKey}`);
11449
+ const previous = this.describe(fontKey);
11450
+ const identity = this.readIdentity(id, {
11451
+ familyName: previous.familyName,
11452
+ weight: previous.weight,
11453
+ italic: previous.italic ? 1 : 0
11454
+ });
11455
+ this.identities.set(fontKey, identity);
11456
+ return identity;
11457
+ }
11458
+ readIdentity(id, given) {
11459
+ const { fn, mem } = this.runtime;
11460
+ const attempt = (read, fallback) => {
11461
+ try {
11462
+ return read();
11463
+ } catch {
11464
+ return fallback;
11465
+ }
11466
+ };
11467
+ return {
11468
+ familyName: attempt(() => readUtf8String(mem, (buf, cap) => fn.EPDFFont_GetFamilyName(id, buf, cap)) ?? given.familyName, given.familyName),
11469
+ weight: attempt(() => fn.EPDFFont_GetWeight(id), given.weight || 400),
11470
+ italic: attempt(() => fn.EPDFFont_IsItalic(id), given.italic === 1),
11471
+ embeddingPermission: attempt(() => permissionFromCode(fn.EPDFFont_GetEmbeddingPermission(id)), "installable"),
11472
+ editingAuthorized: attempt(() => fn.EPDFFont_IsEditingAuthorized(id), true),
11473
+ instanced: attempt(() => fn.EPDFFont_IsInstanced(id), false)
11474
+ };
11475
+ }
8477
11476
  /**
8478
11477
  * Register host-owned startup fonts on this thread, in order, optionally
8479
11478
  * adding each to the fallback chain. Order matters: it fixes both the native
@@ -8499,6 +11498,7 @@ var FontRegistrar = class {
8499
11498
  clear() {
8500
11499
  this.runtime.fn.EPDFFont_ClearRegisteredFonts();
8501
11500
  this.ids.clear();
11501
+ this.identities.clear();
8502
11502
  for (const access of this.fileHandles) access.close();
8503
11503
  this.fileHandles.length = 0;
8504
11504
  }
@@ -8651,35 +11651,14 @@ function flagMasks(flags) {
8651
11651
  };
8652
11652
  }
8653
11653
  //#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
11654
  //#region src/features/forms/FormMutator.ts
8677
11655
  const FAMILY_CODE = {
8678
11656
  checkbox: 2,
8679
11657
  radio: 3,
8680
11658
  text: 4,
8681
11659
  combobox: 5,
8682
- listbox: 6
11660
+ listbox: 6,
11661
+ signature: 7
8683
11662
  };
8684
11663
  /** Widgets a single value write can touch; far above any real form. */
8685
11664
  const CHANGED_WIDGETS_CAPACITY$1 = 256;
@@ -8712,7 +11691,7 @@ var FormMutator = class {
8712
11691
  throwIfAborted(signal);
8713
11692
  const model = acquireFormModel(this.runtime, this.session);
8714
11693
  const resolved = resolveFieldRef(this.runtime, model, ref);
8715
- this.assertWritable(resolved.fieldObjectNumber);
11694
+ this.assertWritable(resolved);
8716
11695
  const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8717
11696
  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
11697
  const changed = this.applyWrite(resolved.fieldObjectNumber, value);
@@ -8722,7 +11701,7 @@ var FormMutator = class {
8722
11701
  throwIfAborted(signal);
8723
11702
  const model = acquireFormModel(this.runtime, this.session);
8724
11703
  const resolved = resolveFieldRef(this.runtime, model, ref);
8725
- this.assertWritable(resolved.fieldObjectNumber);
11704
+ this.assertWritable(resolved);
8726
11705
  const changed = this.withChangedWidgets((buf, cap, countPtr) => this.runtime.fn.EPDFForm_ResetField(this.session.requireDocPtr(), resolved.fieldObjectNumber, buf, cap, countPtr));
8727
11706
  if (changed === null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "form field cannot be reset");
8728
11707
  return this.readBack(resolved.fieldObjectNumber, changed);
@@ -8815,13 +11794,39 @@ var FormMutator = class {
8815
11794
  this.session.noteMutation();
8816
11795
  return { field: this.readBackField(fieldObjectNumber) };
8817
11796
  }
11797
+ /**
11798
+ * Draw a PDF page into every widget of an UNSIGNED signature field: the
11799
+ * visual "sign" of a viewer without a signer. The field's value stays
11800
+ * empty and nothing is sealed; a signed field is refused (its appearance
11801
+ * is part of what the signature covers). Pages whose widgets changed are
11802
+ * reported so their renders re-pin.
11803
+ */
11804
+ setSignatureAppearance(ref, pdf, pageIndex, signal) {
11805
+ throwIfAborted(signal);
11806
+ const docPtr = this.session.requireDocPtr();
11807
+ const model = acquireFormModel(this.runtime, this.session);
11808
+ const resolved = resolveFieldRef(this.runtime, model, ref);
11809
+ this.assertWritable(resolved);
11810
+ const before = readFieldAt(this.runtime, model, resolved.fieldIndex, docPtr);
11811
+ 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`);
11812
+ 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`);
11813
+ 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`);
11814
+ for (const widget of before.widgets) bakeWidgetAppearance(this.runtime, docPtr, widget, pdf, pageIndex);
11815
+ this.session.noteMutation();
11816
+ const pages = [...new Set(before.widgets.map((w) => w.pageObjectNumber))];
11817
+ for (const pon of pages) this.session.bumpRevision(pon);
11818
+ return {
11819
+ field: this.readBackField(resolved.fieldObjectNumber),
11820
+ pages
11821
+ };
11822
+ }
8818
11823
  updateField(ref, patch, signal) {
8819
11824
  throwIfAborted(signal);
8820
11825
  const { fn } = this.runtime;
8821
11826
  const docPtr = this.session.requireDocPtr();
8822
11827
  const model = acquireFormModel(this.runtime, this.session);
8823
11828
  const resolved = resolveFieldRef(this.runtime, model, ref);
8824
- this.assertWritable(resolved.fieldObjectNumber);
11829
+ this.assertWritable(resolved);
8825
11830
  const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8826
11831
  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
11832
  const fieldObjectNumber = resolved.fieldObjectNumber;
@@ -8852,7 +11857,7 @@ var FormMutator = class {
8852
11857
  const { fn, mem } = this.runtime;
8853
11858
  const model = acquireFormModel(this.runtime, this.session);
8854
11859
  const resolved = resolveFieldRef(this.runtime, model, ref);
8855
- this.assertWritable(resolved.fieldObjectNumber);
11860
+ this.assertWritable(resolved);
8856
11861
  const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8857
11862
  if (!withScratchN(mem, [256 * 4, 4], ([buf, countPtr]) => {
8858
11863
  mem.poke(countPtr, "i32", 0);
@@ -8872,7 +11877,7 @@ var FormMutator = class {
8872
11877
  const { fn } = this.runtime;
8873
11878
  const model = acquireFormModel(this.runtime, this.session);
8874
11879
  const resolved = resolveFieldRef(this.runtime, model, ref);
8875
- this.assertWritable(resolved.fieldObjectNumber);
11880
+ this.assertWritable(resolved);
8876
11881
  const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8877
11882
  const toggle = before.family === "checkbox" || before.family === "radio";
8878
11883
  const state = toggle ? onState ?? (before.family === "checkbox" ? "Yes" : "") : "";
@@ -8886,7 +11891,7 @@ var FormMutator = class {
8886
11891
  const { fn } = this.runtime;
8887
11892
  const model = acquireFormModel(this.runtime, this.session);
8888
11893
  const resolved = resolveFieldRef(this.runtime, model, ref);
8889
- this.assertWritable(resolved.fieldObjectNumber);
11894
+ this.assertWritable(resolved);
8890
11895
  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
11896
  this.session.noteMutation();
8892
11897
  return { field: this.readBackField(resolved.fieldObjectNumber) };
@@ -8921,8 +11926,30 @@ var FormMutator = class {
8921
11926
  if (fieldIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "form field vanished after write");
8922
11927
  return readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
8923
11928
  }
8924
- assertWritable(fieldObjectNumber) {
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");
11929
+ assertWritable(resolved) {
11930
+ 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");
11931
+ this.assertNotLockedBySignature(resolved);
11932
+ }
11933
+ /**
11934
+ * A field an earlier signature froze (its FieldMDP, or the /Lock of a
11935
+ * signed field) refuses every write: document-derived authority, the
11936
+ * same way encryption bits are. Off under `signedDocumentPolicy:
11937
+ * 'permit'`. A document whose signature model cannot be built is not
11938
+ * known to be locked.
11939
+ */
11940
+ assertNotLockedBySignature(resolved) {
11941
+ if (this.session.signedDocumentPolicy !== "protect") return;
11942
+ let protection;
11943
+ try {
11944
+ protection = new SignatureReader(this.runtime, this.session).readProtection();
11945
+ } catch {
11946
+ return;
11947
+ }
11948
+ if (protection.fieldLocks.length === 0) return;
11949
+ const model = acquireFormModel(this.runtime, this.session);
11950
+ const name = readUtf16String(this.runtime.mem, (buf, cap) => this.runtime.fn.EPDFForm_GetFieldName(model, resolved.fieldIndex, buf, cap)) ?? "";
11951
+ const lock = (0, _embedpdf_engine_core_runtime.fieldLockFor)(protection, name);
11952
+ 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
11953
  }
8927
11954
  /** Dispatch the typed native write. Returns the changed widget objnums. */
8928
11955
  applyWrite(fieldObjectNumber, value) {
@@ -9334,6 +12361,7 @@ function searchQueryKey(query, mode) {
9334
12361
  query.matchCase ? 1 : 0,
9335
12362
  query.matchDiacritics ? 1 : 0,
9336
12363
  query.wholeWord ? 1 : 0,
12364
+ query.ignoreWhitespace ? 1 : 0,
9337
12365
  mode
9338
12366
  ]);
9339
12367
  }
@@ -9434,7 +12462,7 @@ var SearchReader = class {
9434
12462
  const text = corpus.snapshot.text;
9435
12463
  let ranges;
9436
12464
  if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(text, query);
9437
- else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(text, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
12465
+ else if (query.matchCase || query.matchDiacritics || query.ignoreWhitespace) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(text, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
9438
12466
  else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus.folded, query);
9439
12467
  if (ranges.length > 0) {
9440
12468
  const layout = (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
@@ -9532,6 +12560,100 @@ function requireAction(action, owner, index) {
9532
12560
  throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, `failed to read ${owner}`, { details: { index } });
9533
12561
  }
9534
12562
  //#endregion
12563
+ //#region src/features/measure/internal/readViewports.ts
12564
+ const CALIBRATION_NAME = "EmbedPDF";
12565
+ function readViewports(fn, mem, page) {
12566
+ const viewports = [];
12567
+ for (let i = 0; i < fn.EPDFPage_CountViewports(page); i++) {
12568
+ const viewport = fn.EPDFPage_GetViewport(page, i);
12569
+ if (!viewport) continue;
12570
+ const bbox = withScratch(mem, 16, (p) => fn.EPDFViewport_GetBBox(viewport, p) ? (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, p)) : void 0);
12571
+ if (!bbox) continue;
12572
+ const name = readUtf16String(mem, (p, n) => fn.EPDFViewport_GetName(viewport, p, n));
12573
+ const measure = readMeasure(fn, mem, fn.EPDFViewport_GetMeasure(viewport));
12574
+ viewports.push({
12575
+ bbox,
12576
+ owned: name === "EmbedPDF" && (!measure || measure.subtype === "RL"),
12577
+ ...name !== null ? { name } : {},
12578
+ ...measure ? { measure } : {}
12579
+ });
12580
+ }
12581
+ return viewports;
12582
+ }
12583
+ //#endregion
12584
+ //#region src/features/measure/MeasureReader.ts
12585
+ var MeasureReader = class {
12586
+ constructor(runtime, session) {
12587
+ this.runtime = runtime;
12588
+ this.session = session;
12589
+ }
12590
+ viewports(pon, signal) {
12591
+ throwIfAborted(signal);
12592
+ const pool = this.session.pagePool(), page = pool.acquire(pon);
12593
+ try {
12594
+ return readViewports(this.runtime.fn, this.runtime.mem, page);
12595
+ } finally {
12596
+ pool.release(pon);
12597
+ }
12598
+ }
12599
+ };
12600
+ //#endregion
12601
+ //#region src/features/measure/MeasureMutator.ts
12602
+ var MeasureMutator = class {
12603
+ constructor(runtime, session) {
12604
+ this.runtime = runtime;
12605
+ this.session = session;
12606
+ }
12607
+ setScale(pon, measure, signal) {
12608
+ throwIfAborted(signal);
12609
+ if (measure !== null) try {
12610
+ (0, _embedpdf_engine_core_runtime.assertWritableMeasure)(measure);
12611
+ } catch (e) {
12612
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, e.message);
12613
+ }
12614
+ const { fn, mem } = this.runtime, pool = this.session.pagePool(), page = pool.acquire(pon);
12615
+ try {
12616
+ const owned = [];
12617
+ for (let i = 0; i < fn.EPDFPage_CountViewports(page); i++) {
12618
+ const vp = fn.EPDFPage_GetViewport(page, i);
12619
+ if (!vp || readUtf16String(mem, (p, n) => fn.EPDFViewport_GetName(vp, p, n)) !== "EmbedPDF") continue;
12620
+ const m = fn.EPDFViewport_GetMeasure(vp);
12621
+ if (!m || fn.EPDFMeasure_GetSubtype(m) === 1) owned.push(i);
12622
+ }
12623
+ const bbox = withScratch(mem, 16, (p) => {
12624
+ const index = this.session.recordByObjectNumber(pon).pageIndex;
12625
+ const doc = this.session.requireDocPtr();
12626
+ if (!fn.EPDF_GetPageBoxByIndex(doc, index, 1, p) && !fn.EPDF_GetPageBoxByIndex(doc, index, 0, p)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "Page has no valid calibration box");
12627
+ return (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, p));
12628
+ });
12629
+ if (![
12630
+ bbox.left,
12631
+ bbox.right,
12632
+ bbox.bottom,
12633
+ bbox.top
12634
+ ].every(Number.isFinite)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "Invalid calibration box");
12635
+ throwIfAborted(signal);
12636
+ if (measure === null) {
12637
+ for (const i of owned.reverse()) requireMeasureWrite(fn.EPDFPage_RemoveViewport(page, i));
12638
+ return;
12639
+ }
12640
+ withScratch(mem, 16, (p) => {
12641
+ mem.poke(p, "f32", bbox.left);
12642
+ mem.poke(p, "f32", bbox.top, 4);
12643
+ mem.poke(p, "f32", bbox.right, 8);
12644
+ mem.poke(p, "f32", bbox.bottom, 12);
12645
+ const vp = owned.length ? fn.EPDFPage_GetViewport(page, owned[owned.length - 1]) : fn.EPDFPage_AddViewport(page, p);
12646
+ requireMeasureWrite(vp);
12647
+ requireMeasureWrite(fn.EPDFViewport_SetBBox(vp, p));
12648
+ requireMeasureWrite(writeUtf16String(mem, CALIBRATION_NAME, (name) => fn.EPDFViewport_SetName(vp, name)));
12649
+ writeMeasure(fn, mem, fn.EPDFViewport_AddMeasure(vp), measure);
12650
+ });
12651
+ } finally {
12652
+ pool.release(pon);
12653
+ }
12654
+ }
12655
+ };
12656
+ //#endregion
9535
12657
  //#region src/features/pieceinfo/PieceInfoAccessor.ts
9536
12658
  const OBJ_BOOLEAN = 1;
9537
12659
  const OBJ_NUMBER = 2;
@@ -9769,6 +12891,7 @@ var WorkerHost = class {
9769
12891
  this.aborts.set(msg.jobId, ctrl);
9770
12892
  let resultPack;
9771
12893
  try {
12894
+ this.assertNoPendingSigning(msg);
9772
12895
  switch (msg.kind) {
9773
12896
  case "open.fatMem":
9774
12897
  case "open.layerMemBase":
@@ -9808,6 +12931,36 @@ var WorkerHost = class {
9808
12931
  case "annotations.move":
9809
12932
  resultPack = this.handleAnnotationsMove(msg, ctrl.signal);
9810
12933
  break;
12934
+ case "signatures.list":
12935
+ resultPack = this.handleSignaturesList(msg);
12936
+ break;
12937
+ case "signatures.contents":
12938
+ resultPack = this.handleSignaturesContents(msg);
12939
+ break;
12940
+ case "signatures.digest":
12941
+ resultPack = this.handleSignaturesDigest(msg);
12942
+ break;
12943
+ case "signatures.revisionBytes":
12944
+ resultPack = this.handleSignaturesRevisionBytes(msg);
12945
+ break;
12946
+ case "document.version":
12947
+ resultPack = this.handleDocumentVersion(msg);
12948
+ break;
12949
+ case "signatures.prepare":
12950
+ resultPack = this.handleSignaturesPrepare(msg);
12951
+ break;
12952
+ case "signatures.complete":
12953
+ resultPack = this.handleSignaturesComplete(msg);
12954
+ break;
12955
+ case "signatures.abort":
12956
+ resultPack = this.handleSignaturesAbort(msg);
12957
+ break;
12958
+ case "signatures.analyze":
12959
+ resultPack = this.handleSignaturesAnalyze(msg);
12960
+ break;
12961
+ case "signatures.finalizeCandidate":
12962
+ resultPack = this.handleSignaturesFinalizeCandidate(msg);
12963
+ break;
9811
12964
  case "forms.list":
9812
12965
  resultPack = this.handleFormsList(msg, ctrl.signal);
9813
12966
  break;
@@ -9835,6 +12988,9 @@ var WorkerHost = class {
9835
12988
  case "forms.updateField":
9836
12989
  resultPack = this.handleFormsUpdateField(msg, ctrl.signal);
9837
12990
  break;
12991
+ case "forms.setSignatureAppearance":
12992
+ resultPack = this.handleFormsSetSignatureAppearance(msg, ctrl.signal);
12993
+ break;
9838
12994
  case "forms.deleteField":
9839
12995
  resultPack = this.handleFormsDeleteField(msg, ctrl.signal);
9840
12996
  break;
@@ -9898,6 +13054,12 @@ var WorkerHost = class {
9898
13054
  case "annotations.readFile":
9899
13055
  resultPack = this.handleAnnotationsReadFile(msg, ctrl.signal);
9900
13056
  break;
13057
+ case "measure.viewports":
13058
+ resultPack = this.handleMeasureViewports(msg, ctrl.signal);
13059
+ break;
13060
+ case "measure.setScale":
13061
+ resultPack = this.handleMeasureSetScale(msg, ctrl.signal);
13062
+ break;
9901
13063
  case "pieceInfo.read":
9902
13064
  resultPack = this.handlePieceInfoRead(msg, ctrl.signal);
9903
13065
  break;
@@ -9952,6 +13114,12 @@ var WorkerHost = class {
9952
13114
  case "fonts.clear":
9953
13115
  resultPack = this.handleFontsClear(msg);
9954
13116
  break;
13117
+ case "fonts.authorizeEditing":
13118
+ resultPack = this.handleFontsAuthorizeEditing(msg);
13119
+ break;
13120
+ case "document.setFontSettings":
13121
+ resultPack = this.handleDocumentSetFontSettings(msg);
13122
+ break;
9955
13123
  case "layer.close":
9956
13124
  resultPack = this.handleLayerClose(msg);
9957
13125
  break;
@@ -9983,10 +13151,13 @@ var WorkerHost = class {
9983
13151
  const key = sessionKey(req.docId, req.kind === "open.fatMem" ? void 0 : req.layerName);
9984
13152
  if (this.sessions.has(key)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `document session already open: ${key}`);
9985
13153
  const session = new DocumentSession(this.runtime);
13154
+ session.signedDocumentPolicy = req.signedDocumentPolicy ?? "protect";
13155
+ session.password = req.password;
9986
13156
  if (req.kind === "open.fatMem") {
13157
+ session.sessionKind = req.sessionKind ?? "layer";
9987
13158
  const bytes = new Uint8Array(req.bytes);
9988
13159
  try {
9989
- session.openFromHandle(openFatMemoryDocument(this.runtime, bytes, req.password));
13160
+ this.openSignedAware(session, bytes, req.password);
9990
13161
  } catch (error) {
9991
13162
  if (!isPasswordOpenError(error)) throw error;
9992
13163
  session.parkLocked(bytes);
@@ -10001,14 +13172,16 @@ var WorkerHost = class {
10001
13172
  const base = this.baseDocuments.acquireMemoryBase({
10002
13173
  key: req.baseKey,
10003
13174
  bytes: new Uint8Array(req.baseBytes),
10004
- password: req.password
13175
+ password: req.password,
13176
+ knownSha256: req.baseSha256
10005
13177
  });
10006
13178
  session.openFromHandle(openLayerDocument(this.runtime, base, req.layer, req.password));
10007
13179
  } else {
10008
13180
  const base = this.baseDocuments.acquireFileBase({
10009
13181
  key: req.baseKey,
10010
13182
  path: req.basePath,
10011
- password: req.password
13183
+ password: req.password,
13184
+ knownSha256: req.baseSha256
10012
13185
  });
10013
13186
  session.openFromHandle(openLayerDocument(this.runtime, base, req.layer, req.password));
10014
13187
  }
@@ -10016,9 +13189,160 @@ var WorkerHost = class {
10016
13189
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10017
13190
  tag: "open",
10018
13191
  docId: req.docId,
10019
- security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password ?? "")
13192
+ security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password ?? ""),
13193
+ protection: this.probeProtection(session)
13194
+ });
13195
+ }
13196
+ /**
13197
+ * Open plain bytes into `session` in the shape it asked for. The default,
13198
+ * `layer`, makes the bytes an immutable base with a fresh layer on top:
13199
+ * reads fall through to the base, a write promotes only what it touches,
13200
+ * a save appends exactly that, and a signature keeps its validity across
13201
+ * later edits because the signature dictionary is never rewritten. The
13202
+ * `plain` shape keeps one in-memory document — unless the bytes already
13203
+ * carry a signature value, which always opens as a layer, because a plain
13204
+ * save rewrites every loaded object into the new revision and strict
13205
+ * validators reject that. Auto-layered sessions serialize no artifact per
13206
+ * mutation: the caller never asked for one.
13207
+ */
13208
+ openSignedAware(session, bytes, password) {
13209
+ if (session.sessionKind === "plain") {
13210
+ const plain = openFatMemoryDocument(this.runtime, bytes, password);
13211
+ let signed = false;
13212
+ try {
13213
+ signed = hasSignedSignature(this.runtime, plain.docPtr);
13214
+ } catch {
13215
+ signed = false;
13216
+ }
13217
+ if (!signed) {
13218
+ session.openFromHandle(plain);
13219
+ return;
13220
+ }
13221
+ plain.close();
13222
+ }
13223
+ const base = this.baseDocuments.acquireMemoryBase({
13224
+ key: `signed-open:${generateUuid()}`,
13225
+ bytes,
13226
+ password
13227
+ });
13228
+ session.openFromHandle(openLayerDocument(this.runtime, base, { kind: "fresh" }, password));
13229
+ session.persistLayerArtifact = false;
13230
+ }
13231
+ /**
13232
+ * What the document's signatures forbid, read at open and after an
13233
+ * unlock so the main-thread guard can subtract capabilities before the
13234
+ * first mutation. A document whose signature model cannot be built
13235
+ * still opens: protection is then `null` (nothing is known to be
13236
+ * signed), and the reads report the failure on their own.
13237
+ */
13238
+ probeProtection(session) {
13239
+ try {
13240
+ return new SignatureReader(this.runtime, session).readProtection();
13241
+ } catch {
13242
+ return null;
13243
+ }
13244
+ }
13245
+ handleSignaturesList(req) {
13246
+ const session = this.requireSession(req);
13247
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13248
+ tag: "signatures.list",
13249
+ snapshot: req.workingCopy ? new SignatureAnalyzer(this.runtime, session).readWorkingCopySnapshot() : new SignatureReader(this.runtime, session).readSnapshot()
13250
+ });
13251
+ }
13252
+ handleSignaturesContents(req) {
13253
+ const session = this.requireSession(req);
13254
+ const bytes = new SignatureReader(this.runtime, session).readContents(req.ref);
13255
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13256
+ tag: "signatures.contents",
13257
+ bytes
13258
+ }, [bytes]);
13259
+ }
13260
+ handleSignaturesDigest(req) {
13261
+ const session = this.requireSession(req);
13262
+ const digest = new SignatureReader(this.runtime, session).digest(req.ref, req.algorithm);
13263
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13264
+ tag: "signatures.digest",
13265
+ digest
13266
+ }, [digest]);
13267
+ }
13268
+ handleSignaturesRevisionBytes(req) {
13269
+ const session = this.requireSession(req);
13270
+ const bytes = new SignatureReader(this.runtime, session).revisionBytes(req.revisionIndex);
13271
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13272
+ tag: "signatures.revisionBytes",
13273
+ bytes,
13274
+ size: bytes.byteLength
13275
+ }, [bytes]);
13276
+ }
13277
+ handleDocumentVersion(req) {
13278
+ const session = this.requireSession(req);
13279
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13280
+ tag: "document.version",
13281
+ version: new SignatureReader(this.runtime, session).version()
13282
+ });
13283
+ }
13284
+ handleSignaturesPrepare(req) {
13285
+ const session = this.requireSession(req);
13286
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13287
+ tag: "signatures.prepare",
13288
+ result: new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).prepare(req.input)
13289
+ });
13290
+ }
13291
+ handleSignaturesComplete(req) {
13292
+ const session = this.requireSession(req);
13293
+ const result = new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).complete(req.input);
13294
+ if (result.status === "already-completed") return (0, _embedpdf_engine_core_runtime.wirePack)({
13295
+ tag: "signatures.complete",
13296
+ result
13297
+ });
13298
+ return this.finishMutation(session, {
13299
+ tag: "signatures.complete",
13300
+ result
13301
+ }, req.artifactPath);
13302
+ }
13303
+ handleSignaturesAnalyze(req) {
13304
+ const session = this.requireSession(req);
13305
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13306
+ tag: "signatures.analyze",
13307
+ analysis: new SignatureAnalyzer(this.runtime, session).analyze(req.input)
13308
+ });
13309
+ }
13310
+ /**
13311
+ * Session-less, like `document.renderPageFile`: the candidate file is
13312
+ * the caller's, patched in place and read back through a transient
13313
+ * session that is never stored in `this.sessions`.
13314
+ */
13315
+ handleSignaturesFinalizeCandidate(req) {
13316
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13317
+ tag: "signatures.finalizeCandidate",
13318
+ ...new CandidateFinalizer(this.runtime, this.baseDocuments).finalize({
13319
+ path: req.path,
13320
+ byteRange: req.byteRange,
13321
+ contentsSize: req.contentsSize,
13322
+ fieldObjectNumber: req.fieldObjectNumber,
13323
+ cms: new Uint8Array(req.cms),
13324
+ password: req.password ?? null
13325
+ })
13326
+ });
13327
+ }
13328
+ handleSignaturesAbort(req) {
13329
+ const session = this.requireSession(req);
13330
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13331
+ tag: "signatures.abort",
13332
+ result: new SignatureMutator(this.runtime, session, this.baseDocuments, this.options.signingCandidatePath).abort(req.signingId)
10020
13333
  });
10021
13334
  }
13335
+ /**
13336
+ * The dispatch fence for a parked signing candidate. Only mutating kinds
13337
+ * are refused; a request for a session that is not open falls through
13338
+ * to its handler's own `DocNotOpen`.
13339
+ */
13340
+ assertNoPendingSigning(msg) {
13341
+ if (!MUTATING_KINDS.has(msg.kind) || !("docId" in msg)) return;
13342
+ const layerName = "layerName" in msg ? msg.layerName : void 0;
13343
+ const session = this.sessions.get(sessionKey(msg.docId, layerName));
13344
+ 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`);
13345
+ }
10022
13346
  handleMetadataRead(req, signal) {
10023
13347
  const session = this.requireSession(req);
10024
13348
  return (0, _embedpdf_engine_core_runtime.wirePack)({
@@ -10045,21 +13369,21 @@ var WorkerHost = class {
10045
13369
  const session = this.requireSession(req);
10046
13370
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10047
13371
  tag: "annotations.listRawAll",
10048
- snapshot: new RawAnnotationReader(this.runtime, session).listAll(signal)
13372
+ snapshot: new RawAnnotationReader(this.runtime, session, this.fonts).listAll(signal)
10049
13373
  });
10050
13374
  }
10051
13375
  handleAnnotationsListRawPage(req, signal) {
10052
13376
  const session = this.requireSession(req);
10053
13377
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10054
13378
  tag: "annotations.listRawPage",
10055
- snapshot: new RawAnnotationReader(this.runtime, session).listOne(req.pageObjectNumber, signal)
13379
+ snapshot: new RawAnnotationReader(this.runtime, session, this.fonts).listOne(req.pageObjectNumber, signal)
10056
13380
  });
10057
13381
  }
10058
13382
  handleAnnotationsListFullPage(req, signal) {
10059
13383
  const session = this.requireSession(req);
10060
13384
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10061
13385
  tag: "annotations.listFullPage",
10062
- snapshot: new AnnotationReader(this.runtime, session).list(req.pageObjectNumber, signal)
13386
+ snapshot: new AnnotationReader(this.runtime, session, this.fonts).list(req.pageObjectNumber, signal)
10063
13387
  });
10064
13388
  }
10065
13389
  handleAnnotationsRenderAppearances(req, signal) {
@@ -10266,6 +13590,27 @@ var WorkerHost = class {
10266
13590
  content
10267
13591
  }, content.bytes !== void 0 ? [content.bytes] : []);
10268
13592
  }
13593
+ handleMeasureViewports(req, signal) {
13594
+ const session = this.requireSession(req);
13595
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13596
+ tag: "measure.viewports",
13597
+ viewports: new MeasureReader(this.runtime, session).viewports(req.pageObjectNumber, signal)
13598
+ });
13599
+ }
13600
+ handleMeasureSetScale(req, signal) {
13601
+ const session = this.requireSession(req);
13602
+ new MeasureMutator(this.runtime, session).setScale(req.pageObjectNumber, req.measure, signal);
13603
+ return this.finishMutation(session, {
13604
+ tag: "measure.setScale",
13605
+ result: {
13606
+ pageObjectNumber: req.pageObjectNumber,
13607
+ meta: {
13608
+ affectedPages: [],
13609
+ cacheDelta: null
13610
+ }
13611
+ }
13612
+ }, req.artifactPath);
13613
+ }
10269
13614
  handlePieceInfoRead(req, signal) {
10270
13615
  const session = this.requireSession(req);
10271
13616
  return (0, _embedpdf_engine_core_runtime.wirePack)({
@@ -10420,6 +13765,14 @@ var WorkerHost = class {
10420
13765
  }
10421
13766
  handleDocumentSaveBuffer(req) {
10422
13767
  const session = this.requireSession(req);
13768
+ if (req.mode === "incremental") {
13769
+ const snap = new DocumentSaver(this.runtime, session).snapshot();
13770
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13771
+ tag: "document.saveBuffer",
13772
+ bytes: snap.bytes,
13773
+ size: snap.bytes.byteLength
13774
+ }, [snap.bytes]);
13775
+ }
10423
13776
  const saved = new DocumentSaver(this.runtime, session).saveStandaloneToBuffer(req.mode);
10424
13777
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10425
13778
  tag: "document.saveBuffer",
@@ -10429,6 +13782,17 @@ var WorkerHost = class {
10429
13782
  }
10430
13783
  handleDocumentSaveFile(req) {
10431
13784
  const session = this.requireSession(req);
13785
+ if (req.mode === "incremental" && (!session.hasUnsavedEdits() || new DocumentSaver(this.runtime, session).saveStandaloneToFileEx(req.path, req.mode).unchangedSinceLoad)) {
13786
+ new DocumentSaver(this.runtime, session).copyLoadedBytesToFile(req.path);
13787
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13788
+ tag: "document.saveFile",
13789
+ path: req.path
13790
+ });
13791
+ }
13792
+ if (req.mode === "incremental") return (0, _embedpdf_engine_core_runtime.wirePack)({
13793
+ tag: "document.saveFile",
13794
+ path: req.path
13795
+ });
10432
13796
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10433
13797
  tag: "document.saveFile",
10434
13798
  path: new DocumentSaver(this.runtime, session).saveStandaloneToFile(req.path, req.mode).path
@@ -10485,21 +13849,47 @@ var WorkerHost = class {
10485
13849
  const parked = this.sessions.get(sessionKey(req.docId));
10486
13850
  let session;
10487
13851
  if (parked?.isLocked()) {
10488
- parked.openFromHandle(openFatMemoryDocument(this.runtime, parked.lockedBytes(), req.password));
13852
+ this.openSignedAware(parked, parked.lockedBytes(), req.password);
13853
+ parked.password = req.password;
10489
13854
  session = parked;
10490
13855
  } else session = this.requireSession(req);
10491
13856
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10492
13857
  tag: "document.checkPasswordPermissions",
10493
- security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password, req.mode ?? "any")
13858
+ security: new SecurityReader(this.runtime).checkPasswordPermissions(session, req.password, req.mode ?? "any"),
13859
+ protection: this.probeProtection(session)
10494
13860
  });
10495
13861
  }
10496
13862
  handleFontsRegister(req) {
10497
13863
  this.fonts.register(req.fontKey, req.familyName, req.weight, req.italic, new Uint8Array(req.data));
10498
13864
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10499
13865
  tag: "fonts.register",
10500
- fontKey: req.fontKey
13866
+ fontKey: req.fontKey,
13867
+ identity: this.fonts.describe(req.fontKey)
13868
+ });
13869
+ }
13870
+ handleFontsAuthorizeEditing(req) {
13871
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
13872
+ tag: "fonts.authorizeEditing",
13873
+ identity: this.fonts.authorizeEditing(req.fontKey)
10501
13874
  });
10502
13875
  }
13876
+ /** Per-document font and text-layout settings (session state). */
13877
+ handleDocumentSetFontSettings(req) {
13878
+ const docPtr = this.requireSession(req).requireDocPtr();
13879
+ const { fn } = this.runtime;
13880
+ if (req.embeddingPolicy !== void 0) {
13881
+ const code = {
13882
+ default: 0,
13883
+ subset: 1,
13884
+ full: 2
13885
+ }[req.embeddingPolicy];
13886
+ if (!fn.EPDFDoc_SetFontEmbeddingPolicy(docPtr, code)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "EPDFDoc_SetFontEmbeddingPolicy failed");
13887
+ }
13888
+ if (req.typographicFeatures !== void 0) {
13889
+ if (!fn.EPDFDoc_SetTypographicFeatures(docPtr, req.typographicFeatures)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "EPDFDoc_SetTypographicFeatures failed");
13890
+ }
13891
+ return (0, _embedpdf_engine_core_runtime.wirePack)({ tag: "document.setFontSettings" });
13892
+ }
10503
13893
  handleFontsAddFallback(req) {
10504
13894
  this.fonts.addFallback(req.fontKey);
10505
13895
  return (0, _embedpdf_engine_core_runtime.wirePack)({ tag: "fonts.addFallback" });
@@ -10525,6 +13915,7 @@ var WorkerHost = class {
10525
13915
  const session = this.sessions.get(key);
10526
13916
  if (session) {
10527
13917
  disposeFormModel(this.runtime, session);
13918
+ disposeSignatureModel(this.runtime, session);
10528
13919
  session.close();
10529
13920
  this.sessions.delete(key);
10530
13921
  }
@@ -10534,6 +13925,7 @@ var WorkerHost = class {
10534
13925
  for (const [key, session] of Array.from(this.sessions.entries())) {
10535
13926
  if (!sessionKeyBelongsToDoc(key, req.docId)) continue;
10536
13927
  disposeFormModel(this.runtime, session);
13928
+ disposeSignatureModel(this.runtime, session);
10537
13929
  session.close();
10538
13930
  this.sessions.delete(key);
10539
13931
  }
@@ -10544,6 +13936,7 @@ var WorkerHost = class {
10544
13936
  this.destroyed = true;
10545
13937
  for (const session of this.sessions.values()) {
10546
13938
  disposeFormModel(this.runtime, session);
13939
+ disposeSignatureModel(this.runtime, session);
10547
13940
  session.close();
10548
13941
  }
10549
13942
  this.sessions.clear();
@@ -10649,6 +14042,21 @@ var WorkerHost = class {
10649
14042
  }
10650
14043
  }, req.artifactPath);
10651
14044
  }
14045
+ handleFormsSetSignatureAppearance(req, signal) {
14046
+ const session = this.requireSession(req);
14047
+ const { field, pages } = new FormMutator(this.runtime, session).setSignatureAppearance(req.ref, new Uint8Array(req.pdf), req.pageIndex, signal);
14048
+ const meta = {
14049
+ affectedPages: pages.map((pon) => session.pageState(pon)),
14050
+ cacheDelta: null
14051
+ };
14052
+ return this.finishMutation(session, {
14053
+ tag: "forms.setSignatureAppearance",
14054
+ result: {
14055
+ field,
14056
+ meta
14057
+ }
14058
+ }, req.artifactPath);
14059
+ }
10652
14060
  handleFormsDeleteField(req, signal) {
10653
14061
  const session = this.requireSession(req);
10654
14062
  const { deletedFieldObjectNumber, detachedWidgets } = new FormMutator(this.runtime, session).deleteField(req.ref, signal);
@@ -10694,8 +14102,8 @@ var WorkerHost = class {
10694
14102
  }, req.artifactPath);
10695
14103
  }
10696
14104
  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);
14105
+ if (!payload.tag.startsWith("forms.") && payload.tag !== "pages.flatten" && payload.tag !== "redaction.apply" && payload.tag !== "signatures.complete") session.noteMutation();
14106
+ if (session.kind !== "layer" || !session.persistLayerArtifact) return (0, _embedpdf_engine_core_runtime.wirePack)(payload);
10699
14107
  const saved = this.saveLayerArtifact(session, artifactPath);
10700
14108
  return (0, _embedpdf_engine_core_runtime.wirePack)({
10701
14109
  ...payload,
@@ -10721,6 +14129,40 @@ const EMPTY_FORM_META = {
10721
14129
  cacheDelta: null
10722
14130
  };
10723
14131
  const BASE_SESSION_SUFFIX = "__base__";
14132
+ /** Every request kind that writes to a session's document. */
14133
+ const MUTATING_KINDS = /* @__PURE__ */ new Set([
14134
+ "metadata.update",
14135
+ "annotations.create",
14136
+ "annotations.update",
14137
+ "annotations.delete",
14138
+ "annotations.move",
14139
+ "annotations.flatten",
14140
+ "forms.setValue",
14141
+ "forms.reset",
14142
+ "forms.applyEffects",
14143
+ "forms.import",
14144
+ "forms.repair",
14145
+ "forms.createField",
14146
+ "forms.updateField",
14147
+ "forms.setSignatureAppearance",
14148
+ "forms.deleteField",
14149
+ "forms.attachWidget",
14150
+ "forms.detachWidget",
14151
+ "pages.move",
14152
+ "pages.rotate",
14153
+ "pages.delete",
14154
+ "pages.setName",
14155
+ "pages.removeName",
14156
+ "pages.flatten",
14157
+ "pages.insert",
14158
+ "pages.insertBlank",
14159
+ "redaction.apply",
14160
+ "attachments.create",
14161
+ "attachments.delete",
14162
+ "measure.setScale",
14163
+ "pieceInfo.update",
14164
+ "pieceInfo.clear"
14165
+ ]);
10724
14166
  function sessionKey(docId, layerName) {
10725
14167
  return `${docId}::${layerName ? `layer:${layerName}` : BASE_SESSION_SUFFIX}`;
10726
14168
  }
@@ -10758,7 +14200,9 @@ exports.AnnotationReader = AnnotationReader;
10758
14200
  exports.AttachmentMutator = AttachmentMutator;
10759
14201
  exports.AttachmentReader = AttachmentReader;
10760
14202
  exports.BaseDocumentRegistry = BaseDocumentRegistry;
14203
+ exports.CandidateFinalizer = CandidateFinalizer;
10761
14204
  exports.CloseStack = CloseStack;
14205
+ exports.DIGEST_CODE = DIGEST_CODE;
10762
14206
  exports.DocumentActionsReader = DocumentActionsReader;
10763
14207
  exports.DocumentPagesMutator = PagesMutator;
10764
14208
  exports.DocumentSaver = DocumentSaver;
@@ -10788,21 +14232,30 @@ exports.RedactionApplier = RedactionApplier;
10788
14232
  exports.SearchReader = SearchReader;
10789
14233
  exports.SecurityReader = SecurityReader;
10790
14234
  exports.SessionEventPublisher = SessionEventPublisher;
14235
+ exports.SignatureAnalyzer = SignatureAnalyzer;
14236
+ exports.SignatureMutator = SignatureMutator;
14237
+ exports.SignatureReader = SignatureReader;
10791
14238
  exports.WorkerHost = WorkerHost;
14239
+ exports.acquireSignatureModel = acquireSignatureModel;
10792
14240
  exports.buildRunsFromRawGlyphs = buildRunsFromRawGlyphs;
10793
14241
  exports.computeMutationImpact = computeMutationImpact;
10794
14242
  exports.destroyLibrary = destroyLibrary;
10795
14243
  exports.disposeFormModel = disposeFormModel;
14244
+ exports.disposeSignatureModel = disposeSignatureModel;
10796
14245
  exports.ensureInitialized = ensureInitialized;
10797
14246
  exports.formatPdfDate = formatPdfDate;
10798
14247
  exports.generateUuid = generateUuid;
10799
14248
  exports.hasAllStandardSecurityPermissions = hasAllStandardSecurityPermissions;
14249
+ exports.hasSignedSignature = hasSignedSignature;
10800
14250
  exports.normalizeU32 = normalizeU32;
10801
14251
  exports.openFatMemoryDocument = openFatMemoryDocument;
10802
14252
  exports.openLayerDocument = openLayerDocument;
10803
14253
  exports.pdfDateToIso = pdfDateToIso;
10804
14254
  exports.readActionModel = readActionModel;
10805
14255
  exports.readAttachmentFileInfo = readAttachmentFileInfo;
14256
+ exports.readRevisions = readRevisions;
14257
+ exports.readSignaturesFromModel = readSignaturesFromModel;
14258
+ exports.readStructure = readStructure;
10806
14259
  exports.throwIfAborted = throwIfAborted;
10807
14260
  exports.writeAttachmentFilePayload = writeAttachmentFilePayload;
10808
14261