react-native-pdf-jsi 2.2.8 → 3.0.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 +308 -173
- package/android/src/main/java/org/wonday/pdf/FileDownloader.java +292 -0
- package/android/src/main/java/org/wonday/pdf/FileManager.java +123 -0
- package/android/src/main/java/org/wonday/pdf/LicenseVerifier.java +311 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +769 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +7 -0
- package/index.js +58 -0
- package/ios/RNPDFPdf/PDFExporter.h +16 -0
- package/ios/RNPDFPdf/PDFExporter.m +537 -0
- package/package.json +3 -2
- package/src/components/AnalyticsPanel.jsx +243 -0
- package/src/components/BookmarkIndicator.jsx +66 -0
- package/src/components/BookmarkListModal.jsx +378 -0
- package/src/components/BookmarkModal.jsx +253 -0
- package/src/components/BottomSheet.jsx +121 -0
- package/src/components/ExportMenu.jsx +223 -0
- package/src/components/LoadingOverlay.jsx +52 -0
- package/src/components/OperationsMenu.jsx +231 -0
- package/src/components/SidePanel.jsx +95 -0
- package/src/components/Toast.jsx +140 -0
- package/src/components/Toolbar.jsx +135 -0
- package/src/managers/AnalyticsManager.js +695 -0
- package/src/managers/BookmarkManager.js +538 -0
- package/src/managers/ExportManager.js +687 -0
- package/src/managers/FileManager.js +89 -0
- package/src/utils/ErrorHandler.js +179 -0
- package/src/utils/TestData.js +112 -0
|
@@ -26,6 +26,13 @@ public class RNPDFPackage implements ReactPackage {
|
|
|
26
26
|
// Add JSI modules for enhanced PDF performance
|
|
27
27
|
modules.add(new PDFJSIManager(reactContext));
|
|
28
28
|
modules.add(new EnhancedPdfJSIBridge(reactContext));
|
|
29
|
+
|
|
30
|
+
// Add advanced feature modules
|
|
31
|
+
modules.add(new PDFExporter(reactContext));
|
|
32
|
+
modules.add(new FileDownloader(reactContext));
|
|
33
|
+
modules.add(new FileManager(reactContext));
|
|
34
|
+
modules.add(new LicenseVerifier(reactContext));
|
|
35
|
+
|
|
29
36
|
return modules;
|
|
30
37
|
}
|
|
31
38
|
|
package/index.js
CHANGED
|
@@ -601,3 +601,61 @@ const styles = StyleSheet.create({
|
|
|
601
601
|
height: 2
|
|
602
602
|
}
|
|
603
603
|
});
|
|
604
|
+
|
|
605
|
+
// ========================================
|
|
606
|
+
// TIER 2: Low-Level API (Managers)
|
|
607
|
+
// ========================================
|
|
608
|
+
|
|
609
|
+
import ExportManager from './src/managers/ExportManager';
|
|
610
|
+
import BookmarkManager from './src/managers/BookmarkManager';
|
|
611
|
+
import AnalyticsManager from './src/managers/AnalyticsManager';
|
|
612
|
+
import FileManager from './src/managers/FileManager';
|
|
613
|
+
|
|
614
|
+
export {
|
|
615
|
+
ExportManager,
|
|
616
|
+
BookmarkManager,
|
|
617
|
+
AnalyticsManager,
|
|
618
|
+
FileManager
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// ========================================
|
|
622
|
+
// TIER 3: Pre-built UI Components
|
|
623
|
+
// ========================================
|
|
624
|
+
|
|
625
|
+
import Toolbar from './src/components/Toolbar';
|
|
626
|
+
import BookmarkModal from './src/components/BookmarkModal';
|
|
627
|
+
import BookmarkListModal from './src/components/BookmarkListModal';
|
|
628
|
+
import BookmarkIndicator from './src/components/BookmarkIndicator';
|
|
629
|
+
import ExportMenu from './src/components/ExportMenu';
|
|
630
|
+
import OperationsMenu from './src/components/OperationsMenu';
|
|
631
|
+
import AnalyticsPanel from './src/components/AnalyticsPanel';
|
|
632
|
+
import Toast from './src/components/Toast';
|
|
633
|
+
import LoadingOverlay from './src/components/LoadingOverlay';
|
|
634
|
+
import BottomSheet from './src/components/BottomSheet';
|
|
635
|
+
import SidePanel from './src/components/SidePanel';
|
|
636
|
+
|
|
637
|
+
export {
|
|
638
|
+
Toolbar,
|
|
639
|
+
BookmarkModal,
|
|
640
|
+
BookmarkListModal,
|
|
641
|
+
BookmarkIndicator,
|
|
642
|
+
ExportMenu,
|
|
643
|
+
OperationsMenu,
|
|
644
|
+
AnalyticsPanel,
|
|
645
|
+
Toast,
|
|
646
|
+
LoadingOverlay,
|
|
647
|
+
BottomSheet,
|
|
648
|
+
SidePanel
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// ========================================
|
|
652
|
+
// TIER 4: Utility Modules
|
|
653
|
+
// ========================================
|
|
654
|
+
|
|
655
|
+
import ErrorHandler from './src/utils/ErrorHandler';
|
|
656
|
+
import TestData from './src/utils/TestData';
|
|
657
|
+
|
|
658
|
+
export {
|
|
659
|
+
ErrorHandler,
|
|
660
|
+
TestData
|
|
661
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Export Module (iOS)
|
|
3
|
+
* Exports PDF pages to images and performs PDF operations
|
|
4
|
+
*
|
|
5
|
+
* LICENSE: Commercial License (Pro feature)
|
|
6
|
+
*
|
|
7
|
+
* @author Punith M
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#import <React/RCTBridgeModule.h>
|
|
12
|
+
#import <React/RCTEventEmitter.h>
|
|
13
|
+
|
|
14
|
+
@interface PDFExporter : NSObject <RCTBridgeModule>
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
#import "PDFExporter.h"
|
|
2
|
+
#import <PDFKit/PDFKit.h>
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
@implementation PDFExporter {
|
|
6
|
+
LicenseVerifier *_licenseVerifier;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
RCT_EXPORT_MODULE();
|
|
10
|
+
|
|
11
|
+
- (instancetype)init {
|
|
12
|
+
self = [super init];
|
|
13
|
+
if (self) {
|
|
14
|
+
_licenseVerifier = [[LicenseVerifier alloc] init];
|
|
15
|
+
}
|
|
16
|
+
return self;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//
|
|
20
|
+
RCT_EXPORT_METHOD(exportToImages:(NSString *)filePath
|
|
21
|
+
options:(NSDictionary *)options
|
|
22
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if (![_licenseVerifier isProActive]) {
|
|
27
|
+
reject(@"LICENSE_REQUIRED", @"Export to Images requires a Pro license", nil);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!filePath || filePath.length == 0) {
|
|
32
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
37
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
|
38
|
+
reject(@"FILE_NOT_FOUND", [NSString stringWithFormat:@"PDF file not found: %@", filePath], nil);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
NSArray *pages = options[@"pages"];
|
|
44
|
+
NSNumber *dpi = options[@"dpi"] ?: @150;
|
|
45
|
+
NSString *format = options[@"format"] ?: @"png";
|
|
46
|
+
NSString *outputDir = options[@"outputDir"];
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
NSArray *exportedFiles = [self exportPagesToImages:pdfURL pages:pages dpi:dpi.intValue format:format outputDir:outputDir];
|
|
50
|
+
|
|
51
|
+
resolve(exportedFiles);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Export specific pages to images
|
|
56
|
+
*/
|
|
57
|
+
- (NSArray *)exportPagesToImages:(NSURL *)pdfURL pages:(NSArray *)pages dpi:(int)dpi format:(NSString *)format outputDir:(NSString *)outputDir {
|
|
58
|
+
NSLog(@"🖼️ [EXPORT] exportPagesToImages - START - dpi: %d, format: %@", dpi, format);
|
|
59
|
+
|
|
60
|
+
NSMutableArray *exportedFiles = [NSMutableArray array];
|
|
61
|
+
|
|
62
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
63
|
+
if (!pdfDocument) {
|
|
64
|
+
NSLog(@"❌ [EXPORT] Failed to load PDF document");
|
|
65
|
+
return exportedFiles;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
NSUInteger pageCount = pdfDocument.pageCount;
|
|
69
|
+
NSLog(@"📁 [FILE] PDF has %lu total pages", (unsigned long)pageCount);
|
|
70
|
+
|
|
71
|
+
// Determine which pages to export
|
|
72
|
+
NSMutableArray *pagesToExport = [NSMutableArray array];
|
|
73
|
+
if (pages && pages.count > 0) {
|
|
74
|
+
for (NSNumber *pageNum in pages) {
|
|
75
|
+
int pageIndex = pageNum.intValue;
|
|
76
|
+
if (pageIndex >= 0 && pageIndex < pageCount) {
|
|
77
|
+
[pagesToExport addObject:@(pageIndex)];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
NSLog(@"📊 [PROGRESS] Exporting %lu specific pages", (unsigned long)pagesToExport.count);
|
|
81
|
+
} else {
|
|
82
|
+
// Export all pages
|
|
83
|
+
for (int i = 0; i < pageCount; i++) {
|
|
84
|
+
[pagesToExport addObject:@(i)];
|
|
85
|
+
}
|
|
86
|
+
NSLog(@"📊 [PROGRESS] Exporting all %lu pages", (unsigned long)pageCount);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Export each page
|
|
90
|
+
int current = 0;
|
|
91
|
+
for (NSNumber *pageNum in pagesToExport) {
|
|
92
|
+
int pageIndex = pageNum.intValue;
|
|
93
|
+
current++;
|
|
94
|
+
|
|
95
|
+
NSLog(@"📊 [PROGRESS] Exporting page %d/%lu (page number: %d)", current, (unsigned long)pagesToExport.count, pageIndex + 1);
|
|
96
|
+
|
|
97
|
+
PDFPage *page = [pdfDocument pageAtIndex:pageIndex];
|
|
98
|
+
|
|
99
|
+
if (page) {
|
|
100
|
+
// Calculate dimensions
|
|
101
|
+
CGRect pageRect = [page boundsForBox:kPDFDisplayBoxMediaBox];
|
|
102
|
+
float scale = dpi / 72.0f; // 72 DPI is default
|
|
103
|
+
CGSize imageSize = CGSizeMake(pageRect.size.width * scale, pageRect.size.height * scale);
|
|
104
|
+
|
|
105
|
+
NSLog(@"🖼️ [BITMAP] Creating %.0fx%.0f image", imageSize.width, imageSize.height);
|
|
106
|
+
|
|
107
|
+
// Create image context
|
|
108
|
+
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
|
|
109
|
+
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
110
|
+
|
|
111
|
+
// Fill background with white
|
|
112
|
+
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
|
113
|
+
CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
|
|
114
|
+
|
|
115
|
+
// Scale context
|
|
116
|
+
CGContextScaleCTM(context, scale, scale);
|
|
117
|
+
|
|
118
|
+
// Render page
|
|
119
|
+
NSLog(@"🖼️ [RENDER] Rendering page to image...");
|
|
120
|
+
[page drawWithBox:kPDFDisplayBoxMediaBox toContext:context];
|
|
121
|
+
|
|
122
|
+
// Get image
|
|
123
|
+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
124
|
+
UIGraphicsEndImageContext();
|
|
125
|
+
|
|
126
|
+
// Save image
|
|
127
|
+
NSString *fileName = [NSString stringWithFormat:@"page_%d.%@", pageIndex + 1, format];
|
|
128
|
+
NSString *outputPath = [self saveImage:image fileName:fileName outputDir:outputDir];
|
|
129
|
+
if (outputPath) {
|
|
130
|
+
[exportedFiles addObject:outputPath];
|
|
131
|
+
NSLog(@"✅ [PROGRESS] Page %d exported to %@", pageIndex + 1, outputPath);
|
|
132
|
+
} else {
|
|
133
|
+
NSLog(@"❌ [EXPORT] Failed to save page %d", pageIndex + 1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
NSLog(@"✅ [EXPORT] exportPagesToImages - SUCCESS - Exported %lu pages", (unsigned long)exportedFiles.count);
|
|
139
|
+
|
|
140
|
+
return exportedFiles;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Save image to file
|
|
145
|
+
*/
|
|
146
|
+
- (NSString *)saveImage:(UIImage *)image fileName:(NSString *)fileName outputDir:(NSString *)outputDir {
|
|
147
|
+
NSURL *outputURL;
|
|
148
|
+
|
|
149
|
+
if (outputDir && outputDir.length > 0) {
|
|
150
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
151
|
+
if (![fileManager fileExistsAtPath:outputDir]) {
|
|
152
|
+
[fileManager createDirectoryAtPath:outputDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
153
|
+
}
|
|
154
|
+
outputURL = [NSURL fileURLWithPath:[outputDir stringByAppendingPathComponent:fileName]];
|
|
155
|
+
} else {
|
|
156
|
+
// Save to app's documents directory
|
|
157
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
158
|
+
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
159
|
+
outputURL = [NSURL fileURLWithPath:[documentsDirectory stringByAppendingPathComponent:fileName]];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
NSLog(@"📁 [FILE] Writing to: %@", outputURL.path);
|
|
163
|
+
|
|
164
|
+
// Convert image to data
|
|
165
|
+
NSData *imageData;
|
|
166
|
+
NSString *format;
|
|
167
|
+
if ([fileName.lowercaseString hasSuffix:@".png"]) {
|
|
168
|
+
imageData = UIImagePNGRepresentation(image);
|
|
169
|
+
format = @"PNG";
|
|
170
|
+
} else if ([fileName.lowercaseString hasSuffix:@".jpg"] || [fileName.lowercaseString hasSuffix:@".jpeg"]) {
|
|
171
|
+
imageData = UIImageJPEGRepresentation(image, 0.9);
|
|
172
|
+
format = @"JPEG at 90% quality";
|
|
173
|
+
} else {
|
|
174
|
+
imageData = UIImagePNGRepresentation(image);
|
|
175
|
+
format = @"PNG (default)";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
NSLog(@"📁 [FILE] Compressing as %@", format);
|
|
179
|
+
|
|
180
|
+
// Save to file
|
|
181
|
+
NSError *error;
|
|
182
|
+
BOOL success = [imageData writeToURL:outputURL options:NSDataWritingAtomic error:&error];
|
|
183
|
+
|
|
184
|
+
if (success) {
|
|
185
|
+
unsigned long fileSize = (unsigned long)imageData.length;
|
|
186
|
+
NSLog(@"✅ [FILE] Saved - size: %lu bytes", fileSize);
|
|
187
|
+
return outputURL.path;
|
|
188
|
+
} else {
|
|
189
|
+
NSLog(@"❌ [FILE] Error saving image: %@", error.localizedDescription);
|
|
190
|
+
return nil;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Merge multiple PDFs
|
|
196
|
+
* PRO FEATURE: Requires Pro license
|
|
197
|
+
*/
|
|
198
|
+
RCT_EXPORT_METHOD(mergePDFs:(NSArray *)filePaths
|
|
199
|
+
outputPath:(NSString *)outputPath
|
|
200
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
201
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
202
|
+
|
|
203
|
+
// Check Pro license
|
|
204
|
+
if (![_licenseVerifier isProActive]) {
|
|
205
|
+
reject(@"LICENSE_REQUIRED", @"PDF Operations requires a Pro license", nil);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!filePaths || filePaths.count < 2) {
|
|
210
|
+
reject(@"INVALID_INPUT", @"At least 2 PDF files are required for merging", nil);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create merged PDF
|
|
215
|
+
PDFDocument *mergedPDF = [[PDFDocument alloc] init];
|
|
216
|
+
|
|
217
|
+
for (NSString *filePath in filePaths) {
|
|
218
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
219
|
+
PDFDocument *pdfDoc = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
220
|
+
|
|
221
|
+
if (pdfDoc) {
|
|
222
|
+
NSUInteger pageCount = pdfDoc.pageCount;
|
|
223
|
+
for (int i = 0; i < pageCount; i++) {
|
|
224
|
+
PDFPage *page = [pdfDoc pageAtIndex:i];
|
|
225
|
+
if (page) {
|
|
226
|
+
[mergedPDF insertPage:page atIndex:mergedPDF.pageCount];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Save merged PDF
|
|
233
|
+
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
|
|
234
|
+
BOOL success = [mergedPDF writeToURL:outputURL];
|
|
235
|
+
|
|
236
|
+
if (success) {
|
|
237
|
+
resolve(outputPath);
|
|
238
|
+
} else {
|
|
239
|
+
reject(@"MERGE_ERROR", @"Failed to save merged PDF", nil);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Split PDF into multiple files
|
|
245
|
+
* PRO FEATURE: Requires Pro license
|
|
246
|
+
*/
|
|
247
|
+
RCT_EXPORT_METHOD(splitPDF:(NSString *)filePath
|
|
248
|
+
pageRanges:(NSArray *)pageRanges
|
|
249
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
250
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
251
|
+
|
|
252
|
+
NSLog(@"✂️ [SPLIT] splitPDF - START - file: %@, ranges: %lu", filePath, (unsigned long)pageRanges.count);
|
|
253
|
+
|
|
254
|
+
// Check Pro license
|
|
255
|
+
BOOL licenseActive = [_licenseVerifier isProActive];
|
|
256
|
+
NSLog(@"🔑 [LICENSE] isProActive: %d", licenseActive);
|
|
257
|
+
|
|
258
|
+
if (!licenseActive) {
|
|
259
|
+
NSLog(@"❌ [SPLIT] License required");
|
|
260
|
+
reject(@"LICENSE_REQUIRED", @"PDF Operations requires a Pro license", nil);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!filePath || filePath.length == 0) {
|
|
265
|
+
NSLog(@"❌ [SPLIT] Invalid path");
|
|
266
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
271
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
272
|
+
|
|
273
|
+
if (!pdfDocument) {
|
|
274
|
+
NSLog(@"❌ [FILE] PDF not found: %@", filePath);
|
|
275
|
+
reject(@"FILE_NOT_FOUND", @"PDF file not found", nil);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
NSMutableArray *splitFiles = [NSMutableArray array];
|
|
280
|
+
NSUInteger pageCount = pdfDocument.pageCount;
|
|
281
|
+
NSLog(@"📁 [FILE] PDF opened, total pages: %lu", (unsigned long)pageCount);
|
|
282
|
+
|
|
283
|
+
int rangeIndex = 0;
|
|
284
|
+
for (NSDictionary *range in pageRanges) {
|
|
285
|
+
rangeIndex++;
|
|
286
|
+
NSNumber *startPage = range[@"start"];
|
|
287
|
+
NSNumber *endPage = range[@"end"];
|
|
288
|
+
|
|
289
|
+
if (startPage && endPage) {
|
|
290
|
+
int start = startPage.intValue;
|
|
291
|
+
int end = endPage.intValue;
|
|
292
|
+
|
|
293
|
+
NSLog(@"📊 [PROGRESS] Processing range %d/%lu: pages %d-%d", rangeIndex, (unsigned long)pageRanges.count, start + 1, end + 1);
|
|
294
|
+
|
|
295
|
+
if (start >= 0 && end < pageCount && start <= end) {
|
|
296
|
+
PDFDocument *splitPDF = [[PDFDocument alloc] init];
|
|
297
|
+
|
|
298
|
+
for (int i = start; i <= end; i++) {
|
|
299
|
+
NSLog(@"📊 [PROGRESS] Processing page %d", i + 1);
|
|
300
|
+
PDFPage *page = [pdfDocument pageAtIndex:i];
|
|
301
|
+
if (page) {
|
|
302
|
+
[splitPDF insertPage:page atIndex:splitPDF.pageCount];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Save split PDF
|
|
307
|
+
NSString *fileName = [NSString stringWithFormat:@"split_%d_%d.pdf", start + 1, end + 1];
|
|
308
|
+
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
309
|
+
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
|
|
310
|
+
|
|
311
|
+
NSLog(@"📁 [FILE] Creating split file: %@", outputPath);
|
|
312
|
+
|
|
313
|
+
BOOL success = [splitPDF writeToURL:outputURL];
|
|
314
|
+
if (success) {
|
|
315
|
+
[splitFiles addObject:outputPath];
|
|
316
|
+
NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:outputPath error:nil];
|
|
317
|
+
unsigned long long fileSize = [fileAttrs fileSize];
|
|
318
|
+
NSLog(@"✅ [SPLIT] Created file: %@ (size: %llu bytes)", outputPath, fileSize);
|
|
319
|
+
} else {
|
|
320
|
+
NSLog(@"❌ [SPLIT] Failed to save split file");
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
NSLog(@"⚠️ [SPLIT] Invalid range: [%d, %d]", start + 1, end + 1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
NSLog(@"✅ [SPLIT] splitPDF - SUCCESS - Split into %lu files", (unsigned long)splitFiles.count);
|
|
329
|
+
resolve(splitFiles);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Extract specific pages from PDF
|
|
334
|
+
* PRO FEATURE: Requires Pro license
|
|
335
|
+
*/
|
|
336
|
+
RCT_EXPORT_METHOD(extractPages:(NSString *)filePath
|
|
337
|
+
pageNumbers:(NSArray *)pageNumbers
|
|
338
|
+
outputPath:(NSString *)outputPath
|
|
339
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
340
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
341
|
+
|
|
342
|
+
NSLog(@"✂️ [EXTRACT] extractPages - START - file: %@, pages: %lu", filePath, (unsigned long)pageNumbers.count);
|
|
343
|
+
|
|
344
|
+
// Check Pro license
|
|
345
|
+
BOOL licenseActive = [_licenseVerifier isProActive];
|
|
346
|
+
NSLog(@"🔑 [LICENSE] isProActive: %d", licenseActive);
|
|
347
|
+
|
|
348
|
+
if (!licenseActive) {
|
|
349
|
+
NSLog(@"❌ [EXTRACT] License required");
|
|
350
|
+
reject(@"LICENSE_REQUIRED", @"PDF Operations requires a Pro license", nil);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!filePath || filePath.length == 0) {
|
|
355
|
+
NSLog(@"❌ [EXTRACT] Invalid path");
|
|
356
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
361
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
362
|
+
|
|
363
|
+
if (!pdfDocument) {
|
|
364
|
+
NSLog(@"❌ [FILE] PDF not found: %@", filePath);
|
|
365
|
+
reject(@"FILE_NOT_FOUND", @"PDF file not found", nil);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
NSUInteger totalPages = pdfDocument.pageCount;
|
|
370
|
+
NSLog(@"📁 [FILE] PDF opened, total pages: %lu", (unsigned long)totalPages);
|
|
371
|
+
NSLog(@"📊 [EXTRACT] Pages to extract: %@", [pageNumbers componentsJoinedByString:@", "]);
|
|
372
|
+
NSLog(@"📁 [FILE] Output path: %@", outputPath);
|
|
373
|
+
|
|
374
|
+
// Create extracted PDF
|
|
375
|
+
PDFDocument *extractedPDF = [[PDFDocument alloc] init];
|
|
376
|
+
int extractedCount = 0;
|
|
377
|
+
int current = 0;
|
|
378
|
+
|
|
379
|
+
for (NSNumber *pageNum in pageNumbers) {
|
|
380
|
+
int pageIndex = pageNum.intValue;
|
|
381
|
+
current++;
|
|
382
|
+
|
|
383
|
+
NSLog(@"📊 [PROGRESS] Processing page %d/%lu (page number: %d)", current, (unsigned long)pageNumbers.count, pageIndex);
|
|
384
|
+
|
|
385
|
+
if (pageIndex >= 0 && pageIndex < totalPages) {
|
|
386
|
+
PDFPage *page = [pdfDocument pageAtIndex:pageIndex];
|
|
387
|
+
if (page) {
|
|
388
|
+
[extractedPDF insertPage:page atIndex:extractedPDF.pageCount];
|
|
389
|
+
extractedCount++;
|
|
390
|
+
NSLog(@"✅ [PROGRESS] Extracted page %d", pageIndex);
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
NSLog(@"⚠️ [EXTRACT] Skipping invalid page index: %d", pageIndex);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Save extracted PDF
|
|
398
|
+
NSLog(@"📁 [FILE] Writing extracted PDF...");
|
|
399
|
+
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
|
|
400
|
+
BOOL success = [extractedPDF writeToURL:outputURL];
|
|
401
|
+
|
|
402
|
+
if (success) {
|
|
403
|
+
NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:outputPath error:nil];
|
|
404
|
+
unsigned long long fileSize = [fileAttrs fileSize];
|
|
405
|
+
NSLog(@"✅ [EXTRACT] extractPages - SUCCESS - Extracted %d pages to: %@ (size: %llu bytes)", extractedCount, outputPath, fileSize);
|
|
406
|
+
resolve(outputPath);
|
|
407
|
+
} else {
|
|
408
|
+
NSLog(@"❌ [EXTRACT] Failed to save extracted PDF");
|
|
409
|
+
reject(@"EXTRACT_ERROR", @"Failed to save extracted PDF", nil);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Rotate a page in PDF
|
|
415
|
+
* PRO FEATURE: Requires Pro license
|
|
416
|
+
*/
|
|
417
|
+
RCT_EXPORT_METHOD(rotatePage:(NSString *)filePath
|
|
418
|
+
pageNumber:(int)pageNumber
|
|
419
|
+
degrees:(int)degrees
|
|
420
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
421
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
422
|
+
|
|
423
|
+
// Check Pro license
|
|
424
|
+
if (![_licenseVerifier isProActive]) {
|
|
425
|
+
reject(@"LICENSE_REQUIRED", @"PDF Operations requires a Pro license", nil);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!filePath || filePath.length == 0) {
|
|
430
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
435
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
436
|
+
|
|
437
|
+
if (!pdfDocument) {
|
|
438
|
+
reject(@"FILE_NOT_FOUND", @"PDF file not found", nil);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (pageNumber < 0 || pageNumber >= pdfDocument.pageCount) {
|
|
443
|
+
reject(@"INVALID_PAGE", @"Invalid page number", nil);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
PDFPage *page = [pdfDocument pageAtIndex:pageNumber];
|
|
448
|
+
if (page) {
|
|
449
|
+
// Rotate page
|
|
450
|
+
int currentRotation = page.rotation;
|
|
451
|
+
int newRotation = (currentRotation + degrees) % 360;
|
|
452
|
+
page.rotation = newRotation;
|
|
453
|
+
|
|
454
|
+
// Save PDF
|
|
455
|
+
BOOL success = [pdfDocument writeToURL:pdfURL];
|
|
456
|
+
|
|
457
|
+
if (success) {
|
|
458
|
+
resolve(@YES);
|
|
459
|
+
} else {
|
|
460
|
+
reject(@"ROTATE_ERROR", @"Failed to save rotated PDF", nil);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
reject(@"PAGE_NOT_FOUND", @"Page not found", nil);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Delete a page from PDF
|
|
469
|
+
* PRO FEATURE: Requires Pro license
|
|
470
|
+
*/
|
|
471
|
+
RCT_EXPORT_METHOD(deletePage:(NSString *)filePath
|
|
472
|
+
pageNumber:(int)pageNumber
|
|
473
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
474
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
475
|
+
|
|
476
|
+
// Check Pro license
|
|
477
|
+
if (![_licenseVerifier isProActive]) {
|
|
478
|
+
reject(@"LICENSE_REQUIRED", @"PDF Operations requires a Pro license", nil);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!filePath || filePath.length == 0) {
|
|
483
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
488
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
489
|
+
|
|
490
|
+
if (!pdfDocument) {
|
|
491
|
+
reject(@"FILE_NOT_FOUND", @"PDF file not found", nil);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (pageNumber < 0 || pageNumber >= pdfDocument.pageCount) {
|
|
496
|
+
reject(@"INVALID_PAGE", @"Invalid page number", nil);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Delete page
|
|
501
|
+
[pdfDocument removePageAtIndex:pageNumber];
|
|
502
|
+
|
|
503
|
+
// Save PDF
|
|
504
|
+
BOOL success = [pdfDocument writeToURL:pdfURL];
|
|
505
|
+
|
|
506
|
+
if (success) {
|
|
507
|
+
resolve(@YES);
|
|
508
|
+
} else {
|
|
509
|
+
reject(@"DELETE_ERROR", @"Failed to save PDF after page deletion", nil);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Get PDF page count
|
|
515
|
+
*/
|
|
516
|
+
RCT_EXPORT_METHOD(getPageCount:(NSString *)filePath
|
|
517
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
518
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
519
|
+
|
|
520
|
+
if (!filePath || filePath.length == 0) {
|
|
521
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
NSURL *pdfURL = [NSURL fileURLWithPath:filePath];
|
|
526
|
+
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:pdfURL];
|
|
527
|
+
|
|
528
|
+
if (!pdfDocument) {
|
|
529
|
+
reject(@"FILE_NOT_FOUND", @"PDF file not found", nil);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
NSUInteger pageCount = pdfDocument.pageCount;
|
|
534
|
+
resolve(@(pageCount));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-pdf-jsi",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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",
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"react": "*",
|
|
76
76
|
"react-native": "*",
|
|
77
|
-
"react-native-blob-util": ">=0.13.7"
|
|
77
|
+
"react-native-blob-util": ">=0.13.7",
|
|
78
|
+
"@react-native-async-storage/async-storage": ">=1.17.0"
|
|
78
79
|
},
|
|
79
80
|
"files": [
|
|
80
81
|
"android/",
|