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,514 @@
1
+ /**
2
+ * EditingManager - Document Editing Manager for redaction and flattening operations
3
+ *
4
+ * Provides PDF document editing capabilities:
5
+ * - Content redaction (add, apply, count redaction areas)
6
+ * - Metadata scrubbing (remove Info, XMP, JavaScript)
7
+ * - Form flattening (all pages or single page)
8
+ * - Annotation flattening (all pages or single page)
9
+ *
10
+ * Uses native FFI functions:
11
+ * - pdf_redaction_add, pdf_redaction_apply, pdf_redaction_scrub_metadata, pdf_redaction_count
12
+ * - pdf_document_editor_flatten_forms, pdf_document_editor_flatten_forms_page
13
+ * - pdf_document_editor_flatten_annotations, pdf_document_editor_flatten_annotations_page
14
+ */
15
+
16
+ import { EventEmitter } from 'events';
17
+ import { mapFfiErrorCode } from '../errors';
18
+
19
+ // =============================================================================
20
+ // Type Definitions
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Rectangle coordinates for a redaction area.
25
+ */
26
+ export interface RedactionRect {
27
+ x1: number;
28
+ y1: number;
29
+ x2: number;
30
+ y2: number;
31
+ }
32
+
33
+ /**
34
+ * RGB color specification (values 0.0-1.0).
35
+ */
36
+ export interface RgbColor {
37
+ r: number;
38
+ g: number;
39
+ b: number;
40
+ }
41
+
42
+ /**
43
+ * Options for applying redactions.
44
+ */
45
+ export interface ApplyRedactionsOptions {
46
+ /** Whether to also scrub document metadata when applying redactions. */
47
+ scrubMetadata?: boolean;
48
+ /** Default fill color for redacted areas. Defaults to black (0, 0, 0). */
49
+ fillColor?: RgbColor;
50
+ }
51
+
52
+ /**
53
+ * Options for scrubbing document metadata.
54
+ */
55
+ export interface ScrubMetadataOptions {
56
+ /** Remove /Info dictionary entries (Title, Author, etc.). Default: true */
57
+ removeInfo?: boolean;
58
+ /** Remove XMP metadata stream. Default: true */
59
+ removeXmp?: boolean;
60
+ /** Remove JavaScript actions. Default: true */
61
+ removeJs?: boolean;
62
+ }
63
+
64
+ // =============================================================================
65
+ // Canonical EditingManager
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Manages document editing operations including redaction and flattening.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { EditingManager } from 'pdf_oxide';
74
+ *
75
+ * const editor = new EditingManager(document);
76
+ *
77
+ * // Add redaction areas
78
+ * editor.addRedaction(0, { x1: 100, y1: 200, x2: 300, y2: 250 });
79
+ * editor.addRedaction(0, { x1: 50, y1: 400, x2: 200, y2: 450 }, { r: 0, g: 0, b: 0 });
80
+ *
81
+ * // Apply all queued redactions
82
+ * const count = editor.applyRedactions({ scrubMetadata: true });
83
+ * console.log(`Applied ${count} redactions`);
84
+ *
85
+ * // Flatten forms and annotations
86
+ * editor.flattenForms();
87
+ * editor.flattenAnnotations();
88
+ * ```
89
+ */
90
+ export class EditingManager extends EventEmitter {
91
+ private document: any;
92
+ private native: any;
93
+
94
+ constructor(document: any) {
95
+ super();
96
+ this.document = document;
97
+ try {
98
+ this.native = require('../../index.node');
99
+ } catch {
100
+ this.native = null;
101
+ }
102
+ }
103
+
104
+ // ===========================================================================
105
+ // Redaction Operations
106
+ // ===========================================================================
107
+
108
+ /**
109
+ * Adds a redaction area to the document.
110
+ *
111
+ * Queues a rectangular region on a page for redaction. The content within
112
+ * the rectangle will be permanently removed when {@link applyRedactions}
113
+ * is called.
114
+ *
115
+ * @param page - Zero-based page index
116
+ * @param rect - Rectangle coordinates defining the redaction area
117
+ * @param color - Optional fill color for the redacted area (defaults to black)
118
+ * @throws {PdfException} If the document handle is invalid or page is out of range
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * // Redact a region with default black fill
123
+ * editor.addRedaction(0, { x1: 100, y1: 200, x2: 300, y2: 250 });
124
+ *
125
+ * // Redact with a custom gray fill
126
+ * editor.addRedaction(1, { x1: 50, y1: 100, x2: 200, y2: 150 }, { r: 0.5, g: 0.5, b: 0.5 });
127
+ * ```
128
+ */
129
+ addRedaction(
130
+ page: number,
131
+ rect: RedactionRect,
132
+ color?: RgbColor
133
+ ): void {
134
+ const fillColor = color ?? { r: 0, g: 0, b: 0 };
135
+
136
+ if (this.native?.pdf_redaction_add) {
137
+ const errorCode = { value: 0 };
138
+ const result = this.native.pdf_redaction_add(
139
+ this.document?.handle ?? this.document,
140
+ page,
141
+ rect.x1,
142
+ rect.y1,
143
+ rect.x2,
144
+ rect.y2,
145
+ fillColor.r,
146
+ fillColor.g,
147
+ fillColor.b,
148
+ errorCode
149
+ );
150
+
151
+ if (result < 0 && errorCode.value !== 0) {
152
+ throw mapFfiErrorCode(errorCode.value, 'Failed to add redaction area');
153
+ }
154
+ } else if (this.document?.addRedaction) {
155
+ this.document.addRedaction(page, rect, fillColor);
156
+ }
157
+
158
+ this.emit('redactionAdded', { page, rect, color: fillColor });
159
+ }
160
+
161
+ /**
162
+ * Applies all queued redactions to the document.
163
+ *
164
+ * Permanently removes content within all previously added redaction
165
+ * rectangles. Optionally scrubs document metadata and applies a
166
+ * fill color overlay.
167
+ *
168
+ * @param options - Options controlling redaction behavior
169
+ * @returns Number of redactions applied
170
+ * @throws {PdfException} If redaction application fails
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * // Apply with metadata scrubbing
175
+ * const count = editor.applyRedactions({ scrubMetadata: true });
176
+ *
177
+ * // Apply with custom fill color
178
+ * const count = editor.applyRedactions({
179
+ * fillColor: { r: 1.0, g: 1.0, b: 1.0 }
180
+ * });
181
+ * ```
182
+ */
183
+ applyRedactions(options?: ApplyRedactionsOptions): number {
184
+ const scrubMetadata = options?.scrubMetadata ?? false;
185
+ const fillColor = options?.fillColor ?? { r: 0, g: 0, b: 0 };
186
+
187
+ if (this.native?.pdf_redaction_apply) {
188
+ const errorCode = { value: 0 };
189
+ const result = this.native.pdf_redaction_apply(
190
+ this.document?.handle ?? this.document,
191
+ scrubMetadata,
192
+ fillColor.r,
193
+ fillColor.g,
194
+ fillColor.b,
195
+ errorCode
196
+ );
197
+
198
+ if (result < 0 && errorCode.value !== 0) {
199
+ throw mapFfiErrorCode(errorCode.value, 'Failed to apply redactions');
200
+ }
201
+
202
+ this.emit('redactionsApplied', { count: result, scrubMetadata });
203
+ return result;
204
+ } else if (this.document?.applyRedactions) {
205
+ const count = this.document.applyRedactions(options);
206
+ this.emit('redactionsApplied', { count, scrubMetadata });
207
+ return count ?? 0;
208
+ }
209
+
210
+ return 0;
211
+ }
212
+
213
+ /**
214
+ * Scrubs sensitive metadata from the document.
215
+ *
216
+ * Removes document metadata fields that may contain sensitive information,
217
+ * such as author names, creation tools, and JavaScript.
218
+ *
219
+ * @param options - Options controlling which metadata to remove
220
+ * @throws {PdfException} If metadata scrubbing fails
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * // Remove all metadata
225
+ * editor.scrubMetadata();
226
+ *
227
+ * // Remove only Info dictionary and JavaScript
228
+ * editor.scrubMetadata({ removeInfo: true, removeXmp: false, removeJs: true });
229
+ * ```
230
+ */
231
+ scrubMetadata(options?: ScrubMetadataOptions): void {
232
+ const removeInfo = options?.removeInfo ?? true;
233
+ const removeXmp = options?.removeXmp ?? true;
234
+ const removeJs = options?.removeJs ?? true;
235
+
236
+ if (this.native?.pdf_redaction_scrub_metadata) {
237
+ const errorCode = { value: 0 };
238
+ const result = this.native.pdf_redaction_scrub_metadata(
239
+ this.document?.handle ?? this.document,
240
+ removeInfo,
241
+ removeXmp,
242
+ removeJs,
243
+ errorCode
244
+ );
245
+
246
+ if (result < 0 && errorCode.value !== 0) {
247
+ throw mapFfiErrorCode(errorCode.value, 'Failed to scrub metadata');
248
+ }
249
+ } else if (this.document?.scrubMetadata) {
250
+ this.document.scrubMetadata(options);
251
+ }
252
+
253
+ this.emit('metadataScrubbed', { removeInfo, removeXmp, removeJs });
254
+ }
255
+
256
+ /**
257
+ * Gets the number of queued (pending) redaction areas.
258
+ *
259
+ * @returns Number of redaction areas queued for application
260
+ * @throws {PdfException} If the document handle is invalid
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * editor.addRedaction(0, { x1: 10, y1: 20, x2: 100, y2: 50 });
265
+ * editor.addRedaction(1, { x1: 30, y1: 40, x2: 200, y2: 80 });
266
+ * console.log(editor.getRedactionCount()); // 2
267
+ * ```
268
+ */
269
+ getRedactionCount(): number {
270
+ if (this.native?.pdf_redaction_count) {
271
+ const errorCode = { value: 0 };
272
+ const result = this.native.pdf_redaction_count(
273
+ this.document?.handle ?? this.document,
274
+ errorCode
275
+ );
276
+
277
+ if (result < 0 && errorCode.value !== 0) {
278
+ throw mapFfiErrorCode(errorCode.value, 'Failed to get redaction count');
279
+ }
280
+
281
+ return result;
282
+ } else if (this.document?.getRedactionCount) {
283
+ return this.document.getRedactionCount() ?? 0;
284
+ }
285
+
286
+ return 0;
287
+ }
288
+
289
+ // ===========================================================================
290
+ // Form Flattening Operations
291
+ // ===========================================================================
292
+
293
+ /**
294
+ * Flattens all form fields in the document.
295
+ *
296
+ * Renders form field widgets into page content and removes the AcroForm
297
+ * dictionary. After flattening, form fields become static content and
298
+ * can no longer be edited interactively.
299
+ *
300
+ * @throws {PdfException} If flattening fails
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * editor.flattenForms();
305
+ * ```
306
+ */
307
+ flattenForms(): void {
308
+ if (this.native?.pdf_document_editor_flatten_forms) {
309
+ const errorCode = { value: 0 };
310
+ const result = this.native.pdf_document_editor_flatten_forms(
311
+ this.document?.handle ?? this.document,
312
+ errorCode
313
+ );
314
+
315
+ if (result < 0 && errorCode.value !== 0) {
316
+ throw mapFfiErrorCode(errorCode.value, 'Failed to flatten forms');
317
+ }
318
+ } else if (this.document?.flattenForms) {
319
+ this.document.flattenForms();
320
+ }
321
+
322
+ this.emit('formsFlattened');
323
+ }
324
+
325
+ /**
326
+ * Flattens form fields on a specific page.
327
+ *
328
+ * Only flattens form field widgets located on the specified page,
329
+ * leaving form fields on other pages editable.
330
+ *
331
+ * @param page - Zero-based page index
332
+ * @throws {PdfException} If flattening fails or page is out of range
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * // Flatten forms on first page only
337
+ * editor.flattenFormsPage(0);
338
+ * ```
339
+ */
340
+ flattenFormsPage(page: number): void {
341
+ if (this.native?.pdf_document_editor_flatten_forms_page) {
342
+ const errorCode = { value: 0 };
343
+ const result = this.native.pdf_document_editor_flatten_forms_page(
344
+ this.document?.handle ?? this.document,
345
+ page,
346
+ errorCode
347
+ );
348
+
349
+ if (result < 0 && errorCode.value !== 0) {
350
+ throw mapFfiErrorCode(errorCode.value, `Failed to flatten forms on page ${page}`);
351
+ }
352
+ } else if (this.document?.flattenFormsPage) {
353
+ this.document.flattenFormsPage(page);
354
+ }
355
+
356
+ this.emit('formsPageFlattened', { page });
357
+ }
358
+
359
+ // ===========================================================================
360
+ // Annotation Flattening Operations
361
+ // ===========================================================================
362
+
363
+ /**
364
+ * Flattens all annotations in the document.
365
+ *
366
+ * Renders annotations into page content and removes them from
367
+ * annotation arrays. After flattening, annotations become static
368
+ * content and can no longer be edited or deleted.
369
+ *
370
+ * @throws {PdfException} If flattening fails
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * editor.flattenAnnotations();
375
+ * ```
376
+ */
377
+ flattenAnnotations(): void {
378
+ if (this.native?.pdf_document_editor_flatten_annotations) {
379
+ const errorCode = { value: 0 };
380
+ const result = this.native.pdf_document_editor_flatten_annotations(
381
+ this.document?.handle ?? this.document,
382
+ errorCode
383
+ );
384
+
385
+ if (result < 0 && errorCode.value !== 0) {
386
+ throw mapFfiErrorCode(errorCode.value, 'Failed to flatten annotations');
387
+ }
388
+ } else if (this.document?.flattenAnnotations) {
389
+ this.document.flattenAnnotations();
390
+ }
391
+
392
+ this.emit('annotationsFlattened');
393
+ }
394
+
395
+ /**
396
+ * Flattens annotations on a specific page.
397
+ *
398
+ * Only flattens annotations located on the specified page,
399
+ * leaving annotations on other pages editable.
400
+ *
401
+ * @param page - Zero-based page index
402
+ * @throws {PdfException} If flattening fails or page is out of range
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * // Flatten annotations on page 2
407
+ * editor.flattenAnnotationsPage(1);
408
+ * ```
409
+ */
410
+ flattenAnnotationsPage(page: number): void {
411
+ if (this.native?.pdf_document_editor_flatten_annotations_page) {
412
+ const errorCode = { value: 0 };
413
+ const result = this.native.pdf_document_editor_flatten_annotations_page(
414
+ this.document?.handle ?? this.document,
415
+ page,
416
+ errorCode
417
+ );
418
+
419
+ if (result < 0 && errorCode.value !== 0) {
420
+ throw mapFfiErrorCode(errorCode.value, `Failed to flatten annotations on page ${page}`);
421
+ }
422
+ } else if (this.document?.flattenAnnotationsPage) {
423
+ this.document.flattenAnnotationsPage(page);
424
+ }
425
+
426
+ this.emit('annotationsPageFlattened', { page });
427
+ }
428
+
429
+ // ===========================================================================
430
+ // Form Data Import/Export
431
+ // ===========================================================================
432
+
433
+ /**
434
+ * Import form data from a file (FDF or XFDF, auto-detected by extension).
435
+ * @param filePath Path to the FDF or XFDF file
436
+ * @returns Number of fields imported
437
+ */
438
+ importFormDataFromFile(filePath: string): number {
439
+ if (this.native?.pdf_document_import_form_data) {
440
+ const errorCode = { value: 0 };
441
+ const count = this.native.pdf_document_import_form_data(this.document?.handle ?? this.document, filePath, errorCode);
442
+ if (errorCode.value !== 0) {
443
+ throw mapFfiErrorCode(errorCode.value, `Failed to import form data from ${filePath}`);
444
+ }
445
+ return count;
446
+ }
447
+ throw new Error('Form data import not available');
448
+ }
449
+
450
+ /**
451
+ * Import FDF form data from in-memory bytes.
452
+ * @param data FDF data bytes
453
+ * @returns Number of fields imported
454
+ */
455
+ importFdfBytes(data: Buffer): number {
456
+ if (this.native?.pdf_editor_import_fdf_bytes) {
457
+ const errorCode = { value: 0 };
458
+ const count = this.native.pdf_editor_import_fdf_bytes(this.document?.handle ?? this.document, data, data.length, errorCode);
459
+ if (errorCode.value !== 0) {
460
+ throw mapFfiErrorCode(errorCode.value, 'Failed to import FDF bytes');
461
+ }
462
+ return count;
463
+ }
464
+ throw new Error('FDF byte import not available');
465
+ }
466
+
467
+ /**
468
+ * Import XFDF form data from in-memory bytes.
469
+ * @param data XFDF data bytes
470
+ * @returns Number of fields imported
471
+ */
472
+ importXfdfBytes(data: Buffer): number {
473
+ if (this.native?.pdf_editor_import_xfdf_bytes) {
474
+ const errorCode = { value: 0 };
475
+ const count = this.native.pdf_editor_import_xfdf_bytes(this.document?.handle ?? this.document, data, data.length, errorCode);
476
+ if (errorCode.value !== 0) {
477
+ throw mapFfiErrorCode(errorCode.value, 'Failed to import XFDF bytes');
478
+ }
479
+ return count;
480
+ }
481
+ throw new Error('XFDF byte import not available');
482
+ }
483
+
484
+ /**
485
+ * Export form data to in-memory bytes.
486
+ * @param format 0 for FDF, 1 for XFDF
487
+ * @returns Exported form data bytes
488
+ */
489
+ exportFormDataToBytes(format: 0 | 1 = 0): Buffer {
490
+ if (this.native?.pdf_document_export_form_data_to_bytes) {
491
+ const errorCode = { value: 0 };
492
+ const outLen = { value: 0 };
493
+ const ptr = this.native.pdf_document_export_form_data_to_bytes(this.document?.handle ?? this.document, format, outLen, errorCode);
494
+ if (errorCode.value !== 0 || !ptr) {
495
+ throw mapFfiErrorCode(errorCode.value, 'Failed to export form data');
496
+ }
497
+ return Buffer.from(ptr, 0, outLen.value);
498
+ }
499
+ throw new Error('Form data export not available');
500
+ }
501
+
502
+ // ===========================================================================
503
+ // Lifecycle
504
+ // ===========================================================================
505
+
506
+ /**
507
+ * Releases resources held by this manager.
508
+ */
509
+ destroy(): void {
510
+ this.removeAllListeners();
511
+ }
512
+ }
513
+
514
+ export default EditingManager;