modern-pdf-lib 0.15.1 → 0.22.9

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.
Files changed (95) hide show
  1. package/README.md +119 -9
  2. package/dist/batchOptimize-Ba_pWw71.cjs +427 -0
  3. package/dist/batchOptimize-CxyY4fZe.mjs +392 -0
  4. package/dist/{bridge-DpzMOnHd.mjs → bridge-DTH5LMAK.mjs} +3 -3
  5. package/dist/{bridge-DN7BOHRW.cjs → bridge-DYCQzxF7.cjs} +2 -2
  6. package/dist/browser.cjs +621 -0
  7. package/dist/browser.d.cts +190 -0
  8. package/dist/browser.d.cts.map +1 -0
  9. package/dist/browser.d.mts +190 -0
  10. package/dist/browser.d.mts.map +1 -0
  11. package/dist/browser.mjs +212 -0
  12. package/dist/cli/index.cjs +40 -18
  13. package/dist/cli/index.mjs +40 -18
  14. package/dist/compressionAnalysis-B84FPXaQ.cjs +1525 -0
  15. package/dist/compressionAnalysis-BBv4BkQP.d.mts +261 -0
  16. package/dist/compressionAnalysis-BBv4BkQP.d.mts.map +1 -0
  17. package/dist/compressionAnalysis-ChkscEa1.mjs +1490 -0
  18. package/dist/compressionAnalysis-CtJ2X9l2.d.cts +261 -0
  19. package/dist/compressionAnalysis-CtJ2X9l2.d.cts.map +1 -0
  20. package/dist/create.cjs +35 -0
  21. package/dist/create.d.cts +3 -0
  22. package/dist/create.d.mts +3 -0
  23. package/dist/create.mjs +5 -0
  24. package/dist/{deduplicateImages-BfpjHY9b.mjs → deduplicateImages-CmTeo6Tx.mjs} +3 -3
  25. package/dist/{deduplicateImages-BtJ5tlrr.cjs → deduplicateImages-cKsnD6Ep.cjs} +2 -2
  26. package/dist/{fflateAdapter-D2mv_ttM.mjs → fflateAdapter-CBQpGTlx.mjs} +2 -2
  27. package/dist/{fflateAdapter-cT4YeY_h.cjs → fflateAdapter-LTAeAhaD.cjs} +1 -1
  28. package/dist/fieldAppearance-C8PoLFSc.d.mts +136 -0
  29. package/dist/fieldAppearance-C8PoLFSc.d.mts.map +1 -0
  30. package/dist/fieldAppearance-CdiGFG5e.d.cts +136 -0
  31. package/dist/fieldAppearance-CdiGFG5e.d.cts.map +1 -0
  32. package/dist/fontEmbed-Dsu9fo4U.d.mts +636 -0
  33. package/dist/fontEmbed-Dsu9fo4U.d.mts.map +1 -0
  34. package/dist/fontEmbed-LID6yG6g.d.cts +636 -0
  35. package/dist/fontEmbed-LID6yG6g.d.cts.map +1 -0
  36. package/dist/{fontSubset-BxsF9Tu5.cjs → fontSubset-5SLWMmEw.cjs} +1 -1
  37. package/dist/{fontSubset-ClyTXlhY.mjs → fontSubset-DWpduoY2.mjs} +2 -2
  38. package/dist/forms.cjs +13 -0
  39. package/dist/forms.d.cts +3 -0
  40. package/dist/forms.d.mts +3 -0
  41. package/dist/forms.mjs +3 -0
  42. package/dist/grayscaleDetect-C2m-eEXR.cjs +96 -0
  43. package/dist/grayscaleDetect-C6kFF3dk.mjs +84 -0
  44. package/dist/imageExtract-Dnk_Ssv7.mjs +155 -0
  45. package/dist/imageExtract-zEb1gnkb.cjs +166 -0
  46. package/dist/index-BtYOx5wh.d.mts +4904 -0
  47. package/dist/index-BtYOx5wh.d.mts.map +1 -0
  48. package/dist/index-bpktKzCA.d.cts +4904 -0
  49. package/dist/index-bpktKzCA.d.cts.map +1 -0
  50. package/dist/index.cjs +274 -20704
  51. package/dist/index.d.cts +7 -9151
  52. package/dist/index.d.mts +7 -9151
  53. package/dist/index.mjs +17 -20532
  54. package/dist/layout-CuAVk_Or.cjs +563 -0
  55. package/dist/layout-DgX_0jfK.mjs +438 -0
  56. package/dist/{libdeflateWasm-Cg7cWHOq.cjs → libdeflateWasm-BdiDEJOj.cjs} +2 -2
  57. package/dist/{libdeflateWasm-Cmxa-yiS.mjs → libdeflateWasm-rLppXytE.mjs} +3 -3
  58. package/dist/loader-3u6Tw5T-.mjs +328 -0
  59. package/dist/loader-I4zdkoWc.cjs +393 -0
  60. package/dist/parse.cjs +24 -0
  61. package/dist/parse.d.cts +4 -0
  62. package/dist/parse.d.mts +4 -0
  63. package/dist/parse.mjs +7 -0
  64. package/dist/{pdfCatalog-BcOL6QF-.cjs → pdfCatalog-CYy4NXEY.cjs} +2 -2
  65. package/dist/{pdfCatalog-CnJRovvm.mjs → pdfCatalog-IImGcMbR.mjs} +3 -3
  66. package/dist/pdfDocument-BFxHD_2u.mjs +13755 -0
  67. package/dist/pdfDocument-BSiQdNZq.d.cts +4640 -0
  68. package/dist/pdfDocument-BSiQdNZq.d.cts.map +1 -0
  69. package/dist/pdfDocument-i6U5fQ91.d.mts +4640 -0
  70. package/dist/pdfDocument-i6U5fQ91.d.mts.map +1 -0
  71. package/dist/pdfDocument-pmRXryVI.cjs +14180 -0
  72. package/dist/pdfForm-9gd40uz9.cjs +1796 -0
  73. package/dist/pdfForm-BiyNtYem.d.mts +905 -0
  74. package/dist/pdfForm-BiyNtYem.d.mts.map +1 -0
  75. package/dist/pdfForm-Cn-cVicP.mjs +1695 -0
  76. package/dist/pdfForm-SOXJ72LW.d.cts +905 -0
  77. package/dist/pdfForm-SOXJ72LW.d.cts.map +1 -0
  78. package/dist/{pdfObjects-BrU4Xd0V.cjs → pdfObjects-1veop1_d.cjs} +2 -2
  79. package/dist/{pdfObjects-DZZ2GPRW.mjs → pdfObjects-uEsWlfzU.mjs} +2 -2
  80. package/dist/{pdfPage-Dm5XC_g_.mjs → pdfPage-Cd8e7flb.mjs} +3024 -5
  81. package/dist/{pdfPage-Dz_SVKUS.cjs → pdfPage-Cd8jOJp6.cjs} +3046 -3
  82. package/dist/{pngEmbed-I1hU3Y6m.mjs → pngEmbed-BLj2zi-5.mjs} +3 -3
  83. package/dist/{pngEmbed-C6M1eX6b.cjs → pngEmbed-D4X4ZN-3.cjs} +2 -2
  84. package/dist/src-6L07EQsi.cjs +11852 -0
  85. package/dist/src-Dm4aaZ8q.mjs +11103 -0
  86. package/dist/{imageExtract-vjyQyFcT.mjs → streamDecode-Bj568Nc9.mjs} +1646 -188
  87. package/dist/{imageExtract-BC7TMY98.cjs → streamDecode-CvgErkFu.cjs} +1645 -199
  88. package/package.json +33 -1
  89. package/dist/batchOptimize-ClXizv19.mjs +0 -306
  90. package/dist/batchOptimize-DYQOX1-7.cjs +0 -329
  91. package/dist/index.d.cts.map +0 -1
  92. package/dist/index.d.mts.map +0 -1
  93. package/dist/loader-B6VIrZOJ.mjs +0 -164
  94. package/dist/loader-DdB5Xo5D.cjs +0 -166
  95. package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
