qpdf-compress 0.2.0 → 0.4.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 +87 -57
- package/binding.gyp +3 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -14
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -9
- package/dist/types.d.ts.map +1 -1
- package/lib/index.ts +12 -21
- package/lib/types.ts +3 -9
- package/package.json +2 -2
- package/src/font_subset.cc +480 -0
- package/src/font_subset.h +17 -0
- package/src/images.cc +473 -126
- package/src/images.h +6 -6
- package/src/optimize.cc +1048 -0
- package/src/optimize.h +15 -0
- package/src/qpdf_addon.cc +34 -44
package/README.md
CHANGED
|
@@ -14,14 +14,11 @@ Native PDF compression for Node.js — powered by [QPDF](https://qpdf.sourceforg
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { compress } from 'qpdf-compress';
|
|
16
16
|
|
|
17
|
-
// lossless —
|
|
18
|
-
const optimized = await compress(pdfBuffer
|
|
17
|
+
// lossless (default) — pure structural optimization
|
|
18
|
+
const optimized = await compress(pdfBuffer);
|
|
19
19
|
|
|
20
|
-
// lossy —
|
|
21
|
-
const smaller = await compress(pdfBuffer, {
|
|
22
|
-
|
|
23
|
-
// lossy with explicit quality
|
|
24
|
-
const tiny = await compress(pdfBuffer, { mode: 'lossy', quality: 50 });
|
|
20
|
+
// lossy — image recompression + downscale to 72 DPI
|
|
21
|
+
const smaller = await compress(pdfBuffer, { lossy: true });
|
|
25
22
|
```
|
|
26
23
|
|
|
27
24
|
## 💡 Why qpdf-compress?
|
|
@@ -31,7 +28,7 @@ const tiny = await compress(pdfBuffer, { mode: 'lossy', quality: 50 });
|
|
|
31
28
|
- Native C++ — no WASM overhead, no shell-out to CLI tools
|
|
32
29
|
- Non-blocking — all operations run off the main thread via N-API AsyncWorker
|
|
33
30
|
- Multi-pass optimization — image dedup, JPEG Huffman optimization, Flate level 9
|
|
34
|
-
- Smart defaults —
|
|
31
|
+
- Smart defaults — metadata stripping, image dedup, lossless JPEG Huffman optimization
|
|
35
32
|
|
|
36
33
|
**🛠️ Developer experience**
|
|
37
34
|
|
|
@@ -56,20 +53,28 @@ const tiny = await compress(pdfBuffer, { mode: 'lossy', quality: 50 });
|
|
|
56
53
|
|
|
57
54
|
### 📊 How it compares
|
|
58
55
|
|
|
59
|
-
| | **qpdf-compress**
|
|
60
|
-
| ------------------------- |
|
|
61
|
-
| Integration | Native Node.js addon
|
|
62
|
-
| Async I/O | ✅ Non-blocking
|
|
63
|
-
| Image deduplication | ✅
|
|
64
|
-
| JPEG Huffman optimization | ✅ Lossless (libjpeg)
|
|
65
|
-
| Lossy image compression | ✅ Auto
|
|
66
|
-
| CMYK → RGB conversion | ✅ Automatic
|
|
67
|
-
| DPI downscaling | ✅
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
56
|
+
| | **qpdf-compress** | qpdf CLI | Ghostscript |
|
|
57
|
+
| ------------------------- | --------------------- | ----------------- | ----------------- |
|
|
58
|
+
| Integration | Native Node.js addon | Shell exec | Shell exec |
|
|
59
|
+
| Async I/O | ✅ Non-blocking | ❌ Blocks on exec | ❌ Blocks on exec |
|
|
60
|
+
| Image deduplication | ✅ | ❌ | ❌ |
|
|
61
|
+
| JPEG Huffman optimization | ✅ Lossless (libjpeg) | ❌ | ❌ |
|
|
62
|
+
| Lossy image compression | ✅ Auto quality | ❌ | ✅ |
|
|
63
|
+
| CMYK → RGB conversion | ✅ Automatic | ❌ | ✅ |
|
|
64
|
+
| DPI downscaling | ✅ Lossy mode | ❌ | ✅ |
|
|
65
|
+
| Grayscale detection | ✅ Automatic | ❌ | ❌ |
|
|
66
|
+
| Bitonal conversion | ✅ Automatic | ❌ | ❌ |
|
|
67
|
+
| Font subsetting | ✅ TrueType glyph | ❌ | ❌ |
|
|
68
|
+
| Unused font removal | ✅ Automatic | ❌ | ❌ |
|
|
69
|
+
| ICC profile stripping | ✅ Automatic | ❌ | ❌ |
|
|
70
|
+
| Form flattening | ✅ Automatic | ❌ | ❌ |
|
|
71
|
+
| Stream deduplication | ✅ Automatic | ❌ | ❌ |
|
|
72
|
+
| Content minification | ✅ Automatic | ❌ | ❌ |
|
|
73
|
+
| JS/embedded file removal | ✅ Automatic | ❌ | ❌ |
|
|
74
|
+
| Metadata stripping | ✅ Default on | ✅ Manual flag | ✅ |
|
|
75
|
+
| PDF repair | ✅ Automatic | ✅ Manual flag | ⚠️ Partial |
|
|
76
|
+
| License | Apache-2.0 | Apache-2.0 | AGPL-3.0 ⚠️ |
|
|
77
|
+
| Dependencies | None¹ | System binary | System binary |
|
|
73
78
|
|
|
74
79
|
¹ QPDF is statically linked — no runtime dependencies. Prebuilt binaries downloaded at install.
|
|
75
80
|
|
|
@@ -116,76 +121,101 @@ vcpkg install zlib libjpeg-turbo --triplet x64-windows-static
|
|
|
116
121
|
```typescript
|
|
117
122
|
import { compress } from 'qpdf-compress';
|
|
118
123
|
|
|
119
|
-
// lossless —
|
|
120
|
-
const optimized = await compress(pdfBuffer
|
|
121
|
-
|
|
122
|
-
// lossy — auto quality per image (skips JPEGs ≤ q90, encodes rest at q85)
|
|
123
|
-
const smaller = await compress(pdfBuffer, { mode: 'lossy' });
|
|
124
|
+
// lossless (default) — pure structural optimization, no image re-encoding
|
|
125
|
+
const optimized = await compress(pdfBuffer);
|
|
124
126
|
|
|
125
|
-
// lossy
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
// control DPI downscaling (default: 75, 0 = disabled)
|
|
129
|
-
const highRes = await compress(pdfBuffer, { mode: 'lossless', maxDpi: 150 });
|
|
130
|
-
const noDpi = await compress(pdfBuffer, { mode: 'lossless', maxDpi: 0 });
|
|
127
|
+
// lossy — image recompression + downscale to 72 DPI
|
|
128
|
+
const smaller = await compress(pdfBuffer, { lossy: true });
|
|
131
129
|
|
|
132
130
|
// keep metadata (stripped by default)
|
|
133
|
-
const withMeta = await compress(pdfBuffer, {
|
|
131
|
+
const withMeta = await compress(pdfBuffer, { stripMetadata: false });
|
|
134
132
|
|
|
135
133
|
// file path input (avoids copying into memory twice)
|
|
136
|
-
const result = await compress('/path/to/file.pdf'
|
|
134
|
+
const result = await compress('/path/to/file.pdf');
|
|
137
135
|
|
|
138
136
|
// write directly to file instead of returning a Buffer
|
|
139
|
-
await compress(pdfBuffer, {
|
|
137
|
+
await compress(pdfBuffer, { output: '/path/to/output.pdf' });
|
|
140
138
|
|
|
141
139
|
// damaged PDFs are automatically repaired during compression
|
|
142
|
-
const fixed = await compress(damagedBuffer
|
|
140
|
+
const fixed = await compress(damagedBuffer);
|
|
143
141
|
```
|
|
144
142
|
|
|
145
143
|
## 📖 API
|
|
146
144
|
|
|
147
|
-
### `compress(input, options): Promise<Buffer>`
|
|
145
|
+
### `compress(input, options?): Promise<Buffer>`
|
|
148
146
|
|
|
149
147
|
### `compress(input, options & { output: string }): Promise<void>`
|
|
150
148
|
|
|
151
149
|
Compresses a PDF document. Automatically repairs damaged PDFs.
|
|
152
150
|
|
|
153
|
-
| Parameter | Type
|
|
154
|
-
| ----------------------- |
|
|
155
|
-
| `input` | `Buffer \| string`
|
|
156
|
-
| `options.
|
|
157
|
-
| `options.
|
|
158
|
-
| `options.
|
|
159
|
-
| `options.stripMetadata` | `boolean` | Remove XMP metadata, document info, and thumbnails. Default: `true` |
|
|
160
|
-
| `options.output` | `string` | Write to file path instead of returning a `Buffer` |
|
|
151
|
+
| Parameter | Type | Description |
|
|
152
|
+
| ----------------------- | ------------------ | ------------------------------------------------------------------- |
|
|
153
|
+
| `input` | `Buffer \| string` | PDF data or file path |
|
|
154
|
+
| `options.lossy` | `boolean` | Enable lossy compression. Default: `false` |
|
|
155
|
+
| `options.stripMetadata` | `boolean` | Remove XMP metadata, document info, and thumbnails. Default: `true` |
|
|
156
|
+
| `options.output` | `string` | Write to file path instead of returning a `Buffer` |
|
|
161
157
|
|
|
162
158
|
**Both modes:**
|
|
163
159
|
|
|
164
|
-
- Deduplicates identical images across pages
|
|
160
|
+
- Deduplicates identical images and non-image streams across pages
|
|
161
|
+
- Detects and converts RGB images that are actually grayscale (3× raw data reduction)
|
|
162
|
+
- Converts effectively black-and-white grayscale images to 1-bit (8× raw data reduction)
|
|
165
163
|
- Optimizes embedded JPEG Huffman tables (2–15% savings, zero quality loss)
|
|
164
|
+
- Optimizes soft mask (transparency) JPEG streams
|
|
165
|
+
- Removes unused font resources from pages
|
|
166
|
+
- Subsets TrueType fonts — strips unused glyph outlines from font programs
|
|
167
|
+
- Strips ICC color profiles, replacing with Device equivalents
|
|
168
|
+
- Flattens interactive forms (AcroForm) into page content
|
|
169
|
+
- Flattens page tree (pushes inherited attributes to pages)
|
|
170
|
+
- Coalesces multiple content streams per page into one
|
|
171
|
+
- Minifies content streams (whitespace normalization, numeric formatting)
|
|
172
|
+
- Strips embedded files and JavaScript actions
|
|
166
173
|
- Recompresses all decodable streams with Flate level 9
|
|
167
174
|
- Generates object streams for smaller metadata overhead
|
|
168
|
-
- Removes unreferenced objects
|
|
169
|
-
- Downscales images exceeding `maxDpi` (default: 75 DPI)
|
|
175
|
+
- Removes unreferenced objects
|
|
170
176
|
- Strips XMP metadata, document info, and thumbnails (default: on)
|
|
171
|
-
- Converts CMYK and ICCBased color spaces to RGB
|
|
172
177
|
- Automatically repairs damaged PDFs
|
|
173
178
|
|
|
174
|
-
**
|
|
179
|
+
**Lossless (default):**
|
|
175
180
|
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
- Pure structural optimization — no image re-encoding or downscaling
|
|
182
|
+
- Visually identical to the original
|
|
183
|
+
|
|
184
|
+
**Lossy** (`lossy: true`):
|
|
185
|
+
|
|
186
|
+
- Re-encodes JPEGs above q65 at q75 (skips images already at or below target)
|
|
187
|
+
- Downscales images exceeding 72 DPI using CTM-based rendered size detection
|
|
188
|
+
- Converts CMYK and ICCBased color spaces to RGB
|
|
189
|
+
- Only replaces images where the result is actually smaller
|
|
181
190
|
- Skips tiny images (< 50×50 px)
|
|
182
191
|
|
|
183
192
|
## ⚙️ How it works
|
|
184
193
|
|
|
185
|
-
This package embeds [QPDF](https://github.com/qpdf/qpdf) (v12.3.2) as a statically linked C++ library, exposed to Node.js via N-API. Lossless JPEG optimization uses [libjpeg-turbo](https://libjpeg-turbo.org/) at the DCT coefficient level. Image recompression in lossy mode also uses libjpeg-turbo for JPEG encoding.
|
|
194
|
+
This package embeds [QPDF](https://github.com/qpdf/qpdf) (v12.3.2) as a statically linked C++ library, exposed to Node.js via N-API. Lossless JPEG optimization uses [libjpeg-turbo](https://libjpeg-turbo.org/) at the DCT coefficient level. Image recompression in lossy mode also uses libjpeg-turbo for JPEG encoding. TrueType font subsetting is handled by a custom binary parser that reads cmap tables, resolves composite glyph dependencies, and rebuilds glyf/loca/hmtx tables with only the used glyphs.
|
|
186
195
|
|
|
187
196
|
All operations run in a background thread via `Napi::AsyncWorker`, so the event loop is never blocked.
|
|
188
197
|
|
|
198
|
+
### Compression pipeline (execution order)
|
|
199
|
+
|
|
200
|
+
1. Deduplicate identical images
|
|
201
|
+
2. Convert grayscale RGB images to DeviceGray
|
|
202
|
+
3. Convert bitonal grayscale images to 1-bit
|
|
203
|
+
4. Flatten page tree (push inherited attributes)
|
|
204
|
+
5. _(lossy only)_ Re-encode high-quality JPEGs at q75
|
|
205
|
+
6. _(lossy only)_ Downscale images above 72 DPI
|
|
206
|
+
7. Optimize existing JPEG Huffman tables
|
|
207
|
+
8. Optimize soft mask JPEG streams
|
|
208
|
+
9. Remove unused font resources
|
|
209
|
+
10. Subset TrueType fonts (strip unused glyphs)
|
|
210
|
+
11. Strip ICC color profiles
|
|
211
|
+
12. Flatten interactive forms into page content
|
|
212
|
+
13. Coalesce multiple content streams per page
|
|
213
|
+
14. Minify content streams
|
|
214
|
+
15. Deduplicate identical non-image streams
|
|
215
|
+
16. Strip embedded files and JavaScript
|
|
216
|
+
17. _(optional)_ Strip metadata
|
|
217
|
+
18. QPDFWriter: Flate 9, object streams, unreferenced object removal
|
|
218
|
+
|
|
189
219
|
## License
|
|
190
220
|
|
|
191
221
|
Apache-2.0
|
package/binding.gyp
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -3,16 +3,18 @@ type PdfInput = Buffer | string;
|
|
|
3
3
|
/**
|
|
4
4
|
* Compresses a PDF document. Automatically repairs damaged PDFs.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* By default (lossless), deduplicates images, re-encodes very high quality
|
|
7
|
+
* JPEGs (q91+) at q85, downscales images to 150 DPI, optimizes embedded
|
|
8
|
+
* JPEG Huffman tables, recompresses all streams with Flate level 9,
|
|
9
|
+
* generates object streams, and removes unreferenced objects.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* With `lossy: true`, uses more aggressive image re-encoding (skips JPEGs
|
|
12
|
+
* at q65 or below, re-encodes the rest at q75) and downscales to 72 DPI.
|
|
13
|
+
* Text, vectors, and fonts are preserved.
|
|
12
14
|
*/
|
|
13
15
|
export declare function compress(input: PdfInput, options: CompressOptions & {
|
|
14
16
|
output: string;
|
|
15
17
|
}): Promise<void>;
|
|
16
|
-
export declare function compress(input: PdfInput, options
|
|
18
|
+
export declare function compress(input: PdfInput, options?: CompressOptions): Promise<Buffer>;
|
|
17
19
|
export type { CompressOptions } from './types.js';
|
|
18
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAY/D,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAY/D,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAqBtF,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,22 +22,11 @@ export async function compress(input, options) {
|
|
|
22
22
|
else {
|
|
23
23
|
throw new TypeError('Input must be a Buffer or file path string');
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
if (mode !== 'lossy' && mode !== 'lossless') {
|
|
27
|
-
throw new TypeError("Mode must be 'lossy' or 'lossless'");
|
|
28
|
-
}
|
|
29
|
-
const quality = options.quality ?? 0;
|
|
30
|
-
if (quality !== 0 && (quality < 1 || quality > 100)) {
|
|
31
|
-
throw new RangeError('Quality must be between 1 and 100');
|
|
32
|
-
}
|
|
33
|
-
const maxDpi = options.maxDpi ?? 75;
|
|
34
|
-
const stripMetadata = options.stripMetadata ?? true;
|
|
25
|
+
const stripMetadata = options?.stripMetadata ?? true;
|
|
35
26
|
return addon.compress(input, {
|
|
36
|
-
|
|
37
|
-
quality,
|
|
38
|
-
...(maxDpi > 0 ? { maxDpi } : {}),
|
|
27
|
+
...(options?.lossy ? { lossy: true } : {}),
|
|
39
28
|
...(stripMetadata ? { stripMetadata: true } : {}),
|
|
40
|
-
...(options
|
|
29
|
+
...(options?.output ? { output: options.output } : {}),
|
|
41
30
|
});
|
|
42
31
|
}
|
|
43
32
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,KAAK,GAAgB,OAAO,CAAC,qCAAqC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,KAAK,GAAgB,OAAO,CAAC,qCAAqC,CAAC,CAAC;AAqB1E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAe,EAAE,OAAyB;IACvE,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;IACrD,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC3B,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAA2B,CAAC;AAC/B,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export interface CompressOptions {
|
|
2
|
-
/**
|
|
3
|
-
readonly
|
|
4
|
-
/** JPEG quality for lossy mode (1–100). When omitted, automatically determines optimal quality per image (capped at 85). */
|
|
5
|
-
readonly quality?: number;
|
|
6
|
-
/** Maximum image DPI. Images exceeding this are downscaled. 0 = no limit. Default: 75. */
|
|
7
|
-
readonly maxDpi?: number;
|
|
2
|
+
/** Enable lossy compression for more aggressive size reduction. Default: false. */
|
|
3
|
+
readonly lossy?: boolean;
|
|
8
4
|
/** Remove XMP metadata, document info, and thumbnails. Default: true. */
|
|
9
5
|
readonly stripMetadata?: boolean;
|
|
10
6
|
/** Write to this file path instead of returning a Buffer. */
|
|
@@ -12,9 +8,7 @@ export interface CompressOptions {
|
|
|
12
8
|
}
|
|
13
9
|
export interface NativeAddon {
|
|
14
10
|
compress(input: Buffer | string, options: {
|
|
15
|
-
|
|
16
|
-
quality: number;
|
|
17
|
-
maxDpi?: number;
|
|
11
|
+
lossy?: boolean;
|
|
18
12
|
stripMetadata?: boolean;
|
|
19
13
|
output?: string;
|
|
20
14
|
}): Promise<Buffer | undefined>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../lib/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../lib/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CACN,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GACA,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAChC"}
|
package/lib/index.ts
CHANGED
|
@@ -18,19 +18,21 @@ type PdfInput = Buffer | string;
|
|
|
18
18
|
/**
|
|
19
19
|
* Compresses a PDF document. Automatically repairs damaged PDFs.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* By default (lossless), deduplicates images, re-encodes very high quality
|
|
22
|
+
* JPEGs (q91+) at q85, downscales images to 150 DPI, optimizes embedded
|
|
23
|
+
* JPEG Huffman tables, recompresses all streams with Flate level 9,
|
|
24
|
+
* generates object streams, and removes unreferenced objects.
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
26
|
+
* With `lossy: true`, uses more aggressive image re-encoding (skips JPEGs
|
|
27
|
+
* at q65 or below, re-encodes the rest at q75) and downscales to 72 DPI.
|
|
28
|
+
* Text, vectors, and fonts are preserved.
|
|
27
29
|
*/
|
|
28
30
|
export function compress(
|
|
29
31
|
input: PdfInput,
|
|
30
32
|
options: CompressOptions & { output: string },
|
|
31
33
|
): Promise<void>;
|
|
32
|
-
export function compress(input: PdfInput, options
|
|
33
|
-
export async function compress(input: PdfInput, options
|
|
34
|
+
export function compress(input: PdfInput, options?: CompressOptions): Promise<Buffer>;
|
|
35
|
+
export async function compress(input: PdfInput, options?: CompressOptions): Promise<Buffer | void> {
|
|
34
36
|
if (Buffer.isBuffer(input)) {
|
|
35
37
|
if (input.length === 0) {
|
|
36
38
|
throw new TypeError('Input buffer cannot be empty');
|
|
@@ -42,22 +44,11 @@ export async function compress(input: PdfInput, options: CompressOptions): Promi
|
|
|
42
44
|
} else {
|
|
43
45
|
throw new TypeError('Input must be a Buffer or file path string');
|
|
44
46
|
}
|
|
45
|
-
const
|
|
46
|
-
if (mode !== 'lossy' && mode !== 'lossless') {
|
|
47
|
-
throw new TypeError("Mode must be 'lossy' or 'lossless'");
|
|
48
|
-
}
|
|
49
|
-
const quality = options.quality ?? 0;
|
|
50
|
-
if (quality !== 0 && (quality < 1 || quality > 100)) {
|
|
51
|
-
throw new RangeError('Quality must be between 1 and 100');
|
|
52
|
-
}
|
|
53
|
-
const maxDpi = options.maxDpi ?? 75;
|
|
54
|
-
const stripMetadata = options.stripMetadata ?? true;
|
|
47
|
+
const stripMetadata = options?.stripMetadata ?? true;
|
|
55
48
|
return addon.compress(input, {
|
|
56
|
-
|
|
57
|
-
quality,
|
|
58
|
-
...(maxDpi > 0 ? { maxDpi } : {}),
|
|
49
|
+
...(options?.lossy ? { lossy: true } : {}),
|
|
59
50
|
...(stripMetadata ? { stripMetadata: true } : {}),
|
|
60
|
-
...(options
|
|
51
|
+
...(options?.output ? { output: options.output } : {}),
|
|
61
52
|
}) as Promise<Buffer | void>;
|
|
62
53
|
}
|
|
63
54
|
|
package/lib/types.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export interface CompressOptions {
|
|
2
|
-
/**
|
|
3
|
-
readonly
|
|
4
|
-
/** JPEG quality for lossy mode (1–100). When omitted, automatically determines optimal quality per image (capped at 85). */
|
|
5
|
-
readonly quality?: number;
|
|
6
|
-
/** Maximum image DPI. Images exceeding this are downscaled. 0 = no limit. Default: 75. */
|
|
7
|
-
readonly maxDpi?: number;
|
|
2
|
+
/** Enable lossy compression for more aggressive size reduction. Default: false. */
|
|
3
|
+
readonly lossy?: boolean;
|
|
8
4
|
/** Remove XMP metadata, document info, and thumbnails. Default: true. */
|
|
9
5
|
readonly stripMetadata?: boolean;
|
|
10
6
|
/** Write to this file path instead of returning a Buffer. */
|
|
@@ -15,9 +11,7 @@ export interface NativeAddon {
|
|
|
15
11
|
compress(
|
|
16
12
|
input: Buffer | string,
|
|
17
13
|
options: {
|
|
18
|
-
|
|
19
|
-
quality: number; // 0 = auto, 1–100 = fixed
|
|
20
|
-
maxDpi?: number;
|
|
14
|
+
lossy?: boolean;
|
|
21
15
|
stripMetadata?: boolean;
|
|
22
16
|
output?: string;
|
|
23
17
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qpdf-compress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Native PDF compression for Node.js, powered by QPDF",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -88,4 +88,4 @@
|
|
|
88
88
|
"typescript-eslint": "^8.57.2",
|
|
89
89
|
"vitest": "^4.1.2"
|
|
90
90
|
}
|
|
91
|
-
}
|
|
91
|
+
}
|