modern-pdf-lib 0.14.1 → 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 CHANGED
@@ -15,7 +15,7 @@ Create, parse, fill, merge, sign, and manipulate PDF documents<br />in Node, Den
15
15
 
16
16
  [![npm version](https://img.shields.io/npm/v/modern-pdf-lib?style=flat-square&color=cb3837)](https://www.npmjs.com/package/modern-pdf-lib)
17
17
  [![bundle size](https://img.shields.io/badge/gzip-36kb_core-blue?style=flat-square)](https://bundlephobia.com/package/modern-pdf-lib)
18
- [![tests](https://img.shields.io/badge/tests-2%2C243_passing-brightgreen?style=flat-square)](#)
18
+ [![tests](https://img.shields.io/badge/tests-2%2C323_passing-brightgreen?style=flat-square)](#)
19
19
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-3178c6?style=flat-square&logo=typescript&logoColor=white)](#)
20
20
  [![License: MIT](https://img.shields.io/badge/license-MIT-yellow?style=flat-square)](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)
@@ -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>
@@ -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>
@@ -352,6 +358,38 @@ for (const item of items) {
352
358
  ```
353
359
  </details>
354
360
 
361
+ <details>
362
+ <summary><strong>Image Optimization</strong> &mdash; 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
+
355
393
  <details>
356
394
  <summary><strong>PDF/A & Accessibility</strong></summary>
357
395
 
@@ -403,6 +441,7 @@ await initWasm({
403
441
  deflate: true, // Faster compression
404
442
  png: true, // Faster PNG decoding
405
443
  fonts: true, // Faster font subsetting
444
+ jpeg: true, // JPEG encode/decode for image optimization
406
445
  });
407
446
  ```
408
447
 
@@ -413,6 +452,7 @@ await initWasm({
413
452
  | ttf | Font parsing & subsetting | ~3x |
414
453
  | shaping | Complex script layout | ~10x |
415
454
  | jbig2 | JBIG2 bilevel image decoding | ~3x |
455
+ | jpeg | JPEG encode/decode for image optimization | Required |
416
456
 
417
457
  <br />
418
458
 
@@ -434,8 +474,9 @@ modern-pdf-lib/
434
474
  layers/ Optional content groups (OCG)
435
475
  outline/ Bookmarks / document outline
436
476
  metadata/ XMP metadata, viewer preferences
437
- wasm/ Rust crate sources (5 modules)
438
- tests/ 2,243 tests across 103 suites
477
+ wasm/ Rust crate sources (6 modules)
478
+ cli/ CLI tool (modern-pdf optimize)
479
+ tests/ 2,323 tests across 110 suites
439
480
  docs/ VitePress documentation
440
481
  ```
441
482
 
@@ -447,7 +488,7 @@ modern-pdf-lib/
447
488
  git clone https://github.com/ABCrimson/modern-pdf-lib.git
448
489
  cd modern-pdf-lib
449
490
  npm install
450
- npm test # 2,243 tests
491
+ npm test # 2,323 tests
451
492
  npm run typecheck # TypeScript 6.0 strict
452
493
  npm run build # ESM + CJS + declarations
453
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