@@ -0,0 +1,392 @@
1
+ import { Ka as __exportAll } from "./index-BtYOx5wh.d.mts";
2
+ import { i as PdfName, l as PdfStream, o as PdfNumber, r as PdfDict, t as PdfArray } from "./pdfObjects-uEsWlfzU.mjs";
3
+ import { n as extractImages, t as decodeImageStream } from "./imageExtract-Dnk_Ssv7.mjs";
4
+ import { n as isGrayscaleImage, t as convertToGrayscale } from "./grayscaleDetect-C6kFF3dk.mjs";
5
+
6
+ //#region src/assets/image/iccProfile.ts
7
+ /**
8
+ * @module assets/image/iccProfile
9
+ *
10
+ * ICC color profile extraction and preservation for PDF image XObjects.
11
+ *
12
+ * When images are recompressed during optimization, their ICC color
13
+ * profiles can be stripped, causing color shifts in print workflows.
14
+ * This module extracts ICC profiles from the original image's color
15
+ * space entry and re-embeds them after recompression, preserving
16
+ * color fidelity.
17
+ *
18
+ * No Buffer — uses Uint8Array exclusively.
19
+ */
20
+ /**
21
+ * Known ICC color space signatures (4 bytes at offset 16 in the profile header).
22
+ * @internal
23
+ */
24
+ const ICC_COLOR_SPACE_MAP = {
25
+ "RGB ": "RGB",
26
+ "CMYK": "CMYK",
27
+ "GRAY": "GRAY",
28
+ "Lab ": "Lab",
29
+ "XYZ ": "XYZ",
30
+ "Luv ": "Luv",
31
+ "YCbr": "YCbCr",
32
+ "Yxy ": "Yxy",
33
+ "HSV ": "HSV",
34
+ "HLS ": "HLS"
35
+ };
36
+ /**
37
+ * Read the color space signature from raw ICC profile data.
38
+ *
39
+ * The ICC profile header stores a 4-byte color space of data field
40
+ * at byte offset 16. This function reads and decodes that signature
41
+ * into a human-readable string.
42
+ *
43
+ * @param data - Raw ICC profile bytes.
44
+ * @returns The color space name (e.g. `'RGB'`, `'CMYK'`, `'GRAY'`),
45
+ * or `'Unknown'` if the data is too short or the signature
46
+ * is not recognized.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { parseIccColorSpace } from 'modern-pdf-lib';
51
+ *
52
+ * const colorSpace = parseIccColorSpace(iccProfileBytes);
53
+ * console.log(colorSpace); // 'RGB'
54
+ * ```
55
+ */
56
+ function parseIccColorSpace(data) {
57
+ if (data.length < 20) return "Unknown";
58
+ return ICC_COLOR_SPACE_MAP[String.fromCharCode(data[16], data[17], data[18], data[19])] ?? "Unknown";
59
+ }
60
+ /**
61
+ * Parse the human-readable description from an ICC profile's 'desc' tag.
62
+ *
63
+ * Searches the ICC tag table for a tag with signature `'desc'`
64
+ * (0x64657363) and reads the ASCII description string from it.
65
+ *
66
+ * The 'desc' tag (ICC v2) has the structure:
67
+ * - Bytes 0–3: type signature ('desc')
68
+ * - Bytes 4–7: reserved (0)
69
+ * - Bytes 8–11: ASCII description length (uint32 BE)
70
+ * - Bytes 12+: ASCII description string
71
+ *
72
+ * @param data - Raw ICC profile bytes.
73
+ * @returns The description string, or `undefined` if the tag is not
74
+ * found or cannot be parsed.
75
+ */
76
+ function parseIccDescription(data) {
77
+ if (data.length < 132) return void 0;
78
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
79
+ const tagCount = view.getUint32(128, false);
80
+ const tagTableStart = 132;
81
+ if (data.length < tagTableStart + tagCount * 12) return void 0;
82
+ for (let i = 0; i < tagCount; i++) {
83
+ const entryOffset = tagTableStart + i * 12;
84
+ if (view.getUint32(entryOffset, false) !== 1684370275) continue;
85
+ const tagOffset = view.getUint32(entryOffset + 4, false);
86
+ const tagSize = view.getUint32(entryOffset + 8, false);
87
+ if (tagOffset + tagSize > data.length) return void 0;
88
+ if (tagSize < 12) return void 0;
89
+ const descLen = view.getUint32(tagOffset + 8, false);
90
+ if (descLen === 0) return void 0;
91
+ const strEnd = Math.min(tagOffset + 12 + descLen - 1, tagOffset + tagSize);
92
+ if (strEnd <= tagOffset + 12) return void 0;
93
+ const chars = [];
94
+ for (let j = tagOffset + 12; j < strEnd; j++) {
95
+ const c = data[j];
96
+ if (c === 0) break;
97
+ chars.push(String.fromCharCode(c));
98
+ }
99
+ return chars.length > 0 ? chars.join("") : void 0;
100
+ }
101
+ }
102
+ /**
103
+ * Extract the ICC color profile from a PDF image XObject's `/ColorSpace`.
104
+ *
105
+ * Checks whether the image's `/ColorSpace` entry is an ICCBased array
106
+ * (i.e. `[/ICCBased <stream ref>]`), and if so, extracts the raw ICC
107
+ * profile bytes and metadata from the referenced stream.
108
+ *
109
+ * @param stream - The `PdfStream` for the image XObject.
110
+ * @param registry - The document's `PdfObjectRegistry` for resolving
111
+ * indirect references.
112
+ * @returns An `IccProfile` if the image uses an ICCBased color space,
113
+ * or `undefined` if no ICC profile is attached.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * import { extractIccProfile, extractImages, loadPdf } from 'modern-pdf-lib';
118
+ *
119
+ * const doc = await loadPdf(pdfBytes);
120
+ * const images = extractImages(doc);
121
+ *
122
+ * for (const img of images) {
123
+ * const profile = extractIccProfile(img.stream, doc.getRegistry());
124
+ * if (profile) {
125
+ * console.log(`ICC: ${profile.colorSpace}, ${profile.components} channels`);
126
+ * console.log(`Description: ${profile.description ?? 'none'}`);
127
+ * }
128
+ * }
129
+ * ```
130
+ */
131
+ function extractIccProfile(stream, registry) {
132
+ const csEntry = stream.dict.get("/ColorSpace");
133
+ if (!csEntry) return void 0;
134
+ let cs = csEntry;
135
+ if (cs.kind === "ref") {
136
+ const resolved = registry.resolve(cs);
137
+ if (!resolved) return void 0;
138
+ cs = resolved;
139
+ }
140
+ if (cs.kind !== "array") return void 0;
141
+ const arr = cs;
142
+ if (arr.items.length < 2) return void 0;
143
+ const first = arr.items[0];
144
+ if (first.kind !== "name") return void 0;
145
+ if (first.value !== "/ICCBased") return void 0;
146
+ let profileObj = arr.items[1];
147
+ if (profileObj.kind === "ref") {
148
+ const resolved = registry.resolve(profileObj);
149
+ if (!resolved) return void 0;
150
+ profileObj = resolved;
151
+ }
152
+ if (profileObj.kind !== "stream") return void 0;
153
+ const profileStream = profileObj;
154
+ const profileData = profileStream.data;
155
+ const nEntry = profileStream.dict.get("/N");
156
+ const components = nEntry && nEntry.kind === "number" ? nEntry.value : 0;
157
+ if (components < 1 || components > 4) return void 0;
158
+ return {
159
+ data: profileData,
160
+ components,
161
+ colorSpace: parseIccColorSpace(profileData),
162
+ description: parseIccDescription(profileData)
163
+ };
164
+ }
165
+ /**
166
+ * Embed an ICC color profile into the PDF object registry and return
167
+ * a reference that can be used as a `/ColorSpace` entry.
168
+ *
169
+ * Creates a new `PdfStream` for the ICC profile data with the required
170
+ * `/N` (number of components) entry, registers it, and returns a
171
+ * `PdfRef` to the stream. The caller should then set the image's
172
+ * `/ColorSpace` to `[/ICCBased <returned ref>]`.
173
+ *
174
+ * @param profile - The `IccProfile` to embed.
175
+ * @param registry - The document's `PdfObjectRegistry`.
176
+ * @returns A `PdfRef` pointing to the newly created ICC profile stream.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * import { embedIccProfile, extractIccProfile } from 'modern-pdf-lib';
181
+ *
182
+ * const profile = extractIccProfile(imageStream, registry);
183
+ * if (profile) {
184
+ * const profileRef = embedIccProfile(profile, registry);
185
+ * const colorSpace = PdfArray.of([PdfName.of('/ICCBased'), profileRef]);
186
+ * imageStream.dict.set('/ColorSpace', colorSpace);
187
+ * }
188
+ * ```
189
+ */
190
+ function embedIccProfile(profile, registry) {
191
+ const dict = new PdfDict();
192
+ dict.set("/N", PdfNumber.of(profile.components));
193
+ dict.set("/Length", PdfNumber.of(profile.data.length));
194
+ if (profile.components === 1) dict.set("/Alternate", PdfName.of("/DeviceGray"));
195
+ else if (profile.components === 3) dict.set("/Alternate", PdfName.of("/DeviceRGB"));
196
+ else if (profile.components === 4) dict.set("/Alternate", PdfName.of("/DeviceCMYK"));
197
+ const stream = new PdfStream(dict, profile.data);
198
+ return registry.register(stream);
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/assets/image/batchOptimize.ts
203
+ var batchOptimize_exports = /* @__PURE__ */ __exportAll({ optimizeAllImages: () => optimizeAllImages });
204
+ /** Minimum image size to bother optimizing (10 KB). */
205
+ const SMALL_IMAGE_THRESHOLD = 10240;
206
+ /**
207
+ * Process items concurrently with a maximum parallelism limit.
208
+ * Workers pull from a shared index — no item is processed twice.
209
+ */
210
+ async function processWithConcurrency(items, concurrency, processor) {
211
+ let nextIndex = 0;
212
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
213
+ while (nextIndex < items.length) {
214
+ const index = nextIndex++;
215
+ await processor(items[index], index);
216
+ }
217
+ });
218
+ await Promise.all(workers);
219
+ }
220
+ /**
221
+ * Optimize all images in a PDF document by recompressing them as JPEG.
222
+ *
223
+ * Walks every image XObject in the document, decodes its pixel data,
224
+ * recompresses it as JPEG using the WASM encoder (if available), and
225
+ * replaces the stream data in-place when the result is smaller.
226
+ *
227
+ * **Requires the JPEG WASM module to be initialized** via
228
+ * `initJpegWasm()` or `initWasm({ jpeg: true })`. Without it,
229
+ * no images will be optimized (all will be skipped).
230
+ *
231
+ * @param doc - A parsed `PdfDocument` (from `loadPdf()`).
232
+ * @param options - Optimization settings.
233
+ * @returns A report summarizing the optimization results.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * import { loadPdf, initWasm, optimizeAllImages } from 'modern-pdf-lib';
238
+ *
239
+ * await initWasm({ jpeg: true });
240
+ *
241
+ * const doc = await loadPdf(pdfBytes);
242
+ * const report = await optimizeAllImages(doc);
243
+ *
244
+ * console.log(`Optimized ${report.optimizedImages} of ${report.totalImages} images`);
245
+ * console.log(`Savings: ${report.savings.toFixed(1)}%`);
246
+ *
247
+ * const optimizedBytes = await doc.save();
248
+ * ```
249
+ */
250
+ async function optimizeAllImages(doc, options = {}) {
251
+ const quality = options.quality ?? 80;
252
+ const minSavingsPercent = options.minSavingsPercent ?? 10;
253
+ const skipSmall = options.skipSmallImages ?? false;
254
+ const progressive = options.progressive ?? false;
255
+ const chromaSubsampling = options.chromaSubsampling ?? "4:2:0";
256
+ const concurrency = Math.max(1, options.concurrency ?? 1);
257
+ const { pageRange, minImageSize, colorSpaces, namePattern } = options;
258
+ const { encodeJpegWasm, isJpegWasmReady } = await import("./bridge-DTH5LMAK.mjs").then((n) => n.t);
259
+ const { decodeJpegWasm } = await import("./bridge-DTH5LMAK.mjs").then((n) => n.t);
260
+ const images = extractImages(doc);
261
+ const registry = doc.getRegistry();
262
+ const results = new Array(images.length);
263
+ /** Process a single image and write the result into `results[index]`. */
264
+ const processImage = async (img, index) => {
265
+ const skip = (skippedByFilter, reason) => {
266
+ results[index] = {
267
+ name: img.name,
268
+ pageIndex: img.pageIndex,
269
+ originalSize: img.compressedSize,
270
+ newSize: img.compressedSize,
271
+ skipped: true,
272
+ skippedByFilter,
273
+ reason
274
+ };
275
+ };
276
+ if (pageRange && (img.pageIndex < pageRange.start || img.pageIndex > pageRange.end)) return skip(true, `Page ${img.pageIndex} outside range [${pageRange.start}, ${pageRange.end}]`);
277
+ if (minImageSize && img.compressedSize < minImageSize) return skip(true, `Compressed size ${img.compressedSize} below minimum ${minImageSize} bytes`);
278
+ if (colorSpaces && !colorSpaces.includes(img.colorSpace)) return skip(true, `Color space '${img.colorSpace}' not in allowed list`);
279
+ if (namePattern && !namePattern.test(img.name)) return skip(true, `Name '${img.name}' does not match pattern ${namePattern}`);
280
+ if (!isJpegWasmReady()) return skip(false, "JPEG WASM encoder not initialized");
281
+ if (skipSmall && img.compressedSize < SMALL_IMAGE_THRESHOLD) return skip(false, `Below size threshold (${SMALL_IMAGE_THRESHOLD} bytes)`);
282
+ if (img.bitsPerComponent !== 8) return skip(false, `Unsupported bits per component: ${img.bitsPerComponent}`);
283
+ if (img.colorSpace === "Indexed") return skip(false, "Indexed color space not suitable for JPEG");
284
+ let iccProfile;
285
+ try {
286
+ iccProfile = extractIccProfile(img.stream, registry);
287
+ } catch {
288
+ iccProfile = void 0;
289
+ }
290
+ let pixels;
291
+ let channels = img.channels;
292
+ try {
293
+ if (img.filters[0] === "DCTDecode") {
294
+ const decoded = decodeJpegWasm(img.stream.data);
295
+ if (!decoded) return skip(false, "Failed to decode existing JPEG");
296
+ pixels = decoded.pixels;
297
+ channels = decoded.channels;
298
+ } else pixels = decodeImageStream(img);
299
+ } catch {
300
+ return skip(false, "Failed to decode image stream");
301
+ }
302
+ const expectedLen = img.width * img.height * channels;
303
+ if (pixels.length !== expectedLen) return skip(false, `Pixel data length mismatch: got ${pixels.length}, expected ${expectedLen}`);
304
+ if (channels === 4 && img.colorSpace === "DeviceCMYK") {
305
+ const rgb = new Uint8Array(img.width * img.height * 3);
306
+ for (let i = 0; i < img.width * img.height; i++) {
307
+ const c = pixels[i * 4] / 255;
308
+ const m = pixels[i * 4 + 1] / 255;
309
+ const y = pixels[i * 4 + 2] / 255;
310
+ const k = pixels[i * 4 + 3] / 255;
311
+ rgb[i * 3] = Math.round(255 * (1 - c) * (1 - k));
312
+ rgb[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
313
+ rgb[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
314
+ }
315
+ pixels = rgb;
316
+ channels = 3;
317
+ }
318
+ let convertedToGrayscale = false;
319
+ if (options.autoGrayscale && (channels === 3 || channels === 4)) {
320
+ if (isGrayscaleImage(pixels, img.width, img.height, channels)) {
321
+ pixels = convertToGrayscale(pixels, img.width, img.height, channels);
322
+ channels = 1;
323
+ convertedToGrayscale = true;
324
+ }
325
+ }
326
+ const jpegBytes = encodeJpegWasm(pixels, img.width, img.height, channels, quality, progressive, chromaSubsampling);
327
+ if (!jpegBytes) return skip(false, "JPEG encoding failed");
328
+ const savingsPercent = (img.compressedSize - jpegBytes.length) / img.compressedSize * 100;
329
+ if (savingsPercent < minSavingsPercent) return skip(false, `Savings ${savingsPercent.toFixed(1)}% below threshold ${minSavingsPercent}%`);
330
+ img.stream.data = jpegBytes;
331
+ img.stream.syncLength();
332
+ const dict = img.stream.dict;
333
+ dict.set("/Filter", PdfName.of("/DCTDecode"));
334
+ if (channels === 1) dict.set("/ColorSpace", PdfName.of("/DeviceGray"));
335
+ else if (iccProfile && !convertedToGrayscale && iccProfile.components === channels) {
336
+ const profileRef = embedIccProfile(iccProfile, registry);
337
+ dict.set("/ColorSpace", PdfArray.of([PdfName.of("/ICCBased"), profileRef]));
338
+ } else if (img.colorSpace === "DeviceCMYK" && channels === 3) dict.set("/ColorSpace", PdfName.of("/DeviceRGB"));
339
+ else if (channels === 3) dict.set("/ColorSpace", PdfName.of("/DeviceRGB"));
340
+ dict.delete("/DecodeParms");
341
+ if (img.colorSpace === "DeviceCMYK") dict.delete("/Decode");
342
+ results[index] = {
343
+ name: img.name,
344
+ pageIndex: img.pageIndex,
345
+ originalSize: img.compressedSize,
346
+ newSize: jpegBytes.length,
347
+ skipped: false,
348
+ skippedByFilter: false
349
+ };
350
+ };
351
+ await processWithConcurrency(images, concurrency, processImage);
352
+ let totalOriginal = 0;
353
+ let totalNew = 0;
354
+ let optimizedCount = 0;
355
+ let skippedByFilterCount = 0;
356
+ let cumulativeSavedBytes = 0;
357
+ const { onProgress } = options;
358
+ for (let i = 0; i < results.length; i++) {
359
+ const entry = results[i];
360
+ totalOriginal += entry.originalSize;
361
+ totalNew += entry.newSize;
362
+ if (!entry.skipped) optimizedCount++;
363
+ if (entry.skippedByFilter) skippedByFilterCount++;
364
+ if (onProgress) {
365
+ const saved = entry.originalSize - entry.newSize;
366
+ cumulativeSavedBytes += saved;
367
+ onProgress({
368
+ current: i + 1,
369
+ total: images.length,
370
+ imageName: entry.name,
371
+ pageIndex: entry.pageIndex,
372
+ savedBytes: saved,
373
+ totalSavedBytes: cumulativeSavedBytes,
374
+ skipped: entry.skipped
375
+ });
376
+ }
377
+ }
378
+ const overallSavings = totalOriginal > 0 ? (totalOriginal - totalNew) / totalOriginal * 100 : 0;
379
+ return {
380
+ totalImages: images.length,
381
+ optimizedImages: optimizedCount,
382
+ skippedByFilter: skippedByFilterCount,
383
+ originalTotalBytes: totalOriginal,
384
+ optimizedTotalBytes: totalNew,
385
+ savings: overallSavings,
386
+ perImage: results
387
+ };
388
+ }
389
+
390
+ //#endregion
391
+ export { parseIccColorSpace as a, extractIccProfile as i, optimizeAllImages as n, parseIccDescription as o, embedIccProfile as r, batchOptimize_exports as t };
392
+ //# sourceMappingURL=batchOptimize-CxyY4fZe.mjs.map
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
1
+ import { Ka as __exportAll } from "./index-BtYOx5wh.d.mts";
2
2
 
3
3
  //#region src/wasm/jpeg/bridge.ts
4
4
  var bridge_exports = /* @__PURE__ */ __exportAll({
@@ -22,7 +22,7 @@ async function initJpegWasm(wasmSource) {
22
22
  wasmModule = wasmSource;
23
23
  return;
24
24
  }
25
- const { loadWasmModule: loadWasm } = await import("./loader-B6VIrZOJ.mjs");
25
+ const { loadWasmModule: loadWasm } = await import("./loader-3u6Tw5T-.mjs").then((n) => n.l);
26
26
  const wasmBytes = await loadWasm("jpeg");
27
27
  wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
28
28
  } catch {
@@ -100,4 +100,4 @@ function decodeJpegWasm(jpegBytes) {
100
100
 
101
101
  //#endregion
102
102
  export { isJpegWasmReady as a, initJpegWasm as i, decodeJpegWasm as n, encodeJpegWasm as r, bridge_exports as t };
103
- //# sourceMappingURL=bridge-DpzMOnHd.mjs.map
103
+ //# sourceMappingURL=bridge-DTH5LMAK.mjs.map
@@ -22,7 +22,7 @@ async function initJpegWasm(wasmSource) {
22
22
  wasmModule = wasmSource;
23
23
  return;
24
24
  }
25
- const { loadWasmModule: loadWasm } = await Promise.resolve().then(() => require("./loader-DdB5Xo5D.cjs"));
25
+ const { loadWasmModule: loadWasm } = await Promise.resolve().then(() => require("./loader-I4zdkoWc.cjs")).then((n) => n.loader_exports);
26
26
  const wasmBytes = await loadWasm("jpeg");
27
27
  wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
28
28
  } catch {
@@ -129,4 +129,4 @@ Object.defineProperty(exports, 'isJpegWasmReady', {
129
129
  return isJpegWasmReady;
130
130
  }
131
131
  });
132
- //# sourceMappingURL=bridge-DN7BOHRW.cjs.map
132
+ //# sourceMappingURL=bridge-DYCQzxF7.cjs.map