modern-pdf-lib 0.14.0 → 0.15.0
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 +56 -11
- package/dist/bridge-C7U4E7St.mjs +103 -0
- package/dist/bridge-DUcJFVsk.cjs +132 -0
- package/dist/index.cjs +1737 -91
- package/dist/index.d.cts +878 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +878 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1719 -92
- package/dist/{libdeflateWasm-DlHgU5oy.mjs → libdeflateWasm-82loOtIV.mjs} +2 -2
- package/dist/{libdeflateWasm-OkNoqBnO.cjs → libdeflateWasm-Enus0G1k.cjs} +2 -2
- package/dist/{loader-CQfoGFp9.mjs → loader-1VJXLlMZ.mjs} +3 -2
- package/dist/{loader-_fqS-TmT.cjs → loader-CKlBOHma.cjs} +3 -2
- package/dist/{pngEmbed-OYyOe_W0.cjs → pngEmbed-10m4CfBU.cjs} +2 -2
- package/dist/{pngEmbed-DTOqgEUC.mjs → pngEmbed-gaJ9S2Dk.mjs} +2 -2
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Create, parse, fill, merge, sign, and manipulate PDF documents<br />in Node, Den
|
|
|
15
15
|
|
|
16
16
|
[](https://www.npmjs.com/package/modern-pdf-lib)
|
|
17
17
|
[](https://bundlephobia.com/package/modern-pdf-lib)
|
|
18
|
-
[](#)
|
|
19
19
|
[](#)
|
|
20
20
|
[](LICENSE)
|
|
21
21
|
|
|
@@ -68,6 +68,7 @@ const blob = await doc.saveAsBlob(); // Blob (browsers)
|
|
|
68
68
|
- TrueType & OpenType font embedding
|
|
69
69
|
- Automatic font subsetting
|
|
70
70
|
- JPEG / PNG image embedding
|
|
71
|
+
- Image optimization (JPEG recompression, dedup, grayscale)
|
|
71
72
|
- RGB, CMYK, grayscale colors
|
|
72
73
|
- Linear & radial gradients, tiling patterns
|
|
73
74
|
- Text layout (multiline, combed, auto-size)
|
|
@@ -90,7 +91,7 @@ const blob = await doc.saveAsBlob(); // Blob (browsers)
|
|
|
90
91
|
|
|
91
92
|
**Secure & Compliant**
|
|
92
93
|
- AES-256 / RC4 encryption & decryption
|
|
93
|
-
- Digital signatures (PKCS#7, timestamps)
|
|
94
|
+
- Digital signatures (PKCS#7, visible/invisible, timestamps)
|
|
94
95
|
- PDF/A-1b through PDF/A-3u validation
|
|
95
96
|
- Tagged PDF / PDF/UA accessibility
|
|
96
97
|
- Structure tree & marked content
|
|
@@ -107,6 +108,7 @@ const blob = await doc.saveAsBlob(); // Blob (browsers)
|
|
|
107
108
|
- Linearization (fast web view)
|
|
108
109
|
- 60+ low-level PDF operators
|
|
109
110
|
- Custom appearance providers
|
|
111
|
+
- CLI: `npx modern-pdf optimize`
|
|
110
112
|
|
|
111
113
|
</td>
|
|
112
114
|
</tr>
|
|
@@ -168,7 +170,7 @@ const blob = await doc.saveAsBlob(); // Blob (browsers)
|
|
|
168
170
|
<td align="center">Copy pages only</td></tr>
|
|
169
171
|
|
|
170
172
|
<tr><td><strong>Annotations</strong></td>
|
|
171
|
-
<td align="center">
|
|
173
|
+
<td align="center">18 types + appearances</td>
|
|
172
174
|
<td align="center">No</td></tr>
|
|
173
175
|
|
|
174
176
|
<tr><td><strong>Streaming output</strong></td>
|
|
@@ -195,8 +197,12 @@ const blob = await doc.saveAsBlob(); // Blob (browsers)
|
|
|
195
197
|
<td align="center">Yes</td>
|
|
196
198
|
<td align="center">No</td></tr>
|
|
197
199
|
|
|
200
|
+
<tr><td><strong>Image optimization</strong></td>
|
|
201
|
+
<td align="center">JPEG recompress, dedup, grayscale</td>
|
|
202
|
+
<td align="center">No</td></tr>
|
|
203
|
+
|
|
198
204
|
<tr><td><strong>WASM acceleration</strong></td>
|
|
199
|
-
<td align="center">Optional (compression, PNG, fonts, JBIG2)</td>
|
|
205
|
+
<td align="center">Optional (compression, PNG, fonts, JBIG2, JPEG)</td>
|
|
200
206
|
<td align="center">No</td></tr>
|
|
201
207
|
|
|
202
208
|
<tr><td><strong>Dependencies</strong></td>
|
|
@@ -322,10 +328,14 @@ const bytes = await doc.save({
|
|
|
322
328
|
```ts
|
|
323
329
|
import { signPdf, verifySignatures } from 'modern-pdf-lib';
|
|
324
330
|
|
|
325
|
-
const signed = await signPdf(pdfBytes, {
|
|
326
|
-
certificate:
|
|
327
|
-
privateKey:
|
|
331
|
+
const signed = await signPdf(pdfBytes, 'Signature1', {
|
|
332
|
+
certificate: certDer,
|
|
333
|
+
privateKey: keyDer,
|
|
328
334
|
reason: 'Approved',
|
|
335
|
+
appearance: { // optional visible signature
|
|
336
|
+
rect: [50, 50, 200, 80],
|
|
337
|
+
fontSize: 10,
|
|
338
|
+
},
|
|
329
339
|
});
|
|
330
340
|
|
|
331
341
|
const results = await verifySignatures(signed);
|
|
@@ -348,6 +358,38 @@ for (const item of items) {
|
|
|
348
358
|
```
|
|
349
359
|
</details>
|
|
350
360
|
|
|
361
|
+
<details>
|
|
362
|
+
<summary><strong>Image Optimization</strong> — batch compress, deduplicate, CLI</summary>
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { loadPdf, initWasm, optimizeAllImages, deduplicateImages } from 'modern-pdf-lib';
|
|
366
|
+
|
|
367
|
+
await initWasm({ jpeg: true });
|
|
368
|
+
|
|
369
|
+
const doc = await loadPdf(pdfBytes);
|
|
370
|
+
|
|
371
|
+
// Deduplicate identical images
|
|
372
|
+
const dedupReport = deduplicateImages(doc);
|
|
373
|
+
|
|
374
|
+
// Optimize all images (JPEG recompression)
|
|
375
|
+
const report = await optimizeAllImages(doc, {
|
|
376
|
+
quality: 75,
|
|
377
|
+
progressive: true,
|
|
378
|
+
autoGrayscale: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
console.log(`${report.optimizedImages}/${report.totalImages} images optimized`);
|
|
382
|
+
console.log(`Savings: ${report.savings.toFixed(1)}%`);
|
|
383
|
+
|
|
384
|
+
const optimized = await doc.save();
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**CLI:**
|
|
388
|
+
```sh
|
|
389
|
+
npx modern-pdf optimize report.pdf report-opt.pdf --quality 60 --grayscale --dedup -v
|
|
390
|
+
```
|
|
391
|
+
</details>
|
|
392
|
+
|
|
351
393
|
<details>
|
|
352
394
|
<summary><strong>PDF/A & Accessibility</strong></summary>
|
|
353
395
|
|
|
@@ -399,6 +441,7 @@ await initWasm({
|
|
|
399
441
|
deflate: true, // Faster compression
|
|
400
442
|
png: true, // Faster PNG decoding
|
|
401
443
|
fonts: true, // Faster font subsetting
|
|
444
|
+
jpeg: true, // JPEG encode/decode for image optimization
|
|
402
445
|
});
|
|
403
446
|
```
|
|
404
447
|
|
|
@@ -409,6 +452,7 @@ await initWasm({
|
|
|
409
452
|
| ttf | Font parsing & subsetting | ~3x |
|
|
410
453
|
| shaping | Complex script layout | ~10x |
|
|
411
454
|
| jbig2 | JBIG2 bilevel image decoding | ~3x |
|
|
455
|
+
| jpeg | JPEG encode/decode for image optimization | Required |
|
|
412
456
|
|
|
413
457
|
<br />
|
|
414
458
|
|
|
@@ -420,7 +464,7 @@ modern-pdf-lib/
|
|
|
420
464
|
core/ PDF document model, objects, writer, pages
|
|
421
465
|
parser/ PDF loading, text extraction, content streams
|
|
422
466
|
form/ AcroForm fields (7 types) + appearances
|
|
423
|
-
annotation/
|
|
467
|
+
annotation/ 18 annotation types + appearance generators
|
|
424
468
|
accessibility/ Structure tree, marked content, PDF/UA checker
|
|
425
469
|
compliance/ PDF/A validation & enforcement
|
|
426
470
|
signature/ PKCS#7 signatures, timestamps, verification
|
|
@@ -430,8 +474,9 @@ modern-pdf-lib/
|
|
|
430
474
|
layers/ Optional content groups (OCG)
|
|
431
475
|
outline/ Bookmarks / document outline
|
|
432
476
|
metadata/ XMP metadata, viewer preferences
|
|
433
|
-
wasm/ Rust crate sources (
|
|
434
|
-
|
|
477
|
+
wasm/ Rust crate sources (6 modules)
|
|
478
|
+
cli/ CLI tool (modern-pdf optimize)
|
|
479
|
+
tests/ 2,323 tests across 110 suites
|
|
435
480
|
docs/ VitePress documentation
|
|
436
481
|
```
|
|
437
482
|
|
|
@@ -443,7 +488,7 @@ modern-pdf-lib/
|
|
|
443
488
|
git clone https://github.com/ABCrimson/modern-pdf-lib.git
|
|
444
489
|
cd modern-pdf-lib
|
|
445
490
|
npm install
|
|
446
|
-
npm test # 2,
|
|
491
|
+
npm test # 2,323 tests
|
|
447
492
|
npm run typecheck # TypeScript 6.0 strict
|
|
448
493
|
npm run build # ESM + CJS + declarations
|
|
449
494
|
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/wasm/jpeg/bridge.ts
|
|
4
|
+
var bridge_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
decodeJpegWasm: () => decodeJpegWasm,
|
|
6
|
+
encodeJpegWasm: () => encodeJpegWasm,
|
|
7
|
+
initJpegWasm: () => initJpegWasm,
|
|
8
|
+
isJpegWasmReady: () => isJpegWasmReady
|
|
9
|
+
});
|
|
10
|
+
let wasmModule;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the JPEG WASM module.
|
|
13
|
+
*
|
|
14
|
+
* @param wasmSource - The WASM binary as `Uint8Array`, URL, `Response`,
|
|
15
|
+
* or a pre-built wasm-bindgen module. When omitted,
|
|
16
|
+
* the function uses the universal WASM loader.
|
|
17
|
+
*/
|
|
18
|
+
async function initJpegWasm(wasmSource) {
|
|
19
|
+
if (wasmModule) return;
|
|
20
|
+
try {
|
|
21
|
+
if (wasmSource && typeof wasmSource.encode_jpeg === "function") {
|
|
22
|
+
wasmModule = wasmSource;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { loadWasmModule: loadWasm } = await import("./loader-1VJXLlMZ.mjs");
|
|
26
|
+
const wasmBytes = await loadWasm("jpeg");
|
|
27
|
+
wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
|
|
28
|
+
} catch {
|
|
29
|
+
wasmModule = void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether the JPEG WASM module has been initialized.
|
|
34
|
+
*
|
|
35
|
+
* @returns `true` if {@link initJpegWasm} completed successfully.
|
|
36
|
+
*/
|
|
37
|
+
function isJpegWasmReady() {
|
|
38
|
+
return wasmModule !== void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Map a `ChromaSubsampling` string to the numeric code expected by WASM.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function chromaToCode(chroma) {
|
|
45
|
+
switch (chroma) {
|
|
46
|
+
case "4:4:4": return 0;
|
|
47
|
+
case "4:2:2": return 1;
|
|
48
|
+
default: return 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Encode raw pixel data to JPEG using the WASM encoder.
|
|
53
|
+
*
|
|
54
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
55
|
+
* @param width - Image width in pixels.
|
|
56
|
+
* @param height - Image height in pixels.
|
|
57
|
+
* @param channels - Number of channels: 1 (grayscale), 3 (RGB), or 4 (RGBA).
|
|
58
|
+
* @param quality - JPEG quality 1–100.
|
|
59
|
+
* @param progressive - Encode as progressive JPEG (default: false).
|
|
60
|
+
* @param chroma - Chroma subsampling mode (default: '4:2:0').
|
|
61
|
+
* @returns JPEG-encoded bytes, or `undefined` if WASM is not available.
|
|
62
|
+
*/
|
|
63
|
+
function encodeJpegWasm(pixels, width, height, channels, quality, progressive = false, chroma = "4:2:0") {
|
|
64
|
+
if (!wasmModule) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return wasmModule.encode_jpeg(pixels, width, height, channels, Math.max(1, Math.min(100, Math.round(quality))), progressive, chromaToCode(chroma));
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode JPEG bytes to raw pixel data using the WASM decoder.
|
|
73
|
+
*
|
|
74
|
+
* The WASM module returns a flat byte array with layout:
|
|
75
|
+
* `[width_u32_le, height_u32_le, channels_u8, ...pixels]`.
|
|
76
|
+
*
|
|
77
|
+
* @param jpegBytes - JPEG-encoded image data.
|
|
78
|
+
* @returns Decoded pixel data with metadata, or `undefined` if WASM is not
|
|
79
|
+
* available or decoding failed.
|
|
80
|
+
*/
|
|
81
|
+
function decodeJpegWasm(jpegBytes) {
|
|
82
|
+
if (!wasmModule) return void 0;
|
|
83
|
+
try {
|
|
84
|
+
const raw = wasmModule.decode_jpeg(jpegBytes);
|
|
85
|
+
if (raw.length < 9) return void 0;
|
|
86
|
+
const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
87
|
+
const width = view.getUint32(0, true);
|
|
88
|
+
const height = view.getUint32(4, true);
|
|
89
|
+
const channels = raw[8];
|
|
90
|
+
return {
|
|
91
|
+
pixels: raw.slice(9),
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
channels
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { isJpegWasmReady as a, initJpegWasm as i, decodeJpegWasm as n, encodeJpegWasm as r, bridge_exports as t };
|
|
103
|
+
//# sourceMappingURL=bridge-C7U4E7St.mjs.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-CKhH4XqG.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/wasm/jpeg/bridge.ts
|
|
4
|
+
var bridge_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
5
|
+
decodeJpegWasm: () => decodeJpegWasm,
|
|
6
|
+
encodeJpegWasm: () => encodeJpegWasm,
|
|
7
|
+
initJpegWasm: () => initJpegWasm,
|
|
8
|
+
isJpegWasmReady: () => isJpegWasmReady
|
|
9
|
+
});
|
|
10
|
+
let wasmModule;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the JPEG WASM module.
|
|
13
|
+
*
|
|
14
|
+
* @param wasmSource - The WASM binary as `Uint8Array`, URL, `Response`,
|
|
15
|
+
* or a pre-built wasm-bindgen module. When omitted,
|
|
16
|
+
* the function uses the universal WASM loader.
|
|
17
|
+
*/
|
|
18
|
+
async function initJpegWasm(wasmSource) {
|
|
19
|
+
if (wasmModule) return;
|
|
20
|
+
try {
|
|
21
|
+
if (wasmSource && typeof wasmSource.encode_jpeg === "function") {
|
|
22
|
+
wasmModule = wasmSource;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { loadWasmModule: loadWasm } = await Promise.resolve().then(() => require("./loader-CKlBOHma.cjs"));
|
|
26
|
+
const wasmBytes = await loadWasm("jpeg");
|
|
27
|
+
wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
|
|
28
|
+
} catch {
|
|
29
|
+
wasmModule = void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether the JPEG WASM module has been initialized.
|
|
34
|
+
*
|
|
35
|
+
* @returns `true` if {@link initJpegWasm} completed successfully.
|
|
36
|
+
*/
|
|
37
|
+
function isJpegWasmReady() {
|
|
38
|
+
return wasmModule !== void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Map a `ChromaSubsampling` string to the numeric code expected by WASM.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function chromaToCode(chroma) {
|
|
45
|
+
switch (chroma) {
|
|
46
|
+
case "4:4:4": return 0;
|
|
47
|
+
case "4:2:2": return 1;
|
|
48
|
+
default: return 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Encode raw pixel data to JPEG using the WASM encoder.
|
|
53
|
+
*
|
|
54
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
55
|
+
* @param width - Image width in pixels.
|
|
56
|
+
* @param height - Image height in pixels.
|
|
57
|
+
* @param channels - Number of channels: 1 (grayscale), 3 (RGB), or 4 (RGBA).
|
|
58
|
+
* @param quality - JPEG quality 1–100.
|
|
59
|
+
* @param progressive - Encode as progressive JPEG (default: false).
|
|
60
|
+
* @param chroma - Chroma subsampling mode (default: '4:2:0').
|
|
61
|
+
* @returns JPEG-encoded bytes, or `undefined` if WASM is not available.
|
|
62
|
+
*/
|
|
63
|
+
function encodeJpegWasm(pixels, width, height, channels, quality, progressive = false, chroma = "4:2:0") {
|
|
64
|
+
if (!wasmModule) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return wasmModule.encode_jpeg(pixels, width, height, channels, Math.max(1, Math.min(100, Math.round(quality))), progressive, chromaToCode(chroma));
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode JPEG bytes to raw pixel data using the WASM decoder.
|
|
73
|
+
*
|
|
74
|
+
* The WASM module returns a flat byte array with layout:
|
|
75
|
+
* `[width_u32_le, height_u32_le, channels_u8, ...pixels]`.
|
|
76
|
+
*
|
|
77
|
+
* @param jpegBytes - JPEG-encoded image data.
|
|
78
|
+
* @returns Decoded pixel data with metadata, or `undefined` if WASM is not
|
|
79
|
+
* available or decoding failed.
|
|
80
|
+
*/
|
|
81
|
+
function decodeJpegWasm(jpegBytes) {
|
|
82
|
+
if (!wasmModule) return void 0;
|
|
83
|
+
try {
|
|
84
|
+
const raw = wasmModule.decode_jpeg(jpegBytes);
|
|
85
|
+
if (raw.length < 9) return void 0;
|
|
86
|
+
const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
87
|
+
const width = view.getUint32(0, true);
|
|
88
|
+
const height = view.getUint32(4, true);
|
|
89
|
+
const channels = raw[8];
|
|
90
|
+
return {
|
|
91
|
+
pixels: raw.slice(9),
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
channels
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
Object.defineProperty(exports, 'bridge_exports', {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function () {
|
|
105
|
+
return bridge_exports;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
Object.defineProperty(exports, 'decodeJpegWasm', {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
get: function () {
|
|
111
|
+
return decodeJpegWasm;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(exports, 'encodeJpegWasm', {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () {
|
|
117
|
+
return encodeJpegWasm;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
Object.defineProperty(exports, 'initJpegWasm', {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
get: function () {
|
|
123
|
+
return initJpegWasm;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(exports, 'isJpegWasmReady', {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get: function () {
|
|
129
|
+
return isJpegWasmReady;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=bridge-DUcJFVsk.cjs.map
|