pdf-oxide 0.3.24

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 (62) hide show
  1. package/README.md +218 -0
  2. package/binding.gyp +35 -0
  3. package/package.json +78 -0
  4. package/src/builders/annotation-builder.ts +367 -0
  5. package/src/builders/conversion-options-builder.ts +257 -0
  6. package/src/builders/index.ts +12 -0
  7. package/src/builders/metadata-builder.ts +317 -0
  8. package/src/builders/pdf-builder.ts +386 -0
  9. package/src/builders/search-options-builder.ts +151 -0
  10. package/src/document-editor-manager.ts +318 -0
  11. package/src/errors.ts +1629 -0
  12. package/src/form-field-manager.ts +666 -0
  13. package/src/hybrid-ml-manager.ts +283 -0
  14. package/src/index.ts +453 -0
  15. package/src/managers/accessibility-manager.ts +338 -0
  16. package/src/managers/annotation-manager.ts +439 -0
  17. package/src/managers/barcode-manager.ts +235 -0
  18. package/src/managers/batch-manager.ts +533 -0
  19. package/src/managers/cache-manager.ts +486 -0
  20. package/src/managers/compliance-manager.ts +375 -0
  21. package/src/managers/content-manager.ts +339 -0
  22. package/src/managers/document-utility-manager.ts +922 -0
  23. package/src/managers/dom-pdf-creator.ts +365 -0
  24. package/src/managers/editing-manager.ts +514 -0
  25. package/src/managers/enterprise-manager.ts +478 -0
  26. package/src/managers/extended-managers.ts +437 -0
  27. package/src/managers/extraction-manager.ts +583 -0
  28. package/src/managers/final-utilities.ts +429 -0
  29. package/src/managers/hybrid-ml-advanced.ts +479 -0
  30. package/src/managers/index.ts +239 -0
  31. package/src/managers/layer-manager.ts +500 -0
  32. package/src/managers/metadata-manager.ts +303 -0
  33. package/src/managers/ocr-manager.ts +756 -0
  34. package/src/managers/optimization-manager.ts +262 -0
  35. package/src/managers/outline-manager.ts +196 -0
  36. package/src/managers/page-manager.ts +289 -0
  37. package/src/managers/pattern-detection.ts +440 -0
  38. package/src/managers/rendering-manager.ts +863 -0
  39. package/src/managers/search-manager.ts +385 -0
  40. package/src/managers/security-manager.ts +345 -0
  41. package/src/managers/signature-manager.ts +1664 -0
  42. package/src/managers/streams.ts +618 -0
  43. package/src/managers/xfa-manager.ts +500 -0
  44. package/src/pdf-creator-manager.ts +494 -0
  45. package/src/properties.ts +522 -0
  46. package/src/result-accessors-manager.ts +867 -0
  47. package/src/tests/advanced-features.test.ts +414 -0
  48. package/src/tests/advanced.test.ts +266 -0
  49. package/src/tests/extended-managers.test.ts +316 -0
  50. package/src/tests/final-utilities.test.ts +455 -0
  51. package/src/tests/foundation.test.ts +315 -0
  52. package/src/tests/high-demand.test.ts +257 -0
  53. package/src/tests/specialized.test.ts +97 -0
  54. package/src/thumbnail-manager.ts +272 -0
  55. package/src/types/common.ts +142 -0
  56. package/src/types/document-types.ts +457 -0
  57. package/src/types/index.ts +6 -0
  58. package/src/types/manager-types.ts +284 -0
  59. package/src/types/native-bindings.ts +517 -0
  60. package/src/workers/index.ts +7 -0
  61. package/src/workers/pool.ts +274 -0
  62. package/src/workers/worker.ts +131 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * OptimizationManager - PDF Optimization Operations
