@jackgreen2018/pdf-engine 1.0.112 → 1.0.114
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/README.md +19 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/package.json +1 -1
- package/pkg/README.md +6 -6
- package/pkg/package.json +1 -1
- package/pkg/pdf_engine.d.ts +35 -0
- package/pkg/pdf_engine.js +1 -1
- package/pkg/pdf_engine_bg.js +53 -0
- package/pkg/pdf_engine_bg.wasm +0 -0
- package/pkg/pdf_engine_bg.wasm.d.ts +1 -0
- package/src/index.ts +24 -0
- package/src/lib.rs +47 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ A high-performance PDF processing library for Node.js and the browser. Built wit
|
|
|
17
17
|
- **PDF Encryption**: Encrypt PDFs with RC4 (V1/V2) or AES-128 (V4) using user/owner passwords
|
|
18
18
|
- **PDF Decryption**: Decrypt password-protected PDFs
|
|
19
19
|
- **Encrypted Detection**: Check if a PDF is encrypted with `isEncrypted()`
|
|
20
|
+
- **Stamp Image**: Overlay PNG/JPEG images onto PDF pages with `stampImage()`
|
|
20
21
|
- **Privacy**: All processing happens in the user's browser or local environment
|
|
21
22
|
- **Performance**: Rust-powered with WASM for maximum speed
|
|
22
23
|
|
|
@@ -73,6 +74,16 @@ const encrypted = await pdfEngine.encrypt(
|
|
|
73
74
|
// Check if encrypted
|
|
74
75
|
const isEnc = await pdfEngine.isEncrypted(new Uint8Array(arrayBuffer));
|
|
75
76
|
|
|
77
|
+
// Stamp an image onto a PDF page
|
|
78
|
+
const stampedPdf = await pdfEngine.stampImage(
|
|
79
|
+
new Uint8Array(arrayBuffer),
|
|
80
|
+
imageBuffer,
|
|
81
|
+
100, // x position
|
|
82
|
+
100, // y position
|
|
83
|
+
50, // width
|
|
84
|
+
50, // height
|
|
85
|
+
);
|
|
86
|
+
|
|
76
87
|
// Decrypt a password-protected PDF
|
|
77
88
|
const decrypted = await pdfEngine.decrypt(encrypted, "user-password");
|
|
78
89
|
```
|
|
@@ -130,16 +141,18 @@ npm run benchmark
|
|
|
130
141
|
|
|
131
142
|
| Operation | pdf-engine (ms) | pdf-lib (ms) |
|
|
132
143
|
|-----------|-----------------|--------------|
|
|
133
|
-
| Page Count | 0.
|
|
134
|
-
| Text Extraction | 0.
|
|
135
|
-
| Merge (2 files) | 0.
|
|
136
|
-
| Split (pages [0,1]) | 0.
|
|
144
|
+
| Page Count | 0.72 | 1.59 |
|
|
145
|
+
| Text Extraction | 0.23 | — |
|
|
146
|
+
| Merge (2 files) | 0.80 | 4.06 |
|
|
147
|
+
| Split (pages [0,1]) | 0.24 | 1.77 |
|
|
137
148
|
| Encrypt (V4 AES-128) | 0.79 | — |
|
|
138
|
-
| Decrypt | 0.
|
|
149
|
+
| Decrypt | 0.32 | — |
|
|
150
|
+
| Stamp Image (1x1 PNG) | 1.24 | — |
|
|
139
151
|
|
|
140
152
|
*extractText correctness: PASS — gated via `tests/fixtures/text-fixture.pdf` (FlateDecode-compressed, real-world content stream). v1.0.30 fixes extractText to return actual text instead of "No text extracted."*
|
|
153
|
+
*stampImage: PASS — v1.0.114 fixes empty-page stamping (pages without /Contents now initialize empty content stream before image insertion).*
|
|
141
154
|
|
|
142
|
-
*Run 2026-08-
|
|
155
|
+
*Run 2026-08-04 (v1.0.114); raw JSON at benchmark-results.json.*
|
|
143
156
|
|
|
144
157
|
## License
|
|
145
158
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function split(buffer: Uint8Array, pages: number[]): Promise<Uint
|
|
|
6
6
|
export declare function isEncrypted(buffer: Uint8Array): Promise<boolean>;
|
|
7
7
|
export declare function encrypt(buffer: Uint8Array, userPassword: string, ownerPassword: string, version?: number): Promise<Uint8Array>;
|
|
8
8
|
export declare function decrypt(buffer: Uint8Array, password: string): Promise<Uint8Array>;
|
|
9
|
+
export declare function stampImage(buffer: Uint8Array, imageBuffer: Uint8Array, x: number, y: number, width: number, height: number): Promise<Uint8Array>;
|
|
9
10
|
export declare class PdfEngineImpl {
|
|
10
11
|
constructor();
|
|
11
12
|
private initialized;
|
|
@@ -17,6 +18,7 @@ export declare class PdfEngineImpl {
|
|
|
17
18
|
isEncrypted(buffer: Uint8Array): Promise<boolean>;
|
|
18
19
|
encrypt(buffer: Uint8Array, userPassword: string, ownerPassword: string, version?: number): Promise<Uint8Array>;
|
|
19
20
|
decrypt(buffer: Uint8Array, password: string): Promise<Uint8Array>;
|
|
21
|
+
stampImage(buffer: Uint8Array, imageBuffer: Uint8Array, x: number, y: number, width: number, height: number): Promise<Uint8Array>;
|
|
20
22
|
}
|
|
21
23
|
declare const pdfEngine: PdfEngineImpl;
|
|
22
24
|
export default pdfEngine;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED,wBAAsB,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAGzE;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGtF;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,wBAAsB,OAAO,CACzB,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,SAAI,GACZ,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGvF;AAED,qBAAa,aAAa;IACtB,cAEC;IACD,OAAO,CAAC,WAAW,CAAS;IAEtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAK1B;IAEK,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtD;IAEK,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEK,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAGzD;IAEK,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGtE;IAEK,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtD;IAEK,OAAO,CACT,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,SAAI,GACZ,OAAO,CAAC,UAAU,CAAC,CAGrB;IAEK,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGvE;CACJ;AAED,QAAA,MAAM,SAAS,eAAsB,CAAC;eACvB,SAAS"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED,wBAAsB,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAGzE;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGtF;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,wBAAsB,OAAO,CACzB,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,SAAI,GACZ,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGvF;AAED,wBAAsB,UAAU,CAC5B,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,UAAU,EACvB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED,qBAAa,aAAa;IACtB,cAEC;IACD,OAAO,CAAC,WAAW,CAAS;IAEtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAK1B;IAEK,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtD;IAEK,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEK,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAGzD;IAEK,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGtE;IAEK,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtD;IAEK,OAAO,CACT,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,SAAI,GACZ,OAAO,CAAC,UAAU,CAAC,CAGrB;IAEK,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGvE;IAEK,UAAU,CACZ,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,UAAU,EACvB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAGrB;CACJ;AAED,QAAA,MAAM,SAAS,eAAsB,CAAC;eACvB,SAAS"}
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,10 @@ export async function decrypt(buffer, password) {
|
|
|
29
29
|
const result = await wasm.decrypt(buffer, password);
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
|
+
export async function stampImage(buffer, imageBuffer, x, y, width, height) {
|
|
33
|
+
const result = await wasm.stamp_image(buffer, imageBuffer, x, y, width, height);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
32
36
|
export class PdfEngineImpl {
|
|
33
37
|
constructor() {
|
|
34
38
|
this.initialized = false;
|
|
@@ -68,6 +72,10 @@ export class PdfEngineImpl {
|
|
|
68
72
|
await this.init();
|
|
69
73
|
return await decrypt(buffer, password);
|
|
70
74
|
}
|
|
75
|
+
async stampImage(buffer, imageBuffer, x, y, width, height) {
|
|
76
|
+
await this.init();
|
|
77
|
+
return await stampImage(buffer, imageBuffer, x, y, width, height);
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
const pdfEngine = new PdfEngineImpl();
|
|
73
81
|
export default pdfEngine;
|
package/package.json
CHANGED
package/pkg/README.md
CHANGED
|
@@ -130,16 +130,16 @@ npm run benchmark
|
|
|
130
130
|
|
|
131
131
|
| Operation | pdf-engine (ms) | pdf-lib (ms) |
|
|
132
132
|
|-----------|-----------------|--------------|
|
|
133
|
-
| Page Count | 0.
|
|
134
|
-
| Text Extraction | 0.
|
|
135
|
-
| Merge (2 files) |
|
|
136
|
-
| Split (pages [0,1]) | 0.
|
|
137
|
-
| Encrypt (V4 AES-128) | 0.
|
|
133
|
+
| Page Count | 0.80 | 1.60 |
|
|
134
|
+
| Text Extraction | 0.47 | — |
|
|
135
|
+
| Merge (2 files) | 1.06 | 3.92 |
|
|
136
|
+
| Split (pages [0,1]) | 0.20 | 1.69 |
|
|
137
|
+
| Encrypt (V4 AES-128) | 0.81 | — |
|
|
138
138
|
| Decrypt | 0.35 | — |
|
|
139
139
|
|
|
140
140
|
*extractText correctness: PASS — gated via `tests/fixtures/text-fixture.pdf` (FlateDecode-compressed, real-world content stream). v1.0.30 fixes extractText to return actual text instead of "No text extracted."*
|
|
141
141
|
|
|
142
|
-
*Run 2026-08-03 (v1.0.
|
|
142
|
+
*Run 2026-08-03 (v1.0.113); raw JSON at benchmark-results.json.*
|
|
143
143
|
|
|
144
144
|
## License
|
|
145
145
|
|
package/pkg/package.json
CHANGED
package/pkg/pdf_engine.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
/* tslint:disable */
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Chroma subsampling format
|
|
6
|
+
*/
|
|
7
|
+
export enum ChromaSampling {
|
|
8
|
+
/**
|
|
9
|
+
* Both vertically and horizontally subsampled.
|
|
10
|
+
*/
|
|
11
|
+
Cs420 = 0,
|
|
12
|
+
/**
|
|
13
|
+
* Horizontally subsampled.
|
|
14
|
+
*/
|
|
15
|
+
Cs422 = 1,
|
|
16
|
+
/**
|
|
17
|
+
* Not subsampled.
|
|
18
|
+
*/
|
|
19
|
+
Cs444 = 2,
|
|
20
|
+
/**
|
|
21
|
+
* Monochrome.
|
|
22
|
+
*/
|
|
23
|
+
Cs400 = 3,
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
export function decrypt(buffer: Uint8Array, password: string): Uint8Array;
|
|
5
27
|
|
|
6
28
|
export function encrypt(buffer: Uint8Array, user_password: string, owner_password: string, version: number): Uint8Array;
|
|
@@ -18,3 +40,16 @@ export function is_encrypted(buffer: Uint8Array): boolean;
|
|
|
18
40
|
export function merge(pdf_buffers: Array<any>): Uint8Array;
|
|
19
41
|
|
|
20
42
|
export function split(buffer: Uint8Array, pages: Array<any>): Array<any>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Stamp (composite) an image onto a single-page PDF.
|
|
46
|
+
*
|
|
47
|
+
* `x`, `y` are the bottom-left position in PDF user-space coordinates
|
|
48
|
+
* (points, origin at bottom-left by default). `width` and `height` are in
|
|
49
|
+
* the same units. The image is stretched to fill the given rectangle.
|
|
50
|
+
*
|
|
51
|
+
* ponytail: uses lopdf::xobject::image_from (embed_image feature) which
|
|
52
|
+
* supports PNG and JPEG via the image crate; the image crate is
|
|
53
|
+
* wasm-compatible with the png/jpeg features enabled.
|
|
54
|
+
*/
|
|
55
|
+
export function stamp_image(buffer: Uint8Array, image_buffer: Uint8Array, x: number, y: number, width: number, height: number): Uint8Array;
|
package/pkg/pdf_engine.js
CHANGED
|
@@ -5,5 +5,5 @@ import { __wbg_set_wasm } from "./pdf_engine_bg.js";
|
|
|
5
5
|
__wbg_set_wasm(wasm);
|
|
6
6
|
wasm.__wbindgen_start();
|
|
7
7
|
export {
|
|
8
|
-
decrypt, encrypt, extract_text, get_page_count, get_pdf_info, init, is_encrypted, merge, split
|
|
8
|
+
ChromaSampling, decrypt, encrypt, extract_text, get_page_count, get_pdf_info, init, is_encrypted, merge, split, stamp_image
|
|
9
9
|
} from "./pdf_engine_bg.js";
|
package/pkg/pdf_engine_bg.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chroma subsampling format
|
|
3
|
+
* @enum {0 | 1 | 2 | 3}
|
|
4
|
+
*/
|
|
5
|
+
export const ChromaSampling = Object.freeze({
|
|
6
|
+
/**
|
|
7
|
+
* Both vertically and horizontally subsampled.
|
|
8
|
+
*/
|
|
9
|
+
Cs420: 0, "0": "Cs420",
|
|
10
|
+
/**
|
|
11
|
+
* Horizontally subsampled.
|
|
12
|
+
*/
|
|
13
|
+
Cs422: 1, "1": "Cs422",
|
|
14
|
+
/**
|
|
15
|
+
* Not subsampled.
|
|
16
|
+
*/
|
|
17
|
+
Cs444: 2, "2": "Cs444",
|
|
18
|
+
/**
|
|
19
|
+
* Monochrome.
|
|
20
|
+
*/
|
|
21
|
+
Cs400: 3, "3": "Cs400",
|
|
22
|
+
});
|
|
23
|
+
|
|
1
24
|
/**
|
|
2
25
|
* @param {Uint8Array} buffer
|
|
3
26
|
* @param {string} password
|
|
@@ -147,6 +170,36 @@ export function split(buffer, pages) {
|
|
|
147
170
|
}
|
|
148
171
|
return takeFromExternrefTable0(ret[0]);
|
|
149
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Stamp (composite) an image onto a single-page PDF.
|
|
176
|
+
*
|
|
177
|
+
* `x`, `y` are the bottom-left position in PDF user-space coordinates
|
|
178
|
+
* (points, origin at bottom-left by default). `width` and `height` are in
|
|
179
|
+
* the same units. The image is stretched to fill the given rectangle.
|
|
180
|
+
*
|
|
181
|
+
* ponytail: uses lopdf::xobject::image_from (embed_image feature) which
|
|
182
|
+
* supports PNG and JPEG via the image crate; the image crate is
|
|
183
|
+
* wasm-compatible with the png/jpeg features enabled.
|
|
184
|
+
* @param {Uint8Array} buffer
|
|
185
|
+
* @param {Uint8Array} image_buffer
|
|
186
|
+
* @param {number} x
|
|
187
|
+
* @param {number} y
|
|
188
|
+
* @param {number} width
|
|
189
|
+
* @param {number} height
|
|
190
|
+
* @returns {Uint8Array}
|
|
191
|
+
*/
|
|
192
|
+
export function stamp_image(buffer, image_buffer, x, y, width, height) {
|
|
193
|
+
const ptr0 = passArray8ToWasm0(buffer, wasm.__wbindgen_malloc);
|
|
194
|
+
const len0 = WASM_VECTOR_LEN;
|
|
195
|
+
const ptr1 = passArray8ToWasm0(image_buffer, wasm.__wbindgen_malloc);
|
|
196
|
+
const len1 = WASM_VECTOR_LEN;
|
|
197
|
+
const ret = wasm.stamp_image(ptr0, len0, ptr1, len1, x, y, width, height);
|
|
198
|
+
if (ret[2]) {
|
|
199
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
200
|
+
}
|
|
201
|
+
return takeFromExternrefTable0(ret[0]);
|
|
202
|
+
}
|
|
150
203
|
export function __wbg___wbindgen_number_get_394265ed1e1b84ee(arg0, arg1) {
|
|
151
204
|
const obj = arg1;
|
|
152
205
|
const ret = typeof(obj) === 'number' ? obj : undefined;
|
package/pkg/pdf_engine_bg.wasm
CHANGED
|
Binary file
|
|
@@ -10,6 +10,7 @@ export const init: () => [number, number];
|
|
|
10
10
|
export const is_encrypted: (a: number, b: number) => [number, number, number];
|
|
11
11
|
export const merge: (a: any) => [number, number, number];
|
|
12
12
|
export const split: (a: number, b: number, c: any) => [number, number, number];
|
|
13
|
+
export const stamp_image: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number];
|
|
13
14
|
export const __wbindgen_exn_store: (a: number) => void;
|
|
14
15
|
export const __externref_table_alloc: () => number;
|
|
15
16
|
export const __wbindgen_externrefs: WebAssembly.Table;
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,18 @@ export async function decrypt(buffer: Uint8Array, password: string): Promise<Uin
|
|
|
44
44
|
return result;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export async function stampImage(
|
|
48
|
+
buffer: Uint8Array,
|
|
49
|
+
imageBuffer: Uint8Array,
|
|
50
|
+
x: number,
|
|
51
|
+
y: number,
|
|
52
|
+
width: number,
|
|
53
|
+
height: number,
|
|
54
|
+
): Promise<Uint8Array> {
|
|
55
|
+
const result = await wasm.stamp_image(buffer, imageBuffer, x, y, width, height);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
export class PdfEngineImpl {
|
|
48
60
|
constructor() {
|
|
49
61
|
this.initialized = false;
|
|
@@ -96,6 +108,18 @@ export class PdfEngineImpl {
|
|
|
96
108
|
await this.init();
|
|
97
109
|
return await decrypt(buffer, password);
|
|
98
110
|
}
|
|
111
|
+
|
|
112
|
+
async stampImage(
|
|
113
|
+
buffer: Uint8Array,
|
|
114
|
+
imageBuffer: Uint8Array,
|
|
115
|
+
x: number,
|
|
116
|
+
y: number,
|
|
117
|
+
width: number,
|
|
118
|
+
height: number,
|
|
119
|
+
): Promise<Uint8Array> {
|
|
120
|
+
await this.init();
|
|
121
|
+
return await stampImage(buffer, imageBuffer, x, y, width, height);
|
|
122
|
+
}
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
const pdfEngine = new PdfEngineImpl();
|
package/src/lib.rs
CHANGED
|
@@ -376,6 +376,53 @@ pub fn decrypt(buffer: &[u8], password: &str) -> Result<Uint8Array, JsValue> {
|
|
|
376
376
|
Ok(Uint8Array::new_from_slice(&buf))
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
/// Stamp (composite) an image onto a single-page PDF.
|
|
380
|
+
///
|
|
381
|
+
/// `x`, `y` are the bottom-left position in PDF user-space coordinates
|
|
382
|
+
/// (points, origin at bottom-left by default). `width` and `height` are in
|
|
383
|
+
/// the same units. The image is stretched to fill the given rectangle.
|
|
384
|
+
///
|
|
385
|
+
/// ponytail: uses lopdf::xobject::image_from (embed_image feature) which
|
|
386
|
+
/// supports PNG and JPEG via the image crate; the image crate is
|
|
387
|
+
/// wasm-compatible with the png/jpeg features enabled.
|
|
388
|
+
#[wasm_bindgen]
|
|
389
|
+
pub fn stamp_image(
|
|
390
|
+
buffer: &[u8],
|
|
391
|
+
image_buffer: &[u8],
|
|
392
|
+
x: f64,
|
|
393
|
+
y: f64,
|
|
394
|
+
width: f64,
|
|
395
|
+
height: f64,
|
|
396
|
+
) -> Result<Uint8Array, JsValue> {
|
|
397
|
+
let mut doc = Document::load_mem(buffer)
|
|
398
|
+
.map_err(|e| JsValue::from_str(&format!("PDF load error: {}", e)))?;
|
|
399
|
+
|
|
400
|
+
let pages = doc.get_pages();
|
|
401
|
+
if pages.is_empty() {
|
|
402
|
+
return Err(JsValue::from_str("PDF has no pages"));
|
|
403
|
+
}
|
|
404
|
+
let page_id = *pages.values().next().unwrap();
|
|
405
|
+
|
|
406
|
+
// ponytail: pdf-lib creates pages without a /Contents key; insert_image
|
|
407
|
+
// requires one. Add an empty stream if missing so the page can accept
|
|
408
|
+
// the image overlay.
|
|
409
|
+
if doc.get_page_content(page_id).is_empty() {
|
|
410
|
+
doc.add_page_contents(page_id, Vec::new())
|
|
411
|
+
.map_err(|e| JsValue::from_str(&format!("Page content init error: {}", e)))?;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let img_stream = lopdf::xobject::image_from(image_buffer.to_vec())
|
|
415
|
+
.map_err(|e| JsValue::from_str(&format!("Image decode error: {}", e)))?;
|
|
416
|
+
|
|
417
|
+
doc.insert_image(page_id, img_stream, (x as f32, y as f32), (width as f32, height as f32))
|
|
418
|
+
.map_err(|e| JsValue::from_str(&format!("Insert image error: {}", e)))?;
|
|
419
|
+
|
|
420
|
+
let mut buf = Vec::new();
|
|
421
|
+
doc.save_to(&mut buf)
|
|
422
|
+
.map_err(|e| JsValue::from_str(&format!("Save error: {}", e)))?;
|
|
423
|
+
Ok(Uint8Array::new_from_slice(&buf))
|
|
424
|
+
}
|
|
425
|
+
|
|
379
426
|
#[cfg(test)]
|
|
380
427
|
mod tests {
|
|
381
428
|
use super::*;
|