@kitsy/pdf 0.0.0-stage → 0.2.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/CHANGELOG.md +32 -0
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +256 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/chunk-2K5X7L5J.js +43 -0
- package/dist/chunk-2K5X7L5J.js.map +1 -0
- package/dist/chunk-4EAYBJ6G.js +362 -0
- package/dist/chunk-4EAYBJ6G.js.map +1 -0
- package/dist/chunk-4LBJTDV5.js +165 -0
- package/dist/chunk-4LBJTDV5.js.map +1 -0
- package/dist/chunk-4ZEZ2GLD.js +353 -0
- package/dist/chunk-4ZEZ2GLD.js.map +1 -0
- package/dist/chunk-5OIMR74G.js +273 -0
- package/dist/chunk-5OIMR74G.js.map +1 -0
- package/dist/chunk-6NFNAKWA.js +257 -0
- package/dist/chunk-6NFNAKWA.js.map +1 -0
- package/dist/chunk-7MTEMQLL.js +47 -0
- package/dist/chunk-7MTEMQLL.js.map +1 -0
- package/dist/chunk-7V4SWTUK.js +83 -0
- package/dist/chunk-7V4SWTUK.js.map +1 -0
- package/dist/chunk-7XH7MFZ4.js +295 -0
- package/dist/chunk-7XH7MFZ4.js.map +1 -0
- package/dist/chunk-BK3MYSUS.js +61 -0
- package/dist/chunk-BK3MYSUS.js.map +1 -0
- package/dist/chunk-BLHNKR3S.js +35 -0
- package/dist/chunk-BLHNKR3S.js.map +1 -0
- package/dist/chunk-CCJ43SIX.js +342 -0
- package/dist/chunk-CCJ43SIX.js.map +1 -0
- package/dist/chunk-CNEDWATL.js +210 -0
- package/dist/chunk-CNEDWATL.js.map +1 -0
- package/dist/chunk-CYTWDJE2.js +152 -0
- package/dist/chunk-CYTWDJE2.js.map +1 -0
- package/dist/chunk-DKVOICYZ.js +45 -0
- package/dist/chunk-DKVOICYZ.js.map +1 -0
- package/dist/chunk-DPEJU4P6.js +79 -0
- package/dist/chunk-DPEJU4P6.js.map +1 -0
- package/dist/chunk-DQPABZTU.js +22 -0
- package/dist/chunk-DQPABZTU.js.map +1 -0
- package/dist/chunk-EKHEY3LI.js +46 -0
- package/dist/chunk-EKHEY3LI.js.map +1 -0
- package/dist/chunk-FIEYAKPI.js +421 -0
- package/dist/chunk-FIEYAKPI.js.map +1 -0
- package/dist/chunk-G467LFKC.js +191 -0
- package/dist/chunk-G467LFKC.js.map +1 -0
- package/dist/chunk-GLYWSVSB.js +250 -0
- package/dist/chunk-GLYWSVSB.js.map +1 -0
- package/dist/chunk-GXBIMUO7.js +135 -0
- package/dist/chunk-GXBIMUO7.js.map +1 -0
- package/dist/chunk-H6EERRCR.js +180 -0
- package/dist/chunk-H6EERRCR.js.map +1 -0
- package/dist/chunk-HDTM3LXD.js +35 -0
- package/dist/chunk-HDTM3LXD.js.map +1 -0
- package/dist/chunk-IGMX56C3.js +180 -0
- package/dist/chunk-IGMX56C3.js.map +1 -0
- package/dist/chunk-J4IRICCJ.js +74 -0
- package/dist/chunk-J4IRICCJ.js.map +1 -0
- package/dist/chunk-JXKQ7S6W.js +331 -0
- package/dist/chunk-JXKQ7S6W.js.map +1 -0
- package/dist/chunk-KY6DXS6S.js +34 -0
- package/dist/chunk-KY6DXS6S.js.map +1 -0
- package/dist/chunk-NOFGCYGS.js +22 -0
- package/dist/chunk-NOFGCYGS.js.map +1 -0
- package/dist/chunk-OSHUJRKD.js +131 -0
- package/dist/chunk-OSHUJRKD.js.map +1 -0
- package/dist/chunk-PMH7OWPF.js +40 -0
- package/dist/chunk-PMH7OWPF.js.map +1 -0
- package/dist/chunk-PRBKSX3A.js +299 -0
- package/dist/chunk-PRBKSX3A.js.map +1 -0
- package/dist/chunk-SVPUX7AM.js +48 -0
- package/dist/chunk-SVPUX7AM.js.map +1 -0
- package/dist/chunk-TR6HJ4NO.js +659 -0
- package/dist/chunk-TR6HJ4NO.js.map +1 -0
- package/dist/chunk-TV3E4YUF.js +69 -0
- package/dist/chunk-TV3E4YUF.js.map +1 -0
- package/dist/chunk-VCTN77K2.js +112 -0
- package/dist/chunk-VCTN77K2.js.map +1 -0
- package/dist/chunk-WEOPDD3P.js +120 -0
- package/dist/chunk-WEOPDD3P.js.map +1 -0
- package/dist/chunk-ZBZ7OC6I.js +228 -0
- package/dist/chunk-ZBZ7OC6I.js.map +1 -0
- package/dist/config-CJYo46X9.d.ts +19 -0
- package/dist/index.d.ts +517 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/react/certificate-sign.d.ts +5 -0
- package/dist/react/certificate-sign.js +14 -0
- package/dist/react/certificate-sign.js.map +1 -0
- package/dist/react/compress.d.ts +5 -0
- package/dist/react/compress.js +8 -0
- package/dist/react/compress.js.map +1 -0
- package/dist/react/fill-form.d.ts +5 -0
- package/dist/react/fill-form.js +8 -0
- package/dist/react/fill-form.js.map +1 -0
- package/dist/react/images-to-pdf.d.ts +5 -0
- package/dist/react/images-to-pdf.js +7 -0
- package/dist/react/images-to-pdf.js.map +1 -0
- package/dist/react/merge.d.ts +5 -0
- package/dist/react/merge.js +8 -0
- package/dist/react/merge.js.map +1 -0
- package/dist/react/metadata.d.ts +5 -0
- package/dist/react/metadata.js +8 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/organize.d.ts +5 -0
- package/dist/react/organize.js +12 -0
- package/dist/react/organize.js.map +1 -0
- package/dist/react/pdf-to-images.d.ts +5 -0
- package/dist/react/pdf-to-images.js +13 -0
- package/dist/react/pdf-to-images.js.map +1 -0
- package/dist/react/protect.d.ts +5 -0
- package/dist/react/protect.js +8 -0
- package/dist/react/protect.js.map +1 -0
- package/dist/react/sign.d.ts +5 -0
- package/dist/react/sign.js +13 -0
- package/dist/react/sign.js.map +1 -0
- package/dist/react/split.d.ts +5 -0
- package/dist/react/split.js +11 -0
- package/dist/react/split.js.map +1 -0
- package/dist/react/verify-signatures.d.ts +5 -0
- package/dist/react/verify-signatures.js +8 -0
- package/dist/react/verify-signatures.js.map +1 -0
- package/dist/react/watermark.d.ts +5 -0
- package/dist/react/watermark.js +10 -0
- package/dist/react/watermark.js.map +1 -0
- package/dist/react.d.ts +40 -0
- package/dist/react.js +40 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +9 -0
- package/dist/vite.js.map +1 -0
- package/package.json +163 -4
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { loadPdf } from './chunk-EKHEY3LI.js';
|
|
2
|
+
import { PDFArray, PDFRawStream, PDFName, PDFNumber, PDFRef, PDFDict } from 'pdf-lib';
|
|
3
|
+
|
|
4
|
+
var COMPRESSION_LEVELS = {
|
|
5
|
+
light: { label: "Light", maxDimension: 2400, quality: 0.82 },
|
|
6
|
+
balanced: { label: "Balanced", maxDimension: 1600, quality: 0.7 },
|
|
7
|
+
strong: { label: "Strong", maxDimension: 1100, quality: 0.55 }
|
|
8
|
+
};
|
|
9
|
+
var MIN_STREAM_BYTES = 8 * 1024;
|
|
10
|
+
var MAX_PIXELS = 4e7;
|
|
11
|
+
var MIN_SAVING = 0.9;
|
|
12
|
+
var N = (name) => PDFName.of(name);
|
|
13
|
+
function numberOf(dict, key, fallback) {
|
|
14
|
+
const value = dict.lookup(N(key));
|
|
15
|
+
return value instanceof PDFNumber ? value.asNumber() : fallback;
|
|
16
|
+
}
|
|
17
|
+
function filterNames(dict) {
|
|
18
|
+
const filter = dict.lookup(N("Filter"));
|
|
19
|
+
if (filter instanceof PDFName) return [filter.decodeText()];
|
|
20
|
+
if (filter instanceof PDFArray) {
|
|
21
|
+
return filter.asArray().map((item) => item instanceof PDFName ? item.decodeText() : "?");
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
function decodeParms(dict) {
|
|
26
|
+
const parms = dict.lookup(N("DecodeParms"));
|
|
27
|
+
if (parms instanceof PDFDict) return parms;
|
|
28
|
+
if (parms instanceof PDFArray && parms.size() === 1) {
|
|
29
|
+
const first = parms.lookup(0);
|
|
30
|
+
return first instanceof PDFDict ? first : null;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function colorComponents(dict) {
|
|
35
|
+
const space = dict.lookup(N("ColorSpace"));
|
|
36
|
+
if (space === N("DeviceRGB")) return 3;
|
|
37
|
+
if (space === N("DeviceGray")) return 1;
|
|
38
|
+
if (space instanceof PDFArray && space.size() === 2 && space.lookup(0) === N("ICCBased")) {
|
|
39
|
+
const profile = space.lookup(1);
|
|
40
|
+
const count = profile instanceof PDFRawStream ? numberOf(profile.dict, "N") : void 0;
|
|
41
|
+
if (count === 3) return 3;
|
|
42
|
+
if (count === 1) return 1;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function unpredictPng(data, columns, colors, bitsPerComponent = 8) {
|
|
47
|
+
const bytesPerPixel = Math.max(1, Math.ceil(colors * bitsPerComponent / 8));
|
|
48
|
+
const rowLength = Math.ceil(columns * colors * bitsPerComponent / 8);
|
|
49
|
+
const rows = Math.floor(data.length / (rowLength + 1));
|
|
50
|
+
const out = new Uint8Array(rows * rowLength);
|
|
51
|
+
let previous = new Uint8Array(rowLength);
|
|
52
|
+
for (let row = 0; row < rows; row += 1) {
|
|
53
|
+
const filter = data[row * (rowLength + 1)];
|
|
54
|
+
const input = data.subarray(row * (rowLength + 1) + 1, (row + 1) * (rowLength + 1));
|
|
55
|
+
const current = out.subarray(row * rowLength, (row + 1) * rowLength);
|
|
56
|
+
for (let i = 0; i < rowLength; i += 1) {
|
|
57
|
+
const left = i >= bytesPerPixel ? current[i - bytesPerPixel] : 0;
|
|
58
|
+
const up = previous[i];
|
|
59
|
+
const upLeft = i >= bytesPerPixel ? previous[i - bytesPerPixel] : 0;
|
|
60
|
+
let value = input[i];
|
|
61
|
+
if (filter === 1) value += left;
|
|
62
|
+
else if (filter === 2) value += up;
|
|
63
|
+
else if (filter === 3) value += Math.floor((left + up) / 2);
|
|
64
|
+
else if (filter === 4) {
|
|
65
|
+
const p = left + up - upLeft;
|
|
66
|
+
const pa = Math.abs(p - left);
|
|
67
|
+
const pb = Math.abs(p - up);
|
|
68
|
+
const pc = Math.abs(p - upLeft);
|
|
69
|
+
value += pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft;
|
|
70
|
+
} else if (filter !== 0) {
|
|
71
|
+
throw new Error(`Unknown PNG filter ${filter}.`);
|
|
72
|
+
}
|
|
73
|
+
current[i] = value & 255;
|
|
74
|
+
}
|
|
75
|
+
previous = current;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
function collectMaskRefs(entries) {
|
|
80
|
+
const masks = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const [, object] of entries) {
|
|
82
|
+
if (!(object instanceof PDFRawStream)) continue;
|
|
83
|
+
for (const key of ["SMask", "Mask"]) {
|
|
84
|
+
const value = object.dict.get(N(key));
|
|
85
|
+
if (value instanceof PDFRef) masks.add(value.toString());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return masks;
|
|
89
|
+
}
|
|
90
|
+
async function compressPdf(bytes, target, codec, onProgress) {
|
|
91
|
+
const originalSize = bytes.byteLength;
|
|
92
|
+
const doc = await loadPdf(bytes, { updateMetadata: false });
|
|
93
|
+
const entries = doc.context.enumerateIndirectObjects();
|
|
94
|
+
const masks = collectMaskRefs(entries);
|
|
95
|
+
const candidates = entries.filter(
|
|
96
|
+
([, object]) => object instanceof PDFRawStream && object.dict.lookup(N("Subtype")) === N("Image")
|
|
97
|
+
);
|
|
98
|
+
let compressed = 0;
|
|
99
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
100
|
+
const [ref, stream] = candidates[index];
|
|
101
|
+
onProgress?.(index, candidates.length);
|
|
102
|
+
const input = await readImage(ref, stream, masks, codec);
|
|
103
|
+
if (!input) continue;
|
|
104
|
+
let encoded;
|
|
105
|
+
try {
|
|
106
|
+
encoded = await codec.encodeJpeg(input, target);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (encoded.bytes.length >= stream.contents.length * MIN_SAVING) continue;
|
|
111
|
+
const dict = doc.context.obj({
|
|
112
|
+
Type: "XObject",
|
|
113
|
+
Subtype: "Image",
|
|
114
|
+
Width: encoded.width,
|
|
115
|
+
Height: encoded.height,
|
|
116
|
+
ColorSpace: "DeviceRGB",
|
|
117
|
+
BitsPerComponent: 8,
|
|
118
|
+
Filter: "DCTDecode"
|
|
119
|
+
});
|
|
120
|
+
for (const key of ["SMask", "Interpolate", "Intent", "Metadata", "OC"]) {
|
|
121
|
+
const value = stream.dict.get(N(key));
|
|
122
|
+
if (value) dict.set(N(key), value);
|
|
123
|
+
}
|
|
124
|
+
doc.context.assign(ref, PDFRawStream.of(dict, encoded.bytes));
|
|
125
|
+
compressed += 1;
|
|
126
|
+
}
|
|
127
|
+
onProgress?.(candidates.length, candidates.length);
|
|
128
|
+
const out = await doc.save({ useObjectStreams: true });
|
|
129
|
+
const stats = {
|
|
130
|
+
images: candidates.length,
|
|
131
|
+
compressed,
|
|
132
|
+
originalSize,
|
|
133
|
+
newSize: out.byteLength
|
|
134
|
+
};
|
|
135
|
+
return { bytes: out, stats };
|
|
136
|
+
}
|
|
137
|
+
async function readImage(ref, stream, masks, codec) {
|
|
138
|
+
const { dict } = stream;
|
|
139
|
+
if (masks.has(ref.toString())) return null;
|
|
140
|
+
if (stream.contents.length < MIN_STREAM_BYTES) return null;
|
|
141
|
+
if (dict.lookup(N("ImageMask"))?.toString() === "true") return null;
|
|
142
|
+
if (dict.get(N("Decode"))) return null;
|
|
143
|
+
if (dict.get(N("Mask"))) return null;
|
|
144
|
+
const softMask = dict.lookup(N("SMask"));
|
|
145
|
+
if (softMask instanceof PDFRawStream && softMask.dict.get(N("Matte"))) return null;
|
|
146
|
+
if (numberOf(dict, "BitsPerComponent") !== 8) return null;
|
|
147
|
+
const width = numberOf(dict, "Width", 0);
|
|
148
|
+
const height = numberOf(dict, "Height", 0);
|
|
149
|
+
if (!width || !height || width * height > MAX_PIXELS) return null;
|
|
150
|
+
const components = colorComponents(dict);
|
|
151
|
+
if (!components) return null;
|
|
152
|
+
const filters = filterNames(dict);
|
|
153
|
+
if (filters.length === 1 && filters[0] === "DCTDecode") {
|
|
154
|
+
return { kind: "jpeg", bytes: stream.contents, width, height };
|
|
155
|
+
}
|
|
156
|
+
if (filters.length !== 1 || filters[0] !== "FlateDecode") return null;
|
|
157
|
+
const parms = decodeParms(dict);
|
|
158
|
+
const predictor = parms ? numberOf(parms, "Predictor", 1) : 1;
|
|
159
|
+
if (predictor !== 1 && predictor < 10) return null;
|
|
160
|
+
if (predictor >= 10 && numberOf(parms, "Colors", 1) !== components) return null;
|
|
161
|
+
let pixels;
|
|
162
|
+
try {
|
|
163
|
+
pixels = await codec.inflate(stream.contents);
|
|
164
|
+
if (predictor >= 10) {
|
|
165
|
+
pixels = unpredictPng(pixels, numberOf(parms, "Columns", width), components, 8);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
if (pixels.length < width * height * components) return null;
|
|
171
|
+
return {
|
|
172
|
+
kind: "raw",
|
|
173
|
+
pixels: pixels.subarray(0, width * height * components),
|
|
174
|
+
width,
|
|
175
|
+
height,
|
|
176
|
+
components
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/compress/browserCodec.ts
|
|
181
|
+
async function inflate(bytes) {
|
|
182
|
+
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate"));
|
|
183
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
184
|
+
}
|
|
185
|
+
async function toSource(input) {
|
|
186
|
+
if (input.kind === "jpeg") {
|
|
187
|
+
return createImageBitmap(new Blob([input.bytes], { type: "image/jpeg" }), {
|
|
188
|
+
imageOrientation: "none"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const { width, height, pixels, components } = input;
|
|
192
|
+
const rgba = new Uint8ClampedArray(width * height * 4);
|
|
193
|
+
for (let i = 0, j = 0; i < width * height; i += 1, j += components) {
|
|
194
|
+
rgba[i * 4] = pixels[j];
|
|
195
|
+
rgba[i * 4 + 1] = pixels[components === 3 ? j + 1 : j];
|
|
196
|
+
rgba[i * 4 + 2] = pixels[components === 3 ? j + 2 : j];
|
|
197
|
+
rgba[i * 4 + 3] = 255;
|
|
198
|
+
}
|
|
199
|
+
const canvas = document.createElement("canvas");
|
|
200
|
+
canvas.width = width;
|
|
201
|
+
canvas.height = height;
|
|
202
|
+
canvas.getContext("2d").putImageData(new ImageData(rgba, width, height), 0, 0);
|
|
203
|
+
return canvas;
|
|
204
|
+
}
|
|
205
|
+
async function encodeJpeg(input, target) {
|
|
206
|
+
const source = await toSource(input);
|
|
207
|
+
const scale = Math.min(1, target.maxDimension / Math.max(source.width, source.height));
|
|
208
|
+
const width = Math.max(1, Math.round(source.width * scale));
|
|
209
|
+
const height = Math.max(1, Math.round(source.height * scale));
|
|
210
|
+
const canvas = document.createElement("canvas");
|
|
211
|
+
canvas.width = width;
|
|
212
|
+
canvas.height = height;
|
|
213
|
+
const context = canvas.getContext("2d");
|
|
214
|
+
context.imageSmoothingQuality = "high";
|
|
215
|
+
context.drawImage(source, 0, 0, width, height);
|
|
216
|
+
if ("close" in source) source.close();
|
|
217
|
+
const blob = await new Promise(
|
|
218
|
+
(resolve, reject) => canvas.toBlob((result) => result ? resolve(result) : reject(new Error("encode failed")), "image/jpeg", target.quality)
|
|
219
|
+
);
|
|
220
|
+
canvas.width = 0;
|
|
221
|
+
canvas.height = 0;
|
|
222
|
+
return { bytes: new Uint8Array(await blob.arrayBuffer()), width, height };
|
|
223
|
+
}
|
|
224
|
+
var browserCodec = { inflate, encodeJpeg };
|
|
225
|
+
|
|
226
|
+
export { COMPRESSION_LEVELS, browserCodec, colorComponents, compressPdf, unpredictPng };
|
|
227
|
+
//# sourceMappingURL=chunk-ZBZ7OC6I.js.map
|
|
228
|
+
//# sourceMappingURL=chunk-ZBZ7OC6I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compress/compressPdfCore.ts","../src/compress/browserCodec.ts"],"names":[],"mappings":"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","file":"chunk-ZBZ7OC6I.js","sourcesContent":["import { PDFArray, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\n\nexport type ImageInput =\n | { kind: \"jpeg\"; bytes: Uint8Array; width: number; height: number }\n | { kind: \"raw\"; pixels: Uint8Array; width: number; height: number; components: 1 | 3 };\n\nexport type CompressTarget = { maxDimension: number; quality: number };\n\n/** Browser (canvas) or test implementation of the pixel work. */\nexport type ImageCodec = {\n inflate(bytes: Uint8Array): Promise<Uint8Array>;\n encodeJpeg(\n input: ImageInput,\n target: CompressTarget\n ): Promise<{ bytes: Uint8Array; width: number; height: number }>;\n};\n\nexport const COMPRESSION_LEVELS: Record<string, CompressTarget & { label: string }> = {\n light: { label: \"Light\", maxDimension: 2400, quality: 0.82 },\n balanced: { label: \"Balanced\", maxDimension: 1600, quality: 0.7 },\n strong: { label: \"Strong\", maxDimension: 1100, quality: 0.55 },\n};\n\nconst MIN_STREAM_BYTES = 8 * 1024;\nconst MAX_PIXELS = 40_000_000;\n/** A re-encoded image must be at least this much smaller to replace the original. */\nconst MIN_SAVING = 0.9;\n\nconst N = (name: string) => PDFName.of(name);\n\nfunction numberOf(dict: PDFDict, key: string, fallback?: number) {\n const value = dict.lookup(N(key));\n return value instanceof PDFNumber ? value.asNumber() : fallback;\n}\n\nfunction filterNames(dict: PDFDict) {\n const filter = dict.lookup(N(\"Filter\"));\n if (filter instanceof PDFName) return [filter.decodeText()];\n if (filter instanceof PDFArray) {\n return filter.asArray().map((item) => (item instanceof PDFName ? item.decodeText() : \"?\"));\n }\n return [];\n}\n\nfunction decodeParms(dict: PDFDict) {\n const parms = dict.lookup(N(\"DecodeParms\"));\n if (parms instanceof PDFDict) return parms;\n if (parms instanceof PDFArray && parms.size() === 1) {\n const first = parms.lookup(0);\n return first instanceof PDFDict ? first : null;\n }\n return null;\n}\n\n/** Number of colour components for colour spaces we can safely re-encode. */\nexport function colorComponents(dict: PDFDict): 1 | 3 | null {\n const space = dict.lookup(N(\"ColorSpace\"));\n if (space === N(\"DeviceRGB\")) return 3;\n if (space === N(\"DeviceGray\")) return 1;\n if (space instanceof PDFArray && space.size() === 2 && space.lookup(0) === N(\"ICCBased\")) {\n const profile = space.lookup(1);\n const count = profile instanceof PDFRawStream ? numberOf(profile.dict, \"N\") : undefined;\n if (count === 3) return 3;\n if (count === 1) return 1;\n }\n return null;\n}\n\n/** Reverse PNG row predictors (PDF /Predictor 10-15). */\nexport function unpredictPng(data: Uint8Array, columns: number, colors: number, bitsPerComponent = 8) {\n const bytesPerPixel = Math.max(1, Math.ceil((colors * bitsPerComponent) / 8));\n const rowLength = Math.ceil((columns * colors * bitsPerComponent) / 8);\n const rows = Math.floor(data.length / (rowLength + 1));\n const out = new Uint8Array(rows * rowLength);\n let previous = new Uint8Array(rowLength);\n\n for (let row = 0; row < rows; row += 1) {\n const filter = data[row * (rowLength + 1)];\n const input = data.subarray(row * (rowLength + 1) + 1, (row + 1) * (rowLength + 1));\n const current = out.subarray(row * rowLength, (row + 1) * rowLength);\n\n for (let i = 0; i < rowLength; i += 1) {\n const left = i >= bytesPerPixel ? current[i - bytesPerPixel] : 0;\n const up = previous[i];\n const upLeft = i >= bytesPerPixel ? previous[i - bytesPerPixel] : 0;\n let value = input[i];\n if (filter === 1) value += left;\n else if (filter === 2) value += up;\n else if (filter === 3) value += Math.floor((left + up) / 2);\n else if (filter === 4) {\n const p = left + up - upLeft;\n const pa = Math.abs(p - left);\n const pb = Math.abs(p - up);\n const pc = Math.abs(p - upLeft);\n value += pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft;\n } else if (filter !== 0) {\n throw new Error(`Unknown PNG filter ${filter}.`);\n }\n current[i] = value & 0xff;\n }\n previous = current;\n }\n return out;\n}\n\n/** Refs used as another image's soft mask or stencil mask; never re-encode those. */\nfunction collectMaskRefs(entries: Array<[PDFRef, unknown]>) {\n const masks = new Set<string>();\n for (const [, object] of entries) {\n if (!(object instanceof PDFRawStream)) continue;\n for (const key of [\"SMask\", \"Mask\"]) {\n const value = object.dict.get(N(key));\n if (value instanceof PDFRef) masks.add(value.toString());\n }\n }\n return masks;\n}\n\nexport type CompressStats = {\n images: number;\n compressed: number;\n originalSize: number;\n newSize: number;\n};\n\n/**\n * Re-encode large embedded images as smaller JPEGs. Images that are masks,\n * use unusual colour spaces, or would not shrink are left untouched.\n */\nexport async function compressPdf(\n bytes: ArrayBuffer | Uint8Array,\n target: CompressTarget,\n codec: ImageCodec,\n onProgress?: (done: number, total: number) => void\n) {\n const originalSize = bytes.byteLength;\n const doc = await loadPdf(bytes, { updateMetadata: false });\n const entries = doc.context.enumerateIndirectObjects();\n const masks = collectMaskRefs(entries);\n\n const candidates = entries.filter(\n ([, object]) =>\n object instanceof PDFRawStream && object.dict.lookup(N(\"Subtype\")) === N(\"Image\")\n ) as Array<[PDFRef, PDFRawStream]>;\n\n let compressed = 0;\n for (let index = 0; index < candidates.length; index += 1) {\n const [ref, stream] = candidates[index];\n onProgress?.(index, candidates.length);\n\n const input = await readImage(ref, stream, masks, codec);\n if (!input) continue;\n\n let encoded;\n try {\n encoded = await codec.encodeJpeg(input, target);\n } catch {\n continue;\n }\n if (encoded.bytes.length >= stream.contents.length * MIN_SAVING) continue;\n\n const dict = doc.context.obj({\n Type: \"XObject\",\n Subtype: \"Image\",\n Width: encoded.width,\n Height: encoded.height,\n ColorSpace: \"DeviceRGB\",\n BitsPerComponent: 8,\n Filter: \"DCTDecode\",\n }) as PDFDict;\n for (const key of [\"SMask\", \"Interpolate\", \"Intent\", \"Metadata\", \"OC\"]) {\n const value = stream.dict.get(N(key));\n if (value) dict.set(N(key), value);\n }\n doc.context.assign(ref, PDFRawStream.of(dict, encoded.bytes));\n compressed += 1;\n }\n onProgress?.(candidates.length, candidates.length);\n\n const out = await doc.save({ useObjectStreams: true });\n const stats: CompressStats = {\n images: candidates.length,\n compressed,\n originalSize,\n newSize: out.byteLength,\n };\n return { bytes: out, stats };\n}\n\nasync function readImage(\n ref: PDFRef,\n stream: PDFRawStream,\n masks: Set<string>,\n codec: ImageCodec\n): Promise<ImageInput | null> {\n const { dict } = stream;\n if (masks.has(ref.toString())) return null;\n if (stream.contents.length < MIN_STREAM_BYTES) return null;\n if (dict.lookup(N(\"ImageMask\"))?.toString() === \"true\") return null;\n if (dict.get(N(\"Decode\"))) return null;\n // Colour-key (array) and stencil (stream) masks: a colour key can't survive a\n // lossy JPEG, and the new dict doesn't carry /Mask, so transparency would be lost.\n if (dict.get(N(\"Mask\"))) return null;\n // A soft mask may differ in size from its image, except when it has /Matte.\n const softMask = dict.lookup(N(\"SMask\"));\n if (softMask instanceof PDFRawStream && softMask.dict.get(N(\"Matte\"))) return null;\n if (numberOf(dict, \"BitsPerComponent\") !== 8) return null;\n\n const width = numberOf(dict, \"Width\", 0);\n const height = numberOf(dict, \"Height\", 0);\n if (!width || !height || width * height > MAX_PIXELS) return null;\n\n const components = colorComponents(dict);\n if (!components) return null;\n\n const filters = filterNames(dict);\n if (filters.length === 1 && filters[0] === \"DCTDecode\") {\n return { kind: \"jpeg\", bytes: stream.contents, width, height };\n }\n if (filters.length !== 1 || filters[0] !== \"FlateDecode\") return null;\n\n const parms = decodeParms(dict);\n const predictor = parms ? numberOf(parms, \"Predictor\", 1) : 1;\n if (predictor !== 1 && predictor < 10) return null;\n if (predictor >= 10 && numberOf(parms, \"Colors\", 1) !== components) return null;\n\n let pixels: Uint8Array;\n try {\n pixels = await codec.inflate(stream.contents);\n if (predictor >= 10) {\n pixels = unpredictPng(pixels, numberOf(parms, \"Columns\", width), components, 8);\n }\n } catch {\n return null;\n }\n if (pixels.length < width * height * components) return null;\n\n return {\n kind: \"raw\",\n pixels: pixels.subarray(0, width * height * components),\n width,\n height,\n components,\n };\n}\n","import type { CompressTarget, ImageCodec, ImageInput } from \"./compressPdfCore\";\n\nasync function inflate(bytes: Uint8Array) {\n // PDF FlateDecode is zlib-wrapped deflate, which DecompressionStream calls \"deflate\".\n const stream = new Blob([bytes as BlobPart]).stream().pipeThrough(new DecompressionStream(\"deflate\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n}\n\nasync function toSource(input: ImageInput) {\n if (input.kind === \"jpeg\") {\n // PDF viewers ignore EXIF orientation, so must we.\n return createImageBitmap(new Blob([input.bytes as BlobPart], { type: \"image/jpeg\" }), {\n imageOrientation: \"none\",\n });\n }\n\n const { width, height, pixels, components } = input;\n const rgba = new Uint8ClampedArray(width * height * 4);\n for (let i = 0, j = 0; i < width * height; i += 1, j += components) {\n rgba[i * 4] = pixels[j];\n rgba[i * 4 + 1] = pixels[components === 3 ? j + 1 : j];\n rgba[i * 4 + 2] = pixels[components === 3 ? j + 2 : j];\n rgba[i * 4 + 3] = 255;\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n canvas.getContext(\"2d\").putImageData(new ImageData(rgba, width, height), 0, 0);\n return canvas;\n}\n\nasync function encodeJpeg(input: ImageInput, target: CompressTarget) {\n const source = await toSource(input);\n const scale = Math.min(1, target.maxDimension / Math.max(source.width, source.height));\n const width = Math.max(1, Math.round(source.width * scale));\n const height = Math.max(1, Math.round(source.height * scale));\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n context.imageSmoothingQuality = \"high\";\n context.drawImage(source, 0, 0, width, height);\n if (\"close\" in source) source.close();\n\n const blob = await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob((result) => (result ? resolve(result) : reject(new Error(\"encode failed\"))), \"image/jpeg\", target.quality)\n );\n canvas.width = 0;\n canvas.height = 0;\n return { bytes: new Uint8Array(await blob.arrayBuffer()), width, height };\n}\n\nexport const browserCodec: ImageCodec = { inflate, encodeJpeg };\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type PdfConfig = {
|
|
2
|
+
/** Written to the Producer field of PDFs that tools create from scratch. Default "@kitsy/pdf". */
|
|
3
|
+
producer?: string;
|
|
4
|
+
/** URL of pdfjs-dist/build/pdf.worker.min.mjs. Needed for page previews and PDF to Images. */
|
|
5
|
+
pdfjsWorkerSrc?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Folder that holds pdf.js `cmaps/` and `standard_fonts/` (copied from
|
|
8
|
+
* pdfjs-dist). Optional: without it, PDFs with CJK text or unembedded
|
|
9
|
+
* standard fonts may render with substitute glyphs.
|
|
10
|
+
*/
|
|
11
|
+
pdfjsAssetsBase?: string;
|
|
12
|
+
/** URL of @neslinesli93/qpdf-wasm/dist/qpdf.wasm. Needed for Protect / Unlock. */
|
|
13
|
+
qpdfWasmUrl?: string;
|
|
14
|
+
};
|
|
15
|
+
/** Set asset URLs and options. Later calls merge into earlier ones. */
|
|
16
|
+
declare function configurePdf(next: PdfConfig): void;
|
|
17
|
+
declare function getPdfConfig(): Readonly<PdfConfig>;
|
|
18
|
+
|
|
19
|
+
export { type PdfConfig as P, configurePdf as c, getPdfConfig as g };
|