@embedpdf/plugin-page-edit 3.0.0-next.15 → 3.0.0-next.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{contract-dl4LfrRX.d.ts → contract-IN8rk5LG.d.cts} +41 -30
- package/dist/{contract-dl4LfrRX.d.cts → contract-IN8rk5LG.d.ts} +41 -30
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/index.cjs +218 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -6
- package/dist/index.d.ts +20 -6
- package/dist/index.js +219 -90
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { OperationOptions, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
1
|
+
import { OperationOptions, PagePosition, PagePosition as PagePosition$1, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
2
2
|
//#region src/token.d.ts
|
|
3
3
|
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
4
4
|
//#endregion
|
|
5
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
6
|
/** What an insert resolves: the new pages, in the order they were inserted. */
|
|
18
7
|
interface PageEditInsertResult {
|
|
19
8
|
readonly pages: readonly PageRef[];
|
|
@@ -21,8 +10,8 @@ interface PageEditInsertResult {
|
|
|
21
10
|
/** Options of `insertBlank()`. */
|
|
22
11
|
interface PageInsertBlankOptions extends OperationOptions {
|
|
23
12
|
/** Where the new pages go. Default `'end'`. */
|
|
24
|
-
readonly
|
|
25
|
-
/** The page size, in points. Default: the size of the page the new pages
|
|
13
|
+
readonly position?: PagePosition;
|
|
14
|
+
/** The page size, in points. Default: the size of the page the new pages go next to. */
|
|
26
15
|
readonly size?: PdfSize;
|
|
27
16
|
/** How many blank pages. Default 1. */
|
|
28
17
|
readonly count?: number;
|
|
@@ -32,18 +21,26 @@ interface PageInsertFromBytesOptions extends OperationOptions {
|
|
|
32
21
|
/** Only the source PDF's pages at these indexes, in this order. Default: every page. */
|
|
33
22
|
readonly pageIndexes?: readonly number[];
|
|
34
23
|
/** Where the new pages go. Default `'end'`. */
|
|
35
|
-
readonly
|
|
24
|
+
readonly position?: PagePosition;
|
|
36
25
|
}
|
|
37
26
|
/** Options of the verbs that copy pages in: `insertFromDocument()` and `duplicate()`. */
|
|
38
|
-
interface
|
|
39
|
-
readonly
|
|
27
|
+
interface PagePositionOptions extends OperationOptions {
|
|
28
|
+
readonly position?: PagePosition;
|
|
40
29
|
}
|
|
41
30
|
/**
|
|
42
|
-
* Every edit
|
|
31
|
+
* Every edit is one change: one step of the history, so undo brings the
|
|
32
|
+
* pages back as they were, a deleted page with its annotations under its old
|
|
33
|
+
* ref. It shows once the engine answers. Edits apply in the order they were
|
|
34
|
+
* called, except that the verbs that copy pages first (`duplicate()`,
|
|
35
|
+
* `insertFromDocument()`, `insertFromBytes()` with `pageIndexes`) take their
|
|
36
|
+
* place once the copy is made.
|
|
37
|
+
*
|
|
43
38
|
* 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
|
|
46
|
-
*
|
|
39
|
+
* session may not do it, `not-found` for a page the document doesn't have and
|
|
40
|
+
* `invalid-input` for no pages; a refusal changes nothing. An edit the engine
|
|
41
|
+
* refuses rejects with the engine's reason. When the `signal` fires the verb
|
|
42
|
+
* rejects `operation-cancelled`: before its change is made nothing changes,
|
|
43
|
+
* after it the change still applies.
|
|
47
44
|
*/
|
|
48
45
|
interface PageEditCapability {
|
|
49
46
|
/** Whether rotating, reordering, deleting and inserting pages is allowed (`doc.pages.assemble`). */
|
|
@@ -53,12 +50,20 @@ interface PageEditCapability {
|
|
|
53
50
|
* `duplicate()` and `insertFromDocument()` need it.
|
|
54
51
|
*/
|
|
55
52
|
canExtract(): boolean;
|
|
56
|
-
/**
|
|
53
|
+
/** The selected pages, in the order they were selected; pages the document no longer has drop out. */
|
|
54
|
+
getSelected(): readonly PageRef[];
|
|
55
|
+
/** Make these pages the selection; `[]` clears it. Pages the document doesn't have are left out. */
|
|
56
|
+
select(pages: readonly (PageRef | number)[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Turn each page by a quarter or half turn from its rotation, the one it
|
|
59
|
+
* has once the edits on their way apply: two quick quarter turns are a
|
|
60
|
+
* half turn.
|
|
61
|
+
*/
|
|
57
62
|
rotateBy(pages: readonly (PageRef | number)[], delta: 90 | -90 | 180, options?: OperationOptions): Promise<void>;
|
|
58
63
|
/** Give every page the same rotation. */
|
|
59
64
|
setRotation(pages: readonly (PageRef | number)[], rotation: PdfRotation, options?: OperationOptions): Promise<void>;
|
|
60
|
-
/** Move pages to
|
|
61
|
-
reorder(pages: readonly (PageRef | number)[],
|
|
65
|
+
/** Move pages to `position`; several pages keep their order and land together. */
|
|
66
|
+
reorder(pages: readonly (PageRef | number)[], position: PagePosition, options?: OperationOptions): Promise<void>;
|
|
62
67
|
/** Delete pages. Deleting every page is refused (`invalid-input`): a document keeps one. */
|
|
63
68
|
delete(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<void>;
|
|
64
69
|
/** Insert blank pages, one unless `count` says more. */
|
|
@@ -69,12 +74,18 @@ interface PageEditCapability {
|
|
|
69
74
|
* Insert copies of pages from another document open in the viewer. Needs
|
|
70
75
|
* `doc.download` on that document and `doc.pages.assemble` on this one.
|
|
71
76
|
*/
|
|
72
|
-
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?:
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?: PagePositionOptions): Promise<PageEditInsertResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Copy pages as they show, with the edits on their way, right after the
|
|
80
|
+
* last of them unless `position` says otherwise.
|
|
81
|
+
*/
|
|
82
|
+
duplicate(pages: readonly (PageRef | number)[], options?: PagePositionOptions): Promise<PageEditInsertResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Copy pages into a new PDF, as they show with the edits on their way, and
|
|
85
|
+
* resolve its bytes. This document doesn't change.
|
|
86
|
+
*/
|
|
76
87
|
extract(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<Uint8Array>;
|
|
77
88
|
}
|
|
78
89
|
//#endregion
|
|
79
|
-
export {
|
|
80
|
-
//# sourceMappingURL=contract-
|
|
90
|
+
export { PagePosition$1 as a, PdfSize$1 as c, PageInsertFromBytesOptions as i, PageEditToken as l, PageEditInsertResult as n, PagePositionOptions as o, PageInsertBlankOptions as r, PdfRotation$1 as s, PageEditCapability as t };
|
|
91
|
+
//# sourceMappingURL=contract-IN8rk5LG.d.cts.map
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { OperationOptions, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
1
|
+
import { OperationOptions, PagePosition, PagePosition as PagePosition$1, PageRef, PdfRotation, PdfRotation as PdfRotation$1, PdfSize, PdfSize as PdfSize$1 } from "@embedpdf/core";
|
|
2
2
|
//#region src/token.d.ts
|
|
3
3
|
declare const PageEditToken: import("@embedpdf/core").CapabilityToken<PageEditCapability>;
|
|
4
4
|
//#endregion
|
|
5
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
6
|
/** What an insert resolves: the new pages, in the order they were inserted. */
|
|
18
7
|
interface PageEditInsertResult {
|
|
19
8
|
readonly pages: readonly PageRef[];
|
|
@@ -21,8 +10,8 @@ interface PageEditInsertResult {
|
|
|
21
10
|
/** Options of `insertBlank()`. */
|
|
22
11
|
interface PageInsertBlankOptions extends OperationOptions {
|
|
23
12
|
/** Where the new pages go. Default `'end'`. */
|
|
24
|
-
readonly
|
|
25
|
-
/** The page size, in points. Default: the size of the page the new pages
|
|
13
|
+
readonly position?: PagePosition;
|
|
14
|
+
/** The page size, in points. Default: the size of the page the new pages go next to. */
|
|
26
15
|
readonly size?: PdfSize;
|
|
27
16
|
/** How many blank pages. Default 1. */
|
|
28
17
|
readonly count?: number;
|
|
@@ -32,18 +21,26 @@ interface PageInsertFromBytesOptions extends OperationOptions {
|
|
|
32
21
|
/** Only the source PDF's pages at these indexes, in this order. Default: every page. */
|
|
33
22
|
readonly pageIndexes?: readonly number[];
|
|
34
23
|
/** Where the new pages go. Default `'end'`. */
|
|
35
|
-
readonly
|
|
24
|
+
readonly position?: PagePosition;
|
|
36
25
|
}
|
|
37
26
|
/** Options of the verbs that copy pages in: `insertFromDocument()` and `duplicate()`. */
|
|
38
|
-
interface
|
|
39
|
-
readonly
|
|
27
|
+
interface PagePositionOptions extends OperationOptions {
|
|
28
|
+
readonly position?: PagePosition;
|
|
40
29
|
}
|
|
41
30
|
/**
|
|
42
|
-
* Every edit
|
|
31
|
+
* Every edit is one change: one step of the history, so undo brings the
|
|
32
|
+
* pages back as they were, a deleted page with its annotations under its old
|
|
33
|
+
* ref. It shows once the engine answers. Edits apply in the order they were
|
|
34
|
+
* called, except that the verbs that copy pages first (`duplicate()`,
|
|
35
|
+
* `insertFromDocument()`, `insertFromBytes()` with `pageIndexes`) take their
|
|
36
|
+
* place once the copy is made.
|
|
37
|
+
*
|
|
43
38
|
* 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
|
|
46
|
-
*
|
|
39
|
+
* session may not do it, `not-found` for a page the document doesn't have and
|
|
40
|
+
* `invalid-input` for no pages; a refusal changes nothing. An edit the engine
|
|
41
|
+
* refuses rejects with the engine's reason. When the `signal` fires the verb
|
|
42
|
+
* rejects `operation-cancelled`: before its change is made nothing changes,
|
|
43
|
+
* after it the change still applies.
|
|
47
44
|
*/
|
|
48
45
|
interface PageEditCapability {
|
|
49
46
|
/** Whether rotating, reordering, deleting and inserting pages is allowed (`doc.pages.assemble`). */
|
|
@@ -53,12 +50,20 @@ interface PageEditCapability {
|
|
|
53
50
|
* `duplicate()` and `insertFromDocument()` need it.
|
|
54
51
|
*/
|
|
55
52
|
canExtract(): boolean;
|
|
56
|
-
/**
|
|
53
|
+
/** The selected pages, in the order they were selected; pages the document no longer has drop out. */
|
|
54
|
+
getSelected(): readonly PageRef[];
|
|
55
|
+
/** Make these pages the selection; `[]` clears it. Pages the document doesn't have are left out. */
|
|
56
|
+
select(pages: readonly (PageRef | number)[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Turn each page by a quarter or half turn from its rotation, the one it
|
|
59
|
+
* has once the edits on their way apply: two quick quarter turns are a
|
|
60
|
+
* half turn.
|
|
61
|
+
*/
|
|
57
62
|
rotateBy(pages: readonly (PageRef | number)[], delta: 90 | -90 | 180, options?: OperationOptions): Promise<void>;
|
|
58
63
|
/** Give every page the same rotation. */
|
|
59
64
|
setRotation(pages: readonly (PageRef | number)[], rotation: PdfRotation, options?: OperationOptions): Promise<void>;
|
|
60
|
-
/** Move pages to
|
|
61
|
-
reorder(pages: readonly (PageRef | number)[],
|
|
65
|
+
/** Move pages to `position`; several pages keep their order and land together. */
|
|
66
|
+
reorder(pages: readonly (PageRef | number)[], position: PagePosition, options?: OperationOptions): Promise<void>;
|
|
62
67
|
/** Delete pages. Deleting every page is refused (`invalid-input`): a document keeps one. */
|
|
63
68
|
delete(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<void>;
|
|
64
69
|
/** Insert blank pages, one unless `count` says more. */
|
|
@@ -69,12 +74,18 @@ interface PageEditCapability {
|
|
|
69
74
|
* Insert copies of pages from another document open in the viewer. Needs
|
|
70
75
|
* `doc.download` on that document and `doc.pages.assemble` on this one.
|
|
71
76
|
*/
|
|
72
|
-
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?:
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
insertFromDocument(documentId: string, pages: readonly (PageRef | number)[], options?: PagePositionOptions): Promise<PageEditInsertResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Copy pages as they show, with the edits on their way, right after the
|
|
80
|
+
* last of them unless `position` says otherwise.
|
|
81
|
+
*/
|
|
82
|
+
duplicate(pages: readonly (PageRef | number)[], options?: PagePositionOptions): Promise<PageEditInsertResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Copy pages into a new PDF, as they show with the edits on their way, and
|
|
85
|
+
* resolve its bytes. This document doesn't change.
|
|
86
|
+
*/
|
|
76
87
|
extract(pages: readonly (PageRef | number)[], options?: OperationOptions): Promise<Uint8Array>;
|
|
77
88
|
}
|
|
78
89
|
//#endregion
|
|
79
|
-
export {
|
|
80
|
-
//# sourceMappingURL=contract-
|
|
90
|
+
export { PagePosition$1 as a, PdfSize$1 as c, PageInsertFromBytesOptions as i, PageEditToken as l, PageEditInsertResult as n, PagePositionOptions as o, PageInsertBlankOptions as r, PdfRotation$1 as s, PageEditCapability as t };
|
|
91
|
+
//# sourceMappingURL=contract-IN8rk5LG.d.ts.map
|
package/dist/contract.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions,
|
|
1
|
+
import { a as PagePosition, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePositionOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-IN8rk5LG.cjs";
|
|
2
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, type PagePosition, PagePositionOptions, type PdfRotation, type PdfSize };
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions,
|
|
1
|
+
import { a as PagePosition, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePositionOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-IN8rk5LG.js";
|
|
2
|
+
export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, type PagePosition, PagePositionOptions, type PdfRotation, type PdfSize };
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_contract = require("./contract-B4DeVaKv.cjs");
|
|
3
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
4
|
+
//#region src/model.ts
|
|
5
|
+
const initialPageEditState = () => ({ selected: [] });
|
|
6
|
+
/** The selection becomes `refs`, each page once. */
|
|
7
|
+
function selected(state, refs) {
|
|
8
|
+
const numbers = refs.map((ref) => ref.objectNumber);
|
|
9
|
+
const unique = refs.filter((_ref, i) => numbers.indexOf(numbers[i]) === i);
|
|
10
|
+
return unique.length === state.selected.length && unique.every((ref, i) => ref.objectNumber === state.selected[i].objectNumber) ? state : {
|
|
11
|
+
...state,
|
|
12
|
+
selected: unique
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
4
16
|
//#region src/controller.ts
|
|
5
17
|
/**
|
|
6
|
-
* The page-edit controller
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* of in every app's click handler.
|
|
18
|
+
* The page-edit controller: page edits that take refs or indexes, and the
|
|
19
|
+
* page selection. Turning a page relative to its own rotation, and fixing a
|
|
20
|
+
* position's page, live here once instead of in every app's click handler.
|
|
10
21
|
*
|
|
11
|
-
* A verb refuses before anything starts (permission, pages,
|
|
12
|
-
*
|
|
22
|
+
* A verb refuses before anything starts (permission, pages, position), then
|
|
23
|
+
* stages its edit as one change on the document's queue (`ctx.changes`): one
|
|
24
|
+
* step of the history, sent after everything staged before it. The page list
|
|
25
|
+
* shows it once the engine answers; nothing predicts pages yet.
|
|
13
26
|
*/
|
|
14
27
|
/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */
|
|
15
28
|
const ASSEMBLE = "doc.pages.assemble";
|
|
@@ -20,9 +33,9 @@ const LETTER_SIZE = {
|
|
|
20
33
|
width: 612,
|
|
21
34
|
height: 792
|
|
22
35
|
};
|
|
36
|
+
const NO_PAGES = Object.freeze([]);
|
|
37
|
+
const NO_LAYOUTS = Object.freeze([]);
|
|
23
38
|
function createPageEditController(ctx) {
|
|
24
|
-
const enqueue = ctx.serialQueue("edits");
|
|
25
|
-
ctx.onSettle(() => enqueue.idle());
|
|
26
39
|
/** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */
|
|
27
40
|
const mapErrors = async (work) => {
|
|
28
41
|
try {
|
|
@@ -31,7 +44,7 @@ function createPageEditController(ctx) {
|
|
|
31
44
|
throw (0, _embedpdf_core.toPluginError)("page-edit", error);
|
|
32
45
|
}
|
|
33
46
|
};
|
|
34
|
-
const registry = () => ctx.document()?.pages ??
|
|
47
|
+
const registry = () => ctx.document()?.pages ?? NO_LAYOUTS;
|
|
35
48
|
const noPages = () => new _embedpdf_core.PluginError("invalid-input", "page-edit", "no pages given");
|
|
36
49
|
/**
|
|
37
50
|
* A verb's pages as refs, checked when it is called: an index names the
|
|
@@ -41,17 +54,14 @@ function createPageEditController(ctx) {
|
|
|
41
54
|
if (pages.length === 0) throw noPages();
|
|
42
55
|
return pages.map((page) => ctx.pageOf(page).ref);
|
|
43
56
|
};
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 };
|
|
57
|
+
/** A position, its page checked when the verb is called, like the verb's own pages. Default `'end'`. */
|
|
58
|
+
const positionOf = (position) => {
|
|
59
|
+
if (!position || position === "start" || position === "end") return position ?? "end";
|
|
60
|
+
if ("after" in position) return { after: ctx.pageOf(position.after).ref };
|
|
61
|
+
return { before: ctx.pageOf(position.before).ref };
|
|
52
62
|
};
|
|
53
63
|
/**
|
|
54
|
-
* Default blank-page size: the page the
|
|
64
|
+
* Default blank-page size: the page the position names, else the page the
|
|
55
65
|
* new ones go next to (the first or the last), else Letter.
|
|
56
66
|
*/
|
|
57
67
|
const sizeAt = (position) => {
|
|
@@ -60,83 +70,188 @@ function createPageEditController(ctx) {
|
|
|
60
70
|
if (pages.length === 0) return LETTER_SIZE;
|
|
61
71
|
return (position === "start" ? pages[0] : pages[pages.length - 1]).size;
|
|
62
72
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
const getSelected = (0, _embedpdf_core.memo)(() => [registry(), ctx.state.get().selected], (pages, refs) => {
|
|
74
|
+
if (refs.length === 0) return NO_PAGES;
|
|
75
|
+
const present = new Set(pages.map((page) => page.ref.objectNumber));
|
|
76
|
+
return refs.filter((ref) => present.has(ref.objectNumber));
|
|
77
|
+
});
|
|
78
|
+
const select = (pages) => {
|
|
79
|
+
const refs = pages.flatMap((page) => {
|
|
80
|
+
const found = ctx.getPage(page);
|
|
81
|
+
return found ? [found.ref] : [];
|
|
82
|
+
});
|
|
83
|
+
ctx.state.update(selected, refs);
|
|
84
|
+
};
|
|
85
|
+
/** Refuse before anything starts: without the right, or once the caller cancelled. */
|
|
86
|
+
const refuse = (permission, operation, options) => {
|
|
87
|
+
ctx.assertAllowed(permission, operation);
|
|
88
|
+
if (options?.signal?.aborted) throw new _embedpdf_core.PluginError("operation-cancelled", "page-edit", `${operation} was cancelled`);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Stage one user action: one change, one step of the history. Its label
|
|
92
|
+
* counts the pages it acts on, when the verb knows how many.
|
|
93
|
+
*/
|
|
94
|
+
const stageChange = (verb, count, ops, undo) => {
|
|
95
|
+
const label = count === null ? { key: `pages.${verb}` } : {
|
|
96
|
+
key: `pages.${verb}`,
|
|
97
|
+
count
|
|
98
|
+
};
|
|
99
|
+
return ctx.changes.stage({
|
|
100
|
+
label,
|
|
101
|
+
ops,
|
|
102
|
+
...undo ? { undo } : {}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
/** The engine's answer to a staged change: its first item. */
|
|
106
|
+
const answer = async (change, options) => {
|
|
107
|
+
try {
|
|
108
|
+
return (await ctx.cancellable(options?.signal, change.result)).items[0];
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw (0, _embedpdf_core.toPluginError)("page-edit", error);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/** What an insert's answer names: the new pages. */
|
|
114
|
+
const inserted = async (change, options) => {
|
|
115
|
+
const item = await answer(change, options);
|
|
116
|
+
return { pages: "insertedPages" in item ? item.insertedPages : NO_PAGES };
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* A page's rotation once this session's changes on their way apply: the
|
|
120
|
+
* last rotation one of them gives it, else the page's own. What a turn
|
|
121
|
+
* starts from, so two quick quarter turns are a half turn.
|
|
122
|
+
*/
|
|
123
|
+
const rotationOf = (ref) => {
|
|
124
|
+
let rotation = ctx.pageOf(ref).rotation;
|
|
125
|
+
for (const change of ctx.changes.pending()) for (const op of change.shows) {
|
|
126
|
+
if (op.type !== "pages.rotate") continue;
|
|
127
|
+
if (op.pages.some((page) => page.objectNumber === ref.objectNumber)) rotation = op.rotation;
|
|
128
|
+
}
|
|
129
|
+
return rotation;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Rotate pages as one change: one op per rotation they end up with. What
|
|
133
|
+
* undoing it looks like is each page turned back, so a turn right after an
|
|
134
|
+
* undo starts from where the undo leaves the page.
|
|
135
|
+
*/
|
|
136
|
+
const rotate = (refs, rotationAfter) => {
|
|
137
|
+
const after = /* @__PURE__ */ new Map();
|
|
138
|
+
const before = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const ref of refs) {
|
|
140
|
+
const add = (groups, rotation) => groups.set(rotation, [...groups.get(rotation) ?? [], ref]);
|
|
141
|
+
add(before, rotationOf(ref));
|
|
142
|
+
add(after, rotationAfter(ref));
|
|
143
|
+
}
|
|
144
|
+
const opsOf = (groups) => [...groups].map(([rotation, pages]) => ({
|
|
145
|
+
type: "pages.rotate",
|
|
146
|
+
pages,
|
|
147
|
+
rotation
|
|
148
|
+
}));
|
|
149
|
+
return stageChange("rotate", refs.length, opsOf(after), opsOf(before));
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* The pages as they show: every change on its way sent and answered first,
|
|
153
|
+
* so a copy holds the edits made before it.
|
|
154
|
+
*/
|
|
155
|
+
const settled = async (options) => {
|
|
156
|
+
ctx.changes.sendHolds();
|
|
157
|
+
await ctx.cancellable(options?.signal, ctx.changes.whenSettled());
|
|
158
|
+
};
|
|
159
|
+
/** Copy pages of this document out, as they show. */
|
|
160
|
+
const extractShown = async (refs, options) => {
|
|
161
|
+
await settled(options);
|
|
162
|
+
return ctx.cancellable(options?.signal, ctx.doc.pages.extract([...refs]));
|
|
163
|
+
};
|
|
164
|
+
/** Stage the insert of a PDF's pages; resolves the new pages. */
|
|
165
|
+
const insertBytes = (verb, count, bytes, position, options) => {
|
|
166
|
+
const source = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
167
|
+
const change = stageChange(verb, count, [{
|
|
168
|
+
type: "pages.insert",
|
|
169
|
+
source,
|
|
170
|
+
position
|
|
171
|
+
}]);
|
|
172
|
+
return inserted(change, options);
|
|
173
|
+
};
|
|
174
|
+
/** Some pages of a PDF: open the bytes beside the document and extract them. */
|
|
175
|
+
const pagesOfBytes = async (bytes, pageIndexes, signal) => {
|
|
176
|
+
const source = await mapErrors(() => ctx.engine.open({
|
|
177
|
+
kind: "bytes",
|
|
178
|
+
id: `page-edit-source-${Date.now().toString(36)}`,
|
|
179
|
+
bytes: new Uint8Array(bytes)
|
|
180
|
+
}, { scope: ["*"] }));
|
|
181
|
+
try {
|
|
182
|
+
const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));
|
|
183
|
+
const refs = pageIndexes.map((index) => {
|
|
184
|
+
const page = layout.pages[index];
|
|
185
|
+
if (!page) throw new _embedpdf_core.PluginError("not-found", "page-edit", `the PDF has no page ${index}`);
|
|
186
|
+
return page.ref;
|
|
187
|
+
});
|
|
188
|
+
return await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));
|
|
189
|
+
} finally {
|
|
190
|
+
await source.close();
|
|
191
|
+
}
|
|
66
192
|
};
|
|
67
193
|
return { api: {
|
|
68
194
|
canEdit: () => ctx.allows(ASSEMBLE),
|
|
69
195
|
canExtract: () => ctx.allows(DOWNLOAD),
|
|
196
|
+
getSelected,
|
|
197
|
+
select,
|
|
70
198
|
rotateBy: async (pages, delta, options) => {
|
|
71
|
-
|
|
199
|
+
refuse(ASSEMBLE, "pageEdit.rotateBy", options);
|
|
72
200
|
const refs = refsOf(pages);
|
|
73
|
-
const
|
|
74
|
-
await
|
|
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);
|
|
201
|
+
const change = rotate(refs, (ref) => ((rotationOf(ref) + delta) % 360 + 360) % 360);
|
|
202
|
+
await answer(change, options);
|
|
82
203
|
},
|
|
83
204
|
setRotation: async (pages, rotation, options) => {
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
await
|
|
205
|
+
refuse(ASSEMBLE, "pageEdit.setRotation", options);
|
|
206
|
+
const change = rotate(refsOf(pages), () => rotation);
|
|
207
|
+
await answer(change, options);
|
|
87
208
|
},
|
|
88
|
-
reorder: async (pages,
|
|
89
|
-
|
|
209
|
+
reorder: async (pages, position, options) => {
|
|
210
|
+
refuse(ASSEMBLE, "pageEdit.reorder", options);
|
|
90
211
|
const refs = refsOf(pages);
|
|
91
|
-
const
|
|
92
|
-
|
|
212
|
+
const change = stageChange("reorder", refs.length, [{
|
|
213
|
+
type: "pages.reorder",
|
|
214
|
+
pages: refs,
|
|
215
|
+
position: positionOf(position)
|
|
216
|
+
}]);
|
|
217
|
+
await answer(change, options);
|
|
93
218
|
},
|
|
94
219
|
delete: async (pages, options) => {
|
|
95
|
-
|
|
220
|
+
refuse(ASSEMBLE, "pageEdit.delete", options);
|
|
96
221
|
const refs = refsOf(pages);
|
|
97
222
|
const deleting = new Set(refs.map((ref) => ref.objectNumber));
|
|
98
223
|
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
|
-
|
|
224
|
+
const change = stageChange("delete", refs.length, [{
|
|
225
|
+
type: "pages.delete",
|
|
226
|
+
pages: refs
|
|
227
|
+
}]);
|
|
228
|
+
await answer(change, options);
|
|
100
229
|
},
|
|
101
230
|
insertBlank: async (options = {}) => {
|
|
102
|
-
|
|
103
|
-
const position = positionOf(options.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
231
|
+
refuse(ASSEMBLE, "pageEdit.insertBlank", options);
|
|
232
|
+
const position = positionOf(options.position);
|
|
233
|
+
const count = options.count ?? 1;
|
|
234
|
+
const size = options.size ?? sizeAt(position);
|
|
235
|
+
const change = stageChange("insertBlank", count, [{
|
|
236
|
+
type: "pages.insertBlank",
|
|
237
|
+
spec: {
|
|
107
238
|
size,
|
|
108
|
-
count
|
|
109
|
-
},
|
|
110
|
-
|
|
239
|
+
count
|
|
240
|
+
},
|
|
241
|
+
position
|
|
242
|
+
}]);
|
|
243
|
+
return inserted(change, options);
|
|
111
244
|
},
|
|
112
245
|
insertFromBytes: async (bytes, options = {}) => {
|
|
113
|
-
|
|
114
|
-
const position = positionOf(options.
|
|
246
|
+
refuse(ASSEMBLE, "pageEdit.insertFromBytes", options);
|
|
247
|
+
const position = positionOf(options.position);
|
|
115
248
|
const { pageIndexes, signal } = options;
|
|
116
249
|
if (pageIndexes?.length === 0) throw noPages();
|
|
117
|
-
|
|
118
|
-
|
|
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);
|
|
250
|
+
const source = pageIndexes ? await pagesOfBytes(bytes, pageIndexes, signal) : bytes;
|
|
251
|
+
return insertBytes("insert", pageIndexes?.length ?? null, source, position, options);
|
|
137
252
|
},
|
|
138
253
|
insertFromDocument: async (documentId, pages, options = {}) => {
|
|
139
|
-
|
|
254
|
+
refuse(ASSEMBLE, "pageEdit.insertFromDocument", options);
|
|
140
255
|
if (pages.length === 0) throw noPages();
|
|
141
256
|
const other = ctx.documentHandle(documentId);
|
|
142
257
|
if (!other) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' is not open`);
|
|
@@ -146,44 +261,59 @@ function createPageEditController(ctx) {
|
|
|
146
261
|
if (!found) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' has no such page`);
|
|
147
262
|
return found.ref;
|
|
148
263
|
});
|
|
149
|
-
const position = positionOf(options.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return insertBytes(bytes, position, options.signal);
|
|
153
|
-
}, options);
|
|
264
|
+
const position = positionOf(options.position);
|
|
265
|
+
const bytes = await mapErrors(() => ctx.cancellable(options.signal, other.pages.extract(refs)));
|
|
266
|
+
return insertBytes("insert", refs.length, bytes, position, options);
|
|
154
267
|
},
|
|
155
268
|
duplicate: async (pages, options = {}) => {
|
|
156
|
-
|
|
269
|
+
refuse(ASSEMBLE, "pageEdit.duplicate", options);
|
|
157
270
|
ctx.assertAllowed(DOWNLOAD, "pageEdit.duplicate");
|
|
158
271
|
const refs = refsOf(pages);
|
|
159
|
-
const position = options.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return insertBytes(bytes, position, options.signal);
|
|
163
|
-
}, options);
|
|
272
|
+
const position = options.position ? positionOf(options.position) : { after: refs[refs.length - 1] };
|
|
273
|
+
const bytes = await extractShown(refs, options);
|
|
274
|
+
return insertBytes("duplicate", refs.length, bytes, position, options);
|
|
164
275
|
},
|
|
165
276
|
extract: async (pages, options) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return enqueue(() => ctx.cancellable(options?.signal, ctx.doc.pages.extract(refs)), options);
|
|
277
|
+
refuse(DOWNLOAD, "pageEdit.extract", options);
|
|
278
|
+
return extractShown(refsOf(pages), options);
|
|
169
279
|
}
|
|
170
280
|
} };
|
|
171
281
|
}
|
|
172
282
|
//#endregion
|
|
173
283
|
//#region src/page-edit.plugin.ts
|
|
174
284
|
/**
|
|
175
|
-
* Document-scoped
|
|
176
|
-
*
|
|
177
|
-
* rotation lives in the controller, so no app re-derives it.
|
|
285
|
+
* Document-scoped: the document's page edits, each one change of its
|
|
286
|
+
* history, and which of its pages are selected. Turning a page relative to
|
|
287
|
+
* its own rotation lives in the controller, so no app re-derives it.
|
|
178
288
|
*/
|
|
179
289
|
const pageEditPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
180
290
|
id: "page-edit",
|
|
181
291
|
scope: "document",
|
|
182
292
|
token: require_contract.PageEditToken,
|
|
293
|
+
state: initialPageEditState,
|
|
183
294
|
create: createPageEditController
|
|
184
295
|
});
|
|
185
296
|
//#endregion
|
|
297
|
+
//#region src/state.ts
|
|
298
|
+
/**
|
|
299
|
+
* The pages page's State table as code: what `usePageEditState()` returns,
|
|
300
|
+
* and the same fields in every other framework.
|
|
301
|
+
*/
|
|
302
|
+
const pageEditState = (0, _embedpdf_core.defineState)(require_contract.PageEditToken, {
|
|
303
|
+
read: (pageEdit) => ({
|
|
304
|
+
selected: pageEdit.getSelected(),
|
|
305
|
+
canEdit: pageEdit.canEdit(),
|
|
306
|
+
canExtract: pageEdit.canExtract()
|
|
307
|
+
}),
|
|
308
|
+
empty: {
|
|
309
|
+
selected: [],
|
|
310
|
+
canEdit: false,
|
|
311
|
+
canExtract: false
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
//#endregion
|
|
186
315
|
exports.PageEditToken = require_contract.PageEditToken;
|
|
187
316
|
exports.pageEditPlugin = pageEditPlugin;
|
|
317
|
+
exports.pageEditState = pageEditState;
|
|
188
318
|
|
|
189
319
|
//# sourceMappingURL=index.cjs.map
|