3
+ *
4
+ * Provides document optimization capabilities including:
5
+ * - Font subsetting
6
+ * - Image downsampling
7
+ * - Object deduplication
8
+ * - Full optimization pipeline
9
+ *
10
+ * @since 1.0.0
11
+ */
12
+
13
+ import { EventEmitter } from 'events';
14
+ import { mapFfiErrorCode, OptimizationException } from '../errors';
15
+
16
+ // =============================================================================
17
+ // Type Definitions
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Result of an optimization operation.
22
+ */
23
+ export interface OptimizationResult {
24
+ /** Whether the optimization succeeded */
25
+ readonly success: boolean;
26
+ /** Number of bytes saved */
27
+ readonly bytesSaved: number;
28
+ /** Original document size in bytes */
29
+ readonly originalSize: number;
30
+ /** Optimized document size in bytes */
31
+ readonly optimizedSize: number;
32
+ /** Compression ratio (0.0 - 1.0) */
33
+ readonly compressionRatio: number;
34
+ }
35
+
36
+ // =============================================================================
37
+ // OptimizationManager
38
+ // =============================================================================
39
+
40
+ /**
41
+ * Manager for PDF optimization operations.
42
+ *
43
+ * Provides methods for reducing PDF file size through font subsetting,
44
+ * image downsampling, object deduplication, and combined optimization.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const optimizer = new OptimizationManager(document);
49
+ *
50
+ * // Subset fonts to remove unused glyphs
51
+ * const fontResult = await optimizer.subsetFonts();
52
+ * console.log(`Font subsetting saved ${fontResult.bytesSaved} bytes`);
53
+ *
54
+ * // Downsample high-resolution images
55
+ * const imageResult = await optimizer.downsampleImages(150, 80);
56
+ *
57
+ * // Full optimization pipeline
58
+ * const fullResult = await optimizer.optimizeFull(150, 80);
59
+ * console.log(`Total savings: ${fullResult.bytesSaved} bytes`);
60
+ * ```
61
+ */
62
+ export class OptimizationManager extends EventEmitter {
63
+ private document: any;
64
+ private native: any;
65
+
66
+ constructor(document: any) {
67
+ super();
68
+ if (!document) {
69
+ throw new Error('Document cannot be null or undefined');
70
+ }
71
+ this.document = document;
72
+ try {
73
+ this.native = require('../../index.node');
74
+ } catch {
75
+ this.native = null;
76
+ }
77
+ }
78
+
79
+ // ===========================================================================
80
+ // Optimization Operations
81
+ // ===========================================================================
82
+
83
+ /**
84
+ * Subsets all embedded fonts in the document.
85
+ *
86
+ * Removes unused glyphs from embedded fonts, reducing file size
87
+ * while preserving visual fidelity for the characters actually used.
88
+ *
89
+ * @returns Optimization result with bytes saved
90
+ * @throws OptimizationException if the operation fails
91
+ */
92
+ async subsetFonts(): Promise<OptimizationResult> {
93
+ if (!this.native?.pdf_optimize_subset_fonts) {
94
+ throw new OptimizationException('Native optimization not available: pdf_optimize_subset_fonts not found');
95
+ }
96
+
97
+ const errorCode = Buffer.alloc(4);
98
+ const resultPtr = this.native.pdf_optimize_subset_fonts(
99
+ this.document._handle ?? this.document,
100
+ errorCode,
101
+ );
102
+ const code = errorCode.readInt32LE(0);
103
+
104
+ if (code !== 0) {
105
+ throw mapFfiErrorCode(code, 'Failed to subset fonts');
106
+ }
107
+
108
+ const result = this.parseOptimizationResult(resultPtr);
109
+ this.emit('fonts-subsetted', { bytesSaved: result.bytesSaved });
110
+
111
+ this.freeOptimizationResult(resultPtr);
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * Downsamples images in the document to reduce file size.
117
+ *
118
+ * @param dpi - Target resolution in dots per inch (default: 150)
119
+ * @param quality - JPEG quality for recompression (1-100, default: 80)
120
+ * @returns Optimization result with bytes saved
121
+ * @throws OptimizationException if the operation fails
122
+ */
123
+ async downsampleImages(dpi?: number, quality?: number): Promise<OptimizationResult> {
124
+ if (!this.native?.pdf_optimize_downsample_images) {
125
+ throw new OptimizationException('Native optimization not available: pdf_optimize_downsample_images not found');
126
+ }
127
+
128
+ const errorCode = Buffer.alloc(4);
129
+ const resultPtr = this.native.pdf_optimize_downsample_images(
130
+ this.document._handle ?? this.document,
131
+ dpi ?? 150,
132
+ quality ?? 80,
133
+ errorCode,
134
+ );
135
+ const code = errorCode.readInt32LE(0);
136
+
137
+ if (code !== 0) {
138
+ throw mapFfiErrorCode(code, 'Failed to downsample images');
139
+ }
140
+
141
+ const result = this.parseOptimizationResult(resultPtr);
142
+ this.emit('images-downsampled', { dpi: dpi ?? 150, quality: quality ?? 80, bytesSaved: result.bytesSaved });
143
+
144
+ this.freeOptimizationResult(resultPtr);
145
+ return result;
146
+ }
147
+
148
+ /**
149
+ * Deduplicates identical objects in the document.
150
+ *
151
+ * Identifies and merges duplicate fonts, images, and other resources
152
+ * that appear multiple times in the document.
153
+ *
154
+ * @returns Optimization result with bytes saved
155
+ * @throws OptimizationException if the operation fails
156
+ */
157
+ async deduplicate(): Promise<OptimizationResult> {
158
+ if (!this.native?.pdf_optimize_deduplicate) {
159
+ throw new OptimizationException('Native optimization not available: pdf_optimize_deduplicate not found');
160
+ }
161
+
162
+ const errorCode = Buffer.alloc(4);
163
+ const resultPtr = this.native.pdf_optimize_deduplicate(
164
+ this.document._handle ?? this.document,
165
+ errorCode,
166
+ );
167
+ const code = errorCode.readInt32LE(0);
168
+
169
+ if (code !== 0) {
170
+ throw mapFfiErrorCode(code, 'Failed to deduplicate objects');
171
+ }
172
+
173
+ const result = this.parseOptimizationResult(resultPtr);
174
+ this.emit('deduplicated', { bytesSaved: result.bytesSaved });
175
+
176
+ this.freeOptimizationResult(resultPtr);
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * Runs the full optimization pipeline.
182
+ *
183
+ * Combines font subsetting, image downsampling, and object deduplication
184
+ * into a single operation for maximum file size reduction.
185
+ *
186
+ * @param dpi - Target image resolution in dots per inch (default: 150)
187
+ * @param quality - JPEG quality for recompression (1-100, default: 80)
188
+ * @returns Optimization result with total bytes saved
189
+ * @throws OptimizationException if the operation fails
190
+ */
191
+ async optimizeFull(dpi?: number, quality?: number): Promise<OptimizationResult> {
192
+ if (!this.native?.pdf_optimize_full) {
193
+ throw new OptimizationException('Native optimization not available: pdf_optimize_full not found');
194
+ }
195
+
196
+ const errorCode = Buffer.alloc(4);
197
+ const resultPtr = this.native.pdf_optimize_full(
198
+ this.document._handle ?? this.document,
199
+ dpi ?? 150,
200
+ quality ?? 80,
201
+ errorCode,
202
+ );
203
+ const code = errorCode.readInt32LE(0);
204
+
205
+ if (code !== 0) {
206
+ throw mapFfiErrorCode(code, 'Failed to run full optimization');
207
+ }
208
+
209
+ const result = this.parseOptimizationResult(resultPtr);
210
+ this.emit('optimized-full', { dpi: dpi ?? 150, quality: quality ?? 80, bytesSaved: result.bytesSaved });
211
+
212
+ this.freeOptimizationResult(resultPtr);
213
+ return result;
214
+ }
215
+
216
+ // ===========================================================================
217
+ // Private Helpers
218
+ // ===========================================================================
219
+
220
+ private parseOptimizationResult(resultPtr: any): OptimizationResult {
221
+ if (!resultPtr) {
222
+ return { success: true, bytesSaved: 0, originalSize: 0, optimizedSize: 0, compressionRatio: 0 };
223
+ }
224
+
225
+ if (typeof resultPtr === 'string') {
226
+ try {
227
+ return JSON.parse(resultPtr);
228
+ } catch {
229
+ return { success: true, bytesSaved: 0, originalSize: 0, optimizedSize: 0, compressionRatio: 0 };
230
+ }
231
+ }
232
+
233
+ // Handle native result handle
234
+ const bytesSaved = this.native?.pdf_optimization_result_bytes_saved?.(resultPtr) ?? 0;
235
+ return {
236
+ success: true,
237
+ bytesSaved,
238
+ originalSize: 0,
239
+ optimizedSize: 0,
240
+ compressionRatio: 0,
241
+ };
242
+ }
243
+
244
+ private freeOptimizationResult(resultPtr: any): void {
245
+ if (resultPtr && typeof resultPtr !== 'string' && this.native?.pdf_optimization_result_free) {
246
+ this.native.pdf_optimization_result_free(resultPtr);
247
+ }
248
+ }
249
+
250
+ // ===========================================================================
251
+ // Cleanup
252
+ // ===========================================================================
253
+
254
+ /**
255
+ * Releases resources held by this manager.
256
+ */
257
+ destroy(): void {
258
+ this.removeAllListeners();
259
+ }
260
+ }
261
+
262
+ export default OptimizationManager;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Manager for PDF document outlines (bookmarks)
3
+ *
4
+ * Provides functionality for reading and navigating the document's outline tree,
5
+ * which represents the hierarchical bookmark structure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { OutlineManager } from 'pdf_oxide';
10
+ *
11
+ * const doc = PdfDocument.open('document.pdf');
12
+ * const outlineManager = new OutlineManager(doc);
13
+ *
14
+ * if (outlineManager.hasOutlines()) {
15
+ * const outlines = outlineManager.getOutlines();
16
+ * console.log(`Found ${outlines.length} outline items`);
17
+ * }
18
+ * ```
19
+ */
20
+
21
+ export interface OutlineItem {
22
+ title: string;
23
+ pageIndex: number;
24
+ pageNumber: number | null;
25
+ level: number;
26
+ }
27
+
28
+ export class OutlineManager {
29
+ private _document: any;
30
+
31
+ /**
32
+ * Creates a new OutlineManager for the given document
33
+ * @param document - The PDF document
34
+ * @throws Error if document is null or undefined
35
+ */
36
+ constructor(document: any) {
37
+ if (!document) {
38
+ throw new Error('Document is required');
39
+ }
40
+ this._document = document;
41
+ }
42
+
43
+ /**
44
+ * Checks if the document has an outline (bookmarks)
45
+ * @returns True if the document has outlines
46
+ */
47
+ hasOutlines(): boolean {
48
+ try {
49
+ return this._document.hasOutlines();
50
+ } catch (error) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Gets the number of top-level outline items
57
+ * @returns Number of outline items
58
+ */
59
+ getOutlineCount(): number {
60
+ try {
61
+ return this._document.getOutlineCount();
62
+ } catch (error) {
63
+ return 0;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Gets all outline items (flattened)
69
+ * @returns Array of outline items
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const outlines = manager.getOutlines();
74
+ * outlines.forEach(item => {
75
+ * console.log(`${item.title} -> Page ${item.pageNumber}`);
76
+ * });
77
+ * ```
78
+ */
79
+ getOutlines(): OutlineItem[] {
80
+ try {
81
+ const rawOutlines = this._document.getOutlines();
82
+ // Convert native OutlineInfo to OutlineItem format expected by JS
83
+ return rawOutlines.map((item: any) => ({
84
+ title: item.title,
85
+ pageIndex: item.pageIndex,
86
+ pageNumber: item.pageIndex >= 0 ? item.pageIndex + 1 : null,
87
+ level: item.level,
88
+ }));
89
+ } catch (error) {
90
+ return [];
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Finds an outline item by title (case-insensitive substring match)
96
+ * @param titleFragment - Partial title to search for
97
+ * @returns Matching outline item or null
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const item = manager.findByTitle('Introduction');
102
+ * if (item) {
103
+ * console.log(`Found: ${item.title} on page ${item.pageNumber}`);
104
+ * }
105
+ * ```
106
+ */
107
+ findByTitle(titleFragment: string): OutlineItem | null {
108
+ if (!titleFragment || typeof titleFragment !== 'string') {
109
+ throw new Error('Title fragment must be a non-empty string');
110
+ }
111
+
112
+ const outlines = this.getOutlines();
113
+ const fragment = titleFragment.toLowerCase();
114
+
115
+ for (const item of outlines) {
116
+ if (item.title && item.title.toLowerCase().includes(fragment)) {
117
+ return item;
118
+ }
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ /**
125
+ * Finds all outline items by title (case-insensitive substring match)
126
+ * @param titleFragment - Partial title to search for
127
+ * @returns Array of matching outline items
128
+ */
129
+ findAllByTitle(titleFragment: string): OutlineItem[] {
130
+ if (!titleFragment || typeof titleFragment !== 'string') {
131
+ throw new Error('Title fragment must be a non-empty string');
132
+ }
133
+
134
+ const outlines = this.getOutlines();
135
+ const fragment = titleFragment.toLowerCase();
136
+
137
+ return outlines.filter((item) =>
138
+ item.title && item.title.toLowerCase().includes(fragment)
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Gets outline items for a specific page
144
+ * @param pageIndex - Zero-based page index
145
+ * @returns Outline items on that page
146
+ */
147
+ getOutlinesForPage(pageIndex: number): OutlineItem[] {
148
+ if (typeof pageIndex !== 'number' || pageIndex < 0) {
149
+ throw new Error('Page index must be a non-negative number');
150
+ }
151
+
152
+ const outlines = this.getOutlines();
153
+ return outlines.filter((item) => item.pageIndex === pageIndex);
154
+ }
155
+
156
+ /**
157
+ * Checks if a specific page has outline items
158
+ * @param pageIndex - Zero-based page index
159
+ * @returns True if page has outline items
160
+ */
161
+ pageHasOutlines(pageIndex: number): boolean {
162
+ if (typeof pageIndex !== 'number' || pageIndex < 0) {
163
+ throw new Error('Page index must be a non-negative number');
164
+ }
165
+
166
+ return this.getOutlinesForPage(pageIndex).length > 0;
167
+ }
168
+
169
+ /**
170
+ * Gets an outline item by index
171
+ * @param index - Item index
172
+ * @returns Outline item or null if not found
173
+ */
174
+ getOutlineAt(index: number): OutlineItem | null {
175
+ if (typeof index !== 'number' || index < 0) {
176
+ throw new Error('Index must be a non-negative number');
177
+ }
178
+
179
+ const outlines = this.getOutlines();
180
+ return outlines[index] || null;
181
+ }
182
+
183
+ /**
184
+ * Checks if a page number exists in the outline
185
+ * @param pageNumber - One-based page number
186
+ * @returns True if page appears in outline
187
+ */
188
+ containsPageNumber(pageNumber: number): boolean {
189
+ if (typeof pageNumber !== 'number' || pageNumber < 1) {
190
+ throw new Error('Page number must be a positive number');
191
+ }
192
+
193
+ const outlines = this.getOutlines();
194
+ return outlines.some((item) => item.pageNumber === pageNumber);
195
+ }
196
+ }