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,867 @@
1
+ /**
2
+ * ResultAccessorsManager for extracting extended properties from PDF operations
3
+ *
4
+ * Provides detailed metadata from search results, fonts, images, and annotations.
5
+ * Enables advanced features like context extraction, font metrics analysis, and annotation tracking.
6
+ * API is consistent with Python, Java, C#, Go, and Swift implementations.
7
+ */
8
+
9
+ import { EventEmitter } from 'events';
10
+
11
+ /**
12
+ * Extended properties from a search result
13
+ */
14
+ export interface SearchResultProperties {
15
+ context: string;
16
+ lineNumber: number;
17
+ paragraphNumber: number;
18
+ confidence: number;
19
+ isHighlighted: boolean;
20
+ fontInfo: string; // JSON format
21
+ color: [number, number, number]; // RGB
22
+ rotation: number;
23
+ objectId: number;
24
+ streamIndex: number;
25
+ }
26
+
27
+ /**
28
+ * Extended font metric information
29
+ */
30
+ export interface FontProperties {
31
+ baseFontName: string;
32
+ descriptor: string; // JSON format
33
+ descendantFont: string;
34
+ toUnicodeCmap: string;
35
+ isVertical: boolean;
36
+ widths: Float32Array;
37
+ ascender: number;
38
+ descender: number;
39
+ }
40
+
41
+ /**
42
+ * Extended image metadata
43
+ */
44
+ export interface ImageProperties {
45
+ hasAlphaChannel: boolean;
46
+ iccProfile: Uint8Array;
47
+ filterChain: string; // JSON format
48
+ decodedData: Uint8Array;
49
+ width: number;
50
+ height: number;
51
+ colorSpace: string;
52
+ }
53
+
54
+ /**
55
+ * Extended annotation properties
56
+ */
57
+ export interface AnnotationProperties {
58
+ modifiedDate: number; // timestamp in milliseconds
59
+ subject: string;
60
+ replyToIndex: number;
61
+ pageNumber: number;
62
+ iconName: string;
63
+ author: string;
64
+ }
65
+
66
+ /**
67
+ * Result Accessors Manager for extracting extended properties
68
+ *
69
+ * Provides methods to:
70
+ * - Extract context from search results
71
+ * - Get detailed font metrics
72
+ * - Inspect image metadata and ICC profiles
73
+ * - Track annotation relationships and metadata
74
+ * - Filter and analyze results by properties
75
+ *
76
+ * Matches: Python ResultAccessorsManager, Java ResultAccessorsManager, etc.
77
+ */
78
+ export class ResultAccessorsManager extends EventEmitter {
79
+ private document: any;
80
+ private resultCache = new Map<string, any>();
81
+ private maxCacheSize = 100;
82
+ private native: any;
83
+
84
+ constructor(document: any) {
85
+ super();
86
+ this.document = document;
87
+ try {
88
+ this.native = require('../index.node');
89
+ } catch {
90
+ // Fall back to framework defaults if native module not available
91
+ this.native = null;
92
+ }
93
+ }
94
+
95
+ private setCached(key: string, value: any): void {
96
+ if (this.resultCache.size >= this.maxCacheSize) {
97
+ // Remove oldest entry
98
+ const firstKey = this.resultCache.keys().next().value;
99
+ if (firstKey) this.resultCache.delete(firstKey);
100
+ }
101
+ this.resultCache.set(key, value);
102
+ }
103
+
104
+ // ========== Search Result Accessors (10 functions) ==========
105
+
106
+ /**
107
+ * Gets context text around a search result
108
+ * Includes words before and after the match
109
+ * @param results Search results handle
110
+ * @param index Index of the result
111
+ * @param contextWidth Number of characters for context
112
+ * @returns Context text with highlighted match
113
+ */
114
+ async getSearchResultContext(
115
+ results: any,
116
+ index: number,
117
+ contextWidth: number = 50
118
+ ): Promise<string> {
119
+ const cacheKey = `search:context:${index}:${contextWidth}`;
120
+ if (this.resultCache.has(cacheKey)) {
121
+ return this.resultCache.get(cacheKey);
122
+ }
123
+
124
+ const context = this.native?.search_result_context?.(index, contextWidth) ?? '';
125
+ this.setCached(cacheKey, context);
126
+ this.emit('searchContextExtracted', index);
127
+ return context;
128
+ }
129
+
130
+ /**
131
+ * Gets the line number of a search result
132
+ * @param results Search results handle
133
+ * @param index Index of the result
134
+ * @returns Line number (0-based)
135
+ */
136
+ async getSearchResultLineNumber(results: any, index: number): Promise<number> {
137
+ const cacheKey = `search:linenum:${index}`;
138
+ if (this.resultCache.has(cacheKey)) {
139
+ return this.resultCache.get(cacheKey);
140
+ }
141
+
142
+ const lineNumber = this.native?.search_result_line_number?.(index) ?? 0;
143
+ this.setCached(cacheKey, lineNumber);
144
+ return lineNumber;
145
+ }
146
+
147
+ /**
148
+ * Gets the paragraph number of a search result
149
+ * @param results Search results handle
150
+ * @param index Index of the result
151
+ * @returns Paragraph number (0-based)
152
+ */
153
+ async getSearchResultParagraphNumber(
154
+ results: any,
155
+ index: number
156
+ ): Promise<number> {
157
+ const cacheKey = `search:paragraphnum:${index}`;
158
+ if (this.resultCache.has(cacheKey)) {
159
+ return this.resultCache.get(cacheKey);
160
+ }
161
+
162
+ const paragraphNumber = this.native?.search_result_paragraph_number?.(index) ?? 0;
163
+ this.setCached(cacheKey, paragraphNumber);
164
+ return paragraphNumber;
165
+ }
166
+
167
+ /**
168
+ * Gets the confidence score of a search result
169
+ * Useful for OCR results where confidence varies
170
+ * @param results Search results handle
171
+ * @param index Index of the result
172
+ * @returns Confidence score (0.0 to 1.0)
173
+ */
174
+ async getSearchResultConfidence(results: any, index: number): Promise<number> {
175
+ const cacheKey = `search:confidence:${index}`;
176
+ if (this.resultCache.has(cacheKey)) {
177
+ return this.resultCache.get(cacheKey);
178
+ }
179
+
180
+ const confidence = this.native?.search_result_confidence?.(index) ?? 1.0;
181
+ this.setCached(cacheKey, confidence);
182
+ return confidence;
183
+ }
184
+
185
+ /**
186
+ * Checks if a search result is highlighted in the document
187
+ * @param results Search results handle
188
+ * @param index Index of the result
189
+ * @returns True if the result is highlighted
190
+ */
191
+ async isSearchResultHighlighted(results: any, index: number): Promise<boolean> {
192
+ const cacheKey = `search:highlighted:${index}`;
193
+ if (this.resultCache.has(cacheKey)) {
194
+ return this.resultCache.get(cacheKey);
195
+ }
196
+
197
+ const highlighted = this.native?.search_result_is_highlighted?.(index) ?? false;
198
+ this.setCached(cacheKey, highlighted);
199
+ return highlighted;
200
+ }
201
+
202
+ /**
203
+ * Gets font information for a search result
204
+ * Returns JSON with font name, size, family, etc.
205
+ * @param results Search results handle
206
+ * @param index Index of the result
207
+ * @returns Font info as JSON string
208
+ */
209
+ async getSearchResultFontInfo(results: any, index: number): Promise<string> {
210
+ const cacheKey = `search:fontinfo:${index}`;
211
+ if (this.resultCache.has(cacheKey)) {
212
+ return this.resultCache.get(cacheKey);
213
+ }
214
+
215
+ const fontInfo = this.native?.search_result_font_info?.(index) ?? '{}';
216
+ this.setCached(cacheKey, fontInfo);
217
+ return fontInfo;
218
+ }
219
+
220
+ /**
221
+ * Gets RGB color of a search result
222
+ * @param results Search results handle
223
+ * @param index Index of the result
224
+ * @returns Color as [R, G, B] array (0-255)
225
+ */
226
+ async getSearchResultColor(
227
+ results: any,
228
+ index: number
229
+ ): Promise<[number, number, number]> {
230
+ const cacheKey = `search:color:${index}`;
231
+ if (this.resultCache.has(cacheKey)) {
232
+ return this.resultCache.get(cacheKey);
233
+ }
234
+
235
+ let color: [number, number, number] = [0, 0, 0];
236
+ if (this.native?.search_result_color) {
237
+ try {
238
+ const colorJson = this.native.search_result_color(index);
239
+ const parsed = JSON.parse(colorJson);
240
+ color = [parsed.r ?? 0, parsed.g ?? 0, parsed.b ?? 0];
241
+ } catch {
242
+ color = [0, 0, 0];
243
+ }
244
+ }
245
+ this.setCached(cacheKey, color);
246
+ return color;
247
+ }
248
+
249
+ /**
250
+ * Gets the rotation angle of a search result
251
+ * @param results Search results handle
252
+ * @param index Index of the result
253
+ * @returns Rotation in degrees (0, 90, 180, 270)
254
+ */
255
+ async getSearchResultRotation(results: any, index: number): Promise<number> {
256
+ const cacheKey = `search:rotation:${index}`;
257
+ if (this.resultCache.has(cacheKey)) {
258
+ return this.resultCache.get(cacheKey);
259
+ }
260
+
261
+ const rotation = this.native?.search_result_rotation?.(index) ?? 0;
262
+ this.setCached(cacheKey, rotation);
263
+ return rotation;
264
+ }
265
+
266
+ /**
267
+ * Gets the object ID of a search result
268
+ * @param results Search results handle
269
+ * @param index Index of the result
270
+ * @returns PDF object ID
271
+ */
272
+ async getSearchResultObjectId(results: any, index: number): Promise<number> {
273
+ const cacheKey = `search:objectid:${index}`;
274
+ if (this.resultCache.has(cacheKey)) {
275
+ return this.resultCache.get(cacheKey);
276
+ }
277
+
278
+ const objectId = this.native?.search_result_object_id?.(index) ?? 0;
279
+ this.setCached(cacheKey, objectId);
280
+ return objectId;
281
+ }
282
+
283
+ /**
284
+ * Gets the stream index of a search result
285
+ * @param results Search results handle
286
+ * @param index Index of the result
287
+ * @returns Stream index in the content
288
+ */
289
+ async getSearchResultStreamIndex(results: any, index: number): Promise<number> {
290
+ const cacheKey = `search:streamindex:${index}`;
291
+ if (this.resultCache.has(cacheKey)) {
292
+ return this.resultCache.get(cacheKey);
293
+ }
294
+
295
+ const streamIndex = this.native?.search_result_stream_index?.(index) ?? 0;
296
+ this.setCached(cacheKey, streamIndex);
297
+ return streamIndex;
298
+ }
299
+
300
+ /**
301
+ * Gets all properties of a search result at once
302
+ * More efficient than individual property calls
303
+ * @param results Search results handle
304
+ * @param index Index of the result
305
+ * @returns Object with all properties
306
+ */
307
+ async getSearchResultAllProperties(
308
+ results: any,
309
+ index: number
310
+ ): Promise<SearchResultProperties> {
311
+ const cacheKey = `search:all:${index}`;
312
+ if (this.resultCache.has(cacheKey)) {
313
+ return this.resultCache.get(cacheKey);
314
+ }
315
+
316
+ // Aggregate all properties by calling individual native functions
317
+ const context = this.native?.search_result_context?.(index, 50) ?? '';
318
+ const lineNumber = this.native?.search_result_line_number?.(index) ?? 0;
319
+ const paragraphNumber = this.native?.search_result_paragraph_number?.(index) ?? 0;
320
+ const confidence = this.native?.search_result_confidence?.(index) ?? 1.0;
321
+ const isHighlighted = this.native?.search_result_is_highlighted?.(index) ?? false;
322
+ const fontInfo = this.native?.search_result_font_info?.(index) ?? '{}';
323
+
324
+ let color: [number, number, number] = [0, 0, 0];
325
+ if (this.native?.search_result_color) {
326
+ try {
327
+ const colorJson = this.native.search_result_color(index);
328
+ const parsed = JSON.parse(colorJson);
329
+ color = [parsed.r ?? 0, parsed.g ?? 0, parsed.b ?? 0];
330
+ } catch {
331
+ color = [0, 0, 0];
332
+ }
333
+ }
334
+
335
+ const rotation = this.native?.search_result_rotation?.(index) ?? 0;
336
+ const objectId = this.native?.search_result_object_id?.(index) ?? 0;
337
+ const streamIndex = this.native?.search_result_stream_index?.(index) ?? 0;
338
+
339
+ const props: SearchResultProperties = {
340
+ context,
341
+ lineNumber,
342
+ paragraphNumber,
343
+ confidence,
344
+ isHighlighted,
345
+ fontInfo,
346
+ color,
347
+ rotation,
348
+ objectId,
349
+ streamIndex,
350
+ };
351
+ this.setCached(cacheKey, props);
352
+ this.emit('searchPropertiesExtracted', index);
353
+ return props;
354
+ }
355
+
356
+ // ========== Font Accessors (8 functions) ==========
357
+
358
+ /**
359
+ * Gets the base font name
360
+ * @param fonts Font handle
361
+ * @param index Index of the font
362
+ * @returns Font name (e.g., "Helvetica", "Arial")
363
+ */
364
+ async getFontBaseFontName(fonts: any, index: number): Promise<string> {
365
+ const cacheKey = `font:basename:${index}`;
366
+ if (this.resultCache.has(cacheKey)) {
367
+ return this.resultCache.get(cacheKey);
368
+ }
369
+
370
+ const name = this.native?.font_get_base_font_name?.(index) ?? '';
371
+ this.setCached(cacheKey, name);
372
+ return name;
373
+ }
374
+
375
+ /**
376
+ * Gets the font descriptor JSON
377
+ * Contains details about font metrics and characteristics
378
+ * @param fonts Font handle
379
+ * @param index Index of the font
380
+ * @returns Font descriptor as JSON string
381
+ */
382
+ async getFontDescriptor(fonts: any, index: number): Promise<string> {
383
+ const cacheKey = `font:descriptor:${index}`;
384
+ if (this.resultCache.has(cacheKey)) {
385
+ return this.resultCache.get(cacheKey);
386
+ }
387
+
388
+ const descriptor = this.native?.font_get_descriptor?.(index) ?? '{}';
389
+ this.setCached(cacheKey, descriptor);
390
+ return descriptor;
391
+ }
392
+
393
+ /**
394
+ * Gets the descendant font name (for composite fonts)
395
+ * @param fonts Font handle
396
+ * @param index Index of the font
397
+ * @returns Descendant font name or empty string
398
+ */
399
+ async getFontDescendantFont(fonts: any, index: number): Promise<string> {
400
+ const cacheKey = `font:descendant:${index}`;
401
+ if (this.resultCache.has(cacheKey)) {
402
+ return this.resultCache.get(cacheKey);
403
+ }
404
+
405
+ const descendant = this.native?.font_get_descendant_font?.(index) ?? '';
406
+ this.setCached(cacheKey, descendant);
407
+ return descendant;
408
+ }
409
+
410
+ /**
411
+ * Gets the ToUnicode CMap for character to Unicode mapping
412
+ * @param fonts Font handle
413
+ * @param index Index of the font
414
+ * @returns ToUnicode CMap as string
415
+ */
416
+ async getFontToUnicodeCmap(fonts: any, index: number): Promise<string> {
417
+ const cacheKey = `font:tounicode:${index}`;
418
+ if (this.resultCache.has(cacheKey)) {
419
+ return this.resultCache.get(cacheKey);
420
+ }
421
+
422
+ const cmap = this.native?.font_get_to_unicode_cmap?.(index) ?? '';
423
+ this.setCached(cacheKey, cmap);
424
+ return cmap;
425
+ }
426
+
427
+ /**
428
+ * Checks if font is vertical (top-to-bottom layout)
429
+ * @param fonts Font handle
430
+ * @param index Index of the font
431
+ * @returns True if font is vertical
432
+ */
433
+ async isFontVertical(fonts: any, index: number): Promise<boolean> {
434
+ const cacheKey = `font:isvertical:${index}`;
435
+ if (this.resultCache.has(cacheKey)) {
436
+ return this.resultCache.get(cacheKey);
437
+ }
438
+
439
+ const vertical = this.native?.font_is_vertical?.(index) ?? false;
440
+ this.setCached(cacheKey, vertical);
441
+ return vertical;
442
+ }
443
+
444
+ /**
445
+ * Gets character widths for the font
446
+ * @param fonts Font handle
447
+ * @param index Index of the font
448
+ * @returns Array of character widths
449
+ */
450
+ async getFontWidths(fonts: any, index: number): Promise<Float32Array> {
451
+ const cacheKey = `font:widths:${index}`;
452
+ if (this.resultCache.has(cacheKey)) {
453
+ return this.resultCache.get(cacheKey);
454
+ }
455
+
456
+ const widths = this.native?.font_get_widths?.(index) ?? new Float32Array();
457
+ this.setCached(cacheKey, widths);
458
+ return widths;
459
+ }
460
+
461
+ /**
462
+ * Gets the ascender metric (height above baseline)
463
+ * @param fonts Font handle
464
+ * @param index Index of the font
465
+ * @returns Ascender value in font units
466
+ */
467
+ async getFontAscender(fonts: any, index: number): Promise<number> {
468
+ const cacheKey = `font:ascender:${index}`;
469
+ if (this.resultCache.has(cacheKey)) {
470
+ return this.resultCache.get(cacheKey);
471
+ }
472
+
473
+ const ascender = this.native?.font_get_ascender?.(index) ?? 0;
474
+ this.setCached(cacheKey, ascender);
475
+ return ascender;
476
+ }
477
+
478
+ /**
479
+ * Gets the descender metric (depth below baseline)
480
+ * @param fonts Font handle
481
+ * @param index Index of the font
482
+ * @returns Descender value in font units (usually negative)
483
+ */
484
+ async getFontDescender(fonts: any, index: number): Promise<number> {
485
+ const cacheKey = `font:descender:${index}`;
486
+ if (this.resultCache.has(cacheKey)) {
487
+ return this.resultCache.get(cacheKey);
488
+ }
489
+
490
+ const descender = this.native?.font_get_descender?.(index) ?? 0;
491
+ this.setCached(cacheKey, descender);
492
+ return descender;
493
+ }
494
+
495
+ /**
496
+ * Gets all font properties at once
497
+ * More efficient than individual property calls
498
+ * @param fonts Font handle
499
+ * @param index Index of the font
500
+ * @returns Object with all font properties
501
+ */
502
+ async getFontAllProperties(fonts: any, index: number): Promise<FontProperties> {
503
+ const cacheKey = `font:all:${index}`;
504
+ if (this.resultCache.has(cacheKey)) {
505
+ return this.resultCache.get(cacheKey);
506
+ }
507
+
508
+ // Aggregate all font properties by calling individual native functions
509
+ const baseFontName = this.native?.font_get_base_font_name?.(index) ?? '';
510
+ const descriptor = this.native?.font_get_descriptor?.(index) ?? '{}';
511
+ const descendantFont = this.native?.font_get_descendant_font?.(index) ?? '';
512
+ const toUnicodeCmap = this.native?.font_get_to_unicode_cmap?.(index) ?? '';
513
+ const isVertical = this.native?.font_is_vertical?.(index) ?? false;
514
+ const widths = this.native?.font_get_widths?.(index) ?? new Float32Array();
515
+ const ascender = this.native?.font_get_ascender?.(index) ?? 0;
516
+ const descender = this.native?.font_get_descender?.(index) ?? 0;
517
+
518
+ const props: FontProperties = {
519
+ baseFontName,
520
+ descriptor,
521
+ descendantFont,
522
+ toUnicodeCmap,
523
+ isVertical,
524
+ widths,
525
+ ascender,
526
+ descender,
527
+ };
528
+ this.setCached(cacheKey, props);
529
+ this.emit('fontPropertiesExtracted', index);
530
+ return props;
531
+ }
532
+
533
+ // ========== Image Accessors (5 functions) ==========
534
+
535
+ /**
536
+ * Checks if image has an alpha channel
537
+ * @param images Image handle
538
+ * @param index Index of the image
539
+ * @returns True if alpha channel is present
540
+ */
541
+ async hasImageAlphaChannel(images: any, index: number): Promise<boolean> {
542
+ const cacheKey = `image:hasalpha:${index}`;
543
+ if (this.resultCache.has(cacheKey)) {
544
+ return this.resultCache.get(cacheKey);
545
+ }
546
+
547
+ const hasAlpha = this.native?.image_has_alpha_channel?.(index) ?? false;
548
+ this.setCached(cacheKey, hasAlpha);
549
+ return hasAlpha;
550
+ }
551
+
552
+ /**
553
+ * Gets the ICC color profile
554
+ * @param images Image handle
555
+ * @param index Index of the image
556
+ * @returns ICC profile as binary data
557
+ */
558
+ async getImageIccProfile(images: any, index: number): Promise<Uint8Array> {
559
+ const cacheKey = `image:iccprofile:${index}`;
560
+ if (this.resultCache.has(cacheKey)) {
561
+ return this.resultCache.get(cacheKey);
562
+ }
563
+
564
+ const profile = this.native?.image_get_icc_profile?.(index) ?? new Uint8Array();
565
+ this.setCached(cacheKey, profile);
566
+ return profile;
567
+ }
568
+
569
+ /**
570
+ * Gets the filter chain applied to the image
571
+ * (e.g., ["FlateDecode", "DCTDecode"])
572
+ * @param images Image handle
573
+ * @param index Index of the image
574
+ * @returns Filter chain as JSON string
575
+ */
576
+ async getImageFilterChain(images: any, index: number): Promise<string> {
577
+ const cacheKey = `image:filterchain:${index}`;
578
+ if (this.resultCache.has(cacheKey)) {
579
+ return this.resultCache.get(cacheKey);
580
+ }
581
+
582
+ const filterChain = this.native?.image_get_filter_chain?.(index) ?? '[]';
583
+ this.setCached(cacheKey, filterChain);
584
+ return filterChain;
585
+ }
586
+
587
+ /**
588
+ * Gets the decoded image data
589
+ * @param images Image handle
590
+ * @param index Index of the image
591
+ * @returns Decoded image data as binary
592
+ */
593
+ async getImageDecodedData(images: any, index: number): Promise<Uint8Array> {
594
+ const cacheKey = `image:decoded:${index}`;
595
+ if (this.resultCache.has(cacheKey)) {
596
+ return this.resultCache.get(cacheKey);
597
+ }
598
+
599
+ const data = this.native?.image_get_decoded_data?.(index) ?? new Uint8Array();
600
+ this.setCached(cacheKey, data);
601
+ return data;
602
+ }
603
+
604
+ /**
605
+ * Gets the image width in pixels
606
+ * @param images Image handle
607
+ * @param index Index of the image
608
+ * @returns Width in pixels
609
+ */
610
+ async getImageWidth(images: any, index: number): Promise<number> {
611
+ const cacheKey = `image:width:${index}`;
612
+ if (this.resultCache.has(cacheKey)) {
613
+ return this.resultCache.get(cacheKey);
614
+ }
615
+
616
+ const width = this.native?.image_get_width?.(index) ?? 0;
617
+ this.setCached(cacheKey, width);
618
+ return width;
619
+ }
620
+
621
+ /**
622
+ * Gets the image height in pixels
623
+ * @param images Image handle
624
+ * @param index Index of the image
625
+ * @returns Height in pixels
626
+ */
627
+ async getImageHeight(images: any, index: number): Promise<number> {
628
+ const cacheKey = `image:height:${index}`;
629
+ if (this.resultCache.has(cacheKey)) {
630
+ return this.resultCache.get(cacheKey);
631
+ }
632
+
633
+ const height = this.native?.image_get_height?.(index) ?? 0;
634
+ this.setCached(cacheKey, height);
635
+ return height;
636
+ }
637
+
638
+ /**
639
+ * Gets the color space of the image
640
+ * @param images Image handle
641
+ * @param index Index of the image
642
+ * @returns Color space name (e.g., "RGB", "CMYK", "Gray")
643
+ */
644
+ async getImageColorSpace(images: any, index: number): Promise<string> {
645
+ const cacheKey = `image:colorspace:${index}`;
646
+ if (this.resultCache.has(cacheKey)) {
647
+ return this.resultCache.get(cacheKey);
648
+ }
649
+
650
+ const colorSpace = this.native?.image_get_color_space?.(index) ?? 'RGB';
651
+ this.setCached(cacheKey, colorSpace);
652
+ return colorSpace;
653
+ }
654
+
655
+ /**
656
+ * Gets all image properties at once
657
+ * More efficient than individual property calls
658
+ * @param images Image handle
659
+ * @param index Index of the image
660
+ * @returns Object with all image properties
661
+ */
662
+ async getImageAllProperties(images: any, index: number): Promise<ImageProperties> {
663
+ const cacheKey = `image:all:${index}`;
664
+ if (this.resultCache.has(cacheKey)) {
665
+ return this.resultCache.get(cacheKey);
666
+ }
667
+
668
+ // Aggregate all image properties by calling individual native functions
669
+ const hasAlphaChannel = this.native?.image_has_alpha_channel?.(index) ?? false;
670
+ const iccProfile = this.native?.image_get_icc_profile?.(index) ?? new Uint8Array();
671
+ const filterChain = this.native?.image_get_filter_chain?.(index) ?? '[]';
672
+ const decodedData = this.native?.image_get_decoded_data?.(index) ?? new Uint8Array();
673
+ const width = this.native?.image_get_width?.(index) ?? 0;
674
+ const height = this.native?.image_get_height?.(index) ?? 0;
675
+ const colorSpace = this.native?.image_get_color_space?.(index) ?? 'RGB';
676
+
677
+ const props: ImageProperties = {
678
+ hasAlphaChannel,
679
+ iccProfile,
680
+ filterChain,
681
+ decodedData,
682
+ width,
683
+ height,
684
+ colorSpace,
685
+ };
686
+ this.setCached(cacheKey, props);
687
+ this.emit('imagePropertiesExtracted', index);
688
+ return props;
689
+ }
690
+
691
+ // ========== Annotation Accessors (6 functions) ==========
692
+
693
+ /**
694
+ * Gets the modified date of an annotation
695
+ * @param annotations Annotation handle
696
+ * @param index Index of the annotation
697
+ * @returns Timestamp in milliseconds
698
+ */
699
+ async getAnnotationModifiedDate(
700
+ annotations: any,
701
+ index: number
702
+ ): Promise<number> {
703
+ const cacheKey = `annotation:modifieddate:${index}`;
704
+ if (this.resultCache.has(cacheKey)) {
705
+ return this.resultCache.get(cacheKey);
706
+ }
707
+
708
+ const timestamp = this.native?.annotation_get_modified_date?.(index) ?? 0;
709
+ this.setCached(cacheKey, timestamp);
710
+ return timestamp;
711
+ }
712
+
713
+ /**
714
+ * Gets the subject/title of an annotation
715
+ * @param annotations Annotation handle
716
+ * @param index Index of the annotation
717
+ * @returns Subject text
718
+ */
719
+ async getAnnotationSubject(annotations: any, index: number): Promise<string> {
720
+ const cacheKey = `annotation:subject:${index}`;
721
+ if (this.resultCache.has(cacheKey)) {
722
+ return this.resultCache.get(cacheKey);
723
+ }
724
+
725
+ const subject = this.native?.annotation_get_subject?.(index) ?? '';
726
+ this.setCached(cacheKey, subject);
727
+ return subject;
728
+ }
729
+
730
+ /**
731
+ * Gets the index of the annotation this is replying to
732
+ * @param annotations Annotation handle
733
+ * @param index Index of the annotation
734
+ * @returns Index of parent annotation, or -1 if not a reply
735
+ */
736
+ async getAnnotationReplyToIndex(annotations: any, index: number): Promise<number> {
737
+ const cacheKey = `annotation:replyto:${index}`;
738
+ if (this.resultCache.has(cacheKey)) {
739
+ return this.resultCache.get(cacheKey);
740
+ }
741
+
742
+ const replyToIndex = this.native?.annotation_get_reply_to?.(index) ?? -1;
743
+ this.setCached(cacheKey, replyToIndex);
744
+ return replyToIndex;
745
+ }
746
+
747
+ /**
748
+ * Gets the page number where annotation appears
749
+ * @param annotations Annotation handle
750
+ * @param index Index of the annotation
751
+ * @returns Page number (0-based)
752
+ */
753
+ async getAnnotationPageNumber(annotations: any, index: number): Promise<number> {
754
+ const cacheKey = `annotation:pagenum:${index}`;
755
+ if (this.resultCache.has(cacheKey)) {
756
+ return this.resultCache.get(cacheKey);
757
+ }
758
+
759
+ const pageNumber = this.native?.annotation_get_page_number?.(index) ?? 0;
760
+ this.setCached(cacheKey, pageNumber);
761
+ return pageNumber;
762
+ }
763
+
764
+ /**
765
+ * Gets the icon name for the annotation
766
+ * (e.g., "Comment", "Note", "Help")
767
+ * @param annotations Annotation handle
768
+ * @param index Index of the annotation
769
+ * @returns Icon name
770
+ */
771
+ async getAnnotationIconName(annotations: any, index: number): Promise<string> {
772
+ const cacheKey = `annotation:icon:${index}`;
773
+ if (this.resultCache.has(cacheKey)) {
774
+ return this.resultCache.get(cacheKey);
775
+ }
776
+
777
+ const icon = this.native?.annotation_get_icon_name?.(index) ?? '';
778
+ this.setCached(cacheKey, icon);
779
+ return icon;
780
+ }
781
+
782
+ /**
783
+ * Gets the author/creator of the annotation
784
+ * @param annotations Annotation handle
785
+ * @param index Index of the annotation
786
+ * @returns Author name
787
+ */
788
+ async getAnnotationAuthor(annotations: any, index: number): Promise<string> {
789
+ const cacheKey = `annotation:author:${index}`;
790
+ if (this.resultCache.has(cacheKey)) {
791
+ return this.resultCache.get(cacheKey);
792
+ }
793
+
794
+ const author = this.native?.annotation_get_author?.(index) ?? '';
795
+ this.setCached(cacheKey, author);
796
+ return author;
797
+ }
798
+
799
+ /**
800
+ * Gets all annotation properties at once
801
+ * More efficient than individual property calls
802
+ * @param annotations Annotation handle
803
+ * @param index Index of the annotation
804
+ * @returns Object with all annotation properties
805
+ */
806
+ async getAnnotationAllProperties(
807
+ annotations: any,
808
+ index: number
809
+ ): Promise<AnnotationProperties> {
810
+ const cacheKey = `annotation:all:${index}`;
811
+ if (this.resultCache.has(cacheKey)) {
812
+ return this.resultCache.get(cacheKey);
813
+ }
814
+
815
+ // Aggregate all annotation properties by calling individual native functions
816
+ const modifiedDate = this.native?.annotation_get_modified_date?.(index) ?? 0;
817
+ const subject = this.native?.annotation_get_subject?.(index) ?? '';
818
+ const replyToIndex = this.native?.annotation_get_reply_to?.(index) ?? -1;
819
+ const pageNumber = this.native?.annotation_get_page_number?.(index) ?? 0;
820
+ const iconName = this.native?.annotation_get_icon_name?.(index) ?? '';
821
+ const author = this.native?.annotation_get_author?.(index) ?? '';
822
+
823
+ const props: AnnotationProperties = {
824
+ modifiedDate,
825
+ subject,
826
+ replyToIndex,
827
+ pageNumber,
828
+ iconName,
829
+ author,
830
+ };
831
+ this.setCached(cacheKey, props);
832
+ this.emit('annotationPropertiesExtracted', index);
833
+ return props;
834
+ }
835
+
836
+ // ========== Cache Management ==========
837
+
838
+ /**
839
+ * Clears the result cache
840
+ */
841
+ clearCache(): void {
842
+ this.resultCache.clear();
843
+ this.emit('cacheCleared');
844
+ }
845
+
846
+ /**
847
+ * Gets cache statistics
848
+ * @returns Object with cache information
849
+ */
850
+ getCacheStats(): Record<string, any> {
851
+ return {
852
+ cacheSize: this.resultCache.size,
853
+ maxCacheSize: this.maxCacheSize,
854
+ entries: Array.from(this.resultCache.keys()),
855
+ };
856
+ }
857
+
858
+ private clearCachePattern(pattern: string): void {
859
+ const regex = new RegExp(pattern);
860
+ const keysToDelete = Array.from(this.resultCache.keys()).filter((key) =>
861
+ regex.test(key)
862
+ );
863
+ keysToDelete.forEach((key) => this.resultCache.delete(key));
864
+ }
865
+ }
866
+
867
+ export default ResultAccessorsManager;