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,500 @@
1
+ /**
2
+ * Manager for PDF layers (Optional Content Groups - OCG)
3
+ *
4
+ * Provides methods to manage and interact with PDF layers which are used
5
+ * for optional content groups in PDF documents.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { LayerManager } from 'pdf_oxide';
10
+ *
11
+ * const doc = PdfDocument.open('document.pdf');
12
+ * const layerManager = new LayerManager(doc);
13
+ *
14
+ * // Check if document has layers
15
+ * if (layerManager.hasLayers()) {
16
+ * const layers = layerManager.getLayers();
17
+ * console.log(`Document has ${layers.length} layers`);
18
+ * }
19
+ * ```
20
+ */
21
+
22
+ export interface Layer {
23
+ id: string;
24
+ name: string;
25
+ visible: boolean;
26
+ index: number;
27
+ parentId: string | null;
28
+ printable: boolean;
29
+ export: boolean;
30
+ description?: string;
31
+ dependsOn?: string[];
32
+ }
33
+
34
+ export interface LayerNode extends Layer {
35
+ children: LayerNode[];
36
+ }
37
+
38
+ export interface LayerHierarchy {
39
+ root: LayerNode[];
40
+ }
41
+
42
+ export interface LayerStatistics {
43
+ count: number;
44
+ rootCount: number;
45
+ maxDepth: number;
46
+ visible: number;
47
+ hidden: number;
48
+ printable: number;
49
+ exportable: number;
50
+ hasConflicts: boolean;
51
+ }
52
+
53
+ export interface LayerValidation {
54
+ isValid: boolean;
55
+ issues: string[];
56
+ }
57
+
58
+ export class LayerManager {
59
+ private _document: any;
60
+ private _layerCache: Layer[] | null;
61
+ private _hierarchyCache: LayerHierarchy | null;
62
+ private _statisticsCache: LayerStatistics | null;
63
+
64
+ /**
65
+ * Creates a new LayerManager for the given document
66
+ * @param document - The PDF document
67
+ * @throws Error if document is null or undefined
68
+ */
69
+ constructor(document: any) {
70
+ if (!document) {
71
+ throw new Error('Document is required');
72
+ }
73
+ this._document = document;
74
+ // Performance optimization: cache layer data
75
+ this._layerCache = null;
76
+ this._hierarchyCache = null;
77
+ this._statisticsCache = null;
78
+ }
79
+
80
+ /**
81
+ * Clears the layer cache
82
+ * Useful when document content might have changed
83
+ */
84
+ clearCache(): void {
85
+ this._layerCache = null;
86
+ this._hierarchyCache = null;
87
+ this._statisticsCache = null;
88
+ }
89
+
90
+ /**
91
+ * Checks if document has layers
92
+ * @returns True if document contains layers
93
+ */
94
+ hasLayers(): boolean {
95
+ try {
96
+ return this.getLayerCount() > 0;
97
+ } catch (error) {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Gets number of layers in document
104
+ * @returns Number of layers
105
+ */
106
+ getLayerCount(): number {
107
+ const layers = this.getLayers();
108
+ return layers.length;
109
+ }
110
+
111
+ /**
112
+ * Gets all layers in document
113
+ * @returns Array of layer objects with id, name, visible, etc.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const layers = manager.getLayers();
118
+ * layers.forEach(layer => {
119
+ * console.log(`Layer: ${layer.name} (visible: ${layer.visible})`);
120
+ * });
121
+ * ```
122
+ */
123
+ getLayers(): Layer[] {
124
+ // Performance optimization: cache layer data
125
+ if (this._layerCache !== null) {
126
+ return this._layerCache;
127
+ }
128
+
129
+ try {
130
+ const rawLayers = this._document.getLayers();
131
+ // Convert native LayerInfo to the expected JS format
132
+ const layers: Layer[] = rawLayers.map((layer: any) => ({
133
+ id: layer.id,
134
+ name: layer.name,
135
+ visible: layer.visible,
136
+ index: layer.index,
137
+ parentId: null, // OCGs don't have hierarchy in PDF spec
138
+ printable: true, // Default assumption
139
+ export: true, // Default assumption
140
+ }));
141
+ this._layerCache = layers;
142
+ return layers;
143
+ } catch (error) {
144
+ return [];
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Gets layer by name
150
+ * @param name - Layer name to find
151
+ * @returns Layer object or null if not found
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const layer = manager.getLayerByName('Background');
156
+ * if (layer) {
157
+ * console.log(`Found layer: ${layer.name}`);
158
+ * }
159
+ * ```
160
+ */
161
+ getLayerByName(name: string): Layer | null {
162
+ if (!name || typeof name !== 'string') {
163
+ throw new Error('Layer name must be a non-empty string');
164
+ }
165
+
166
+ const layers = this.getLayers();
167
+ return layers.find(layer => layer.name === name) || null;
168
+ }
169
+
170
+ /**
171
+ * Gets layer by ID
172
+ * @param id - Layer ID to find
173
+ * @returns Layer object or null if not found
174
+ */
175
+ getLayerById(id: string): Layer | null {
176
+ if (!id || typeof id !== 'string') {
177
+ throw new Error('Layer ID must be a non-empty string');
178
+ }
179
+
180
+ const layers = this.getLayers();
181
+ return layers.find(layer => layer.id === id) || null;
182
+ }
183
+
184
+ /**
185
+ * Gets root-level layers (not nested under other layers)
186
+ * @returns Array of root-level layers
187
+ */
188
+ getRootLayers(): Layer[] {
189
+ const layers = this.getLayers();
190
+ return layers.filter(layer => !layer.parentId);
191
+ }
192
+
193
+ /**
194
+ * Gets the full layer hierarchy as a tree structure
195
+ * @returns Layer hierarchy tree
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const hierarchy = manager.getLayerHierarchy();
200
+ * // { root: [{ name: 'Layer1', children: [...] }, ...] }
201
+ * ```
202
+ */
203
+ getLayerHierarchy(): LayerHierarchy {
204
+ // Performance optimization: cache hierarchy
205
+ if (this._hierarchyCache !== null) {
206
+ return this._hierarchyCache;
207
+ }
208
+
209
+ const layers = this.getLayers();
210
+ const hierarchy: LayerHierarchy = { root: [] };
211
+
212
+ // Build parent-child relationships
213
+ const layerMap = new Map<string, LayerNode>(
214
+ layers.map((l) => [l.id, { ...l, children: [] } as LayerNode])
215
+ );
216
+
217
+ for (const layer of layerMap.values()) {
218
+ if (layer.parentId) {
219
+ const parent = layerMap.get(layer.parentId);
220
+ if (parent) {
221
+ parent.children.push(layer);
222
+ }
223
+ } else {
224
+ hierarchy.root.push(layer);
225
+ }
226
+ }
227
+
228
+ this._hierarchyCache = hierarchy;
229
+ return hierarchy;
230
+ }
231
+
232
+ /**
233
+ * Gets child layers of a parent layer
234
+ * @param parentId - Parent layer ID
235
+ * @returns Array of child layers
236
+ */
237
+ getChildLayers(parentId: string): Layer[] {
238
+ if (!parentId || typeof parentId !== 'string') {
239
+ throw new Error('Parent layer ID must be a non-empty string');
240
+ }
241
+
242
+ const layers = this.getLayers();
243
+ return layers.filter(layer => layer.parentId === parentId);
244
+ }
245
+
246
+ /**
247
+ * Gets parent layer of a layer
248
+ * @param layerId - Layer ID
249
+ * @returns Parent layer object or null if no parent
250
+ */
251
+ getParentLayer(layerId: string): Layer | null {
252
+ if (!layerId || typeof layerId !== 'string') {
253
+ throw new Error('Layer ID must be a non-empty string');
254
+ }
255
+
256
+ const layer = this.getLayerById(layerId);
257
+ if (!layer || !layer.parentId) {
258
+ return null;
259
+ }
260
+
261
+ return this.getLayerById(layer.parentId);
262
+ }
263
+
264
+ /**
265
+ * Checks if a layer is visible
266
+ * @param layerId - Layer ID
267
+ * @returns True if layer is visible
268
+ */
269
+ isLayerVisible(layerId: string): boolean {
270
+ const layer = this.getLayerById(layerId);
271
+ return layer ? layer.visible !== false : false;
272
+ }
273
+
274
+ /**
275
+ * Gets visibility chain from root to layer
276
+ * Shows visibility state of all parent layers
277
+ * @param layerId - Layer ID
278
+ * @returns Array of layers from root to target
279
+ */
280
+ getVisibilityChain(layerId: string): Layer[] {
281
+ const chain: Layer[] = [];
282
+ let current = this.getLayerById(layerId);
283
+
284
+ while (current) {
285
+ chain.unshift(current);
286
+ current = current.parentId ? this.getLayerById(current.parentId) : null;
287
+ }
288
+
289
+ return chain;
290
+ }
291
+
292
+ /**
293
+ * Gets layer usage information
294
+ * @returns Layer usage { view, print, export }
295
+ */
296
+ getLayerUsages(): Record<string, number> {
297
+ const layers = this.getLayers();
298
+ return {
299
+ view: layers.filter(l => l.printable === false).length,
300
+ print: layers.filter(l => l.printable === true).length,
301
+ export: layers.filter(l => l.export !== false).length,
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Gets statistics about layers
307
+ * @returns Layer statistics
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * const stats = manager.getLayerStatistics();
312
+ * console.log(`Total layers: ${stats.count}`);
313
+ * console.log(`Max depth: ${stats.maxDepth}`);
314
+ * ```
315
+ */
316
+ getLayerStatistics(): LayerStatistics {
317
+ // Performance optimization: cache statistics
318
+ if (this._statisticsCache !== null) {
319
+ return this._statisticsCache;
320
+ }
321
+
322
+ const layers = this.getLayers();
323
+ const hierarchy = this.getLayerHierarchy();
324
+
325
+ // Calculate max depth
326
+ const calculateDepth = (node: any, depth: number = 0): number => {
327
+ if (!node.children || node.children.length === 0) {
328
+ return depth;
329
+ }
330
+ return Math.max(...node.children.map((child: any) => calculateDepth(child, depth + 1)));
331
+ };
332
+
333
+ let maxDepth = 0;
334
+ for (const rootLayer of hierarchy.root) {
335
+ maxDepth = Math.max(maxDepth, calculateDepth(rootLayer));
336
+ }
337
+
338
+ const stats: LayerStatistics = {
339
+ count: layers.length,
340
+ rootCount: hierarchy.root.length,
341
+ maxDepth,
342
+ visible: layers.filter(l => l.visible !== false).length,
343
+ hidden: layers.filter(l => l.visible === false).length,
344
+ printable: layers.filter(l => l.printable !== false).length,
345
+ exportable: layers.filter(l => l.export !== false).length,
346
+ hasConflicts: this._detectLayerConflicts().length > 0,
347
+ };
348
+
349
+ this._statisticsCache = stats;
350
+ return stats;
351
+ }
352
+
353
+ /**
354
+ * Gets layer dependencies
355
+ * @returns Layer dependencies map
356
+ * @private
357
+ */
358
+ private getLayerDependencies(): Record<string, any> {
359
+ const layers = this.getLayers();
360
+ const dependencies: Record<string, any> = {};
361
+
362
+ layers.forEach(layer => {
363
+ dependencies[layer.id] = {
364
+ dependsOn: layer.dependsOn || [],
365
+ dependents: [],
366
+ };
367
+ });
368
+
369
+ // Build reverse dependencies
370
+ Object.entries(dependencies).forEach(([layerId, deps]) => {
371
+ deps.dependsOn.forEach((depId: string) => {
372
+ if (dependencies[depId]) {
373
+ dependencies[depId].dependents.push(layerId);
374
+ }
375
+ });
376
+ });
377
+
378
+ return dependencies;
379
+ }
380
+
381
+ /**
382
+ * Finds layers by pattern
383
+ * @param pattern - Pattern to match
384
+ * @returns Matching layers
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const backgroundLayers = manager.findLayersByPattern(/background/i);
389
+ * ```
390
+ */
391
+ findLayersByPattern(pattern: RegExp | string): Layer[] {
392
+ if (!pattern) {
393
+ throw new Error('Pattern must be provided');
394
+ }
395
+
396
+ const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i');
397
+ const layers = this.getLayers();
398
+
399
+ return layers.filter(layer =>
400
+ regex.test(layer.name) ||
401
+ (layer.description && regex.test(layer.description))
402
+ );
403
+ }
404
+
405
+ /**
406
+ * Validates layer state for conflicts and issues
407
+ * @returns Validation result { isValid, issues }
408
+ */
409
+ validateLayerState(): LayerValidation {
410
+ const issues: string[] = [];
411
+ const conflicts = this._detectLayerConflicts();
412
+ const cycles = this._detectLayerCycles();
413
+
414
+ if (conflicts.length > 0) {
415
+ issues.push(...conflicts.map(c => `Conflict: ${c}`));
416
+ }
417
+
418
+ if (cycles.length > 0) {
419
+ issues.push(...cycles.map(c => `Cycle detected: ${c}`));
420
+ }
421
+
422
+ return {
423
+ isValid: issues.length === 0,
424
+ issues,
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Detects layer conflicts
430
+ * @returns Array of conflict descriptions
431
+ * @private
432
+ */
433
+ private _detectLayerConflicts(): string[] {
434
+ const conflicts: string[] = [];
435
+ const layers = this.getLayers();
436
+
437
+ // Check for layers with same name
438
+ const nameMap = new Map<string, string>();
439
+ layers.forEach(layer => {
440
+ if (nameMap.has(layer.name)) {
441
+ conflicts.push(`Duplicate layer name: ${layer.name}`);
442
+ }
443
+ nameMap.set(layer.name, layer.id);
444
+ });
445
+
446
+ // Check for orphaned layers
447
+ const parentIds = new Set(layers.map(l => l.parentId).filter(id => id));
448
+ const layerIds = new Set(layers.map(l => l.id));
449
+
450
+ parentIds.forEach(parentId => {
451
+ if (!layerIds.has(parentId as string)) {
452
+ conflicts.push(`Orphaned layer reference: ${parentId}`);
453
+ }
454
+ });
455
+
456
+ return conflicts;
457
+ }
458
+
459
+ /**
460
+ * Detects cycles in layer hierarchy
461
+ * @returns Array of cycle descriptions
462
+ * @private
463
+ */
464
+ private _detectLayerCycles(): string[] {
465
+ const cycles: string[] = [];
466
+ const layers = this.getLayers();
467
+ const visited = new Set<string>();
468
+ const stack = new Set<string>();
469
+
470
+ const detectCycle = (layerId: string, path: string[] = []): void => {
471
+ if (stack.has(layerId)) {
472
+ cycles.push(`Cycle detected: ${path.join(' -> ')} -> ${layerId}`);
473
+ return;
474
+ }
475
+
476
+ if (visited.has(layerId)) {
477
+ return;
478
+ }
479
+
480
+ visited.add(layerId);
481
+ stack.add(layerId);
482
+ path.push(layerId);
483
+
484
+ const layer = this.getLayerById(layerId);
485
+ if (layer && layer.parentId) {
486
+ detectCycle(layer.parentId, [...path]);
487
+ }
488
+
489
+ stack.delete(layerId);
490
+ };
491
+
492
+ layers.forEach(layer => {
493
+ if (!visited.has(layer.id)) {
494
+ detectCycle(layer.id);
495
+ }
496
+ });
497
+
498
+ return cycles;
499
+ }
500
+ }