react-native-pdf-jsi 4.2.1 → 4.2.2
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.
- package/README.md +13 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +74 -0
- package/index.d.ts +244 -0
- package/index.js +5 -1
- package/ios/RNPDFPdf/PDFExporter.m +83 -0
- package/package.json +1 -1
- package/src/PDFCompressor.js +476 -0
- package/src/index.js +15 -1
package/README.md
CHANGED
|
@@ -409,6 +409,19 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
409
409
|
- **Author**: Punith M ([@126punith](https://github.com/126punith))
|
|
410
410
|
|
|
411
411
|
## Recent Fixes
|
|
412
|
+
|
|
413
|
+
### PDFCompressor Module Fix (v4.2.2)
|
|
414
|
+
Fixed "Unable to resolve module react-native-pdf-jsi/src/PDFCompressor" error ([#17](https://github.com/126punith/react-native-pdf-jsi/issues/17)). The PDFCompressor module is now properly exported and accessible. Also fixed iOS compilation error for missing `RCTLogInfo` import. The compression feature now works correctly with accurate size estimates (~15-18% compression using native zlib deflate).
|
|
415
|
+
|
|
416
|
+
**Usage:**
|
|
417
|
+
```jsx
|
|
418
|
+
import { PDFCompressor, CompressionPreset } from 'react-native-pdf-jsi';
|
|
419
|
+
|
|
420
|
+
// Compress a PDF
|
|
421
|
+
const result = await PDFCompressor.compressWithPreset(pdfPath, CompressionPreset.WEB);
|
|
422
|
+
console.log(`Compressed: ${result.originalSizeMB}MB → ${result.compressedSizeMB}MB`);
|
|
423
|
+
```
|
|
424
|
+
|
|
412
425
|
### iOS Performance - Unnecessary Path Handlers (v4.2.1)
|
|
413
426
|
Use v4.2.1 it contains stable fixes for IOS with unwanted debug logs removed
|
|
414
427
|
|
|
@@ -734,4 +734,78 @@ public class PDFExporter extends ReactContextBaseJavaModule {
|
|
|
734
734
|
promise.reject("PAGE_COUNT_ERROR", e.getMessage());
|
|
735
735
|
}
|
|
736
736
|
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Compress PDF using streaming processor
|
|
740
|
+
* Uses O(1) constant memory regardless of file size
|
|
741
|
+
* @param inputPath Input PDF file path
|
|
742
|
+
* @param outputPath Output compressed PDF file path
|
|
743
|
+
* @param compressionLevel Compression level (0-9, 9 is maximum compression)
|
|
744
|
+
* @param promise Promise to resolve with compression result
|
|
745
|
+
*/
|
|
746
|
+
@ReactMethod
|
|
747
|
+
public void compressPDF(String inputPath, String outputPath, int compressionLevel, Promise promise) {
|
|
748
|
+
try {
|
|
749
|
+
Log.d(TAG, "compressPDF called with inputPath: " + inputPath + ", outputPath: " + outputPath + ", level: " + compressionLevel);
|
|
750
|
+
|
|
751
|
+
if (inputPath == null || inputPath.isEmpty()) {
|
|
752
|
+
promise.reject("INVALID_PATH", "Input file path is required");
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
File inputFile = new File(inputPath);
|
|
757
|
+
if (!inputFile.exists()) {
|
|
758
|
+
promise.reject("FILE_NOT_FOUND", "Input PDF file not found: " + inputPath);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Generate output path if not provided
|
|
763
|
+
File outputFile;
|
|
764
|
+
if (outputPath == null || outputPath.isEmpty()) {
|
|
765
|
+
String baseName = inputFile.getName().replaceAll("\\.pdf$", "");
|
|
766
|
+
String outputFileName = generateTimestampedFileName(baseName + "_compressed", -1, "pdf");
|
|
767
|
+
outputFile = new File(inputFile.getParent(), outputFileName);
|
|
768
|
+
} else {
|
|
769
|
+
outputFile = new File(outputPath);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Ensure output directory exists
|
|
773
|
+
File outputDir = outputFile.getParentFile();
|
|
774
|
+
if (outputDir != null && !outputDir.exists()) {
|
|
775
|
+
outputDir.mkdirs();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
Log.d(TAG, "Starting compression: " + inputFile.getAbsolutePath() + " -> " + outputFile.getAbsolutePath());
|
|
779
|
+
|
|
780
|
+
// Use StreamingPDFProcessor for O(1) memory compression
|
|
781
|
+
StreamingPDFProcessor processor = new StreamingPDFProcessor();
|
|
782
|
+
StreamingPDFProcessor.CompressionResult result = processor.compressPDFStreaming(
|
|
783
|
+
inputFile,
|
|
784
|
+
outputFile,
|
|
785
|
+
compressionLevel
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
// Build response
|
|
789
|
+
WritableMap response = Arguments.createMap();
|
|
790
|
+
response.putDouble("originalSize", result.originalSize);
|
|
791
|
+
response.putDouble("compressedSize", result.compressedSize);
|
|
792
|
+
response.putDouble("durationMs", result.durationMs);
|
|
793
|
+
response.putDouble("compressionRatio", result.compressionRatio);
|
|
794
|
+
response.putDouble("spaceSavedPercent", result.spaceSavedPercent);
|
|
795
|
+
response.putString("outputPath", outputFile.getAbsolutePath());
|
|
796
|
+
response.putBoolean("success", true);
|
|
797
|
+
|
|
798
|
+
Log.d(TAG, String.format("Compression complete: %.2f MB -> %.2f MB (%.1f%% saved) in %dms",
|
|
799
|
+
result.originalSize / (1024.0 * 1024.0),
|
|
800
|
+
result.compressedSize / (1024.0 * 1024.0),
|
|
801
|
+
result.spaceSavedPercent,
|
|
802
|
+
result.durationMs));
|
|
803
|
+
|
|
804
|
+
promise.resolve(response);
|
|
805
|
+
|
|
806
|
+
} catch (Exception e) {
|
|
807
|
+
Log.e(TAG, "Error compressing PDF", e);
|
|
808
|
+
promise.reject("COMPRESSION_ERROR", e.getMessage(), e);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
737
811
|
}
|
package/index.d.ts
CHANGED
|
@@ -219,3 +219,247 @@ export const PDFCache: PDFCacheManager;
|
|
|
219
219
|
* CacheManager (alias for PDFCache)
|
|
220
220
|
*/
|
|
221
221
|
export const CacheManager: PDFCacheManager;
|
|
222
|
+
|
|
223
|
+
// ========================================
|
|
224
|
+
// PDFCompressor (PDF Compression)
|
|
225
|
+
// ========================================
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Compression presets for different use cases
|
|
229
|
+
*/
|
|
230
|
+
export enum CompressionPreset {
|
|
231
|
+
/** Optimized for email attachments (high compression, smaller file) */
|
|
232
|
+
EMAIL = 'email',
|
|
233
|
+
/** Optimized for web viewing (balanced compression) */
|
|
234
|
+
WEB = 'web',
|
|
235
|
+
/** Optimized for mobile devices (good compression, fast decompression) */
|
|
236
|
+
MOBILE = 'mobile',
|
|
237
|
+
/** Optimized for printing (low compression, high quality) */
|
|
238
|
+
PRINT = 'print',
|
|
239
|
+
/** Optimized for long-term archival (maximum compression) */
|
|
240
|
+
ARCHIVE = 'archive',
|
|
241
|
+
/** Custom compression settings */
|
|
242
|
+
CUSTOM = 'custom'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Compression levels (0-9, higher = more compression but slower)
|
|
247
|
+
*/
|
|
248
|
+
export enum CompressionLevel {
|
|
249
|
+
NONE = 0,
|
|
250
|
+
FASTEST = 1,
|
|
251
|
+
FAST = 3,
|
|
252
|
+
BALANCED = 5,
|
|
253
|
+
DEFAULT = 6,
|
|
254
|
+
GOOD = 7,
|
|
255
|
+
BETTER = 8,
|
|
256
|
+
BEST = 9
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Compression options
|
|
261
|
+
*/
|
|
262
|
+
export interface CompressionOptions {
|
|
263
|
+
/** Compression preset (from CompressionPreset) */
|
|
264
|
+
preset?: CompressionPreset;
|
|
265
|
+
/** Compression level (0-9), overrides preset */
|
|
266
|
+
level?: number;
|
|
267
|
+
/** Output file path (optional, auto-generated if not provided) */
|
|
268
|
+
outputPath?: string;
|
|
269
|
+
/** Progress callback function */
|
|
270
|
+
onProgress?: (progress: { progress: number; bytesProcessed: number; totalBytes: number }) => void;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Compression result
|
|
275
|
+
*/
|
|
276
|
+
export interface CompressionResult {
|
|
277
|
+
/** Whether compression was successful */
|
|
278
|
+
success: boolean;
|
|
279
|
+
/** Input file path */
|
|
280
|
+
inputPath: string;
|
|
281
|
+
/** Output file path */
|
|
282
|
+
outputPath: string;
|
|
283
|
+
/** Original file size in bytes */
|
|
284
|
+
originalSize: number;
|
|
285
|
+
/** Compressed file size in bytes */
|
|
286
|
+
compressedSize: number;
|
|
287
|
+
/** Original file size in MB */
|
|
288
|
+
originalSizeMB: number;
|
|
289
|
+
/** Compressed file size in MB */
|
|
290
|
+
compressedSizeMB: number;
|
|
291
|
+
/** Compression ratio (0-1, lower is better) */
|
|
292
|
+
compressionRatio: number;
|
|
293
|
+
/** Space saved percentage */
|
|
294
|
+
spaceSavedPercent: number;
|
|
295
|
+
/** Duration in milliseconds */
|
|
296
|
+
durationMs: number;
|
|
297
|
+
/** Throughput in MB/s */
|
|
298
|
+
throughputMBps: number;
|
|
299
|
+
/** Preset used */
|
|
300
|
+
preset: CompressionPreset;
|
|
301
|
+
/** Compression level used */
|
|
302
|
+
compressionLevel: number;
|
|
303
|
+
/** Method used (native_streaming or fallback) */
|
|
304
|
+
method: string;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Compression estimate result
|
|
309
|
+
*/
|
|
310
|
+
export interface CompressionEstimate {
|
|
311
|
+
/** Input file path */
|
|
312
|
+
inputPath: string;
|
|
313
|
+
/** Original file size in bytes */
|
|
314
|
+
originalSize: number;
|
|
315
|
+
/** Original file size in MB */
|
|
316
|
+
originalSizeMB: number;
|
|
317
|
+
/** Estimated compressed size in bytes */
|
|
318
|
+
estimatedCompressedSize: number;
|
|
319
|
+
/** Estimated compressed size in MB */
|
|
320
|
+
estimatedCompressedSizeMB: number;
|
|
321
|
+
/** Estimated compression ratio */
|
|
322
|
+
estimatedCompressionRatio: number;
|
|
323
|
+
/** Estimated space savings percentage */
|
|
324
|
+
estimatedSavingsPercent: number;
|
|
325
|
+
/** Estimated duration in milliseconds */
|
|
326
|
+
estimatedDurationMs: number;
|
|
327
|
+
/** Preset used for estimate */
|
|
328
|
+
preset: CompressionPreset;
|
|
329
|
+
/** Description of the preset */
|
|
330
|
+
presetDescription: string;
|
|
331
|
+
/** Confidence level of the estimate */
|
|
332
|
+
confidence: 'low' | 'medium' | 'high';
|
|
333
|
+
/** Additional notes */
|
|
334
|
+
note: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* PDFCompressor capabilities
|
|
339
|
+
*/
|
|
340
|
+
export interface CompressionCapabilities {
|
|
341
|
+
/** Whether streaming compression is available */
|
|
342
|
+
streamingCompression: boolean;
|
|
343
|
+
/** Available presets */
|
|
344
|
+
presets: CompressionPreset[];
|
|
345
|
+
/** Maximum file size in MB */
|
|
346
|
+
maxFileSizeMB: number;
|
|
347
|
+
/** Supported platforms */
|
|
348
|
+
supportedPlatforms: string[];
|
|
349
|
+
/** Current platform */
|
|
350
|
+
currentPlatform: string;
|
|
351
|
+
/** Whether native module is available */
|
|
352
|
+
nativeModuleAvailable: boolean;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* PDFCompressor Manager for PDF compression
|
|
357
|
+
* Uses native streaming for O(1) memory operations on large files (1GB+)
|
|
358
|
+
*/
|
|
359
|
+
export interface PDFCompressorManager {
|
|
360
|
+
/**
|
|
361
|
+
* Check if compression functionality is available
|
|
362
|
+
* @returns True if compression is available
|
|
363
|
+
*/
|
|
364
|
+
isAvailable(): boolean;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get compression capabilities
|
|
368
|
+
* @returns Capabilities object
|
|
369
|
+
*/
|
|
370
|
+
getCapabilities(): CompressionCapabilities;
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Compress a PDF file
|
|
374
|
+
* @param inputPath Path to input PDF file
|
|
375
|
+
* @param options Compression options
|
|
376
|
+
* @returns Promise resolving to compression result
|
|
377
|
+
*/
|
|
378
|
+
compress(inputPath: string, options?: CompressionOptions): Promise<CompressionResult>;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Compress PDF with a specific preset
|
|
382
|
+
* @param inputPath Path to input PDF file
|
|
383
|
+
* @param preset Compression preset
|
|
384
|
+
* @param outputPath Output file path (optional)
|
|
385
|
+
* @returns Promise resolving to compression result
|
|
386
|
+
*/
|
|
387
|
+
compressWithPreset(inputPath: string, preset: CompressionPreset, outputPath?: string): Promise<CompressionResult>;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Compress PDF for email (maximum compression)
|
|
391
|
+
* @param inputPath Path to input PDF file
|
|
392
|
+
* @param outputPath Output file path (optional)
|
|
393
|
+
* @returns Promise resolving to compression result
|
|
394
|
+
*/
|
|
395
|
+
compressForEmail(inputPath: string, outputPath?: string): Promise<CompressionResult>;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Compress PDF for web viewing
|
|
399
|
+
* @param inputPath Path to input PDF file
|
|
400
|
+
* @param outputPath Output file path (optional)
|
|
401
|
+
* @returns Promise resolving to compression result
|
|
402
|
+
*/
|
|
403
|
+
compressForWeb(inputPath: string, outputPath?: string): Promise<CompressionResult>;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Compress PDF for mobile viewing
|
|
407
|
+
* @param inputPath Path to input PDF file
|
|
408
|
+
* @param outputPath Output file path (optional)
|
|
409
|
+
* @returns Promise resolving to compression result
|
|
410
|
+
*/
|
|
411
|
+
compressForMobile(inputPath: string, outputPath?: string): Promise<CompressionResult>;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Compress PDF for archival (maximum compression)
|
|
415
|
+
* @param inputPath Path to input PDF file
|
|
416
|
+
* @param outputPath Output file path (optional)
|
|
417
|
+
* @returns Promise resolving to compression result
|
|
418
|
+
*/
|
|
419
|
+
compressForArchive(inputPath: string, outputPath?: string): Promise<CompressionResult>;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Estimate compression result without actually compressing
|
|
423
|
+
* @param inputPath Path to input PDF file
|
|
424
|
+
* @param preset Compression preset
|
|
425
|
+
* @returns Promise resolving to estimated compression result
|
|
426
|
+
*/
|
|
427
|
+
estimateCompression(inputPath: string, preset?: CompressionPreset): Promise<CompressionEstimate>;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Delete a compressed file
|
|
431
|
+
* @param filePath Path to file to delete
|
|
432
|
+
* @returns Promise resolving to true if deleted successfully
|
|
433
|
+
*/
|
|
434
|
+
deleteCompressedFile(filePath: string): Promise<boolean>;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get preset configuration
|
|
438
|
+
* @param preset Preset name
|
|
439
|
+
* @returns Preset configuration
|
|
440
|
+
*/
|
|
441
|
+
getPresetConfig(preset: CompressionPreset): {
|
|
442
|
+
level: CompressionLevel;
|
|
443
|
+
targetSizeKB: number | null;
|
|
444
|
+
description: string;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get module information
|
|
449
|
+
* @returns Module info object
|
|
450
|
+
*/
|
|
451
|
+
getModuleInfo(): {
|
|
452
|
+
name: string;
|
|
453
|
+
version: string;
|
|
454
|
+
platform: string;
|
|
455
|
+
nativeAvailable: boolean;
|
|
456
|
+
presets: string[];
|
|
457
|
+
compressionLevels: string[];
|
|
458
|
+
capabilities: CompressionCapabilities;
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* PDFCompressor singleton instance
|
|
464
|
+
*/
|
|
465
|
+
export const PDFCompressor: PDFCompressorManager;
|
package/index.js
CHANGED
|
@@ -742,6 +742,7 @@ import BookmarkManager from './src/managers/BookmarkManager';
|
|
|
742
742
|
import AnalyticsManager from './src/managers/AnalyticsManager';
|
|
743
743
|
import FileManager from './src/managers/FileManager';
|
|
744
744
|
import CacheManager from './src/managers/CacheManager';
|
|
745
|
+
import PDFCompressor, { CompressionPreset, CompressionLevel } from './src/PDFCompressor';
|
|
745
746
|
|
|
746
747
|
// Alias for backward compatibility and intuitive naming
|
|
747
748
|
export const PDFCache = CacheManager;
|
|
@@ -751,7 +752,10 @@ export {
|
|
|
751
752
|
BookmarkManager,
|
|
752
753
|
AnalyticsManager,
|
|
753
754
|
FileManager,
|
|
754
|
-
CacheManager
|
|
755
|
+
CacheManager,
|
|
756
|
+
PDFCompressor,
|
|
757
|
+
CompressionPreset,
|
|
758
|
+
CompressionLevel
|
|
755
759
|
};
|
|
756
760
|
|
|
757
761
|
// ========================================
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#import "PDFExporter.h"
|
|
2
2
|
#import "ImagePool.h"
|
|
3
|
+
#import "StreamingPDFProcessor.h"
|
|
4
|
+
#import <React/RCTLog.h>
|
|
3
5
|
#import <PDFKit/PDFKit.h>
|
|
4
6
|
#import <UIKit/UIKit.h>
|
|
5
7
|
|
|
@@ -956,4 +958,85 @@ RCT_EXPORT_METHOD(getPageCount:(NSString *)filePath
|
|
|
956
958
|
resolve(@(pageCount));
|
|
957
959
|
}
|
|
958
960
|
|
|
961
|
+
/**
|
|
962
|
+
* Compress PDF using streaming processor
|
|
963
|
+
* Uses O(1) constant memory regardless of file size
|
|
964
|
+
* @param inputPath Input PDF file path
|
|
965
|
+
* @param outputPath Output compressed PDF file path
|
|
966
|
+
* @param compressionLevel Compression level (0-9, 9 is maximum compression)
|
|
967
|
+
*/
|
|
968
|
+
RCT_EXPORT_METHOD(compressPDF:(NSString *)inputPath
|
|
969
|
+
outputPath:(NSString *)outputPath
|
|
970
|
+
compressionLevel:(int)compressionLevel
|
|
971
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
972
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
973
|
+
|
|
974
|
+
RCTLogInfo(@"compressPDF called with inputPath: %@, outputPath: %@, level: %d", inputPath, outputPath, compressionLevel);
|
|
975
|
+
|
|
976
|
+
if (!inputPath || inputPath.length == 0) {
|
|
977
|
+
reject(@"INVALID_PATH", @"Input file path is required", nil);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
982
|
+
if (![fileManager fileExistsAtPath:inputPath]) {
|
|
983
|
+
reject(@"FILE_NOT_FOUND", [NSString stringWithFormat:@"Input PDF file not found: %@", inputPath], nil);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Generate output path if not provided
|
|
988
|
+
NSString *finalOutputPath = outputPath;
|
|
989
|
+
if (!finalOutputPath || finalOutputPath.length == 0) {
|
|
990
|
+
NSString *directory = [inputPath stringByDeletingLastPathComponent];
|
|
991
|
+
NSString *baseName = [[inputPath lastPathComponent] stringByDeletingPathExtension];
|
|
992
|
+
NSString *outputFileName = [self generateTimestampedFileName:[baseName stringByAppendingString:@"_compressed"] pageNum:-1 extension:@"pdf"];
|
|
993
|
+
finalOutputPath = [directory stringByAppendingPathComponent:outputFileName];
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Ensure output directory exists
|
|
997
|
+
NSString *outputDir = [finalOutputPath stringByDeletingLastPathComponent];
|
|
998
|
+
if (![fileManager fileExistsAtPath:outputDir]) {
|
|
999
|
+
NSError *error;
|
|
1000
|
+
[fileManager createDirectoryAtPath:outputDir withIntermediateDirectories:YES attributes:nil error:&error];
|
|
1001
|
+
if (error) {
|
|
1002
|
+
reject(@"DIR_CREATE_ERROR", @"Failed to create output directory", error);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
RCTLogInfo(@"Starting compression: %@ -> %@", inputPath, finalOutputPath);
|
|
1008
|
+
|
|
1009
|
+
// Use StreamingPDFProcessor for O(1) memory compression
|
|
1010
|
+
StreamingPDFProcessor *processor = [StreamingPDFProcessor sharedInstance];
|
|
1011
|
+
NSError *error;
|
|
1012
|
+
CompressionResult *result = [processor compressPDFStreaming:inputPath
|
|
1013
|
+
outputPath:finalOutputPath
|
|
1014
|
+
compressionLevel:compressionLevel
|
|
1015
|
+
error:&error];
|
|
1016
|
+
|
|
1017
|
+
if (error || !result) {
|
|
1018
|
+
reject(@"COMPRESSION_ERROR", error ? error.localizedDescription : @"Compression failed", error);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Build response
|
|
1023
|
+
NSDictionary *response = @{
|
|
1024
|
+
@"originalSize": @(result.originalSize),
|
|
1025
|
+
@"compressedSize": @(result.compressedSize),
|
|
1026
|
+
@"durationMs": @(result.durationMs),
|
|
1027
|
+
@"compressionRatio": @(result.compressionRatio),
|
|
1028
|
+
@"spaceSavedPercent": @(result.spaceSavedPercent),
|
|
1029
|
+
@"outputPath": finalOutputPath,
|
|
1030
|
+
@"success": @YES
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
RCTLogInfo(@"Compression complete: %.2f MB -> %.2f MB (%.1f%% saved) in %.0fms",
|
|
1034
|
+
result.originalSize / (1024.0 * 1024.0),
|
|
1035
|
+
result.compressedSize / (1024.0 * 1024.0),
|
|
1036
|
+
result.spaceSavedPercent,
|
|
1037
|
+
result.durationMs);
|
|
1038
|
+
|
|
1039
|
+
resolve(response);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
959
1042
|
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-pdf-jsi",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.2",
|
|
4
4
|
"summary": "High-performance React Native PDF viewer with JSI acceleration - up to 80x faster than traditional bridge",
|
|
5
5
|
"description": "🚀 Ultra-fast React Native PDF viewer with JSI (JavaScript Interface) integration for maximum performance. Features lazy loading, smart caching, progressive loading, and zero-bridge overhead operations. Perfect for large PDF files with 30-day persistent cache and advanced memory optimization. Google Play 16KB page size compliant for Android 15+. Supports iOS, Android, and Windows platforms.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDFCompressor - PDF Compression Manager
|
|
3
|
+
* Handles PDF file compression with streaming support for large files
|
|
4
|
+
*
|
|
5
|
+
* Uses native StreamingPDFProcessor for O(1) memory operations
|
|
6
|
+
* Can compress 1GB+ PDFs without memory issues
|
|
7
|
+
*
|
|
8
|
+
* @author Punith M
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { NativeModules, Platform } from 'react-native';
|
|
13
|
+
import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
14
|
+
|
|
15
|
+
const { PDFExporter, StreamingPDFProcessor } = NativeModules;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compression presets for different use cases
|
|
19
|
+
*/
|
|
20
|
+
export const CompressionPreset = {
|
|
21
|
+
/** Optimized for email attachments (high compression, smaller file) */
|
|
22
|
+
EMAIL: 'email',
|
|
23
|
+
/** Optimized for web viewing (balanced compression) */
|
|
24
|
+
WEB: 'web',
|
|
25
|
+
/** Optimized for mobile devices (good compression, fast decompression) */
|
|
26
|
+
MOBILE: 'mobile',
|
|
27
|
+
/** Optimized for printing (low compression, high quality) */
|
|
28
|
+
PRINT: 'print',
|
|
29
|
+
/** Optimized for long-term archival (maximum compression) */
|
|
30
|
+
ARCHIVE: 'archive',
|
|
31
|
+
/** Custom compression settings */
|
|
32
|
+
CUSTOM: 'custom'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Compression levels (0-9, higher = more compression but slower)
|
|
37
|
+
*/
|
|
38
|
+
export const CompressionLevel = {
|
|
39
|
+
NONE: 0,
|
|
40
|
+
FASTEST: 1,
|
|
41
|
+
FAST: 3,
|
|
42
|
+
BALANCED: 5,
|
|
43
|
+
DEFAULT: 6,
|
|
44
|
+
GOOD: 7,
|
|
45
|
+
BETTER: 8,
|
|
46
|
+
BEST: 9
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Preset configurations
|
|
51
|
+
*/
|
|
52
|
+
const PRESET_CONFIGS = {
|
|
53
|
+
[CompressionPreset.EMAIL]: {
|
|
54
|
+
level: CompressionLevel.BEST,
|
|
55
|
+
targetSizeKB: 10000, // 10MB target
|
|
56
|
+
description: 'Maximum compression for email attachments'
|
|
57
|
+
},
|
|
58
|
+
[CompressionPreset.WEB]: {
|
|
59
|
+
level: CompressionLevel.GOOD,
|
|
60
|
+
targetSizeKB: 50000, // 50MB target
|
|
61
|
+
description: 'Balanced compression for web delivery'
|
|
62
|
+
},
|
|
63
|
+
[CompressionPreset.MOBILE]: {
|
|
64
|
+
level: CompressionLevel.BALANCED,
|
|
65
|
+
targetSizeKB: 25000, // 25MB target
|
|
66
|
+
description: 'Optimized for mobile viewing'
|
|
67
|
+
},
|
|
68
|
+
[CompressionPreset.PRINT]: {
|
|
69
|
+
level: CompressionLevel.FAST,
|
|
70
|
+
targetSizeKB: null, // No size limit
|
|
71
|
+
description: 'Low compression for print quality'
|
|
72
|
+
},
|
|
73
|
+
[CompressionPreset.ARCHIVE]: {
|
|
74
|
+
level: CompressionLevel.BEST,
|
|
75
|
+
targetSizeKB: null, // No size limit, just maximum compression
|
|
76
|
+
description: 'Maximum compression for archival'
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* PDFCompressor Class
|
|
82
|
+
* Provides PDF compression functionality with streaming support
|
|
83
|
+
*/
|
|
84
|
+
export class PDFCompressor {
|
|
85
|
+
constructor() {
|
|
86
|
+
this.isNativeAvailable = this._checkNativeAvailability();
|
|
87
|
+
|
|
88
|
+
if (this.isNativeAvailable) {
|
|
89
|
+
console.log('📦 PDFCompressor: Native streaming compression available');
|
|
90
|
+
} else {
|
|
91
|
+
console.warn('📦 PDFCompressor: Native module not available - compression may be limited');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if native compression module is available
|
|
97
|
+
* @private
|
|
98
|
+
* @returns {boolean} True if native module is available
|
|
99
|
+
*/
|
|
100
|
+
_checkNativeAvailability() {
|
|
101
|
+
// Check if PDFExporter module exists and has the compressPDF method
|
|
102
|
+
const hasCompressPDF = PDFExporter && typeof PDFExporter.compressPDF === 'function';
|
|
103
|
+
console.log('📦 PDFCompressor: Checking native availability...');
|
|
104
|
+
console.log('📦 PDFCompressor: PDFExporter exists:', !!PDFExporter);
|
|
105
|
+
console.log('📦 PDFCompressor: PDFExporter.compressPDF exists:', hasCompressPDF);
|
|
106
|
+
return hasCompressPDF;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if compression is available
|
|
111
|
+
* @returns {boolean} True if compression functionality is available
|
|
112
|
+
*/
|
|
113
|
+
isAvailable() {
|
|
114
|
+
return this.isNativeAvailable;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get compression capabilities
|
|
119
|
+
* @returns {Object} Capabilities object
|
|
120
|
+
*/
|
|
121
|
+
getCapabilities() {
|
|
122
|
+
return {
|
|
123
|
+
streamingCompression: this.isNativeAvailable,
|
|
124
|
+
presets: Object.values(CompressionPreset),
|
|
125
|
+
maxFileSizeMB: this.isNativeAvailable ? 1024 : 100, // 1GB+ with native, 100MB without
|
|
126
|
+
supportedPlatforms: ['ios', 'android'],
|
|
127
|
+
currentPlatform: Platform.OS,
|
|
128
|
+
nativeModuleAvailable: this.isNativeAvailable
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get preset configuration
|
|
134
|
+
* @param {string} preset - Preset name from CompressionPreset
|
|
135
|
+
* @returns {Object} Preset configuration
|
|
136
|
+
*/
|
|
137
|
+
getPresetConfig(preset) {
|
|
138
|
+
return PRESET_CONFIGS[preset] || PRESET_CONFIGS[CompressionPreset.WEB];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Compress a PDF file
|
|
143
|
+
* @param {string} inputPath - Path to input PDF file
|
|
144
|
+
* @param {Object} options - Compression options
|
|
145
|
+
* @param {string} options.preset - Compression preset (from CompressionPreset)
|
|
146
|
+
* @param {number} options.level - Compression level (0-9), overrides preset
|
|
147
|
+
* @param {string} options.outputPath - Output file path (optional, auto-generated if not provided)
|
|
148
|
+
* @param {Function} options.onProgress - Progress callback function
|
|
149
|
+
* @returns {Promise<Object>} Compression result
|
|
150
|
+
*/
|
|
151
|
+
async compress(inputPath, options = {}) {
|
|
152
|
+
const {
|
|
153
|
+
preset = CompressionPreset.WEB,
|
|
154
|
+
level = null,
|
|
155
|
+
outputPath = null,
|
|
156
|
+
onProgress = null
|
|
157
|
+
} = options;
|
|
158
|
+
|
|
159
|
+
console.log(`📦 PDFCompressor: Starting compression with preset '${preset}'`);
|
|
160
|
+
|
|
161
|
+
// Validate input file exists
|
|
162
|
+
const fileExists = await this._fileExists(inputPath);
|
|
163
|
+
if (!fileExists) {
|
|
164
|
+
throw new Error(`Input file not found: ${inputPath}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get file size
|
|
168
|
+
const inputStats = await this._getFileStats(inputPath);
|
|
169
|
+
const inputSizeMB = inputStats.size / (1024 * 1024);
|
|
170
|
+
|
|
171
|
+
console.log(`📦 PDFCompressor: Input file size: ${inputSizeMB.toFixed(2)} MB`);
|
|
172
|
+
|
|
173
|
+
// Determine compression level
|
|
174
|
+
const presetConfig = this.getPresetConfig(preset);
|
|
175
|
+
const compressionLevel = level !== null ? level : presetConfig.level;
|
|
176
|
+
|
|
177
|
+
// Generate output path if not provided
|
|
178
|
+
const finalOutputPath = outputPath || this._generateOutputPath(inputPath);
|
|
179
|
+
|
|
180
|
+
// Perform compression
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
let result;
|
|
183
|
+
|
|
184
|
+
if (this.isNativeAvailable) {
|
|
185
|
+
result = await this._compressNative(inputPath, finalOutputPath, compressionLevel, onProgress);
|
|
186
|
+
} else {
|
|
187
|
+
result = await this._compressFallback(inputPath, finalOutputPath, compressionLevel, onProgress);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const duration = Date.now() - startTime;
|
|
191
|
+
|
|
192
|
+
// Get output file stats
|
|
193
|
+
const outputStats = await this._getFileStats(finalOutputPath);
|
|
194
|
+
const outputSizeMB = outputStats.size / (1024 * 1024);
|
|
195
|
+
const compressionRatio = inputStats.size > 0 ? outputStats.size / inputStats.size : 1;
|
|
196
|
+
const spaceSavedPercent = (1 - compressionRatio) * 100;
|
|
197
|
+
const throughputMBps = duration > 0 ? (inputSizeMB / (duration / 1000)) : 0;
|
|
198
|
+
|
|
199
|
+
const compressionResult = {
|
|
200
|
+
success: true,
|
|
201
|
+
inputPath,
|
|
202
|
+
outputPath: finalOutputPath,
|
|
203
|
+
originalSize: inputStats.size,
|
|
204
|
+
compressedSize: outputStats.size,
|
|
205
|
+
originalSizeMB: inputSizeMB,
|
|
206
|
+
compressedSizeMB: outputSizeMB,
|
|
207
|
+
compressionRatio,
|
|
208
|
+
spaceSavedPercent,
|
|
209
|
+
durationMs: duration,
|
|
210
|
+
throughputMBps,
|
|
211
|
+
preset,
|
|
212
|
+
compressionLevel,
|
|
213
|
+
method: this.isNativeAvailable ? 'native_streaming' : 'fallback'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
console.log(`📦 PDFCompressor: Compression complete!`);
|
|
217
|
+
console.log(` 📊 ${inputSizeMB.toFixed(2)} MB → ${outputSizeMB.toFixed(2)} MB (${spaceSavedPercent.toFixed(1)}% saved)`);
|
|
218
|
+
console.log(` ⏱️ ${duration}ms (${throughputMBps.toFixed(1)} MB/s)`);
|
|
219
|
+
|
|
220
|
+
return compressionResult;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Compress PDF with a specific preset
|
|
225
|
+
* @param {string} inputPath - Path to input PDF file
|
|
226
|
+
* @param {string} preset - Compression preset
|
|
227
|
+
* @param {string} outputPath - Output file path (optional)
|
|
228
|
+
* @returns {Promise<Object>} Compression result
|
|
229
|
+
*/
|
|
230
|
+
async compressWithPreset(inputPath, preset, outputPath = null) {
|
|
231
|
+
return this.compress(inputPath, { preset, outputPath });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Compress PDF for email (maximum compression)
|
|
236
|
+
* @param {string} inputPath - Path to input PDF file
|
|
237
|
+
* @param {string} outputPath - Output file path (optional)
|
|
238
|
+
* @returns {Promise<Object>} Compression result
|
|
239
|
+
*/
|
|
240
|
+
async compressForEmail(inputPath, outputPath = null) {
|
|
241
|
+
return this.compress(inputPath, {
|
|
242
|
+
preset: CompressionPreset.EMAIL,
|
|
243
|
+
outputPath
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Compress PDF for web viewing
|
|
249
|
+
* @param {string} inputPath - Path to input PDF file
|
|
250
|
+
* @param {string} outputPath - Output file path (optional)
|
|
251
|
+
* @returns {Promise<Object>} Compression result
|
|
252
|
+
*/
|
|
253
|
+
async compressForWeb(inputPath, outputPath = null) {
|
|
254
|
+
return this.compress(inputPath, {
|
|
255
|
+
preset: CompressionPreset.WEB,
|
|
256
|
+
outputPath
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Compress PDF for mobile viewing
|
|
262
|
+
* @param {string} inputPath - Path to input PDF file
|
|
263
|
+
* @param {string} outputPath - Output file path (optional)
|
|
264
|
+
* @returns {Promise<Object>} Compression result
|
|
265
|
+
*/
|
|
266
|
+
async compressForMobile(inputPath, outputPath = null) {
|
|
267
|
+
return this.compress(inputPath, {
|
|
268
|
+
preset: CompressionPreset.MOBILE,
|
|
269
|
+
outputPath
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Compress PDF for archival (maximum compression)
|
|
275
|
+
* @param {string} inputPath - Path to input PDF file
|
|
276
|
+
* @param {string} outputPath - Output file path (optional)
|
|
277
|
+
* @returns {Promise<Object>} Compression result
|
|
278
|
+
*/
|
|
279
|
+
async compressForArchive(inputPath, outputPath = null) {
|
|
280
|
+
return this.compress(inputPath, {
|
|
281
|
+
preset: CompressionPreset.ARCHIVE,
|
|
282
|
+
outputPath
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Estimate compression result without actually compressing
|
|
288
|
+
* @param {string} inputPath - Path to input PDF file
|
|
289
|
+
* @param {string} preset - Compression preset
|
|
290
|
+
* @returns {Promise<Object>} Estimated compression result
|
|
291
|
+
*/
|
|
292
|
+
async estimateCompression(inputPath, preset = CompressionPreset.WEB) {
|
|
293
|
+
const fileExists = await this._fileExists(inputPath);
|
|
294
|
+
if (!fileExists) {
|
|
295
|
+
throw new Error(`Input file not found: ${inputPath}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const inputStats = await this._getFileStats(inputPath);
|
|
299
|
+
const inputSizeMB = inputStats.size / (1024 * 1024);
|
|
300
|
+
const presetConfig = this.getPresetConfig(preset);
|
|
301
|
+
|
|
302
|
+
// IMPORTANT: Native module uses zlib deflate which produces ~15-18% compression
|
|
303
|
+
// on PDFs regardless of compression level, because PDFs already contain
|
|
304
|
+
// compressed content (JPEG images, embedded fonts, compressed streams).
|
|
305
|
+
// All presets produce approximately the same result.
|
|
306
|
+
const estimatedRatio = 0.84; // ~16% reduction - same for all presets
|
|
307
|
+
const estimatedSize = inputStats.size * estimatedRatio;
|
|
308
|
+
const estimatedSizeMB = estimatedSize / (1024 * 1024);
|
|
309
|
+
const estimatedSavingsPercent = (1 - estimatedRatio) * 100;
|
|
310
|
+
|
|
311
|
+
// Estimate time based on file size (actual ~25ms/MB with native streaming)
|
|
312
|
+
const msPerMB = this.isNativeAvailable ? 25 : 100;
|
|
313
|
+
const estimatedTimeMs = inputSizeMB * msPerMB;
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
inputPath,
|
|
317
|
+
originalSize: inputStats.size,
|
|
318
|
+
originalSizeMB: inputSizeMB,
|
|
319
|
+
estimatedCompressedSize: estimatedSize,
|
|
320
|
+
estimatedCompressedSizeMB: estimatedSizeMB,
|
|
321
|
+
estimatedCompressionRatio: estimatedRatio,
|
|
322
|
+
estimatedSavingsPercent,
|
|
323
|
+
estimatedDurationMs: estimatedTimeMs,
|
|
324
|
+
preset,
|
|
325
|
+
presetDescription: presetConfig.description,
|
|
326
|
+
confidence: 'low',
|
|
327
|
+
note: 'All presets produce ~15-18% compression. Native uses zlib deflate which has minimal effect on already-compressed PDF content.'
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Native compression using PDFExporter.compressPDF
|
|
333
|
+
* @private
|
|
334
|
+
*/
|
|
335
|
+
async _compressNative(inputPath, outputPath, compressionLevel, onProgress) {
|
|
336
|
+
console.log(`📦 PDFCompressor: Using native streaming compression (level: ${compressionLevel})`);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
// Use PDFExporter.compressPDF - the main native compression method
|
|
340
|
+
if (PDFExporter && typeof PDFExporter.compressPDF === 'function') {
|
|
341
|
+
console.log('📦 PDFCompressor: Calling PDFExporter.compressPDF...');
|
|
342
|
+
const result = await PDFExporter.compressPDF(
|
|
343
|
+
inputPath,
|
|
344
|
+
outputPath,
|
|
345
|
+
compressionLevel
|
|
346
|
+
);
|
|
347
|
+
console.log('📦 PDFCompressor: Native compression result:', result);
|
|
348
|
+
return result;
|
|
349
|
+
} else {
|
|
350
|
+
// Native module not available - use fallback
|
|
351
|
+
console.warn('📦 PDFCompressor: PDFExporter.compressPDF not available, using fallback');
|
|
352
|
+
console.warn('📦 PDFCompressor: PDFExporter available:', !!PDFExporter);
|
|
353
|
+
console.warn('📦 PDFCompressor: PDFExporter.compressPDF:', PDFExporter ? typeof PDFExporter.compressPDF : 'N/A');
|
|
354
|
+
return this._compressFallback(inputPath, outputPath, compressionLevel, onProgress);
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error('📦 PDFCompressor: Native compression failed:', error);
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Fallback compression (file copy with potential optimization)
|
|
364
|
+
* @private
|
|
365
|
+
*/
|
|
366
|
+
async _compressFallback(inputPath, outputPath, compressionLevel, onProgress) {
|
|
367
|
+
console.log('📦 PDFCompressor: Using fallback compression (limited functionality)');
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// For fallback, we simply copy the file
|
|
371
|
+
// Real compression requires native code
|
|
372
|
+
await ReactNativeBlobUtil.fs.cp(inputPath, outputPath);
|
|
373
|
+
|
|
374
|
+
if (onProgress) {
|
|
375
|
+
onProgress({
|
|
376
|
+
progress: 1.0,
|
|
377
|
+
bytesProcessed: 0,
|
|
378
|
+
totalBytes: 0
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.warn('📦 PDFCompressor: Fallback mode - file copied without compression');
|
|
383
|
+
console.warn('📦 PDFCompressor: For actual compression, ensure native module is properly linked');
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
success: true,
|
|
387
|
+
method: 'fallback_copy',
|
|
388
|
+
note: 'Native compression not available - file was copied without compression'
|
|
389
|
+
};
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error('📦 PDFCompressor: Fallback compression failed:', error);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if file exists
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
async _fileExists(filePath) {
|
|
401
|
+
try {
|
|
402
|
+
return await ReactNativeBlobUtil.fs.exists(filePath);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Get file statistics
|
|
410
|
+
* @private
|
|
411
|
+
*/
|
|
412
|
+
async _getFileStats(filePath) {
|
|
413
|
+
try {
|
|
414
|
+
const stats = await ReactNativeBlobUtil.fs.stat(filePath);
|
|
415
|
+
return {
|
|
416
|
+
size: parseInt(stats.size, 10),
|
|
417
|
+
lastModified: stats.lastModified,
|
|
418
|
+
path: stats.path
|
|
419
|
+
};
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.error('📦 PDFCompressor: Failed to get file stats:', error);
|
|
422
|
+
return { size: 0, lastModified: 0, path: filePath };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Generate output path based on input path
|
|
428
|
+
* @private
|
|
429
|
+
*/
|
|
430
|
+
_generateOutputPath(inputPath) {
|
|
431
|
+
const timestamp = Date.now();
|
|
432
|
+
const baseName = inputPath.replace(/\.pdf$/i, '');
|
|
433
|
+
return `${baseName}_compressed_${timestamp}.pdf`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Delete a compressed file
|
|
438
|
+
* @param {string} filePath - Path to file to delete
|
|
439
|
+
* @returns {Promise<boolean>} Success status
|
|
440
|
+
*/
|
|
441
|
+
async deleteCompressedFile(filePath) {
|
|
442
|
+
try {
|
|
443
|
+
const exists = await this._fileExists(filePath);
|
|
444
|
+
if (exists) {
|
|
445
|
+
await ReactNativeBlobUtil.fs.unlink(filePath);
|
|
446
|
+
console.log(`📦 PDFCompressor: Deleted file: ${filePath}`);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error('📦 PDFCompressor: Failed to delete file:', error);
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get module information
|
|
458
|
+
* @returns {Object} Module info
|
|
459
|
+
*/
|
|
460
|
+
getModuleInfo() {
|
|
461
|
+
return {
|
|
462
|
+
name: 'PDFCompressor',
|
|
463
|
+
version: '1.0.0',
|
|
464
|
+
platform: Platform.OS,
|
|
465
|
+
nativeAvailable: this.isNativeAvailable,
|
|
466
|
+
presets: Object.keys(CompressionPreset),
|
|
467
|
+
compressionLevels: Object.keys(CompressionLevel),
|
|
468
|
+
capabilities: this.getCapabilities()
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Create singleton instance
|
|
474
|
+
const pdfCompressor = new PDFCompressor();
|
|
475
|
+
|
|
476
|
+
export default pdfCompressor;
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
// Core JSI functionality
|
|
10
10
|
export { default as PDFJSI } from './PDFJSI';
|
|
11
11
|
|
|
12
|
+
// PDF Compression
|
|
13
|
+
export {
|
|
14
|
+
default as PDFCompressor,
|
|
15
|
+
PDFCompressor as PDFCompressorClass,
|
|
16
|
+
CompressionPreset,
|
|
17
|
+
CompressionLevel
|
|
18
|
+
} from './PDFCompressor';
|
|
19
|
+
|
|
12
20
|
// Enhanced PDF View component
|
|
13
21
|
export { default as EnhancedPdfView, EnhancedPdfUtils } from './EnhancedPdfView';
|
|
14
22
|
|
|
@@ -29,4 +37,10 @@ export {
|
|
|
29
37
|
getJSIStats,
|
|
30
38
|
getPerformanceHistory,
|
|
31
39
|
clearPerformanceHistory
|
|
32
|
-
} from './PDFJSI';
|
|
40
|
+
} from './PDFJSI';
|
|
41
|
+
|
|
42
|
+
// Re-export compression utilities
|
|
43
|
+
export {
|
|
44
|
+
CompressionPreset,
|
|
45
|
+
CompressionLevel
|
|
46
|
+
} from './PDFCompressor';
|