@embedpdf/engine-services 3.0.0-next.15 → 3.0.0-next.16

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
@@ -1264,7 +1264,7 @@ var DocumentSession = class {
1264
1264
  * Nothing else creates objects at held numbers, so a held number is free
1265
1265
  * otherwise; the fork checks that again as it creates.
1266
1266
  */
1267
- useObjectNumber(objectNumber) {
1267
+ claimObjectNumber(objectNumber) {
1268
1268
  if (!Number.isInteger(objectNumber) || objectNumber < 1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `${objectNumber} is not an object number`, { details: { field: "objectNumber" } });
1269
1269
  if (this.objectNumberAuthority === "session" && !this.heldObjectNumbers.has(objectNumber)) throw objectNumberUnavailable(objectNumber, "not-held");
1270
1270
  if (this.objectNumbersInUse.includes(objectNumber)) throw objectNumberUnavailable(objectNumber, "taken");
@@ -2420,6 +2420,10 @@ function readBoxes(fn, mem, docPtr, index, rectPtr) {
2420
2420
  art: read(BOX_ART)
2421
2421
  });
2422
2422
  }
2423
+ /** The turn of the page at `index` as it reads: its own `/Rotate`, or the one it inherits. */
2424
+ function rotationAt(runtime, docPtr, index) {
2425
+ return withScratch(runtime.mem, 4, (intPtr) => readRotation(runtime.fn, runtime.mem, docPtr, index, intPtr));
2426
+ }
2423
2427
  /** The page's turn: its `/Rotate` as written, read by the rules of `pageRotationOf`. */
2424
2428
  function readRotation(fn, mem, docPtr, index, intPtr) {
2425
2429
  if (!fn.EPDF_GetPageRotateByIndex(docPtr, index, intPtr)) return 0;
@@ -2698,12 +2702,18 @@ var PagesMutator = class {
2698
2702
  //#endregion
2699
2703
  //#region src/features/pages/PagesExtractor.ts
2700
2704
  const FPDF_NO_INCREMENTAL$3 = 2;
2705
+ const FLAT_NORMALDISPLAY = 0;
2706
+ const FLAT_PRINT = 1;
2707
+ const EXPORT_FLATTENED_ANNOTATIONS = 1;
2708
+ const EXPORT_FLATTENED_FORM_FIELDS = 2;
2701
2709
  /**
2702
2710
  * Export a subset of pages as a standalone PDF. A read over the session:
2703
- * `FPDF_ImportPagesByIndex` copies pages into a scratch document, so the
2704
- * source is untouched — no registry refresh, no layer
2705
- * artifact. Lives next to `PagesReader`/`PagesMutator` (one file per page
2706
- * verb family) so both worker hosts share the code path.
2711
+ * the pages are copied into a scratch document, so the source is untouched —
2712
+ * no registry refresh, no layer artifact. A plain copy is
2713
+ * `FPDF_ImportPagesByIndex`; a flattened one `EPDFDoc_ExportPagesFlattened`,
2714
+ * which copies the pages as they draw. Either keeps the document's title.
2715
+ * Lives next to `PagesReader`/`PagesMutator` (one file per page verb family)
2716
+ * so both worker hosts share the code path.
2707
2717
  */
2708
2718
  var PagesExtractor = class {
2709
2719
  constructor(runtime, session) {
@@ -2712,11 +2722,13 @@ var PagesExtractor = class {
2712
2722
  }
2713
2723
  /**
2714
2724
  * Copy the given pages, in the supplied caller order, into a new
2715
- * document and serialize it. Validation up front: non-empty, no
2716
- * duplicates, every page object number resolvable (`NotFound` from the session on a
2717
- * bad one). The scratch document is always closed, success or throw.
2725
+ * document and serialize it: as they are, or, with `flatten`, as they draw
2726
+ * (checked against the caller's rights before it gets here). Validation up
2727
+ * front: non-empty, no duplicates, every page object number resolvable
2728
+ * (`NotFound` from the session on a bad one). The scratch document is
2729
+ * always closed, success or throw.
2718
2730
  */
2719
- extract(pages, signal) {
2731
+ extract(pages, flatten, signal) {
2720
2732
  throwIfAborted(signal);
2721
2733
  if (pages.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "pages.extract requires at least one page");
2722
2734
  const pageObjectNumbers = this.session.resolvePageRefs(pages);
@@ -2728,14 +2740,20 @@ var PagesExtractor = class {
2728
2740
  const indices = pageObjectNumbers.map((pageObjectNumber) => this.session.recordByObjectNumber(pageObjectNumber).pageIndex);
2729
2741
  const { fn, mem } = this.runtime;
2730
2742
  const srcPtr = this.session.requireDocPtr();
2731
- const destPtr = fn.FPDF_CreateNewDocument();
2732
- if (!destPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDF_CreateNewDocument failed");
2733
2743
  const idxPtr = mem.alloc(4 * indices.length);
2744
+ for (let i = 0; i < indices.length; i++) mem.poke(idxPtr, "i32", indices[i], 4 * i);
2745
+ let destPtr;
2746
+ try {
2747
+ destPtr = flatten ? this.copyFlattened(srcPtr, idxPtr, indices, flatten) : this.copyPlain(srcPtr, idxPtr, indices);
2748
+ } catch (error) {
2749
+ mem.free(idxPtr);
2750
+ throw error;
2751
+ }
2734
2752
  const sizePtr = mem.alloc(8);
2735
2753
  let pdfPtr = null;
2736
2754
  try {
2737
- for (let i = 0; i < indices.length; i++) mem.poke(idxPtr, "i32", indices[i], 4 * i);
2738
- if (!fn.FPDF_ImportPagesByIndex(destPtr, srcPtr, idxPtr, indices.length, 0)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDF_ImportPagesByIndex rejected the request (indices=[${indices.join(",")}])`);
2755
+ const title = readMetaText(fn, mem, srcPtr, "Title");
2756
+ if (title !== null) writeMetaText(fn, mem, destPtr, "Title", title);
2739
2757
  pokeU64(mem, sizePtr, 0);
2740
2758
  pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(destPtr, FPDF_NO_INCREMENTAL$3, sizePtr);
2741
2759
  const size = Number(mem.peek(sizePtr, "i32"));
@@ -2754,6 +2772,28 @@ var PagesExtractor = class {
2754
2772
  fn.FPDF_CloseDocument(destPtr);
2755
2773
  }
2756
2774
  }
2775
+ /** A new document with copies of the pages at `indices`, as they are. */
2776
+ copyPlain(srcPtr, idxPtr, indices) {
2777
+ const { fn } = this.runtime;
2778
+ const destPtr = fn.FPDF_CreateNewDocument();
2779
+ if (!destPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDF_CreateNewDocument failed");
2780
+ if (!fn.FPDF_ImportPagesByIndex(destPtr, srcPtr, idxPtr, indices.length, 0)) {
2781
+ fn.FPDF_CloseDocument(destPtr);
2782
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDF_ImportPagesByIndex rejected the request (indices=[${indices.join(",")}])`);
2783
+ }
2784
+ return destPtr;
2785
+ }
2786
+ /**
2787
+ * A new document with the pages at `indices` as they draw for
2788
+ * `flatten.usage`: what `flatten` includes painted in, nothing interactive
2789
+ * left, layers in their state for the usage.
2790
+ */
2791
+ copyFlattened(srcPtr, idxPtr, indices, flatten) {
2792
+ const flags = (flatten.includeAnnotations ? EXPORT_FLATTENED_ANNOTATIONS : 0) | (flatten.includeFormFields ? EXPORT_FLATTENED_FORM_FIELDS : 0);
2793
+ const destPtr = this.runtime.fn.EPDFDoc_ExportPagesFlattened(srcPtr, idxPtr, indices.length, flatten.usage === "print" ? FLAT_PRINT : FLAT_NORMALDISPLAY, flags);
2794
+ if (!destPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `EPDFDoc_ExportPagesFlattened rejected the request (indices=[${indices.join(",")}])`);
2795
+ return destPtr;
2796
+ }
2757
2797
  };
2758
2798
  //#endregion
2759
2799
  //#region src/features/annotations/internal/write/promoteInlineAnnotations.ts
@@ -2852,7 +2892,7 @@ var PagesInserter = class {
2852
2892
  const destPtr = this.session.requireDocPtr();
2853
2893
  const at = pageIndexAt(this.session, position, fn.FPDF_GetPageCount(destPtr));
2854
2894
  if (objectNumbers && objectNumbers.length !== count) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank needs one object number per page: ${objectNumbers.length} for ${count}`, { details: { field: "objectNumbers" } });
2855
- for (const objectNumber of objectNumbers ?? []) this.session.useObjectNumber(objectNumber);
2895
+ for (const objectNumber of objectNumbers ?? []) this.session.claimObjectNumber(objectNumber);
2856
2896
  for (let i = 0; i < count; i++) {
2857
2897
  const objectNumber = objectNumbers?.[i];
2858
2898
  if (!fn.EPDFPage_InsertBlankRaw(destPtr, at + i, size.width, size.height, objectNumber ?? 0)) {
@@ -3874,6 +3914,11 @@ function toExactArrayBuffer(bytes) {
3874
3914
  const FPDF_RENDER_ANNOT = 1;
3875
3915
  /** `EPDF_RENDER_WIDGETS`: the form fields' widgets. */
3876
3916
  const EPDF_RENDER_WIDGETS = 32768;
3917
+ /**
3918
+ * `FPDF_PRINTING`: the print rules. Layers take their print state, and
3919
+ * annotations and widgets draw by their `print` flag (no-view ones too).
3920
+ */
3921
+ const FPDF_PRINTING = 2048;
3877
3922
  const FPDF_RENDER_TOBECONTINUED = 1;
3878
3923
  const FPDF_RENDER_DONE = 2;
3879
3924
  var PageRenderReader = class {
@@ -3893,6 +3938,8 @@ var PageRenderReader = class {
3893
3938
  const pagePtr = await pool.acquireInSlices(pageObjectNumber, signal, slices);
3894
3939
  try {
3895
3940
  throwIfAborted(signal);
3941
+ (0, _embedpdf_engine_core_runtime.checkRenderUsage)(options, null);
3942
+ const print = options.usage === "print";
3896
3943
  const page = readPageBox(this.runtime, pagePtr);
3897
3944
  const target = resolveTarget(options.target, page);
3898
3945
  const rotation = options.rotation ?? 0;
@@ -3907,6 +3954,7 @@ var PageRenderReader = class {
3907
3954
  let flags = 16;
3908
3955
  if (layers.includeAnnotations) flags |= FPDF_RENDER_ANNOT;
3909
3956
  if (layers.includeFormFields) flags |= EPDF_RENDER_WIDGETS;
3957
+ if (print) flags |= FPDF_PRINTING;
3910
3958
  const raster = await rasterizeAsync(this.runtime, {
3911
3959
  rect: target,
3912
3960
  page,
@@ -4790,7 +4838,7 @@ function extractAttachmentToBuffer(runtime, attachmentPtr, maxDecodedBytes) {
4790
4838
  mem.poke(bufPtrSlot, "ptr", 0);
4791
4839
  mem.poke(sizePtr, "i32", 0);
4792
4840
  mem.poke(statusPtr, "i32", -1);
4793
- if (!fn.EPDFAttachment_ExtractFileToOwnedBuffer(attachmentPtr, BigInt(maxDecodedBytes ?? 0), bufPtrSlot, sizePtr, statusPtr)) throw extractStatusError(Number(mem.peek(statusPtr, "i32")));
4841
+ if (!fn.EPDFAttachment_ExtractFileToOwnedBuffer(attachmentPtr, BigInt(maxDecodedBytes ?? 0), bufPtrSlot, sizePtr, statusPtr)) throw extractStatusError(Number(mem.peek(statusPtr, "i32")), maxDecodedBytes);
4794
4842
  const size = Number(mem.peek(sizePtr, "i32")) >>> 0;
4795
4843
  dataPtr = mem.peek(bufPtrSlot, "ptr");
4796
4844
  if (size === 0 || !dataPtr) return {
@@ -4826,7 +4874,7 @@ function extractAttachmentToFile(runtime, attachmentPtr, path, maxDecodedBytes)
4826
4874
  try {
4827
4875
  mem.poke(sizePtr, "i32", 0);
4828
4876
  mem.poke(statusPtr, "i32", -1);
4829
- if (!fn.EPDFAttachment_ExtractFile(attachmentPtr, writer.ptr, BigInt(maxDecodedBytes ?? 0), sizePtr, statusPtr)) throw extractStatusError(Number(mem.peek(statusPtr, "i32")));
4877
+ if (!fn.EPDFAttachment_ExtractFile(attachmentPtr, writer.ptr, BigInt(maxDecodedBytes ?? 0), sizePtr, statusPtr)) throw extractStatusError(Number(mem.peek(statusPtr, "i32")), maxDecodedBytes);
4830
4878
  return { size: Number(mem.peek(sizePtr, "i32")) >>> 0 };
4831
4879
  } finally {
4832
4880
  writer.close();
@@ -4835,11 +4883,11 @@ function extractAttachmentToFile(runtime, attachmentPtr, path, maxDecodedBytes)
4835
4883
  }
4836
4884
  }
4837
4885
  /** `EPDFAttachmentExtractStatus` (public/fpdf_attachment.h) → EngineError. */
4838
- function extractStatusError(status) {
4886
+ function extractStatusError(status, maxDecodedBytes) {
4839
4887
  switch (status) {
4840
4888
  case 1: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "attachment has no embedded file stream (missing /EF)");
4841
4889
  case 2: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "embedded file stream failed to decode");
4842
- case 3: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge, "embedded file exceeds the configured decoded-size limit");
4890
+ case 3: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge, `the embedded file decodes to more than ${maxDecodedBytes} bytes, the limit`, { details: { limit: maxDecodedBytes } });
4843
4891
  case 4: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "writing the extracted attachment failed");
4844
4892
  default: return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `attachment extraction failed (status ${status})`);
4845
4893
  }
@@ -5662,34 +5710,50 @@ function readLine(fn, mem, annotPtr, base) {
5662
5710
  };
5663
5711
  }
5664
5712
  //#endregion
5665
- //#region src/features/annotations/internal/read/readLinkAnnotation.ts
5713
+ //#region src/features/destinations/createDestination.ts
5666
5714
  /**
5667
- * Link reader: rect/flags/relationship ride the base (a link grouped to an
5668
- * annotation is plain `inReplyTo` + `replyType: 'group'`); this module only
5669
- * materialises the normalized target.
5670
- *
5671
- * One truth, one projection: the target is a pure function of the same
5672
- * payload-carrying action tree the scripting plane reads
5673
- * (`base.actions.activate`) — no second native read exists any more, so the
5674
- * two action-shaped surfaces cannot drift by construction.
5675
- *
5676
- * `/A` precedence, pinned: an activate action that exists but cannot be
5677
- * executed (an `incomplete` tree, a degraded/unreadable root) projects
5678
- * `unsupported` — a broken action is a dead link, never an invitation to
5679
- * guess. The direct `/Dest` (which is data, not an action) is consulted
5680
- * only when no `/A` exists at all.
5715
+ * Build an indirect explicit-destination array for `dest`. The target page
5716
+ * is named by its object number and never loaded: a destination only refers
5717
+ * to the page's dictionary.
5681
5718
  */
5682
- function readLink(fn, mem, annotPtr, base, _rawSubtypeCode, ctx) {
5683
- return {
5684
- ...base,
5685
- subtype: "link",
5686
- target: readLinkTarget(fn, mem, annotPtr, base, ctx)
5687
- };
5719
+ function createDestination(fn, mem, docPtr, dest) {
5720
+ const page = dest.page.objectNumber;
5721
+ const destPtr = dest.kind === "xyz" ? fn.EPDFDest_CreateXYZByObjectNumber(docPtr, page, dest.left != null, dest.left ?? 0, dest.top != null, dest.top ?? 0, dest.zoom != null, dest.zoom ?? 0) : createViewDestination(fn, mem, docPtr, page, dest);
5722
+ if (!destPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `destination page not found: pageObjectNumber=${page}`);
5723
+ return destPtr;
5724
+ }
5725
+ function createViewDestination(fn, mem, docPtr, page, dest) {
5726
+ const params = (() => {
5727
+ switch (dest.kind) {
5728
+ case "fitH":
5729
+ case "fitBH": return dest.top != null ? [dest.top] : [];
5730
+ case "fitV":
5731
+ case "fitBV": return dest.left != null ? [dest.left] : [];
5732
+ case "fitR": return [
5733
+ dest.left,
5734
+ dest.bottom,
5735
+ dest.right,
5736
+ dest.top
5737
+ ];
5738
+ default: return [];
5739
+ }
5740
+ })();
5741
+ const view = VIEW_CODE_BY_KIND[dest.kind];
5742
+ if (!params.length) return fn.EPDFDest_CreateViewByObjectNumber(docPtr, page, view, _embedpdf_engine_runtime.NULL_PTR, 0);
5743
+ return withScratch(mem, params.length * 4, (buf) => {
5744
+ for (let i = 0; i < params.length; i++) mem.poke(buf, "f32", params[i], i * 4);
5745
+ return fn.EPDFDest_CreateViewByObjectNumber(docPtr, page, view, buf, params.length);
5746
+ });
5688
5747
  }
5748
+ //#endregion
5749
+ //#region src/features/destinations/linkTarget.ts
5689
5750
  /**
5690
- * Project the navigation view from a payload-carrying activate tree. Pure —
5691
- * shared with any consumer that wants the root-level navigation reading of a
5692
- * tree (the link plugin's no-actions-plugin fallback uses the same law).
5751
+ * A link target read the way every reader reads one (links, outline items):
5752
+ * the navigation view of a payload-carrying activate tree. Pure.
5753
+ *
5754
+ * The law, enforced at the projection too: never execute — not even
5755
+ * navigate the root of — a tree marked incomplete. A broken action is a
5756
+ * dead target, never an invitation to guess.
5693
5757
  */
5694
5758
  function linkTargetFromActionTree(tree) {
5695
5759
  if (tree.incomplete) return { kind: "unsupported" };
@@ -5720,6 +5784,41 @@ function linkTargetFromActionTree(tree) {
5720
5784
  default: return { kind: "unsupported" };
5721
5785
  }
5722
5786
  }
5787
+ /**
5788
+ * A new indirect action of the document for a writable target: a GoTo to a
5789
+ * new destination, a URI, or a standard named verb. Links and outline items
5790
+ * write their targets with it, so both read back the same way.
5791
+ */
5792
+ function createLinkTargetAction(fn, mem, docPtr, target) {
5793
+ const actionPtr = target.kind === "uri" ? fn.EPDFAction_CreateURI(docPtr, target.uri) : target.kind === "named" ? fn.EPDFAction_CreateNamed(docPtr, target.name) : fn.EPDFAction_CreateGoTo(docPtr, createDestination(fn, mem, docPtr, target.destination));
5794
+ if (!actionPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `failed to create '${target.kind}' action`);
5795
+ return actionPtr;
5796
+ }
5797
+ //#endregion
5798
+ //#region src/features/annotations/internal/read/readLinkAnnotation.ts
5799
+ /**
5800
+ * Link reader: rect/flags/relationship ride the base (a link grouped to an
5801
+ * annotation is plain `inReplyTo` + `replyType: 'group'`); this module only
5802
+ * materialises the normalized target.
5803
+ *
5804
+ * One truth, one projection: the target is a pure function of the same
5805
+ * payload-carrying action tree the scripting plane reads
5806
+ * (`base.actions.activate`) — no second native read exists any more, so the
5807
+ * two action-shaped surfaces cannot drift by construction.
5808
+ *
5809
+ * `/A` precedence, pinned: an activate action that exists but cannot be
5810
+ * executed (an `incomplete` tree, a degraded/unreadable root) projects
5811
+ * `unsupported` — a broken action is a dead link, never an invitation to
5812
+ * guess. The direct `/Dest` (which is data, not an action) is consulted
5813
+ * only when no `/A` exists at all.
5814
+ */
5815
+ function readLink(fn, mem, annotPtr, base, _rawSubtypeCode, ctx) {
5816
+ return {
5817
+ ...base,
5818
+ subtype: "link",
5819
+ target: readLinkTarget(fn, mem, annotPtr, base, ctx)
5820
+ };
5821
+ }
5723
5822
  function readLinkTarget(fn, mem, annotPtr, base, ctx) {
5724
5823
  const activate = base.actions?.activate;
5725
5824
  if (activate) return linkTargetFromActionTree(activate);
@@ -8525,42 +8624,6 @@ function isLineSubtype(subtype) {
8525
8624
  return subtype === "line";
8526
8625
  }
8527
8626
  //#endregion
8528
- //#region src/features/destinations/createDestination.ts
8529
- /**
8530
- * Build an indirect explicit-destination array for `dest`. The target page
8531
- * is named by its object number and never loaded: a destination only refers
8532
- * to the page's dictionary.
8533
- */
8534
- function createDestination(fn, mem, docPtr, dest) {
8535
- const page = dest.page.objectNumber;
8536
- const destPtr = dest.kind === "xyz" ? fn.EPDFDest_CreateXYZByObjectNumber(docPtr, page, dest.left != null, dest.left ?? 0, dest.top != null, dest.top ?? 0, dest.zoom != null, dest.zoom ?? 0) : createViewDestination(fn, mem, docPtr, page, dest);
8537
- if (!destPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `destination page not found: pageObjectNumber=${page}`);
8538
- return destPtr;
8539
- }
8540
- function createViewDestination(fn, mem, docPtr, page, dest) {
8541
- const params = (() => {
8542
- switch (dest.kind) {
8543
- case "fitH":
8544
- case "fitBH": return dest.top != null ? [dest.top] : [];
8545
- case "fitV":
8546
- case "fitBV": return dest.left != null ? [dest.left] : [];
8547
- case "fitR": return [
8548
- dest.left,
8549
- dest.bottom,
8550
- dest.right,
8551
- dest.top
8552
- ];
8553
- default: return [];
8554
- }
8555
- })();
8556
- const view = VIEW_CODE_BY_KIND[dest.kind];
8557
- if (!params.length) return fn.EPDFDest_CreateViewByObjectNumber(docPtr, page, view, _embedpdf_engine_runtime.NULL_PTR, 0);
8558
- return withScratch(mem, params.length * 4, (buf) => {
8559
- for (let i = 0; i < params.length; i++) mem.poke(buf, "f32", params[i], i * 4);
8560
- return fn.EPDFDest_CreateViewByObjectNumber(docPtr, page, view, buf, params.length);
8561
- });
8562
- }
8563
- //#endregion
8564
8627
  //#region src/features/annotations/internal/write/writeLinkAnnotation.ts
8565
8628
  /**
8566
8629
  * Link writer: rect + the `/A` action. `goto`, `uri` and the standard
@@ -8603,8 +8666,7 @@ function isLinkSubtype(subtype) {
8603
8666
  function applyLinkTarget(fn, mem, annotPtr, target, ctx) {
8604
8667
  const docPtr = ctx?.docPtr;
8605
8668
  if (!docPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "writing a link target requires a document pointer on the write context");
8606
- const actionPtr = target.kind === "uri" ? fn.EPDFAction_CreateURI(docPtr, target.uri) : target.kind === "named" ? fn.EPDFAction_CreateNamed(docPtr, target.name) : fn.EPDFAction_CreateGoTo(docPtr, createDestination(fn, mem, docPtr, target.destination));
8607
- if (!actionPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `failed to create '${target.kind}' action`);
8669
+ const actionPtr = createLinkTargetAction(fn, mem, docPtr, target);
8608
8670
  if (!fn.EPDFAnnot_SetAction(annotPtr, actionPtr)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFAnnot_SetAction failed for link");
8609
8671
  }
8610
8672
  //#endregion
@@ -9800,7 +9862,7 @@ var AnnotationBatchApplier = class {
9800
9862
  ...given,
9801
9863
  nm: (0, _embedpdf_engine_core_runtime.generateUuidV7)()
9802
9864
  };
9803
- if (create.objectNumber !== void 0) labelled(create.label, () => this.session.useObjectNumber(create.objectNumber));
9865
+ if (create.objectNumber !== void 0) labelled(create.label, () => this.session.claimObjectNumber(create.objectNumber));
9804
9866
  const replyTo = create.replyTo && {
9805
9867
  type: create.replyTo.type,
9806
9868
  to: labelled(create.label, () => this.linkTarget(create.replyTo.to, create.page, creates.length, "reply"))
@@ -9818,7 +9880,7 @@ var AnnotationBatchApplier = class {
9818
9880
  throwIfAborted(signal);
9819
9881
  if (prepared.length === 0) return {
9820
9882
  created: [],
9821
- meta: metaOf(this.session.writeStamp(), [], [])
9883
+ meta: metaOf$1(this.session.writeStamp(), [], [])
9822
9884
  };
9823
9885
  const placed = prepared.map(({ create, record, ctx, draft }) => {
9824
9886
  throwIfAborted(signal);
@@ -9864,7 +9926,7 @@ var AnnotationBatchApplier = class {
9864
9926
  joinWidgetFieldNumbers(this.runtime, this.session, created);
9865
9927
  return {
9866
9928
  created,
9867
- meta: metaOf(this.session.writeStamp(), placed, linked)
9929
+ meta: metaOf$1(this.session.writeStamp(), placed, linked)
9868
9930
  };
9869
9931
  }
9870
9932
  /**
@@ -9935,7 +9997,7 @@ var AnnotationBatchApplier = class {
9935
9997
  * and each it linked a popup to. Creates only append, so no other annotation
9936
9998
  * moves.
9937
9999
  */
9938
- function metaOf(stamp, placed, linked) {
10000
+ function metaOf$1(stamp, placed, linked) {
9939
10001
  return {
9940
10002
  affectedPages: [...new Set(placed.map((at) => at.pageObjectNumber))].map((page) => (0, _embedpdf_engine_core_runtime.toPageRef)(page)),
9941
10003
  cacheDelta: null,
@@ -10783,6 +10845,16 @@ function importAnnots(runtime, docPtr, pageIndex, capture) {
10783
10845
  function exportDict(runtime, docPtr, objectNumber, deepKeys) {
10784
10846
  return takeOwned(runtime, `object ${objectNumber}`, (sizePtr) => runtime.fn.EPDFDoc_ExportDictRawToOwnedBuffer(docPtr, objectNumber, deepKeys.join(" "), sizePtr));
10785
10847
  }
10848
+ /**
10849
+ * The dictionary `objectNumber` as bytes a guard compares: without
10850
+ * `skipKeys`, and under `deepKeys` the objects the layer holds, each stream
10851
+ * as it reads. They stay the same while it reads the same, also after a save
10852
+ * encoded its streams again or the layer learned its file's hash; never
10853
+ * imported.
10854
+ */
10855
+ function canonicalDict(runtime, docPtr, objectNumber, deepKeys, skipKeys = []) {
10856
+ return takeOwned(runtime, `object ${objectNumber}`, (sizePtr) => runtime.fn.EPDFDoc_ExportDictCanonicalToOwnedBuffer(docPtr, objectNumber, deepKeys.join(" "), skipKeys.join(" "), sizePtr));
10857
+ }
10786
10858
  /** Puts the dictionary of `capture` back as it was: exactly its keys. */
10787
10859
  function importDict(runtime, docPtr, capture) {
10788
10860
  withBytes(runtime, capture, (ptr) => {
@@ -10799,6 +10871,49 @@ function importField(runtime, docPtr, capture) {
10799
10871
  if (!runtime.fn.EPDFForm_ImportFieldRaw(docPtr, ptr, capture.byteLength)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFForm_ImportFieldRaw refused a capture");
10800
10872
  });
10801
10873
  }
10874
+ /** An outline item as its delete removes it: the item, the items under it, its place. */
10875
+ function exportOutlineItem(runtime, docPtr, objectNumber) {
10876
+ return takeOwned(runtime, `outline item ${objectNumber}`, (sizePtr) => runtime.fn.EPDFOutline_ExportItemRawToOwnedBuffer(docPtr, objectNumber, sizePtr));
10877
+ }
10878
+ /** Puts the outline item of `capture` back, with the items under it, at its place. */
10879
+ function importOutlineItem(runtime, docPtr, capture) {
10880
+ withBytes(runtime, capture, (ptr) => {
10881
+ if (!runtime.fn.EPDFOutline_ImportItemRaw(docPtr, ptr, capture.byteLength)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFOutline_ImportItemRaw refused a capture");
10882
+ });
10883
+ }
10884
+ /** A page as its delete removes it: the page, what it reaches, its names, its place. */
10885
+ function exportPage(runtime, docPtr, pageObjectNumber) {
10886
+ return takeOwned(runtime, `page ${pageObjectNumber}`, (sizePtr) => runtime.fn.EPDFPage_ExportRawToOwnedBuffer(docPtr, pageObjectNumber, sizePtr));
10887
+ }
10888
+ /** Puts the page of `capture` back into the page tree, at its place, with its names. */
10889
+ function importPage(runtime, docPtr, capture) {
10890
+ withBytes(runtime, capture, (ptr) => {
10891
+ if (!runtime.fn.EPDFPage_ImportRaw(docPtr, ptr, capture.byteLength)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFPage_ImportRaw refused a capture");
10892
+ });
10893
+ }
10894
+ /** The embedded file under `key` as its delete removes it: its key, its file specification, its streams. */
10895
+ function exportAttachment(runtime, docPtr, key) {
10896
+ return takeOwned(runtime, `attachment '${key}'`, (sizePtr) => withUtf16String(runtime.mem, key, (keyPtr) => runtime.fn.EPDFAttachment_ExportRawToOwnedBuffer(docPtr, keyPtr, sizePtr)));
10897
+ }
10898
+ /**
10899
+ * The embedded file under `key` as bytes a guard compares: the same while it
10900
+ * is the same file, as it was (its number included); null when no file has
10901
+ * the key. Never imported.
10902
+ */
10903
+ function canonicalAttachment(runtime, docPtr, key) {
10904
+ try {
10905
+ return takeOwned(runtime, `attachment '${key}'`, (sizePtr) => withUtf16String(runtime.mem, key, (keyPtr) => runtime.fn.EPDFAttachment_ExportCanonicalToOwnedBuffer(docPtr, keyPtr, sizePtr)));
10906
+ } catch (error) {
10907
+ if (_embedpdf_engine_core_runtime.EngineError.is(error)) return null;
10908
+ throw error;
10909
+ }
10910
+ }
10911
+ /** Puts the embedded file of `capture` back under its key. */
10912
+ function importAttachment(runtime, docPtr, capture) {
10913
+ withBytes(runtime, capture, (ptr) => {
10914
+ if (!runtime.fn.EPDFAttachment_ImportRaw(docPtr, ptr, capture.byteLength)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFAttachment_ImportRaw refused a capture");
10915
+ });
10916
+ }
10802
10917
  /** The bytes of a buffer an export returned, which is then released. */
10803
10918
  function takeOwned(runtime, what, call) {
10804
10919
  const { fn, mem } = runtime;
@@ -10877,6 +10992,14 @@ function assertExpected(opIndex, current, expected) {
10877
10992
  } });
10878
10993
  }
10879
10994
  //#endregion
10995
+ //#region src/shared/bytesEqual.ts
10996
+ /** Whether two byte arrays hold the same bytes. */
10997
+ function bytesEqual(a, b) {
10998
+ if (a.byteLength !== b.byteLength) return false;
10999
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
11000
+ return true;
11001
+ }
11002
+ //#endregion
10880
11003
  //#region src/features/changes/internal/objectCaptures.ts
10881
11004
  /**
10882
11005
  * Captures each dictionary before an op writes it: every key, and under
@@ -10895,18 +11018,21 @@ function captureBefore(runtime, session, objects) {
10895
11018
  }
10896
11019
  return [...captured.values()];
10897
11020
  }
10898
- /** Completes captures once the op has written: each dictionary as the op left it, the guard. */
11021
+ /**
11022
+ * Completes captures once the op has written: each dictionary as the op left
11023
+ * it, the guard, in canonical bytes (`canonicalDict`).
11024
+ */
10899
11025
  function captureAfter(runtime, session, pending) {
10900
11026
  return pending.map((object) => ({
10901
11027
  ...object,
10902
- after: exportDict(runtime, session.requireDocPtr(), object.objectNumber, [])
11028
+ after: canonicalDict(runtime, session.requireDocPtr(), object.objectNumber, [])
10903
11029
  }));
10904
11030
  }
10905
11031
  /** Whether every dictionary still reads exactly as the op left it. */
10906
11032
  function unchangedSince(runtime, session, objects) {
10907
11033
  return objects.every((object) => {
10908
- const now = tryExportDict(runtime, session, object.objectNumber, []);
10909
- return now !== null && sameBytes(now, object.after);
11034
+ const now = tryCanonicalDict(runtime, session, object.objectNumber);
11035
+ return now !== null && bytesEqual(now, object.after);
10910
11036
  });
10911
11037
  }
10912
11038
  /**
@@ -10921,6 +11047,15 @@ function revertObjects(runtime, session, objects) {
10921
11047
  session.invalidateDerived();
10922
11048
  return captureAfter(runtime, session, redo);
10923
11049
  }
11050
+ /** A dictionary's canonical bytes, or null when there is none at that number. */
11051
+ function tryCanonicalDict(runtime, session, objectNumber) {
11052
+ try {
11053
+ return canonicalDict(runtime, session.requireDocPtr(), objectNumber, []);
11054
+ } catch (error) {
11055
+ if (_embedpdf_engine_core_runtime.EngineError.is(error)) return null;
11056
+ throw error;
11057
+ }
11058
+ }
10924
11059
  /** The capture of a dictionary, or null when there is none at that number. */
10925
11060
  function tryExportDict(runtime, session, objectNumber, deep) {
10926
11061
  try {
@@ -10930,11 +11065,6 @@ function tryExportDict(runtime, session, objectNumber, deep) {
10930
11065
  throw error;
10931
11066
  }
10932
11067
  }
10933
- function sameBytes(a, b) {
10934
- if (a.byteLength !== b.byteLength) return false;
10935
- for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
10936
- return true;
10937
- }
10938
11068
  //#endregion
10939
11069
  //#region src/features/annotations/internal/stackingOrder.ts
10940
11070
  /**
@@ -11177,7 +11307,7 @@ function updateAnnotation(ctx, op, opIndex) {
11177
11307
  if (current.subtype === "widget") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "a widget is the form's: change its place and look with doc.forms.updateWidget");
11178
11308
  (0, _embedpdf_engine_core_runtime.authorizeUnprotected)(ctx.authority, (0, _embedpdf_engine_core_runtime.annotationWriteCapability)(current.subtype));
11179
11309
  assertExpected(opIndex, current, op.expect);
11180
- return writeUpdate$1(ctx, op.ref, op.patch, "annotations.update", op.resources);
11310
+ return writeUpdate$2(ctx, op.ref, op.patch, "annotations.update", op.resources);
11181
11311
  }
11182
11312
  /** `annotations.delete`; its reverse brings back everything it removed. */
11183
11313
  function deleteAnnotation(ctx, op, opIndex) {
@@ -11185,7 +11315,7 @@ function deleteAnnotation(ctx, op, opIndex) {
11185
11315
  if (current.subtype === "widget") throw widgetDeleteRefused(current);
11186
11316
  (0, _embedpdf_engine_core_runtime.authorizeUnprotected)(ctx.authority, (0, _embedpdf_engine_core_runtime.annotationWriteCapability)(current.subtype));
11187
11317
  assertExpected(opIndex, current, op.expect);
11188
- return writeDelete$1(ctx, op.ref);
11318
+ return writeDelete$4(ctx, op.ref);
11189
11319
  }
11190
11320
  /** Why a widget isn't deleted as an annotation, naming the form op that removes it. */
11191
11321
  function widgetDeleteRefused(widget) {
@@ -11252,7 +11382,7 @@ function revertAnnotation(ctx, step) {
11252
11382
  const kept = keys.filter((key) => valuesEqual(now[key], left[key]));
11253
11383
  if (kept.length === 0) return leftAlone(ctx, reports);
11254
11384
  const writable = (key, value) => key === "actions" && current.subtype === "widget" ? (0, _embedpdf_engine_core_runtime.widgetActionsOf)(value) : value;
11255
- const done = writeUpdate$1(ctx, ref, Object.fromEntries(kept.map((key) => [key, writable(key, restore[key])])), reports);
11385
+ const done = writeUpdate$2(ctx, ref, Object.fromEntries(kept.map((key) => [key, writable(key, restore[key])])), reports);
11256
11386
  const skipped = keys.filter((key) => !kept.includes(key));
11257
11387
  const item = done.item;
11258
11388
  if (skipped.length === 0 || (0, _embedpdf_engine_core_runtime.isSkippedItem)(item) || item.type !== reports) return done;
@@ -11276,7 +11406,7 @@ function removeAnnotation(ctx, step) {
11276
11406
  }))) return leftAlone(ctx, "annotations.delete");
11277
11407
  for (const capability of (0, _embedpdf_engine_core_runtime.annotationWriteCapabilities)(members)) (0, _embedpdf_engine_core_runtime.authorizeUnprotected)(ctx.authority, capability);
11278
11408
  if (step.rights) authorizeImportRights(ctx.authority);
11279
- return writeDelete$1(ctx, step.ref, step.rights);
11409
+ return writeDelete$4(ctx, step.ref, step.rights);
11280
11410
  }
11281
11411
  /**
11282
11412
  * Brings back what a delete removed, at the same numbers and positions. Skipped
@@ -11397,7 +11527,7 @@ function reorderItem(ctx, family, page, order, moved) {
11397
11527
  * An update of `ref` with `patch`, recording the dictionaries it writes, and
11398
11528
  * reported as `reports`: an annotation's update, or a widget's form op.
11399
11529
  */
11400
- function writeUpdate$1(ctx, ref, patch, reports, resources) {
11530
+ function writeUpdate$2(ctx, ref, patch, reports, resources) {
11401
11531
  promoteInlineAnnotations(ctx.runtime, ctx.session, ref.page.objectNumber);
11402
11532
  const before = readAnnotation(ctx, ref);
11403
11533
  const written = before.subtype === "widget" ? (0, _embedpdf_engine_core_runtime.fitWidgetToFamily)(before.fieldFamily, patch) : patch;
@@ -11445,7 +11575,7 @@ function updatedItem(ctx, reports, ref, result) {
11445
11575
  }
11446
11576
  /** A delete of `ref` and what goes with it, capturing them first. */
11447
11577
  /** Deletes `ref` and what goes with it; `rights` is what a step under an import's rights checked. */
11448
- function writeDelete$1(ctx, ref, rights) {
11578
+ function writeDelete$4(ctx, ref, rights) {
11449
11579
  const page = ref.page.objectNumber;
11450
11580
  promoteInlineAnnotations(ctx.runtime, ctx.session, page);
11451
11581
  const members = (0, _embedpdf_engine_core_runtime.deletedWith)(listPage(ctx, page), ref);
@@ -11562,66 +11692,314 @@ function objectNumberOf$1(ctx, ref) {
11562
11692
  }
11563
11693
  }
11564
11694
  //#endregion
11565
- //#region src/features/forms/internal/authorWidget.ts
11566
- const WIDGET_SUBTYPE_CODE = 20;
11695
+ //#region src/features/attachments/AttachmentReader.ts
11567
11696
  /**
11568
- * Birth a widget through the annotation plane (EPDFPage_CreateAnnotRaw -
11569
- * indirect, durable object number, no page load), place it and turn it
11570
- * (`/MK /R`), style it with the placement's style fields through the
11571
- * widget-plane writer (`applyWidgetStyle` - the same code the widget
11572
- * annotation kind uses for create/patch), and give it the placement's
11573
- * actions. Like every annotation the engine makes, it gets a fresh UUIDv7
11574
- * `/NM`. Returns the widget's object number (`objectNumber`, which the
11575
- * session checked, or the next free one), ready for EPDFForm_AttachWidget
11576
- * adoption, which draws its appearance turned.
11697
+ * Read-only access to embedded files, at both of their homes: the
11698
+ * document catalog's `/EmbeddedFiles` name tree (list / readFile by
11699
+ * index) and a FileAttachment annotation's `/FS` (readAnnotationFile by
11700
+ * ref). Pure reads over the session — no layer artifacts (the
11701
+ * `PagesExtractor` shape).
11702
+ *
11703
+ * Byte delivery mirrors the request's `path?`: absent → a standalone
11704
+ * buffer for the transfer list (browser); present → the decoded file is
11705
+ * streamed to `path` via `FPDF_FILEWRITE` (server), and only metadata
11706
+ * rides the response.
11577
11707
  */
11578
- function createUnattachedWidget(runtime, docPtr, pageIndex, placement, objectNumber) {
11579
- const { fn, mem } = runtime;
11580
- const annotPtr = fn.EPDFPage_CreateAnnotRaw(docPtr, pageIndex, WIDGET_SUBTYPE_CODE, objectNumber ?? 0);
11581
- if (annotPtr === _embedpdf_engine_runtime.NULL_PTR && objectNumber !== void 0) throw objectNumberUnavailable(objectNumber, "taken");
11582
- if (annotPtr === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to create widget annotation");
11583
- try {
11584
- writeAnnotString(fn, mem, annotPtr, "NM", (0, _embedpdf_engine_core_runtime.generateUuidV7)());
11585
- const { page: _page, rect, rotation, exportValue: _exportValue, actions, ...style } = placement;
11586
- setAnnotRect(fn, mem, annotPtr, rect);
11587
- if (rotation) setWidgetTurn(fn, annotPtr, rotation);
11588
- if (Object.keys(style).length > 0) applyWidgetStyle(fn, mem, annotPtr, style);
11589
- if (actions) writeWidgetActions(runtime, docPtr, annotPtr, actions);
11590
- const widgetObjectNumber = fn.EPDFAnnot_GetObjectNumber(annotPtr);
11591
- if (widgetObjectNumber <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "widget annotation has no object number");
11592
- return widgetObjectNumber;
11593
- } finally {
11594
- fn.FPDFPage_CloseAnnot(annotPtr);
11708
+ var AttachmentReader = class {
11709
+ constructor(runtime, session) {
11710
+ this.runtime = runtime;
11711
+ this.session = session;
11595
11712
  }
11596
- }
11597
- //#endregion
11598
- //#region src/features/forms/internal/fieldFlagBits.ts
11599
- /**
11600
- * /Ff bits writable through drafts and patches (1-based spec bit numbers).
11601
- * Family-defining bits (Radio 1<<15, Pushbutton 1<<16, Combo 1<<17) are set
11602
- * once by EPDFForm_CreateField and immutable afterwards - the native
11603
- * setter rejects them.
11604
- */
11605
- const FIELD_FLAG_BITS = {
11606
- readOnly: 1,
11607
- required: 2,
11608
- noExport: 4,
11609
- multiline: 4096,
11610
- password: 8192,
11611
- noToggleToOff: 16384,
11612
- edit: 1 << 18,
11613
- multiSelect: 1 << 21,
11614
- comb: 1 << 24,
11615
- radiosInUnison: 1 << 25
11616
- };
11617
- /**
11618
- * Fold optional booleans into set/clear masks: `true` sets the bit,
11619
- * `false` clears it, `undefined` leaves it alone.
11620
- */
11621
- function flagMasks(flags) {
11622
- let setBits = 0;
11623
- let clearBits = 0;
11624
- for (const [name, bit] of Object.entries(FIELD_FLAG_BITS)) {
11713
+ /** Snapshot of the `/EmbeddedFiles` name tree, in tree (key-sorted) order. */
11714
+ list(signal) {
11715
+ throwIfAborted(signal);
11716
+ const { fn, mem } = this.runtime;
11717
+ const docPtr = this.session.requireDocPtr();
11718
+ const count = fn.FPDFDoc_GetAttachmentCount(docPtr);
11719
+ const items = [];
11720
+ for (let index = 0; index < count; index++) {
11721
+ const attachmentPtr = fn.FPDFDoc_GetAttachment(docPtr, index);
11722
+ if (!attachmentPtr) continue;
11723
+ const key = readAttachmentKey(fn, mem, docPtr, index) ?? "";
11724
+ items.push({
11725
+ ...readAttachmentFileInfo(fn, mem, attachmentPtr),
11726
+ ref: (0, _embedpdf_engine_core_runtime.toAttachmentRef)(key),
11727
+ index
11728
+ });
11729
+ }
11730
+ return items;
11731
+ }
11732
+ /** Decode one document-level embedded file, addressed by key. */
11733
+ readFile(ref, path, maxDecodedBytes, signal) {
11734
+ throwIfAborted(signal);
11735
+ const { fn, mem } = this.runtime;
11736
+ const docPtr = this.session.requireDocPtr();
11737
+ const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
11738
+ if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no embedded file with key '${ref.key}' in the document`);
11739
+ const attachmentPtr = fn.FPDFDoc_GetAttachment(docPtr, index);
11740
+ if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `embedded file with key '${ref.key}' resolved to index ${index} but could not be opened`);
11741
+ const info = readAttachmentFileInfo(fn, mem, attachmentPtr);
11742
+ return this.extract(attachmentPtr, info.name, info.mimeType ?? void 0, path, maxDecodedBytes);
11743
+ }
11744
+ /** Decode the file embedded in a FileAttachment annotation's `/FS`. */
11745
+ /** A file attachment annotation's file; a page not parsed yet loads in slices. */
11746
+ async readAnnotationFile(pageObjectNumber, ref, path, maxDecodedBytes, signal, slices) {
11747
+ throwIfAborted(signal);
11748
+ const { fn, mem } = this.runtime;
11749
+ const pool = this.session.pagePool();
11750
+ const pagePtr = await pool.acquireInSlices(pageObjectNumber, signal, slices);
11751
+ let annotPtr = null;
11752
+ try {
11753
+ annotPtr = resolveAnnotPtr(this.runtime, this.session, pagePtr, ref);
11754
+ const attachmentPtr = fn.FPDFAnnot_GetFileAttachment(annotPtr);
11755
+ if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "annotation is not a file attachment or carries no embedded file (/FS)");
11756
+ const info = readAttachmentFileInfo(fn, mem, attachmentPtr);
11757
+ return this.extract(attachmentPtr, info.name, info.mimeType ?? void 0, path, maxDecodedBytes);
11758
+ } finally {
11759
+ if (annotPtr !== null) fn.FPDFPage_CloseAnnot(annotPtr);
11760
+ pool.release(pageObjectNumber);
11761
+ }
11762
+ }
11763
+ extract(attachmentPtr, name, mimeType, path, maxDecodedBytes) {
11764
+ if (path !== void 0) {
11765
+ const { size } = extractAttachmentToFile(this.runtime, attachmentPtr, path, maxDecodedBytes);
11766
+ return {
11767
+ name,
11768
+ ...mimeType ? { mimeType } : {},
11769
+ size,
11770
+ path
11771
+ };
11772
+ }
11773
+ const { bytes, size } = extractAttachmentToBuffer(this.runtime, attachmentPtr, maxDecodedBytes);
11774
+ return {
11775
+ name,
11776
+ ...mimeType ? { mimeType } : {},
11777
+ size,
11778
+ bytes
11779
+ };
11780
+ }
11781
+ };
11782
+ //#endregion
11783
+ //#region src/features/attachments/AttachmentMutator.ts
11784
+ /**
11785
+ * Create/delete writes over the document catalog's `/EmbeddedFiles` name
11786
+ * tree, sharing the write path (`writeAttachmentFilePayload`) with the
11787
+ * file-attachment annotation writer. The change ops run them
11788
+ * (`attachmentChanges.ts`), which decide what an undo needs.
11789
+ *
11790
+ * Identity is the name-tree key (unique by construction). Note the
11791
+ * tree is key-sorted: both writes shift other entries' indices; keys
11792
+ * never move.
11793
+ */
11794
+ var AttachmentMutator = class {
11795
+ constructor(runtime, session) {
11796
+ this.runtime = runtime;
11797
+ this.session = session;
11798
+ }
11799
+ /**
11800
+ * Create an embedded file from `bytes`. `file.name` becomes the tree key
11801
+ * (PDFium writes key = /UF = /F); a duplicate key rejects with
11802
+ * `InvalidArg`. The result carries the read-back entry,
11803
+ * `checksum`/`size`/dates included.
11804
+ */
11805
+ create(file, bytes, signal) {
11806
+ throwIfAborted(signal);
11807
+ const { fn, mem } = this.runtime;
11808
+ const docPtr = this.session.requireDocPtr();
11809
+ if (!bytes) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "an attachments.create op needs its bytes as its file resource", { details: { field: "resources.file" } });
11810
+ if (file.name.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "attachment file requires a name");
11811
+ if (resolveAttachmentIndex(fn, mem, docPtr, {
11812
+ kind: "key",
11813
+ key: file.name
11814
+ }) !== -1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `an embedded file with key '${file.name}' already exists — keys are unique; rename and retry`);
11815
+ throwIfAborted(signal);
11816
+ const namePtr = mem.writeU16String(file.name);
11817
+ let attachmentPtr;
11818
+ try {
11819
+ attachmentPtr = fn.FPDFDoc_AddAttachment(docPtr, namePtr);
11820
+ } finally {
11821
+ mem.free(namePtr);
11822
+ }
11823
+ if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFDoc_AddAttachment returned NULL");
11824
+ writeAttachmentFilePayload(fn, mem, attachmentPtr, docPtr, file, {
11825
+ bytes,
11826
+ ...file.mimeType !== void 0 ? { mimeType: file.mimeType } : {},
11827
+ name: file.name
11828
+ });
11829
+ const ref = (0, _embedpdf_engine_core_runtime.toAttachmentRef)(file.name);
11830
+ const attachment = this.find(ref);
11831
+ if (!attachment) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "created attachment did not resolve by key after the write");
11832
+ return {
11833
+ attachment,
11834
+ meta: this.meta(ref)
11835
+ };
11836
+ }
11837
+ /** The entry under `ref`'s key as it reads now; `null` when no file has the key. */
11838
+ find(ref) {
11839
+ const { fn, mem } = this.runtime;
11840
+ const docPtr = this.session.requireDocPtr();
11841
+ const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
11842
+ const attachmentPtr = index < 0 ? null : fn.FPDFDoc_GetAttachment(docPtr, index);
11843
+ if (!attachmentPtr) return null;
11844
+ return {
11845
+ ...readAttachmentFileInfo(fn, mem, attachmentPtr),
11846
+ ref,
11847
+ index
11848
+ };
11849
+ }
11850
+ /** A write's meta, naming the file it changed. Cloud coherence pins are a server concern. */
11851
+ meta(ref) {
11852
+ return {
11853
+ affectedPages: [],
11854
+ cacheDelta: null,
11855
+ ...this.session.writeStamp(),
11856
+ changed: [ref]
11857
+ };
11858
+ }
11859
+ /** Delete an embedded file by key. Unlinks the tree entry only. */
11860
+ delete(ref, signal) {
11861
+ throwIfAborted(signal);
11862
+ const { fn, mem } = this.runtime;
11863
+ const docPtr = this.session.requireDocPtr();
11864
+ const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
11865
+ if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no embedded file with key '${ref.key}' in the document`);
11866
+ if (!fn.FPDFDoc_DeleteAttachment(docPtr, index)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFDoc_DeleteAttachment returned false");
11867
+ return { meta: this.meta(ref) };
11868
+ }
11869
+ };
11870
+ //#endregion
11871
+ //#region src/features/changes/internal/attachmentChanges.ts
11872
+ const mutatorOf$2 = (ctx) => new AttachmentMutator(ctx.runtime, ctx.session);
11873
+ const authorize$2 = (ctx) => (0, _embedpdf_engine_core_runtime.authorizeCapability)(ctx.authority, "doc.attachments.modify");
11874
+ /** `attachments.create`; its reverse deletes the file while it is still the file this made. */
11875
+ function createAttachment(ctx, op) {
11876
+ authorize$2(ctx);
11877
+ const result = mutatorOf$2(ctx).create(op.file, op.resources.file, ctx.signal);
11878
+ return {
11879
+ item: {
11880
+ type: "attachments.create",
11881
+ ...result
11882
+ },
11883
+ reverse: [removeStepOf$1(ctx, result.attachment.ref)]
11884
+ };
11885
+ }
11886
+ /** `attachments.delete`, captured in its transaction so an undo brings back the same file. */
11887
+ function deleteAttachment(ctx, op) {
11888
+ authorize$2(ctx);
11889
+ return writeDelete$3(ctx, op.ref);
11890
+ }
11891
+ /** Deletes a file a create or a restore brought, while it reads as the change left it. */
11892
+ function removeAttachment(ctx, step) {
11893
+ const now = canonicalAttachment(ctx.runtime, ctx.session.requireDocPtr(), step.ref.key);
11894
+ if (!now || !bytesEqual(now, step.left)) return leftAlone(ctx, "attachments.delete");
11895
+ authorize$2(ctx);
11896
+ return writeDelete$3(ctx, step.ref);
11897
+ }
11898
+ /** Brings back a file a delete removed, while no file has its key. */
11899
+ function restoreAttachment(ctx, step) {
11900
+ const mutator = mutatorOf$2(ctx);
11901
+ if (mutator.find(step.ref)) return leftAlone(ctx, "attachments.restore");
11902
+ authorize$2(ctx);
11903
+ importAttachment(ctx.runtime, ctx.session.requireDocPtr(), step.capture);
11904
+ const attachment = mutator.find(step.ref);
11905
+ if (!attachment) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `attachment '${step.ref.key}' did not come back`);
11906
+ return {
11907
+ item: {
11908
+ type: "attachments.restore",
11909
+ attachment,
11910
+ meta: mutator.meta(step.ref)
11911
+ },
11912
+ reverse: [removeStepOf$1(ctx, step.ref)]
11913
+ };
11914
+ }
11915
+ /** Deletes the file under `ref`, captured first so its restore brings it back. */
11916
+ function writeDelete$3(ctx, ref) {
11917
+ const mutator = mutatorOf$2(ctx);
11918
+ if (!mutator.find(ref)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no embedded file with key '${ref.key}' in the document`, { details: { field: "ref" } });
11919
+ const capture = exportAttachment(ctx.runtime, ctx.session.requireDocPtr(), ref.key);
11920
+ return {
11921
+ item: {
11922
+ type: "attachments.delete",
11923
+ ...mutator.delete(ref, ctx.signal)
11924
+ },
11925
+ reverse: [{
11926
+ kind: "attachments.restore",
11927
+ ref,
11928
+ capture
11929
+ }]
11930
+ };
11931
+ }
11932
+ /** The step that deletes the file under `ref` again while it reads as it does now. */
11933
+ function removeStepOf$1(ctx, ref) {
11934
+ const left = canonicalAttachment(ctx.runtime, ctx.session.requireDocPtr(), ref.key);
11935
+ if (!left) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `attachment '${ref.key}' is not there to guard`);
11936
+ return {
11937
+ kind: "attachments.remove",
11938
+ ref,
11939
+ left
11940
+ };
11941
+ }
11942
+ //#endregion
11943
+ //#region src/features/forms/internal/authorWidget.ts
11944
+ const WIDGET_SUBTYPE_CODE = 20;
11945
+ /**
11946
+ * Birth a widget through the annotation plane (EPDFPage_CreateAnnotRaw -
11947
+ * indirect, durable object number, no page load), place it and turn it
11948
+ * (`/MK /R`), style it with the placement's style fields through the
11949
+ * widget-plane writer (`applyWidgetStyle` - the same code the widget
11950
+ * annotation kind uses for create/patch), and give it the placement's
11951
+ * actions. Like every annotation the engine makes, it gets a fresh UUIDv7
11952
+ * `/NM`. Returns the widget's object number (`objectNumber`, which the
11953
+ * session checked, or the next free one), ready for EPDFForm_AttachWidget
11954
+ * adoption, which draws its appearance turned.
11955
+ */
11956
+ function createUnattachedWidget(runtime, docPtr, pageIndex, placement, objectNumber) {
11957
+ const { fn, mem } = runtime;
11958
+ const annotPtr = fn.EPDFPage_CreateAnnotRaw(docPtr, pageIndex, WIDGET_SUBTYPE_CODE, objectNumber ?? 0);
11959
+ if (annotPtr === _embedpdf_engine_runtime.NULL_PTR && objectNumber !== void 0) throw objectNumberUnavailable(objectNumber, "taken");
11960
+ if (annotPtr === _embedpdf_engine_runtime.NULL_PTR) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "failed to create widget annotation");
11961
+ try {
11962
+ writeAnnotString(fn, mem, annotPtr, "NM", (0, _embedpdf_engine_core_runtime.generateUuidV7)());
11963
+ const { page: _page, rect, rotation, exportValue: _exportValue, actions, ...style } = placement;
11964
+ setAnnotRect(fn, mem, annotPtr, rect);
11965
+ if (rotation) setWidgetTurn(fn, annotPtr, rotation);
11966
+ if (Object.keys(style).length > 0) applyWidgetStyle(fn, mem, annotPtr, style);
11967
+ if (actions) writeWidgetActions(runtime, docPtr, annotPtr, actions);
11968
+ const widgetObjectNumber = fn.EPDFAnnot_GetObjectNumber(annotPtr);
11969
+ if (widgetObjectNumber <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "widget annotation has no object number");
11970
+ return widgetObjectNumber;
11971
+ } finally {
11972
+ fn.FPDFPage_CloseAnnot(annotPtr);
11973
+ }
11974
+ }
11975
+ //#endregion
11976
+ //#region src/features/forms/internal/fieldFlagBits.ts
11977
+ /**
11978
+ * /Ff bits writable through drafts and patches (1-based spec bit numbers).
11979
+ * Family-defining bits (Radio 1<<15, Pushbutton 1<<16, Combo 1<<17) are set
11980
+ * once by EPDFForm_CreateField and immutable afterwards - the native
11981
+ * setter rejects them.
11982
+ */
11983
+ const FIELD_FLAG_BITS = {
11984
+ readOnly: 1,
11985
+ required: 2,
11986
+ noExport: 4,
11987
+ multiline: 4096,
11988
+ password: 8192,
11989
+ noToggleToOff: 16384,
11990
+ edit: 1 << 18,
11991
+ multiSelect: 1 << 21,
11992
+ comb: 1 << 24,
11993
+ radiosInUnison: 1 << 25
11994
+ };
11995
+ /**
11996
+ * Fold optional booleans into set/clear masks: `true` sets the bit,
11997
+ * `false` clears it, `undefined` leaves it alone.
11998
+ */
11999
+ function flagMasks(flags) {
12000
+ let setBits = 0;
12001
+ let clearBits = 0;
12002
+ for (const [name, bit] of Object.entries(FIELD_FLAG_BITS)) {
11625
12003
  const value = flags[name];
11626
12004
  if (value === true) setBits |= bit;
11627
12005
  else if (value === false) clearBits |= bit;
@@ -12527,8 +12905,8 @@ var FormMutator = class {
12527
12905
  }
12528
12906
  const { objectNumber, widgetObjectNumbers } = numbers;
12529
12907
  if (widgetObjectNumbers && widgetObjectNumbers.length !== placements.length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `forms.create needs one widget object number per widget: ${widgetObjectNumbers.length} for ${placements.length}`, { details: { field: "widgetObjectNumbers" } });
12530
- if (objectNumber !== void 0) this.session.useObjectNumber(objectNumber);
12531
- for (const number of widgetObjectNumbers ?? []) this.session.useObjectNumber(number);
12908
+ if (objectNumber !== void 0) this.session.claimObjectNumber(objectNumber);
12909
+ for (const number of widgetObjectNumbers ?? []) this.session.claimObjectNumber(number);
12532
12910
  throwIfAborted(signal);
12533
12911
  const fieldObjectNumber = this.createFieldNode(draft, objectNumber);
12534
12912
  this.configureNewField(draft, fieldObjectNumber);
@@ -12748,9 +13126,9 @@ var FormMutator = class {
12748
13126
  const pageIndex = this.preflightPlacement(fitted);
12749
13127
  const onState = onStateOf(before.family, fitted);
12750
13128
  const mergedPage = before.widgets.find((w) => w.objectNumber === resolved.fieldObjectNumber)?.page ?? null;
12751
- if (numbers.objectNumber !== void 0) this.session.useObjectNumber(numbers.objectNumber);
13129
+ if (numbers.objectNumber !== void 0) this.session.claimObjectNumber(numbers.objectNumber);
12752
13130
  const splitObjectNumber = mergedPage ? numbers.splitObjectNumber : void 0;
12753
- if (splitObjectNumber !== void 0) this.session.useObjectNumber(splitObjectNumber);
13131
+ if (splitObjectNumber !== void 0) this.session.claimObjectNumber(splitObjectNumber);
12754
13132
  throwIfAborted(signal);
12755
13133
  if (mergedPage) promoteInlineAnnotations(this.runtime, this.session, mergedPage.objectNumber);
12756
13134
  const widgetObjectNumber = createUnattachedWidget(this.runtime, docPtr, pageIndex, fitted, numbers.objectNumber);
@@ -13315,14 +13693,14 @@ function updateField(ctx, op, opIndex) {
13315
13693
  const before = readField(ctx, op.field);
13316
13694
  if (op.patch.groupId !== void 0 && op.patch.groupId !== before.field.groupId) (0, _embedpdf_engine_core_runtime.authorizeFieldGroup)(ctx.authority, op.patch.groupId);
13317
13695
  assertExpected(opIndex, propertiesOf(before.field), op.expect);
13318
- return writeUpdate(ctx, before, op.patch);
13696
+ return writeUpdate$1(ctx, before, op.patch);
13319
13697
  }
13320
13698
  /** `forms.delete`; its reverse brings back the field, its widgets and the parents it pruned. */
13321
13699
  function deleteField(ctx, op, opIndex) {
13322
13700
  (0, _embedpdf_engine_core_runtime.authorizeCapability)(ctx.authority, "doc.forms.modify");
13323
13701
  const before = readField(ctx, op.field);
13324
13702
  assertExpected(opIndex, before.field, op.expect);
13325
- return writeDelete(ctx, before);
13703
+ return writeDelete$2(ctx, before);
13326
13704
  }
13327
13705
  /**
13328
13706
  * `forms.addWidget`. Its reverse puts back the field and the pages the widget
@@ -13451,7 +13829,7 @@ function updateWidget(ctx, op, opIndex) {
13451
13829
  const current = readAnnotation(ctx, op.widget);
13452
13830
  if (current.subtype !== "widget") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `${current.subtype} is not a widget: change it with doc.annotations.update`, { details: { field: "widget" } });
13453
13831
  assertExpected(opIndex, current, op.expect);
13454
- return writeUpdate$1(ctx, op.widget, {
13832
+ return writeUpdate$2(ctx, op.widget, {
13455
13833
  ...op.patch,
13456
13834
  subtype: "widget"
13457
13835
  }, "forms.updateWidget");
@@ -13727,7 +14105,7 @@ function revertProperties(ctx, fallback) {
13727
14105
  const keys = Object.keys(left);
13728
14106
  const kept = keys.filter((key) => valuesEqual(now[key], left[key]) && (key !== "name" || nameIsFree(ctx, current.field.name, String(restore.name))));
13729
14107
  if (kept.length === 0) return leftAlone(ctx, "forms.update");
13730
- const done = writeUpdate(ctx, current, pick$1(restore, kept));
14108
+ const done = writeUpdate$1(ctx, current, pick$1(restore, kept));
13731
14109
  const skipped = keys.filter((key) => !kept.includes(key));
13732
14110
  const item = done.item;
13733
14111
  if (skipped.length === 0 || (0, _embedpdf_engine_core_runtime.isSkippedItem)(item) || item.type !== "forms.update") return done;
@@ -13752,7 +14130,7 @@ function removeField(ctx, step) {
13752
14130
  if (step.rights) (0, _embedpdf_engine_core_runtime.authorizeCapability)(ctx.authority, "doc.forms.import");
13753
14131
  const current = tryReadFieldByNumber(ctx, step.objectNumber);
13754
14132
  if (!current || !valuesEqual(current.field, step.left)) return leftAlone(ctx, "forms.delete");
13755
- return writeDelete(ctx, current, step.rights);
14133
+ return writeDelete$2(ctx, current, step.rights);
13756
14134
  }
13757
14135
  /**
13758
14136
  * Brings back a field a delete removed. Left alone when another field took its
@@ -13887,7 +14265,7 @@ function writeEffect(ctx, type, write, before) {
13887
14265
  * An update of the field `before` reads, recording the dictionaries it writes
13888
14266
  * and the properties it changes: their old values, and the values it left.
13889
14267
  */
13890
- function writeUpdate(ctx, before, patch) {
14268
+ function writeUpdate$1(ctx, before, patch) {
13891
14269
  const ref = {
13892
14270
  kind: "objectNumber",
13893
14271
  objectNumber: before.objectNumber
@@ -13962,7 +14340,7 @@ function recordRevert(ctx, before, type, write, fallbackOf) {
13962
14340
  * A delete of the field `before` reads, capturing it first. Its restore
13963
14341
  * takes `rights` too.
13964
14342
  */
13965
- function writeDelete(ctx, before, rights) {
14343
+ function writeDelete$2(ctx, before, rights) {
13966
14344
  const capture = exportField(ctx.runtime, ctx.session.requireDocPtr(), before.objectNumber);
13967
14345
  const ref = {
13968
14346
  kind: "objectNumber",
@@ -14295,57 +14673,1153 @@ function entryOf(values, key) {
14295
14673
  function pick(values, keys) {
14296
14674
  return Object.fromEntries(keys.map((key) => [key, entryOf(values, key)]));
14297
14675
  }
14298
- //#endregion
14299
- //#region src/features/changes/ChangeApplier.ts
14300
14676
  /**
14301
- * Runs changes inside the job's layer transaction, which its caller opens and
14302
- * ends, so a failure anywhere leaves the document as it was: a change's ops in
14303
- * order (`apply`), or the steps of a recorded reverse (`undo`). Each op and
14304
- * step is checked against the change's authority as it runs, as its single
14305
- * verb's job checks it.
14306
- *
14307
- * Every op records the steps that reverse it as it runs, from what it found
14308
- * and wrote; the record lists them in reverse op order, so each step meets the
14309
- * document as the op after it left it.
14310
- */
14311
- var ChangeApplier = class {
14312
- constructor(runtime, session, fonts) {
14313
- this.runtime = runtime;
14314
- this.session = session;
14315
- this.fonts = fonts;
14677
+ * The outline's shape as the document links it now, by object number: each
14678
+ * item's parent ({@link TOP} for a top-level item) and each parent's
14679
+ * children in order. Only what a write needs: no titles, no targets. An item
14680
+ * that isn't an indirect object is left out with the items under it, a loop
14681
+ * ends its list, and items deeper than a read goes aren't in it, as every
14682
+ * read of the outline does.
14683
+ */
14684
+ var OutlineTree = class {
14685
+ constructor(fn, docPtr) {
14686
+ _defineProperty(this, "childrenByParent", /* @__PURE__ */ new Map());
14687
+ _defineProperty(this, "parentByItem", /* @__PURE__ */ new Map());
14688
+ const visit = (parentHandle, parent, depth) => {
14689
+ if (depth >= _embedpdf_engine_core_runtime.OUTLINE_READ_LIMITS.depth) return;
14690
+ const children = [];
14691
+ this.childrenByParent.set(parent, children);
14692
+ for (let handle = fn.FPDFBookmark_GetFirstChild(docPtr, parentHandle); handle; handle = fn.FPDFBookmark_GetNextSibling(docPtr, handle)) {
14693
+ const item = fn.EPDFOutline_GetObjectNumber(handle);
14694
+ if (!item) continue;
14695
+ if (this.parentByItem.has(item)) break;
14696
+ this.parentByItem.set(item, parent);
14697
+ children.push(item);
14698
+ visit(handle, item, depth + 1);
14699
+ }
14700
+ };
14701
+ visit(_embedpdf_engine_runtime.NULL_PTR, 0, 0);
14316
14702
  }
14317
- apply(ops, authority, signal) {
14318
- const ctx = this.context(authority, signal, (self) => opsFillOwnField(self, ops));
14319
- return collect(authority, ops.map((op, at) => () => runOp(ctx, op, at)), signal);
14703
+ has(item) {
14704
+ return this.parentByItem.has(item);
14320
14705
  }
14321
- undo(record, authority, signal) {
14322
- const ctx = this.context(authority, signal, (self) => stepsFillOwnField(self, record.steps));
14323
- return collect(authority, record.steps.map((step) => () => runStep(ctx, step)), signal);
14706
+ /** The item's parent ({@link TOP} at the top level); `undefined` when it isn't in the outline. */
14707
+ parentOf(item) {
14708
+ return this.parentByItem.get(item);
14324
14709
  }
14325
- context(authority, signal, ownFill) {
14326
- let answer;
14327
- const ctx = {
14328
- runtime: this.runtime,
14329
- session: this.session,
14330
- ...this.fonts ? { fonts: this.fonts } : {},
14331
- authority,
14332
- signal,
14333
- fillsOwnField: () => answer ?? (answer = ownFill(ctx))
14334
- };
14335
- return ctx;
14710
+ /** A parent's children in order: none for a leaf, or an item the outline doesn't have. */
14711
+ childrenOf(parent) {
14712
+ return this.childrenByParent.get(parent) ?? [];
14336
14713
  }
14337
- };
14338
- /** Runs each in order; the record reverses them in the opposite order. */
14339
- function collect(authority, runs, signal) {
14340
- const items = [];
14341
- const reverses = [];
14342
- for (const run of runs) {
14343
- throwIfAborted(signal);
14344
- const done = run();
14345
- items.push(done.item);
14346
- reverses.push(done.reverse);
14714
+ /** The item and the items under it, depth first: the item first. */
14715
+ subtreeOf(item) {
14716
+ const items = [item];
14717
+ for (const child of this.childrenOf(item)) items.push(...this.subtreeOf(child));
14718
+ return items;
14347
14719
  }
14348
- return {
14720
+ /** How deep the item sits: 0 at the top level; `undefined` when it isn't in the outline. */
14721
+ levelOf(item) {
14722
+ let level = -1;
14723
+ for (let current = item; current; current = this.parentOf(current)) level += 1;
14724
+ return this.has(item) ? level : void 0;
14725
+ }
14726
+ /** Whether `node` is `item` or one of the items under it. */
14727
+ isAtOrUnder(node, item) {
14728
+ for (let current = node; current; current = this.parentOf(current)) if (current === item) return true;
14729
+ return false;
14730
+ }
14731
+ /** Every item, depth first: what an order of the whole outline is. */
14732
+ items() {
14733
+ return this.childrenOf(0).flatMap((item) => this.subtreeOf(item));
14734
+ }
14735
+ };
14736
+ //#endregion
14737
+ //#region src/features/outline/internal/readOutlineItem.ts
14738
+ /**
14739
+ * What one read of an outline may spend on its items' actions: one action
14740
+ * model per item at most, and room for a URI or a script on each. Past it,
14741
+ * an item's action reads as missing and the outline as incomplete.
14742
+ */
14743
+ const OUTLINE_ACTION_READ_BUDGET = {
14744
+ maxModels: 5e4,
14745
+ maxNodes: 2e5,
14746
+ maxScriptCodeUnits: 16e6,
14747
+ maxTargetEntries: 65536,
14748
+ maxPayloadCodeUnits: 16e6
14749
+ };
14750
+ /** `/F` bit 1. */
14751
+ const ITALIC$1 = 1;
14752
+ /** `/F` bit 2. */
14753
+ const BOLD$1 = 2;
14754
+ /** A budget for one read of the outline's actions. */
14755
+ function outlineActionBudget() {
14756
+ return new ActionReadBudgetTracker(OUTLINE_ACTION_READ_BUDGET);
14757
+ }
14758
+ /**
14759
+ * The item `handle` reads, at `objectNumber` under `parent` ({@link TOP} for
14760
+ * the top level). Its target is read the way a link's is: the `/A` tree's
14761
+ * navigation view, else the `/Dest`; an action that can't be read makes the
14762
+ * target `unsupported`.
14763
+ */
14764
+ function readOutlineItem(runtime, docPtr, handle, objectNumber, parent, budget) {
14765
+ const { fn, mem } = runtime;
14766
+ const title = readUtf16String(mem, (buf, capacity) => fn.FPDFBookmark_GetTitle(handle, buf, capacity)) ?? "";
14767
+ const hasChildren = fn.FPDFBookmark_GetFirstChild(docPtr, handle) !== _embedpdf_engine_runtime.NULL_PTR;
14768
+ const style = fn.FPDFBookmark_GetStyle(handle);
14769
+ const { action, target, complete } = readTarget(runtime, docPtr, handle, budget);
14770
+ return {
14771
+ item: {
14772
+ ref: (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(objectNumber),
14773
+ parent: parent === 0 ? null : (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(parent),
14774
+ title,
14775
+ target,
14776
+ action,
14777
+ open: hasChildren && fn.FPDFBookmark_GetCount(handle) > 0,
14778
+ color: readColor(runtime, handle),
14779
+ bold: (style & BOLD$1) !== 0,
14780
+ italic: (style & ITALIC$1) !== 0
14781
+ },
14782
+ complete
14783
+ };
14784
+ }
14785
+ function readTarget(runtime, docPtr, handle, budget) {
14786
+ const { fn, mem } = runtime;
14787
+ const actionPtr = fn.FPDFBookmark_GetAction(handle);
14788
+ if (actionPtr) try {
14789
+ const action = readActionModel(fn, mem, docPtr, fn.EPDFAction_LoadModel(actionPtr), budget);
14790
+ return {
14791
+ action,
14792
+ target: action ? linkTargetFromActionTree(action) : { kind: "unsupported" },
14793
+ complete: action !== null && !action.incomplete
14794
+ };
14795
+ } catch (error) {
14796
+ if (!_embedpdf_engine_core_runtime.EngineError.is(error)) throw error;
14797
+ return {
14798
+ action: null,
14799
+ target: { kind: "unsupported" },
14800
+ complete: false
14801
+ };
14802
+ }
14803
+ const destPtr = fn.FPDFBookmark_GetDest(docPtr, handle);
14804
+ if (!destPtr) return {
14805
+ action: null,
14806
+ target: null,
14807
+ complete: true
14808
+ };
14809
+ const destination = readDestination(fn, mem, docPtr, destPtr);
14810
+ return {
14811
+ action: null,
14812
+ target: destination ? {
14813
+ kind: "goto",
14814
+ destination
14815
+ } : { kind: "unsupported" },
14816
+ complete: true
14817
+ };
14818
+ }
14819
+ /** `/C` as `#rrggbb`, or `null` when the item has none. */
14820
+ function readColor(runtime, handle) {
14821
+ const { fn, mem } = runtime;
14822
+ return withScratchN(mem, [
14823
+ 4,
14824
+ 4,
14825
+ 4
14826
+ ], ([r, g, b]) => {
14827
+ if (!fn.FPDFBookmark_GetColor(handle, r, g, b)) return null;
14828
+ const channel = (ptr) => Math.min(255, Math.max(0, Math.round(readF32(mem, ptr) * 255)));
14829
+ return (0, _embedpdf_engine_core_runtime.colorOf)(channel(r), channel(g), channel(b));
14830
+ });
14831
+ }
14832
+ //#endregion
14833
+ //#region src/features/outline/OutlineReader.ts
14834
+ /**
14835
+ * Synchronous read of the document's outline, shared by the local worker and
14836
+ * the server. Dictionary reads only: no page is loaded. It never throws for
14837
+ * a bad outline: what it can't read (a loop, an item that isn't an indirect
14838
+ * object, a broken action, past `OUTLINE_READ_LIMITS`) it leaves out or
14839
+ * marks, and says `incomplete`.
14840
+ */
14841
+ var OutlineReader = class {
14842
+ constructor(runtime, session) {
14843
+ this.runtime = runtime;
14844
+ this.session = session;
14845
+ }
14846
+ read(signal) {
14847
+ const { fn } = this.runtime;
14848
+ const docPtr = this.session.requireDocPtr();
14849
+ const budget = outlineActionBudget();
14850
+ const items = [];
14851
+ const seen = /* @__PURE__ */ new Set();
14852
+ let incomplete = false;
14853
+ const visit = (parentHandle, parent, depth) => {
14854
+ for (let handle = fn.FPDFBookmark_GetFirstChild(docPtr, parentHandle); handle; handle = fn.FPDFBookmark_GetNextSibling(docPtr, handle)) {
14855
+ throwIfAborted(signal);
14856
+ const objectNumber = fn.EPDFOutline_GetObjectNumber(handle);
14857
+ if (!objectNumber) {
14858
+ incomplete = true;
14859
+ continue;
14860
+ }
14861
+ if (seen.has(objectNumber)) {
14862
+ incomplete = true;
14863
+ break;
14864
+ }
14865
+ if (items.length >= _embedpdf_engine_core_runtime.OUTLINE_READ_LIMITS.items) {
14866
+ incomplete = true;
14867
+ return false;
14868
+ }
14869
+ seen.add(objectNumber);
14870
+ const read = readOutlineItem(this.runtime, docPtr, handle, objectNumber, parent, budget);
14871
+ items.push(read.item);
14872
+ if (!read.complete) incomplete = true;
14873
+ if (fn.FPDFBookmark_GetFirstChild(docPtr, handle) === _embedpdf_engine_runtime.NULL_PTR) continue;
14874
+ if (depth + 1 >= _embedpdf_engine_core_runtime.OUTLINE_READ_LIMITS.depth) {
14875
+ incomplete = true;
14876
+ continue;
14877
+ }
14878
+ if (!visit(handle, objectNumber, depth + 1)) return false;
14879
+ }
14880
+ return true;
14881
+ };
14882
+ visit(_embedpdf_engine_runtime.NULL_PTR, 0, 0);
14883
+ return {
14884
+ items,
14885
+ incomplete
14886
+ };
14887
+ }
14888
+ /** The item `objectNumber`, under `parent` ({@link TOP} for the top level), as it reads now. */
14889
+ readItem(objectNumber, parent) {
14890
+ const docPtr = this.session.requireDocPtr();
14891
+ const handle = this.runtime.fn.EPDFOutline_GetItem(docPtr, objectNumber);
14892
+ if (!handle) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no outline item ${objectNumber}`, { details: { field: "ref" } });
14893
+ return readOutlineItem(this.runtime, docPtr, handle, objectNumber, parent, outlineActionBudget()).item;
14894
+ }
14895
+ };
14896
+ //#endregion
14897
+ //#region src/features/outline/internal/writeOutlineFields.ts
14898
+ /** `/F` bit 1. */
14899
+ const ITALIC = 1;
14900
+ /** `/F` bit 2. */
14901
+ const BOLD = 2;
14902
+ /** The most a PDF text string holds in every PDF app (ISO 32000-2 Annex C). */
14903
+ const MAX_TITLE_LENGTH = 32767;
14904
+ const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
14905
+ /**
14906
+ * Refuses, before anything is written, fields no write can take: a title
14907
+ * too long, a colour that isn't `#rrggbb`, a target a write can't make.
14908
+ */
14909
+ function assertWritableFields(fields) {
14910
+ const refuse = (field, message) => {
14911
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, message, { details: { field } });
14912
+ };
14913
+ if (fields.title !== void 0) {
14914
+ if (typeof fields.title !== "string") refuse("title", "a title is a string");
14915
+ if (fields.title.length > MAX_TITLE_LENGTH) refuse("title", `a title holds at most ${MAX_TITLE_LENGTH} characters`);
14916
+ }
14917
+ if (fields.color != null && !HEX_COLOR.test(fields.color)) refuse("color", "expected a color as '#rrggbb'");
14918
+ const target = fields.target;
14919
+ if (target != null) {
14920
+ if (!(target.kind === "goto" || target.kind === "uri" || target.kind === "named" && [
14921
+ "NextPage",
14922
+ "PrevPage",
14923
+ "FirstPage",
14924
+ "LastPage"
14925
+ ].includes(target.name ?? ""))) refuse("target", `a '${target.kind}' target can't be written`);
14926
+ }
14927
+ }
14928
+ /**
14929
+ * Writes the fields `fields` names on the item `objectNumber`; the others
14930
+ * stay as they are. A target is always written as `/A`, as a link's is.
14931
+ */
14932
+ function writeOutlineFields(runtime, docPtr, objectNumber, fields) {
14933
+ const { fn, mem } = runtime;
14934
+ const check = (ok, what) => {
14935
+ if (!ok) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `the ${what} of outline item ${objectNumber} could not be written`);
14936
+ };
14937
+ if (fields.title !== void 0) {
14938
+ const title = fields.title;
14939
+ check(withUtf16String(mem, title, (ptr) => fn.EPDFOutline_SetTitle(docPtr, objectNumber, ptr)), "title");
14940
+ }
14941
+ if (fields.target !== void 0) {
14942
+ const action = fields.target === null ? _embedpdf_engine_runtime.NULL_PTR : createLinkTargetAction(fn, mem, docPtr, fields.target);
14943
+ check(fn.EPDFOutline_SetAction(docPtr, objectNumber, action), "target");
14944
+ }
14945
+ if (fields.color !== void 0) if (fields.color === null) check(fn.EPDFOutline_SetColor(docPtr, objectNumber, false, 0, 0, 0), "colour");
14946
+ else {
14947
+ const { r, g, b } = (0, _embedpdf_engine_core_runtime.rgbOf)(fields.color);
14948
+ check(fn.EPDFOutline_SetColor(docPtr, objectNumber, true, r / 255, g / 255, b / 255), "colour");
14949
+ }
14950
+ if (fields.bold !== void 0 || fields.italic !== void 0) {
14951
+ let flags = fn.FPDFBookmark_GetStyle(fn.EPDFOutline_GetItem(docPtr, objectNumber));
14952
+ if (fields.bold !== void 0) flags = fields.bold ? flags | BOLD : flags & -3;
14953
+ if (fields.italic !== void 0) flags = fields.italic ? flags | ITALIC : flags & -2;
14954
+ check(fn.EPDFOutline_SetStyle(docPtr, objectNumber, flags), "style");
14955
+ }
14956
+ if (fields.open !== void 0) check(fn.EPDFOutline_SetOpen(docPtr, objectNumber, fields.open), "open state");
14957
+ }
14958
+ //#endregion
14959
+ //#region src/features/outline/OutlineMutator.ts
14960
+ /**
14961
+ * Synchronous outline writes, shared by the local worker and the server.
14962
+ * Each checks everything it can before the first native write, and answers
14963
+ * with what it wrote, re-read. The fork keeps the links and `/Count` right
14964
+ * (public/epdf_outline.h); what goes where is decided here.
14965
+ */
14966
+ var OutlineMutator = class {
14967
+ constructor(runtime, session) {
14968
+ this.runtime = runtime;
14969
+ this.session = session;
14970
+ _defineProperty(this, "reader", void 0);
14971
+ this.reader = new OutlineReader(runtime, session);
14972
+ }
14973
+ /** The outline's shape as it is now. */
14974
+ tree() {
14975
+ return new OutlineTree(this.runtime.fn, this.session.requireDocPtr());
14976
+ }
14977
+ create(parent, position, data, objectNumber) {
14978
+ assertWritableFields(data);
14979
+ const tree = this.tree();
14980
+ const parentNumber = this.parentNumberOf(tree, parent);
14981
+ const children = tree.childrenOf(parentNumber);
14982
+ const at = (0, _embedpdf_engine_core_runtime.positionIndex)(children.map(_embedpdf_engine_core_runtime.toOutlineItemRef), position, keyOf);
14983
+ const after = at === 0 ? 0 : children[at - 1];
14984
+ if (objectNumber !== void 0) this.session.claimObjectNumber(objectNumber);
14985
+ const { fn, mem } = this.runtime;
14986
+ const docPtr = this.session.requireDocPtr();
14987
+ const created = withUtf16String(mem, data.title, (title) => fn.EPDFOutline_CreateItem(docPtr, objectNumber ?? 0, parentNumber, after, title));
14988
+ if (!created) throw outlineRefused("create the item");
14989
+ const { title: _title, ...fields } = data;
14990
+ writeOutlineFields(this.runtime, docPtr, created, fields);
14991
+ const item = this.reader.readItem(created, parentNumber);
14992
+ return {
14993
+ item,
14994
+ order: this.orderOf(this.tree(), parentNumber),
14995
+ meta: this.meta([item.ref])
14996
+ };
14997
+ }
14998
+ update(ref, patch) {
14999
+ assertWritableFields(patch);
15000
+ const tree = this.tree();
15001
+ const parent = this.requireItem(tree, ref, "ref");
15002
+ writeOutlineFields(this.runtime, this.session.requireDocPtr(), ref.objectNumber, patch);
15003
+ return {
15004
+ item: this.reader.readItem(ref.objectNumber, parent),
15005
+ meta: this.meta([ref])
15006
+ };
15007
+ }
15008
+ /**
15009
+ * The items go together, in the order given, under `parent` at `position`
15010
+ * among its children; the rules of a reorder (`reorderedList`) hold.
15011
+ */
15012
+ move(refs, parent, position) {
15013
+ const tree = this.tree();
15014
+ const items = refs.map((ref) => {
15015
+ this.requireItem(tree, ref, "refs");
15016
+ return ref.objectNumber;
15017
+ });
15018
+ const moving = new Set(items);
15019
+ if (moving.size !== items.length) invalid("refs", "an item is named twice");
15020
+ for (const item of items) if (items.some((other) => other !== item && tree.isAtOrUnder(item, other))) invalid("refs", `item ${item} is under another item that moves`);
15021
+ const parentNumber = this.parentNumberOf(tree, parent);
15022
+ if (items.some((item) => tree.isAtOrUnder(parentNumber, item))) invalid("parent", "an item would go under itself");
15023
+ const staying = tree.childrenOf(parentNumber).filter((child) => !moving.has(child));
15024
+ const anchor = position === "start" || position === "end" ? null : "before" in position ? position.before : position.after;
15025
+ if (anchor && moving.has(anchor.objectNumber)) invalid("position", `the neighbour ${anchor.objectNumber} is one of the items that move`);
15026
+ const at = (0, _embedpdf_engine_core_runtime.positionIndex)(staying.map(_embedpdf_engine_core_runtime.toOutlineItemRef), position, keyOf);
15027
+ let after = at === 0 ? 0 : staying[at - 1];
15028
+ return this.place(items.map((item) => {
15029
+ const placement = {
15030
+ item,
15031
+ parent: parentNumber,
15032
+ after
15033
+ };
15034
+ after = item;
15035
+ return placement;
15036
+ }), tree);
15037
+ }
15038
+ /**
15039
+ * Puts items where `placements` say, in order: each right after its
15040
+ * `after` while that is a child of its parent, else first. What an undo of
15041
+ * a move does; the caller has checked that each item and parent is there.
15042
+ */
15043
+ place(placements, before = this.tree()) {
15044
+ const { fn } = this.runtime;
15045
+ const docPtr = this.session.requireDocPtr();
15046
+ const parents = /* @__PURE__ */ new Set();
15047
+ for (const { item, parent, after } of placements) {
15048
+ parents.add(before.parentOf(item) ?? 0);
15049
+ parents.add(parent);
15050
+ const now = this.tree();
15051
+ const anchor = after && now.parentOf(after) === parent ? after : 0;
15052
+ if (!fn.EPDFOutline_MoveItem(docPtr, item, parent, anchor)) throw outlineRefused(`move item ${item}`);
15053
+ }
15054
+ const after = this.tree();
15055
+ return {
15056
+ orders: [...parents].map((parent) => this.orderOf(after, parent)),
15057
+ meta: this.meta(placements.map(({ item }) => (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(item)))
15058
+ };
15059
+ }
15060
+ /** Where `items` are now, in document order: an anchor always comes before what follows it. */
15061
+ placementsOf(items, tree = this.tree()) {
15062
+ const wanted = new Set(items);
15063
+ return tree.items().filter((item) => wanted.has(item)).map((item) => {
15064
+ const parent = tree.parentOf(item);
15065
+ const siblings = tree.childrenOf(parent);
15066
+ const index = siblings.indexOf(item);
15067
+ return {
15068
+ item,
15069
+ parent,
15070
+ after: index > 0 ? siblings[index - 1] : 0
15071
+ };
15072
+ });
15073
+ }
15074
+ /** The item goes, with the items under it. */
15075
+ delete(ref) {
15076
+ const tree = this.tree();
15077
+ this.requireItem(tree, ref, "ref");
15078
+ const going = tree.subtreeOf(ref.objectNumber);
15079
+ if (!this.runtime.fn.EPDFOutline_DeleteItem(this.session.requireDocPtr(), ref.objectNumber)) throw outlineRefused(`delete item ${ref.objectNumber}`);
15080
+ return { meta: this.meta(going.map(_embedpdf_engine_core_runtime.toOutlineItemRef)) };
15081
+ }
15082
+ /**
15083
+ * The items a capture brought back (`importOutlineItem`, run by the
15084
+ * caller): the item first, then the items under it, and its parent's
15085
+ * children.
15086
+ */
15087
+ restored(objectNumber) {
15088
+ const tree = this.tree();
15089
+ const parent = tree.parentOf(objectNumber);
15090
+ if (parent === void 0) throw outlineRefused(`restore item ${objectNumber}`);
15091
+ const items = this.subtree(objectNumber, tree);
15092
+ return {
15093
+ items,
15094
+ order: this.orderOf(tree, parent),
15095
+ meta: this.meta(items.map((item) => item.ref))
15096
+ };
15097
+ }
15098
+ /** The item as it reads now; `null` when the outline has no such item. */
15099
+ find(ref, tree = this.tree()) {
15100
+ const parent = tree.parentOf(ref.objectNumber);
15101
+ return parent === void 0 ? null : this.reader.readItem(ref.objectNumber, parent);
15102
+ }
15103
+ /** The item and the items under it, depth first, as they read now. */
15104
+ subtree(objectNumber, tree = this.tree()) {
15105
+ return tree.subtreeOf(objectNumber).map((item) => this.reader.readItem(item, tree.parentOf(item)));
15106
+ }
15107
+ /** An update's answer for an item written some other way: a revert put it back. */
15108
+ updated(ref) {
15109
+ const item = this.find(ref);
15110
+ if (!item) throw outlineRefused(`read item ${ref.objectNumber}`);
15111
+ return {
15112
+ item,
15113
+ meta: this.meta([ref])
15114
+ };
15115
+ }
15116
+ /**
15117
+ * Counts the items above `ref` again from the tree, as every outline write
15118
+ * does: a revert puts back the item's own dictionary, its `/Count` with
15119
+ * it, and nothing above it. Setting the open state it reads changes
15120
+ * nothing else.
15121
+ */
15122
+ countAgain(ref) {
15123
+ const item = this.find(ref);
15124
+ if (!item) return;
15125
+ writeOutlineFields(this.runtime, this.session.requireDocPtr(), ref.objectNumber, { open: item.open });
15126
+ }
15127
+ /**
15128
+ * The number a parent ref names: {@link TOP} for `null`; `NotFound` when it
15129
+ * isn't an item; `InvalidArg` for an item at the deepest level a read goes
15130
+ * to, whose children no read shows.
15131
+ */
15132
+ parentNumberOf(tree, parent) {
15133
+ if (parent === null) return 0;
15134
+ this.requireItem(tree, parent, "parent");
15135
+ if ((tree.levelOf(parent.objectNumber) ?? 0) >= _embedpdf_engine_core_runtime.OUTLINE_READ_LIMITS.depth - 1) invalid("parent", `an item under ${parent.objectNumber} would be deeper than reads go`);
15136
+ return parent.objectNumber;
15137
+ }
15138
+ /** The item's parent; `NotFound` when the outline has no such item. */
15139
+ requireItem(tree, ref, field) {
15140
+ const parent = tree.parentOf(ref.objectNumber);
15141
+ if (parent === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no outline item ${ref.objectNumber}`, { details: { field } });
15142
+ return parent;
15143
+ }
15144
+ orderOf(tree, parent) {
15145
+ return {
15146
+ parent: parent === 0 ? null : (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(parent),
15147
+ children: tree.childrenOf(parent).map(_embedpdf_engine_core_runtime.toOutlineItemRef)
15148
+ };
15149
+ }
15150
+ meta(changed) {
15151
+ return {
15152
+ affectedPages: [],
15153
+ cacheDelta: null,
15154
+ ...this.session.writeStamp(),
15155
+ changed
15156
+ };
15157
+ }
15158
+ };
15159
+ const keyOf = (ref) => String(ref.objectNumber);
15160
+ function invalid(field, message) {
15161
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, message, { details: { field } });
15162
+ }
15163
+ /**
15164
+ * The fork refused a write the checks above let through: a list it would
15165
+ * change holds an item that isn't an indirect object, or `/Root /Outlines`
15166
+ * is a direct dictionary.
15167
+ */
15168
+ function outlineRefused(what) {
15169
+ return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, `could not ${what}: the outline holds an item that isn't an indirect object`);
15170
+ }
15171
+ //#endregion
15172
+ //#region src/features/changes/internal/outlineChanges.ts
15173
+ /** The fields an update can change, each with a value a revert can put back. */
15174
+ const FIELDS = [
15175
+ "title",
15176
+ "target",
15177
+ "color",
15178
+ "bold",
15179
+ "italic",
15180
+ "open"
15181
+ ];
15182
+ /** What a capture of an item follows: its action, and its destination. */
15183
+ const TARGET_KEYS = ["A", "Dest"];
15184
+ const mutatorOf$1 = (ctx) => new OutlineMutator(ctx.runtime, ctx.session);
15185
+ const authorize$1 = (ctx) => (0, _embedpdf_engine_core_runtime.authorizeCapability)(ctx.authority, "doc.outline.modify");
15186
+ /** `outline.create`; its reverse deletes the item while nobody changed it since. */
15187
+ function createOutlineItem(ctx, op) {
15188
+ authorize$1(ctx);
15189
+ const result = mutatorOf$1(ctx).create(op.parent, op.position ?? "end", op.data, op.objectNumber);
15190
+ return {
15191
+ item: {
15192
+ type: "outline.create",
15193
+ ...result
15194
+ },
15195
+ reverse: [{
15196
+ kind: "outline.remove",
15197
+ ref: result.item.ref,
15198
+ left: guardOf(ctx, [result.item.ref])
15199
+ }]
15200
+ };
15201
+ }
15202
+ /**
15203
+ * `outline.update`; its reverse puts the item's dictionary back as it was,
15204
+ * or, when someone changed it since, the fields still holding what it set.
15205
+ */
15206
+ function updateOutlineItem(ctx, op, opIndex) {
15207
+ authorize$1(ctx);
15208
+ const before = requireItem(ctx, op.ref);
15209
+ assertExpected(opIndex, before, op.expect);
15210
+ return writeUpdate(ctx, op.ref, op.patch, before);
15211
+ }
15212
+ /**
15213
+ * `outline.move`; its reverse puts each item back beside the sibling it
15214
+ * followed, and opens again a parent it emptied that was open.
15215
+ */
15216
+ function moveOutlineItems(ctx, op) {
15217
+ authorize$1(ctx);
15218
+ const mutator = mutatorOf$1(ctx);
15219
+ const tree = mutator.tree();
15220
+ const items = op.refs.map((ref) => ref.objectNumber);
15221
+ const back = mutator.placementsOf(items, tree);
15222
+ const reopens = reopensOf(ctx, tree, items);
15223
+ const result = mutator.move(op.refs, op.parent, op.position);
15224
+ const parent = op.parent?.objectNumber ?? 0;
15225
+ return {
15226
+ item: {
15227
+ type: "outline.move",
15228
+ ...result
15229
+ },
15230
+ reverse: [{
15231
+ kind: "outline.move",
15232
+ placements: back,
15233
+ left: items.map((item) => ({
15234
+ item,
15235
+ parent
15236
+ }))
15237
+ }, ...reopens]
15238
+ };
15239
+ }
15240
+ /** `outline.delete`; its reverse brings the item and the items under it back. */
15241
+ function deleteOutlineItem(ctx, op, opIndex) {
15242
+ authorize$1(ctx);
15243
+ assertExpected(opIndex, requireItem(ctx, op.ref), op.expect);
15244
+ return writeDelete$1(ctx, op.ref);
15245
+ }
15246
+ /** Puts an item's dictionary back, or the fields that still show the update (see `updateOutlineItem`). */
15247
+ function revertOutlineItem(ctx, step) {
15248
+ const ref = step.subject.outlineItem;
15249
+ const mutator = mutatorOf$1(ctx);
15250
+ const current = mutator.find(ref);
15251
+ if (!current) return leftAlone(ctx, "outline.update");
15252
+ authorize$1(ctx);
15253
+ if (unchangedSince(ctx.runtime, ctx.session, step.objects)) {
15254
+ const reverted = revertObjects(ctx.runtime, ctx.session, step.objects);
15255
+ mutator.countAgain(ref);
15256
+ const redo = captureAfter(ctx.runtime, ctx.session, reverted);
15257
+ return {
15258
+ item: {
15259
+ type: "outline.update",
15260
+ ...mutator.updated(ref)
15261
+ },
15262
+ reverse: [{
15263
+ ...step,
15264
+ objects: redo,
15265
+ ...swapped(step.fallback)
15266
+ }]
15267
+ };
15268
+ }
15269
+ const fallback = step.fallback;
15270
+ if (fallback?.kind !== "outline") return leftAlone(ctx, "outline.update");
15271
+ const now = current;
15272
+ const left = fallback.left;
15273
+ const restore = fallback.restore;
15274
+ const keys = Object.keys(left);
15275
+ const kept = keys.filter((key) => valuesEqual(now[key], left[key]) && writable(ctx, key, restore[key]));
15276
+ if (kept.length === 0) return leftAlone(ctx, "outline.update");
15277
+ const done = writeUpdate(ctx, ref, Object.fromEntries(kept.map((key) => [key, restore[key]])), current);
15278
+ const skipped = keys.filter((key) => !kept.includes(key));
15279
+ if (skipped.length === 0 || done.item.type !== "outline.update") return done;
15280
+ return {
15281
+ ...done,
15282
+ item: {
15283
+ ...done.item,
15284
+ skipped
15285
+ }
15286
+ };
15287
+ }
15288
+ /**
15289
+ * Deletes what a create or a restore brought back, while the item and the
15290
+ * items under it are the same ones and read exactly as the change left them.
15291
+ */
15292
+ function removeOutlineItem(ctx, step) {
15293
+ const tree = mutatorOf$1(ctx).tree();
15294
+ if (!tree.has(step.ref.objectNumber)) return leftAlone(ctx, "outline.delete");
15295
+ authorize$1(ctx);
15296
+ if (!valuesEqual(tree.subtreeOf(step.ref.objectNumber), step.left.map((object) => object.objectNumber)) || !unchangedSince(ctx.runtime, ctx.session, step.left)) return leftAlone(ctx, "outline.delete");
15297
+ return writeDelete$1(ctx, step.ref);
15298
+ }
15299
+ /** Brings back what a delete removed, while its parent is there and it isn't back already. */
15300
+ function restoreOutlineItem(ctx, step) {
15301
+ const mutator = mutatorOf$1(ctx);
15302
+ const tree = mutator.tree();
15303
+ if (tree.has(step.ref.objectNumber)) return leftAlone(ctx, "outline.restore");
15304
+ if (step.parent && !tree.has(step.parent.objectNumber)) return leftAlone(ctx, "outline.restore");
15305
+ authorize$1(ctx);
15306
+ importOutlineItem(ctx.runtime, ctx.session.requireDocPtr(), step.capture);
15307
+ const result = mutator.restored(step.ref.objectNumber);
15308
+ return {
15309
+ item: {
15310
+ type: "outline.restore",
15311
+ ...result
15312
+ },
15313
+ reverse: [{
15314
+ kind: "outline.remove",
15315
+ ref: step.ref,
15316
+ left: guardOf(ctx, result.items.map((item) => item.ref))
15317
+ }]
15318
+ };
15319
+ }
15320
+ /**
15321
+ * Puts items back where a move found them: each still under the parent the
15322
+ * move left it under, its old parent still there, and not under itself.
15323
+ */
15324
+ function moveOutlineItemsBack(ctx, step) {
15325
+ const mutator = mutatorOf$1(ctx);
15326
+ const tree = mutator.tree();
15327
+ const leftUnder = new Map(step.left.map(({ item, parent }) => [item, parent]));
15328
+ const placements = step.placements.filter(({ item, parent }) => tree.parentOf(item) === leftUnder.get(item) && (parent === 0 || tree.has(parent)) && !tree.isAtOrUnder(parent, item));
15329
+ if (placements.length === 0) return leftAlone(ctx, "outline.move");
15330
+ authorize$1(ctx);
15331
+ const items = placements.map(({ item }) => item);
15332
+ const back = mutator.placementsOf(items, tree);
15333
+ const reopens = reopensOf(ctx, tree, items);
15334
+ return {
15335
+ item: {
15336
+ type: "outline.move",
15337
+ ...mutator.place(placements, tree)
15338
+ },
15339
+ reverse: [{
15340
+ kind: "outline.move",
15341
+ placements: back,
15342
+ left: placements.map(({ item, parent }) => ({
15343
+ item,
15344
+ parent
15345
+ }))
15346
+ }, ...reopens]
15347
+ };
15348
+ }
15349
+ /** Opens a parent again, as its step says (see `OutlineReopenStep`). */
15350
+ function reopenOutlineItem(ctx, step) {
15351
+ const mutator = mutatorOf$1(ctx);
15352
+ const tree = mutator.tree();
15353
+ const item = mutator.find(step.ref, tree);
15354
+ const children = tree.childrenOf(step.ref.objectNumber);
15355
+ const same = children.length === step.children.length && children.every((child) => step.children.includes(child));
15356
+ if (!item || item.open || !same) return leftAlone(ctx, "outline.update");
15357
+ authorize$1(ctx);
15358
+ return {
15359
+ item: {
15360
+ type: "outline.update",
15361
+ ...mutator.update(step.ref, { open: true })
15362
+ },
15363
+ reverse: []
15364
+ };
15365
+ }
15366
+ /** An update, captured so it can be put back exactly. */
15367
+ function writeUpdate(ctx, ref, patch, before) {
15368
+ const pending = captureBefore(ctx.runtime, ctx.session, [{
15369
+ objectNumber: ref.objectNumber,
15370
+ deep: TARGET_KEYS
15371
+ }]);
15372
+ const result = mutatorOf$1(ctx).update(ref, patch);
15373
+ const objects = captureAfter(ctx.runtime, ctx.session, pending);
15374
+ return {
15375
+ item: {
15376
+ type: "outline.update",
15377
+ ...result
15378
+ },
15379
+ reverse: [{
15380
+ kind: "objects.revert",
15381
+ reports: "outline.update",
15382
+ objects,
15383
+ subject: { outlineItem: ref },
15384
+ fallback: fallbackOf(before, result.item, patch)
15385
+ }]
15386
+ };
15387
+ }
15388
+ /**
15389
+ * A delete, captured in its transaction so an undo brings back the same
15390
+ * objects, and opens again the parent it emptied when it was open.
15391
+ */
15392
+ function writeDelete$1(ctx, ref) {
15393
+ const mutator = mutatorOf$1(ctx);
15394
+ const tree = mutator.tree();
15395
+ const parent = tree.parentOf(ref.objectNumber);
15396
+ const reopens = reopensOf(ctx, tree, [ref.objectNumber]);
15397
+ const capture = exportOutlineItem(ctx.runtime, ctx.session.requireDocPtr(), ref.objectNumber);
15398
+ return {
15399
+ item: {
15400
+ type: "outline.delete",
15401
+ ...mutator.delete(ref)
15402
+ },
15403
+ reverse: [{
15404
+ kind: "outline.restore",
15405
+ ref,
15406
+ parent: parent === void 0 || parent === 0 ? null : (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(parent),
15407
+ capture
15408
+ }, ...reopens]
15409
+ };
15410
+ }
15411
+ /**
15412
+ * The parents that lose every child when `items` leave them, and are open:
15413
+ * each comes back closed with its children, so the undo opens it again.
15414
+ */
15415
+ function reopensOf(ctx, tree, items) {
15416
+ const leaving = new Set(items);
15417
+ const parents = new Set(items.map((item) => tree.parentOf(item) ?? 0));
15418
+ parents.delete(0);
15419
+ return [...parents].flatMap((parent) => {
15420
+ const children = tree.childrenOf(parent);
15421
+ if (!children.every((child) => leaving.has(child))) return [];
15422
+ if (!mutatorOf$1(ctx).find((0, _embedpdf_engine_core_runtime.toOutlineItemRef)(parent), tree)?.open) return [];
15423
+ return [{
15424
+ kind: "outline.reopen",
15425
+ ref: (0, _embedpdf_engine_core_runtime.toOutlineItemRef)(parent),
15426
+ children
15427
+ }];
15428
+ });
15429
+ }
15430
+ /** The guard of items a change leaves: each dictionary as it reads now. */
15431
+ function guardOf(ctx, refs) {
15432
+ return captureAfter(ctx.runtime, ctx.session, refs.map((ref) => ({
15433
+ objectNumber: ref.objectNumber,
15434
+ deep: [],
15435
+ before: null
15436
+ })));
15437
+ }
15438
+ function requireItem(ctx, ref) {
15439
+ const item = mutatorOf$1(ctx).find(ref);
15440
+ if (!item) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no outline item ${ref.objectNumber}`, { details: { field: "ref" } });
15441
+ return item;
15442
+ }
15443
+ /** The fields an update changed: their values before, and the values it left. */
15444
+ function fallbackOf(before, after, patch) {
15445
+ const changed = FIELDS.filter((field) => patch[field] !== void 0 && !valuesEqual(before[field], after[field]));
15446
+ return {
15447
+ kind: "outline",
15448
+ restore: Object.fromEntries(changed.map((field) => [field, before[field]])),
15449
+ left: Object.fromEntries(changed.map((field) => [field, after[field]]))
15450
+ };
15451
+ }
15452
+ /**
15453
+ * Whether a value read back can be written again: a target another app made
15454
+ * may not be, nor one that goes to a page the document no longer has.
15455
+ */
15456
+ function writable(ctx, field, value) {
15457
+ if (field !== "target" || value === null) return true;
15458
+ const target = value;
15459
+ if (target.kind === "goto") {
15460
+ const page = target.destination?.page.objectNumber;
15461
+ return ctx.session.allRecords().some((record) => record.pageObjectNumber === page);
15462
+ }
15463
+ if (target.kind === "uri") return true;
15464
+ return target.kind === "named" && [
15465
+ "NextPage",
15466
+ "PrevPage",
15467
+ "FirstPage",
15468
+ "LastPage"
15469
+ ].includes(target.name ?? "");
15470
+ }
15471
+ function swapped(fallback) {
15472
+ if (fallback?.kind !== "outline") return fallback ? { fallback } : {};
15473
+ return { fallback: {
15474
+ kind: "outline",
15475
+ restore: fallback.left,
15476
+ left: fallback.restore
15477
+ } };
15478
+ }
15479
+ //#endregion
15480
+ //#region src/features/changes/internal/pageChanges.ts
15481
+ /** What a page's guard digests: the page, and what the layer holds of these. */
15482
+ const PAGE_DEEP_KEYS = [
15483
+ "Annots",
15484
+ "Contents",
15485
+ "Resources"
15486
+ ];
15487
+ /**
15488
+ * What it leaves out of the page's bytes: its place (/Parent), so moving
15489
+ * pages around it changes nothing, and its /Rotate, which counts by value
15490
+ * instead: turning a page and turning it back reads the same.
15491
+ */
15492
+ const PAGE_SKIPPED_KEYS = ["Parent", "Rotate"];
15493
+ /**
15494
+ * Runs a page op or step. It changes the page tree in place, so the
15495
+ * document's idle parsed pages close before and after it, as a
15496
+ * `contentWrite` job's do: no page that went, or came back, stays parsed.
15497
+ */
15498
+ function changingPageTree(ctx, write) {
15499
+ const pool = ctx.session.pagePool();
15500
+ pool.closeIdle();
15501
+ try {
15502
+ return write();
15503
+ } finally {
15504
+ pool.closeIdle();
15505
+ }
15506
+ }
15507
+ const mutatorOf = (ctx) => new PagesMutator(ctx.runtime, ctx.session);
15508
+ const inserterOf = (ctx) => new PagesInserter(ctx.runtime, ctx.session);
15509
+ const authorize = (ctx) => (0, _embedpdf_engine_core_runtime.authorizeCapability)(ctx.authority, "doc.pages.assemble");
15510
+ /** `pages.rotate`; its reverse turns each page back while it still has this rotation. */
15511
+ function rotatePages(ctx, op) {
15512
+ authorize(ctx);
15513
+ const before = rotationsOf(layoutOf(ctx));
15514
+ const result = mutatorOf(ctx).rotate([...op.pages], op.rotation, ctx.signal);
15515
+ const pages = op.pages.map((page) => page.objectNumber);
15516
+ return {
15517
+ item: {
15518
+ type: "pages.rotate",
15519
+ pages: [...op.pages],
15520
+ rotation: op.rotation,
15521
+ ...result
15522
+ },
15523
+ reverse: rotateSteps(pages, before, op.rotation)
15524
+ };
15525
+ }
15526
+ /** Turns pages to a step's rotation, those that still have the rotation it left. */
15527
+ function rotatePagesBack(ctx, step) {
15528
+ const now = rotationsOf(layoutOf(ctx));
15529
+ const pages = step.pages.filter((page) => now.get(page) === step.left);
15530
+ if (pages.length === 0) return leftAlone(ctx, "pages.rotate");
15531
+ authorize(ctx);
15532
+ const refs = pages.map(_embedpdf_engine_core_runtime.toPageRef);
15533
+ const result = mutatorOf(ctx).rotate(refs, step.rotation, ctx.signal);
15534
+ return {
15535
+ item: {
15536
+ type: "pages.rotate",
15537
+ pages: refs,
15538
+ rotation: step.rotation,
15539
+ ...result
15540
+ },
15541
+ reverse: [{
15542
+ kind: "pages.rotate",
15543
+ pages,
15544
+ rotation: step.left,
15545
+ left: step.rotation
15546
+ }]
15547
+ };
15548
+ }
15549
+ /** `pages.reorder`; its reverse puts each page back after the page it followed. */
15550
+ function reorderPages(ctx, op) {
15551
+ authorize(ctx);
15552
+ const pages = op.pages.map((page) => page.objectNumber);
15553
+ const back = placementsOf(orderOf(ctx), pages);
15554
+ return {
15555
+ item: {
15556
+ type: "pages.reorder",
15557
+ ...mutatorOf(ctx).reorder([...op.pages], op.position, ctx.signal)
15558
+ },
15559
+ reverse: [{
15560
+ kind: "pages.reorder",
15561
+ placements: back,
15562
+ left: placementsOf(orderOf(ctx), pages)
15563
+ }]
15564
+ };
15565
+ }
15566
+ /**
15567
+ * Puts pages back where a move found them: each still after the page the move
15568
+ * left it after, the page it goes after still there.
15569
+ */
15570
+ function reorderPagesBack(ctx, step) {
15571
+ const order = orderOf(ctx);
15572
+ const leftAfter = new Map(step.left.map(({ page, after }) => [page, after]));
15573
+ const placements = step.placements.filter(({ page, after }) => {
15574
+ const at = order.indexOf(page);
15575
+ return at >= 0 && (order[at - 1] ?? 0) === leftAfter.get(page) && (after === 0 || order.includes(after));
15576
+ });
15577
+ if (placements.length === 0) return leftAlone(ctx, "pages.reorder");
15578
+ authorize(ctx);
15579
+ const pages = placements.map(({ page }) => page);
15580
+ const back = placementsOf(order, pages);
15581
+ const mutator = mutatorOf(ctx);
15582
+ let layout = null;
15583
+ for (const { page, after } of placements) {
15584
+ const position = after === 0 ? "start" : { after: (0, _embedpdf_engine_core_runtime.toPageRef)(after) };
15585
+ ({layout} = mutator.reorder([(0, _embedpdf_engine_core_runtime.toPageRef)(page)], position, ctx.signal));
15586
+ }
15587
+ return {
15588
+ item: {
15589
+ type: "pages.reorder",
15590
+ pages: pages.map(_embedpdf_engine_core_runtime.toPageRef),
15591
+ layout,
15592
+ meta: metaOf(ctx)
15593
+ },
15594
+ reverse: [{
15595
+ kind: "pages.reorder",
15596
+ placements: back,
15597
+ left: placementsOf(orderOf(ctx), pages)
15598
+ }]
15599
+ };
15600
+ }
15601
+ /** `pages.delete`, captured in its transaction so an undo brings back the same pages. */
15602
+ function deletePages(ctx, op) {
15603
+ authorize(ctx);
15604
+ return writeDelete(ctx, inDisplayOrder(orderOf(ctx), ctx.session.resolvePageRefs(op.pages)));
15605
+ }
15606
+ /** Brings back pages a delete removed, those that aren't back already. */
15607
+ function restorePages(ctx, step) {
15608
+ const order = orderOf(ctx);
15609
+ const gone = step.pages.filter(({ page }) => !order.includes(page));
15610
+ if (gone.length === 0) return leftAlone(ctx, "pages.restore");
15611
+ authorize(ctx);
15612
+ const docPtr = ctx.session.requireDocPtr();
15613
+ for (const { capture } of gone) importPage(ctx.runtime, docPtr, capture);
15614
+ ctx.session.refreshPageRegistry();
15615
+ ctx.session.invalidateDerived();
15616
+ const pages = inDisplayOrder(orderOf(ctx), gone.map(({ page }) => page));
15617
+ return {
15618
+ item: {
15619
+ type: "pages.restore",
15620
+ pages: pages.map(_embedpdf_engine_core_runtime.toPageRef),
15621
+ layout: layoutOf(ctx),
15622
+ meta: metaOf(ctx)
15623
+ },
15624
+ reverse: [removeStepOf(ctx, pages)]
15625
+ };
15626
+ }
15627
+ /**
15628
+ * Deletes pages an insert or a restore brought, those that read as the change
15629
+ * left them; never every page of the document.
15630
+ */
15631
+ function removePages(ctx, step) {
15632
+ const order = orderOf(ctx);
15633
+ const pages = step.pages.filter(({ page, left }) => order.includes(page) && digestOf(ctx, page) === left).map(({ page }) => page);
15634
+ if (pages.length === 0 || pages.length >= order.length) return leftAlone(ctx, "pages.delete");
15635
+ authorize(ctx);
15636
+ return writeDelete(ctx, inDisplayOrder(order, pages));
15637
+ }
15638
+ /** `pages.insertBlank`; its reverse deletes the new pages while nobody changed them. */
15639
+ function insertBlankPages(ctx, op) {
15640
+ authorize(ctx);
15641
+ const result = inserterOf(ctx).insertBlank(op.spec, op.position ?? "end", ctx.signal, op.objectNumbers);
15642
+ const pages = result.insertedPages.map((page) => page.objectNumber);
15643
+ return {
15644
+ item: {
15645
+ type: "pages.insertBlank",
15646
+ ...result
15647
+ },
15648
+ reverse: [removeStepOf(ctx, pages)]
15649
+ };
15650
+ }
15651
+ /** `pages.insert`; its reverse deletes the pages it copied in while nobody changed them. */
15652
+ function insertPages(ctx, op) {
15653
+ authorize(ctx);
15654
+ const result = inserterOf(ctx).insert(bufferOf(op.source), op.position ?? "end", ctx.signal);
15655
+ ctx.session.invalidateDerived();
15656
+ const pages = result.insertedPages.map((page) => page.objectNumber);
15657
+ return {
15658
+ item: {
15659
+ type: "pages.insert",
15660
+ ...result
15661
+ },
15662
+ reverse: [removeStepOf(ctx, pages)]
15663
+ };
15664
+ }
15665
+ /** Deletes `pages`, in display order, each captured first so its restore brings it back. */
15666
+ function writeDelete(ctx, pages) {
15667
+ const docPtr = ctx.session.requireDocPtr();
15668
+ const captures = pages.map((page) => ({
15669
+ page,
15670
+ capture: exportPage(ctx.runtime, docPtr, page)
15671
+ }));
15672
+ const refs = pages.map(_embedpdf_engine_core_runtime.toPageRef);
15673
+ const result = mutatorOf(ctx).delete(refs, ctx.signal);
15674
+ ctx.session.invalidateDerived();
15675
+ return {
15676
+ item: {
15677
+ type: "pages.delete",
15678
+ pages: refs,
15679
+ ...result
15680
+ },
15681
+ reverse: [{
15682
+ kind: "pages.restore",
15683
+ pages: captures
15684
+ }]
15685
+ };
15686
+ }
15687
+ /** The step that deletes `pages` again while each reads as it does now. */
15688
+ function removeStepOf(ctx, pages) {
15689
+ return {
15690
+ kind: "pages.remove",
15691
+ pages: pages.map((page) => ({
15692
+ page,
15693
+ left: digestOf(ctx, page)
15694
+ }))
15695
+ };
15696
+ }
15697
+ /**
15698
+ * How a page reads now: the digest of its dictionary and of what the layer
15699
+ * holds of its annotations, contents and resources, as they read (canonical
15700
+ * bytes: a save that only encodes a stream again changes nothing), without
15701
+ * its place, and with its turn as a value.
15702
+ */
15703
+ function digestOf(ctx, page) {
15704
+ const { fn, mem } = ctx.runtime;
15705
+ const docPtr = ctx.session.requireDocPtr();
15706
+ const bytes = canonicalDict(ctx.runtime, docPtr, page, PAGE_DEEP_KEYS, PAGE_SKIPPED_KEYS);
15707
+ const index = ctx.session.allRecords().find((record) => record.pageObjectNumber === page);
15708
+ const turn = index ? rotationAt(ctx.runtime, docPtr, index.pageIndex) : 0;
15709
+ const turned = new TextEncoder().encode(`\nturn ${turn}`);
15710
+ const digested = new Uint8Array(bytes.byteLength + turned.byteLength);
15711
+ digested.set(bytes);
15712
+ digested.set(turned, bytes.byteLength);
15713
+ return sha256HexOf(fn, mem, digested);
15714
+ }
15715
+ /** The steps that turn each page back, one for each rotation the pages had. */
15716
+ function rotateSteps(pages, before, left) {
15717
+ const byRotation = /* @__PURE__ */ new Map();
15718
+ for (const page of pages) {
15719
+ const rotation = before.get(page);
15720
+ byRotation.set(rotation, [...byRotation.get(rotation) ?? [], page]);
15721
+ }
15722
+ return [...byRotation].map(([rotation, group]) => ({
15723
+ kind: "pages.rotate",
15724
+ pages: group,
15725
+ rotation,
15726
+ left
15727
+ }));
15728
+ }
15729
+ /** Where each of `pages` is in `order`: the page it follows (0: first), in display order. */
15730
+ function placementsOf(order, pages) {
15731
+ return inDisplayOrder(order, pages).map((page) => ({
15732
+ page,
15733
+ after: order[order.indexOf(page) - 1] ?? 0
15734
+ }));
15735
+ }
15736
+ /** `pages` sorted as `order` has them. */
15737
+ function inDisplayOrder(order, pages) {
15738
+ return [...pages].sort((left, right) => order.indexOf(left) - order.indexOf(right));
15739
+ }
15740
+ /** The document's pages by object number, in display order. */
15741
+ function orderOf(ctx) {
15742
+ return ctx.session.allRecords().map((record) => record.pageObjectNumber);
15743
+ }
15744
+ function layoutOf(ctx) {
15745
+ return new PagesReader(ctx.runtime, ctx.session).read(ctx.signal);
15746
+ }
15747
+ function rotationsOf(layout) {
15748
+ return new Map(layout.pages.map((page) => [page.ref.objectNumber, page.rotation]));
15749
+ }
15750
+ function metaOf(ctx) {
15751
+ return {
15752
+ affectedPages: [],
15753
+ cacheDelta: null,
15754
+ ...ctx.session.writeStamp()
15755
+ };
15756
+ }
15757
+ /** The bytes of `source` as an ArrayBuffer of their own. */
15758
+ function bufferOf(source) {
15759
+ return source.byteOffset === 0 && source.byteLength === source.buffer.byteLength ? source.buffer : source.slice().buffer;
15760
+ }
15761
+ //#endregion
15762
+ //#region src/features/changes/ChangeApplier.ts
15763
+ /**
15764
+ * The file's own coordinates, in and out: for a single verb's one-op change,
15765
+ * whose job converts at the worker's boundary.
15766
+ */
15767
+ const FILE_SPACE = {
15768
+ opInFileSpace: (op) => op,
15769
+ itemOut: (item) => item
15770
+ };
15771
+ /**
15772
+ * Runs changes inside the job's layer transaction, which its caller opens and
15773
+ * ends, so a failure anywhere leaves the document as it was: a change's ops in
15774
+ * order (`apply`), or the steps of a recorded reverse (`undo`). Each op and
15775
+ * step is checked against the change's authority as it runs, as its single
15776
+ * verb's job checks it.
15777
+ *
15778
+ * Every op records the steps that reverse it as it runs, from what it found
15779
+ * and wrote; the record lists them in reverse op order, so each step meets the
15780
+ * document as the op after it left it.
15781
+ */
15782
+ var ChangeApplier = class {
15783
+ constructor(runtime, session, fonts) {
15784
+ this.runtime = runtime;
15785
+ this.session = session;
15786
+ this.fonts = fonts;
15787
+ }
15788
+ apply(ops, authority, signal, space) {
15789
+ const ctx = this.context(authority, signal, (self) => opsFillOwnField(self, ops));
15790
+ return collect(authority, ops.map((op, at) => () => runOp(ctx, space.opInFileSpace(op), at)), space, signal);
15791
+ }
15792
+ undo(record, authority, signal, space) {
15793
+ const ctx = this.context(authority, signal, (self) => stepsFillOwnField(self, record.steps));
15794
+ return collect(authority, record.steps.map((step) => () => runStep(ctx, step)), space, signal);
15795
+ }
15796
+ context(authority, signal, ownFill) {
15797
+ let answer;
15798
+ const ctx = {
15799
+ runtime: this.runtime,
15800
+ session: this.session,
15801
+ ...this.fonts ? { fonts: this.fonts } : {},
15802
+ authority,
15803
+ signal,
15804
+ fillsOwnField: () => answer ?? (answer = ownFill(ctx))
15805
+ };
15806
+ return ctx;
15807
+ }
15808
+ };
15809
+ /**
15810
+ * Runs each in order, its item measured as soon as it ran; the record
15811
+ * reverses them in the opposite order.
15812
+ */
15813
+ function collect(authority, runs, space, signal) {
15814
+ const items = [];
15815
+ const reverses = [];
15816
+ for (const run of runs) {
15817
+ throwIfAborted(signal);
15818
+ const done = run();
15819
+ items.push(space.itemOut(done.item));
15820
+ reverses.push(done.reverse);
15821
+ }
15822
+ return {
14349
15823
  items,
14350
15824
  record: {
14351
15825
  userId: authority.identity.userId ?? null,
@@ -14378,17 +15852,34 @@ function runOp(ctx, op, at) {
14378
15852
  case "forms.importValues": return importFormValues(ctx, op);
14379
15853
  case "metadata.update": return updateMetadata(ctx, op, at);
14380
15854
  case "metadata.updateCustom": return updateCustomMetadata(ctx, op);
15855
+ case "outline.create": return createOutlineItem(ctx, op);
15856
+ case "outline.update": return updateOutlineItem(ctx, op, at);
15857
+ case "outline.move": return moveOutlineItems(ctx, op);
15858
+ case "outline.delete": return deleteOutlineItem(ctx, op, at);
15859
+ case "pages.rotate": return changingPageTree(ctx, () => rotatePages(ctx, op));
15860
+ case "pages.reorder": return changingPageTree(ctx, () => reorderPages(ctx, op));
15861
+ case "pages.delete": return changingPageTree(ctx, () => deletePages(ctx, op));
15862
+ case "pages.insertBlank": return changingPageTree(ctx, () => insertBlankPages(ctx, op));
15863
+ case "pages.insert": return changingPageTree(ctx, () => insertPages(ctx, op));
15864
+ case "attachments.create": return createAttachment(ctx, op);
15865
+ case "attachments.delete": return deleteAttachment(ctx, op);
14381
15866
  }
14382
15867
  }
14383
15868
  function runStep(ctx, step) {
14384
15869
  switch (step.kind) {
14385
- case "objects.revert": return "annotation" in step.subject ? revertAnnotation(ctx, {
14386
- ...step,
14387
- subject: step.subject
14388
- }) : revertForm(ctx, {
14389
- ...step,
14390
- subject: step.subject
14391
- });
15870
+ case "objects.revert":
15871
+ if ("annotation" in step.subject) return revertAnnotation(ctx, {
15872
+ ...step,
15873
+ subject: step.subject
15874
+ });
15875
+ if ("outlineItem" in step.subject) return revertOutlineItem(ctx, {
15876
+ ...step,
15877
+ subject: step.subject
15878
+ });
15879
+ return revertForm(ctx, {
15880
+ ...step,
15881
+ subject: step.subject
15882
+ });
14392
15883
  case "annotation.remove": return removeAnnotation(ctx, step);
14393
15884
  case "annotation.restore": return restoreAnnotations(ctx, step);
14394
15885
  case "annotation.reorder": return reorderBack(ctx, step);
@@ -14398,6 +15889,16 @@ function runStep(ctx, step) {
14398
15889
  case "widget.delete": return deleteRestoredWidget(ctx, step);
14399
15890
  case "calculations.restore": return restoreCalculationOrder(ctx, step);
14400
15891
  case "metadata.revert": return revertMetadata(ctx, step);
15892
+ case "outline.remove": return removeOutlineItem(ctx, step);
15893
+ case "outline.restore": return restoreOutlineItem(ctx, step);
15894
+ case "outline.move": return moveOutlineItemsBack(ctx, step);
15895
+ case "outline.reopen": return reopenOutlineItem(ctx, step);
15896
+ case "pages.rotate": return changingPageTree(ctx, () => rotatePagesBack(ctx, step));
15897
+ case "pages.reorder": return changingPageTree(ctx, () => reorderPagesBack(ctx, step));
15898
+ case "pages.restore": return changingPageTree(ctx, () => restorePages(ctx, step));
15899
+ case "pages.remove": return changingPageTree(ctx, () => removePages(ctx, step));
15900
+ case "attachments.remove": return removeAttachment(ctx, step);
15901
+ case "attachments.restore": return restoreAttachment(ctx, step);
14401
15902
  }
14402
15903
  }
14403
15904
  //#endregion
@@ -14417,6 +15918,19 @@ function recordBytes(record) {
14417
15918
  objects(step.unlinked);
14418
15919
  break;
14419
15920
  case "field.restore":
15921
+ case "outline.restore":
15922
+ bytes += step.capture.byteLength;
15923
+ break;
15924
+ case "outline.remove":
15925
+ objects(step.left);
15926
+ break;
15927
+ case "pages.restore":
15928
+ for (const { capture } of step.pages) bytes += capture.byteLength;
15929
+ break;
15930
+ case "attachments.remove":
15931
+ bytes += step.left.byteLength;
15932
+ break;
15933
+ case "attachments.restore":
14420
15934
  bytes += step.capture.byteLength;
14421
15935
  break;
14422
15936
  default: break;
@@ -14562,172 +16076,6 @@ function unpackChangeRecord(reverse, capture) {
14562
16076
  });
14563
16077
  }
14564
16078
  //#endregion
14565
- //#region src/features/attachments/AttachmentReader.ts
14566
- /**
14567
- * Read-only access to embedded files, at both of their homes: the
14568
- * document catalog's `/EmbeddedFiles` name tree (list / readFile by
14569
- * index) and a FileAttachment annotation's `/FS` (readAnnotationFile by
14570
- * ref). Pure reads over the session — no layer artifacts (the
14571
- * `PagesExtractor` shape).
14572
- *
14573
- * Byte delivery mirrors the request's `path?`: absent → a standalone
14574
- * buffer for the transfer list (browser); present → the decoded file is
14575
- * streamed to `path` via `FPDF_FILEWRITE` (server), and only metadata
14576
- * rides the response.
14577
- */
14578
- var AttachmentReader = class {
14579
- constructor(runtime, session) {
14580
- this.runtime = runtime;
14581
- this.session = session;
14582
- }
14583
- /** Snapshot of the `/EmbeddedFiles` name tree, in tree (key-sorted) order. */
14584
- list(signal) {
14585
- throwIfAborted(signal);
14586
- const { fn, mem } = this.runtime;
14587
- const docPtr = this.session.requireDocPtr();
14588
- const count = fn.FPDFDoc_GetAttachmentCount(docPtr);
14589
- const items = [];
14590
- for (let index = 0; index < count; index++) {
14591
- const attachmentPtr = fn.FPDFDoc_GetAttachment(docPtr, index);
14592
- if (!attachmentPtr) continue;
14593
- const key = readAttachmentKey(fn, mem, docPtr, index) ?? "";
14594
- items.push({
14595
- ...readAttachmentFileInfo(fn, mem, attachmentPtr),
14596
- ref: (0, _embedpdf_engine_core_runtime.toAttachmentRef)(key),
14597
- index
14598
- });
14599
- }
14600
- return items;
14601
- }
14602
- /** Decode one document-level embedded file, addressed by key. */
14603
- readFile(ref, path, maxDecodedBytes, signal) {
14604
- throwIfAborted(signal);
14605
- const { fn, mem } = this.runtime;
14606
- const docPtr = this.session.requireDocPtr();
14607
- const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
14608
- if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no embedded file with key '${ref.key}' in the document`);
14609
- const attachmentPtr = fn.FPDFDoc_GetAttachment(docPtr, index);
14610
- if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `embedded file with key '${ref.key}' resolved to index ${index} but could not be opened`);
14611
- const info = readAttachmentFileInfo(fn, mem, attachmentPtr);
14612
- return this.extract(attachmentPtr, info.name, info.mimeType ?? void 0, path, maxDecodedBytes);
14613
- }
14614
- /** Decode the file embedded in a FileAttachment annotation's `/FS`. */
14615
- /** A file attachment annotation's file; a page not parsed yet loads in slices. */
14616
- async readAnnotationFile(pageObjectNumber, ref, path, maxDecodedBytes, signal, slices) {
14617
- throwIfAborted(signal);
14618
- const { fn, mem } = this.runtime;
14619
- const pool = this.session.pagePool();
14620
- const pagePtr = await pool.acquireInSlices(pageObjectNumber, signal, slices);
14621
- let annotPtr = null;
14622
- try {
14623
- annotPtr = resolveAnnotPtr(this.runtime, this.session, pagePtr, ref);
14624
- const attachmentPtr = fn.FPDFAnnot_GetFileAttachment(annotPtr);
14625
- if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "annotation is not a file attachment or carries no embedded file (/FS)");
14626
- const info = readAttachmentFileInfo(fn, mem, attachmentPtr);
14627
- return this.extract(attachmentPtr, info.name, info.mimeType ?? void 0, path, maxDecodedBytes);
14628
- } finally {
14629
- if (annotPtr !== null) fn.FPDFPage_CloseAnnot(annotPtr);
14630
- pool.release(pageObjectNumber);
14631
- }
14632
- }
14633
- extract(attachmentPtr, name, mimeType, path, maxDecodedBytes) {
14634
- if (path !== void 0) {
14635
- const { size } = extractAttachmentToFile(this.runtime, attachmentPtr, path, maxDecodedBytes);
14636
- return {
14637
- name,
14638
- ...mimeType ? { mimeType } : {},
14639
- size,
14640
- path
14641
- };
14642
- }
14643
- const { bytes, size } = extractAttachmentToBuffer(this.runtime, attachmentPtr, maxDecodedBytes);
14644
- return {
14645
- name,
14646
- ...mimeType ? { mimeType } : {},
14647
- size,
14648
- bytes
14649
- };
14650
- }
14651
- };
14652
- //#endregion
14653
- //#region src/features/attachments/AttachmentMutator.ts
14654
- /**
14655
- * Create/delete mutations over the document catalog's `/EmbeddedFiles`
14656
- * name tree — the doc-level sibling of `AnnotationMutator`, sharing the
14657
- * write path (`writeAttachmentFilePayload`) with the file-attachment
14658
- * annotation writer.
14659
- *
14660
- * Identity is the name-tree key (unique by construction). Note the
14661
- * tree is key-sorted: both mutations shift other entries' indices; keys
14662
- * never move. Layer persistence (`finishMutation`) and event publication
14663
- * happen in the callers, exactly like every other mutation family.
14664
- */
14665
- var AttachmentMutator = class {
14666
- constructor(runtime, session) {
14667
- this.runtime = runtime;
14668
- this.session = session;
14669
- }
14670
- /**
14671
- * Create an embedded file. `file.name` becomes the tree key (PDFium
14672
- * writes key = /UF = /F); a duplicate key rejects with `InvalidArg`.
14673
- * The result carries the read-back entry, `checksum`/`size`/dates
14674
- * included.
14675
- */
14676
- create(file, resources, signal) {
14677
- throwIfAborted(signal);
14678
- const { fn, mem } = this.runtime;
14679
- const docPtr = this.session.requireDocPtr();
14680
- const resource = resources?.[file.resources.file];
14681
- if (!resource) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `attachment file references resource '${file.resources.file}' but no such binary payload accompanied the mutation`);
14682
- if (file.name.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "attachment file requires a name");
14683
- if (resolveAttachmentIndex(fn, mem, docPtr, {
14684
- kind: "key",
14685
- key: file.name
14686
- }) !== -1) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `an embedded file with key '${file.name}' already exists — keys are unique; rename and retry`);
14687
- throwIfAborted(signal);
14688
- const namePtr = mem.writeU16String(file.name);
14689
- let attachmentPtr;
14690
- try {
14691
- attachmentPtr = fn.FPDFDoc_AddAttachment(docPtr, namePtr);
14692
- } finally {
14693
- mem.free(namePtr);
14694
- }
14695
- if (!attachmentPtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFDoc_AddAttachment returned NULL");
14696
- writeAttachmentFilePayload(fn, mem, attachmentPtr, docPtr, file, resource);
14697
- const ref = (0, _embedpdf_engine_core_runtime.toAttachmentRef)(file.name);
14698
- const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
14699
- if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "created attachment did not resolve by key after the write");
14700
- return {
14701
- attachment: {
14702
- ...readAttachmentFileInfo(fn, mem, attachmentPtr),
14703
- ref,
14704
- index
14705
- },
14706
- meta: {
14707
- affectedPages: [],
14708
- cacheDelta: null,
14709
- ...this.session.writeStamp(),
14710
- changed: [ref]
14711
- }
14712
- };
14713
- }
14714
- /** Delete an embedded file by key. Unlinks the tree entry only. */
14715
- delete(ref, signal) {
14716
- throwIfAborted(signal);
14717
- const { fn, mem } = this.runtime;
14718
- const docPtr = this.session.requireDocPtr();
14719
- const index = resolveAttachmentIndex(fn, mem, docPtr, ref);
14720
- if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no embedded file with key '${ref.key}' in the document`);
14721
- if (!fn.FPDFDoc_DeleteAttachment(docPtr, index)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFDoc_DeleteAttachment returned false");
14722
- return { meta: {
14723
- affectedPages: [],
14724
- cacheDelta: null,
14725
- ...this.session.writeStamp(),
14726
- changed: [ref]
14727
- } };
14728
- }
14729
- };
14730
- //#endregion
14731
16079
  //#region src/features/security/internal/buildSecurityInfo.ts
14732
16080
  /**
14733
16081
  * Single constructor for the "successfully inspected" shape of
@@ -15562,11 +16910,6 @@ function assertSealedSignature(runtime, docPtr, model, expected) {
15562
16910
  function sameRange(a, b) {
15563
16911
  return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
15564
16912
  }
15565
- function bytesEqual(a, b) {
15566
- if (a.byteLength !== b.byteLength) return false;
15567
- for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
15568
- return true;
15569
- }
15570
16913
  //#endregion
15571
16914
  //#region src/features/forms/internal/fieldGroups.ts
15572
16915
  /** The group of the field `fieldObjectNumber`; `null` in none, or when the form has no such field. */
@@ -17096,13 +18439,8 @@ function resultInPageSpace(payload, boxOf) {
17096
18439
  ...payload,
17097
18440
  snapshot: (0, _embedpdf_engine_core_runtime.pageListOf)(payload.snapshot)
17098
18441
  };
17099
- case "pages.reorder":
17100
- case "pages.rotate":
17101
- case "pages.delete":
17102
18442
  case "pages.setName":
17103
- case "pages.removeName":
17104
- case "pages.insert":
17105
- case "pages.insertBlank": return {
18443
+ case "pages.removeName": return {
17106
18444
  ...payload,
17107
18445
  result: {
17108
18446
  ...payload.result,
@@ -17145,6 +18483,10 @@ function resultInPageSpace(payload, boxOf) {
17145
18483
  ...payload,
17146
18484
  snapshot: (0, _embedpdf_engine_core_runtime.mapDocumentActions)(payload.snapshot, toPage)
17147
18485
  };
18486
+ case "outline.read": return {
18487
+ ...payload,
18488
+ outline: (0, _embedpdf_engine_core_runtime.pageOutlineOf)(payload.outline, boxOf)
18489
+ };
17148
18490
  case "annotations.list": return {
17149
18491
  ...payload,
17150
18492
  list: {
@@ -17166,17 +18508,8 @@ function resultInPageSpace(payload, boxOf) {
17166
18508
  annotation: annotation(payload.result.annotation)
17167
18509
  }
17168
18510
  };
17169
- case "document.apply": return {
17170
- ...payload,
17171
- result: changeResultInPageSpace(payload.result, boxOf)
17172
- };
17173
- case "document.applyChanges": return {
17174
- ...payload,
17175
- outcomes: payload.outcomes.map((outcome) => outcome.status === "applied" ? {
17176
- ...outcome,
17177
- result: changeResultInPageSpace(outcome.result, boxOf)
17178
- } : outcome)
17179
- };
18511
+ case "document.apply":
18512
+ case "document.applyChanges": return payload;
17180
18513
  case "annotations.import": return {
17181
18514
  ...payload,
17182
18515
  result: {
@@ -17271,17 +18604,8 @@ function requestInFileSpace(job, boxOf) {
17271
18604
  ...job,
17272
18605
  draft: (0, _embedpdf_engine_core_runtime.pdfFormFieldDraftOf)(job.draft, boxOf)
17273
18606
  };
17274
- case "document.apply": return {
17275
- ...job,
17276
- change: changeInFileSpace(job.change, boxOf)
17277
- };
17278
- case "document.applyChanges": return {
17279
- ...job,
17280
- changes: job.changes.map((entry) => ({
17281
- ...entry,
17282
- change: changeInFileSpace(entry.change, boxOf)
17283
- }))
17284
- };
18607
+ case "document.apply":
18608
+ case "document.applyChanges": return job;
17285
18609
  case "forms.addWidget": return {
17286
18610
  ...job,
17287
18611
  placement: (0, _embedpdf_engine_core_runtime.pdfWidgetPlacementOf)(job.placement, boxOf)
@@ -17314,10 +18638,7 @@ function renderOptionsInFileSpace(options, visible) {
17314
18638
  target: target.kind === "rect" ? (0, _embedpdf_engine_core_runtime.pdfRenderTargetOf)(target, visible()) : target
17315
18639
  };
17316
18640
  }
17317
- function changeInFileSpace(change, boxOf) {
17318
- if ("undoOf" in change) return change;
17319
- return { ops: change.ops.map((op) => opInFileSpace(op, boxOf)) };
17320
- }
18641
+ /** An op's places in the file's coordinates, as its single verb's job's are. */
17321
18642
  function opInFileSpace(op, boxOf) {
17322
18643
  switch (op.type) {
17323
18644
  case "annotations.import":
@@ -17367,16 +18688,29 @@ function opInFileSpace(op, boxOf) {
17367
18688
  expect: pdfWidgetPatchOf(expect, boxOf(op.widget.page), boxOf)
17368
18689
  } : rest;
17369
18690
  }
18691
+ case "outline.create": return {
18692
+ ...op,
18693
+ data: (0, _embedpdf_engine_core_runtime.pdfOutlineDraftOf)(op.data, boxOf)
18694
+ };
18695
+ case "outline.update": {
18696
+ const { expect, ...rest } = op;
18697
+ return {
18698
+ ...rest,
18699
+ patch: (0, _embedpdf_engine_core_runtime.pdfOutlinePatchOf)(op.patch, boxOf),
18700
+ ...expect ? { expect: (0, _embedpdf_engine_core_runtime.pdfOutlinePatchOf)(expect, boxOf) } : {}
18701
+ };
18702
+ }
18703
+ case "outline.delete": {
18704
+ const { expect, ...rest } = op;
18705
+ return expect ? {
18706
+ ...rest,
18707
+ expect: (0, _embedpdf_engine_core_runtime.pdfOutlinePatchOf)(expect, boxOf)
18708
+ } : rest;
18709
+ }
17370
18710
  default: return op;
17371
18711
  }
17372
18712
  }
17373
- /** What a change did, each place in page space, as its single verbs' results are. */
17374
- function changeResultInPageSpace(result, boxOf) {
17375
- return {
17376
- ...result,
17377
- items: result.items.map((item) => itemInPageSpace(item, boxOf))
17378
- };
17379
- }
18713
+ /** What an op did, each place in page space, as its single verb's result is. */
17380
18714
  function itemInPageSpace(item, boxOf) {
17381
18715
  if ((0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) return item;
17382
18716
  const annotation = (read) => (0, _embedpdf_engine_core_runtime.pageAnnotationOf)(read, boxOf(read.page), boxOf);
@@ -17428,6 +18762,24 @@ function itemInPageSpace(item, boxOf) {
17428
18762
  field: item.field && (0, _embedpdf_engine_core_runtime.pageFormFieldOf)(item.field, boxOf),
17429
18763
  widgets: widgets(item.widgets)
17430
18764
  };
18765
+ case "outline.create":
18766
+ case "outline.update": return {
18767
+ ...item,
18768
+ item: (0, _embedpdf_engine_core_runtime.pageOutlineItemOf)(item.item, boxOf)
18769
+ };
18770
+ case "outline.restore": return {
18771
+ ...item,
18772
+ items: item.items.map((read) => (0, _embedpdf_engine_core_runtime.pageOutlineItemOf)(read, boxOf))
18773
+ };
18774
+ case "pages.rotate":
18775
+ case "pages.reorder":
18776
+ case "pages.delete":
18777
+ case "pages.insertBlank":
18778
+ case "pages.insert":
18779
+ case "pages.restore": return {
18780
+ ...item,
18781
+ layout: (0, _embedpdf_engine_core_runtime.pageListOf)(item.layout)
18782
+ };
17431
18783
  default: return item;
17432
18784
  }
17433
18785
  }
@@ -17965,6 +19317,9 @@ var WorkerHost = class {
17965
19317
  case "actions.read":
17966
19318
  resultPack = this.handleActionsRead(msg, ctrl.signal);
17967
19319
  break;
19320
+ case "outline.read":
19321
+ resultPack = this.handleOutlineRead(msg, ctrl.signal);
19322
+ break;
17968
19323
  case "annotations.list":
17969
19324
  resultPack = this.handleAnnotationsList(msg, ctrl.signal);
17970
19325
  break;
@@ -18070,15 +19425,6 @@ var WorkerHost = class {
18070
19425
  case "pages.list":
18071
19426
  resultPack = this.handlePagesList(msg, ctrl.signal);
18072
19427
  break;
18073
- case "pages.reorder":
18074
- resultPack = this.handlePagesReorder(msg, ctrl.signal);
18075
- break;
18076
- case "pages.rotate":
18077
- resultPack = this.handlePagesRotate(msg, ctrl.signal);
18078
- break;
18079
- case "pages.delete":
18080
- resultPack = this.handlePagesDelete(msg, ctrl.signal);
18081
- break;
18082
19428
  case "pages.setName":
18083
19429
  resultPack = this.handlePagesSetName(msg, ctrl.signal);
18084
19430
  break;
@@ -18109,24 +19455,12 @@ var WorkerHost = class {
18109
19455
  case "pages.extract":
18110
19456
  resultPack = this.handlePagesExtract(msg, ctrl.signal);
18111
19457
  break;
18112
- case "pages.insert":
18113
- resultPack = this.handlePagesInsert(msg, ctrl.signal);
18114
- break;
18115
- case "pages.insertBlank":
18116
- resultPack = this.handlePagesInsertBlank(msg, ctrl.signal);
18117
- break;
18118
19458
  case "attachments.list":
18119
19459
  resultPack = this.handleAttachmentsList(msg, ctrl.signal);
18120
19460
  break;
18121
19461
  case "attachments.readFile":
18122
19462
  resultPack = this.handleAttachmentsReadFile(msg, ctrl.signal);
18123
19463
  break;
18124
- case "attachments.create":
18125
- resultPack = this.handleAttachmentsCreate(msg, ctrl.signal);
18126
- break;
18127
- case "attachments.delete":
18128
- resultPack = this.handleAttachmentsDelete(msg, ctrl.signal);
18129
- break;
18130
19464
  case "measure.setScale":
18131
19465
  resultPack = this.handleMeasureSetScale(msg, ctrl.signal);
18132
19466
  break;
@@ -18484,6 +19818,13 @@ var WorkerHost = class {
18484
19818
  snapshot: new DocumentActionsReader(this.runtime, session).read(signal)
18485
19819
  });
18486
19820
  }
19821
+ handleOutlineRead(req, signal) {
19822
+ const session = this.requireSession(req);
19823
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
19824
+ tag: "outline.read",
19825
+ outline: new OutlineReader(this.runtime, session).read(signal)
19826
+ });
19827
+ }
18487
19828
  handleAnnotationsList(req, signal) {
18488
19829
  const session = this.requireSession(req);
18489
19830
  const pages = req.pages?.map((page) => session.resolvePageRef(page).pageObjectNumber);
@@ -18669,12 +20010,15 @@ var WorkerHost = class {
18669
20010
  const prior = ledger.outcome(opId);
18670
20011
  let result;
18671
20012
  if (prior) result = this.replayed(prior, fingerprint, opId);
18672
- else result = this.recorded(session, opId, fingerprint, () => new ChangeApplier(this.runtime, session, this.fonts).apply(change.ops, authority, signal));
20013
+ else result = this.recorded(session, opId, fingerprint, () => new ChangeApplier(this.runtime, session, this.fonts).apply(change.ops, authority, signal, FILE_SPACE));
18673
20014
  const item = result.items[0];
18674
20015
  if (!item || (0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `${op.type} wrote nothing`);
18675
20016
  return item;
18676
20017
  }
18677
- /** A change's answer the first time, kept under its `opId`; a retry's checks. */
20018
+ /**
20019
+ * A change's answer the first time, kept under its `opId`; a retry's
20020
+ * checks. The answer is in the space its job answered in (`ChangeOutcome`).
20021
+ */
18678
20022
  replayed(prior, fingerprint, opId) {
18679
20023
  if (prior.fingerprint !== fingerprint) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.IdempotencyKeyReused, `opId ${opId} already answered a different change`);
18680
20024
  if (prior.kind === "refused") throw (0, _embedpdf_engine_core_runtime.deserializeError)(prior.error);
@@ -18707,12 +20051,13 @@ var WorkerHost = class {
18707
20051
  session.assertUsable();
18708
20052
  if (req.objectNumberFloor !== void 0) session.raiseLastObjectNumber(req.objectNumberFloor - 1);
18709
20053
  const applier = new ChangeApplier(this.runtime, session, this.fonts);
20054
+ const space = this.pageSpaceOf(session);
18710
20055
  const records = /* @__PURE__ */ new Map();
18711
20056
  const outcomes = [];
18712
20057
  for (const entry of req.changes) {
18713
20058
  session.beginTransaction(entry.opId);
18714
20059
  try {
18715
- const applied = runServerChange(applier, entry, records, signal);
20060
+ const applied = runServerChange(applier, entry, records, signal, space);
18716
20061
  const result = resultOf(session, applied);
18717
20062
  session.commitTransaction();
18718
20063
  const record = result.meta.undoable ? applied.record : null;
@@ -18741,11 +20086,23 @@ var WorkerHost = class {
18741
20086
  if (!outcomes.some((outcome) => outcome.status === "applied" && outcome.result.items.some(_embedpdf_engine_core_runtime.itemWrote))) return (0, _embedpdf_engine_core_runtime.wirePack)(payload);
18742
20087
  return this.finishMutation(session, payload, req.artifactPath);
18743
20088
  }
20089
+ /**
20090
+ * Page space for a change, measured as each op runs: the page boxes are read
20091
+ * anew each time, as an op can add, remove or put back the page a later
20092
+ * one names.
20093
+ */
20094
+ pageSpaceOf(session) {
20095
+ return {
20096
+ opInFileSpace: (op) => opInFileSpace(op, visibleBoxReader(this.runtime, session)),
20097
+ itemOut: (item) => itemInPageSpace(item, visibleBoxReader(this.runtime, session))
20098
+ };
20099
+ }
18744
20100
  /** A change's ops, or the steps of the record an undo names. */
18745
20101
  runChange(session, req, signal) {
18746
20102
  const applier = new ChangeApplier(this.runtime, session, this.fonts);
20103
+ const space = this.pageSpaceOf(session);
18747
20104
  const userId = req.authority.identity.userId ?? null;
18748
- if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(req.change)) return applier.apply(req.change.ops, req.authority, signal);
20105
+ if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(req.change)) return applier.apply(req.change.ops, req.authority, signal, space);
18749
20106
  const kept = changeLedgerOf(session).record(req.change.undoOf);
18750
20107
  if (!kept || "unavailable" in kept) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.UndoUnavailable, `change ${req.change.undoOf} can no longer be undone`, { details: { reason: kept ? kept.unavailable : "expired" } });
18751
20108
  if (!kept.record) return {
@@ -18756,7 +20113,7 @@ var WorkerHost = class {
18756
20113
  }
18757
20114
  };
18758
20115
  if (kept.record.userId !== null && kept.record.userId !== userId) throw new _embedpdf_engine_core_runtime.PermissionDenied("changes:undo", "target");
18759
- return applier.undo(kept.record, req.authority, signal);
20116
+ return applier.undo(kept.record, req.authority, signal, space);
18760
20117
  }
18761
20118
  handlePagesList(req, signal) {
18762
20119
  const session = this.requireSession(req);
@@ -18765,30 +20122,6 @@ var WorkerHost = class {
18765
20122
  snapshot: new PagesReader(this.runtime, session).read(signal)
18766
20123
  });
18767
20124
  }
18768
- handlePagesReorder(req, signal) {
18769
- const session = this.requireSession(req);
18770
- const result = new PagesMutator(this.runtime, session).reorder(req.pages, req.position, signal);
18771
- return this.finishMutation(session, {
18772
- tag: "pages.reorder",
18773
- result
18774
- }, req.artifactPath);
18775
- }
18776
- handlePagesRotate(req, signal) {
18777
- const session = this.requireSession(req);
18778
- const result = new PagesMutator(this.runtime, session).rotate(req.pages, req.rotation, signal);
18779
- return this.finishMutation(session, {
18780
- tag: "pages.rotate",
18781
- result
18782
- }, req.artifactPath);
18783
- }
18784
- handlePagesDelete(req, signal) {
18785
- const session = this.requireSession(req);
18786
- const result = new PagesMutator(this.runtime, session).delete(req.pages, signal);
18787
- return this.finishMutation(session, {
18788
- tag: "pages.delete",
18789
- result
18790
- }, req.artifactPath);
18791
- }
18792
20125
  handlePagesSetName(req, signal) {
18793
20126
  const session = this.requireSession(req);
18794
20127
  const result = new PagesMutator(this.runtime, session).setName({
@@ -18841,32 +20174,13 @@ var WorkerHost = class {
18841
20174
  }
18842
20175
  handlePagesExtract(req, signal) {
18843
20176
  const session = this.requireSession(req);
18844
- const extracted = new PagesExtractor(this.runtime, session).extract(req.pages, signal);
20177
+ const extracted = new PagesExtractor(this.runtime, session).extract(req.pages, req.flatten, signal);
18845
20178
  return (0, _embedpdf_engine_core_runtime.wirePack)({
18846
20179
  tag: "pages.extract",
18847
20180
  bytes: extracted.bytes,
18848
20181
  size: extracted.size
18849
20182
  }, [extracted.bytes]);
18850
20183
  }
18851
- handlePagesInsert(req, signal) {
18852
- const session = this.requireSession(req);
18853
- const result = new PagesInserter(this.runtime, session).insert(req.bytes, req.position ?? "end", signal);
18854
- return this.finishMutation(session, {
18855
- tag: "pages.insert",
18856
- result
18857
- }, req.artifactPath);
18858
- }
18859
- handlePagesInsertBlank(req, signal) {
18860
- const session = this.requireSession(req);
18861
- const result = new PagesInserter(this.runtime, session).insertBlank({
18862
- size: req.size,
18863
- count: req.count
18864
- }, req.position ?? "end", signal, req.objectNumbers);
18865
- return this.finishMutation(session, {
18866
- tag: "pages.insertBlank",
18867
- result
18868
- }, req.artifactPath);
18869
- }
18870
20184
  handleAttachmentsList(req, signal) {
18871
20185
  const session = this.requireSession(req);
18872
20186
  return (0, _embedpdf_engine_core_runtime.wirePack)({
@@ -18882,22 +20196,6 @@ var WorkerHost = class {
18882
20196
  content
18883
20197
  }, content.bytes !== void 0 ? [content.bytes] : []);
18884
20198
  }
18885
- handleAttachmentsCreate(req, signal) {
18886
- const session = this.requireSession(req);
18887
- const result = new AttachmentMutator(this.runtime, session).create(req.file, req.resources, signal);
18888
- return this.finishMutation(session, {
18889
- tag: "attachments.create",
18890
- result
18891
- }, req.artifactPath);
18892
- }
18893
- handleAttachmentsDelete(req, signal) {
18894
- const session = this.requireSession(req);
18895
- const result = new AttachmentMutator(this.runtime, session).delete(req.ref, signal);
18896
- return this.finishMutation(session, {
18897
- tag: "attachments.delete",
18898
- result
18899
- }, req.artifactPath);
18900
- }
18901
20199
  async handleAnnotationsReadFile(req, signal) {
18902
20200
  const session = this.requireSession(req);
18903
20201
  const content = await new AttachmentReader(this.runtime, session).readAnnotationFile(session.resolvePageRef(req.page).pageObjectNumber, req.ref, req.path, req.maxDecodedBytes, signal, this.slices);
@@ -19662,9 +20960,9 @@ function resultOf(session, applied) {
19662
20960
  * this job kept for an earlier change of the request, or the one the server
19663
20961
  * sent. A change that left no record leaves nothing to undo.
19664
20962
  */
19665
- function runServerChange(applier, entry, records, signal) {
20963
+ function runServerChange(applier, entry, records, signal, space) {
19666
20964
  const { change, authority } = entry;
19667
- if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return applier.apply(change.ops, authority, signal);
20965
+ if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return applier.apply(change.ops, authority, signal, space);
19668
20966
  const userId = authority.identity.userId ?? null;
19669
20967
  const record = records.has(change.undoOf) ? records.get(change.undoOf) : entry.record;
19670
20968
  if (record === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.UndoUnavailable, `change ${change.undoOf} can no longer be undone`, { details: { reason: "expired" } });
@@ -19675,7 +20973,7 @@ function runServerChange(applier, entry, records, signal) {
19675
20973
  steps: []
19676
20974
  }
19677
20975
  };
19678
- return applier.undo(record, authority, signal);
20976
+ return applier.undo(record, authority, signal, space);
19679
20977
  }
19680
20978
  /** Every page a change's items touched, once each, in order. */
19681
20979
  function pagesOf(items) {
@@ -19769,6 +21067,7 @@ exports.DocumentPagesMutator = PagesMutator;
19769
21067
  exports.DocumentSaver = DocumentSaver;
19770
21068
  exports.DocumentSession = DocumentSession;
19771
21069
  exports.EPDF_CHAR_GEOMETRY_LAYOUT = EPDF_CHAR_GEOMETRY_LAYOUT;
21070
+ exports.FILE_SPACE = FILE_SPACE;
19772
21071
  exports.FontRegistrar = FontRegistrar;
19773
21072
  exports.FormExporter = FormExporter;
19774
21073
  exports.FormImporter = FormImporter;
@@ -19821,6 +21120,7 @@ exports.readAttachmentFileInfo = readAttachmentFileInfo;
19821
21120
  exports.readRevisions = readRevisions;
19822
21121
  exports.readSignaturesFromModel = readSignaturesFromModel;
19823
21122
  exports.readStructure = readStructure;
21123
+ exports.rotationAt = rotationAt;
19824
21124
  exports.throwIfAborted = throwIfAborted;
19825
21125
  exports.unpackChangeRecord = unpackChangeRecord;
19826
21126
  exports.visibleBoxReader = visibleBoxReader;