@embedpdf/plugin-page-edit 3.0.0-next.14 → 3.0.0-next.15
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/contract-B4DeVaKv.cjs +25 -0
- package/dist/contract-B4DeVaKv.cjs.map +1 -0
- package/dist/contract-CYWYR5Pd.js +21 -0
- package/dist/contract-CYWYR5Pd.js.map +1 -0
- package/dist/contract-dl4LfrRX.d.cts +80 -0
- package/dist/contract-dl4LfrRX.d.ts +80 -0
- package/dist/contract.cjs +1 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +1 -1
- package/dist/index.cjs +155 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -8
- package/dist/index.d.ts +6 -8
- package/dist/index.js +156 -86
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/contract-BnXi11KD.d.cts +0 -82
- package/dist/contract-BnXi11KD.d.ts +0 -82
- package/dist/contract-BsNelyd8.js +0 -7
- package/dist/contract-BsNelyd8.js.map +0 -1
- package/dist/contract-UeGRCn-t.cjs +0 -11
- package/dist/contract-UeGRCn-t.cjs.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/token.ts
|
|
2
|
+
/** The package token, created once here and re-exported by `contract.ts`. */
|
|
3
|
+
const PageEditToken = (0, require("@embedpdf/core").createCapabilityToken)("page-edit", {
|
|
4
|
+
hint: `add pageEditPlugin() from '@embedpdf/plugin-page-edit' to your plugins list`,
|
|
5
|
+
promises: {
|
|
6
|
+
rotateBy: true,
|
|
7
|
+
setRotation: true,
|
|
8
|
+
reorder: true,
|
|
9
|
+
delete: true,
|
|
10
|
+
insertBlank: true,
|
|
11
|
+
insertFromBytes: true,
|
|
12
|
+
insertFromDocument: true,
|
|
13
|
+
duplicate: true,
|
|
14
|
+
extract: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
//#endregion
|
|
18
|
+
Object.defineProperty(exports, "PageEditToken", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function() {
|
|
21
|
+
return PageEditToken;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=contract-B4DeVaKv.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-B4DeVaKv.cjs","names":[],"sources":["../src/token.ts"],"sourcesContent":["/** The package token, created once here and re-exported by `contract.ts`. */\nimport { createCapabilityToken } from '@embedpdf/core';\n\nimport type { PageEditCapability } from './contract';\n\nexport const PageEditToken = createCapabilityToken<PageEditCapability>('page-edit', {\n hint: `add pageEditPlugin() from '@embedpdf/plugin-page-edit' to your plugins list`,\n // Without a document, an adapter's stand-in rejects these with `not-ready`, as the\n // capability would; the type asks for every member that returns a promise.\n promises: {\n rotateBy: true,\n setRotation: true,\n reorder: true,\n delete: true,\n insertBlank: true,\n insertFromBytes: true,\n insertFromDocument: true,\n duplicate: true,\n extract: true,\n },\n});\n"],"mappings":";;AAKA,MAAa,iBAAA,2BAAA,CAAA,CAAA,sBAAA,CAA0D,aAAa;CAClF,MAAM;CAGN,UAAU;EACR,UAAU;EACV,aAAa;EACb,SAAS;EACT,QAAQ;EACR,aAAa;EACb,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,SAAS;CACX;AACF,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createCapabilityToken } from "@embedpdf/core";
|
|
2
|
+
//#region src/token.ts
|
|
3
|
+
/** The package token, created once here and re-exported by `contract.ts`. */
|
|
4
|
+
const PageEditToken = createCapabilityToken("page-edit", {
|
|
5
|
+
hint: `add pageEditPlugin() from '@embedpdf/plugin-page-edit' to your plugins list`,
|
|
6
|
+
promises: {
|
|
7
|
+
rotateBy: true,
|
|
8
|
+
setRotation: true,
|
|
9
|
+
reorder: true,
|
|
10
|
+
delete: true,
|
|
11
|
+
insertBlank: true,
|
|
12
|
+
insertFromBytes: true,
|
|
13
|
+
insertFromDocument: true,
|
|
14
|
+
duplicate: true,
|
|
15
|
+
extract: true
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export { PageEditToken as t };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=contract-CYWYR5Pd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-CYWYR5Pd.js","names":[],"sources":["../src/token.ts"],"sourcesContent":["/** The package token, created once here and re-exported by `contract.ts`. */\nimport { createCapabilityToken } from '@embedpdf/core';\n\nimport type { PageEditCapability } from './contract';\n\nexport const PageEditToken = createCapabilityToken<PageEditCapability>('page-edit', {\n hint: `add pageEditPlugin() from '@embedpdf/plugin-page-edit' to your plugins list`,\n // Without a document, an adapter's stand-in rejects these with `not-ready`, as the\n // capability would; the type asks for every member that returns a promise.\n promises: {\n rotateBy: true,\n setRotation: true,\n reorder: true,\n delete: true,\n insertBlank: true,\n insertFromBytes: true,\n insertFromDocument: true,\n duplicate: true,\n extract: true,\n },\n});\n"],"mappings":";;;AAKA,MAAa,gBAAgB,sBAA0C,aAAa;CAClF,MAAM;CAGN,UAAU;EACR,UAAU;EACV,aAAa;EACb,SAAS;EACT,QAAQ;EACR,aAAa;EACb,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,SAAS;CACX;AACF,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { OperationOptions, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
2
|
+
//#region src/token.d.ts
|
|
3
|
+
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/contract.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Where pages land: right after or before a page, or at the start or the end.
|
|
8
|
+
* A page named by its index is the page at that index when the verb is
|
|
9
|
+
* called, the page the user clicked; from then on it is that page, so the
|
|
10
|
+
* placement stays right when pages are reordered before the edit runs.
|
|
11
|
+
*/
|
|
12
|
+
type PagePlacement = {
|
|
13
|
+
readonly after: PageRef | number;
|
|
14
|
+
} | {
|
|
15
|
+
readonly before: PageRef | number;
|
|
16
|
+
} | 'start' | 'end';
|
|
17
|
+
/** What an insert resolves: the new pages, in the order they were inserted. */
|
|
18
|
+
interface PageEditInsertResult {
|
|
19
|
+
readonly pages: readonly PageRef[];
|
|
20
|
+
}
|
|
21
|
+
/** Options of `insertBlank()`. */
|
|
22
|
+
interface PageInsertBlankOptions extends OperationOptions {
|
|
23
|
+
/** Where the new pages go. Default `'end'`. */
|
|
24
|
+
readonly placement?: PagePlacement;
|
|
25
|
+
/** The page size, in points. Default: the size of the page the new pages are placed next to. */
|
|
26
|
+
readonly size?: PdfSize;
|
|
27
|
+
/** How many blank pages. Default 1. */
|
|
28
|
+
readonly count?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Options of `insertFromBytes()`. */
|
|
31
|
+
interface PageInsertFromBytesOptions extends OperationOptions {
|
|
32
|
+
/** Only the source PDF's pages at these indexes, in this order. Default: every page. */
|
|
33
|
+
readonly pageIndexes?: readonly number[];
|
|
34
|
+
/** Where the new pages go. Default `'end'`. */
|
|
35
|
+
readonly placement?: PagePlacement;
|
|
36
|
+
}
|
|
37
|
+
/** Options of the verbs that copy pages in: `insertFromDocument()` and `duplicate()`. */
|
|
38
|
+
interface PagePlacementOptions extends OperationOptions {
|
|
39
|
+
readonly placement?: PagePlacement;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Every edit runs in the order it was called, after the edits before it.
|
|
43
|
+
* Every verb rejects `permission-denied` (with `error.permission`) when the
|
|
44
|
+
* session may not do it, `not-found` for a page the document doesn't have,
|
|
45
|
+
* `invalid-input` for no pages, and `operation-cancelled` when its `signal`
|
|
46
|
+
* fires; a refusal changes nothing.
|
|
47
|
+
*/
|
|
48
|
+
interface PageEditCapability {
|
|
49
|
+
/** Whether rotating, reordering, deleting and inserting pages is allowed (`doc.pages.assemble`). */
|
|
50
|
+
canEdit(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether copying pages out is allowed (`doc.download`): `extract()`,
|
|
53
|
+
* `duplicate()` and `insertFromDocument()` need it.
|
|
54
|
+
*/
|
|
55
|
+
canExtract(): boolean;
|
|
56
|
+
/** Turn each page by a quarter or half turn from its own rotation. */
|
|
57
|
+
rotateBy(pages: readonly (PageRef | number)[], delta: 90 | -90 | 180, options?: OperationOptions): Promise<void>;
|
|
58
|
+
/** Give every page the same rotation. */
|
|
59
|
+
setRotation(pages: readonly (PageRef | number)[], rotation: PdfRotation, options?: OperationOptions): Promise<void>;
|
|
60
|
+
/** Move pages to the placement; several pages keep their order and land together. */
|
|
61
|
+
reorder(pages: readonly (PageRef | number)[], placement: PagePlacement, options?: OperationOptions): Promise<void>;
|
|
62
|
+
/** Delete pages. Deleting every page is refused (`invalid-input`): a document keeps one. */
|
|
63
|
+
delete(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<void>;
|
|
64
|
+
/** Insert blank pages, one unless `count` says more. */
|
|
65
|
+
insertBlank(options?: PageInsertBlankOptions): Promise<PageEditInsertResult>;
|
|
66
|
+
/** Insert the pages of another PDF, or only the ones at `pageIndexes`. */
|
|
67
|
+
insertFromBytes(bytes: Uint8Array | ArrayBuffer, options?: PageInsertFromBytesOptions): Promise<PageEditInsertResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Insert copies of pages from another document open in the viewer. Needs
|
|
70
|
+
* `doc.download` on that document and `doc.pages.assemble` on this one.
|
|
71
|
+
*/
|
|
72
|
+
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?: PagePlacementOptions): Promise<PageEditInsertResult>;
|
|
73
|
+
/** Copy pages, right after the last of them unless a placement says otherwise. */
|
|
74
|
+
duplicate(pages: readonly (PageRef | number)[], options?: PagePlacementOptions): Promise<PageEditInsertResult>;
|
|
75
|
+
/** Copy pages into a new PDF and resolve its bytes. This document doesn't change. */
|
|
76
|
+
extract(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<Uint8Array>;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { PagePlacement as a, PdfSize$1 as c, PageInsertFromBytesOptions as i, PageEditToken as l, PageEditInsertResult as n, PagePlacementOptions as o, PageInsertBlankOptions as r, PdfRotation$1 as s, PageEditCapability as t };
|
|
80
|
+
//# sourceMappingURL=contract-dl4LfrRX.d.cts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { OperationOptions, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
2
|
+
//#region src/token.d.ts
|
|
3
|
+
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/contract.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Where pages land: right after or before a page, or at the start or the end.
|
|
8
|
+
* A page named by its index is the page at that index when the verb is
|
|
9
|
+
* called, the page the user clicked; from then on it is that page, so the
|
|
10
|
+
* placement stays right when pages are reordered before the edit runs.
|
|
11
|
+
*/
|
|
12
|
+
type PagePlacement = {
|
|
13
|
+
readonly after: PageRef | number;
|
|
14
|
+
} | {
|
|
15
|
+
readonly before: PageRef | number;
|
|
16
|
+
} | 'start' | 'end';
|
|
17
|
+
/** What an insert resolves: the new pages, in the order they were inserted. */
|
|
18
|
+
interface PageEditInsertResult {
|
|
19
|
+
readonly pages: readonly PageRef[];
|
|
20
|
+
}
|
|
21
|
+
/** Options of `insertBlank()`. */
|
|
22
|
+
interface PageInsertBlankOptions extends OperationOptions {
|
|
23
|
+
/** Where the new pages go. Default `'end'`. */
|
|
24
|
+
readonly placement?: PagePlacement;
|
|
25
|
+
/** The page size, in points. Default: the size of the page the new pages are placed next to. */
|
|
26
|
+
readonly size?: PdfSize;
|
|
27
|
+
/** How many blank pages. Default 1. */
|
|
28
|
+
readonly count?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Options of `insertFromBytes()`. */
|
|
31
|
+
interface PageInsertFromBytesOptions extends OperationOptions {
|
|
32
|
+
/** Only the source PDF's pages at these indexes, in this order. Default: every page. */
|
|
33
|
+
readonly pageIndexes?: readonly number[];
|
|
34
|
+
/** Where the new pages go. Default `'end'`. */
|
|
35
|
+
readonly placement?: PagePlacement;
|
|
36
|
+
}
|
|
37
|
+
/** Options of the verbs that copy pages in: `insertFromDocument()` and `duplicate()`. */
|
|
38
|
+
interface PagePlacementOptions extends OperationOptions {
|
|
39
|
+
readonly placement?: PagePlacement;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Every edit runs in the order it was called, after the edits before it.
|
|
43
|
+
* Every verb rejects `permission-denied` (with `error.permission`) when the
|
|
44
|
+
* session may not do it, `not-found` for a page the document doesn't have,
|
|
45
|
+
* `invalid-input` for no pages, and `operation-cancelled` when its `signal`
|
|
46
|
+
* fires; a refusal changes nothing.
|
|
47
|
+
*/
|
|
48
|
+
interface PageEditCapability {
|
|
49
|
+
/** Whether rotating, reordering, deleting and inserting pages is allowed (`doc.pages.assemble`). */
|
|
50
|
+
canEdit(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether copying pages out is allowed (`doc.download`): `extract()`,
|
|
53
|
+
* `duplicate()` and `insertFromDocument()` need it.
|
|
54
|
+
*/
|
|
55
|
+
canExtract(): boolean;
|
|
56
|
+
/** Turn each page by a quarter or half turn from its own rotation. */
|
|
57
|
+
rotateBy(pages: readonly (PageRef | number)[], delta: 90 | -90 | 180, options?: OperationOptions): Promise<void>;
|
|
58
|
+
/** Give every page the same rotation. */
|
|
59
|
+
setRotation(pages: readonly (PageRef | number)[], rotation: PdfRotation, options?: OperationOptions): Promise<void>;
|
|
60
|
+
/** Move pages to the placement; several pages keep their order and land together. */
|
|
61
|
+
reorder(pages: readonly (PageRef | number)[], placement: PagePlacement, options?: OperationOptions): Promise<void>;
|
|
62
|
+
/** Delete pages. Deleting every page is refused (`invalid-input`): a document keeps one. */
|
|
63
|
+
delete(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<void>;
|
|
64
|
+
/** Insert blank pages, one unless `count` says more. */
|
|
65
|
+
insertBlank(options?: PageInsertBlankOptions): Promise<PageEditInsertResult>;
|
|
66
|
+
/** Insert the pages of another PDF, or only the ones at `pageIndexes`. */
|
|
67
|
+
insertFromBytes(bytes: Uint8Array | ArrayBuffer, options?: PageInsertFromBytesOptions): Promise<PageEditInsertResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Insert copies of pages from another document open in the viewer. Needs
|
|
70
|
+
* `doc.download` on that document and `doc.pages.assemble` on this one.
|
|
71
|
+
*/
|
|
72
|
+
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?: PagePlacementOptions): Promise<PageEditInsertResult>;
|
|
73
|
+
/** Copy pages, right after the last of them unless a placement says otherwise. */
|
|
74
|
+
duplicate(pages: readonly (PageRef | number)[], options?: PagePlacementOptions): Promise<PageEditInsertResult>;
|
|
75
|
+
/** Copy pages into a new PDF and resolve its bytes. This document doesn't change. */
|
|
76
|
+
extract(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<Uint8Array>;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { PagePlacement as a, PdfSize$1 as c, PageInsertFromBytesOptions as i, PageEditToken as l, PageEditInsertResult as n, PagePlacementOptions as o, PageInsertBlankOptions as r, PdfRotation$1 as s, PageEditCapability as t };
|
|
80
|
+
//# sourceMappingURL=contract-dl4LfrRX.d.ts.map
|
package/dist/contract.cjs
CHANGED
package/dist/contract.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { a as PagePlacement, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePlacementOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-dl4LfrRX.cjs";
|
|
2
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, PagePlacement, PagePlacementOptions, type PdfRotation, type PdfSize };
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { a as PagePlacement, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePlacementOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-dl4LfrRX.js";
|
|
2
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, PagePlacement, PagePlacementOptions, type PdfRotation, type PdfSize };
|
package/dist/contract.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as PageEditToken } from "./contract-
|
|
1
|
+
import { t as PageEditToken } from "./contract-CYWYR5Pd.js";
|
|
2
2
|
export { PageEditToken };
|
package/dist/index.cjs
CHANGED
|
@@ -1,116 +1,186 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_contract = require("./contract-
|
|
2
|
+
const require_contract = require("./contract-B4DeVaKv.cjs");
|
|
3
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/controller.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The page-edit controller. Stateless: it turns the engine handle's page
|
|
7
|
+
* service into page edits that take refs or indexes. Turning a page relative
|
|
8
|
+
* to its own rotation, and fixing a placement's page, live here once instead
|
|
9
|
+
* of in every app's click handler.
|
|
10
|
+
*
|
|
11
|
+
* A verb refuses before anything starts (permission, pages, placement), then
|
|
12
|
+
* queues its edit: edits run one at a time, in the order they were called.
|
|
11
13
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */
|
|
15
|
+
const ASSEMBLE = "doc.pages.assemble";
|
|
16
|
+
/** Copying pages out of a document is a partial download. The engine enforces it too. */
|
|
17
|
+
const DOWNLOAD = "doc.download";
|
|
18
|
+
/** The size of a blank page when the document has no page to match: Letter, in points. */
|
|
19
|
+
const LETTER_SIZE = {
|
|
20
|
+
width: 612,
|
|
21
|
+
height: 792
|
|
22
|
+
};
|
|
23
|
+
function createPageEditController(ctx) {
|
|
24
|
+
const enqueue = ctx.serialQueue("edits");
|
|
25
|
+
ctx.onSettle(() => enqueue.idle());
|
|
26
|
+
/** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */
|
|
27
|
+
const mapErrors = async (work) => {
|
|
28
|
+
try {
|
|
29
|
+
return await work();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw (0, _embedpdf_core.toPluginError)("page-edit", error);
|
|
32
|
+
}
|
|
25
33
|
};
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
34
|
+
const registry = () => ctx.document()?.pages ?? [];
|
|
35
|
+
const noPages = () => new _embedpdf_core.PluginError("invalid-input", "page-edit", "no pages given");
|
|
36
|
+
/**
|
|
37
|
+
* A verb's pages as refs, checked when it is called: an index names the
|
|
38
|
+
* page at that position now, which is the page the user clicked.
|
|
39
|
+
*/
|
|
40
|
+
const refsOf = (pages) => {
|
|
41
|
+
if (pages.length === 0) throw noPages();
|
|
42
|
+
return pages.map((page) => ctx.pageOf(page).ref);
|
|
29
43
|
};
|
|
30
44
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* a concurrent reorder. Returns the anchor page too, so `addBlank` can
|
|
34
|
-
* default its size without a second lookup.
|
|
45
|
+
* A placement as the engine's position, its page fixed when the verb is
|
|
46
|
+
* called, like the verb's own pages. Default `'end'`.
|
|
35
47
|
*/
|
|
36
|
-
const
|
|
37
|
-
if (!placement) return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
if ("index" in placement) return {
|
|
42
|
-
destIndex: placement.index,
|
|
43
|
-
anchor: void 0
|
|
44
|
-
};
|
|
45
|
-
const pon = "after" in placement ? placement.after : placement.before;
|
|
46
|
-
const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);
|
|
47
|
-
if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);
|
|
48
|
-
return {
|
|
49
|
-
destIndex: "after" in placement ? anchor.index + 1 : anchor.index,
|
|
50
|
-
anchor
|
|
51
|
-
};
|
|
48
|
+
const positionOf = (placement) => {
|
|
49
|
+
if (!placement || placement === "start" || placement === "end") return placement ?? "end";
|
|
50
|
+
if ("after" in placement) return { after: ctx.pageOf(placement.after).ref };
|
|
51
|
+
return { before: ctx.pageOf(placement.before).ref };
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
|
-
* Default blank-page size: the
|
|
55
|
-
*
|
|
56
|
-
* with an empty registry, which an open document never has — US Letter.
|
|
54
|
+
* Default blank-page size: the page the placement names, else the page the
|
|
55
|
+
* new ones go next to (the first or the last), else Letter.
|
|
57
56
|
*/
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
if (destIndex === void 0) return pages[pages.length - 1].size;
|
|
65
|
-
return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;
|
|
57
|
+
const sizeAt = (position) => {
|
|
58
|
+
if (typeof position === "object") return ctx.pageOf("after" in position ? position.after : position.before).size;
|
|
59
|
+
const pages = registry();
|
|
60
|
+
if (pages.length === 0) return LETTER_SIZE;
|
|
61
|
+
return (position === "start" ? pages[0] : pages[pages.length - 1]).size;
|
|
66
62
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
/** Insert a PDF's pages; resolves the new pages. */
|
|
64
|
+
const insertBytes = async (bytes, position, signal) => {
|
|
65
|
+
return { pages: (await ctx.cancellable(signal, ctx.doc.pages.insert(bytes, position))).insertedPages };
|
|
66
|
+
};
|
|
67
|
+
return { api: {
|
|
68
|
+
canEdit: () => ctx.allows(ASSEMBLE),
|
|
69
|
+
canExtract: () => ctx.allows(DOWNLOAD),
|
|
70
|
+
rotateBy: async (pages, delta, options) => {
|
|
71
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.rotateBy");
|
|
72
|
+
const refs = refsOf(pages);
|
|
73
|
+
const signal = options?.signal;
|
|
74
|
+
await enqueue(async () => {
|
|
75
|
+
const groups = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const ref of refs) {
|
|
77
|
+
const rotation = ((ctx.pageOf(ref).rotation + delta) % 360 + 360) % 360;
|
|
78
|
+
groups.set(rotation, [...groups.get(rotation) ?? [], ref]);
|
|
79
|
+
}
|
|
80
|
+
for (const [rotation, group] of groups) await ctx.cancellable(signal, ctx.doc.pages.rotate(group, rotation));
|
|
81
|
+
}, options);
|
|
82
|
+
},
|
|
83
|
+
setRotation: async (pages, rotation, options) => {
|
|
84
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.setRotation");
|
|
85
|
+
const refs = refsOf(pages);
|
|
86
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.rotate(refs, rotation)), options);
|
|
70
87
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
reorder: async (pages, placement, options) => {
|
|
89
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.reorder");
|
|
90
|
+
const refs = refsOf(pages);
|
|
91
|
+
const position = positionOf(placement);
|
|
92
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.reorder(refs, position)), options);
|
|
74
93
|
},
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
delete: async (pages, options) => {
|
|
95
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.delete");
|
|
96
|
+
const refs = refsOf(pages);
|
|
97
|
+
const deleting = new Set(refs.map((ref) => ref.objectNumber));
|
|
98
|
+
if (registry().every((page) => deleting.has(page.ref.objectNumber))) throw new _embedpdf_core.PluginError("invalid-input", "page-edit", "a document keeps at least one page");
|
|
99
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.delete(refs)), options);
|
|
77
100
|
},
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
insertBlank: async (options = {}) => {
|
|
102
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertBlank");
|
|
103
|
+
const position = positionOf(options.placement);
|
|
104
|
+
return enqueue(async () => {
|
|
105
|
+
const size = options.size ?? sizeAt(position);
|
|
106
|
+
return { pages: (await ctx.cancellable(options.signal, ctx.doc.pages.insertBlank({
|
|
107
|
+
size,
|
|
108
|
+
count: options.count
|
|
109
|
+
}, position))).insertedPages };
|
|
110
|
+
}, options);
|
|
80
111
|
},
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
insertFromBytes: async (bytes, options = {}) => {
|
|
113
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertFromBytes");
|
|
114
|
+
const position = positionOf(options.placement);
|
|
115
|
+
const { pageIndexes, signal } = options;
|
|
116
|
+
if (pageIndexes?.length === 0) throw noPages();
|
|
117
|
+
return enqueue(async () => {
|
|
118
|
+
if (!pageIndexes) return insertBytes(bytes, position, signal);
|
|
119
|
+
const source = await mapErrors(() => ctx.engine.open({
|
|
120
|
+
kind: "bytes",
|
|
121
|
+
id: `page-edit-source-${Date.now().toString(36)}`,
|
|
122
|
+
bytes: new Uint8Array(bytes)
|
|
123
|
+
}, { scope: ["*"] }));
|
|
124
|
+
try {
|
|
125
|
+
const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));
|
|
126
|
+
const refs = pageIndexes.map((index) => {
|
|
127
|
+
const page = layout.pages[index];
|
|
128
|
+
if (!page) throw new _embedpdf_core.PluginError("not-found", "page-edit", `the PDF has no page ${index}`);
|
|
129
|
+
return page.ref;
|
|
130
|
+
});
|
|
131
|
+
const subset = await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));
|
|
132
|
+
return await insertBytes(subset, position, signal);
|
|
133
|
+
} finally {
|
|
134
|
+
await source.close();
|
|
135
|
+
}
|
|
136
|
+
}, options);
|
|
83
137
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
138
|
+
insertFromDocument: async (documentId, pages, options = {}) => {
|
|
139
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertFromDocument");
|
|
140
|
+
if (pages.length === 0) throw noPages();
|
|
141
|
+
const other = ctx.documentHandle(documentId);
|
|
142
|
+
if (!other) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' is not open`);
|
|
143
|
+
const otherPages = ctx.get(_embedpdf_core.DocumentsToken).listPages(documentId);
|
|
144
|
+
const refs = pages.map((page) => {
|
|
145
|
+
const found = typeof page === "number" ? otherPages[page] : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);
|
|
146
|
+
if (!found) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' has no such page`);
|
|
147
|
+
return found.ref;
|
|
148
|
+
});
|
|
149
|
+
const position = positionOf(options.placement);
|
|
150
|
+
return enqueue(async () => {
|
|
151
|
+
const bytes = await mapErrors(() => ctx.cancellable(options.signal, other.pages.extract(refs)));
|
|
152
|
+
return insertBytes(bytes, position, options.signal);
|
|
153
|
+
}, options);
|
|
92
154
|
},
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
155
|
+
duplicate: async (pages, options = {}) => {
|
|
156
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.duplicate");
|
|
157
|
+
ctx.assertAllowed(DOWNLOAD, "pageEdit.duplicate");
|
|
158
|
+
const refs = refsOf(pages);
|
|
159
|
+
const position = options.placement ? positionOf(options.placement) : { after: refs[refs.length - 1] };
|
|
160
|
+
return enqueue(async () => {
|
|
161
|
+
const bytes = await ctx.cancellable(options.signal, ctx.doc.pages.extract(refs));
|
|
162
|
+
return insertBytes(bytes, position, options.signal);
|
|
163
|
+
}, options);
|
|
164
|
+
},
|
|
165
|
+
extract: async (pages, options) => {
|
|
166
|
+
ctx.assertAllowed(DOWNLOAD, "pageEdit.extract");
|
|
167
|
+
const refs = refsOf(pages);
|
|
168
|
+
return enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.extract(refs)), options);
|
|
97
169
|
}
|
|
98
|
-
};
|
|
170
|
+
} };
|
|
99
171
|
}
|
|
100
172
|
//#endregion
|
|
101
173
|
//#region src/page-edit.plugin.ts
|
|
102
174
|
/**
|
|
103
|
-
* Document-scoped
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* home for client-side edit state (pending/optimistic/undo) when it lands —
|
|
107
|
-
* adapters keep their thin hook over `PageEditToken` unchanged.
|
|
175
|
+
* Document-scoped and stateless: the engine handle's page service as page
|
|
176
|
+
* edits that take refs or indexes. Turning a page relative to its own
|
|
177
|
+
* rotation lives in the controller, so no app re-derives it.
|
|
108
178
|
*/
|
|
109
179
|
const pageEditPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
110
180
|
id: "page-edit",
|
|
111
181
|
scope: "document",
|
|
112
182
|
token: require_contract.PageEditToken,
|
|
113
|
-
|
|
183
|
+
create: createPageEditController
|
|
114
184
|
});
|
|
115
185
|
//#endregion
|
|
116
186
|
exports.PageEditToken = require_contract.PageEditToken;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["PageEditToken"],"sources":["../src/capability.ts","../src/page-edit.plugin.ts"],"sourcesContent":["import type {\n PluginContext,\n PageObjectNumber,\n PageRotation,\n DocCapability,\n PdfSize,\n} from '@embedpdf/core';\nimport type { PageEditCapability, PagePlacement } from './types';\n\n/**\n * Authorization for structural page edits. Maps to PDF bit 11 (ASSEMBLE =\n * insert/rotate/delete pages). The engine ALSO enforces this — every\n * page-structure verb throws `PermissionDenied` without it, identically on\n * local and cloud — so `canEdit()` is the UI mirror of a real guard, not the\n * guard itself. Typed as `DocCapability` so a typo is a compile error.\n */\nconst ASSEMBLE_CAPABILITY: DocCapability = 'doc.pages.assemble';\n\n/**\n * Forwards structural edits to the document's engine handle, addressed by PON.\n * The handle (`ctx.doc`) and the page registry (`ctx.document()`, kept in sync\n * by the kernel's event→registry bridge) are both already on the plugin\n * context — this capability is the thin layer that turns the relative rotate\n * gesture into the engine's absolute wire.\n */\nexport function createPageEditCapability(ctx: PluginContext<unknown>): PageEditCapability {\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[page-edit] no document bound');\n return doc;\n };\n\n /** Current absolute rotation of a page from the registry; 0 if unknown. */\n const rotationOf = (pon: PageObjectNumber): PageRotation => {\n const page = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n return page?.rotation ?? 0;\n };\n\n /**\n * Resolve a placement to the engine's index wire, from the registry at\n * call time — the PON anchors are why \"add after this thumbnail\" survives\n * a concurrent reorder. Returns the anchor page too, so `addBlank` can\n * default its size without a second lookup.\n */\n const resolvePlacement = (placement?: PagePlacement) => {\n if (!placement) return { destIndex: undefined, anchor: undefined };\n if ('index' in placement) return { destIndex: placement.index, anchor: undefined };\n const pon = 'after' in placement ? placement.after : placement.before;\n const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);\n return { destIndex: 'after' in placement ? anchor.index + 1 : anchor.index, anchor };\n };\n\n /**\n * Default blank-page size: the insertion point's predecessor (the page\n * the new one will follow), else the first page, else — only reachable\n * with an empty registry, which an open document never has — US Letter.\n */\n const neighbourSize = (destIndex: number | undefined): PdfSize => {\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return { width: 612, height: 792 };\n if (destIndex === undefined) return pages[pages.length - 1].size;\n return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;\n };\n\n return {\n canEdit() {\n // Wildcard-aware predicate (mirrors the engine's own enforcement),\n // identical on both engines. NOT `effectiveScope.includes(...)` — that\n // enumeration drops the `*` admin grant, so it would hide the UI on a\n // default admin open even though the engine allows the edit.\n return ctx.doc?.security.allows(ASSEMBLE_CAPABILITY) ?? false;\n },\n\n rotateBy(pon, delta) {\n // Wrap to [0, 360) — the double-mod keeps -90 from current 0 landing on 270.\n const next = ((((rotationOf(pon) + delta) % 360) + 360) % 360) as PageRotation;\n return requireDoc().pages.rotate([pon], next);\n },\n\n setRotation(pons, rotation) {\n return requireDoc().pages.rotate(pons, rotation);\n },\n\n move(pons, destIndex) {\n return requireDoc().pages.move(pons, destIndex);\n },\n\n delete(pons) {\n return requireDoc().pages.delete(pons);\n },\n\n addBlank(opts = {}) {\n const doc = requireDoc();\n const { destIndex, anchor } = resolvePlacement(opts.placement);\n // PON placements match the anchor the user is looking at; everything\n // else matches the neighbour the new page will follow.\n const size = opts.size ?? anchor?.size ?? neighbourSize(destIndex);\n return doc.pages.insertBlank({ size, count: opts.count }, destIndex);\n },\n\n insert(bytes, opts = {}) {\n const doc = requireDoc();\n const { destIndex } = resolvePlacement(opts.placement);\n return doc.pages.insert(bytes, destIndex);\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createPageEditCapability } from './capability';\nimport { PageEditToken } from './types';\n\n/**\n * Document-scoped, stateless: turns the engine handle's page service into a\n * PON-addressed edit capability. The relative→absolute rotation lives in the\n * capability so the four framework adapters never re-derive it, and this is the\n * home for client-side edit state (pending/optimistic/undo) when it lands —\n * adapters keep their thin hook over `PageEditToken` unchanged.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n capability: createPageEditCapability,\n });\n"],"mappings":";;;;;;;;;;;AAgBA,MAAM,sBAAqC;;;;;;;;AAS3C,SAAgB,yBAAyB,KAAiD;CACxF,MAAM,mBAAmB;EACvB,MAAM,MAAM,IAAI;EAChB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,+BAA+B;EACzD,OAAO;CACT;;CAGA,MAAM,cAAc,QAAwC;EAE1D,QADa,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG,EAAA,EAC5D,YAAY;CAC3B;;;;;;;CAQA,MAAM,oBAAoB,cAA8B;EACtD,IAAI,CAAC,WAAW,OAAO;GAAE,WAAW,KAAA;GAAW,QAAQ,KAAA;EAAU;EACjE,IAAI,WAAW,WAAW,OAAO;GAAE,WAAW,UAAU;GAAO,QAAQ,KAAA;EAAU;EACjF,MAAM,MAAM,WAAW,YAAY,UAAU,QAAQ,UAAU;EAC/D,MAAM,SAAS,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG;EAC3E,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yCAAyC,KAAK;EAC3E,OAAO;GAAE,WAAW,WAAW,YAAY,OAAO,QAAQ,IAAI,OAAO;GAAO;EAAO;CACrF;;;;;;CAOA,MAAM,iBAAiB,cAA2C;EAChE,MAAM,QAAQ,IAAI,SAAS,CAAC,EAAE,SAAS,CAAC;EACxC,IAAI,MAAM,WAAW,GAAG,OAAO;GAAE,OAAO;GAAK,QAAQ;EAAI;EACzD,IAAI,cAAc,KAAA,GAAW,OAAO,MAAM,MAAM,SAAS,EAAE,CAAC;EAC5D,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC;CACvE;CAEA,OAAO;EACL,UAAU;GAKR,OAAO,IAAI,KAAK,SAAS,OAAO,mBAAmB,KAAK;EAC1D;EAEA,SAAS,KAAK,OAAO;GAEnB,MAAM,SAAW,WAAW,GAAG,IAAI,SAAS,MAAO,OAAO;GAC1D,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,GAAG,IAAI;EAC9C;EAEA,YAAY,MAAM,UAAU;GAC1B,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,MAAM,QAAQ;EACjD;EAEA,KAAK,MAAM,WAAW;GACpB,OAAO,WAAW,CAAC,CAAC,MAAM,KAAK,MAAM,SAAS;EAChD;EAEA,OAAO,MAAM;GACX,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,IAAI;EACvC;EAEA,SAAS,OAAO,CAAC,GAAG;GAClB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,WAAW,WAAW,iBAAiB,KAAK,SAAS;GAG7D,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,cAAc,SAAS;GACjE,OAAO,IAAI,MAAM,YAAY;IAAE;IAAM,OAAO,KAAK;GAAM,GAAG,SAAS;EACrE;EAEA,OAAO,OAAO,OAAO,CAAC,GAAG;GACvB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,cAAc,iBAAiB,KAAK,SAAS;GACrD,OAAO,IAAI,MAAM,OAAO,OAAO,SAAS;EAC1C;CACF;AACF;;;;;;;;;;AChGA,MAAa,wBAAA,GAAA,eAAA,aAAA,CACE;CACX,IAAI;CACJ,OAAO;CACP,OAAOA,iBAAAA;CACP,YAAY;AACd,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["PluginError","DocumentsToken","PageEditToken"],"sources":["../src/controller.ts","../src/page-edit.plugin.ts"],"sourcesContent":["/**\n * The page-edit controller. Stateless: it turns the engine handle's page\n * service into page edits that take refs or indexes. Turning a page relative\n * to its own rotation, and fixing a placement's page, live here once instead\n * of in every app's click handler.\n *\n * A verb refuses before anything starts (permission, pages, placement), then\n * queues its edit: edits run one at a time, in the order they were called.\n */\nimport { DocumentsToken, PluginError, toPluginError } from '@embedpdf/core';\nimport type {\n DocCapability,\n PageInfo,\n PagePosition,\n PageRef,\n PdfRotation,\n PdfSize,\n PluginContext,\n} from '@embedpdf/core';\n\nimport type { PageEditCapability, PageEditInsertResult, PagePlacement } from './contract';\n\n/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */\nconst ASSEMBLE: DocCapability = 'doc.pages.assemble';\n/** Copying pages out of a document is a partial download. The engine enforces it too. */\nconst DOWNLOAD: DocCapability = 'doc.download';\n\n/** The size of a blank page when the document has no page to match: Letter, in points. */\nconst LETTER_SIZE: PdfSize = { width: 612, height: 792 };\n\nexport function createPageEditController(ctx: PluginContext<void>) {\n const enqueue = ctx.serialQueue('edits');\n // A download waits for the page edits on their way.\n ctx.onSettle(() => enqueue.idle());\n\n /** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */\n const mapErrors = async <T>(work: () => Promise<T>): Promise<T> => {\n try {\n return await work();\n } catch (error) {\n throw toPluginError('page-edit', error);\n }\n };\n const registry = (): readonly PageInfo[] => ctx.document()?.pages ?? [];\n const noPages = () => new PluginError('invalid-input', 'page-edit', 'no pages given');\n\n /**\n * A verb's pages as refs, checked when it is called: an index names the\n * page at that position now, which is the page the user clicked.\n */\n const refsOf = (pages: readonly (PageRef | number)[]): PageRef[] => {\n if (pages.length === 0) throw noPages();\n return pages.map((page) => ctx.pageOf(page).ref);\n };\n\n /**\n * A placement as the engine's position, its page fixed when the verb is\n * called, like the verb's own pages. Default `'end'`.\n */\n const positionOf = (placement: PagePlacement | undefined): PagePosition => {\n if (!placement || placement === 'start' || placement === 'end') return placement ?? 'end';\n if ('after' in placement) return { after: ctx.pageOf(placement.after).ref };\n return { before: ctx.pageOf(placement.before).ref };\n };\n /**\n * Default blank-page size: the page the placement names, else the page the\n * new ones go next to (the first or the last), else Letter.\n */\n const sizeAt = (position: PagePosition): PdfSize => {\n if (typeof position === 'object') {\n return ctx.pageOf('after' in position ? position.after : position.before).size;\n }\n const pages = registry();\n if (pages.length === 0) return LETTER_SIZE;\n return (position === 'start' ? pages[0] : pages[pages.length - 1]).size;\n };\n\n /** Insert a PDF's pages; resolves the new pages. */\n const insertBytes = async (\n bytes: Uint8Array | ArrayBuffer,\n position: PagePosition,\n signal: AbortSignal | undefined,\n ): Promise<PageEditInsertResult> => {\n const result = await ctx.cancellable(signal, ctx.doc.pages.insert(bytes, position));\n return { pages: result.insertedPages };\n };\n\n const api: PageEditCapability = {\n canEdit: () => ctx.allows(ASSEMBLE),\n canExtract: () => ctx.allows(DOWNLOAD),\n\n rotateBy: async (pages, delta, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.rotateBy');\n const refs = refsOf(pages);\n const signal = options?.signal;\n await enqueue(async () => {\n // The engine sets one rotation per call, so pages are grouped by where\n // they end up. Each page's rotation is read when the edit runs, after\n // the edits queued before it; the double modulo keeps -90 from 0 at 270.\n const groups = new Map<PdfRotation, PageRef[]>();\n for (const ref of refs) {\n const turned = (ctx.pageOf(ref).rotation + delta) % 360;\n const rotation = ((turned + 360) % 360) as PdfRotation;\n groups.set(rotation, [...(groups.get(rotation) ?? []), ref]);\n }\n for (const [rotation, group] of groups) {\n await ctx.cancellable(signal, ctx.doc.pages.rotate(group, rotation));\n }\n }, options);\n },\n\n setRotation: async (pages, rotation, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.setRotation');\n const refs = refsOf(pages);\n await enqueue(\n () => ctx.cancellable(options?.signal, ctx.doc.pages.rotate(refs, rotation)),\n options,\n );\n },\n\n reorder: async (pages, placement, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.reorder');\n const refs = refsOf(pages);\n const position = positionOf(placement);\n await enqueue(\n () => ctx.cancellable(options?.signal, ctx.doc.pages.reorder(refs, position)),\n options,\n );\n },\n\n delete: async (pages, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.delete');\n const refs = refsOf(pages);\n const deleting = new Set(refs.map((ref) => ref.objectNumber));\n if (registry().every((page) => deleting.has(page.ref.objectNumber))) {\n throw new PluginError('invalid-input', 'page-edit', 'a document keeps at least one page');\n }\n await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.delete(refs)), options);\n },\n\n insertBlank: async (options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertBlank');\n const position = positionOf(options.placement);\n return enqueue(async () => {\n const size = options.size ?? sizeAt(position);\n const result = await ctx.cancellable(\n options.signal,\n ctx.doc.pages.insertBlank({ size, count: options.count }, position),\n );\n return { pages: result.insertedPages };\n }, options);\n },\n\n insertFromBytes: async (bytes, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertFromBytes');\n const position = positionOf(options.placement);\n const { pageIndexes, signal } = options;\n if (pageIndexes?.length === 0) throw noPages();\n return enqueue(async () => {\n if (!pageIndexes) return insertBytes(bytes, position, signal);\n // Some of the pages: open the bytes beside the document, extract them, insert those.\n const source = await mapErrors(() =>\n ctx.engine.open(\n {\n kind: 'bytes',\n id: `page-edit-source-${Date.now().toString(36)}`,\n bytes: new Uint8Array(bytes),\n },\n { scope: ['*'] },\n ),\n );\n try {\n const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));\n const refs = pageIndexes.map((index) => {\n const page = layout.pages[index];\n if (!page) {\n throw new PluginError('not-found', 'page-edit', `the PDF has no page ${index}`);\n }\n return page.ref;\n });\n const subset = await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));\n return await insertBytes(subset, position, signal);\n } finally {\n await source.close();\n }\n }, options);\n },\n\n insertFromDocument: async (documentId, pages, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertFromDocument');\n if (pages.length === 0) throw noPages();\n const other = ctx.documentHandle(documentId);\n if (!other) {\n throw new PluginError('not-found', 'page-edit', `document '${documentId}' is not open`);\n }\n // The pages are the other document's, so its page list resolves them.\n const otherPages = ctx.get(DocumentsToken).listPages(documentId);\n const refs = pages.map((page) => {\n const found =\n typeof page === 'number'\n ? otherPages[page]\n : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);\n if (!found) {\n throw new PluginError(\n 'not-found',\n 'page-edit',\n `document '${documentId}' has no such page`,\n );\n }\n return found.ref;\n });\n const position = positionOf(options.placement);\n return enqueue(async () => {\n // The other document's session decides whether its pages may be copied\n // out: the engine refuses `doc.download` there with the permission named.\n const bytes = await mapErrors(() =>\n ctx.cancellable(options.signal, other.pages.extract(refs)),\n );\n return insertBytes(bytes, position, options.signal);\n }, options);\n },\n\n duplicate: async (pages, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.duplicate');\n ctx.assertAllowed(DOWNLOAD, 'pageEdit.duplicate');\n const refs = refsOf(pages);\n const position = options.placement\n ? positionOf(options.placement)\n : { after: refs[refs.length - 1]! };\n return enqueue(async () => {\n const bytes = await ctx.cancellable(options.signal, ctx.doc.pages.extract(refs));\n return insertBytes(bytes, position, options.signal);\n }, options);\n },\n\n extract: async (pages, options) => {\n ctx.assertAllowed(DOWNLOAD, 'pageEdit.extract');\n const refs = refsOf(pages);\n return enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.extract(refs)), options);\n },\n };\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { PageEditToken } from './contract';\nimport { createPageEditController } from './controller';\n\n/**\n * Document-scoped and stateless: the engine handle's page service as page\n * edits that take refs or indexes. Turning a page relative to its own\n * rotation lives in the controller, so no app re-derives it.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n create: createPageEditController,\n });\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as PagePlacement, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePlacementOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-dl4LfrRX.cjs";
|
|
2
2
|
//#region src/page-edit.plugin.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Document-scoped
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* home for client-side edit state (pending/optimistic/undo) when it lands —
|
|
8
|
-
* adapters keep their thin hook over `PageEditToken` unchanged.
|
|
4
|
+
* Document-scoped and stateless: the engine handle's page service as page
|
|
5
|
+
* edits that take refs or indexes. Turning a page relative to its own
|
|
6
|
+
* rotation lives in the controller, so no app re-derives it.
|
|
9
7
|
*/
|
|
10
|
-
declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<
|
|
8
|
+
declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<void, PageEditCapability, import("@embedpdf/core").NoSettings>;
|
|
11
9
|
//#endregion
|
|
12
|
-
export {
|
|
10
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, PagePlacement, PagePlacementOptions, type PdfRotation, type PdfSize, pageEditPlugin };
|
|
13
11
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as PagePlacement, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePlacementOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-dl4LfrRX.js";
|
|
2
2
|
//#region src/page-edit.plugin.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Document-scoped
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* home for client-side edit state (pending/optimistic/undo) when it lands —
|
|
8
|
-
* adapters keep their thin hook over `PageEditToken` unchanged.
|
|
4
|
+
* Document-scoped and stateless: the engine handle's page service as page
|
|
5
|
+
* edits that take refs or indexes. Turning a page relative to its own
|
|
6
|
+
* rotation lives in the controller, so no app re-derives it.
|
|
9
7
|
*/
|
|
10
|
-
declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<
|
|
8
|
+
declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<void, PageEditCapability, import("@embedpdf/core").NoSettings>;
|
|
11
9
|
//#endregion
|
|
12
|
-
export {
|
|
10
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, PagePlacement, PagePlacementOptions, type PdfRotation, type PdfSize, pageEditPlugin };
|
|
13
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,115 +1,185 @@
|
|
|
1
|
-
import { t as PageEditToken } from "./contract-
|
|
2
|
-
import { definePlugin } from "@embedpdf/core";
|
|
3
|
-
//#region src/
|
|
1
|
+
import { t as PageEditToken } from "./contract-CYWYR5Pd.js";
|
|
2
|
+
import { DocumentsToken, PluginError, definePlugin, toPluginError } from "@embedpdf/core";
|
|
3
|
+
//#region src/controller.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* The page-edit controller. Stateless: it turns the engine handle's page
|
|
6
|
+
* service into page edits that take refs or indexes. Turning a page relative
|
|
7
|
+
* to its own rotation, and fixing a placement's page, live here once instead
|
|
8
|
+
* of in every app's click handler.
|
|
9
|
+
*
|
|
10
|
+
* A verb refuses before anything starts (permission, pages, placement), then
|
|
11
|
+
* queues its edit: edits run one at a time, in the order they were called.
|
|
10
12
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */
|
|
14
|
+
const ASSEMBLE = "doc.pages.assemble";
|
|
15
|
+
/** Copying pages out of a document is a partial download. The engine enforces it too. */
|
|
16
|
+
const DOWNLOAD = "doc.download";
|
|
17
|
+
/** The size of a blank page when the document has no page to match: Letter, in points. */
|
|
18
|
+
const LETTER_SIZE = {
|
|
19
|
+
width: 612,
|
|
20
|
+
height: 792
|
|
21
|
+
};
|
|
22
|
+
function createPageEditController(ctx) {
|
|
23
|
+
const enqueue = ctx.serialQueue("edits");
|
|
24
|
+
ctx.onSettle(() => enqueue.idle());
|
|
25
|
+
/** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */
|
|
26
|
+
const mapErrors = async (work) => {
|
|
27
|
+
try {
|
|
28
|
+
return await work();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw toPluginError("page-edit", error);
|
|
31
|
+
}
|
|
24
32
|
};
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
33
|
+
const registry = () => ctx.document()?.pages ?? [];
|
|
34
|
+
const noPages = () => new PluginError("invalid-input", "page-edit", "no pages given");
|
|
35
|
+
/**
|
|
36
|
+
* A verb's pages as refs, checked when it is called: an index names the
|
|
37
|
+
* page at that position now, which is the page the user clicked.
|
|
38
|
+
*/
|
|
39
|
+
const refsOf = (pages) => {
|
|
40
|
+
if (pages.length === 0) throw noPages();
|
|
41
|
+
return pages.map((page) => ctx.pageOf(page).ref);
|
|
28
42
|
};
|
|
29
43
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* a concurrent reorder. Returns the anchor page too, so `addBlank` can
|
|
33
|
-
* default its size without a second lookup.
|
|
44
|
+
* A placement as the engine's position, its page fixed when the verb is
|
|
45
|
+
* called, like the verb's own pages. Default `'end'`.
|
|
34
46
|
*/
|
|
35
|
-
const
|
|
36
|
-
if (!placement) return
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
if ("index" in placement) return {
|
|
41
|
-
destIndex: placement.index,
|
|
42
|
-
anchor: void 0
|
|
43
|
-
};
|
|
44
|
-
const pon = "after" in placement ? placement.after : placement.before;
|
|
45
|
-
const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);
|
|
46
|
-
if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);
|
|
47
|
-
return {
|
|
48
|
-
destIndex: "after" in placement ? anchor.index + 1 : anchor.index,
|
|
49
|
-
anchor
|
|
50
|
-
};
|
|
47
|
+
const positionOf = (placement) => {
|
|
48
|
+
if (!placement || placement === "start" || placement === "end") return placement ?? "end";
|
|
49
|
+
if ("after" in placement) return { after: ctx.pageOf(placement.after).ref };
|
|
50
|
+
return { before: ctx.pageOf(placement.before).ref };
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Default blank-page size: the
|
|
54
|
-
*
|
|
55
|
-
* with an empty registry, which an open document never has — US Letter.
|
|
53
|
+
* Default blank-page size: the page the placement names, else the page the
|
|
54
|
+
* new ones go next to (the first or the last), else Letter.
|
|
56
55
|
*/
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
-
if (destIndex === void 0) return pages[pages.length - 1].size;
|
|
64
|
-
return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;
|
|
56
|
+
const sizeAt = (position) => {
|
|
57
|
+
if (typeof position === "object") return ctx.pageOf("after" in position ? position.after : position.before).size;
|
|
58
|
+
const pages = registry();
|
|
59
|
+
if (pages.length === 0) return LETTER_SIZE;
|
|
60
|
+
return (position === "start" ? pages[0] : pages[pages.length - 1]).size;
|
|
65
61
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
/** Insert a PDF's pages; resolves the new pages. */
|
|
63
|
+
const insertBytes = async (bytes, position, signal) => {
|
|
64
|
+
return { pages: (await ctx.cancellable(signal, ctx.doc.pages.insert(bytes, position))).insertedPages };
|
|
65
|
+
};
|
|
66
|
+
return { api: {
|
|
67
|
+
canEdit: () => ctx.allows(ASSEMBLE),
|
|
68
|
+
canExtract: () => ctx.allows(DOWNLOAD),
|
|
69
|
+
rotateBy: async (pages, delta, options) => {
|
|
70
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.rotateBy");
|
|
71
|
+
const refs = refsOf(pages);
|
|
72
|
+
const signal = options?.signal;
|
|
73
|
+
await enqueue(async () => {
|
|
74
|
+
const groups = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const ref of refs) {
|
|
76
|
+
const rotation = ((ctx.pageOf(ref).rotation + delta) % 360 + 360) % 360;
|
|
77
|
+
groups.set(rotation, [...groups.get(rotation) ?? [], ref]);
|
|
78
|
+
}
|
|
79
|
+
for (const [rotation, group] of groups) await ctx.cancellable(signal, ctx.doc.pages.rotate(group, rotation));
|
|
80
|
+
}, options);
|
|
81
|
+
},
|
|
82
|
+
setRotation: async (pages, rotation, options) => {
|
|
83
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.setRotation");
|
|
84
|
+
const refs = refsOf(pages);
|
|
85
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.rotate(refs, rotation)), options);
|
|
69
86
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
reorder: async (pages, placement, options) => {
|
|
88
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.reorder");
|
|
89
|
+
const refs = refsOf(pages);
|
|
90
|
+
const position = positionOf(placement);
|
|
91
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.reorder(refs, position)), options);
|
|
73
92
|
},
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
delete: async (pages, options) => {
|
|
94
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.delete");
|
|
95
|
+
const refs = refsOf(pages);
|
|
96
|
+
const deleting = new Set(refs.map((ref) => ref.objectNumber));
|
|
97
|
+
if (registry().every((page) => deleting.has(page.ref.objectNumber))) throw new PluginError("invalid-input", "page-edit", "a document keeps at least one page");
|
|
98
|
+
await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.delete(refs)), options);
|
|
76
99
|
},
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
insertBlank: async (options = {}) => {
|
|
101
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertBlank");
|
|
102
|
+
const position = positionOf(options.placement);
|
|
103
|
+
return enqueue(async () => {
|
|
104
|
+
const size = options.size ?? sizeAt(position);
|
|
105
|
+
return { pages: (await ctx.cancellable(options.signal, ctx.doc.pages.insertBlank({
|
|
106
|
+
size,
|
|
107
|
+
count: options.count
|
|
108
|
+
}, position))).insertedPages };
|
|
109
|
+
}, options);
|
|
79
110
|
},
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
insertFromBytes: async (bytes, options = {}) => {
|
|
112
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertFromBytes");
|
|
113
|
+
const position = positionOf(options.placement);
|
|
114
|
+
const { pageIndexes, signal } = options;
|
|
115
|
+
if (pageIndexes?.length === 0) throw noPages();
|
|
116
|
+
return enqueue(async () => {
|
|
117
|
+
if (!pageIndexes) return insertBytes(bytes, position, signal);
|
|
118
|
+
const source = await mapErrors(() => ctx.engine.open({
|
|
119
|
+
kind: "bytes",
|
|
120
|
+
id: `page-edit-source-${Date.now().toString(36)}`,
|
|
121
|
+
bytes: new Uint8Array(bytes)
|
|
122
|
+
}, { scope: ["*"] }));
|
|
123
|
+
try {
|
|
124
|
+
const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));
|
|
125
|
+
const refs = pageIndexes.map((index) => {
|
|
126
|
+
const page = layout.pages[index];
|
|
127
|
+
if (!page) throw new PluginError("not-found", "page-edit", `the PDF has no page ${index}`);
|
|
128
|
+
return page.ref;
|
|
129
|
+
});
|
|
130
|
+
const subset = await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));
|
|
131
|
+
return await insertBytes(subset, position, signal);
|
|
132
|
+
} finally {
|
|
133
|
+
await source.close();
|
|
134
|
+
}
|
|
135
|
+
}, options);
|
|
82
136
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
insertFromDocument: async (documentId, pages, options = {}) => {
|
|
138
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.insertFromDocument");
|
|
139
|
+
if (pages.length === 0) throw noPages();
|
|
140
|
+
const other = ctx.documentHandle(documentId);
|
|
141
|
+
if (!other) throw new PluginError("not-found", "page-edit", `document '${documentId}' is not open`);
|
|
142
|
+
const otherPages = ctx.get(DocumentsToken).listPages(documentId);
|
|
143
|
+
const refs = pages.map((page) => {
|
|
144
|
+
const found = typeof page === "number" ? otherPages[page] : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);
|
|
145
|
+
if (!found) throw new PluginError("not-found", "page-edit", `document '${documentId}' has no such page`);
|
|
146
|
+
return found.ref;
|
|
147
|
+
});
|
|
148
|
+
const position = positionOf(options.placement);
|
|
149
|
+
return enqueue(async () => {
|
|
150
|
+
const bytes = await mapErrors(() => ctx.cancellable(options.signal, other.pages.extract(refs)));
|
|
151
|
+
return insertBytes(bytes, position, options.signal);
|
|
152
|
+
}, options);
|
|
91
153
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
154
|
+
duplicate: async (pages, options = {}) => {
|
|
155
|
+
ctx.assertAllowed(ASSEMBLE, "pageEdit.duplicate");
|
|
156
|
+
ctx.assertAllowed(DOWNLOAD, "pageEdit.duplicate");
|
|
157
|
+
const refs = refsOf(pages);
|
|
158
|
+
const position = options.placement ? positionOf(options.placement) : { after: refs[refs.length - 1] };
|
|
159
|
+
return enqueue(async () => {
|
|
160
|
+
const bytes = await ctx.cancellable(options.signal, ctx.doc.pages.extract(refs));
|
|
161
|
+
return insertBytes(bytes, position, options.signal);
|
|
162
|
+
}, options);
|
|
163
|
+
},
|
|
164
|
+
extract: async (pages, options) => {
|
|
165
|
+
ctx.assertAllowed(DOWNLOAD, "pageEdit.extract");
|
|
166
|
+
const refs = refsOf(pages);
|
|
167
|
+
return enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.extract(refs)), options);
|
|
96
168
|
}
|
|
97
|
-
};
|
|
169
|
+
} };
|
|
98
170
|
}
|
|
99
171
|
//#endregion
|
|
100
172
|
//#region src/page-edit.plugin.ts
|
|
101
173
|
/**
|
|
102
|
-
* Document-scoped
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* home for client-side edit state (pending/optimistic/undo) when it lands —
|
|
106
|
-
* adapters keep their thin hook over `PageEditToken` unchanged.
|
|
174
|
+
* Document-scoped and stateless: the engine handle's page service as page
|
|
175
|
+
* edits that take refs or indexes. Turning a page relative to its own
|
|
176
|
+
* rotation lives in the controller, so no app re-derives it.
|
|
107
177
|
*/
|
|
108
178
|
const pageEditPlugin = () => definePlugin({
|
|
109
179
|
id: "page-edit",
|
|
110
180
|
scope: "document",
|
|
111
181
|
token: PageEditToken,
|
|
112
|
-
|
|
182
|
+
create: createPageEditController
|
|
113
183
|
});
|
|
114
184
|
//#endregion
|
|
115
185
|
export { PageEditToken, pageEditPlugin };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/page-edit.plugin.ts"],"sourcesContent":["import type {\n PluginContext,\n PageObjectNumber,\n PageRotation,\n DocCapability,\n PdfSize,\n} from '@embedpdf/core';\nimport type { PageEditCapability, PagePlacement } from './types';\n\n/**\n * Authorization for structural page edits. Maps to PDF bit 11 (ASSEMBLE =\n * insert/rotate/delete pages). The engine ALSO enforces this — every\n * page-structure verb throws `PermissionDenied` without it, identically on\n * local and cloud — so `canEdit()` is the UI mirror of a real guard, not the\n * guard itself. Typed as `DocCapability` so a typo is a compile error.\n */\nconst ASSEMBLE_CAPABILITY: DocCapability = 'doc.pages.assemble';\n\n/**\n * Forwards structural edits to the document's engine handle, addressed by PON.\n * The handle (`ctx.doc`) and the page registry (`ctx.document()`, kept in sync\n * by the kernel's event→registry bridge) are both already on the plugin\n * context — this capability is the thin layer that turns the relative rotate\n * gesture into the engine's absolute wire.\n */\nexport function createPageEditCapability(ctx: PluginContext<unknown>): PageEditCapability {\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[page-edit] no document bound');\n return doc;\n };\n\n /** Current absolute rotation of a page from the registry; 0 if unknown. */\n const rotationOf = (pon: PageObjectNumber): PageRotation => {\n const page = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n return page?.rotation ?? 0;\n };\n\n /**\n * Resolve a placement to the engine's index wire, from the registry at\n * call time — the PON anchors are why \"add after this thumbnail\" survives\n * a concurrent reorder. Returns the anchor page too, so `addBlank` can\n * default its size without a second lookup.\n */\n const resolvePlacement = (placement?: PagePlacement) => {\n if (!placement) return { destIndex: undefined, anchor: undefined };\n if ('index' in placement) return { destIndex: placement.index, anchor: undefined };\n const pon = 'after' in placement ? placement.after : placement.before;\n const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);\n return { destIndex: 'after' in placement ? anchor.index + 1 : anchor.index, anchor };\n };\n\n /**\n * Default blank-page size: the insertion point's predecessor (the page\n * the new one will follow), else the first page, else — only reachable\n * with an empty registry, which an open document never has — US Letter.\n */\n const neighbourSize = (destIndex: number | undefined): PdfSize => {\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return { width: 612, height: 792 };\n if (destIndex === undefined) return pages[pages.length - 1].size;\n return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;\n };\n\n return {\n canEdit() {\n // Wildcard-aware predicate (mirrors the engine's own enforcement),\n // identical on both engines. NOT `effectiveScope.includes(...)` — that\n // enumeration drops the `*` admin grant, so it would hide the UI on a\n // default admin open even though the engine allows the edit.\n return ctx.doc?.security.allows(ASSEMBLE_CAPABILITY) ?? false;\n },\n\n rotateBy(pon, delta) {\n // Wrap to [0, 360) — the double-mod keeps -90 from current 0 landing on 270.\n const next = ((((rotationOf(pon) + delta) % 360) + 360) % 360) as PageRotation;\n return requireDoc().pages.rotate([pon], next);\n },\n\n setRotation(pons, rotation) {\n return requireDoc().pages.rotate(pons, rotation);\n },\n\n move(pons, destIndex) {\n return requireDoc().pages.move(pons, destIndex);\n },\n\n delete(pons) {\n return requireDoc().pages.delete(pons);\n },\n\n addBlank(opts = {}) {\n const doc = requireDoc();\n const { destIndex, anchor } = resolvePlacement(opts.placement);\n // PON placements match the anchor the user is looking at; everything\n // else matches the neighbour the new page will follow.\n const size = opts.size ?? anchor?.size ?? neighbourSize(destIndex);\n return doc.pages.insertBlank({ size, count: opts.count }, destIndex);\n },\n\n insert(bytes, opts = {}) {\n const doc = requireDoc();\n const { destIndex } = resolvePlacement(opts.placement);\n return doc.pages.insert(bytes, destIndex);\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createPageEditCapability } from './capability';\nimport { PageEditToken } from './types';\n\n/**\n * Document-scoped, stateless: turns the engine handle's page service into a\n * PON-addressed edit capability. The relative→absolute rotation lives in the\n * capability so the four framework adapters never re-derive it, and this is the\n * home for client-side edit state (pending/optimistic/undo) when it lands —\n * adapters keep their thin hook over `PageEditToken` unchanged.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n capability: createPageEditCapability,\n });\n"],"mappings":";;;;;;;;;;AAgBA,MAAM,sBAAqC;;;;;;;;AAS3C,SAAgB,yBAAyB,KAAiD;CACxF,MAAM,mBAAmB;EACvB,MAAM,MAAM,IAAI;EAChB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,+BAA+B;EACzD,OAAO;CACT;;CAGA,MAAM,cAAc,QAAwC;EAE1D,QADa,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG,EAAA,EAC5D,YAAY;CAC3B;;;;;;;CAQA,MAAM,oBAAoB,cAA8B;EACtD,IAAI,CAAC,WAAW,OAAO;GAAE,WAAW,KAAA;GAAW,QAAQ,KAAA;EAAU;EACjE,IAAI,WAAW,WAAW,OAAO;GAAE,WAAW,UAAU;GAAO,QAAQ,KAAA;EAAU;EACjF,MAAM,MAAM,WAAW,YAAY,UAAU,QAAQ,UAAU;EAC/D,MAAM,SAAS,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG;EAC3E,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yCAAyC,KAAK;EAC3E,OAAO;GAAE,WAAW,WAAW,YAAY,OAAO,QAAQ,IAAI,OAAO;GAAO;EAAO;CACrF;;;;;;CAOA,MAAM,iBAAiB,cAA2C;EAChE,MAAM,QAAQ,IAAI,SAAS,CAAC,EAAE,SAAS,CAAC;EACxC,IAAI,MAAM,WAAW,GAAG,OAAO;GAAE,OAAO;GAAK,QAAQ;EAAI;EACzD,IAAI,cAAc,KAAA,GAAW,OAAO,MAAM,MAAM,SAAS,EAAE,CAAC;EAC5D,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC;CACvE;CAEA,OAAO;EACL,UAAU;GAKR,OAAO,IAAI,KAAK,SAAS,OAAO,mBAAmB,KAAK;EAC1D;EAEA,SAAS,KAAK,OAAO;GAEnB,MAAM,SAAW,WAAW,GAAG,IAAI,SAAS,MAAO,OAAO;GAC1D,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,GAAG,IAAI;EAC9C;EAEA,YAAY,MAAM,UAAU;GAC1B,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,MAAM,QAAQ;EACjD;EAEA,KAAK,MAAM,WAAW;GACpB,OAAO,WAAW,CAAC,CAAC,MAAM,KAAK,MAAM,SAAS;EAChD;EAEA,OAAO,MAAM;GACX,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,IAAI;EACvC;EAEA,SAAS,OAAO,CAAC,GAAG;GAClB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,WAAW,WAAW,iBAAiB,KAAK,SAAS;GAG7D,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,cAAc,SAAS;GACjE,OAAO,IAAI,MAAM,YAAY;IAAE;IAAM,OAAO,KAAK;GAAM,GAAG,SAAS;EACrE;EAEA,OAAO,OAAO,OAAO,CAAC,GAAG;GACvB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,cAAc,iBAAiB,KAAK,SAAS;GACrD,OAAO,IAAI,MAAM,OAAO,OAAO,SAAS;EAC1C;CACF;AACF;;;;;;;;;;AChGA,MAAa,uBACX,aAAa;CACX,IAAI;CACJ,OAAO;CACP,OAAO;CACP,YAAY;AACd,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/controller.ts","../src/page-edit.plugin.ts"],"sourcesContent":["/**\n * The page-edit controller. Stateless: it turns the engine handle's page\n * service into page edits that take refs or indexes. Turning a page relative\n * to its own rotation, and fixing a placement's page, live here once instead\n * of in every app's click handler.\n *\n * A verb refuses before anything starts (permission, pages, placement), then\n * queues its edit: edits run one at a time, in the order they were called.\n */\nimport { DocumentsToken, PluginError, toPluginError } from '@embedpdf/core';\nimport type {\n DocCapability,\n PageInfo,\n PagePosition,\n PageRef,\n PdfRotation,\n PdfSize,\n PluginContext,\n} from '@embedpdf/core';\n\nimport type { PageEditCapability, PageEditInsertResult, PagePlacement } from './contract';\n\n/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */\nconst ASSEMBLE: DocCapability = 'doc.pages.assemble';\n/** Copying pages out of a document is a partial download. The engine enforces it too. */\nconst DOWNLOAD: DocCapability = 'doc.download';\n\n/** The size of a blank page when the document has no page to match: Letter, in points. */\nconst LETTER_SIZE: PdfSize = { width: 612, height: 792 };\n\nexport function createPageEditController(ctx: PluginContext<void>) {\n const enqueue = ctx.serialQueue('edits');\n // A download waits for the page edits on their way.\n ctx.onSettle(() => enqueue.idle());\n\n /** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */\n const mapErrors = async <T>(work: () => Promise<T>): Promise<T> => {\n try {\n return await work();\n } catch (error) {\n throw toPluginError('page-edit', error);\n }\n };\n const registry = (): readonly PageInfo[] => ctx.document()?.pages ?? [];\n const noPages = () => new PluginError('invalid-input', 'page-edit', 'no pages given');\n\n /**\n * A verb's pages as refs, checked when it is called: an index names the\n * page at that position now, which is the page the user clicked.\n */\n const refsOf = (pages: readonly (PageRef | number)[]): PageRef[] => {\n if (pages.length === 0) throw noPages();\n return pages.map((page) => ctx.pageOf(page).ref);\n };\n\n /**\n * A placement as the engine's position, its page fixed when the verb is\n * called, like the verb's own pages. Default `'end'`.\n */\n const positionOf = (placement: PagePlacement | undefined): PagePosition => {\n if (!placement || placement === 'start' || placement === 'end') return placement ?? 'end';\n if ('after' in placement) return { after: ctx.pageOf(placement.after).ref };\n return { before: ctx.pageOf(placement.before).ref };\n };\n /**\n * Default blank-page size: the page the placement names, else the page the\n * new ones go next to (the first or the last), else Letter.\n */\n const sizeAt = (position: PagePosition): PdfSize => {\n if (typeof position === 'object') {\n return ctx.pageOf('after' in position ? position.after : position.before).size;\n }\n const pages = registry();\n if (pages.length === 0) return LETTER_SIZE;\n return (position === 'start' ? pages[0] : pages[pages.length - 1]).size;\n };\n\n /** Insert a PDF's pages; resolves the new pages. */\n const insertBytes = async (\n bytes: Uint8Array | ArrayBuffer,\n position: PagePosition,\n signal: AbortSignal | undefined,\n ): Promise<PageEditInsertResult> => {\n const result = await ctx.cancellable(signal, ctx.doc.pages.insert(bytes, position));\n return { pages: result.insertedPages };\n };\n\n const api: PageEditCapability = {\n canEdit: () => ctx.allows(ASSEMBLE),\n canExtract: () => ctx.allows(DOWNLOAD),\n\n rotateBy: async (pages, delta, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.rotateBy');\n const refs = refsOf(pages);\n const signal = options?.signal;\n await enqueue(async () => {\n // The engine sets one rotation per call, so pages are grouped by where\n // they end up. Each page's rotation is read when the edit runs, after\n // the edits queued before it; the double modulo keeps -90 from 0 at 270.\n const groups = new Map<PdfRotation, PageRef[]>();\n for (const ref of refs) {\n const turned = (ctx.pageOf(ref).rotation + delta) % 360;\n const rotation = ((turned + 360) % 360) as PdfRotation;\n groups.set(rotation, [...(groups.get(rotation) ?? []), ref]);\n }\n for (const [rotation, group] of groups) {\n await ctx.cancellable(signal, ctx.doc.pages.rotate(group, rotation));\n }\n }, options);\n },\n\n setRotation: async (pages, rotation, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.setRotation');\n const refs = refsOf(pages);\n await enqueue(\n () => ctx.cancellable(options?.signal, ctx.doc.pages.rotate(refs, rotation)),\n options,\n );\n },\n\n reorder: async (pages, placement, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.reorder');\n const refs = refsOf(pages);\n const position = positionOf(placement);\n await enqueue(\n () => ctx.cancellable(options?.signal, ctx.doc.pages.reorder(refs, position)),\n options,\n );\n },\n\n delete: async (pages, options) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.delete');\n const refs = refsOf(pages);\n const deleting = new Set(refs.map((ref) => ref.objectNumber));\n if (registry().every((page) => deleting.has(page.ref.objectNumber))) {\n throw new PluginError('invalid-input', 'page-edit', 'a document keeps at least one page');\n }\n await enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.delete(refs)), options);\n },\n\n insertBlank: async (options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertBlank');\n const position = positionOf(options.placement);\n return enqueue(async () => {\n const size = options.size ?? sizeAt(position);\n const result = await ctx.cancellable(\n options.signal,\n ctx.doc.pages.insertBlank({ size, count: options.count }, position),\n );\n return { pages: result.insertedPages };\n }, options);\n },\n\n insertFromBytes: async (bytes, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertFromBytes');\n const position = positionOf(options.placement);\n const { pageIndexes, signal } = options;\n if (pageIndexes?.length === 0) throw noPages();\n return enqueue(async () => {\n if (!pageIndexes) return insertBytes(bytes, position, signal);\n // Some of the pages: open the bytes beside the document, extract them, insert those.\n const source = await mapErrors(() =>\n ctx.engine.open(\n {\n kind: 'bytes',\n id: `page-edit-source-${Date.now().toString(36)}`,\n bytes: new Uint8Array(bytes),\n },\n { scope: ['*'] },\n ),\n );\n try {\n const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));\n const refs = pageIndexes.map((index) => {\n const page = layout.pages[index];\n if (!page) {\n throw new PluginError('not-found', 'page-edit', `the PDF has no page ${index}`);\n }\n return page.ref;\n });\n const subset = await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));\n return await insertBytes(subset, position, signal);\n } finally {\n await source.close();\n }\n }, options);\n },\n\n insertFromDocument: async (documentId, pages, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.insertFromDocument');\n if (pages.length === 0) throw noPages();\n const other = ctx.documentHandle(documentId);\n if (!other) {\n throw new PluginError('not-found', 'page-edit', `document '${documentId}' is not open`);\n }\n // The pages are the other document's, so its page list resolves them.\n const otherPages = ctx.get(DocumentsToken).listPages(documentId);\n const refs = pages.map((page) => {\n const found =\n typeof page === 'number'\n ? otherPages[page]\n : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);\n if (!found) {\n throw new PluginError(\n 'not-found',\n 'page-edit',\n `document '${documentId}' has no such page`,\n );\n }\n return found.ref;\n });\n const position = positionOf(options.placement);\n return enqueue(async () => {\n // The other document's session decides whether its pages may be copied\n // out: the engine refuses `doc.download` there with the permission named.\n const bytes = await mapErrors(() =>\n ctx.cancellable(options.signal, other.pages.extract(refs)),\n );\n return insertBytes(bytes, position, options.signal);\n }, options);\n },\n\n duplicate: async (pages, options = {}) => {\n ctx.assertAllowed(ASSEMBLE, 'pageEdit.duplicate');\n ctx.assertAllowed(DOWNLOAD, 'pageEdit.duplicate');\n const refs = refsOf(pages);\n const position = options.placement\n ? positionOf(options.placement)\n : { after: refs[refs.length - 1]! };\n return enqueue(async () => {\n const bytes = await ctx.cancellable(options.signal, ctx.doc.pages.extract(refs));\n return insertBytes(bytes, position, options.signal);\n }, options);\n },\n\n extract: async (pages, options) => {\n ctx.assertAllowed(DOWNLOAD, 'pageEdit.extract');\n const refs = refsOf(pages);\n return enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.extract(refs)), options);\n },\n };\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { PageEditToken } from './contract';\nimport { createPageEditController } from './controller';\n\n/**\n * Document-scoped and stateless: the engine handle's page service as page\n * edits that take refs or indexes. Turning a page relative to its own\n * rotation lives in the controller, so no app re-derives it.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n create: createPageEditController,\n });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@embedpdf/plugin-page-edit",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.15",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Document-scoped page-edit capability:
|
|
5
|
+
"description": "Document-scoped page-edit capability: ref-addressed rotate/move/delete/insert over the engine DocumentHandle. Owns relative→absolute rotation and placement resolution so framework adapters never re-derive them.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"module": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@embedpdf/core": "3.0.0-next.
|
|
25
|
+
"@embedpdf/core": "3.0.0-next.15"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"typescript": "^5.0.0",
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { AbortablePromise, PageDeleteResult, PageInsertResult, PageMoveResult, PageObjectNumber, PageRotateResult, PageRotation, PdfSize } from "@embedpdf/core";
|
|
2
|
-
//#region src/types.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Where added pages land. The PON anchors are the thumbnail gestures ("+
|
|
5
|
-
* after this page") and stay correct across a concurrent reorder between
|
|
6
|
-
* click and call — a raw index does not; `index` remains for absolute
|
|
7
|
-
* positions ("at the start"). Omitted → append. Resolved to the engine's
|
|
8
|
-
* index wire by the capability at call time, from the registry.
|
|
9
|
-
*/
|
|
10
|
-
type PagePlacement = {
|
|
11
|
-
after: PageObjectNumber;
|
|
12
|
-
} | {
|
|
13
|
-
before: PageObjectNumber;
|
|
14
|
-
} | {
|
|
15
|
-
index: number;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Structural page edits, addressed by durable PON (never display index — an
|
|
19
|
-
* index shifts the moment a sibling is moved or deleted). Mirrors the engine's
|
|
20
|
-
* `DocumentPagesService` 1:1, with one addition the engine can't have: the
|
|
21
|
-
* relative `rotateBy` gesture. The engine wire is always ABSOLUTE; turning a
|
|
22
|
-
* "+90° on this thumbnail" click into an absolute value needs the page's
|
|
23
|
-
* current rotation, so that read+arithmetic lives HERE — once — instead of
|
|
24
|
-
* being re-derived in every framework adapter's click handler.
|
|
25
|
-
*
|
|
26
|
-
* Document-scoped: rotation is shared document metadata, so editing through the
|
|
27
|
-
* sidebar lens and seeing it in the main lens is automatic (both read the same
|
|
28
|
-
* registry, which the kernel keeps in sync from the engine's event stream).
|
|
29
|
-
*/
|
|
30
|
-
interface PageEditCapability {
|
|
31
|
-
/**
|
|
32
|
-
* Whether this caller is authorized to perform structural page edits —
|
|
33
|
-
* `effectiveScope` includes `doc.pages.assemble` (PDF bit 11). UIs gate
|
|
34
|
-
* ALL their edit affordances on this, the add verbs included (rotate,
|
|
35
|
-
* move, delete, addBlank, and insert share the one capability); the
|
|
36
|
-
* engine independently enforces the same capability and throws
|
|
37
|
-
* `PermissionDenied` if a call slips through.
|
|
38
|
-
*/
|
|
39
|
-
canEdit(): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* Rotate a SINGLE page by a relative quarter-turn — the per-thumbnail button
|
|
42
|
-
* gesture. Reads the page's current rotation from the registry and forwards
|
|
43
|
-
* the resulting absolute rotation to the engine.
|
|
44
|
-
*/
|
|
45
|
-
rotateBy(pon: PageObjectNumber, delta: 90 | -90): AbortablePromise<PageRotateResult>;
|
|
46
|
-
/**
|
|
47
|
-
* Set the ABSOLUTE rotation of one or more pages to a single shared value —
|
|
48
|
-
* the multi-select gesture. Maps 1:1 to the engine.
|
|
49
|
-
*/
|
|
50
|
-
setRotation(pons: PageObjectNumber[], rotation: PageRotation): AbortablePromise<PageRotateResult>;
|
|
51
|
-
/** Reorder pages (by PON) as a contiguous block starting at `destIndex`. */
|
|
52
|
-
move(pons: PageObjectNumber[], destIndex: number): AbortablePromise<PageMoveResult>;
|
|
53
|
-
/** Delete pages by PON. The engine rejects deleting every page. */
|
|
54
|
-
delete(pons: PageObjectNumber[]): AbortablePromise<PageDeleteResult>;
|
|
55
|
-
/**
|
|
56
|
-
* Create blank pages — gate the affordance on `canEdit()`, like every
|
|
57
|
-
* other structure verb (the add verbs share the `doc.pages.assemble`
|
|
58
|
-
* capability and are mandatory on the engine contract). `size` defaults
|
|
59
|
-
* to the page the new pages sit beside — the anchor for PON placements,
|
|
60
|
-
* else the insertion point's predecessor, else the last page — so "add
|
|
61
|
-
* page" matches the document without an A4-vs-Letter guess. That default
|
|
62
|
-
* is a registry read, which is why it lives HERE and the engine wire
|
|
63
|
-
* stays explicit (the `rotateBy` law). `count` defaults to 1.
|
|
64
|
-
*/
|
|
65
|
-
addBlank(opts?: {
|
|
66
|
-
size?: PdfSize;
|
|
67
|
-
count?: number;
|
|
68
|
-
placement?: PagePlacement;
|
|
69
|
-
}): AbortablePromise<PageInsertResult>;
|
|
70
|
-
/**
|
|
71
|
-
* Copy every page of a standalone PDF (`bytes`) in at the placement —
|
|
72
|
-
* the merge/import gesture. Forwards to `pages.insert`; the inserted
|
|
73
|
-
* copies get fresh PONs, returned in insertion order.
|
|
74
|
-
*/
|
|
75
|
-
insert(bytes: Uint8Array | ArrayBuffer, opts?: {
|
|
76
|
-
placement?: PagePlacement;
|
|
77
|
-
}): AbortablePromise<PageInsertResult>;
|
|
78
|
-
}
|
|
79
|
-
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
80
|
-
//#endregion
|
|
81
|
-
export { PageEditToken as n, PageEditCapability as t };
|
|
82
|
-
//# sourceMappingURL=contract-BnXi11KD.d.cts.map
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { AbortablePromise, PageDeleteResult, PageInsertResult, PageMoveResult, PageObjectNumber, PageRotateResult, PageRotation, PdfSize } from "@embedpdf/core";
|
|
2
|
-
//#region src/types.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Where added pages land. The PON anchors are the thumbnail gestures ("+
|
|
5
|
-
* after this page") and stay correct across a concurrent reorder between
|
|
6
|
-
* click and call — a raw index does not; `index` remains for absolute
|
|
7
|
-
* positions ("at the start"). Omitted → append. Resolved to the engine's
|
|
8
|
-
* index wire by the capability at call time, from the registry.
|
|
9
|
-
*/
|
|
10
|
-
type PagePlacement = {
|
|
11
|
-
after: PageObjectNumber;
|
|
12
|
-
} | {
|
|
13
|
-
before: PageObjectNumber;
|
|
14
|
-
} | {
|
|
15
|
-
index: number;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Structural page edits, addressed by durable PON (never display index — an
|
|
19
|
-
* index shifts the moment a sibling is moved or deleted). Mirrors the engine's
|
|
20
|
-
* `DocumentPagesService` 1:1, with one addition the engine can't have: the
|
|
21
|
-
* relative `rotateBy` gesture. The engine wire is always ABSOLUTE; turning a
|
|
22
|
-
* "+90° on this thumbnail" click into an absolute value needs the page's
|
|
23
|
-
* current rotation, so that read+arithmetic lives HERE — once — instead of
|
|
24
|
-
* being re-derived in every framework adapter's click handler.
|
|
25
|
-
*
|
|
26
|
-
* Document-scoped: rotation is shared document metadata, so editing through the
|
|
27
|
-
* sidebar lens and seeing it in the main lens is automatic (both read the same
|
|
28
|
-
* registry, which the kernel keeps in sync from the engine's event stream).
|
|
29
|
-
*/
|
|
30
|
-
interface PageEditCapability {
|
|
31
|
-
/**
|
|
32
|
-
* Whether this caller is authorized to perform structural page edits —
|
|
33
|
-
* `effectiveScope` includes `doc.pages.assemble` (PDF bit 11). UIs gate
|
|
34
|
-
* ALL their edit affordances on this, the add verbs included (rotate,
|
|
35
|
-
* move, delete, addBlank, and insert share the one capability); the
|
|
36
|
-
* engine independently enforces the same capability and throws
|
|
37
|
-
* `PermissionDenied` if a call slips through.
|
|
38
|
-
*/
|
|
39
|
-
canEdit(): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* Rotate a SINGLE page by a relative quarter-turn — the per-thumbnail button
|
|
42
|
-
* gesture. Reads the page's current rotation from the registry and forwards
|
|
43
|
-
* the resulting absolute rotation to the engine.
|
|
44
|
-
*/
|
|
45
|
-
rotateBy(pon: PageObjectNumber, delta: 90 | -90): AbortablePromise<PageRotateResult>;
|
|
46
|
-
/**
|
|
47
|
-
* Set the ABSOLUTE rotation of one or more pages to a single shared value —
|
|
48
|
-
* the multi-select gesture. Maps 1:1 to the engine.
|
|
49
|
-
*/
|
|
50
|
-
setRotation(pons: PageObjectNumber[], rotation: PageRotation): AbortablePromise<PageRotateResult>;
|
|
51
|
-
/** Reorder pages (by PON) as a contiguous block starting at `destIndex`. */
|
|
52
|
-
move(pons: PageObjectNumber[], destIndex: number): AbortablePromise<PageMoveResult>;
|
|
53
|
-
/** Delete pages by PON. The engine rejects deleting every page. */
|
|
54
|
-
delete(pons: PageObjectNumber[]): AbortablePromise<PageDeleteResult>;
|
|
55
|
-
/**
|
|
56
|
-
* Create blank pages — gate the affordance on `canEdit()`, like every
|
|
57
|
-
* other structure verb (the add verbs share the `doc.pages.assemble`
|
|
58
|
-
* capability and are mandatory on the engine contract). `size` defaults
|
|
59
|
-
* to the page the new pages sit beside — the anchor for PON placements,
|
|
60
|
-
* else the insertion point's predecessor, else the last page — so "add
|
|
61
|
-
* page" matches the document without an A4-vs-Letter guess. That default
|
|
62
|
-
* is a registry read, which is why it lives HERE and the engine wire
|
|
63
|
-
* stays explicit (the `rotateBy` law). `count` defaults to 1.
|
|
64
|
-
*/
|
|
65
|
-
addBlank(opts?: {
|
|
66
|
-
size?: PdfSize;
|
|
67
|
-
count?: number;
|
|
68
|
-
placement?: PagePlacement;
|
|
69
|
-
}): AbortablePromise<PageInsertResult>;
|
|
70
|
-
/**
|
|
71
|
-
* Copy every page of a standalone PDF (`bytes`) in at the placement —
|
|
72
|
-
* the merge/import gesture. Forwards to `pages.insert`; the inserted
|
|
73
|
-
* copies get fresh PONs, returned in insertion order.
|
|
74
|
-
*/
|
|
75
|
-
insert(bytes: Uint8Array | ArrayBuffer, opts?: {
|
|
76
|
-
placement?: PagePlacement;
|
|
77
|
-
}): AbortablePromise<PageInsertResult>;
|
|
78
|
-
}
|
|
79
|
-
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
80
|
-
//#endregion
|
|
81
|
-
export { PageEditToken as n, PageEditCapability as t };
|
|
82
|
-
//# sourceMappingURL=contract-BnXi11KD.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contract-BsNelyd8.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import {\n createCapabilityToken,\n type AbortablePromise,\n type PageObjectNumber,\n type PageRotation,\n type PageRotateResult,\n type PageMoveResult,\n type PageDeleteResult,\n type PageInsertResult,\n type PdfSize,\n} from '@embedpdf/core';\n\n/**\n * Where added pages land. The PON anchors are the thumbnail gestures (\"+\n * after this page\") and stay correct across a concurrent reorder between\n * click and call — a raw index does not; `index` remains for absolute\n * positions (\"at the start\"). Omitted → append. Resolved to the engine's\n * index wire by the capability at call time, from the registry.\n */\nexport type PagePlacement =\n | { after: PageObjectNumber }\n | { before: PageObjectNumber }\n | { index: number };\n\n/**\n * Structural page edits, addressed by durable PON (never display index — an\n * index shifts the moment a sibling is moved or deleted). Mirrors the engine's\n * `DocumentPagesService` 1:1, with one addition the engine can't have: the\n * relative `rotateBy` gesture. The engine wire is always ABSOLUTE; turning a\n * \"+90° on this thumbnail\" click into an absolute value needs the page's\n * current rotation, so that read+arithmetic lives HERE — once — instead of\n * being re-derived in every framework adapter's click handler.\n *\n * Document-scoped: rotation is shared document metadata, so editing through the\n * sidebar lens and seeing it in the main lens is automatic (both read the same\n * registry, which the kernel keeps in sync from the engine's event stream).\n */\nexport interface PageEditCapability {\n /**\n * Whether this caller is authorized to perform structural page edits —\n * `effectiveScope` includes `doc.pages.assemble` (PDF bit 11). UIs gate\n * ALL their edit affordances on this, the add verbs included (rotate,\n * move, delete, addBlank, and insert share the one capability); the\n * engine independently enforces the same capability and throws\n * `PermissionDenied` if a call slips through.\n */\n canEdit(): boolean;\n\n /**\n * Rotate a SINGLE page by a relative quarter-turn — the per-thumbnail button\n * gesture. Reads the page's current rotation from the registry and forwards\n * the resulting absolute rotation to the engine.\n */\n rotateBy(pon: PageObjectNumber, delta: 90 | -90): AbortablePromise<PageRotateResult>;\n\n /**\n * Set the ABSOLUTE rotation of one or more pages to a single shared value —\n * the multi-select gesture. Maps 1:1 to the engine.\n */\n setRotation(pons: PageObjectNumber[], rotation: PageRotation): AbortablePromise<PageRotateResult>;\n\n /** Reorder pages (by PON) as a contiguous block starting at `destIndex`. */\n move(pons: PageObjectNumber[], destIndex: number): AbortablePromise<PageMoveResult>;\n\n /** Delete pages by PON. The engine rejects deleting every page. */\n delete(pons: PageObjectNumber[]): AbortablePromise<PageDeleteResult>;\n\n /**\n * Create blank pages — gate the affordance on `canEdit()`, like every\n * other structure verb (the add verbs share the `doc.pages.assemble`\n * capability and are mandatory on the engine contract). `size` defaults\n * to the page the new pages sit beside — the anchor for PON placements,\n * else the insertion point's predecessor, else the last page — so \"add\n * page\" matches the document without an A4-vs-Letter guess. That default\n * is a registry read, which is why it lives HERE and the engine wire\n * stays explicit (the `rotateBy` law). `count` defaults to 1.\n */\n addBlank(opts?: {\n size?: PdfSize;\n count?: number;\n placement?: PagePlacement;\n }): AbortablePromise<PageInsertResult>;\n\n /**\n * Copy every page of a standalone PDF (`bytes`) in at the placement —\n * the merge/import gesture. Forwards to `pages.insert`; the inserted\n * copies get fresh PONs, returned in insertion order.\n */\n insert(\n bytes: Uint8Array | ArrayBuffer,\n opts?: { placement?: PagePlacement },\n ): AbortablePromise<PageInsertResult>;\n}\n\nexport const PageEditToken = createCapabilityToken<PageEditCapability>('page-edit');\n"],"mappings":";;AA8FA,MAAa,gBAAgB,sBAA0C,WAAW"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
//#region src/types.ts
|
|
2
|
-
const PageEditToken = (0, require("@embedpdf/core").createCapabilityToken)("page-edit");
|
|
3
|
-
//#endregion
|
|
4
|
-
Object.defineProperty(exports, "PageEditToken", {
|
|
5
|
-
enumerable: true,
|
|
6
|
-
get: function() {
|
|
7
|
-
return PageEditToken;
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
//# sourceMappingURL=contract-UeGRCn-t.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contract-UeGRCn-t.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import {\n createCapabilityToken,\n type AbortablePromise,\n type PageObjectNumber,\n type PageRotation,\n type PageRotateResult,\n type PageMoveResult,\n type PageDeleteResult,\n type PageInsertResult,\n type PdfSize,\n} from '@embedpdf/core';\n\n/**\n * Where added pages land. The PON anchors are the thumbnail gestures (\"+\n * after this page\") and stay correct across a concurrent reorder between\n * click and call — a raw index does not; `index` remains for absolute\n * positions (\"at the start\"). Omitted → append. Resolved to the engine's\n * index wire by the capability at call time, from the registry.\n */\nexport type PagePlacement =\n | { after: PageObjectNumber }\n | { before: PageObjectNumber }\n | { index: number };\n\n/**\n * Structural page edits, addressed by durable PON (never display index — an\n * index shifts the moment a sibling is moved or deleted). Mirrors the engine's\n * `DocumentPagesService` 1:1, with one addition the engine can't have: the\n * relative `rotateBy` gesture. The engine wire is always ABSOLUTE; turning a\n * \"+90° on this thumbnail\" click into an absolute value needs the page's\n * current rotation, so that read+arithmetic lives HERE — once — instead of\n * being re-derived in every framework adapter's click handler.\n *\n * Document-scoped: rotation is shared document metadata, so editing through the\n * sidebar lens and seeing it in the main lens is automatic (both read the same\n * registry, which the kernel keeps in sync from the engine's event stream).\n */\nexport interface PageEditCapability {\n /**\n * Whether this caller is authorized to perform structural page edits —\n * `effectiveScope` includes `doc.pages.assemble` (PDF bit 11). UIs gate\n * ALL their edit affordances on this, the add verbs included (rotate,\n * move, delete, addBlank, and insert share the one capability); the\n * engine independently enforces the same capability and throws\n * `PermissionDenied` if a call slips through.\n */\n canEdit(): boolean;\n\n /**\n * Rotate a SINGLE page by a relative quarter-turn — the per-thumbnail button\n * gesture. Reads the page's current rotation from the registry and forwards\n * the resulting absolute rotation to the engine.\n */\n rotateBy(pon: PageObjectNumber, delta: 90 | -90): AbortablePromise<PageRotateResult>;\n\n /**\n * Set the ABSOLUTE rotation of one or more pages to a single shared value —\n * the multi-select gesture. Maps 1:1 to the engine.\n */\n setRotation(pons: PageObjectNumber[], rotation: PageRotation): AbortablePromise<PageRotateResult>;\n\n /** Reorder pages (by PON) as a contiguous block starting at `destIndex`. */\n move(pons: PageObjectNumber[], destIndex: number): AbortablePromise<PageMoveResult>;\n\n /** Delete pages by PON. The engine rejects deleting every page. */\n delete(pons: PageObjectNumber[]): AbortablePromise<PageDeleteResult>;\n\n /**\n * Create blank pages — gate the affordance on `canEdit()`, like every\n * other structure verb (the add verbs share the `doc.pages.assemble`\n * capability and are mandatory on the engine contract). `size` defaults\n * to the page the new pages sit beside — the anchor for PON placements,\n * else the insertion point's predecessor, else the last page — so \"add\n * page\" matches the document without an A4-vs-Letter guess. That default\n * is a registry read, which is why it lives HERE and the engine wire\n * stays explicit (the `rotateBy` law). `count` defaults to 1.\n */\n addBlank(opts?: {\n size?: PdfSize;\n count?: number;\n placement?: PagePlacement;\n }): AbortablePromise<PageInsertResult>;\n\n /**\n * Copy every page of a standalone PDF (`bytes`) in at the placement —\n * the merge/import gesture. Forwards to `pages.insert`; the inserted\n * copies get fresh PONs, returned in insertion order.\n */\n insert(\n bytes: Uint8Array | ArrayBuffer,\n opts?: { placement?: PagePlacement },\n ): AbortablePromise<PageInsertResult>;\n}\n\nexport const PageEditToken = createCapabilityToken<PageEditCapability>('page-edit');\n"],"mappings":";AA8FA,MAAa,iBAAA,2BAAA,CAAA,CAAA,sBAAA,CAA0D,WAAW"}
|