modern-pdf-lib 0.15.0 → 0.19.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.
- package/README.md +106 -7
- package/dist/batchOptimize-7U_kD3_j.mjs +392 -0
- package/dist/batchOptimize-xo6BXbGZ.cjs +427 -0
- package/dist/{bridge-C7U4E7St.mjs → bridge-DTH5LMAK.mjs} +3 -3
- package/dist/{bridge-DUcJFVsk.cjs → bridge-DYCQzxF7.cjs} +2 -2
- package/dist/browser.cjs +621 -0
- package/dist/browser.d.cts +190 -0
- package/dist/browser.d.cts.map +1 -0
- package/dist/browser.d.mts +190 -0
- package/dist/browser.d.mts.map +1 -0
- package/dist/browser.mjs +212 -0
- package/dist/cli/index.cjs +247 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +248 -0
- package/dist/compressionAnalysis-BBv4BkQP.d.mts +261 -0
- package/dist/compressionAnalysis-BBv4BkQP.d.mts.map +1 -0
- package/dist/compressionAnalysis-Bw2alOxt.mjs +1490 -0
- package/dist/compressionAnalysis-CtJ2X9l2.d.cts +261 -0
- package/dist/compressionAnalysis-CtJ2X9l2.d.cts.map +1 -0
- package/dist/compressionAnalysis-eXYyDsrh.cjs +1525 -0
- package/dist/create.cjs +35 -0
- package/dist/create.d.cts +3 -0
- package/dist/create.d.mts +3 -0
- package/dist/create.mjs +5 -0
- package/dist/deduplicateImages-B5lmzL9j.cjs +113 -0
- package/dist/deduplicateImages-BX3Zg8Qp.mjs +102 -0
- package/dist/{fflateAdapter-DX0VqT5k.mjs → fflateAdapter-CBQpGTlx.mjs} +2 -2
- package/dist/{fflateAdapter-AHC_S3cb.cjs → fflateAdapter-LTAeAhaD.cjs} +1 -1
- package/dist/fieldAppearance-C8PoLFSc.d.mts +136 -0
- package/dist/fieldAppearance-C8PoLFSc.d.mts.map +1 -0
- package/dist/fieldAppearance-CdiGFG5e.d.cts +136 -0
- package/dist/fieldAppearance-CdiGFG5e.d.cts.map +1 -0
- package/dist/fontEmbed-Dsu9fo4U.d.mts +636 -0
- package/dist/fontEmbed-Dsu9fo4U.d.mts.map +1 -0
- package/dist/fontEmbed-LID6yG6g.d.cts +636 -0
- package/dist/fontEmbed-LID6yG6g.d.cts.map +1 -0
- package/dist/{fontSubset-pFc8Dueu.cjs → fontSubset-5SLWMmEw.cjs} +1 -1
- package/dist/{fontSubset-ZpLoOZ2e.mjs → fontSubset-DWpduoY2.mjs} +2 -2
- package/dist/forms.cjs +13 -0
- package/dist/forms.d.cts +3 -0
- package/dist/forms.d.mts +3 -0
- package/dist/forms.mjs +3 -0
- package/dist/grayscaleDetect-C2m-eEXR.cjs +96 -0
- package/dist/grayscaleDetect-C6kFF3dk.mjs +84 -0
- package/dist/imageExtract-B6OvUEp-.mjs +155 -0
- package/dist/imageExtract-PxdBvpHj.cjs +166 -0
- package/dist/index-BtYOx5wh.d.mts +4904 -0
- package/dist/index-BtYOx5wh.d.mts.map +1 -0
- package/dist/index-bpktKzCA.d.cts +4904 -0
- package/dist/index-bpktKzCA.d.cts.map +1 -0
- package/dist/index.cjs +288 -25851
- package/dist/index.d.cts +7 -9151
- package/dist/index.d.mts +7 -9151
- package/dist/index.mjs +17 -25665
- package/dist/layout-BZ8tTeAk.mjs +438 -0
- package/dist/layout-Inbqegsk.cjs +563 -0
- package/dist/{libdeflateWasm-Enus0G1k.cjs → libdeflateWasm-BdiDEJOj.cjs} +2 -2
- package/dist/{libdeflateWasm-82loOtIV.mjs → libdeflateWasm-rLppXytE.mjs} +3 -3
- package/dist/loader-3u6Tw5T-.mjs +328 -0
- package/dist/loader-I4zdkoWc.cjs +393 -0
- package/dist/parse.cjs +24 -0
- package/dist/parse.d.cts +4 -0
- package/dist/parse.d.mts +4 -0
- package/dist/parse.mjs +7 -0
- package/dist/pdfCatalog-CYy4NXEY.cjs +173 -0
- package/dist/pdfCatalog-IImGcMbR.mjs +138 -0
- package/dist/pdfDocument-BSiQdNZq.d.cts +4640 -0
- package/dist/pdfDocument-BSiQdNZq.d.cts.map +1 -0
- package/dist/pdfDocument-DOg240g9.mjs +13685 -0
- package/dist/pdfDocument-Duf9LelM.cjs +14110 -0
- package/dist/pdfDocument-i6U5fQ91.d.mts +4640 -0
- package/dist/pdfDocument-i6U5fQ91.d.mts.map +1 -0
- package/dist/pdfForm-9gd40uz9.cjs +1796 -0
- package/dist/pdfForm-BiyNtYem.d.mts +905 -0
- package/dist/pdfForm-BiyNtYem.d.mts.map +1 -0
- package/dist/pdfForm-Cn-cVicP.mjs +1695 -0
- package/dist/pdfForm-SOXJ72LW.d.cts +905 -0
- package/dist/pdfForm-SOXJ72LW.d.cts.map +1 -0
- package/dist/{pdfCatalog-COKoYQ8C.cjs → pdfObjects-1veop1_d.cjs} +2 -172
- package/dist/{pdfCatalog-BB2Wnmud.mjs → pdfObjects-uEsWlfzU.mjs} +3 -138
- package/dist/{pdfPage-N1K2U3jI.mjs → pdfPage-BacMkrLe.mjs} +3024 -4
- package/dist/{pdfPage-DBfdinTR.cjs → pdfPage-CirlQRzJ.cjs} +3148 -104
- package/dist/{pngEmbed-gaJ9S2Dk.mjs → pngEmbed-BLj2zi-5.mjs} +3 -3
- package/dist/{pngEmbed-10m4CfBU.cjs → pngEmbed-D4X4ZN-3.cjs} +2 -2
- package/dist/src-BLWEEbd7.cjs +11852 -0
- package/dist/src-x0g7wiRq.mjs +11103 -0
- package/dist/streamDecode-Bs0_MT_Q.cjs +4607 -0
- package/dist/streamDecode-CWN-nfPJ.mjs +4596 -0
- package/package.json +33 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/loader-1VJXLlMZ.mjs +0 -164
- package/dist/loader-CKlBOHma.cjs +0 -166
- package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-CKhH4XqG.cjs');
|
|
2
|
+
const require_pdfObjects = require('./pdfObjects-1veop1_d.cjs');
|
|
3
|
+
const require_imageExtract = require('./imageExtract-PxdBvpHj.cjs');
|
|
4
|
+
const require_grayscaleDetect = require('./grayscaleDetect-C2m-eEXR.cjs');
|
|
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 require_pdfObjects.PdfDict();
|
|
192
|
+
dict.set("/N", require_pdfObjects.PdfNumber.of(profile.components));
|
|
193
|
+
dict.set("/Length", require_pdfObjects.PdfNumber.of(profile.data.length));
|
|
194
|
+
if (profile.components === 1) dict.set("/Alternate", require_pdfObjects.PdfName.of("/DeviceGray"));
|
|
195
|
+
else if (profile.components === 3) dict.set("/Alternate", require_pdfObjects.PdfName.of("/DeviceRGB"));
|
|
196
|
+
else if (profile.components === 4) dict.set("/Alternate", require_pdfObjects.PdfName.of("/DeviceCMYK"));
|
|
197
|
+
const stream = new require_pdfObjects.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__ */ require_rolldown_runtime.__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 Promise.resolve().then(() => require("./bridge-DYCQzxF7.cjs")).then((n) => n.bridge_exports);
|
|
259
|
+
const { decodeJpegWasm } = await Promise.resolve().then(() => require("./bridge-DYCQzxF7.cjs")).then((n) => n.bridge_exports);
|
|
260
|
+
const images = require_imageExtract.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 = require_imageExtract.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 (require_grayscaleDetect.isGrayscaleImage(pixels, img.width, img.height, channels)) {
|
|
321
|
+
pixels = require_grayscaleDetect.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", require_pdfObjects.PdfName.of("/DCTDecode"));
|
|
334
|
+
if (channels === 1) dict.set("/ColorSpace", require_pdfObjects.PdfName.of("/DeviceGray"));
|
|
335
|
+
else if (iccProfile && !convertedToGrayscale && iccProfile.components === channels) {
|
|
336
|
+
const profileRef = embedIccProfile(iccProfile, registry);
|
|
337
|
+
dict.set("/ColorSpace", require_pdfObjects.PdfArray.of([require_pdfObjects.PdfName.of("/ICCBased"), profileRef]));
|
|
338
|
+
} else if (img.colorSpace === "DeviceCMYK" && channels === 3) dict.set("/ColorSpace", require_pdfObjects.PdfName.of("/DeviceRGB"));
|
|
339
|
+
else if (channels === 3) dict.set("/ColorSpace", require_pdfObjects.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
|
+
Object.defineProperty(exports, 'batchOptimize_exports', {
|
|
392
|
+
enumerable: true,
|
|
393
|
+
get: function () {
|
|
394
|
+
return batchOptimize_exports;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
Object.defineProperty(exports, 'embedIccProfile', {
|
|
398
|
+
enumerable: true,
|
|
399
|
+
get: function () {
|
|
400
|
+
return embedIccProfile;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
Object.defineProperty(exports, 'extractIccProfile', {
|
|
404
|
+
enumerable: true,
|
|
405
|
+
get: function () {
|
|
406
|
+
return extractIccProfile;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
Object.defineProperty(exports, 'optimizeAllImages', {
|
|
410
|
+
enumerable: true,
|
|
411
|
+
get: function () {
|
|
412
|
+
return optimizeAllImages;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
Object.defineProperty(exports, 'parseIccColorSpace', {
|
|
416
|
+
enumerable: true,
|
|
417
|
+
get: function () {
|
|
418
|
+
return parseIccColorSpace;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
Object.defineProperty(exports, 'parseIccDescription', {
|
|
422
|
+
enumerable: true,
|
|
423
|
+
get: function () {
|
|
424
|
+
return parseIccDescription;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
//# sourceMappingURL=batchOptimize-xo6BXbGZ.cjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
132
|
+
//# sourceMappingURL=bridge-DYCQzxF7.cjs.map
|