pdf-oxide 0.3.37 → 0.3.39
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/lib/builders/document-builder.d.ts +350 -0
- package/lib/builders/document-builder.js +724 -0
- package/lib/builders/index.d.ts +4 -2
- package/lib/builders/index.js +4 -2
- package/lib/builders/pdf-builder.d.ts +2 -0
- package/lib/builders/pdf-builder.js +12 -0
- package/lib/builders/streaming-table.d.ts +49 -0
- package/lib/builders/streaming-table.js +110 -0
- package/lib/document-editor.d.ts +122 -0
- package/lib/document-editor.js +313 -0
- package/lib/errors.js +3 -4
- package/lib/form-field-manager.js +3 -1
- package/lib/index.d.ts +41 -7
- package/lib/index.js +266 -90
- package/lib/managers/accessibility-manager.js +19 -8
- package/lib/managers/annotation-manager.js +9 -9
- package/lib/managers/barcode-manager.js +18 -7
- package/lib/managers/batch-manager.js +2 -5
- package/lib/managers/cache-manager.js +1 -3
- package/lib/managers/compliance-manager.js +58 -19
- package/lib/managers/document-utility-manager.js +6 -6
- package/lib/managers/dom-pdf-creator.js +9 -9
- package/lib/managers/enterprise-manager.js +4 -1
- package/lib/managers/extended-managers.js +8 -1
- package/lib/managers/extraction-manager.js +7 -2
- package/lib/managers/final-utilities.d.ts +3 -3
- package/lib/managers/final-utilities.js +9 -4
- package/lib/managers/hybrid-ml-advanced.js +22 -6
- package/lib/managers/index.d.ts +22 -22
- package/lib/managers/index.js +23 -23
- package/lib/managers/layer-manager.js +20 -21
- package/lib/managers/ocr-manager.d.ts +2 -2
- package/lib/managers/ocr-manager.js +7 -7
- package/lib/managers/optimization-manager.js +24 -4
- package/lib/managers/page-manager.js +5 -6
- package/lib/managers/pattern-detection.d.ts +1 -1
- package/lib/managers/pattern-detection.js +4 -6
- package/lib/managers/search-manager.js +3 -3
- package/lib/managers/signature-manager.d.ts +14 -0
- package/lib/managers/signature-manager.js +185 -40
- package/lib/managers/streams.js +8 -2
- package/lib/managers/xfa-manager.js +69 -19
- package/lib/native-loader.d.ts +7 -0
- package/lib/native-loader.js +62 -0
- package/lib/native.d.ts +16 -0
- package/lib/native.js +69 -0
- package/lib/pdf-creator-manager.js +4 -1
- package/lib/result-accessors-manager.js +3 -1
- package/lib/timestamp.d.ts +54 -0
- package/lib/timestamp.js +115 -0
- package/lib/tsa-client.d.ts +44 -0
- package/lib/tsa-client.js +67 -0
- package/lib/types/common.d.ts +80 -1
- package/lib/types/common.js +14 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/manager-types.js +4 -2
- package/lib/workers/index.d.ts +1 -1
- package/lib/workers/pool.js +2 -4
- package/package.json +17 -11
- package/prebuilds/darwin-arm64/pdf_oxide.node +0 -0
- package/prebuilds/darwin-x64/pdf_oxide.node +0 -0
- package/prebuilds/linux-arm64/pdf_oxide.node +0 -0
- package/prebuilds/linux-x64/pdf_oxide.node +0 -0
- package/prebuilds/win32-x64/pdf_oxide.node +0 -0
package/lib/builders/index.d.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* This module exports builder classes that implement the fluent builder pattern
|
|
5
5
|
* for configuring PDF documents, annotations, search options, metadata, and conversion.
|
|
6
6
|
*/
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './annotation-builder';
|
|
8
8
|
export * from './conversion-options-builder';
|
|
9
|
+
export * from './document-builder';
|
|
9
10
|
export * from './metadata-builder';
|
|
10
|
-
export * from './
|
|
11
|
+
export * from './pdf-builder';
|
|
11
12
|
export * from './search-options-builder';
|
|
13
|
+
export * from './streaming-table';
|
package/lib/builders/index.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* This module exports builder classes that implement the fluent builder pattern
|
|
5
5
|
* for configuring PDF documents, annotations, search options, metadata, and conversion.
|
|
6
6
|
*/
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './annotation-builder.js';
|
|
8
8
|
export * from './conversion-options-builder.js';
|
|
9
|
+
export * from './document-builder.js';
|
|
9
10
|
export * from './metadata-builder.js';
|
|
10
|
-
export * from './
|
|
11
|
+
export * from './pdf-builder.js';
|
|
11
12
|
export * from './search-options-builder.js';
|
|
13
|
+
export * from './streaming-table.js';
|
|
@@ -156,6 +156,8 @@ export declare class PdfBuilder {
|
|
|
156
156
|
* ```
|
|
157
157
|
*/
|
|
158
158
|
fromText(text: string): any;
|
|
159
|
+
fromHtmlCss(html: string, css: string, fontBytes: Buffer | Uint8Array): any;
|
|
160
|
+
fromHtmlCssWithFonts(html: string, css: string, families: string[], fonts: (Buffer | Uint8Array)[]): any;
|
|
159
161
|
/**
|
|
160
162
|
* Asynchronously creates a PDF document from Markdown content
|
|
161
163
|
* @param markdown - Markdown formatted content
|
|
@@ -231,6 +231,18 @@ export class PdfBuilder {
|
|
|
231
231
|
this._applyConfiguration(pdf);
|
|
232
232
|
return pdf;
|
|
233
233
|
}
|
|
234
|
+
fromHtmlCss(html, css, fontBytes) {
|
|
235
|
+
const { Pdf } = require('../index.js');
|
|
236
|
+
const pdf = Pdf.fromHtmlCss(html, css, fontBytes);
|
|
237
|
+
this._applyConfiguration(pdf);
|
|
238
|
+
return pdf;
|
|
239
|
+
}
|
|
240
|
+
fromHtmlCssWithFonts(html, css, families, fonts) {
|
|
241
|
+
const { Pdf } = require('../index.js');
|
|
242
|
+
const pdf = Pdf.fromHtmlCssWithFonts(html, css, families, fonts);
|
|
243
|
+
this._applyConfiguration(pdf);
|
|
244
|
+
return pdf;
|
|
245
|
+
}
|
|
234
246
|
/**
|
|
235
247
|
* Asynchronously creates a PDF document from Markdown content
|
|
236
248
|
* @param markdown - Markdown formatted content
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming-table adapter backed by the native row-at-a-time FFI
|
|
3
|
+
* (`pdf_page_builder_streaming_table_begin_v2` / `_push_row` / `_finish`).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const t = page.streamingTable({
|
|
8
|
+
* columns: [
|
|
9
|
+
* { header: 'SKU', width: 72 },
|
|
10
|
+
* { header: 'Item', width: 200 },
|
|
11
|
+
* { header: 'Qty', width: 48, align: Align.Right },
|
|
12
|
+
* ],
|
|
13
|
+
* repeatHeader: true,
|
|
14
|
+
* mode: { kind: 'sample', sampleRows: 30 },
|
|
15
|
+
* });
|
|
16
|
+
* for await (const row of readRowsFromDb()) {
|
|
17
|
+
* t.pushRow([row.sku, row.item, String(row.qty)]);
|
|
18
|
+
* }
|
|
19
|
+
* await t.finish();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { SpanCell, StreamingTableConfig } from '../types/common.js';
|
|
23
|
+
import type { PageBuilder } from './document-builder.js';
|
|
24
|
+
export declare class StreamingTable {
|
|
25
|
+
private _page;
|
|
26
|
+
private _columns;
|
|
27
|
+
private _opened;
|
|
28
|
+
private _finished;
|
|
29
|
+
/** @internal — constructed via `PageBuilder.streamingTable(...)`. */
|
|
30
|
+
constructor(page: PageBuilder, config: StreamingTableConfig);
|
|
31
|
+
/** Push a single row (all rowspan=1). Throws if `cells.length !== columns.length`. */
|
|
32
|
+
pushRow(cells: Array<string | null | undefined>): this;
|
|
33
|
+
/**
|
|
34
|
+
* Push a single row with per-cell rowspan values. Each element is either
|
|
35
|
+
* a `SpanCell` (`{ text, rowspan }`) or a plain string (rowspan=1).
|
|
36
|
+
* Requires `maxRowspan ≥ 2` in the `StreamingTableConfig`.
|
|
37
|
+
*/
|
|
38
|
+
pushRowSpan(cells: Array<SpanCell | string | null | undefined>): this;
|
|
39
|
+
/**
|
|
40
|
+
* Convenience: consume a sync or async iterable and push each row.
|
|
41
|
+
*/
|
|
42
|
+
pushAll(rows: Iterable<Array<string | null | undefined>> | AsyncIterable<Array<string | null | undefined>>): Promise<this>;
|
|
43
|
+
/**
|
|
44
|
+
* Close the streaming table and return the parent PageBuilder for chaining.
|
|
45
|
+
*/
|
|
46
|
+
finish(): Promise<PageBuilder>;
|
|
47
|
+
/** Number of the columns this table was opened with. */
|
|
48
|
+
get columnCount(): number;
|
|
49
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming-table adapter backed by the native row-at-a-time FFI
|
|
3
|
+
* (`pdf_page_builder_streaming_table_begin_v2` / `_push_row` / `_finish`).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const t = page.streamingTable({
|
|
8
|
+
* columns: [
|
|
9
|
+
* { header: 'SKU', width: 72 },
|
|
10
|
+
* { header: 'Item', width: 200 },
|
|
11
|
+
* { header: 'Qty', width: 48, align: Align.Right },
|
|
12
|
+
* ],
|
|
13
|
+
* repeatHeader: true,
|
|
14
|
+
* mode: { kind: 'sample', sampleRows: 30 },
|
|
15
|
+
* });
|
|
16
|
+
* for await (const row of readRowsFromDb()) {
|
|
17
|
+
* t.pushRow([row.sku, row.item, String(row.qty)]);
|
|
18
|
+
* }
|
|
19
|
+
* await t.finish();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class StreamingTable {
|
|
23
|
+
/** @internal — constructed via `PageBuilder.streamingTable(...)`. */
|
|
24
|
+
constructor(page, config) {
|
|
25
|
+
this._opened = false;
|
|
26
|
+
this._finished = false;
|
|
27
|
+
if (!config || !Array.isArray(config.columns) || config.columns.length === 0) {
|
|
28
|
+
throw new Error('StreamingTable requires at least one column');
|
|
29
|
+
}
|
|
30
|
+
this._page = page;
|
|
31
|
+
this._columns = config.columns;
|
|
32
|
+
const headers = config.columns.map((c) => c.header ?? '');
|
|
33
|
+
const widths = config.columns.map((c) => c.width);
|
|
34
|
+
const aligns = config.columns.map((c) => (c.align ?? 0));
|
|
35
|
+
const repeat = config.repeatHeader !== false;
|
|
36
|
+
const maxRowspan = config.maxRowspan != null && config.maxRowspan >= 2 ? config.maxRowspan : 1;
|
|
37
|
+
this._page._streamingTableBeginV2(headers, widths, aligns, repeat, config.mode, maxRowspan);
|
|
38
|
+
this._opened = true;
|
|
39
|
+
}
|
|
40
|
+
/** Push a single row (all rowspan=1). Throws if `cells.length !== columns.length`. */
|
|
41
|
+
pushRow(cells) {
|
|
42
|
+
if (this._finished) {
|
|
43
|
+
throw new Error('StreamingTable already finished');
|
|
44
|
+
}
|
|
45
|
+
if (cells.length !== this._columns.length) {
|
|
46
|
+
throw new Error(`row width ${cells.length} does not match column count ${this._columns.length}`);
|
|
47
|
+
}
|
|
48
|
+
this._page._streamingTablePushRow(cells.map((c) => (c == null ? null : String(c))));
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Push a single row with per-cell rowspan values. Each element is either
|
|
53
|
+
* a `SpanCell` (`{ text, rowspan }`) or a plain string (rowspan=1).
|
|
54
|
+
* Requires `maxRowspan ≥ 2` in the `StreamingTableConfig`.
|
|
55
|
+
*/
|
|
56
|
+
pushRowSpan(cells) {
|
|
57
|
+
if (this._finished) {
|
|
58
|
+
throw new Error('StreamingTable already finished');
|
|
59
|
+
}
|
|
60
|
+
if (cells.length !== this._columns.length) {
|
|
61
|
+
throw new Error(`row width ${cells.length} does not match column count ${this._columns.length}`);
|
|
62
|
+
}
|
|
63
|
+
const normalized = cells.map((c) => {
|
|
64
|
+
if (c == null)
|
|
65
|
+
return [null, 1];
|
|
66
|
+
if (typeof c === 'string')
|
|
67
|
+
return [c, 1];
|
|
68
|
+
return [c.text, c.rowspan];
|
|
69
|
+
});
|
|
70
|
+
this._page._streamingTablePushRowV2(normalized);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Convenience: consume a sync or async iterable and push each row.
|
|
75
|
+
*/
|
|
76
|
+
async pushAll(rows) {
|
|
77
|
+
if (this._finished) {
|
|
78
|
+
throw new Error('StreamingTable already finished');
|
|
79
|
+
}
|
|
80
|
+
const anyRows = rows;
|
|
81
|
+
if (typeof anyRows[Symbol.asyncIterator] === 'function') {
|
|
82
|
+
for await (const row of rows) {
|
|
83
|
+
this.pushRow(row);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
this.pushRow(row);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Close the streaming table and return the parent PageBuilder for chaining.
|
|
95
|
+
*/
|
|
96
|
+
async finish() {
|
|
97
|
+
if (this._finished) {
|
|
98
|
+
throw new Error('StreamingTable already finished');
|
|
99
|
+
}
|
|
100
|
+
this._finished = true;
|
|
101
|
+
if (this._opened) {
|
|
102
|
+
this._page._streamingTableFinish();
|
|
103
|
+
}
|
|
104
|
+
return this._page;
|
|
105
|
+
}
|
|
106
|
+
/** Number of the columns this table was opened with. */
|
|
107
|
+
get columnCount() {
|
|
108
|
+
return this._columns.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page rotation angles valid for {@link DocumentEditor.setPageRotation}.
|
|
3
|
+
*/
|
|
4
|
+
export type PageRotation = 0 | 90 | 180 | 270;
|
|
5
|
+
/**
|
|
6
|
+
* PDF editor bound to a concrete file on disk. Open via
|
|
7
|
+
* {@link DocumentEditor.open}; always pair with {@link close} (or use
|
|
8
|
+
* `using` / the explicit-resource-management protocol once it's
|
|
9
|
+
* stable in your toolchain).
|
|
10
|
+
*/
|
|
11
|
+
export declare class DocumentEditor {
|
|
12
|
+
private _handle;
|
|
13
|
+
private _closed;
|
|
14
|
+
private constructor();
|
|
15
|
+
/** Open a PDF file for editing. */
|
|
16
|
+
static open(path: string): DocumentEditor;
|
|
17
|
+
/** Open a PDF from an in-memory buffer for editing. */
|
|
18
|
+
static openFromBytes(data: Buffer | Uint8Array): DocumentEditor;
|
|
19
|
+
/** True if the editor has been closed. Subsequent calls will throw. */
|
|
20
|
+
get closed(): boolean;
|
|
21
|
+
private _throwIfClosed;
|
|
22
|
+
/** Current page count. */
|
|
23
|
+
pageCount(): number;
|
|
24
|
+
/** True if the editor has unsaved modifications. */
|
|
25
|
+
isModified(): boolean;
|
|
26
|
+
setTitle(title: string): void;
|
|
27
|
+
setAuthor(author: string): void;
|
|
28
|
+
setSubject(subject: string): void;
|
|
29
|
+
getKeywords(): string | null;
|
|
30
|
+
setKeywords(keywords: string): void;
|
|
31
|
+
getProducer(): string;
|
|
32
|
+
setProducer(producer: string): void;
|
|
33
|
+
getCreationDate(): string;
|
|
34
|
+
setCreationDate(date: string): void;
|
|
35
|
+
/** Delete the page at `pageIndex` (zero-based). */
|
|
36
|
+
deletePage(pageIndex: number): void;
|
|
37
|
+
/** Move a page. Indices refer to positions before the move. */
|
|
38
|
+
movePage(fromIndex: number, toIndex: number): void;
|
|
39
|
+
/** Set rotation on a page (0/90/180/270). */
|
|
40
|
+
setPageRotation(pageIndex: number, degrees: PageRotation): void;
|
|
41
|
+
/**
|
|
42
|
+
* Append every page of another PDF to the end of this document.
|
|
43
|
+
*/
|
|
44
|
+
mergeFrom(sourcePath: string): void;
|
|
45
|
+
/** Flatten form fields across the entire document. */
|
|
46
|
+
flattenForms(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Return warnings collected during the last form-flattening save.
|
|
49
|
+
* Each entry names a widget field that had no `/AP` appearance stream;
|
|
50
|
+
* flattening it produces a blank rectangle.
|
|
51
|
+
*/
|
|
52
|
+
flattenWarnings(): string[];
|
|
53
|
+
/** Flatten annotations. If `pageIndex` is omitted, flattens all pages. */
|
|
54
|
+
flattenAnnotations(pageIndex?: number): void;
|
|
55
|
+
/** Set a form field value by fully-qualified field name. */
|
|
56
|
+
setFormFieldValue(fieldName: string, value: string): void;
|
|
57
|
+
/** Import an FDF file (bytes) into the document's form. */
|
|
58
|
+
importFdfBytes(fdf: Buffer | Uint8Array): void;
|
|
59
|
+
/** Import an XFDF file (bytes) into the document's form. */
|
|
60
|
+
importXfdfBytes(xfdf: Buffer | Uint8Array): void;
|
|
61
|
+
/**
|
|
62
|
+
* Append every page of another PDF (supplied as bytes) to this document.
|
|
63
|
+
* Returns the number of pages added.
|
|
64
|
+
*/
|
|
65
|
+
mergeFromBytes(data: Buffer | Uint8Array): number;
|
|
66
|
+
/** Embed a file attachment into the document. */
|
|
67
|
+
embedFile(name: string, data: Buffer | Uint8Array): void;
|
|
68
|
+
/** Burn in redaction annotations on a single page (zero-based). */
|
|
69
|
+
applyPageRedactions(pageIndex: number): void;
|
|
70
|
+
/** Burn in all pending redaction annotations across the document. */
|
|
71
|
+
applyAllRedactions(): void;
|
|
72
|
+
/** Rotate all pages by `degrees` (additive). */
|
|
73
|
+
rotateAllPages(degrees: number): void;
|
|
74
|
+
/** Rotate a single page by `degrees` (additive). */
|
|
75
|
+
rotatePageBy(pageIndex: number, degrees: number): void;
|
|
76
|
+
/** Get the MediaBox of a page as `{x, y, width, height}`. */
|
|
77
|
+
getPageMediaBox(pageIndex: number): {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
};
|
|
83
|
+
/** Set the MediaBox of a page. */
|
|
84
|
+
setPageMediaBox(pageIndex: number, x: number, y: number, width: number, height: number): void;
|
|
85
|
+
/** Get the CropBox of a page. Returns `{x:0,y:0,width:0,height:0}` if none set. */
|
|
86
|
+
getPageCropBox(pageIndex: number): {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
};
|
|
92
|
+
/** Set the CropBox of a page. */
|
|
93
|
+
setPageCropBox(pageIndex: number, x: number, y: number, width: number, height: number): void;
|
|
94
|
+
/**
|
|
95
|
+
* Erase rectangular regions on a page.
|
|
96
|
+
* `rects` is an array of `[x, y, w, h]` tuples.
|
|
97
|
+
*/
|
|
98
|
+
eraseRegions(pageIndex: number, rects: [number, number, number, number][]): void;
|
|
99
|
+
/** Clear all pending erase-region entries for a page. */
|
|
100
|
+
clearEraseRegions(pageIndex: number): void;
|
|
101
|
+
/** Flatten form fields on a single page. */
|
|
102
|
+
flattenFormsOnPage(pageIndex: number): void;
|
|
103
|
+
/** True if the page is marked for annotation-flatten. */
|
|
104
|
+
isPageMarkedForFlatten(pageIndex: number): boolean;
|
|
105
|
+
/** Remove the flatten mark from a page. */
|
|
106
|
+
unmarkPageForFlatten(pageIndex: number): void;
|
|
107
|
+
/** True if the page is marked for redaction. */
|
|
108
|
+
isPageMarkedForRedaction(pageIndex: number): boolean;
|
|
109
|
+
/** Remove the redaction mark from a page. */
|
|
110
|
+
unmarkPageForRedaction(pageIndex: number): void;
|
|
111
|
+
/** Save the document to `path`. */
|
|
112
|
+
save(path: string): void;
|
|
113
|
+
/** Save with AES-256 encryption (user + owner passwords). */
|
|
114
|
+
saveEncrypted(path: string, userPassword: string, ownerPassword: string): void;
|
|
115
|
+
/** Save the document to an in-memory Buffer. */
|
|
116
|
+
saveToBytes(): Buffer;
|
|
117
|
+
/** Save to an in-memory Buffer with explicit compression / GC / linearize flags. */
|
|
118
|
+
saveToBytesWithOptions(compress: boolean, garbageCollect: boolean, linearize: boolean): Buffer;
|
|
119
|
+
/** Release the native handle. Safe to call multiple times. */
|
|
120
|
+
close(): void;
|
|
121
|
+
}
|
|
122
|
+
export default DocumentEditor;
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEditor — thin TS wrapper around the N-API `editor*` exports
|
|
3
|
+
* in `binding.cc`. Mirrors the C# `DocumentEditor` / Go
|
|
4
|
+
* `DocumentEditor` surface.
|
|
5
|
+
*
|
|
6
|
+
* Every mutation is a synchronous call into the Rust core; the same
|
|
7
|
+
* handle is carried until {@link DocumentEditor.close}. Throws plain
|
|
8
|
+
* `Error` with the native message on failure.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { DocumentEditor } from 'pdf-oxide';
|
|
12
|
+
*
|
|
13
|
+
* const editor = DocumentEditor.open('in.pdf');
|
|
14
|
+
* try {
|
|
15
|
+
* editor.mergeFrom('other.pdf');
|
|
16
|
+
* editor.deletePage(0);
|
|
17
|
+
* editor.movePage(0, 2);
|
|
18
|
+
* editor.setPageRotation(0, 90);
|
|
19
|
+
* editor.save('out.pdf');
|
|
20
|
+
* } finally {
|
|
21
|
+
* editor.close();
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
// Load the native addon via the shared prebuild-aware loader.
|
|
26
|
+
// Importing `./index.js` would create an ESM cycle (index.js imports
|
|
27
|
+
// us back), so we go through `./native.js` — same resolver, no cycle,
|
|
28
|
+
// resolves against `prebuilds/<triple>/pdf_oxide.node` in the
|
|
29
|
+
// published package.
|
|
30
|
+
import { loadNative } from './native.js';
|
|
31
|
+
const native = loadNative();
|
|
32
|
+
/**
|
|
33
|
+
* PDF editor bound to a concrete file on disk. Open via
|
|
34
|
+
* {@link DocumentEditor.open}; always pair with {@link close} (or use
|
|
35
|
+
* `using` / the explicit-resource-management protocol once it's
|
|
36
|
+
* stable in your toolchain).
|
|
37
|
+
*/
|
|
38
|
+
export class DocumentEditor {
|
|
39
|
+
constructor(handle) {
|
|
40
|
+
this._closed = false;
|
|
41
|
+
this._handle = handle;
|
|
42
|
+
}
|
|
43
|
+
/** Open a PDF file for editing. */
|
|
44
|
+
static open(path) {
|
|
45
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
46
|
+
throw new TypeError('path must be a non-empty string');
|
|
47
|
+
}
|
|
48
|
+
const handle = native.editorOpen(path);
|
|
49
|
+
return new DocumentEditor(handle);
|
|
50
|
+
}
|
|
51
|
+
/** Open a PDF from an in-memory buffer for editing. */
|
|
52
|
+
static openFromBytes(data) {
|
|
53
|
+
if (!data || data.length === 0) {
|
|
54
|
+
throw new TypeError('data must be a non-empty Buffer or Uint8Array');
|
|
55
|
+
}
|
|
56
|
+
const handle = native.editorOpenFromBytes(data);
|
|
57
|
+
return new DocumentEditor(handle);
|
|
58
|
+
}
|
|
59
|
+
/** True if the editor has been closed. Subsequent calls will throw. */
|
|
60
|
+
get closed() {
|
|
61
|
+
return this._closed;
|
|
62
|
+
}
|
|
63
|
+
_throwIfClosed() {
|
|
64
|
+
if (this._closed)
|
|
65
|
+
throw new Error('DocumentEditor is closed');
|
|
66
|
+
}
|
|
67
|
+
/** Current page count. */
|
|
68
|
+
pageCount() {
|
|
69
|
+
this._throwIfClosed();
|
|
70
|
+
return native.editorGetPageCount(this._handle);
|
|
71
|
+
}
|
|
72
|
+
/** True if the editor has unsaved modifications. */
|
|
73
|
+
isModified() {
|
|
74
|
+
this._throwIfClosed();
|
|
75
|
+
return native.editorIsModified(this._handle);
|
|
76
|
+
}
|
|
77
|
+
// ----- metadata ---------------------------------------------------
|
|
78
|
+
setTitle(title) {
|
|
79
|
+
this._throwIfClosed();
|
|
80
|
+
native.editorSetTitle(this._handle, title);
|
|
81
|
+
}
|
|
82
|
+
setAuthor(author) {
|
|
83
|
+
this._throwIfClosed();
|
|
84
|
+
native.editorSetAuthor(this._handle, author);
|
|
85
|
+
}
|
|
86
|
+
setSubject(subject) {
|
|
87
|
+
this._throwIfClosed();
|
|
88
|
+
native.editorSetSubject(this._handle, subject);
|
|
89
|
+
}
|
|
90
|
+
getKeywords() {
|
|
91
|
+
this._throwIfClosed();
|
|
92
|
+
return native.editorGetKeywords(this._handle);
|
|
93
|
+
}
|
|
94
|
+
setKeywords(keywords) {
|
|
95
|
+
this._throwIfClosed();
|
|
96
|
+
native.editorSetKeywords(this._handle, keywords);
|
|
97
|
+
}
|
|
98
|
+
getProducer() {
|
|
99
|
+
this._throwIfClosed();
|
|
100
|
+
return native.editorGetProducer(this._handle);
|
|
101
|
+
}
|
|
102
|
+
setProducer(producer) {
|
|
103
|
+
this._throwIfClosed();
|
|
104
|
+
native.editorSetProducer(this._handle, producer);
|
|
105
|
+
}
|
|
106
|
+
getCreationDate() {
|
|
107
|
+
this._throwIfClosed();
|
|
108
|
+
return native.editorGetCreationDate(this._handle);
|
|
109
|
+
}
|
|
110
|
+
setCreationDate(date) {
|
|
111
|
+
this._throwIfClosed();
|
|
112
|
+
native.editorSetCreationDate(this._handle, date);
|
|
113
|
+
}
|
|
114
|
+
// ----- page mutations ---------------------------------------------
|
|
115
|
+
/** Delete the page at `pageIndex` (zero-based). */
|
|
116
|
+
deletePage(pageIndex) {
|
|
117
|
+
this._throwIfClosed();
|
|
118
|
+
native.editorDeletePage(this._handle, pageIndex);
|
|
119
|
+
}
|
|
120
|
+
/** Move a page. Indices refer to positions before the move. */
|
|
121
|
+
movePage(fromIndex, toIndex) {
|
|
122
|
+
this._throwIfClosed();
|
|
123
|
+
native.editorMovePage(this._handle, fromIndex, toIndex);
|
|
124
|
+
}
|
|
125
|
+
/** Set rotation on a page (0/90/180/270). */
|
|
126
|
+
setPageRotation(pageIndex, degrees) {
|
|
127
|
+
this._throwIfClosed();
|
|
128
|
+
native.editorSetPageRotation(this._handle, pageIndex, degrees);
|
|
129
|
+
}
|
|
130
|
+
// ----- document-level mutations -----------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Append every page of another PDF to the end of this document.
|
|
133
|
+
*/
|
|
134
|
+
mergeFrom(sourcePath) {
|
|
135
|
+
this._throwIfClosed();
|
|
136
|
+
if (typeof sourcePath !== 'string' || sourcePath.length === 0) {
|
|
137
|
+
throw new TypeError('sourcePath must be a non-empty string');
|
|
138
|
+
}
|
|
139
|
+
native.editorMergeFrom(this._handle, sourcePath);
|
|
140
|
+
}
|
|
141
|
+
/** Flatten form fields across the entire document. */
|
|
142
|
+
flattenForms() {
|
|
143
|
+
this._throwIfClosed();
|
|
144
|
+
native.editorFlattenForms(this._handle);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Return warnings collected during the last form-flattening save.
|
|
148
|
+
* Each entry names a widget field that had no `/AP` appearance stream;
|
|
149
|
+
* flattening it produces a blank rectangle.
|
|
150
|
+
*/
|
|
151
|
+
flattenWarnings() {
|
|
152
|
+
this._throwIfClosed();
|
|
153
|
+
return native.editorFlattenWarnings(this._handle);
|
|
154
|
+
}
|
|
155
|
+
/** Flatten annotations. If `pageIndex` is omitted, flattens all pages. */
|
|
156
|
+
flattenAnnotations(pageIndex) {
|
|
157
|
+
this._throwIfClosed();
|
|
158
|
+
if (pageIndex === undefined) {
|
|
159
|
+
native.editorFlattenAnnotations(this._handle);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
native.editorFlattenAnnotations(this._handle, pageIndex);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Set a form field value by fully-qualified field name. */
|
|
166
|
+
setFormFieldValue(fieldName, value) {
|
|
167
|
+
this._throwIfClosed();
|
|
168
|
+
native.editorSetFormFieldValue(this._handle, fieldName, value);
|
|
169
|
+
}
|
|
170
|
+
/** Import an FDF file (bytes) into the document's form. */
|
|
171
|
+
importFdfBytes(fdf) {
|
|
172
|
+
this._throwIfClosed();
|
|
173
|
+
native.editorImportFdfBytes(this._handle, fdf);
|
|
174
|
+
}
|
|
175
|
+
/** Import an XFDF file (bytes) into the document's form. */
|
|
176
|
+
importXfdfBytes(xfdf) {
|
|
177
|
+
this._throwIfClosed();
|
|
178
|
+
native.editorImportXfdfBytes(this._handle, xfdf);
|
|
179
|
+
}
|
|
180
|
+
// ----- byte-level merge / embed -----------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* Append every page of another PDF (supplied as bytes) to this document.
|
|
183
|
+
* Returns the number of pages added.
|
|
184
|
+
*/
|
|
185
|
+
mergeFromBytes(data) {
|
|
186
|
+
this._throwIfClosed();
|
|
187
|
+
return native.editorMergeFromBytes(this._handle, data);
|
|
188
|
+
}
|
|
189
|
+
/** Embed a file attachment into the document. */
|
|
190
|
+
embedFile(name, data) {
|
|
191
|
+
this._throwIfClosed();
|
|
192
|
+
native.editorEmbedFile(this._handle, name, data);
|
|
193
|
+
}
|
|
194
|
+
// ----- redactions -------------------------------------------------
|
|
195
|
+
/** Burn in redaction annotations on a single page (zero-based). */
|
|
196
|
+
applyPageRedactions(pageIndex) {
|
|
197
|
+
this._throwIfClosed();
|
|
198
|
+
native.editorApplyPageRedactions(this._handle, pageIndex);
|
|
199
|
+
}
|
|
200
|
+
/** Burn in all pending redaction annotations across the document. */
|
|
201
|
+
applyAllRedactions() {
|
|
202
|
+
this._throwIfClosed();
|
|
203
|
+
native.editorApplyAllRedactions(this._handle);
|
|
204
|
+
}
|
|
205
|
+
// ----- rotation (additive) ----------------------------------------
|
|
206
|
+
/** Rotate all pages by `degrees` (additive). */
|
|
207
|
+
rotateAllPages(degrees) {
|
|
208
|
+
this._throwIfClosed();
|
|
209
|
+
native.editorRotateAllPages(this._handle, degrees);
|
|
210
|
+
}
|
|
211
|
+
/** Rotate a single page by `degrees` (additive). */
|
|
212
|
+
rotatePageBy(pageIndex, degrees) {
|
|
213
|
+
this._throwIfClosed();
|
|
214
|
+
native.editorRotatePageBy(this._handle, pageIndex, degrees);
|
|
215
|
+
}
|
|
216
|
+
// ----- page boxes -------------------------------------------------
|
|
217
|
+
/** Get the MediaBox of a page as `{x, y, width, height}`. */
|
|
218
|
+
getPageMediaBox(pageIndex) {
|
|
219
|
+
this._throwIfClosed();
|
|
220
|
+
return native.editorGetPageMediaBox(this._handle, pageIndex);
|
|
221
|
+
}
|
|
222
|
+
/** Set the MediaBox of a page. */
|
|
223
|
+
setPageMediaBox(pageIndex, x, y, width, height) {
|
|
224
|
+
this._throwIfClosed();
|
|
225
|
+
native.editorSetPageMediaBox(this._handle, pageIndex, x, y, width, height);
|
|
226
|
+
}
|
|
227
|
+
/** Get the CropBox of a page. Returns `{x:0,y:0,width:0,height:0}` if none set. */
|
|
228
|
+
getPageCropBox(pageIndex) {
|
|
229
|
+
this._throwIfClosed();
|
|
230
|
+
return native.editorGetPageCropBox(this._handle, pageIndex);
|
|
231
|
+
}
|
|
232
|
+
/** Set the CropBox of a page. */
|
|
233
|
+
setPageCropBox(pageIndex, x, y, width, height) {
|
|
234
|
+
this._throwIfClosed();
|
|
235
|
+
native.editorSetPageCropBox(this._handle, pageIndex, x, y, width, height);
|
|
236
|
+
}
|
|
237
|
+
// ----- erase regions ----------------------------------------------
|
|
238
|
+
/**
|
|
239
|
+
* Erase rectangular regions on a page.
|
|
240
|
+
* `rects` is an array of `[x, y, w, h]` tuples.
|
|
241
|
+
*/
|
|
242
|
+
eraseRegions(pageIndex, rects) {
|
|
243
|
+
this._throwIfClosed();
|
|
244
|
+
native.editorEraseRegions(this._handle, pageIndex, rects);
|
|
245
|
+
}
|
|
246
|
+
/** Clear all pending erase-region entries for a page. */
|
|
247
|
+
clearEraseRegions(pageIndex) {
|
|
248
|
+
this._throwIfClosed();
|
|
249
|
+
native.editorClearEraseRegions(this._handle, pageIndex);
|
|
250
|
+
}
|
|
251
|
+
// ----- form flattening on single page ------------------------------
|
|
252
|
+
/** Flatten form fields on a single page. */
|
|
253
|
+
flattenFormsOnPage(pageIndex) {
|
|
254
|
+
this._throwIfClosed();
|
|
255
|
+
native.editorFlattenFormsOnPage(this._handle, pageIndex);
|
|
256
|
+
}
|
|
257
|
+
// ----- page-mark state queries ------------------------------------
|
|
258
|
+
/** True if the page is marked for annotation-flatten. */
|
|
259
|
+
isPageMarkedForFlatten(pageIndex) {
|
|
260
|
+
this._throwIfClosed();
|
|
261
|
+
return native.editorIsPageMarkedForFlatten(this._handle, pageIndex);
|
|
262
|
+
}
|
|
263
|
+
/** Remove the flatten mark from a page. */
|
|
264
|
+
unmarkPageForFlatten(pageIndex) {
|
|
265
|
+
this._throwIfClosed();
|
|
266
|
+
native.editorUnmarkPageForFlatten(this._handle, pageIndex);
|
|
267
|
+
}
|
|
268
|
+
/** True if the page is marked for redaction. */
|
|
269
|
+
isPageMarkedForRedaction(pageIndex) {
|
|
270
|
+
this._throwIfClosed();
|
|
271
|
+
return native.editorIsPageMarkedForRedaction(this._handle, pageIndex);
|
|
272
|
+
}
|
|
273
|
+
/** Remove the redaction mark from a page. */
|
|
274
|
+
unmarkPageForRedaction(pageIndex) {
|
|
275
|
+
this._throwIfClosed();
|
|
276
|
+
native.editorUnmarkPageForRedaction(this._handle, pageIndex);
|
|
277
|
+
}
|
|
278
|
+
// ----- save paths -------------------------------------------------
|
|
279
|
+
/** Save the document to `path`. */
|
|
280
|
+
save(path) {
|
|
281
|
+
this._throwIfClosed();
|
|
282
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
283
|
+
throw new TypeError('path must be a non-empty string');
|
|
284
|
+
}
|
|
285
|
+
native.editorSave(this._handle, path);
|
|
286
|
+
}
|
|
287
|
+
/** Save with AES-256 encryption (user + owner passwords). */
|
|
288
|
+
saveEncrypted(path, userPassword, ownerPassword) {
|
|
289
|
+
this._throwIfClosed();
|
|
290
|
+
native.editorSaveEncrypted(this._handle, path, userPassword, ownerPassword);
|
|
291
|
+
}
|
|
292
|
+
/** Save the document to an in-memory Buffer. */
|
|
293
|
+
saveToBytes() {
|
|
294
|
+
this._throwIfClosed();
|
|
295
|
+
return native.editorSaveToBytes(this._handle);
|
|
296
|
+
}
|
|
297
|
+
/** Save to an in-memory Buffer with explicit compression / GC / linearize flags. */
|
|
298
|
+
saveToBytesWithOptions(compress, garbageCollect, linearize) {
|
|
299
|
+
this._throwIfClosed();
|
|
300
|
+
return native.editorSaveToBytesWithOptions(this._handle, compress, garbageCollect, linearize);
|
|
301
|
+
}
|
|
302
|
+
// ----- lifecycle --------------------------------------------------
|
|
303
|
+
/** Release the native handle. Safe to call multiple times. */
|
|
304
|
+
close() {
|
|
305
|
+
if (!this._closed) {
|
|
306
|
+
if (native.editorFree) {
|
|
307
|
+
native.editorFree(this._handle);
|
|
308
|
+
}
|
|
309
|
+
this._closed = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export default DocumentEditor;
|
package/lib/errors.js
CHANGED
|
@@ -135,9 +135,7 @@ export class PdfException extends Error {
|
|
|
135
135
|
parts.push(`Context:\n${contextStr}`);
|
|
136
136
|
}
|
|
137
137
|
if (this.recovery.suggestions.length > 0) {
|
|
138
|
-
const suggestionsStr = this.recovery.suggestions
|
|
139
|
-
.map((s) => ` • ${s}`)
|
|
140
|
-
.join('\n');
|
|
138
|
+
const suggestionsStr = this.recovery.suggestions.map((s) => ` • ${s}`).join('\n');
|
|
141
139
|
parts.push(`Recovery Suggestions:\n${suggestionsStr}`);
|
|
142
140
|
}
|
|
143
141
|
if (this.recovery.alternativeApproach) {
|
|
@@ -931,7 +929,8 @@ export function mapFfiErrorCode(errorCode, message) {
|
|
|
931
929
|
case 11:
|
|
932
930
|
return new AccessibilityException(message ?? 'Accessibility error: Tagging, structure tree, or alt text operation failed');
|
|
933
931
|
case 12:
|
|
934
|
-
return new OptimizationException(message ??
|
|
932
|
+
return new OptimizationException(message ??
|
|
933
|
+
'Optimization error: Font subsetting, image downsampling, or deduplication failed');
|
|
935
934
|
default:
|
|
936
935
|
return new UnknownError(message ?? `Unknown error (code: ${errorCode})`);
|
|
937
936
|
}
|
|
@@ -560,7 +560,9 @@ export class FormFieldManager extends EventEmitter {
|
|
|
560
560
|
clearCachePattern(pattern) {
|
|
561
561
|
const regex = new RegExp(pattern);
|
|
562
562
|
const keysToDelete = Array.from(this.resultCache.keys()).filter((key) => regex.test(key));
|
|
563
|
-
keysToDelete.forEach((key) =>
|
|
563
|
+
keysToDelete.forEach((key) => {
|
|
564
|
+
this.resultCache.delete(key);
|
|
565
|
+
});
|
|
564
566
|
}
|
|
565
567
|
}
|
|
566
568
|
export default FormFieldManager;
|