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