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,922 @@
1
+ /**
2
+ * Document Utility Manager - Document optimization and manipulation utilities
3
+ *
4
+ * Provides comprehensive document utilities:
5
+ * - Document optimization and compression
6
+ * - PDF linearization (fast web view)
7
+ * - Font optimization and subsetting
8
+ * - Image optimization and recompression
9
+ * - Page manipulation utilities
10
+ * - Document repair
11
+ * - Resource cleanup
12
+ *
13
+ * This completes the document utility coverage for 100% FFI parity.
14
+ */
15
+
16
+ import { EventEmitter } from 'events';
17
+ import { promises as fs } from 'fs';
18
+ import { dirname } from 'path';
19
+
20
+ // ============================================================================
21
+ // Type Definitions
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Optimization level enumeration
26
+ */
27
+ export enum OptimizationLevel {
28
+ /** No optimization */
29
+ NONE = 'none',
30
+ /** Light optimization - fast, minimal size reduction */
31
+ LIGHT = 'light',
32
+ /** Balanced optimization - good balance of speed and size */
33
+ BALANCED = 'balanced',
34
+ /** Aggressive optimization - maximum size reduction, slower */
35
+ AGGRESSIVE = 'aggressive',
36
+ /** Maximum optimization - smallest possible size */
37
+ MAXIMUM = 'maximum',
38
+ }
39
+
40
+ /**
41
+ * Image compression type
42
+ */
43
+ export enum ImageCompressionType {
44
+ NONE = 'none',
45
+ JPEG = 'jpeg',
46
+ JPEG2000 = 'jpeg2000',
47
+ JBIG2 = 'jbig2',
48
+ FLATE = 'flate',
49
+ LZW = 'lzw',
50
+ RUN_LENGTH = 'run_length',
51
+ CCITT_FAX = 'ccitt_fax',
52
+ }
53
+
54
+ /**
55
+ * Color space type
56
+ */
57
+ export enum ColorSpaceType {
58
+ RGB = 'rgb',
59
+ CMYK = 'cmyk',
60
+ GRAYSCALE = 'grayscale',
61
+ INDEXED = 'indexed',
62
+ }
63
+
64
+ /**
65
+ * Font embedding mode
66
+ */
67
+ export enum FontEmbeddingMode {
68
+ /** Embed full fonts */
69
+ FULL = 'full',
70
+ /** Embed subset of used glyphs only */
71
+ SUBSET = 'subset',
72
+ /** Remove all font embedding */
73
+ REMOVE = 'remove',
74
+ }
75
+
76
+ /**
77
+ * Page range specification
78
+ */
79
+ export interface PageRange {
80
+ readonly start: number;
81
+ readonly end: number;
82
+ }
83
+
84
+ /**
85
+ * Optimization options
86
+ */
87
+ export interface OptimizationOptions {
88
+ readonly level?: OptimizationLevel;
89
+ readonly compressImages?: boolean;
90
+ readonly imageQuality?: number; // 1-100
91
+ readonly imageCompression?: ImageCompressionType;
92
+ readonly downsampleImages?: boolean;
93
+ readonly maxImageDpi?: number;
94
+ readonly removeUnusedObjects?: boolean;
95
+ readonly removeMetadata?: boolean;
96
+ readonly removeThumbnails?: boolean;
97
+ readonly removeBookmarks?: boolean;
98
+ readonly removeAnnotations?: boolean;
99
+ readonly removeJavaScript?: boolean;
100
+ readonly removeFormFields?: boolean;
101
+ readonly removeEmbeddedFiles?: boolean;
102
+ readonly linearize?: boolean;
103
+ readonly fontEmbedding?: FontEmbeddingMode;
104
+ readonly convertColorSpace?: ColorSpaceType;
105
+ readonly flattenTransparency?: boolean;
106
+ }
107
+
108
+ /**
109
+ * Compression options
110
+ */
111
+ export interface CompressionOptions {
112
+ readonly compressStreams?: boolean;
113
+ readonly compressObjects?: boolean;
114
+ readonly algorithm?: 'flate' | 'lzw' | 'none';
115
+ readonly level?: number; // 1-9
116
+ }
117
+
118
+ /**
119
+ * Image optimization options
120
+ */
121
+ export interface ImageOptimizationOptions {
122
+ readonly maxDpi?: number;
123
+ readonly minDpi?: number;
124
+ readonly targetQuality?: number;
125
+ readonly targetFormat?: ImageCompressionType;
126
+ readonly convertColor?: ColorSpaceType;
127
+ readonly removeIccProfiles?: boolean;
128
+ }
129
+
130
+ /**
131
+ * Font optimization options
132
+ */
133
+ export interface FontOptimizationOptions {
134
+ readonly mode?: FontEmbeddingMode;
135
+ readonly removeUnusedFonts?: boolean;
136
+ readonly mergeSubsets?: boolean;
137
+ readonly convertToType1?: boolean;
138
+ readonly convertToOpenType?: boolean;
139
+ }
140
+
141
+ /**
142
+ * Linearization options
143
+ */
144
+ export interface LinearizationOptions {
145
+ readonly firstPageEnd?: number;
146
+ readonly primaryHint?: boolean;
147
+ readonly overflowHint?: boolean;
148
+ }
149
+
150
+ /**
151
+ * Optimization result
152
+ */
153
+ export interface OptimizationResult {
154
+ readonly success: boolean;
155
+ readonly originalSize: number;
156
+ readonly optimizedSize: number;
157
+ readonly reductionPercent: number;
158
+ readonly reductionBytes: number;
159
+ readonly duration: number;
160
+ readonly details?: {
161
+ readonly imagesOptimized?: number;
162
+ readonly fontsOptimized?: number;
163
+ readonly objectsRemoved?: number;
164
+ readonly streamsCompressed?: number;
165
+ };
166
+ readonly error?: string;
167
+ readonly warnings?: readonly string[];
168
+ }
169
+
170
+ /**
171
+ * Document repair result
172
+ */
173
+ export interface RepairResult {
174
+ readonly success: boolean;
175
+ readonly issuesFound: number;
176
+ readonly issuesFixed: number;
177
+ readonly issues: readonly string[];
178
+ readonly error?: string;
179
+ }
180
+
181
+ /**
182
+ * Document statistics
183
+ */
184
+ export interface DocumentStatistics {
185
+ readonly pageCount: number;
186
+ readonly fileSize: number;
187
+ readonly objectCount: number;
188
+ readonly streamCount: number;
189
+ readonly imageCount: number;
190
+ readonly fontCount: number;
191
+ readonly annotationCount: number;
192
+ readonly bookmarkCount: number;
193
+ readonly embeddedFileCount: number;
194
+ readonly formFieldCount: number;
195
+ readonly signatureCount: number;
196
+ readonly hasJavaScript: boolean;
197
+ readonly hasXfa: boolean;
198
+ readonly isLinearized: boolean;
199
+ readonly isEncrypted: boolean;
200
+ readonly pdfVersion: string;
201
+ }
202
+
203
+ /**
204
+ * Page information
205
+ */
206
+ export interface PageInfo {
207
+ readonly index: number;
208
+ readonly width: number;
209
+ readonly height: number;
210
+ readonly rotation: number;
211
+ readonly hasAnnotations: boolean;
212
+ readonly hasText: boolean;
213
+ readonly hasImages: boolean;
214
+ readonly mediaBox: readonly [number, number, number, number];
215
+ readonly cropBox?: readonly [number, number, number, number];
216
+ }
217
+
218
+ // ============================================================================
219
+ // Document Utility Manager
220
+ // ============================================================================
221
+
222
+ /**
223
+ * Document Utility Manager - Complete document optimization and manipulation
224
+ *
225
+ * Provides 35 functions for document utilities.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const doc = await PdfDocument.open('large-document.pdf');
230
+ * const utilityManager = new DocumentUtilityManager(doc);
231
+ *
232
+ * // Optimize document
233
+ * const result = await utilityManager.optimizeDocument({
234
+ * level: OptimizationLevel.AGGRESSIVE,
235
+ * compressImages: true,
236
+ * imageQuality: 75,
237
+ * linearize: true,
238
+ * });
239
+ *
240
+ * console.log(`Reduced by ${result.reductionPercent}%`);
241
+ *
242
+ * // Save optimized document
243
+ * await utilityManager.saveOptimized('optimized.pdf');
244
+ * ```
245
+ */
246
+ export class DocumentUtilityManager extends EventEmitter {
247
+ private readonly document: any;
248
+ private lastOptimizationResult: OptimizationResult | null = null;
249
+
250
+ constructor(document: any) {
251
+ super();
252
+ if (!document) {
253
+ throw new Error('Document cannot be null or undefined');
254
+ }
255
+ this.document = document;
256
+ }
257
+
258
+ // ==========================================================================
259
+ // Optimization Functions (8 functions)
260
+ // ==========================================================================
261
+
262
+ /**
263
+ * Optimizes the document with specified options
264
+ */
265
+ async optimizeDocument(options?: OptimizationOptions): Promise<OptimizationResult> {
266
+ const startTime = Date.now();
267
+
268
+ try {
269
+ const originalSize = await this.getFileSize();
270
+
271
+ const result = await this.document?.optimizeDocument?.({
272
+ level: options?.level ?? OptimizationLevel.BALANCED,
273
+ compressImages: options?.compressImages ?? true,
274
+ imageQuality: options?.imageQuality ?? 85,
275
+ imageCompression: options?.imageCompression ?? ImageCompressionType.JPEG,
276
+ downsampleImages: options?.downsampleImages ?? true,
277
+ maxImageDpi: options?.maxImageDpi ?? 150,
278
+ removeUnusedObjects: options?.removeUnusedObjects ?? true,
279
+ removeMetadata: options?.removeMetadata ?? false,
280
+ removeThumbnails: options?.removeThumbnails ?? true,
281
+ removeBookmarks: options?.removeBookmarks ?? false,
282
+ removeAnnotations: options?.removeAnnotations ?? false,
283
+ removeJavaScript: options?.removeJavaScript ?? false,
284
+ removeFormFields: options?.removeFormFields ?? false,
285
+ removeEmbeddedFiles: options?.removeEmbeddedFiles ?? false,
286
+ linearize: options?.linearize ?? false,
287
+ fontEmbedding: options?.fontEmbedding ?? FontEmbeddingMode.SUBSET,
288
+ convertColorSpace: options?.convertColorSpace,
289
+ flattenTransparency: options?.flattenTransparency ?? false,
290
+ });
291
+
292
+ const optimizedSize = await this.getFileSize();
293
+ const reductionBytes = originalSize - optimizedSize;
294
+ const reductionPercent = originalSize > 0 ? (reductionBytes / originalSize) * 100 : 0;
295
+ const duration = Date.now() - startTime;
296
+
297
+ const optimizationResult: OptimizationResult = {
298
+ success: true,
299
+ originalSize,
300
+ optimizedSize,
301
+ reductionPercent: Math.round(reductionPercent * 100) / 100,
302
+ reductionBytes,
303
+ duration,
304
+ details: result?.details,
305
+ warnings: result?.warnings,
306
+ };
307
+
308
+ this.lastOptimizationResult = optimizationResult;
309
+ this.emit('optimization-complete', optimizationResult);
310
+
311
+ return optimizationResult;
312
+ } catch (error) {
313
+ const duration = Date.now() - startTime;
314
+ const errorResult: OptimizationResult = {
315
+ success: false,
316
+ originalSize: 0,
317
+ optimizedSize: 0,
318
+ reductionPercent: 0,
319
+ reductionBytes: 0,
320
+ duration,
321
+ error: error instanceof Error ? error.message : 'Unknown error',
322
+ };
323
+
324
+ this.emit('error', error);
325
+ return errorResult;
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Compresses document streams
331
+ */
332
+ async compressStreams(options?: CompressionOptions): Promise<boolean> {
333
+ try {
334
+ const result = await this.document?.compressStreams?.({
335
+ algorithm: options?.algorithm ?? 'flate',
336
+ level: options?.level ?? 6,
337
+ });
338
+
339
+ this.emit('streams-compressed');
340
+ return !!result;
341
+ } catch (error) {
342
+ this.emit('error', error);
343
+ return false;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Optimizes images in the document
349
+ */
350
+ async optimizeImages(options?: ImageOptimizationOptions): Promise<number> {
351
+ try {
352
+ const result = await this.document?.optimizeImages?.({
353
+ maxDpi: options?.maxDpi ?? 150,
354
+ minDpi: options?.minDpi ?? 72,
355
+ targetQuality: options?.targetQuality ?? 85,
356
+ targetFormat: options?.targetFormat ?? ImageCompressionType.JPEG,
357
+ convertColor: options?.convertColor,
358
+ removeIccProfiles: options?.removeIccProfiles ?? false,
359
+ });
360
+
361
+ const count = result?.optimizedCount ?? 0;
362
+ this.emit('images-optimized', { count });
363
+ return count;
364
+ } catch (error) {
365
+ this.emit('error', error);
366
+ return 0;
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Optimizes fonts in the document
372
+ */
373
+ async optimizeFonts(options?: FontOptimizationOptions): Promise<number> {
374
+ try {
375
+ const result = await this.document?.optimizeFonts?.({
376
+ mode: options?.mode ?? FontEmbeddingMode.SUBSET,
377
+ removeUnusedFonts: options?.removeUnusedFonts ?? true,
378
+ mergeSubsets: options?.mergeSubsets ?? true,
379
+ convertToType1: options?.convertToType1 ?? false,
380
+ convertToOpenType: options?.convertToOpenType ?? false,
381
+ });
382
+
383
+ const count = result?.optimizedCount ?? 0;
384
+ this.emit('fonts-optimized', { count });
385
+ return count;
386
+ } catch (error) {
387
+ this.emit('error', error);
388
+ return 0;
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Linearizes the document for fast web view
394
+ */
395
+ async linearize(options?: LinearizationOptions): Promise<boolean> {
396
+ try {
397
+ const result = await this.document?.linearize?.({
398
+ firstPageEnd: options?.firstPageEnd,
399
+ primaryHint: options?.primaryHint ?? true,
400
+ overflowHint: options?.overflowHint ?? true,
401
+ });
402
+
403
+ this.emit('document-linearized');
404
+ return !!result;
405
+ } catch (error) {
406
+ this.emit('error', error);
407
+ return false;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Removes unused objects from the document
413
+ */
414
+ async removeUnusedObjects(): Promise<number> {
415
+ try {
416
+ const result = await this.document?.removeUnusedObjects?.();
417
+ const count = result ?? 0;
418
+ this.emit('unused-objects-removed', { count });
419
+ return count;
420
+ } catch (error) {
421
+ this.emit('error', error);
422
+ return 0;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Flattens transparency in the document
428
+ */
429
+ async flattenTransparency(): Promise<boolean> {
430
+ try {
431
+ const result = await this.document?.flattenTransparency?.();
432
+ this.emit('transparency-flattened');
433
+ return !!result;
434
+ } catch (error) {
435
+ this.emit('error', error);
436
+ return false;
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Converts color space
442
+ */
443
+ async convertColorSpace(targetColorSpace: ColorSpaceType): Promise<boolean> {
444
+ try {
445
+ const result = await this.document?.convertColorSpace?.(targetColorSpace);
446
+ this.emit('color-space-converted', { targetColorSpace });
447
+ return !!result;
448
+ } catch (error) {
449
+ this.emit('error', error);
450
+ return false;
451
+ }
452
+ }
453
+
454
+ // ==========================================================================
455
+ // Page Manipulation (8 functions)
456
+ // ==========================================================================
457
+
458
+ /**
459
+ * Rotates pages
460
+ */
461
+ async rotatePages(pageRange: PageRange | 'all', degrees: 90 | 180 | 270): Promise<number> {
462
+ try {
463
+ const range = pageRange === 'all' ? null : pageRange;
464
+ const result = await this.document?.rotatePages?.(range, degrees);
465
+ const count = result ?? 0;
466
+ this.emit('pages-rotated', { count, degrees });
467
+ return count;
468
+ } catch (error) {
469
+ this.emit('error', error);
470
+ return 0;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Scales pages
476
+ */
477
+ async scalePages(
478
+ pageRange: PageRange | 'all',
479
+ scaleX: number,
480
+ scaleY: number
481
+ ): Promise<number> {
482
+ try {
483
+ const range = pageRange === 'all' ? null : pageRange;
484
+ const result = await this.document?.scalePages?.(range, scaleX, scaleY);
485
+ const count = result ?? 0;
486
+ this.emit('pages-scaled', { count, scaleX, scaleY });
487
+ return count;
488
+ } catch (error) {
489
+ this.emit('error', error);
490
+ return 0;
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Crops pages
496
+ */
497
+ async cropPages(
498
+ pageRange: PageRange | 'all',
499
+ cropBox: readonly [number, number, number, number]
500
+ ): Promise<number> {
501
+ try {
502
+ const range = pageRange === 'all' ? null : pageRange;
503
+ const result = await this.document?.cropPages?.(range, cropBox);
504
+ const count = result ?? 0;
505
+ this.emit('pages-cropped', { count });
506
+ return count;
507
+ } catch (error) {
508
+ this.emit('error', error);
509
+ return 0;
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Removes pages
515
+ */
516
+ async removePages(pageIndices: readonly number[]): Promise<boolean> {
517
+ try {
518
+ const result = await this.document?.removePages?.(pageIndices);
519
+ this.emit('pages-removed', { count: pageIndices.length });
520
+ return !!result;
521
+ } catch (error) {
522
+ this.emit('error', error);
523
+ return false;
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Reorders pages
529
+ */
530
+ async reorderPages(newOrder: readonly number[]): Promise<boolean> {
531
+ try {
532
+ const result = await this.document?.reorderPages?.(newOrder);
533
+ this.emit('pages-reordered');
534
+ return !!result;
535
+ } catch (error) {
536
+ this.emit('error', error);
537
+ return false;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Duplicates pages
543
+ */
544
+ async duplicatePages(pageIndices: readonly number[], times: number = 1): Promise<boolean> {
545
+ try {
546
+ const result = await this.document?.duplicatePages?.(pageIndices, times);
547
+ this.emit('pages-duplicated', { count: pageIndices.length * times });
548
+ return !!result;
549
+ } catch (error) {
550
+ this.emit('error', error);
551
+ return false;
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Extracts pages to a new document
557
+ */
558
+ async extractPages(pageIndices: readonly number[]): Promise<Buffer | null> {
559
+ try {
560
+ const result = await this.document?.extractPages?.(pageIndices);
561
+ this.emit('pages-extracted', { count: pageIndices.length });
562
+ return result ?? null;
563
+ } catch (error) {
564
+ this.emit('error', error);
565
+ return null;
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Inserts blank pages
571
+ */
572
+ async insertBlankPages(
573
+ afterPageIndex: number,
574
+ count: number,
575
+ width?: number,
576
+ height?: number
577
+ ): Promise<boolean> {
578
+ try {
579
+ const result = await this.document?.insertBlankPages?.(
580
+ afterPageIndex,
581
+ count,
582
+ width ?? 612,
583
+ height ?? 792
584
+ );
585
+ this.emit('blank-pages-inserted', { afterPageIndex, count });
586
+ return !!result;
587
+ } catch (error) {
588
+ this.emit('error', error);
589
+ return false;
590
+ }
591
+ }
592
+
593
+ // ==========================================================================
594
+ // Document Information (6 functions)
595
+ // ==========================================================================
596
+
597
+ /**
598
+ * Gets document statistics
599
+ */
600
+ async getDocumentStatistics(): Promise<DocumentStatistics | null> {
601
+ try {
602
+ const stats = await this.document?.getDocumentStatistics?.();
603
+
604
+ return stats ?? {
605
+ pageCount: await this.getPageCount(),
606
+ fileSize: await this.getFileSize(),
607
+ objectCount: 0,
608
+ streamCount: 0,
609
+ imageCount: 0,
610
+ fontCount: 0,
611
+ annotationCount: 0,
612
+ bookmarkCount: 0,
613
+ embeddedFileCount: 0,
614
+ formFieldCount: 0,
615
+ signatureCount: 0,
616
+ hasJavaScript: false,
617
+ hasXfa: false,
618
+ isLinearized: false,
619
+ isEncrypted: false,
620
+ pdfVersion: '1.7',
621
+ };
622
+ } catch (error) {
623
+ this.emit('error', error);
624
+ return null;
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Gets page information
630
+ */
631
+ async getPageInfo(pageIndex: number): Promise<PageInfo | null> {
632
+ try {
633
+ const info = await this.document?.getPageInfo?.(pageIndex);
634
+ return info ?? null;
635
+ } catch (error) {
636
+ this.emit('error', error);
637
+ return null;
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Gets page count
643
+ */
644
+ async getPageCount(): Promise<number> {
645
+ try {
646
+ return await this.document?.getPageCount?.() ?? 0;
647
+ } catch (error) {
648
+ this.emit('error', error);
649
+ return 0;
650
+ }
651
+ }
652
+
653
+ /**
654
+ * Gets file size in bytes
655
+ */
656
+ async getFileSize(): Promise<number> {
657
+ try {
658
+ return await this.document?.getFileSize?.() ?? 0;
659
+ } catch (error) {
660
+ this.emit('error', error);
661
+ return 0;
662
+ }
663
+ }
664
+
665
+ /**
666
+ * Checks if document is linearized
667
+ */
668
+ async isLinearized(): Promise<boolean> {
669
+ try {
670
+ return await this.document?.isLinearized?.() ?? false;
671
+ } catch (error) {
672
+ this.emit('error', error);
673
+ return false;
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Gets last optimization result
679
+ */
680
+ getLastOptimizationResult(): OptimizationResult | null {
681
+ return this.lastOptimizationResult;
682
+ }
683
+
684
+ // ==========================================================================
685
+ // Repair and Cleanup (5 functions)
686
+ // ==========================================================================
687
+
688
+ /**
689
+ * Repairs the document
690
+ */
691
+ async repairDocument(): Promise<RepairResult> {
692
+ try {
693
+ const result = await this.document?.repairDocument?.();
694
+
695
+ const repairResult: RepairResult = {
696
+ success: result?.success ?? true,
697
+ issuesFound: result?.issuesFound ?? 0,
698
+ issuesFixed: result?.issuesFixed ?? 0,
699
+ issues: result?.issues ?? [],
700
+ error: result?.error,
701
+ };
702
+
703
+ this.emit('document-repaired', repairResult);
704
+ return repairResult;
705
+ } catch (error) {
706
+ this.emit('error', error);
707
+ return {
708
+ success: false,
709
+ issuesFound: 0,
710
+ issuesFixed: 0,
711
+ issues: [error instanceof Error ? error.message : 'Unknown error'],
712
+ error: error instanceof Error ? error.message : 'Unknown error',
713
+ };
714
+ }
715
+ }
716
+
717
+ /**
718
+ * Removes all metadata
719
+ */
720
+ async removeAllMetadata(): Promise<boolean> {
721
+ try {
722
+ const result = await this.document?.removeAllMetadata?.();
723
+ this.emit('metadata-removed');
724
+ return !!result;
725
+ } catch (error) {
726
+ this.emit('error', error);
727
+ return false;
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Removes all JavaScript
733
+ */
734
+ async removeAllJavaScript(): Promise<boolean> {
735
+ try {
736
+ const result = await this.document?.removeAllJavaScript?.();
737
+ this.emit('javascript-removed');
738
+ return !!result;
739
+ } catch (error) {
740
+ this.emit('error', error);
741
+ return false;
742
+ }
743
+ }
744
+
745
+ /**
746
+ * Removes all annotations
747
+ */
748
+ async removeAllAnnotations(): Promise<number> {
749
+ try {
750
+ const result = await this.document?.removeAllAnnotations?.();
751
+ const count = result ?? 0;
752
+ this.emit('annotations-removed', { count });
753
+ return count;
754
+ } catch (error) {
755
+ this.emit('error', error);
756
+ return 0;
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Removes all embedded files
762
+ */
763
+ async removeAllEmbeddedFiles(): Promise<number> {
764
+ try {
765
+ const result = await this.document?.removeAllEmbeddedFiles?.();
766
+ const count = result ?? 0;
767
+ this.emit('embedded-files-removed', { count });
768
+ return count;
769
+ } catch (error) {
770
+ this.emit('error', error);
771
+ return 0;
772
+ }
773
+ }
774
+
775
+ // ==========================================================================
776
+ // Save Functions (5 functions)
777
+ // ==========================================================================
778
+
779
+ /**
780
+ * Saves the optimized document to a file
781
+ */
782
+ async saveOptimized(filePath: string): Promise<boolean> {
783
+ try {
784
+ await fs.mkdir(dirname(filePath), { recursive: true });
785
+ const result = await this.document?.save?.(filePath);
786
+ this.emit('document-saved', { filePath });
787
+ return !!result;
788
+ } catch (error) {
789
+ this.emit('error', error);
790
+ return false;
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Saves the document to bytes
796
+ */
797
+ async saveToBytes(): Promise<Buffer | null> {
798
+ try {
799
+ return await this.document?.saveToBytes?.() ?? null;
800
+ } catch (error) {
801
+ this.emit('error', error);
802
+ return null;
803
+ }
804
+ }
805
+
806
+ /**
807
+ * Saves with incremental update
808
+ */
809
+ async saveIncremental(filePath: string): Promise<boolean> {
810
+ try {
811
+ await fs.mkdir(dirname(filePath), { recursive: true });
812
+ const result = await this.document?.saveIncremental?.(filePath);
813
+ this.emit('document-saved-incremental', { filePath });
814
+ return !!result;
815
+ } catch (error) {
816
+ this.emit('error', error);
817
+ return false;
818
+ }
819
+ }
820
+
821
+ /**
822
+ * Saves with specific PDF version
823
+ */
824
+ async saveWithVersion(filePath: string, version: string): Promise<boolean> {
825
+ try {
826
+ await fs.mkdir(dirname(filePath), { recursive: true });
827
+ const result = await this.document?.saveWithVersion?.(filePath, version);
828
+ this.emit('document-saved', { filePath, version });
829
+ return !!result;
830
+ } catch (error) {
831
+ this.emit('error', error);
832
+ return false;
833
+ }
834
+ }
835
+
836
+ /**
837
+ * Exports pages as separate PDFs
838
+ */
839
+ async exportPagesAsSeparateFiles(outputDir: string, prefix: string = 'page'): Promise<number> {
840
+ try {
841
+ await fs.mkdir(outputDir, { recursive: true });
842
+ const pageCount = await this.getPageCount();
843
+ let exported = 0;
844
+
845
+ for (let i = 0; i < pageCount; i++) {
846
+ const pageBuffer = await this.extractPages([i]);
847
+ if (pageBuffer) {
848
+ const filePath = `${outputDir}/${prefix}_${i + 1}.pdf`;
849
+ await fs.writeFile(filePath, pageBuffer);
850
+ exported++;
851
+ }
852
+ }
853
+
854
+ this.emit('pages-exported-as-files', { count: exported, outputDir });
855
+ return exported;
856
+ } catch (error) {
857
+ this.emit('error', error);
858
+ return 0;
859
+ }
860
+ }
861
+
862
+ // ==========================================================================
863
+ // Merge Functions (3 functions)
864
+ // ==========================================================================
865
+
866
+ /**
867
+ * Merges another PDF into this document
868
+ */
869
+ async mergePdf(otherPdfBuffer: Buffer, atPageIndex?: number): Promise<boolean> {
870
+ try {
871
+ const result = await this.document?.mergePdf?.(otherPdfBuffer, atPageIndex);
872
+ this.emit('pdf-merged');
873
+ return !!result;
874
+ } catch (error) {
875
+ this.emit('error', error);
876
+ return false;
877
+ }
878
+ }
879
+
880
+ /**
881
+ * Merges multiple PDFs
882
+ */
883
+ async mergeMultiplePdfs(pdfBuffers: readonly Buffer[]): Promise<boolean> {
884
+ try {
885
+ for (const buffer of pdfBuffers) {
886
+ const success = await this.mergePdf(buffer);
887
+ if (!success) return false;
888
+ }
889
+ return true;
890
+ } catch (error) {
891
+ this.emit('error', error);
892
+ return false;
893
+ }
894
+ }
895
+
896
+ /**
897
+ * Appends pages from another PDF
898
+ */
899
+ async appendPagesFromPdf(
900
+ otherPdfBuffer: Buffer,
901
+ pageIndices?: readonly number[]
902
+ ): Promise<boolean> {
903
+ try {
904
+ const result = await this.document?.appendPagesFromPdf?.(otherPdfBuffer, pageIndices);
905
+ this.emit('pages-appended');
906
+ return !!result;
907
+ } catch (error) {
908
+ this.emit('error', error);
909
+ return false;
910
+ }
911
+ }
912
+
913
+ /**
914
+ * Cleanup resources
915
+ */
916
+ destroy(): void {
917
+ this.lastOptimizationResult = null;
918
+ this.removeAllListeners();
919
+ }
920
+ }
921
+
922
+ export default DocumentUtilityManager;