react-native-pdf-jsi 4.2.1 → 4.3.0
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 +78 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +74 -0
- package/app.plugin.js +2 -0
- package/index.d.ts +244 -0
- package/index.js +5 -1
- package/ios/RNPDFPdf/PDFExporter.m +83 -0
- package/package.json +16 -3
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +26 -0
- package/plugin/build/withPdfJsiAndroid.d.ts +8 -0
- package/plugin/build/withPdfJsiAndroid.js +55 -0
- package/plugin/build/withPdfJsiIos.d.ts +11 -0
- package/plugin/build/withPdfJsiIos.js +34 -0
- package/plugin/src/index.ts +31 -0
- package/plugin/src/withPdfJsiAndroid.ts +67 -0
- package/plugin/src/withPdfJsiIos.ts +40 -0
- package/plugin/tsconfig.json +14 -0
- package/src/PDFCompressor.js +476 -0
- package/src/index.js +15 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/react-native-pdf-jsi)
|
|
12
|
+
[](https://expo.dev)
|
|
12
13
|
[](https://www.npmjs.com/package/react-native-pdf-jsi)
|
|
13
14
|
[](https://www.npmjs.com/package/react-native-pdf-jsi)
|
|
14
15
|
[](https://github.com/126punith/react-native-pdf-jsi)
|
|
@@ -111,6 +112,51 @@ react-native link react-native-blob-util
|
|
|
111
112
|
react-native link react-native-pdf-jsi
|
|
112
113
|
```
|
|
113
114
|
|
|
115
|
+
### Expo Installation
|
|
116
|
+
|
|
117
|
+
This package works with **Expo development builds** (not Expo Go, as it requires native code).
|
|
118
|
+
|
|
119
|
+
**1. Install the package and peer dependencies:**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx expo install react-native-pdf-jsi react-native-blob-util @react-native-async-storage/async-storage
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**2. Add the config plugin to your `app.json` or `app.config.js`:**
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"expo": {
|
|
130
|
+
"plugins": ["react-native-pdf-jsi"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**3. Rebuild your development build:**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Generate native projects
|
|
139
|
+
npx expo prebuild
|
|
140
|
+
|
|
141
|
+
# Run on iOS
|
|
142
|
+
npx expo run:ios
|
|
143
|
+
|
|
144
|
+
# Run on Android
|
|
145
|
+
npx expo run:android
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Note:** The config plugin automatically configures:
|
|
149
|
+
> - Android: Adds Jitpack repository for PDF rendering dependencies
|
|
150
|
+
> - iOS: Ensures PDFKit framework is properly linked
|
|
151
|
+
|
|
152
|
+
**For EAS Build users:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Build development client
|
|
156
|
+
eas build --profile development --platform ios
|
|
157
|
+
eas build --profile development --platform android
|
|
158
|
+
```
|
|
159
|
+
|
|
114
160
|
### Windows Installation
|
|
115
161
|
|
|
116
162
|
1. Open your solution in Visual Studio 2019 (e.g., `windows\yourapp.sln`)
|
|
@@ -409,6 +455,38 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
409
455
|
- **Author**: Punith M ([@126punith](https://github.com/126punith))
|
|
410
456
|
|
|
411
457
|
## Recent Fixes
|
|
458
|
+
|
|
459
|
+
### Expo Support (v4.3.0)
|
|
460
|
+
Added Expo config plugin for seamless integration with Expo development builds. The package now works with `npx expo prebuild` and `npx expo run:ios/android`.
|
|
461
|
+
|
|
462
|
+
**Installation:**
|
|
463
|
+
```bash
|
|
464
|
+
npx expo install react-native-pdf-jsi react-native-blob-util @react-native-async-storage/async-storage
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Configuration (app.json):**
|
|
468
|
+
```json
|
|
469
|
+
{
|
|
470
|
+
"expo": {
|
|
471
|
+
"plugins": ["react-native-pdf-jsi"]
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Note:** Expo Go is NOT supported (requires native code). Use Expo development builds.
|
|
477
|
+
|
|
478
|
+
### PDFCompressor Module Fix (v4.2.2)
|
|
479
|
+
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).
|
|
480
|
+
|
|
481
|
+
**Usage:**
|
|
482
|
+
```jsx
|
|
483
|
+
import { PDFCompressor, CompressionPreset } from 'react-native-pdf-jsi';
|
|
484
|
+
|
|
485
|
+
// Compress a PDF
|
|
486
|
+
const result = await PDFCompressor.compressWithPreset(pdfPath, CompressionPreset.WEB);
|
|
487
|
+
console.log(`Compressed: ${result.originalSizeMB}MB → ${result.compressedSizeMB}MB`);
|
|
488
|
+
```
|
|
489
|
+
|
|
412
490
|
### iOS Performance - Unnecessary Path Handlers (v4.2.1)
|
|
413
491
|
Use v4.2.1 it contains stable fixes for IOS with unwanted debug logs removed
|
|
414
492
|
|
|
@@ -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/app.plugin.js
ADDED
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,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-pdf-jsi",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
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",
|
|
7
7
|
"typings": "./index.d.ts",
|
|
8
|
+
"app": {
|
|
9
|
+
"plugin": "./plugin/build"
|
|
10
|
+
},
|
|
8
11
|
"repository": {
|
|
9
12
|
"type": "git",
|
|
10
13
|
"url": "git+https://github.com/126punith/react-native-pdf-jsi.git"
|
|
@@ -50,7 +53,9 @@
|
|
|
50
53
|
"optimization",
|
|
51
54
|
"caching",
|
|
52
55
|
"persistent-cache",
|
|
53
|
-
"react-native-pdf-jsi-enhanced"
|
|
56
|
+
"react-native-pdf-jsi-enhanced",
|
|
57
|
+
"expo",
|
|
58
|
+
"expo-plugin"
|
|
54
59
|
],
|
|
55
60
|
"author": {
|
|
56
61
|
"name": "Punith M",
|
|
@@ -69,7 +74,13 @@
|
|
|
69
74
|
"devDependencies": {
|
|
70
75
|
"@babel/core": "^7.20.2",
|
|
71
76
|
"@babel/runtime": "^7.20.1",
|
|
72
|
-
"
|
|
77
|
+
"@expo/config-plugins": "^9.0.0",
|
|
78
|
+
"prop-types": "^15.7.2",
|
|
79
|
+
"typescript": "^5.0.0"
|
|
80
|
+
},
|
|
81
|
+
"scripts": {
|
|
82
|
+
"build:plugin": "tsc -p plugin/tsconfig.json",
|
|
83
|
+
"prepublishOnly": "npm run build:plugin"
|
|
73
84
|
},
|
|
74
85
|
"peerDependencies": {
|
|
75
86
|
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
@@ -82,6 +93,8 @@
|
|
|
82
93
|
"ios/",
|
|
83
94
|
"windows/",
|
|
84
95
|
"src/",
|
|
96
|
+
"plugin/",
|
|
97
|
+
"app.plugin.js",
|
|
85
98
|
"DoubleTapView.js",
|
|
86
99
|
"index.d.ts",
|
|
87
100
|
"index.js",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
4
|
+
const withPdfJsiAndroid_1 = require("./withPdfJsiAndroid");
|
|
5
|
+
const withPdfJsiIos_1 = require("./withPdfJsiIos");
|
|
6
|
+
const pkg = require('../../package.json');
|
|
7
|
+
/**
|
|
8
|
+
* Expo config plugin for react-native-pdf-jsi
|
|
9
|
+
*
|
|
10
|
+
* This plugin configures the native projects for Expo development builds.
|
|
11
|
+
* It handles:
|
|
12
|
+
* - Android: Jitpack repository for AndroidPdfViewer, NDK configuration
|
|
13
|
+
* - iOS: PDFKit framework linking
|
|
14
|
+
*
|
|
15
|
+
* Note: This package requires development builds and won't work with Expo Go.
|
|
16
|
+
*/
|
|
17
|
+
const withPdfJsi = (config) => {
|
|
18
|
+
// Warn about peer dependencies
|
|
19
|
+
console.log('[react-native-pdf-jsi] Remember to install peer dependencies:\n' +
|
|
20
|
+
' - react-native-blob-util\n' +
|
|
21
|
+
' - @react-native-async-storage/async-storage');
|
|
22
|
+
config = (0, withPdfJsiAndroid_1.withPdfJsiAndroid)(config);
|
|
23
|
+
config = (0, withPdfJsiIos_1.withPdfJsiIos)(config);
|
|
24
|
+
return config;
|
|
25
|
+
};
|
|
26
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withPdfJsi, pkg.name, pkg.version);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ConfigPlugin } from '@expo/config-plugins';
|
|
2
|
+
/**
|
|
3
|
+
* Android config plugin for react-native-pdf-jsi
|
|
4
|
+
*
|
|
5
|
+
* Configures the Android project to support the PDF library:
|
|
6
|
+
* - Adds Jitpack repository for AndroidPdfViewer dependency
|
|
7
|
+
*/
|
|
8
|
+
export declare const withPdfJsiAndroid: ConfigPlugin;
|