react-native-pdf-jsi 4.3.2 → 4.4.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 +27 -1
- package/android/src/main/java/org/wonday/pdf/PDFJSIManager.java +138 -3
- package/android/src/main/java/org/wonday/pdf/PdfManager.java +10 -0
- package/android/src/main/java/org/wonday/pdf/PdfView.java +110 -3
- package/android/src/main/java/org/wonday/pdf/SearchRegistry.java +44 -0
- package/android/src/main/jniLibs/arm64-v8a/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/x86/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libpdfjsi.so +0 -0
- package/fabric/RNPDFPdfNativeComponent.js +2 -0
- package/index.d.ts +34 -0
- package/index.js +29 -2
- package/ios/RNPDFPdf/PDFJSIManager.m +89 -4
- package/ios/RNPDFPdf/RNPDFPdfView.h +2 -0
- package/ios/RNPDFPdf/RNPDFPdfView.mm +127 -4
- package/ios/RNPDFPdf/RNPDFPdfViewManager.mm +5 -1
- package/ios/RNPDFPdf/SearchRegistry.h +21 -0
- package/ios/RNPDFPdf/SearchRegistry.m +71 -0
- package/package.json +4 -2
- package/src/PDFJSI.js +1 -1
- package/android/.gradle/5.6.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/5.6.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/5.6.1/gc.properties +0 -0
- package/android/.gradle/8.5/checksums/checksums.lock +0 -0
- package/android/.gradle/8.5/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.5/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.5/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.5/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.5/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.5/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.5/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ High-performance React Native PDF viewer with JSI (JavaScript Interface) acceler
|
|
|
41
41
|
- **Export Operations**: Export pages to PNG/JPEG with quality control
|
|
42
42
|
- **PDF Operations**: Split, merge, extract, rotate, and delete pages
|
|
43
43
|
- **PDF Compression**: Reduce file sizes with 5 smart presets (EMAIL, WEB, MOBILE, PRINT, ARCHIVE)
|
|
44
|
-
- **Text Extraction**: Extract and search text with statistics and context
|
|
44
|
+
- **Text Extraction & Search**: Extract and search text with statistics and context; **programmatic search** via `searchTextDirect(pdfId, term, startPage, endPage)` with bounding rects, and **highlight rendering** via `pdfId` + `highlightRects` props (Android & iOS)
|
|
45
45
|
- **File Management** (Android): Download to public storage, open folders with MediaStore API
|
|
46
46
|
|
|
47
47
|
### Compliance & Compatibility
|
|
@@ -261,6 +261,8 @@ The documentation includes:
|
|
|
261
261
|
| `enableAnnotationRendering` | boolean | true | Enable annotation rendering |
|
|
262
262
|
| `enableDoubleTapZoom` | boolean | true | Enable double tap to zoom |
|
|
263
263
|
| `singlePage` | boolean | false | Show only first page (thumbnail mode) |
|
|
264
|
+
| `pdfId` | string | undefined | Stable ID for this PDF (e.g. `"main-pdf"`); required for `searchTextDirect()` so native code can resolve the document path |
|
|
265
|
+
| `highlightRects` | array | undefined | Array of `{ page: number, rect: string }` (rect: `"left,top,right,bottom"` in PDF points) to draw yellow highlights; use with `searchTextDirect()` results |
|
|
264
266
|
| `trustAllCerts` | boolean | true | Allow self-signed certificates |
|
|
265
267
|
| `onLoadProgress` | function(percent) | null | Loading progress callback (0-1) |
|
|
266
268
|
| `onLoadComplete` | function(pages, path, size, tableContents) | null | Called when PDF loads |
|
|
@@ -304,6 +306,30 @@ const pdfRef = useRef(null);
|
|
|
304
306
|
pdfRef.current?.setPage(42);
|
|
305
307
|
```
|
|
306
308
|
|
|
309
|
+
#### searchTextDirect(pdfId, searchTerm, startPage, endPage)
|
|
310
|
+
|
|
311
|
+
Programmatic PDF text search. Returns a promise that resolves to an array of `{ page, text, rect }` (rect is `"left,top,right,bottom"` in PDF coordinates). Use with `pdfId` and `highlightRects` to show highlights.
|
|
312
|
+
|
|
313
|
+
```jsx
|
|
314
|
+
import Pdf, { searchTextDirect } from 'react-native-pdf-jsi';
|
|
315
|
+
|
|
316
|
+
const PDF_ID = 'main-pdf';
|
|
317
|
+
const [highlights, setHighlights] = useState([]);
|
|
318
|
+
|
|
319
|
+
<Pdf
|
|
320
|
+
pdfId={PDF_ID}
|
|
321
|
+
source={source}
|
|
322
|
+
highlightRects={highlights.filter(r => r.rect).map(r => ({ page: r.page, rect: r.rect }))}
|
|
323
|
+
onLoadComplete={(pages, path) => { /* path is registered for search */ }}
|
|
324
|
+
/>
|
|
325
|
+
|
|
326
|
+
// After PDF has loaded, e.g. on button press:
|
|
327
|
+
const results = await searchTextDirect(PDF_ID, 'Lorem', 1, 999);
|
|
328
|
+
setHighlights(results);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
On iOS, the path is registered when the document loads (local file only); you can also call `NativeModules.PDFJSIManager.registerPathForSearch(pdfId, path)` after `onLoadComplete` if needed. Highlights stay aligned when zooming and scrolling on both Android and iOS.
|
|
332
|
+
|
|
307
333
|
## ProGuard / R8 Configuration (Android Release Builds)
|
|
308
334
|
|
|
309
335
|
**IMPORTANT:** If you're using ProGuard or R8 code shrinking in your release builds, you must add the following rules to prevent crashes. These rules preserve JSI classes and native module interfaces that are required at runtime.
|
|
@@ -18,15 +18,27 @@ import com.facebook.react.bridge.ReactMethod;
|
|
|
18
18
|
import com.facebook.react.bridge.Promise;
|
|
19
19
|
import com.facebook.react.bridge.ReadableMap;
|
|
20
20
|
import com.facebook.react.bridge.ReadableArray;
|
|
21
|
+
import com.facebook.react.bridge.WritableArray;
|
|
21
22
|
import com.facebook.react.bridge.WritableMap;
|
|
22
23
|
import com.facebook.react.bridge.Arguments;
|
|
23
24
|
|
|
24
25
|
// import com.facebook.react.turbomodule.core.CallInvokerHolder; // Not available in this RN version
|
|
25
26
|
import com.facebook.soloader.SoLoader;
|
|
26
27
|
|
|
28
|
+
import java.io.File;
|
|
29
|
+
import java.io.IOException;
|
|
27
30
|
import java.util.concurrent.ExecutorService;
|
|
28
31
|
import java.util.concurrent.Executors;
|
|
29
32
|
|
|
33
|
+
import android.graphics.RectF;
|
|
34
|
+
import android.net.Uri;
|
|
35
|
+
import android.os.ParcelFileDescriptor;
|
|
36
|
+
|
|
37
|
+
import io.legere.pdfiumandroid.PdfiumCore;
|
|
38
|
+
import io.legere.pdfiumandroid.PdfDocument;
|
|
39
|
+
import io.legere.pdfiumandroid.PdfPage;
|
|
40
|
+
import io.legere.pdfiumandroid.PdfTextPage;
|
|
41
|
+
|
|
30
42
|
public class PDFJSIManager extends ReactContextBaseJavaModule {
|
|
31
43
|
private static final String MODULE_NAME = "PDFJSIManager";
|
|
32
44
|
private static final String TAG = "PDFJSI";
|
|
@@ -220,9 +232,24 @@ public class PDFJSIManager extends ReactContextBaseJavaModule {
|
|
|
220
232
|
}
|
|
221
233
|
});
|
|
222
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Register a path for search by pdfId. Called from JS when loadComplete fires so search works
|
|
238
|
+
* even if the native view has not received pdfId yet. On Android the view also registers; this is for parity with iOS.
|
|
239
|
+
*/
|
|
240
|
+
@ReactMethod
|
|
241
|
+
public void registerPathForSearch(String pdfId, String path, Promise promise) {
|
|
242
|
+
if (pdfId != null && !pdfId.isEmpty() && path != null && !path.isEmpty()) {
|
|
243
|
+
SearchRegistry.registerPath(pdfId, path);
|
|
244
|
+
promise.resolve(true);
|
|
245
|
+
} else {
|
|
246
|
+
promise.resolve(false);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
223
249
|
|
|
224
250
|
/**
|
|
225
|
-
* Search text directly via JSI
|
|
251
|
+
* Search text directly via JSI.
|
|
252
|
+
* Uses SearchRegistry to get path for pdfId, then io.legere PdfiumCore to extract text and find matches.
|
|
226
253
|
*/
|
|
227
254
|
@ReactMethod
|
|
228
255
|
public void searchTextDirect(String pdfId, String searchTerm, int startPage, int endPage, Promise promise) {
|
|
@@ -230,11 +257,20 @@ public class PDFJSIManager extends ReactContextBaseJavaModule {
|
|
|
230
257
|
promise.reject("JSI_NOT_INITIALIZED", "JSI is not initialized");
|
|
231
258
|
return;
|
|
232
259
|
}
|
|
233
|
-
|
|
260
|
+
if (searchTerm == null || searchTerm.isEmpty()) {
|
|
261
|
+
promise.resolve(Arguments.createArray());
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
234
264
|
backgroundExecutor.execute(() -> {
|
|
235
265
|
try {
|
|
236
266
|
Log.d(TAG, "Searching text via JSI: '" + searchTerm + "' in pages " + startPage + "-" + endPage);
|
|
237
|
-
|
|
267
|
+
String path = SearchRegistry.getPath(pdfId);
|
|
268
|
+
if (path == null || path.isEmpty()) {
|
|
269
|
+
Log.w(TAG, "No path registered for pdfId: " + pdfId + " - pass pdfId to Pdf view to enable search");
|
|
270
|
+
promise.resolve(Arguments.createArray());
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
WritableArray results = searchInPdf(pdfId, path, searchTerm, startPage, endPage);
|
|
238
274
|
promise.resolve(results);
|
|
239
275
|
} catch (Exception e) {
|
|
240
276
|
Log.e(TAG, "Error searching text via JSI", e);
|
|
@@ -242,6 +278,105 @@ public class PDFJSIManager extends ReactContextBaseJavaModule {
|
|
|
242
278
|
}
|
|
243
279
|
});
|
|
244
280
|
}
|
|
281
|
+
|
|
282
|
+
private WritableArray searchInPdf(String pdfId, String path, String searchTerm, int startPage, int endPage) {
|
|
283
|
+
WritableArray out = Arguments.createArray();
|
|
284
|
+
ParcelFileDescriptor pfd = null;
|
|
285
|
+
PdfDocument doc = null;
|
|
286
|
+
try {
|
|
287
|
+
if (path.startsWith("content://")) {
|
|
288
|
+
pfd = getReactApplicationContext().getContentResolver()
|
|
289
|
+
.openFileDescriptor(Uri.parse(path), "r");
|
|
290
|
+
} else {
|
|
291
|
+
File file = new File(path);
|
|
292
|
+
if (!file.exists() || !file.canRead()) {
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
296
|
+
}
|
|
297
|
+
if (pfd == null) return out;
|
|
298
|
+
PdfiumCore core = new PdfiumCore();
|
|
299
|
+
doc = core.newDocument(pfd);
|
|
300
|
+
int pageCount = doc.getPageCount();
|
|
301
|
+
int from = Math.max(1, startPage);
|
|
302
|
+
int to = Math.min(endPage, pageCount);
|
|
303
|
+
String termLower = searchTerm.toLowerCase();
|
|
304
|
+
for (int pageIndex = from; pageIndex <= to; pageIndex++) {
|
|
305
|
+
int zeroBased = pageIndex - 1;
|
|
306
|
+
PdfPage page = doc.openPage(zeroBased);
|
|
307
|
+
if (page == null) continue;
|
|
308
|
+
try {
|
|
309
|
+
// Store page size in PDF points for highlight scaling in PdfView
|
|
310
|
+
try {
|
|
311
|
+
int wPt = page.getPageWidthPoint();
|
|
312
|
+
int hPt = page.getPageHeightPoint();
|
|
313
|
+
if (wPt > 0 && hPt > 0) {
|
|
314
|
+
SearchRegistry.registerPageSizePoints(pdfId, zeroBased, (float) wPt, (float) hPt);
|
|
315
|
+
}
|
|
316
|
+
} catch (Exception ignored) {}
|
|
317
|
+
PdfTextPage textPage = page.openTextPage();
|
|
318
|
+
if (textPage == null) continue;
|
|
319
|
+
try {
|
|
320
|
+
int chars = textPage.textPageCountChars();
|
|
321
|
+
if (chars <= 0) continue;
|
|
322
|
+
String text = textPage.textPageGetText(0, chars);
|
|
323
|
+
if (text == null) continue;
|
|
324
|
+
String textLower = text.toLowerCase();
|
|
325
|
+
int idx = 0;
|
|
326
|
+
while ((idx = textLower.indexOf(termLower, idx)) >= 0) {
|
|
327
|
+
int end = Math.min(idx + searchTerm.length(), text.length());
|
|
328
|
+
int len = end - idx;
|
|
329
|
+
String snippet = text.substring(idx, end);
|
|
330
|
+
WritableMap item = Arguments.createMap();
|
|
331
|
+
item.putInt("page", pageIndex);
|
|
332
|
+
item.putString("text", snippet);
|
|
333
|
+
String rectStr = "{}";
|
|
334
|
+
try {
|
|
335
|
+
int rectCount = textPage.textPageCountRects(idx, len);
|
|
336
|
+
if (rectCount > 0) {
|
|
337
|
+
RectF first = textPage.textPageGetRect(0);
|
|
338
|
+
if (first != null) {
|
|
339
|
+
rectStr = first.left + "," + first.top + "," + first.right + "," + first.bottom;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if ("{}".equals(rectStr)) {
|
|
343
|
+
RectF charBox = textPage.textPageGetCharBox(idx);
|
|
344
|
+
if (charBox != null) {
|
|
345
|
+
rectStr = charBox.left + "," + charBox.top + "," + charBox.right + "," + charBox.bottom;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch (Exception e) {
|
|
349
|
+
Log.d(TAG, "Rect lookup for match at " + idx + ": " + e.getMessage());
|
|
350
|
+
}
|
|
351
|
+
item.putString("rect", rectStr);
|
|
352
|
+
out.pushMap(item);
|
|
353
|
+
idx = end;
|
|
354
|
+
}
|
|
355
|
+
} finally {
|
|
356
|
+
textPage.close();
|
|
357
|
+
}
|
|
358
|
+
} finally {
|
|
359
|
+
page.close();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch (IOException e) {
|
|
363
|
+
Log.e(TAG, "Search IO error", e);
|
|
364
|
+
} catch (Exception e) {
|
|
365
|
+
Log.e(TAG, "Search error", e);
|
|
366
|
+
} finally {
|
|
367
|
+
if (doc != null) {
|
|
368
|
+
try {
|
|
369
|
+
doc.close();
|
|
370
|
+
} catch (Exception ignored) {}
|
|
371
|
+
}
|
|
372
|
+
if (pfd != null) {
|
|
373
|
+
try {
|
|
374
|
+
pfd.close();
|
|
375
|
+
} catch (IOException ignored) {}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
245
380
|
|
|
246
381
|
/**
|
|
247
382
|
* Get performance metrics via JSI
|
|
@@ -69,6 +69,16 @@ public class PdfManager extends SimpleViewManager<PdfView> implements RNPDFPdfVi
|
|
|
69
69
|
pdfView.setPath(path);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
@ReactProp(name = "pdfId")
|
|
73
|
+
public void setPdfId(PdfView pdfView, String pdfId) {
|
|
74
|
+
pdfView.setPdfId(pdfId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@ReactProp(name = "highlightRects")
|
|
78
|
+
public void setHighlightRects(PdfView pdfView, ReadableArray highlightRects) {
|
|
79
|
+
pdfView.setHighlightRects(highlightRects);
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
// page start from 1
|
|
73
83
|
@ReactProp(name = "page")
|
|
74
84
|
public void setPage(PdfView pdfView, int page) {
|
|
@@ -20,7 +20,12 @@ import android.net.Uri;
|
|
|
20
20
|
import android.util.AttributeSet;
|
|
21
21
|
import android.view.MotionEvent;
|
|
22
22
|
import android.graphics.Canvas;
|
|
23
|
+
import android.graphics.Color;
|
|
24
|
+
import android.graphics.Paint;
|
|
23
25
|
import android.os.Handler;
|
|
26
|
+
|
|
27
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
28
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
24
29
|
import android.os.Looper;
|
|
25
30
|
|
|
26
31
|
|
|
@@ -52,6 +57,8 @@ import static java.lang.String.format;
|
|
|
52
57
|
|
|
53
58
|
import java.io.FileNotFoundException;
|
|
54
59
|
import java.io.InputStream;
|
|
60
|
+
import java.util.ArrayList;
|
|
61
|
+
import java.util.List;
|
|
55
62
|
|
|
56
63
|
import com.google.gson.Gson;
|
|
57
64
|
|
|
@@ -77,7 +84,8 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
77
84
|
private FitPolicy fitPolicy = FitPolicy.WIDTH;
|
|
78
85
|
private boolean singlePage = false;
|
|
79
86
|
private boolean scrollEnabled = true;
|
|
80
|
-
|
|
87
|
+
private String pdfId = null;
|
|
88
|
+
|
|
81
89
|
private String decelerationRate = "normal"; // "normal", "fast", "slow"
|
|
82
90
|
|
|
83
91
|
private float originalWidth = 0;
|
|
@@ -94,8 +102,29 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
94
102
|
private int oldW = 0;
|
|
95
103
|
private int oldH = 0;
|
|
96
104
|
|
|
105
|
+
/** Search highlight rects: list of { page (1-based), rect "left,top,right,bottom" in PDF points } */
|
|
106
|
+
private List<HighlightRect> highlightRects = new ArrayList<>();
|
|
107
|
+
private static final int HIGHLIGHT_COLOR = Color.argb(80, 255, 255, 0);
|
|
108
|
+
private final Paint highlightPaint = new Paint();
|
|
109
|
+
|
|
97
110
|
public PdfView(Context context, AttributeSet set){
|
|
98
111
|
super(context, set);
|
|
112
|
+
highlightPaint.setColor(HIGHLIGHT_COLOR);
|
|
113
|
+
highlightPaint.setStyle(Paint.Style.FILL);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Entry for one highlight: page (1-based) and rect in PDF points "left,top,right,bottom". */
|
|
117
|
+
private static class HighlightRect {
|
|
118
|
+
final int page;
|
|
119
|
+
final float left, top, right, bottom;
|
|
120
|
+
|
|
121
|
+
HighlightRect(int page, float left, float top, float right, float bottom) {
|
|
122
|
+
this.page = page;
|
|
123
|
+
this.left = left;
|
|
124
|
+
this.top = top;
|
|
125
|
+
this.right = right;
|
|
126
|
+
this.bottom = bottom;
|
|
127
|
+
}
|
|
99
128
|
}
|
|
100
129
|
|
|
101
130
|
@Override
|
|
@@ -225,6 +254,9 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
225
254
|
self.loadCompleteDispatched = true;
|
|
226
255
|
showLog("loadComplete: Event dispatched successfully (delayed)");
|
|
227
256
|
}
|
|
257
|
+
if (self.pdfId != null && self.path != null) {
|
|
258
|
+
SearchRegistry.registerPath(self.pdfId, self.path);
|
|
259
|
+
}
|
|
228
260
|
}
|
|
229
261
|
});
|
|
230
262
|
} else {
|
|
@@ -340,6 +372,42 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
340
372
|
|
|
341
373
|
lastPageWidth = pageWidth;
|
|
342
374
|
lastPageHeight = pageHeight;
|
|
375
|
+
|
|
376
|
+
if (!highlightRects.isEmpty() && pdfId != null) {
|
|
377
|
+
int pageOneBased = displayedPage + 1;
|
|
378
|
+
try {
|
|
379
|
+
float pdfW = 0, pdfH = 0;
|
|
380
|
+
float[] sizePt = SearchRegistry.getPageSizePoints(pdfId, displayedPage);
|
|
381
|
+
if (sizePt != null && sizePt.length >= 2 && sizePt[0] > 0 && sizePt[1] > 0) {
|
|
382
|
+
pdfW = sizePt[0];
|
|
383
|
+
pdfH = sizePt[1];
|
|
384
|
+
}
|
|
385
|
+
if (pdfW <= 0 || pdfH <= 0) {
|
|
386
|
+
SizeF fallback = getPageSize(displayedPage);
|
|
387
|
+
if (fallback != null) {
|
|
388
|
+
pdfW = fallback.getWidth();
|
|
389
|
+
pdfH = fallback.getHeight();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (pdfW > 0 && pdfH > 0) {
|
|
393
|
+
float scaleX = pageWidth / pdfW;
|
|
394
|
+
float scaleY = pageHeight / pdfH;
|
|
395
|
+
for (HighlightRect hr : highlightRects) {
|
|
396
|
+
if (hr.page != pageOneBased) continue;
|
|
397
|
+
float left = hr.left * scaleX;
|
|
398
|
+
float right = hr.right * scaleX;
|
|
399
|
+
float top = hr.top * scaleY;
|
|
400
|
+
float bottom = hr.bottom * scaleY;
|
|
401
|
+
// PDF coords: origin bottom-left, so top > bottom. Canvas: origin top-left.
|
|
402
|
+
float canvasTop = pageHeight - top;
|
|
403
|
+
float canvasBottom = pageHeight - bottom;
|
|
404
|
+
canvas.drawRect(left, canvasTop, right, canvasBottom, highlightPaint);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} catch (Exception e) {
|
|
408
|
+
showLog("Highlight draw error: " + e.getMessage());
|
|
409
|
+
}
|
|
410
|
+
}
|
|
343
411
|
}
|
|
344
412
|
|
|
345
413
|
@Override
|
|
@@ -361,6 +429,14 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
361
429
|
// the PDF on navigation. We only recycle when PdfManager.onDropViewInstance is called.
|
|
362
430
|
}
|
|
363
431
|
|
|
432
|
+
@Override
|
|
433
|
+
public void recycle() {
|
|
434
|
+
if (pdfId != null) {
|
|
435
|
+
SearchRegistry.unregisterPath(pdfId);
|
|
436
|
+
}
|
|
437
|
+
super.recycle();
|
|
438
|
+
}
|
|
439
|
+
|
|
364
440
|
public void drawPdf() {
|
|
365
441
|
|
|
366
442
|
// FIX: Check if we actually need to reload the document
|
|
@@ -444,14 +520,45 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
|
|
444
520
|
}
|
|
445
521
|
|
|
446
522
|
public void setPath(String path) {
|
|
447
|
-
|
|
523
|
+
if (pdfId != null) {
|
|
524
|
+
SearchRegistry.unregisterPath(pdfId);
|
|
525
|
+
}
|
|
448
526
|
needsReload = true;
|
|
449
527
|
this.path = path;
|
|
450
|
-
// Reset flags when path changes
|
|
451
528
|
loadCompleteDispatched = false;
|
|
452
529
|
lastKnownPageCount = 0;
|
|
453
530
|
}
|
|
454
531
|
|
|
532
|
+
public void setPdfId(String pdfId) {
|
|
533
|
+
if (this.pdfId != null && !this.pdfId.equals(pdfId)) {
|
|
534
|
+
SearchRegistry.unregisterPath(this.pdfId);
|
|
535
|
+
}
|
|
536
|
+
this.pdfId = pdfId;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public void setHighlightRects(ReadableArray arr) {
|
|
540
|
+
highlightRects.clear();
|
|
541
|
+
if (arr == null) return;
|
|
542
|
+
for (int i = 0; i < arr.size(); i++) {
|
|
543
|
+
ReadableMap map = arr.getMap(i);
|
|
544
|
+
if (map == null || !map.hasKey("page") || !map.hasKey("rect")) continue;
|
|
545
|
+
int page = map.getInt("page");
|
|
546
|
+
String rectStr = map.getString("rect");
|
|
547
|
+
if (rectStr == null || rectStr.equals("{}")) continue;
|
|
548
|
+
String[] parts = rectStr.split(",");
|
|
549
|
+
if (parts.length != 4) continue;
|
|
550
|
+
try {
|
|
551
|
+
float left = Float.parseFloat(parts[0].trim());
|
|
552
|
+
float top = Float.parseFloat(parts[1].trim());
|
|
553
|
+
float right = Float.parseFloat(parts[2].trim());
|
|
554
|
+
float bottom = Float.parseFloat(parts[3].trim());
|
|
555
|
+
highlightRects.add(new HighlightRect(page, left, top, right, bottom));
|
|
556
|
+
} catch (NumberFormatException ignored) {}
|
|
557
|
+
}
|
|
558
|
+
invalidate();
|
|
559
|
+
postInvalidate();
|
|
560
|
+
}
|
|
561
|
+
|
|
455
562
|
// page start from 1
|
|
456
563
|
public void setPage(int page) {
|
|
457
564
|
int newPage = page>1?page:1;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry mapping pdfId to current PDF file path for programmatic search.
|
|
3
|
+
* PdfView registers when a document loads with pdfId; searchTextDirect looks up path by pdfId.
|
|
4
|
+
* Also stores PDF page sizes in points (per pdfId + pageIndex) for highlight coordinate scaling.
|
|
5
|
+
*/
|
|
6
|
+
package org.wonday.pdf;
|
|
7
|
+
|
|
8
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
9
|
+
|
|
10
|
+
public final class SearchRegistry {
|
|
11
|
+
private static final ConcurrentHashMap<String, String> pdfIdToPath = new ConcurrentHashMap<>();
|
|
12
|
+
/** Key: pdfId + "_" + pageIndex0Based, value: float[2] = { widthPt, heightPt } */
|
|
13
|
+
private static final ConcurrentHashMap<String, float[]> pdfIdPageSizePoints = new ConcurrentHashMap<>();
|
|
14
|
+
|
|
15
|
+
public static void registerPath(String pdfId, String path) {
|
|
16
|
+
if (pdfId != null && !pdfId.isEmpty() && path != null && !path.isEmpty()) {
|
|
17
|
+
pdfIdToPath.put(pdfId, path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static void unregisterPath(String pdfId) {
|
|
22
|
+
if (pdfId != null && !pdfId.isEmpty()) {
|
|
23
|
+
pdfIdToPath.remove(pdfId);
|
|
24
|
+
// Clear page sizes for this pdfId
|
|
25
|
+
pdfIdPageSizePoints.keySet().removeIf(k -> k != null && k.startsWith(pdfId + "_"));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static String getPath(String pdfId) {
|
|
30
|
+
return pdfId == null ? null : pdfIdToPath.get(pdfId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Register page size in PDF points (for highlight scaling). */
|
|
34
|
+
public static void registerPageSizePoints(String pdfId, int pageIndex0Based, float widthPt, float heightPt) {
|
|
35
|
+
if (pdfId != null && !pdfId.isEmpty() && widthPt > 0 && heightPt > 0) {
|
|
36
|
+
pdfIdPageSizePoints.put(pdfId + "_" + pageIndex0Based, new float[] { widthPt, heightPt });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Get page size in PDF points; returns float[2] = { widthPt, heightPt } or null. */
|
|
41
|
+
public static float[] getPageSizePoints(String pdfId, int pageIndex0Based) {
|
|
42
|
+
return pdfId == null ? null : pdfIdPageSizePoints.get(pdfId + "_" + pageIndex0Based);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -88,6 +88,17 @@ export interface PdfProps {
|
|
|
88
88
|
onPageSingleTap?: (page: number, x: number, y: number) => void,
|
|
89
89
|
onScaleChanged?: (scale: number) => void,
|
|
90
90
|
onPressLink?: (url: string) => void,
|
|
91
|
+
/**
|
|
92
|
+
* Optional. When set, use this id with searchTextDirect(pdfId, ...) for programmatic text search.
|
|
93
|
+
* On iOS, the same id must be passed to the Pdf view and to searchTextDirect. One view per pdfId.
|
|
94
|
+
*/
|
|
95
|
+
pdfId?: string,
|
|
96
|
+
/**
|
|
97
|
+
* Optional. Array of rects to highlight on the PDF (e.g. from searchTextDirect results).
|
|
98
|
+
* Each item: { page: number, rect: string } where rect is "left,top,right,bottom" in PDF page coordinates.
|
|
99
|
+
* Supported on Android; iOS can be added later.
|
|
100
|
+
*/
|
|
101
|
+
highlightRects?: Array<{ page: number; rect: string }>,
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
declare class Pdf extends React.Component<PdfProps, any> {
|
|
@@ -463,3 +474,26 @@ export interface PDFCompressorManager {
|
|
|
463
474
|
* PDFCompressor singleton instance
|
|
464
475
|
*/
|
|
465
476
|
export const PDFCompressor: PDFCompressorManager;
|
|
477
|
+
|
|
478
|
+
// ========================================
|
|
479
|
+
// JSI / Programmatic search
|
|
480
|
+
// ========================================
|
|
481
|
+
|
|
482
|
+
export interface PDFSearchResultItem {
|
|
483
|
+
page: number;
|
|
484
|
+
text: string;
|
|
485
|
+
/** Bounds of the match (format is platform-specific; on iOS a CGRect string). */
|
|
486
|
+
rect: string;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Search PDF text programmatically.
|
|
491
|
+
* On iOS: pass the same pdfId to the Pdf view (pdfId prop) so the view is registered for search.
|
|
492
|
+
* On Android: returns empty array until a native text-extraction implementation is added.
|
|
493
|
+
*/
|
|
494
|
+
export function searchTextDirect(
|
|
495
|
+
pdfId: string,
|
|
496
|
+
searchTerm: string,
|
|
497
|
+
startPage: number,
|
|
498
|
+
endPage: number
|
|
499
|
+
): Promise<PDFSearchResultItem[]>;
|
package/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
StyleSheet,
|
|
19
19
|
Image,
|
|
20
20
|
Text,
|
|
21
|
+
NativeModules,
|
|
21
22
|
requireNativeComponent
|
|
22
23
|
} from 'react-native';
|
|
23
24
|
// Codegen component variables - will be loaded lazily to prevent hooks errors
|
|
@@ -27,7 +28,7 @@ import ReactNativeBlobUtil from 'react-native-blob-util'
|
|
|
27
28
|
import {ViewPropTypes} from 'deprecated-react-native-prop-types';
|
|
28
29
|
const SHA1 = require('crypto-js/sha1');
|
|
29
30
|
import PdfView from './PdfView';
|
|
30
|
-
import PDFJSI from './src/PDFJSI';
|
|
31
|
+
import PDFJSI, { searchTextDirect } from './src/PDFJSI';
|
|
31
32
|
|
|
32
33
|
export default class Pdf extends Component {
|
|
33
34
|
|
|
@@ -67,6 +68,8 @@ export default class Pdf extends Component {
|
|
|
67
68
|
onPageSingleTap: PropTypes.func,
|
|
68
69
|
onScaleChanged: PropTypes.func,
|
|
69
70
|
onPressLink: PropTypes.func,
|
|
71
|
+
pdfId: PropTypes.string,
|
|
72
|
+
highlightRects: PropTypes.arrayOf(PropTypes.shape({ page: PropTypes.number.isRequired, rect: PropTypes.string.isRequired })),
|
|
70
73
|
|
|
71
74
|
// Props that are not available in the earlier react native version, added to prevent crashed on android
|
|
72
75
|
accessibilityLabel: PropTypes.string,
|
|
@@ -111,6 +114,8 @@ export default class Pdf extends Component {
|
|
|
111
114
|
},
|
|
112
115
|
onPressLink: (url) => {
|
|
113
116
|
},
|
|
117
|
+
pdfId: undefined,
|
|
118
|
+
highlightRects: undefined,
|
|
114
119
|
};
|
|
115
120
|
|
|
116
121
|
constructor(props) {
|
|
@@ -594,7 +599,24 @@ export default class Pdf extends Component {
|
|
|
594
599
|
if (!filePath || filePath.trim() === '') {
|
|
595
600
|
filePath = this.downloadedFilePath || this.state.path || '';
|
|
596
601
|
}
|
|
597
|
-
|
|
602
|
+
// Register path for search (iOS: ensures SearchRegistry has path when pdfId may not reach native view)
|
|
603
|
+
if (this.props.pdfId && filePath) {
|
|
604
|
+
const PDFJSIManager = NativeModules.PDFJSIManager;
|
|
605
|
+
if (PDFJSIManager && typeof PDFJSIManager.registerPathForSearch === 'function') {
|
|
606
|
+
if (__DEV__) {
|
|
607
|
+
console.log('📌 [Pdf] Registering path for search:', this.props.pdfId, 'pathLength:', filePath.length);
|
|
608
|
+
}
|
|
609
|
+
PDFJSIManager.registerPathForSearch(this.props.pdfId, filePath).then(() => {
|
|
610
|
+
if (__DEV__) console.log('✅ [Pdf] Path registered for search:', this.props.pdfId);
|
|
611
|
+
}).catch((err) => {
|
|
612
|
+
if (__DEV__) console.warn('⚠️ [Pdf] registerPathForSearch failed:', err);
|
|
613
|
+
});
|
|
614
|
+
} else if (__DEV__) {
|
|
615
|
+
console.warn('⚠️ [Pdf] PDFJSIManager.registerPathForSearch not available');
|
|
616
|
+
}
|
|
617
|
+
} else if (__DEV__) {
|
|
618
|
+
console.log('📌 [Pdf] Skip path registration: pdfId=', this.props.pdfId, 'hasPath=', !!filePath);
|
|
619
|
+
}
|
|
598
620
|
// Log path extraction for debugging
|
|
599
621
|
if (__DEV__) {
|
|
600
622
|
console.log('📁 [Pdf] loadComplete - Path extraction:', {
|
|
@@ -758,6 +780,11 @@ export {
|
|
|
758
780
|
CompressionLevel
|
|
759
781
|
};
|
|
760
782
|
|
|
783
|
+
// ========================================
|
|
784
|
+
// Programmatic search and JSI API (fix #24)
|
|
785
|
+
// ========================================
|
|
786
|
+
export { searchTextDirect, PDFJSI };
|
|
787
|
+
|
|
761
788
|
// ========================================
|
|
762
789
|
// TIER 3: Pre-built UI Components
|
|
763
790
|
// ========================================
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
#import "PDFJSIManager.h"
|
|
10
10
|
#import "PDFNativeCacheManager.h"
|
|
11
|
+
#import "SearchRegistry.h"
|
|
11
12
|
#import <React/RCTLog.h>
|
|
12
13
|
#import <React/RCTUtils.h>
|
|
13
14
|
#import <React/RCTBridge.h>
|
|
15
|
+
#import <PDFKit/PDFKit.h>
|
|
14
16
|
#import <dispatch/dispatch.h>
|
|
15
17
|
|
|
16
18
|
@implementation PDFJSIManager {
|
|
@@ -248,6 +250,21 @@ RCT_EXPORT_METHOD(optimizeMemory:(NSString *)pdfId
|
|
|
248
250
|
});
|
|
249
251
|
}
|
|
250
252
|
|
|
253
|
+
RCT_EXPORT_METHOD(registerPathForSearch:(NSString *)pdfId
|
|
254
|
+
path:(NSString *)path
|
|
255
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
256
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
257
|
+
{
|
|
258
|
+
if (pdfId.length && path.length) {
|
|
259
|
+
[SearchRegistry registerPath:pdfId path:path];
|
|
260
|
+
RCTLogInfo(@"✅ [SearchRegistry] Registered path for pdfId: %@ (path length %lu)", pdfId, (unsigned long)path.length);
|
|
261
|
+
resolve(@YES);
|
|
262
|
+
} else {
|
|
263
|
+
RCTLogWarn(@"⚠️ [SearchRegistry] registerPathForSearch skipped: pdfId length=%lu path length=%lu", (unsigned long)pdfId.length, (unsigned long)path.length);
|
|
264
|
+
resolve(@NO);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
251
268
|
RCT_EXPORT_METHOD(searchTextDirect:(NSString *)pdfId
|
|
252
269
|
searchTerm:(NSString *)searchTerm
|
|
253
270
|
startPage:(NSInteger)startPage
|
|
@@ -259,14 +276,82 @@ RCT_EXPORT_METHOD(searchTextDirect:(NSString *)pdfId
|
|
|
259
276
|
reject(@"JSI_NOT_INITIALIZED", @"JSI is not initialized", nil);
|
|
260
277
|
return;
|
|
261
278
|
}
|
|
279
|
+
if (!searchTerm || searchTerm.length == 0) {
|
|
280
|
+
resolve(@[]);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
262
283
|
|
|
263
284
|
dispatch_async(_backgroundQueue, ^{
|
|
264
285
|
@try {
|
|
265
286
|
RCTLogInfo(@"🔍 Searching text via JSI: '%@' in pages %ld-%ld", searchTerm, (long)startPage, (long)endPage);
|
|
266
287
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
288
|
+
NSString *path = [SearchRegistry pathForPdfId:pdfId];
|
|
289
|
+
if (!path || path.length == 0) {
|
|
290
|
+
RCTLogWarn(@"❌ [Search] No path registered for pdfId: %@ - ensure onLoadComplete ran and pdfId is set on Pdf", pdfId);
|
|
291
|
+
resolve(@[]);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
|
295
|
+
RCTLogWarn(@"❌ [Search] Path for pdfId %@ is a URI (not a local file path) - cannot open for search", pdfId);
|
|
296
|
+
resolve(@[]);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
RCTLogInfo(@"📂 [Search] Path for pdfId '%@': length %lu", pdfId, (unsigned long)path.length);
|
|
300
|
+
if ([path hasPrefix:@"file://"]) {
|
|
301
|
+
path = [path substringFromIndex:7];
|
|
302
|
+
}
|
|
303
|
+
BOOL readable = [[NSFileManager defaultManager] isReadableFileAtPath:path];
|
|
304
|
+
if (!readable) {
|
|
305
|
+
RCTLogWarn(@"❌ [Search] File not readable at path (length %lu)", (unsigned long)path.length);
|
|
306
|
+
resolve(@[]);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
NSURL *fileURL = [NSURL fileURLWithPath:path];
|
|
310
|
+
PDFDocument *doc = [[PDFDocument alloc] initWithURL:fileURL];
|
|
311
|
+
if (!doc || doc.pageCount == 0) {
|
|
312
|
+
RCTLogWarn(@"❌ [Search] PDFDocument init failed or empty: doc=%p pageCount=%lu", (__bridge void *)doc, (unsigned long)doc.pageCount);
|
|
313
|
+
resolve(@[]);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
NSInteger from = MAX(1, startPage);
|
|
318
|
+
NSInteger to = MIN((NSInteger)doc.pageCount, endPage);
|
|
319
|
+
NSMutableArray *out = [NSMutableArray array];
|
|
320
|
+
|
|
321
|
+
// findString:withOptions: returns selections; each can span multiple pages
|
|
322
|
+
NSArray<PDFSelection *> *selections = [doc findString:searchTerm withOptions:NSCaseInsensitiveSearch];
|
|
323
|
+
RCTLogInfo(@"📄 [Search] findString returned %lu selection(s) for '%@'", (unsigned long)selections.count, searchTerm);
|
|
324
|
+
for (PDFSelection *sel in selections) {
|
|
325
|
+
for (PDFPage *page in sel.pages) {
|
|
326
|
+
NSInteger pageIndex1Based = [doc indexForPage:page] + 1;
|
|
327
|
+
if (pageIndex1Based < from || pageIndex1Based > to) continue;
|
|
328
|
+
|
|
329
|
+
CGRect bounds = [sel boundsForPage:page];
|
|
330
|
+
// PDF page coords: origin bottom-left. Serialize as "left,top,right,bottom" (y-up: top > bottom)
|
|
331
|
+
CGFloat left = bounds.origin.x;
|
|
332
|
+
CGFloat bottom = bounds.origin.y;
|
|
333
|
+
CGFloat right = bounds.origin.x + bounds.size.width;
|
|
334
|
+
CGFloat top = bounds.origin.y + bounds.size.height;
|
|
335
|
+
NSString *rectStr = [NSString stringWithFormat:@"%g,%g,%g,%g", left, top, right, bottom];
|
|
336
|
+
|
|
337
|
+
[out addObject:@{
|
|
338
|
+
@"page": @(pageIndex1Based),
|
|
339
|
+
@"text": sel.string ?: @"",
|
|
340
|
+
@"rect": rectStr
|
|
341
|
+
}];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Register page sizes in points for highlight scaling (use first page of range if we have selections)
|
|
346
|
+
for (NSInteger idx = from; idx <= to; idx++) {
|
|
347
|
+
PDFPage *page = [doc pageAtIndex:(NSUInteger)(idx - 1)];
|
|
348
|
+
if (page) {
|
|
349
|
+
CGRect box = [page boundsForBox:kPDFDisplayBoxMediaBox];
|
|
350
|
+
[SearchRegistry registerPageSizePointsForPdfId:pdfId pageIndex0Based:(idx - 1) widthPt:box.size.width heightPt:box.size.height];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
resolve([out copy]);
|
|
270
355
|
|
|
271
356
|
} @catch (NSException *exception) {
|
|
272
357
|
RCTLogError(@"❌ Error searching text via JSI: %@", exception.reason);
|
|
@@ -580,7 +665,7 @@ RCT_EXPORT_METHOD(testNativeCache:(RCTPromiseResolveBlock)resolve
|
|
|
580
665
|
|
|
581
666
|
// iOS doesn't have the same 16KB page size requirements as Android
|
|
582
667
|
// but we still check for compatibility
|
|
583
|
-
|
|
668
|
+
(void)[self checkiOS16KBSupport];
|
|
584
669
|
|
|
585
670
|
NSDictionary *result = @{
|
|
586
671
|
@"supported": @YES, // iOS is generally compatible
|
|
@@ -68,6 +68,8 @@ UIView
|
|
|
68
68
|
@property(nonatomic) int spacing;
|
|
69
69
|
@property(nonatomic, strong) NSString *password;
|
|
70
70
|
@property(nonatomic) BOOL singlePage;
|
|
71
|
+
@property(nonatomic, strong) NSString *pdfId;
|
|
72
|
+
@property(nonatomic, copy) NSArray *highlightRects;
|
|
71
73
|
|
|
72
74
|
@property(nonatomic, copy) RCTBubblingEventBlock onChange;
|
|
73
75
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#import "RNPDFPdfView.h"
|
|
10
|
+
#import "SearchRegistry.h"
|
|
10
11
|
|
|
11
12
|
#import <Foundation/Foundation.h>
|
|
12
13
|
#import <QuartzCore/QuartzCore.h>
|
|
@@ -55,6 +56,37 @@ const float MAX_SCALE = 3.0f;
|
|
|
55
56
|
const float MIN_SCALE = 1.0f;
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
/** Overlay that draws highlight rects on top of the PDF view. */
|
|
60
|
+
@interface HighlightOverlayView : UIView
|
|
61
|
+
@property (nonatomic, weak) PDFView *pdfView;
|
|
62
|
+
@property (nonatomic, copy) NSArray<NSDictionary *> *highlightRects;
|
|
63
|
+
@end
|
|
64
|
+
|
|
65
|
+
@implementation HighlightOverlayView
|
|
66
|
+
- (void)drawRect:(CGRect)rect {
|
|
67
|
+
PDFView *pv = self.pdfView;
|
|
68
|
+
NSArray *items = self.highlightRects;
|
|
69
|
+
if (!pv || !pv.document || !items.count) return;
|
|
70
|
+
PDFDocument *doc = pv.document;
|
|
71
|
+
[[UIColor colorWithRed:1 green:1 blue:0 alpha:0.35] setFill];
|
|
72
|
+
for (NSDictionary *item in items) {
|
|
73
|
+
NSNumber *pageNum = item[@"page"];
|
|
74
|
+
NSString *rectStr = item[@"rect"];
|
|
75
|
+
if (!pageNum || !rectStr.length) continue;
|
|
76
|
+
int page1 = pageNum.intValue;
|
|
77
|
+
if (page1 < 1) continue;
|
|
78
|
+
PDFPage *page = [doc pageAtIndex:(NSUInteger)(page1 - 1)];
|
|
79
|
+
if (!page) continue;
|
|
80
|
+
NSArray<NSString *> *parts = [rectStr componentsSeparatedByString:@","];
|
|
81
|
+
if (parts.count != 4) continue;
|
|
82
|
+
CGFloat left = parts[0].doubleValue, top = parts[1].doubleValue, right = parts[2].doubleValue, bottom = parts[3].doubleValue;
|
|
83
|
+
CGRect pageRect = CGRectMake(left, bottom, right - left, top - bottom);
|
|
84
|
+
CGRect viewRect = [pv convertRect:pageRect fromPage:page];
|
|
85
|
+
CGContextFillRect(UIGraphicsGetCurrentContext(), viewRect);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
@end
|
|
89
|
+
|
|
58
90
|
@interface RNPDFScrollViewDelegateProxy : NSObject <UIScrollViewDelegate>
|
|
59
91
|
- (instancetype)initWithPrimary:(id<UIScrollViewDelegate>)primary secondary:(id<UIScrollViewDelegate>)secondary;
|
|
60
92
|
@end
|
|
@@ -181,6 +213,13 @@ const float MIN_SCALE = 1.0f;
|
|
|
181
213
|
// Track usePageViewController state to prevent unnecessary reconfiguration
|
|
182
214
|
BOOL _currentUsePageViewController;
|
|
183
215
|
BOOL _usePageViewControllerStateInitialized;
|
|
216
|
+
|
|
217
|
+
// Search and highlight (iOS parity with Android)
|
|
218
|
+
NSString *_pdfId;
|
|
219
|
+
NSArray *_highlightRects;
|
|
220
|
+
HighlightOverlayView *_highlightOverlay;
|
|
221
|
+
/// Local file path when document loaded (used for SearchRegistry; may differ from _path which can be URI)
|
|
222
|
+
NSString *_lastLoadedPath;
|
|
184
223
|
}
|
|
185
224
|
|
|
186
225
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
@@ -315,6 +354,30 @@ using namespace facebook::react;
|
|
|
315
354
|
_scrollEnabled = newProps.scrollEnabled;
|
|
316
355
|
[updatedPropNames addObject:@"scrollEnabled"];
|
|
317
356
|
}
|
|
357
|
+
NSString *newPdfId = RCTNSStringFromStringNilIfEmpty(newProps.pdfId);
|
|
358
|
+
if (_pdfId != newPdfId && ![newPdfId isEqualToString:_pdfId]) {
|
|
359
|
+
if (_pdfId.length) [SearchRegistry unregisterPath:_pdfId];
|
|
360
|
+
_pdfId = [newPdfId copy];
|
|
361
|
+
[updatedPropNames addObject:@"pdfId"];
|
|
362
|
+
// Only register local file paths; never register URIs - onDocumentChanged will register when we have local path
|
|
363
|
+
NSString *pathToRegister = nil;
|
|
364
|
+
if (_lastLoadedPath.length > 0) {
|
|
365
|
+
pathToRegister = _lastLoadedPath;
|
|
366
|
+
} else if (_path.length > 0 && [_path hasPrefix:@"/"]) {
|
|
367
|
+
pathToRegister = _path;
|
|
368
|
+
}
|
|
369
|
+
if (_pdfId.length && pathToRegister.length > 0) {
|
|
370
|
+
[SearchRegistry registerPath:_pdfId path:pathToRegister];
|
|
371
|
+
RCTLogInfo(@"✅ [iOS] SearchRegistry registered path for pdfId: %@ (from updateProps)", _pdfId);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Convert codegen vector of {page, rect} to NSArray for setHighlightRects
|
|
375
|
+
NSMutableArray *newHighlightRects = [NSMutableArray array];
|
|
376
|
+
for (const auto &item : newProps.highlightRects) {
|
|
377
|
+
[newHighlightRects addObject:@{ @"page": @(item.page), @"rect": [NSString stringWithUTF8String:item.rect.c_str()] }];
|
|
378
|
+
}
|
|
379
|
+
[self setHighlightRects:[newHighlightRects copy]];
|
|
380
|
+
[updatedPropNames addObject:@"highlightRects"];
|
|
318
381
|
|
|
319
382
|
[super updateProps:props oldProps:oldProps];
|
|
320
383
|
[self didSetProps:updatedPropNames];
|
|
@@ -329,10 +392,11 @@ using namespace facebook::react;
|
|
|
329
392
|
- (void)prepareForRecycle
|
|
330
393
|
{
|
|
331
394
|
[super prepareForRecycle];
|
|
332
|
-
|
|
395
|
+
if (_pdfId.length) [SearchRegistry unregisterPath:_pdfId];
|
|
333
396
|
[_pdfView removeFromSuperview];
|
|
334
397
|
_pdfDocument = Nil;
|
|
335
398
|
_pdfView = Nil;
|
|
399
|
+
_highlightOverlay = Nil;
|
|
336
400
|
//Remove notifications
|
|
337
401
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"PDFViewDocumentChangedNotification" object:nil];
|
|
338
402
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"PDFViewPageChangedNotification" object:nil];
|
|
@@ -442,6 +506,11 @@ using namespace facebook::react;
|
|
|
442
506
|
_preloadQueue.maxConcurrentOperationCount = 3;
|
|
443
507
|
_preloadQueue.qualityOfService = NSQualityOfServiceBackground;
|
|
444
508
|
|
|
509
|
+
_pdfId = nil;
|
|
510
|
+
_highlightRects = nil;
|
|
511
|
+
_lastLoadedPath = nil;
|
|
512
|
+
_highlightOverlay = nil;
|
|
513
|
+
|
|
445
514
|
// init and config PDFView
|
|
446
515
|
_pdfView = [[PDFView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
|
|
447
516
|
_pdfView.displayMode = kPDFDisplaySinglePageContinuous;
|
|
@@ -997,10 +1066,58 @@ using namespace facebook::react;
|
|
|
997
1066
|
RCTLogInfo(@"🔍 [iOS] loadComplete message: %@", message);
|
|
998
1067
|
|
|
999
1068
|
[self notifyOnChangeWithMessage:message];
|
|
1069
|
+
|
|
1070
|
+
// Store local path so we can register when pdfId is set (Fabric may set pdfId after document load)
|
|
1071
|
+
_lastLoadedPath = [pathValue copy];
|
|
1072
|
+
// Register path for searchTextDirect (iOS parity with Android)
|
|
1073
|
+
if (_pdfId.length && pathValue.length) {
|
|
1074
|
+
[SearchRegistry registerPath:_pdfId path:pathValue];
|
|
1075
|
+
RCTLogInfo(@"✅ [iOS] SearchRegistry registered path for pdfId: %@ (from onDocumentChanged)", _pdfId);
|
|
1076
|
+
}
|
|
1000
1077
|
}
|
|
1001
1078
|
|
|
1002
1079
|
}
|
|
1003
1080
|
|
|
1081
|
+
- (void)setPdfId:(NSString *)pdfId {
|
|
1082
|
+
if (_pdfId.length && ![pdfId isEqualToString:_pdfId]) {
|
|
1083
|
+
[SearchRegistry unregisterPath:_pdfId];
|
|
1084
|
+
}
|
|
1085
|
+
_pdfId = [pdfId copy];
|
|
1086
|
+
// If document already loaded, register path now (Fabric may set pdfId after path/document load).
|
|
1087
|
+
// Only register local file paths; never register URIs (http/https) - PDFDocument needs file path.
|
|
1088
|
+
NSString *pathToRegister = nil;
|
|
1089
|
+
if (_lastLoadedPath.length > 0) {
|
|
1090
|
+
pathToRegister = _lastLoadedPath;
|
|
1091
|
+
} else if (_path.length > 0 && [_path hasPrefix:@"/"]) {
|
|
1092
|
+
pathToRegister = _path;
|
|
1093
|
+
}
|
|
1094
|
+
if (_pdfId.length && pathToRegister.length > 0) {
|
|
1095
|
+
[SearchRegistry registerPath:_pdfId path:pathToRegister];
|
|
1096
|
+
RCTLogInfo(@"✅ [iOS] SearchRegistry registered path for pdfId: %@ (from setPdfId)", _pdfId);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
- (void)setHighlightRects:(NSArray *)highlightRects {
|
|
1101
|
+
_highlightRects = [highlightRects copy];
|
|
1102
|
+
if (!_pdfView) return;
|
|
1103
|
+
if (_highlightRects.count > 0) {
|
|
1104
|
+
if (!_highlightOverlay) {
|
|
1105
|
+
_highlightOverlay = [[HighlightOverlayView alloc] initWithFrame:_pdfView.bounds];
|
|
1106
|
+
_highlightOverlay.backgroundColor = [UIColor clearColor];
|
|
1107
|
+
_highlightOverlay.userInteractionEnabled = NO;
|
|
1108
|
+
_highlightOverlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
1109
|
+
_highlightOverlay.pdfView = _pdfView;
|
|
1110
|
+
[_pdfView addSubview:_highlightOverlay];
|
|
1111
|
+
[_pdfView bringSubviewToFront:_highlightOverlay];
|
|
1112
|
+
}
|
|
1113
|
+
_highlightOverlay.highlightRects = _highlightRects;
|
|
1114
|
+
[_highlightOverlay setNeedsDisplay];
|
|
1115
|
+
} else if (_highlightOverlay) {
|
|
1116
|
+
_highlightOverlay.highlightRects = @[];
|
|
1117
|
+
[_highlightOverlay setNeedsDisplay];
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1004
1121
|
-(NSString *) getTableContents
|
|
1005
1122
|
{
|
|
1006
1123
|
|
|
@@ -1118,6 +1235,7 @@ using namespace facebook::react;
|
|
|
1118
1235
|
|
|
1119
1236
|
RLog(@"Enhanced PDF: Navigated to page %d", _page);
|
|
1120
1237
|
[self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"pageChanged|%lu|%lu", page+1, numberOfPages]]];
|
|
1238
|
+
if (_highlightOverlay) [_highlightOverlay setNeedsDisplay];
|
|
1121
1239
|
}
|
|
1122
1240
|
|
|
1123
1241
|
}
|
|
@@ -1132,6 +1250,7 @@ using namespace facebook::react;
|
|
|
1132
1250
|
[self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"scaleChanged|%f", _scale]]];
|
|
1133
1251
|
}
|
|
1134
1252
|
}
|
|
1253
|
+
if (_highlightOverlay) [_highlightOverlay setNeedsDisplay];
|
|
1135
1254
|
}
|
|
1136
1255
|
|
|
1137
1256
|
#pragma mark gesture process
|
|
@@ -1435,9 +1554,11 @@ using namespace facebook::react;
|
|
|
1435
1554
|
#pragma mark - UIScrollViewDelegate
|
|
1436
1555
|
|
|
1437
1556
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
|
1557
|
+
// Redraw highlight overlay so rects stay aligned when user scrolls (pan)
|
|
1558
|
+
if (_highlightOverlay) [_highlightOverlay setNeedsDisplay];
|
|
1438
1559
|
static int scrollEventCount = 0;
|
|
1439
1560
|
scrollEventCount++;
|
|
1440
|
-
|
|
1561
|
+
|
|
1441
1562
|
// Log scroll events periodically (every 10th event to avoid spam)
|
|
1442
1563
|
if (scrollEventCount % 10 == 0) {
|
|
1443
1564
|
RCTLogInfo(@"📜 [iOS Scroll] scrollViewDidScroll #%d - offset=(%.2f, %.2f), contentSize=(%.2f, %.2f), bounds=(%.2f, %.2f), scrollEnabled=%d",
|
|
@@ -1507,7 +1628,8 @@ using namespace facebook::react;
|
|
|
1507
1628
|
#pragma mark - UIScrollViewDelegate Zoom Support
|
|
1508
1629
|
|
|
1509
1630
|
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
|
1510
|
-
// Called during pinch-to-zoom
|
|
1631
|
+
// Called during pinch-to-zoom — redraw highlight overlay so rects stay aligned with zoomed content
|
|
1632
|
+
if (_highlightOverlay) [_highlightOverlay setNeedsDisplay];
|
|
1511
1633
|
if (_fixScaleFactor > 0 && _pdfView.scaleFactor > 0) {
|
|
1512
1634
|
float newScale = _pdfView.scaleFactor / _fixScaleFactor;
|
|
1513
1635
|
|
|
@@ -1551,7 +1673,8 @@ using namespace facebook::react;
|
|
|
1551
1673
|
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
|
|
1552
1674
|
withView:(UIView *)view
|
|
1553
1675
|
atScale:(CGFloat)scale {
|
|
1554
|
-
//
|
|
1676
|
+
// Redraw highlight overlay so rects match final zoom level
|
|
1677
|
+
if (_highlightOverlay) [_highlightOverlay setNeedsDisplay];
|
|
1555
1678
|
RCTLogInfo(@"🔍 [iOS Zoom] Did end zooming at scale %f", scale);
|
|
1556
1679
|
}
|
|
1557
1680
|
|
|
@@ -52,6 +52,8 @@ RCT_EXPORT_VIEW_PROPERTY(spacing, int);
|
|
|
52
52
|
RCT_EXPORT_VIEW_PROPERTY(password, NSString);
|
|
53
53
|
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock);
|
|
54
54
|
RCT_EXPORT_VIEW_PROPERTY(singlePage, BOOL);
|
|
55
|
+
RCT_EXPORT_VIEW_PROPERTY(pdfId, NSString);
|
|
56
|
+
RCT_EXPORT_VIEW_PROPERTY(highlightRects, NSArray);
|
|
55
57
|
|
|
56
58
|
RCT_EXPORT_METHOD(supportPDFKit:(RCTResponseSenderBlock)callback)
|
|
57
59
|
{
|
|
@@ -177,12 +179,14 @@ RCT_EXPORT_METHOD(optimizeMemory:(NSString *)pdfId
|
|
|
177
179
|
|
|
178
180
|
RCT_EXPORT_METHOD(searchTextDirect:(NSString *)pdfId
|
|
179
181
|
searchTerm:(NSString *)searchTerm
|
|
182
|
+
startPage:(NSInteger)startPage
|
|
183
|
+
endPage:(NSInteger)endPage
|
|
180
184
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
181
185
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
182
186
|
{
|
|
183
187
|
PDFJSIManager *jsiManager = [self.bridge moduleForClass:[PDFJSIManager class]];
|
|
184
188
|
if (jsiManager) {
|
|
185
|
-
[jsiManager searchTextDirect:pdfId searchTerm:searchTerm startPage:
|
|
189
|
+
[jsiManager searchTextDirect:pdfId searchTerm:searchTerm startPage:startPage endPage:endPage resolver:resolve rejecter:reject];
|
|
186
190
|
} else {
|
|
187
191
|
reject(@"JSI_NOT_AVAILABLE", @"PDFJSIManager not available", nil);
|
|
188
192
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry mapping pdfId to current PDF file path for programmatic search.
|
|
3
|
+
* RNPDFPdfView registers when a document loads with pdfId; searchTextDirect looks up path by pdfId.
|
|
4
|
+
* Also stores PDF page sizes in points (per pdfId + pageIndex) for highlight coordinate scaling.
|
|
5
|
+
*/
|
|
6
|
+
#import <Foundation/Foundation.h>
|
|
7
|
+
|
|
8
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
9
|
+
|
|
10
|
+
@interface SearchRegistry : NSObject
|
|
11
|
+
|
|
12
|
+
+ (void)registerPath:(NSString *)pdfId path:(NSString *)path;
|
|
13
|
+
+ (void)unregisterPath:(NSString *)pdfId;
|
|
14
|
+
+ (nullable NSString *)pathForPdfId:(NSString *)pdfId;
|
|
15
|
+
|
|
16
|
+
+ (void)registerPageSizePointsForPdfId:(NSString *)pdfId pageIndex0Based:(NSInteger)pageIndex widthPt:(CGFloat)widthPt heightPt:(CGFloat)heightPt;
|
|
17
|
+
+ (void)getPageSizePointsForPdfId:(NSString *)pdfId pageIndex0Based:(NSInteger)pageIndex widthOut:(CGFloat *)widthOut heightOut:(CGFloat *)heightOut;
|
|
18
|
+
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry mapping pdfId to current PDF file path for programmatic search.
|
|
3
|
+
*/
|
|
4
|
+
#import "SearchRegistry.h"
|
|
5
|
+
|
|
6
|
+
@implementation SearchRegistry
|
|
7
|
+
|
|
8
|
+
static NSMutableDictionary<NSString *, NSString *> *_pathByPdfId;
|
|
9
|
+
static NSMutableDictionary<NSString *, NSValue *> *_pageSizeByKey; // key = "pdfId_pageIndex", value = NSValue with CGSize
|
|
10
|
+
static dispatch_queue_t _queue;
|
|
11
|
+
|
|
12
|
+
+ (void)initialize {
|
|
13
|
+
if (self == [SearchRegistry class]) {
|
|
14
|
+
_pathByPdfId = [NSMutableDictionary new];
|
|
15
|
+
_pageSizeByKey = [NSMutableDictionary new];
|
|
16
|
+
_queue = dispatch_queue_create("com.rnpdf.searchregistry", DISPATCH_QUEUE_SERIAL);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
+ (void)registerPath:(NSString *)pdfId path:(NSString *)path {
|
|
21
|
+
if (!pdfId.length || !path.length) return;
|
|
22
|
+
dispatch_sync(_queue, ^{
|
|
23
|
+
_pathByPdfId[pdfId] = path;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
+ (void)unregisterPath:(NSString *)pdfId {
|
|
28
|
+
if (!pdfId.length) return;
|
|
29
|
+
dispatch_sync(_queue, ^{
|
|
30
|
+
[_pathByPdfId removeObjectForKey:pdfId];
|
|
31
|
+
NSString *prefix = [pdfId stringByAppendingString:@"_"];
|
|
32
|
+
NSArray *keysToRemove = [_pageSizeByKey.allKeys filteredArrayUsingPredicate:
|
|
33
|
+
[NSPredicate predicateWithBlock:^BOOL(NSString *key, id _) { return [key hasPrefix:prefix]; }]];
|
|
34
|
+
[_pageSizeByKey removeObjectsForKeys:keysToRemove];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
+ (NSString *)pathForPdfId:(NSString *)pdfId {
|
|
39
|
+
if (!pdfId.length) return nil;
|
|
40
|
+
__block NSString *path = nil;
|
|
41
|
+
dispatch_sync(_queue, ^{
|
|
42
|
+
path = _pathByPdfId[pdfId];
|
|
43
|
+
});
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
+ (void)registerPageSizePointsForPdfId:(NSString *)pdfId pageIndex0Based:(NSInteger)pageIndex widthPt:(CGFloat)widthPt heightPt:(CGFloat)heightPt {
|
|
48
|
+
if (!pdfId.length || widthPt <= 0 || heightPt <= 0) return;
|
|
49
|
+
NSString *key = [NSString stringWithFormat:@"%@_%ld", pdfId, (long)pageIndex];
|
|
50
|
+
dispatch_sync(_queue, ^{
|
|
51
|
+
_pageSizeByKey[key] = [NSValue valueWithCGSize:CGSizeMake(widthPt, heightPt)];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
+ (void)getPageSizePointsForPdfId:(NSString *)pdfId pageIndex0Based:(NSInteger)pageIndex widthOut:(CGFloat *)widthOut heightOut:(CGFloat *)heightOut {
|
|
56
|
+
if (!pdfId.length || !widthOut || !heightOut) return;
|
|
57
|
+
*widthOut = 0;
|
|
58
|
+
*heightOut = 0;
|
|
59
|
+
NSString *key = [NSString stringWithFormat:@"%@_%ld", pdfId, (long)pageIndex];
|
|
60
|
+
__block NSValue *val = nil;
|
|
61
|
+
dispatch_sync(_queue, ^{
|
|
62
|
+
val = _pageSizeByKey[key];
|
|
63
|
+
});
|
|
64
|
+
if (val) {
|
|
65
|
+
CGSize s = [val CGSizeValue];
|
|
66
|
+
*widthOut = s.width;
|
|
67
|
+
*heightOut = s.height;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-pdf-jsi",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.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",
|
|
@@ -80,7 +80,9 @@
|
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build:plugin": "tsc -p plugin/tsconfig.json",
|
|
83
|
-
"prepublishOnly": "npm run build:plugin"
|
|
83
|
+
"prepublishOnly": "npm run build:plugin",
|
|
84
|
+
"clean:for-publish": "node -e \"const fs=require('fs'),path=require('path');['android/.cxx','android/.gradle','android/build'].forEach(p=>{try{fs.rmSync(path.join(__dirname,p),{recursive:true});console.log('Removed',p);}catch(e){}})\"",
|
|
85
|
+
"prepack": "npm run clean:for-publish"
|
|
84
86
|
},
|
|
85
87
|
"peerDependencies": {
|
|
86
88
|
"@react-native-async-storage/async-storage": ">=1.17.0",
|
package/src/PDFJSI.js
CHANGED
|
@@ -392,7 +392,7 @@ class PDFJSIManager {
|
|
|
392
392
|
if (Platform.OS === 'android') {
|
|
393
393
|
results = await PDFJSIManagerNative.searchTextDirect(pdfId, searchTerm, startPage, endPage);
|
|
394
394
|
} else if (Platform.OS === 'ios') {
|
|
395
|
-
results = await RNPDFPdfViewManager.searchTextDirect(pdfId, searchTerm);
|
|
395
|
+
results = await RNPDFPdfViewManager.searchTextDirect(pdfId, searchTerm, startPage, endPage);
|
|
396
396
|
} else {
|
|
397
397
|
throw new Error(`Platform ${Platform.OS} not supported`);
|
|
398
398
|
}
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|