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
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
package org.wonday.pdf;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.graphics.Canvas;
|
|
5
|
+
import android.graphics.Color;
|
|
6
|
+
import android.graphics.Paint;
|
|
7
|
+
import android.graphics.Rect;
|
|
8
|
+
import android.graphics.pdf.PdfRenderer;
|
|
9
|
+
import android.os.ParcelFileDescriptor;
|
|
10
|
+
import android.util.Log;
|
|
11
|
+
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
13
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
14
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
15
|
+
import com.facebook.react.bridge.Promise;
|
|
16
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
17
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
18
|
+
import com.facebook.react.bridge.WritableArray;
|
|
19
|
+
import com.facebook.react.bridge.WritableMap;
|
|
20
|
+
import com.facebook.react.bridge.Arguments;
|
|
21
|
+
|
|
22
|
+
import java.io.File;
|
|
23
|
+
import java.io.FileOutputStream;
|
|
24
|
+
import java.io.IOException;
|
|
25
|
+
import java.util.ArrayList;
|
|
26
|
+
import java.util.List;
|
|
27
|
+
import java.util.Date;
|
|
28
|
+
import java.util.Locale;
|
|
29
|
+
import java.text.SimpleDateFormat;
|
|
30
|
+
import android.graphics.pdf.PdfDocument;
|
|
31
|
+
|
|
32
|
+
public class PDFExporter extends ReactContextBaseJavaModule {
|
|
33
|
+
private static final String TAG = "PDFExporter";
|
|
34
|
+
private LicenseVerifier licenseVerifier;
|
|
35
|
+
|
|
36
|
+
public PDFExporter(ReactApplicationContext reactContext) {
|
|
37
|
+
super(reactContext);
|
|
38
|
+
licenseVerifier = new LicenseVerifier(reactContext);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public String getName() {
|
|
43
|
+
return "PDFExporter";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate a unique filename with timestamp to prevent overwrites
|
|
48
|
+
* @param baseName Base filename without extension
|
|
49
|
+
* @param pageNum Page number (or -1 for PDF operations)
|
|
50
|
+
* @param extension File extension (png, jpeg, pdf)
|
|
51
|
+
* @return Filename with timestamp (e.g., "filename_page_1_20251102_181059.png")
|
|
52
|
+
*/
|
|
53
|
+
private String generateTimestampedFileName(String baseName, int pageNum, String extension) {
|
|
54
|
+
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
|
|
55
|
+
String timestamp = sdf.format(new Date());
|
|
56
|
+
|
|
57
|
+
if (pageNum >= 0) {
|
|
58
|
+
// For image exports: filename_page_1_20251102_181059.png
|
|
59
|
+
return baseName + "_page_" + pageNum + "_" + timestamp + "." + extension;
|
|
60
|
+
} else {
|
|
61
|
+
// For PDF operations: filename_20251102_181059.pdf
|
|
62
|
+
return baseName + "_" + timestamp + "." + extension;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@ReactMethod
|
|
68
|
+
public void exportToImages(String filePath, ReadableMap options, Promise promise) {
|
|
69
|
+
try {
|
|
70
|
+
if (!licenseVerifier.isProActive()) {
|
|
71
|
+
promise.reject("LICENSE_REQUIRED", "Export to Images requires a Pro license");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
76
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
File pdfFile = new File(filePath);
|
|
81
|
+
if (!pdfFile.exists()) {
|
|
82
|
+
promise.reject("FILE_NOT_FOUND", "PDF file not found: " + filePath);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
ReadableArray pages = options.hasKey("pages") ? options.getArray("pages") : null;
|
|
87
|
+
int dpi = options.hasKey("dpi") ? options.getInt("dpi") : 150;
|
|
88
|
+
String format = options.hasKey("format") ? options.getString("format") : "png";
|
|
89
|
+
String outputDir = options.hasKey("outputDir") ? options.getString("outputDir") : null;
|
|
90
|
+
|
|
91
|
+
List<String> exportedFiles = exportPagesToImages(pdfFile, pages, dpi, format, outputDir);
|
|
92
|
+
|
|
93
|
+
WritableArray result = Arguments.createArray();
|
|
94
|
+
for (String file : exportedFiles) {
|
|
95
|
+
result.pushString(file);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
promise.resolve(result);
|
|
99
|
+
|
|
100
|
+
} catch (Exception e) {
|
|
101
|
+
Log.e(TAG, "Error exporting to images", e);
|
|
102
|
+
promise.reject("EXPORT_ERROR", e.getMessage());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@ReactMethod
|
|
107
|
+
public void exportPageToImage(String filePath, int pageIndex, ReadableMap options, Promise promise) {
|
|
108
|
+
try {
|
|
109
|
+
String format = options.hasKey("format") ? options.getString("format") : "jpeg";
|
|
110
|
+
double quality = options.hasKey("quality") ? options.getDouble("quality") : 0.9;
|
|
111
|
+
double scale = options.hasKey("scale") ? options.getDouble("scale") : 2.0;
|
|
112
|
+
|
|
113
|
+
Log.i(TAG, "🖼️ [EXPORT] exportPageToImage - START - page: " + pageIndex + ", format: " + format + ", quality: " + quality + ", scale: " + scale);
|
|
114
|
+
|
|
115
|
+
boolean licenseActive = licenseVerifier.isProActive();
|
|
116
|
+
Log.i(TAG, "🔑 [LICENSE] isProActive: " + licenseActive);
|
|
117
|
+
|
|
118
|
+
if (!licenseActive) {
|
|
119
|
+
Log.e(TAG, "❌ [EXPORT] exportPageToImage - FAILED - License required");
|
|
120
|
+
promise.reject("LICENSE_REQUIRED", "Export to Images requires a Pro license");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
125
|
+
Log.e(TAG, "❌ [EXPORT] exportPageToImage - FAILED - Invalid path");
|
|
126
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
File pdfFile = new File(filePath);
|
|
131
|
+
Log.i(TAG, "📁 [FILE] PDF path: " + filePath);
|
|
132
|
+
|
|
133
|
+
if (!pdfFile.exists()) {
|
|
134
|
+
Log.e(TAG, "❌ [FILE] PDF not found: " + filePath);
|
|
135
|
+
promise.reject("FILE_NOT_FOUND", "PDF file not found: " + filePath);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Log.i(TAG, "📁 [FILE] PDF exists, size: " + pdfFile.length() + " bytes");
|
|
140
|
+
|
|
141
|
+
int dpi = (int)(72 * scale);
|
|
142
|
+
Log.i(TAG, "🖼️ [EXPORT] Calculated DPI: " + dpi);
|
|
143
|
+
|
|
144
|
+
String outputPath = exportSinglePageToImage(pdfFile, pageIndex, dpi, format, null);
|
|
145
|
+
|
|
146
|
+
Log.i(TAG, "✅ [EXPORT] exportPageToImage - SUCCESS - output: " + outputPath);
|
|
147
|
+
promise.resolve(outputPath);
|
|
148
|
+
|
|
149
|
+
} catch (Exception e) {
|
|
150
|
+
Log.e(TAG, "❌ [EXPORT] exportPageToImage - ERROR: " + e.getMessage(), e);
|
|
151
|
+
promise.reject("EXPORT_ERROR", e.getMessage());
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private String exportSinglePageToImage(File pdfFile, int pageIndex, int dpi, String format, String outputDir) throws IOException {
|
|
156
|
+
Log.i(TAG, "🖼️ [EXPORT] exportSinglePageToImage - START - pageIndex: " + pageIndex + ", dpi: " + dpi + ", format: " + format);
|
|
157
|
+
|
|
158
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
159
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
160
|
+
|
|
161
|
+
int totalPages = pdfRenderer.getPageCount();
|
|
162
|
+
Log.i(TAG, "📁 [FILE] PDF opened, total pages: " + totalPages);
|
|
163
|
+
|
|
164
|
+
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
165
|
+
Log.e(TAG, "❌ [EXPORT] Invalid page index: " + pageIndex + " (total: " + totalPages + ")");
|
|
166
|
+
throw new IOException("Invalid page index: " + pageIndex);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
PdfRenderer.Page page = pdfRenderer.openPage(pageIndex);
|
|
170
|
+
Log.i(TAG, "📄 [PAGE] Opened page " + (pageIndex + 1));
|
|
171
|
+
|
|
172
|
+
int width = (int)(page.getWidth() * dpi / 72);
|
|
173
|
+
int height = (int)(page.getHeight() * dpi / 72);
|
|
174
|
+
|
|
175
|
+
Log.i(TAG, "🖼️ [BITMAP] Creating bitmap - width: " + width + "px, height: " + height + "px, dpi: " + dpi);
|
|
176
|
+
|
|
177
|
+
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
178
|
+
Canvas canvas = new Canvas(bitmap);
|
|
179
|
+
canvas.drawColor(Color.WHITE);
|
|
180
|
+
|
|
181
|
+
Bitmap renderBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
182
|
+
Log.i(TAG, "🖼️ [RENDER] Rendering page to bitmap...");
|
|
183
|
+
page.render(renderBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
|
|
184
|
+
canvas.drawBitmap(renderBitmap, 0, 0, null);
|
|
185
|
+
renderBitmap.recycle();
|
|
186
|
+
|
|
187
|
+
page.close();
|
|
188
|
+
Log.i(TAG, "✅ [RENDER] Page rendered successfully");
|
|
189
|
+
|
|
190
|
+
if (outputDir == null) {
|
|
191
|
+
outputDir = getReactApplicationContext().getCacheDir().getAbsolutePath();
|
|
192
|
+
Log.i(TAG, "📁 [FILE] Using cache dir: " + outputDir);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Generate unique filename with timestamp to prevent overwrites
|
|
196
|
+
String baseName = pdfFile.getName().replace(".pdf", "");
|
|
197
|
+
String fileName = generateTimestampedFileName(baseName, pageIndex + 1, format);
|
|
198
|
+
File outputFile = new File(outputDir, fileName);
|
|
199
|
+
|
|
200
|
+
Log.i(TAG, "📁 [FILE] Writing to: " + outputFile.getAbsolutePath());
|
|
201
|
+
|
|
202
|
+
try (FileOutputStream out = new FileOutputStream(outputFile)) {
|
|
203
|
+
Bitmap.CompressFormat compressFormat = format.equalsIgnoreCase("png")
|
|
204
|
+
? Bitmap.CompressFormat.PNG
|
|
205
|
+
: Bitmap.CompressFormat.JPEG;
|
|
206
|
+
int qualityPercent = format.equalsIgnoreCase("png") ? 100 : 90;
|
|
207
|
+
|
|
208
|
+
Log.i(TAG, "📁 [FILE] Compressing as " + compressFormat + " at " + qualityPercent + "% quality");
|
|
209
|
+
bitmap.compress(compressFormat, qualityPercent, out);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
bitmap.recycle();
|
|
213
|
+
|
|
214
|
+
long fileSize = outputFile.length();
|
|
215
|
+
Log.i(TAG, "✅ [EXPORT] exportSinglePageToImage - SUCCESS - size: " + fileSize + " bytes, path: " + outputFile.getAbsolutePath());
|
|
216
|
+
return outputFile.getAbsolutePath();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
private List<String> exportPagesToImages(File pdfFile, ReadableArray pages, int dpi, String format, String outputDir) throws IOException {
|
|
222
|
+
Log.i(TAG, "🖼️ [EXPORT] exportPagesToImages - START - dpi: " + dpi + ", format: " + format);
|
|
223
|
+
|
|
224
|
+
List<String> exportedFiles = new ArrayList<>();
|
|
225
|
+
|
|
226
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
227
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
228
|
+
|
|
229
|
+
int pageCount = pdfRenderer.getPageCount();
|
|
230
|
+
Log.i(TAG, "📁 [FILE] PDF has " + pageCount + " total pages");
|
|
231
|
+
|
|
232
|
+
List<Integer> pagesToExport = new ArrayList<>();
|
|
233
|
+
if (pages != null && pages.size() > 0) {
|
|
234
|
+
for (int i = 0; i < pages.size(); i++) {
|
|
235
|
+
int pageIndex = pages.getInt(i);
|
|
236
|
+
if (pageIndex >= 0 && pageIndex < pageCount) {
|
|
237
|
+
pagesToExport.add(pageIndex);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
Log.i(TAG, "📊 [PROGRESS] Exporting " + pagesToExport.size() + " specific pages");
|
|
241
|
+
} else {
|
|
242
|
+
for (int i = 0; i < pageCount; i++) {
|
|
243
|
+
pagesToExport.add(i);
|
|
244
|
+
}
|
|
245
|
+
Log.i(TAG, "📊 [PROGRESS] Exporting all " + pageCount + " pages");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
int current = 0;
|
|
249
|
+
for (int pageIndex : pagesToExport) {
|
|
250
|
+
try {
|
|
251
|
+
current++;
|
|
252
|
+
Log.i(TAG, "📊 [PROGRESS] Exporting page " + current + "/" + pagesToExport.size() + " (page number: " + (pageIndex + 1) + ")");
|
|
253
|
+
|
|
254
|
+
PdfRenderer.Page page = pdfRenderer.openPage(pageIndex);
|
|
255
|
+
|
|
256
|
+
float scale = dpi / 72f; // 72 DPI is default
|
|
257
|
+
int width = (int) (page.getWidth() * scale);
|
|
258
|
+
int height = (int) (page.getHeight() * scale);
|
|
259
|
+
|
|
260
|
+
Log.i(TAG, "🖼️ [BITMAP] Creating " + width + "x" + height + " bitmap");
|
|
261
|
+
|
|
262
|
+
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
263
|
+
Canvas canvas = new Canvas(bitmap);
|
|
264
|
+
canvas.drawColor(Color.WHITE);
|
|
265
|
+
|
|
266
|
+
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
|
|
267
|
+
|
|
268
|
+
String fileName = String.format("page_%d.%s", pageIndex + 1, format);
|
|
269
|
+
String outputPath = saveBitmap(bitmap, fileName, outputDir);
|
|
270
|
+
exportedFiles.add(outputPath);
|
|
271
|
+
|
|
272
|
+
bitmap.recycle();
|
|
273
|
+
page.close();
|
|
274
|
+
|
|
275
|
+
Log.i(TAG, "✅ [PROGRESS] Page " + (pageIndex + 1) + " exported to " + outputPath);
|
|
276
|
+
|
|
277
|
+
} catch (Exception e) {
|
|
278
|
+
Log.e(TAG, "❌ [EXPORT] Error exporting page " + (pageIndex + 1), e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
Log.i(TAG, "✅ [EXPORT] exportPagesToImages - SUCCESS - Exported " + exportedFiles.size() + " pages");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return exportedFiles;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
private String saveBitmap(Bitmap bitmap, String fileName, String outputDir) throws IOException {
|
|
290
|
+
File outputFile;
|
|
291
|
+
|
|
292
|
+
if (outputDir != null && !outputDir.isEmpty()) {
|
|
293
|
+
File dir = new File(outputDir);
|
|
294
|
+
if (!dir.exists()) {
|
|
295
|
+
dir.mkdirs();
|
|
296
|
+
}
|
|
297
|
+
outputFile = new File(dir, fileName);
|
|
298
|
+
} else {
|
|
299
|
+
File cacheDir = getReactApplicationContext().getCacheDir();
|
|
300
|
+
outputFile = new File(cacheDir, fileName);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try (FileOutputStream out = new FileOutputStream(outputFile)) {
|
|
304
|
+
if (fileName.toLowerCase().endsWith(".png")) {
|
|
305
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
|
306
|
+
} else if (fileName.toLowerCase().endsWith(".jpg") || fileName.toLowerCase().endsWith(".jpeg")) {
|
|
307
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
|
|
308
|
+
} else {
|
|
309
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return outputFile.getAbsolutePath();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@ReactMethod
|
|
318
|
+
public void mergePDFs(ReadableArray filePaths, String outputPath, Promise promise) {
|
|
319
|
+
try {
|
|
320
|
+
// Check Pro license
|
|
321
|
+
if (!licenseVerifier.isProActive()) {
|
|
322
|
+
promise.reject("LICENSE_REQUIRED", "PDF Operations requires a Pro license");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (filePaths == null || filePaths.size() < 2) {
|
|
327
|
+
promise.reject("INVALID_INPUT", "At least 2 PDF files are required for merging");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Validate all files exist
|
|
332
|
+
List<String> validPaths = new ArrayList<>();
|
|
333
|
+
for (int i = 0; i < filePaths.size(); i++) {
|
|
334
|
+
String filePath = filePaths.getString(i);
|
|
335
|
+
File file = new File(filePath);
|
|
336
|
+
if (file.exists()) {
|
|
337
|
+
validPaths.add(filePath);
|
|
338
|
+
} else {
|
|
339
|
+
Log.w(TAG, "File not found: " + filePath);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (validPaths.size() < 2) {
|
|
344
|
+
promise.reject("INVALID_INPUT", "At least 2 valid PDF files are required");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Determine output path
|
|
349
|
+
if (outputPath == null || outputPath.isEmpty()) {
|
|
350
|
+
File firstFile = new File(validPaths.get(0));
|
|
351
|
+
outputPath = new File(firstFile.getParent(), "merged_" + System.currentTimeMillis() + ".pdf").getAbsolutePath();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Merge PDFs using PdfRenderer and PdfDocument
|
|
355
|
+
PdfDocument mergedDoc = new PdfDocument();
|
|
356
|
+
int pageCount = 0;
|
|
357
|
+
|
|
358
|
+
for (String filePath : validPaths) {
|
|
359
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(new File(filePath), ParcelFileDescriptor.MODE_READ_ONLY);
|
|
360
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
361
|
+
|
|
362
|
+
int srcPageCount = pdfRenderer.getPageCount();
|
|
363
|
+
|
|
364
|
+
for (int i = 0; i < srcPageCount; i++) {
|
|
365
|
+
PdfRenderer.Page page = pdfRenderer.openPage(i);
|
|
366
|
+
|
|
367
|
+
// Create new page in merged document
|
|
368
|
+
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(
|
|
369
|
+
page.getWidth(),
|
|
370
|
+
page.getHeight(),
|
|
371
|
+
pageCount + 1
|
|
372
|
+
).create();
|
|
373
|
+
|
|
374
|
+
PdfDocument.Page newPage = mergedDoc.startPage(pageInfo);
|
|
375
|
+
Canvas canvas = newPage.getCanvas();
|
|
376
|
+
|
|
377
|
+
// Render page to bitmap first, then draw on canvas
|
|
378
|
+
Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
|
|
379
|
+
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
|
|
380
|
+
canvas.drawBitmap(bitmap, 0, 0, null);
|
|
381
|
+
bitmap.recycle();
|
|
382
|
+
|
|
383
|
+
mergedDoc.finishPage(newPage);
|
|
384
|
+
page.close();
|
|
385
|
+
pageCount++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Write merged PDF
|
|
391
|
+
try (FileOutputStream outputStream = new FileOutputStream(outputPath)) {
|
|
392
|
+
mergedDoc.writeTo(outputStream);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
mergedDoc.close();
|
|
396
|
+
|
|
397
|
+
Log.i(TAG, "Merged " + validPaths.size() + " PDFs with " + pageCount + " pages to: " + outputPath);
|
|
398
|
+
promise.resolve(outputPath);
|
|
399
|
+
|
|
400
|
+
} catch (Exception e) {
|
|
401
|
+
Log.e(TAG, "Error merging PDFs", e);
|
|
402
|
+
promise.reject("MERGE_ERROR", e.getMessage());
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@ReactMethod
|
|
408
|
+
public void splitPDF(String filePath, ReadableArray pageRanges, String outputDir, Promise promise) {
|
|
409
|
+
try {
|
|
410
|
+
// 🔍 DEEP DEBUG: Inspect incoming parameters
|
|
411
|
+
Log.i(TAG, "🔍 [DEBUG] ========== SPLIT PDF DEBUG START ==========");
|
|
412
|
+
Log.i(TAG, "🔍 [DEBUG] filePath type: " + (filePath != null ? filePath.getClass().getName() : "null"));
|
|
413
|
+
Log.i(TAG, "🔍 [DEBUG] filePath value: " + filePath);
|
|
414
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges type: " + (pageRanges != null ? pageRanges.getClass().getName() : "null"));
|
|
415
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges size: " + (pageRanges != null ? pageRanges.size() : "null"));
|
|
416
|
+
Log.i(TAG, "🔍 [DEBUG] outputDir type: " + (outputDir != null ? outputDir.getClass().getName() : "null"));
|
|
417
|
+
Log.i(TAG, "🔍 [DEBUG] outputDir value: " + outputDir);
|
|
418
|
+
|
|
419
|
+
// Inspect each element in pageRanges
|
|
420
|
+
if (pageRanges != null) {
|
|
421
|
+
for (int i = 0; i < pageRanges.size(); i++) {
|
|
422
|
+
try {
|
|
423
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "] type: " + pageRanges.getType(i));
|
|
424
|
+
|
|
425
|
+
// Try to get as array
|
|
426
|
+
try {
|
|
427
|
+
ReadableArray range = pageRanges.getArray(i);
|
|
428
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "] is Array, size: " + range.size());
|
|
429
|
+
if (range.size() >= 2) {
|
|
430
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "][0] = " + range.getInt(0));
|
|
431
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "][1] = " + range.getInt(1));
|
|
432
|
+
}
|
|
433
|
+
} catch (Exception e) {
|
|
434
|
+
Log.e(TAG, "🔍 [DEBUG] pageRanges[" + i + "] NOT an array: " + e.getMessage());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Try to get as map
|
|
438
|
+
try {
|
|
439
|
+
ReadableMap rangeMap = pageRanges.getMap(i);
|
|
440
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "] is Map");
|
|
441
|
+
if (rangeMap.hasKey("start") && rangeMap.hasKey("end")) {
|
|
442
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "].start = " + rangeMap.getInt("start"));
|
|
443
|
+
Log.i(TAG, "🔍 [DEBUG] pageRanges[" + i + "].end = " + rangeMap.getInt("end"));
|
|
444
|
+
}
|
|
445
|
+
} catch (Exception e) {
|
|
446
|
+
Log.e(TAG, "🔍 [DEBUG] pageRanges[" + i + "] NOT a map: " + e.getMessage());
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
} catch (Exception e) {
|
|
450
|
+
Log.e(TAG, "🔍 [DEBUG] Error inspecting pageRanges[" + i + "]: " + e.getMessage());
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
Log.i(TAG, "🔍 [DEBUG] ========== SPLIT PDF DEBUG END ==========");
|
|
456
|
+
|
|
457
|
+
// Original code continues...
|
|
458
|
+
Log.i(TAG, "✂️ [SPLIT] splitPDF - START - file: " + filePath + ", ranges: " + pageRanges.size());
|
|
459
|
+
|
|
460
|
+
// Check Pro license
|
|
461
|
+
boolean licenseActive = licenseVerifier.isProActive();
|
|
462
|
+
Log.i(TAG, "🔑 [LICENSE] isProActive: " + licenseActive);
|
|
463
|
+
|
|
464
|
+
if (!licenseActive) {
|
|
465
|
+
Log.e(TAG, "❌ [SPLIT] License required");
|
|
466
|
+
promise.reject("LICENSE_REQUIRED", "PDF Operations requires a Pro license");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
471
|
+
Log.e(TAG, "❌ [SPLIT] Invalid path");
|
|
472
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
File pdfFile = new File(filePath);
|
|
477
|
+
if (!pdfFile.exists()) {
|
|
478
|
+
Log.e(TAG, "❌ [FILE] PDF not found: " + filePath);
|
|
479
|
+
promise.reject("FILE_NOT_FOUND", "PDF file not found: " + filePath);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
Log.i(TAG, "📁 [FILE] PDF exists, size: " + pdfFile.length() + " bytes");
|
|
484
|
+
|
|
485
|
+
// Use provided outputDir or default to cache directory
|
|
486
|
+
if (outputDir == null || outputDir.isEmpty()) {
|
|
487
|
+
outputDir = getReactApplicationContext().getCacheDir().getAbsolutePath();
|
|
488
|
+
}
|
|
489
|
+
Log.i(TAG, "📁 [FILE] Output directory: " + outputDir);
|
|
490
|
+
|
|
491
|
+
List<String> splitFiles = new ArrayList<>();
|
|
492
|
+
|
|
493
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
494
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
495
|
+
|
|
496
|
+
int totalPages = pdfRenderer.getPageCount();
|
|
497
|
+
Log.i(TAG, "📁 [FILE] PDF opened, total pages: " + totalPages);
|
|
498
|
+
|
|
499
|
+
// Parse page ranges from flat array: [start1, end1, start2, end2, ...]
|
|
500
|
+
// Example: [1, 10, 11, 21] means ranges 1-10 and 11-21
|
|
501
|
+
int rangeCount = pageRanges.size() / 2;
|
|
502
|
+
Log.i(TAG, "📊 [PROGRESS] Processing " + rangeCount + " ranges from flat array");
|
|
503
|
+
|
|
504
|
+
for (int i = 0; i < pageRanges.size(); i += 2) {
|
|
505
|
+
if (i + 1 >= pageRanges.size()) {
|
|
506
|
+
Log.w(TAG, "⚠️ [SPLIT] Skipping incomplete range at index " + i);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
int start = pageRanges.getInt(i) - 1; // Convert to 0-indexed
|
|
511
|
+
int end = pageRanges.getInt(i + 1); // End is inclusive, 1-indexed
|
|
512
|
+
|
|
513
|
+
int rangeNum = (i / 2) + 1;
|
|
514
|
+
Log.i(TAG, "📊 [PROGRESS] Processing range " + rangeNum + "/" + rangeCount + ": pages " + (start + 1) + "-" + end);
|
|
515
|
+
|
|
516
|
+
// Validate range
|
|
517
|
+
if (start < 0 || end > totalPages || start >= end) {
|
|
518
|
+
Log.w(TAG, "⚠️ [SPLIT] Invalid range: [" + (start + 1) + ", " + end + "]");
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Create split PDF for this range with timestamped filename
|
|
523
|
+
PdfDocument splitDoc = new PdfDocument();
|
|
524
|
+
String baseName = pdfFile.getName().replace(".pdf", "") + "_pages_" + (start + 1) + "-" + end;
|
|
525
|
+
String fileName = generateTimestampedFileName(baseName, -1, "pdf");
|
|
526
|
+
String outputFile = new File(outputDir, fileName).getAbsolutePath();
|
|
527
|
+
|
|
528
|
+
Log.i(TAG, "📁 [FILE] Creating split file: " + outputFile);
|
|
529
|
+
|
|
530
|
+
int pageNum = 1;
|
|
531
|
+
for (int page = start; page < end && page < totalPages; page++) {
|
|
532
|
+
Log.i(TAG, "📊 [PROGRESS] Processing page " + (page + 1) + " (page " + pageNum + " in new PDF)");
|
|
533
|
+
|
|
534
|
+
PdfRenderer.Page srcPage = pdfRenderer.openPage(page);
|
|
535
|
+
|
|
536
|
+
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(
|
|
537
|
+
srcPage.getWidth(),
|
|
538
|
+
srcPage.getHeight(),
|
|
539
|
+
pageNum++
|
|
540
|
+
).create();
|
|
541
|
+
|
|
542
|
+
PdfDocument.Page newPage = splitDoc.startPage(pageInfo);
|
|
543
|
+
Canvas canvas = newPage.getCanvas();
|
|
544
|
+
|
|
545
|
+
// Render page to bitmap first, then draw on canvas
|
|
546
|
+
Bitmap bitmap = Bitmap.createBitmap(srcPage.getWidth(), srcPage.getHeight(), Bitmap.Config.ARGB_8888);
|
|
547
|
+
srcPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
|
|
548
|
+
canvas.drawBitmap(bitmap, 0, 0, null);
|
|
549
|
+
bitmap.recycle();
|
|
550
|
+
|
|
551
|
+
splitDoc.finishPage(newPage);
|
|
552
|
+
srcPage.close();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Write split PDF
|
|
556
|
+
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
|
557
|
+
splitDoc.writeTo(outputStream);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
splitDoc.close();
|
|
561
|
+
splitFiles.add(outputFile);
|
|
562
|
+
|
|
563
|
+
File createdFile = new File(outputFile);
|
|
564
|
+
Log.i(TAG, "✅ [SPLIT] Created file: " + outputFile + " (size: " + createdFile.length() + " bytes)");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
Log.i(TAG, "✅ [SPLIT] splitPDF - SUCCESS - Split into " + splitFiles.size() + " files");
|
|
569
|
+
|
|
570
|
+
// Convert ArrayList to WritableArray for React Native bridge
|
|
571
|
+
WritableArray result = Arguments.createArray();
|
|
572
|
+
for (String file : splitFiles) {
|
|
573
|
+
result.pushString(file);
|
|
574
|
+
}
|
|
575
|
+
promise.resolve(result);
|
|
576
|
+
|
|
577
|
+
} catch (Exception e) {
|
|
578
|
+
Log.e(TAG, "❌ [SPLIT] Error: " + e.getMessage(), e);
|
|
579
|
+
promise.reject("SPLIT_ERROR", e.getMessage());
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@ReactMethod
|
|
585
|
+
public void extractPages(String filePath, ReadableArray pageNumbers, String outputPath, Promise promise) {
|
|
586
|
+
try {
|
|
587
|
+
Log.i(TAG, "✂️ [EXTRACT] extractPages - START - file: " + filePath + ", pages: " + pageNumbers.size());
|
|
588
|
+
|
|
589
|
+
// Check Pro license
|
|
590
|
+
boolean licenseActive = licenseVerifier.isProActive();
|
|
591
|
+
Log.i(TAG, "🔑 [LICENSE] isProActive: " + licenseActive);
|
|
592
|
+
|
|
593
|
+
if (!licenseActive) {
|
|
594
|
+
Log.e(TAG, "❌ [EXTRACT] License required");
|
|
595
|
+
promise.reject("LICENSE_REQUIRED", "PDF Operations requires a Pro license");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
600
|
+
Log.e(TAG, "❌ [EXTRACT] Invalid path");
|
|
601
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
File pdfFile = new File(filePath);
|
|
606
|
+
if (!pdfFile.exists()) {
|
|
607
|
+
Log.e(TAG, "❌ [FILE] PDF not found: " + filePath);
|
|
608
|
+
promise.reject("FILE_NOT_FOUND", "PDF file not found: " + filePath);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
Log.i(TAG, "📁 [FILE] PDF exists, size: " + pdfFile.length() + " bytes");
|
|
613
|
+
|
|
614
|
+
// Determine output path with timestamped filename
|
|
615
|
+
if (outputPath == null || outputPath.isEmpty()) {
|
|
616
|
+
String baseName = pdfFile.getName().replace(".pdf", "") + "_extracted";
|
|
617
|
+
String fileName = generateTimestampedFileName(baseName, -1, "pdf");
|
|
618
|
+
outputPath = new File(pdfFile.getParent(), fileName).getAbsolutePath();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
Log.i(TAG, "📁 [FILE] Output path: " + outputPath);
|
|
622
|
+
|
|
623
|
+
// Log page numbers to extract
|
|
624
|
+
StringBuilder pageList = new StringBuilder();
|
|
625
|
+
for (int i = 0; i < pageNumbers.size(); i++) {
|
|
626
|
+
if (i > 0) pageList.append(", ");
|
|
627
|
+
pageList.append(pageNumbers.getInt(i));
|
|
628
|
+
}
|
|
629
|
+
Log.i(TAG, "📊 [EXTRACT] Pages to extract: " + pageList.toString());
|
|
630
|
+
|
|
631
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
632
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
633
|
+
|
|
634
|
+
int totalPages = pdfRenderer.getPageCount();
|
|
635
|
+
Log.i(TAG, "📁 [FILE] PDF opened, total pages: " + totalPages);
|
|
636
|
+
|
|
637
|
+
PdfDocument extractDoc = new PdfDocument();
|
|
638
|
+
int pageNum = 1;
|
|
639
|
+
int extractedCount = 0;
|
|
640
|
+
|
|
641
|
+
for (int i = 0; i < pageNumbers.size(); i++) {
|
|
642
|
+
int pageIndex = pageNumbers.getInt(i) - 1; // Convert to 0-indexed
|
|
643
|
+
|
|
644
|
+
Log.i(TAG, "📊 [PROGRESS] Processing page " + (i + 1) + "/" + pageNumbers.size() + " (page number: " + (pageIndex + 1) + ")");
|
|
645
|
+
|
|
646
|
+
if (pageIndex >= 0 && pageIndex < totalPages) {
|
|
647
|
+
PdfRenderer.Page srcPage = pdfRenderer.openPage(pageIndex);
|
|
648
|
+
|
|
649
|
+
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(
|
|
650
|
+
srcPage.getWidth(),
|
|
651
|
+
srcPage.getHeight(),
|
|
652
|
+
pageNum++
|
|
653
|
+
).create();
|
|
654
|
+
|
|
655
|
+
PdfDocument.Page newPage = extractDoc.startPage(pageInfo);
|
|
656
|
+
Canvas canvas = newPage.getCanvas();
|
|
657
|
+
|
|
658
|
+
// Render page to bitmap first, then draw on canvas
|
|
659
|
+
Bitmap bitmap = Bitmap.createBitmap(srcPage.getWidth(), srcPage.getHeight(), Bitmap.Config.ARGB_8888);
|
|
660
|
+
srcPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
|
|
661
|
+
canvas.drawBitmap(bitmap, 0, 0, null);
|
|
662
|
+
bitmap.recycle();
|
|
663
|
+
|
|
664
|
+
extractDoc.finishPage(newPage);
|
|
665
|
+
srcPage.close();
|
|
666
|
+
extractedCount++;
|
|
667
|
+
|
|
668
|
+
Log.i(TAG, "✅ [PROGRESS] Extracted page " + (pageIndex + 1));
|
|
669
|
+
} else {
|
|
670
|
+
Log.w(TAG, "⚠️ [EXTRACT] Skipping invalid page index: " + (pageIndex + 1));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Write extracted PDF
|
|
675
|
+
Log.i(TAG, "📁 [FILE] Writing extracted PDF...");
|
|
676
|
+
try (FileOutputStream outputStream = new FileOutputStream(outputPath)) {
|
|
677
|
+
extractDoc.writeTo(outputStream);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
extractDoc.close();
|
|
681
|
+
|
|
682
|
+
File createdFile = new File(outputPath);
|
|
683
|
+
Log.i(TAG, "✅ [EXTRACT] extractPages - SUCCESS - Extracted " + extractedCount + " pages to: " + outputPath + " (size: " + createdFile.length() + " bytes)");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
promise.resolve(outputPath);
|
|
687
|
+
|
|
688
|
+
} catch (Exception e) {
|
|
689
|
+
Log.e(TAG, "❌ [EXTRACT] Error: " + e.getMessage(), e);
|
|
690
|
+
promise.reject("EXTRACT_ERROR", e.getMessage());
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
@ReactMethod
|
|
696
|
+
public void rotatePage(String filePath, int pageNumber, int degrees, Promise promise) {
|
|
697
|
+
try {
|
|
698
|
+
// Check Pro license
|
|
699
|
+
if (!licenseVerifier.isProActive()) {
|
|
700
|
+
promise.reject("LICENSE_REQUIRED", "PDF Operations requires a Pro license");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
705
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
promise.reject("NOT_IMPLEMENTED", "Page rotation not yet implemented on Android");
|
|
710
|
+
|
|
711
|
+
} catch (Exception e) {
|
|
712
|
+
Log.e(TAG, "Error rotating page", e);
|
|
713
|
+
promise.reject("ROTATE_ERROR", e.getMessage());
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
@ReactMethod
|
|
719
|
+
public void deletePage(String filePath, int pageNumber, Promise promise) {
|
|
720
|
+
try {
|
|
721
|
+
// Check Pro license
|
|
722
|
+
if (!licenseVerifier.isProActive()) {
|
|
723
|
+
promise.reject("LICENSE_REQUIRED", "PDF Operations requires a Pro license");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
728
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
promise.reject("NOT_IMPLEMENTED", "Page deletion not yet implemented on Android");
|
|
733
|
+
|
|
734
|
+
} catch (Exception e) {
|
|
735
|
+
Log.e(TAG, "Error deleting page", e);
|
|
736
|
+
promise.reject("DELETE_ERROR", e.getMessage());
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Get PDF page count
|
|
742
|
+
*/
|
|
743
|
+
@ReactMethod
|
|
744
|
+
public void getPageCount(String filePath, Promise promise) {
|
|
745
|
+
try {
|
|
746
|
+
if (filePath == null || filePath.isEmpty()) {
|
|
747
|
+
promise.reject("INVALID_PATH", "File path is required");
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
File pdfFile = new File(filePath);
|
|
752
|
+
if (!pdfFile.exists()) {
|
|
753
|
+
promise.reject("FILE_NOT_FOUND", "PDF file not found: " + filePath);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
try (ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
758
|
+
PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor)) {
|
|
759
|
+
|
|
760
|
+
int pageCount = pdfRenderer.getPageCount();
|
|
761
|
+
promise.resolve(pageCount);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
} catch (Exception e) {
|
|
765
|
+
Log.e(TAG, "Error getting page count", e);
|
|
766
|
+
promise.reject("PAGE_COUNT_ERROR", e.getMessage());
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|