@papyrus-sdk/engine-native 0.1.1 → 0.1.3

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.
Files changed (26) hide show
  1. package/LICENSE +21 -0
  2. package/android/build.gradle +40 -40
  3. package/android/src/main/AndroidManifest.xml +3 -3
  4. package/android/src/main/java/com/papyrus/engine/PapyrusEngineStore.java +71 -71
  5. package/android/src/main/java/com/papyrus/engine/PapyrusNativeEngineModule.java +533 -509
  6. package/android/src/main/java/com/papyrus/engine/PapyrusNativeEngineModule.kt +561 -0
  7. package/android/src/main/java/com/papyrus/engine/PapyrusOutline.java +20 -20
  8. package/android/src/main/java/com/papyrus/engine/PapyrusOutlineItem.java +13 -13
  9. package/android/src/main/java/com/papyrus/engine/PapyrusPackage.java +24 -24
  10. package/android/src/main/java/com/papyrus/engine/PapyrusPageView.java +86 -86
  11. package/android/src/main/java/com/papyrus/engine/PapyrusPageViewManager.java +16 -16
  12. package/android/src/main/java/com/papyrus/engine/PapyrusPageViewModule.kt +12 -0
  13. package/android/src/main/java/com/papyrus/engine/PapyrusTextHit.java +15 -15
  14. package/android/src/main/java/com/papyrus/engine/PapyrusTextSearch.java +20 -20
  15. package/android/src/main/java/com/papyrus/engine/PapyrusTextSelect.java +20 -20
  16. package/android/src/main/java/com/papyrus/engine/PapyrusTextSelection.java +11 -11
  17. package/dist/index.d.mts +8 -7
  18. package/dist/index.d.ts +8 -7
  19. package/dist/index.js +213 -8
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +214 -9
  22. package/dist/index.mjs.map +1 -1
  23. package/ios/PapyrusNativeEngine.podspec +1 -1
  24. package/ios/PapyrusPageViewManager.m +19 -19
  25. package/package.json +30 -30
  26. package/react-native.config.js +10 -10
@@ -1,499 +1,512 @@
1
- package com.papyrus.engine;
2
-
3
- import android.content.ContentResolver;
4
- import android.content.Context;
5
- import android.net.Uri;
6
- import android.os.ParcelFileDescriptor;
7
- import android.view.View;
8
-
9
- import com.facebook.react.bridge.Arguments;
10
- import com.facebook.react.bridge.Promise;
11
- import com.facebook.react.bridge.ReactApplicationContext;
12
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
13
- import com.facebook.react.bridge.ReactMethod;
14
- import com.facebook.react.bridge.ReadableArray;
15
- import com.facebook.react.bridge.ReadableMap;
16
- import com.facebook.react.bridge.WritableArray;
17
- import com.facebook.react.bridge.WritableMap;
18
- import com.facebook.react.bridge.ReadableType;
19
- import com.facebook.react.uimanager.UIBlock;
20
- import com.facebook.react.uimanager.UIManagerModule;
21
-
22
- import com.shockwave.pdfium.PdfDocument;
23
-
24
- import java.io.File;
25
- import java.io.FileOutputStream;
26
- import java.io.IOException;
27
- import java.io.InputStream;
28
- import java.net.HttpURLConnection;
29
- import java.net.URL;
30
- import java.util.concurrent.ExecutorService;
31
- import java.util.concurrent.Executors;
32
-
33
- public class PapyrusNativeEngineModule extends ReactContextBaseJavaModule {
34
- private final ReactApplicationContext reactContext;
35
- private final ExecutorService executor = Executors.newSingleThreadExecutor();
36
-
37
- public PapyrusNativeEngineModule(ReactApplicationContext reactContext) {
38
- super(reactContext);
39
- this.reactContext = reactContext;
40
- }
41
-
42
- @Override
43
- public String getName() {
44
- return "PapyrusNativeEngine";
45
- }
46
-
47
- @ReactMethod(isBlockingSynchronousMethod = true)
48
- public String createEngine() {
49
- return PapyrusEngineStore.createEngine(reactContext);
50
- }
51
-
52
- @ReactMethod
53
- public void destroyEngine(String engineId) {
54
- PapyrusEngineStore.destroyEngine(engineId);
55
- }
56
-
57
- @ReactMethod
58
- public void load(final String engineId, final ReadableMap source, final Promise promise) {
59
- executor.execute(() -> {
60
- try {
61
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
62
- if (state == null) {
63
- promise.reject("papyrus_no_engine", "Engine not found");
64
- return;
65
- }
66
-
67
- File file = materializeSource(source, reactContext);
68
- if (file == null) {
69
- promise.reject("papyrus_invalid_source", "Unsupported PDF source");
70
- return;
71
- }
72
-
73
- ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
74
- PdfDocument document = state.pdfium.newDocument(fd);
75
- PapyrusEngineStore.setDocument(state, document, fd, file.getAbsolutePath());
76
-
77
- int pageCount = state.pdfium.getPageCount(document);
78
- WritableMap result = Arguments.createMap();
79
- result.putInt("pageCount", pageCount);
80
- promise.resolve(result);
81
- } catch (Throwable error) {
82
- promise.reject("papyrus_load_failed", error);
83
- }
84
- });
85
- }
86
-
87
- @ReactMethod(isBlockingSynchronousMethod = true)
88
- public int getPageCount(String engineId) {
89
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
90
- if (state == null || state.document == null) return 0;
91
- return state.pdfium.getPageCount(state.document);
92
- }
93
-
94
- @ReactMethod
95
- public void renderPage(final String engineId, final int pageIndex, final int target, final float scale, final float zoom, final int rotation) {
96
- final PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
97
- if (state == null) return;
98
- UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
99
- if (uiManager == null) return;
100
-
101
- uiManager.addUIBlock(new UIBlock() {
102
- @Override
103
- public void execute(com.facebook.react.uimanager.NativeViewHierarchyManager nativeViewHierarchyManager) {
104
- View view = nativeViewHierarchyManager.resolveView(target);
105
- if (view instanceof PapyrusPageView) {
106
- ((PapyrusPageView) view).render(state, pageIndex, scale, zoom, rotation);
107
- }
108
- }
109
- });
110
- }
111
-
112
- @ReactMethod
113
- public void renderTextLayer(String engineId, int pageIndex, int target, float scale, float zoom, int rotation) {
114
- }
115
-
116
- @ReactMethod
117
- public void getTextContent(String engineId, int pageIndex, Promise promise) {
118
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
119
- if (state == null || state.document == null) {
120
- promise.resolve(Arguments.createArray());
121
- return;
122
- }
123
- String text;
124
- synchronized (state.pdfiumLock) {
125
- text = extractPageText(state, pageIndex);
126
- }
127
- WritableArray items = Arguments.createArray();
128
- if (text != null && !text.isEmpty()) {
129
- WritableMap item = Arguments.createMap();
130
- item.putString("str", text);
131
- item.putString("dir", "ltr");
132
- item.putDouble("width", 0);
133
- item.putDouble("height", 0);
134
- WritableArray transform = Arguments.createArray();
135
- transform.pushDouble(1);
136
- transform.pushDouble(0);
137
- transform.pushDouble(0);
138
- transform.pushDouble(1);
139
- transform.pushDouble(0);
140
- transform.pushDouble(0);
141
- item.putArray("transform", transform);
142
- item.putString("fontName", "");
143
- items.pushMap(item);
144
- }
145
- promise.resolve(items);
146
- }
147
-
148
- @ReactMethod
149
- public void getPageDimensions(String engineId, int pageIndex, Promise promise) {
150
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
151
- if (state == null || state.document == null) {
152
- WritableMap result = Arguments.createMap();
153
- result.putInt("width", 0);
154
- result.putInt("height", 0);
155
- promise.resolve(result);
156
- return;
157
- }
158
- int width;
159
- int height;
160
- synchronized (state.pdfiumLock) {
161
- width = state.pdfium.getPageWidthPoint(state.document, pageIndex);
162
- height = state.pdfium.getPageHeightPoint(state.document, pageIndex);
163
- }
164
- WritableMap result = Arguments.createMap();
165
- result.putInt("width", width);
166
- result.putInt("height", height);
167
- promise.resolve(result);
168
- }
169
-
170
- @ReactMethod
171
- public void getOutline(String engineId, Promise promise) {
172
- executor.execute(() -> {
173
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
174
- if (state == null || state.document == null) {
175
- promise.resolve(Arguments.createArray());
176
- return;
177
- }
178
-
179
- PapyrusOutlineItem[] items = null;
180
- try {
181
- if (PapyrusOutline.AVAILABLE) {
182
- if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
183
- synchronized (state.pdfiumLock) {
184
- items = PapyrusOutline.nativeGetOutlineFile(state.sourcePath);
185
- }
186
- } else {
187
- long docPtr;
188
- synchronized (state.pdfiumLock) {
189
- docPtr = extractNativeDocPointer(state.document);
190
- }
191
- if (docPtr != 0) {
192
- synchronized (state.pdfiumLock) {
193
- items = PapyrusOutline.nativeGetOutline(docPtr);
194
- }
195
- }
196
- }
197
- }
198
- } catch (Throwable ignored) {
199
- items = null;
200
- }
201
-
202
- WritableArray result = Arguments.createArray();
203
- if (items != null) {
204
- for (PapyrusOutlineItem item : items) {
205
- result.pushMap(serializeOutlineItem(item));
206
- }
207
- }
208
- promise.resolve(result);
209
- });
210
- }
211
-
212
- @ReactMethod
213
- public void getPageIndex(String engineId, Object dest, Promise promise) {
214
- promise.resolve(null);
215
- }
216
-
217
- @ReactMethod
218
- public void searchText(String engineId, String query, Promise promise) {
219
- executor.execute(() -> {
220
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
221
- if (state == null || state.document == null || query == null || query.length() < 2) {
222
- promise.resolve(Arguments.createArray());
223
- return;
224
- }
225
-
226
- int pageCount = state.pdfium.getPageCount(state.document);
227
- state.isSearching = true;
228
- try {
229
- try {
230
- if (PapyrusTextSearch.AVAILABLE) {
231
- PapyrusTextHit[] hits = null;
232
- if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
233
- synchronized (state.pdfiumLock) {
234
- hits = PapyrusTextSearch.nativeSearchFile(state.sourcePath, query);
235
- }
236
- } else {
237
- long docPtr;
238
- synchronized (state.pdfiumLock) {
239
- docPtr = extractNativeDocPointer(state.document);
240
- }
241
- if (docPtr != 0) {
242
- synchronized (state.pdfiumLock) {
243
- hits = PapyrusTextSearch.nativeSearch(docPtr, pageCount, query);
244
- }
245
- }
246
- }
247
-
248
- if (hits != null && hits.length > 0) {
249
- WritableArray results = Arguments.createArray();
250
- for (PapyrusTextHit hit : hits) {
251
- WritableMap result = Arguments.createMap();
252
- result.putInt("pageIndex", hit.pageIndex);
253
- result.putString("text", hit.text != null ? hit.text : query);
254
- result.putInt("matchIndex", hit.matchIndex);
255
- if (hit.rects != null && hit.rects.length >= 4) {
256
- WritableArray rects = Arguments.createArray();
257
- for (int i = 0; i + 3 < hit.rects.length; i += 4) {
258
- WritableMap rect = Arguments.createMap();
259
- rect.putDouble("x", hit.rects[i]);
260
- rect.putDouble("y", hit.rects[i + 1]);
261
- rect.putDouble("width", hit.rects[i + 2]);
262
- rect.putDouble("height", hit.rects[i + 3]);
263
- rects.pushMap(rect);
264
- }
265
- result.putArray("rects", rects);
266
- }
267
- results.pushMap(result);
268
- }
269
- promise.resolve(results);
270
- return;
271
- }
272
- }
273
- } catch (Throwable ignored) {
274
- }
275
-
276
- String normalizedQuery = query.toLowerCase();
277
- WritableArray results = Arguments.createArray();
278
-
279
- for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
280
- String text;
281
- synchronized (state.pdfiumLock) {
282
- text = extractPageText(state, pageIndex);
283
- }
284
- if (text == null || text.isEmpty()) continue;
285
-
286
- String lower = text.toLowerCase();
287
- int pos = lower.indexOf(normalizedQuery);
288
- int matchIndex = 0;
289
- while (pos != -1) {
290
- int start = Math.max(0, pos - 20);
291
- int end = Math.min(text.length(), pos + normalizedQuery.length() + 20);
292
- String preview = text.substring(start, end);
293
-
294
- WritableMap result = Arguments.createMap();
295
- result.putInt("pageIndex", pageIndex);
296
- result.putString("text", preview);
297
- result.putInt("matchIndex", matchIndex++);
298
- results.pushMap(result);
299
-
300
- pos = lower.indexOf(normalizedQuery, pos + 1);
301
- }
302
- }
303
-
304
- promise.resolve(results);
305
- } finally {
306
- state.isSearching = false;
307
- }
308
- });
309
- }
310
-
311
- @ReactMethod
312
- public void selectText(String engineId, int pageIndex, double x, double y, double width, double height, Promise promise) {
313
- executor.execute(() -> {
314
- PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
315
- if (state == null || state.document == null || pageIndex < 0) {
316
- promise.resolve(null);
317
- return;
318
- }
319
-
320
- if (!PapyrusTextSelect.AVAILABLE) {
321
- promise.resolve(null);
322
- return;
323
- }
324
-
325
- PapyrusTextSelection selection = null;
326
- try {
327
- if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
328
- synchronized (state.pdfiumLock) {
329
- selection = PapyrusTextSelect.nativeSelectTextFile(state.sourcePath, pageIndex, (float) x, (float) y, (float) width, (float) height);
330
- }
331
- } else {
332
- long docPtr;
333
- synchronized (state.pdfiumLock) {
334
- docPtr = extractNativeDocPointer(state.document);
335
- }
336
- if (docPtr != 0) {
337
- synchronized (state.pdfiumLock) {
338
- selection = PapyrusTextSelect.nativeSelectText(docPtr, pageIndex, (float) x, (float) y, (float) width, (float) height);
339
- }
340
- }
341
- }
342
- } catch (Throwable ignored) {
343
- selection = null;
344
- }
345
-
346
- if (selection == null || selection.rects == null || selection.rects.length == 0) {
347
- promise.resolve(null);
348
- return;
349
- }
350
-
351
- WritableMap result = Arguments.createMap();
352
- result.putString("text", selection.text != null ? selection.text : "");
353
- WritableArray rects = Arguments.createArray();
354
- for (int i = 0; i + 3 < selection.rects.length; i += 4) {
355
- WritableMap rect = Arguments.createMap();
356
- rect.putDouble("x", selection.rects[i]);
357
- rect.putDouble("y", selection.rects[i + 1]);
358
- rect.putDouble("width", selection.rects[i + 2]);
359
- rect.putDouble("height", selection.rects[i + 3]);
360
- rects.pushMap(rect);
361
- }
362
- result.putArray("rects", rects);
363
- promise.resolve(result);
364
- });
365
- }
366
-
367
- private String extractPageText(PapyrusEngineStore.EngineState state, int pageIndex) {
368
- try {
369
- state.pdfium.openPage(state.document, pageIndex);
370
- } catch (Throwable ignored) {
371
- }
372
-
373
- try {
374
- java.lang.reflect.Method method = null;
375
- try {
376
- method = state.pdfium.getClass().getDeclaredMethod("getPageText", PdfDocument.class, int.class);
377
- } catch (NoSuchMethodException ignored) {
378
- }
379
-
380
- if (method == null) {
381
- try {
382
- method = state.pdfium.getClass().getDeclaredMethod("nativeGetPageText", long.class, int.class);
383
- } catch (NoSuchMethodException ignored) {
384
- }
1
+ package com.papyrus.engine;
2
+
3
+ import android.content.ContentResolver;
4
+ import android.content.Context;
5
+ import android.net.Uri;
6
+ import android.os.ParcelFileDescriptor;
7
+ import android.view.View;
8
+
9
+ import com.facebook.react.bridge.Arguments;
10
+ import com.facebook.react.bridge.Promise;
11
+ import com.facebook.react.bridge.ReactApplicationContext;
12
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
13
+ import com.facebook.react.bridge.ReactMethod;
14
+ import com.facebook.react.bridge.ReadableArray;
15
+ import com.facebook.react.bridge.ReadableMap;
16
+ import com.facebook.react.bridge.WritableArray;
17
+ import com.facebook.react.bridge.WritableMap;
18
+ import com.facebook.react.bridge.ReadableType;
19
+ import com.facebook.react.uimanager.UIBlock;
20
+ import com.facebook.react.uimanager.UIManagerModule;
21
+
22
+ import com.shockwave.pdfium.PdfDocument;
23
+
24
+ import java.io.File;
25
+ import java.io.FileOutputStream;
26
+ import java.io.IOException;
27
+ import java.io.InputStream;
28
+ import java.net.HttpURLConnection;
29
+ import java.net.URL;
30
+ import java.util.concurrent.ExecutorService;
31
+ import java.util.concurrent.Executors;
32
+
33
+ public class PapyrusNativeEngineModule extends ReactContextBaseJavaModule {
34
+ private final ReactApplicationContext reactContext;
35
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
36
+
37
+ public PapyrusNativeEngineModule(ReactApplicationContext reactContext) {
38
+ super(reactContext);
39
+ this.reactContext = reactContext;
40
+ }
41
+
42
+ @Override
43
+ public String getName() {
44
+ return "PapyrusNativeEngine";
45
+ }
46
+
47
+ @ReactMethod(isBlockingSynchronousMethod = true)
48
+ public String createEngine() {
49
+ return PapyrusEngineStore.createEngine(reactContext);
50
+ }
51
+
52
+ @ReactMethod
53
+ public void destroyEngine(String engineId) {
54
+ PapyrusEngineStore.destroyEngine(engineId);
55
+ }
56
+
57
+ @ReactMethod
58
+ public void load(final String engineId, final ReadableMap source, final Promise promise) {
59
+ executor.execute(() -> {
60
+ try {
61
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
62
+ if (state == null) {
63
+ promise.reject("papyrus_no_engine", "Engine not found");
64
+ return;
65
+ }
66
+
67
+ File file = materializeSource(source, reactContext);
68
+ if (file == null) {
69
+ promise.reject("papyrus_invalid_source", "Unsupported PDF source");
70
+ return;
71
+ }
72
+
73
+ ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
74
+ PdfDocument document = state.pdfium.newDocument(fd);
75
+ PapyrusEngineStore.setDocument(state, document, fd, file.getAbsolutePath());
76
+
77
+ int pageCount = state.pdfium.getPageCount(document);
78
+ WritableMap result = Arguments.createMap();
79
+ result.putInt("pageCount", pageCount);
80
+ promise.resolve(result);
81
+ } catch (Throwable error) {
82
+ promise.reject("papyrus_load_failed", error);
83
+ }
84
+ });
85
+ }
86
+
87
+ @ReactMethod(isBlockingSynchronousMethod = true)
88
+ public int getPageCount(String engineId) {
89
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
90
+ if (state == null || state.document == null) return 0;
91
+ return state.pdfium.getPageCount(state.document);
92
+ }
93
+
94
+ @ReactMethod
95
+ public void renderPage(final String engineId, final int pageIndex, final int target, final float scale, final float zoom, final int rotation) {
96
+ final PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
97
+ if (state == null) return;
98
+ UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
99
+ if (uiManager == null) return;
100
+
101
+ uiManager.addUIBlock(new UIBlock() {
102
+ @Override
103
+ public void execute(com.facebook.react.uimanager.NativeViewHierarchyManager nativeViewHierarchyManager) {
104
+ View view = nativeViewHierarchyManager.resolveView(target);
105
+ if (view instanceof PapyrusPageView) {
106
+ ((PapyrusPageView) view).render(state, pageIndex, scale, zoom, rotation);
107
+ }
108
+ }
109
+ });
110
+ }
111
+
112
+ @ReactMethod
113
+ public void renderTextLayer(String engineId, int pageIndex, int target, float scale, float zoom, int rotation) {
114
+ }
115
+
116
+ @ReactMethod
117
+ public void getTextContent(String engineId, int pageIndex, Promise promise) {
118
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
119
+ if (state == null || state.document == null) {
120
+ promise.resolve(Arguments.createArray());
121
+ return;
122
+ }
123
+ String text;
124
+ synchronized (state.pdfiumLock) {
125
+ text = extractPageText(state, pageIndex);
126
+ }
127
+ WritableArray items = Arguments.createArray();
128
+ if (text != null && !text.isEmpty()) {
129
+ WritableMap item = Arguments.createMap();
130
+ item.putString("str", text);
131
+ item.putString("dir", "ltr");
132
+ item.putDouble("width", 0);
133
+ item.putDouble("height", 0);
134
+ WritableArray transform = Arguments.createArray();
135
+ transform.pushDouble(1);
136
+ transform.pushDouble(0);
137
+ transform.pushDouble(0);
138
+ transform.pushDouble(1);
139
+ transform.pushDouble(0);
140
+ transform.pushDouble(0);
141
+ item.putArray("transform", transform);
142
+ item.putString("fontName", "");
143
+ items.pushMap(item);
144
+ }
145
+ promise.resolve(items);
146
+ }
147
+
148
+ @ReactMethod
149
+ public void getPageDimensions(String engineId, int pageIndex, Promise promise) {
150
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
151
+ if (state == null || state.document == null) {
152
+ WritableMap result = Arguments.createMap();
153
+ result.putInt("width", 0);
154
+ result.putInt("height", 0);
155
+ promise.resolve(result);
156
+ return;
157
+ }
158
+ int width;
159
+ int height;
160
+ synchronized (state.pdfiumLock) {
161
+ width = state.pdfium.getPageWidthPoint(state.document, pageIndex);
162
+ height = state.pdfium.getPageHeightPoint(state.document, pageIndex);
163
+ }
164
+ WritableMap result = Arguments.createMap();
165
+ result.putInt("width", width);
166
+ result.putInt("height", height);
167
+ promise.resolve(result);
168
+ }
169
+
170
+ @ReactMethod
171
+ public void getOutline(String engineId, Promise promise) {
172
+ executor.execute(() -> {
173
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
174
+ if (state == null || state.document == null) {
175
+ promise.resolve(Arguments.createArray());
176
+ return;
177
+ }
178
+
179
+ PapyrusOutlineItem[] items = null;
180
+ try {
181
+ if (PapyrusOutline.AVAILABLE) {
182
+ if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
183
+ synchronized (state.pdfiumLock) {
184
+ items = PapyrusOutline.nativeGetOutlineFile(state.sourcePath);
185
+ }
186
+ } else {
187
+ long docPtr;
188
+ synchronized (state.pdfiumLock) {
189
+ docPtr = extractNativeDocPointer(state.document);
190
+ }
191
+ if (docPtr != 0) {
192
+ synchronized (state.pdfiumLock) {
193
+ items = PapyrusOutline.nativeGetOutline(docPtr);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ } catch (Throwable ignored) {
199
+ items = null;
200
+ }
201
+
202
+ WritableArray result = Arguments.createArray();
203
+ if (items != null) {
204
+ for (PapyrusOutlineItem item : items) {
205
+ result.pushMap(serializeOutlineItem(item));
206
+ }
207
+ }
208
+ promise.resolve(result);
209
+ });
210
+ }
211
+
212
+ @ReactMethod
213
+ public void getPageIndex(String engineId, Object dest, Promise promise) {
214
+ promise.resolve(null);
215
+ }
216
+
217
+ @ReactMethod
218
+ public void searchText(String engineId, String query, Promise promise) {
219
+ executor.execute(() -> {
220
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
221
+ if (state == null || state.document == null || query == null || query.length() < 2) {
222
+ promise.resolve(Arguments.createArray());
223
+ return;
224
+ }
225
+
226
+ int pageCount = state.pdfium.getPageCount(state.document);
227
+ state.isSearching = true;
228
+ try {
229
+ try {
230
+ if (PapyrusTextSearch.AVAILABLE) {
231
+ PapyrusTextHit[] hits = null;
232
+ if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
233
+ synchronized (state.pdfiumLock) {
234
+ hits = PapyrusTextSearch.nativeSearchFile(state.sourcePath, query);
235
+ }
236
+ } else {
237
+ long docPtr;
238
+ synchronized (state.pdfiumLock) {
239
+ docPtr = extractNativeDocPointer(state.document);
240
+ }
241
+ if (docPtr != 0) {
242
+ synchronized (state.pdfiumLock) {
243
+ hits = PapyrusTextSearch.nativeSearch(docPtr, pageCount, query);
244
+ }
245
+ }
246
+ }
247
+
248
+ if (hits != null && hits.length > 0) {
249
+ WritableArray results = Arguments.createArray();
250
+ for (PapyrusTextHit hit : hits) {
251
+ WritableMap result = Arguments.createMap();
252
+ result.putInt("pageIndex", hit.pageIndex);
253
+ result.putString("text", hit.text != null ? hit.text : query);
254
+ result.putInt("matchIndex", hit.matchIndex);
255
+ if (hit.rects != null && hit.rects.length >= 4) {
256
+ WritableArray rects = Arguments.createArray();
257
+ for (int i = 0; i + 3 < hit.rects.length; i += 4) {
258
+ WritableMap rect = Arguments.createMap();
259
+ rect.putDouble("x", hit.rects[i]);
260
+ rect.putDouble("y", hit.rects[i + 1]);
261
+ rect.putDouble("width", hit.rects[i + 2]);
262
+ rect.putDouble("height", hit.rects[i + 3]);
263
+ rects.pushMap(rect);
264
+ }
265
+ result.putArray("rects", rects);
266
+ }
267
+ results.pushMap(result);
268
+ }
269
+ promise.resolve(results);
270
+ return;
271
+ }
272
+ }
273
+ } catch (Throwable ignored) {
274
+ }
275
+
276
+ String normalizedQuery = query.toLowerCase();
277
+ WritableArray results = Arguments.createArray();
278
+
279
+ for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
280
+ String text;
281
+ synchronized (state.pdfiumLock) {
282
+ text = extractPageText(state, pageIndex);
283
+ }
284
+ if (text == null || text.isEmpty()) continue;
285
+
286
+ String lower = text.toLowerCase();
287
+ int pos = lower.indexOf(normalizedQuery);
288
+ int matchIndex = 0;
289
+ while (pos != -1) {
290
+ int start = Math.max(0, pos - 20);
291
+ int end = Math.min(text.length(), pos + normalizedQuery.length() + 20);
292
+ String preview = text.substring(start, end);
293
+
294
+ WritableMap result = Arguments.createMap();
295
+ result.putInt("pageIndex", pageIndex);
296
+ result.putString("text", preview);
297
+ result.putInt("matchIndex", matchIndex++);
298
+ results.pushMap(result);
299
+
300
+ pos = lower.indexOf(normalizedQuery, pos + 1);
301
+ }
302
+ }
303
+
304
+ promise.resolve(results);
305
+ } finally {
306
+ state.isSearching = false;
307
+ }
308
+ });
309
+ }
310
+
311
+ @ReactMethod
312
+ public void selectText(String engineId, int pageIndex, double x, double y, double width, double height, Promise promise) {
313
+ executor.execute(() -> {
314
+ PapyrusEngineStore.EngineState state = PapyrusEngineStore.getEngine(engineId);
315
+ if (state == null || state.document == null || pageIndex < 0) {
316
+ promise.resolve(null);
317
+ return;
318
+ }
319
+
320
+ if (!PapyrusTextSelect.AVAILABLE) {
321
+ promise.resolve(null);
322
+ return;
323
+ }
324
+
325
+ PapyrusTextSelection selection = null;
326
+ try {
327
+ if (state.sourcePath != null && !state.sourcePath.isEmpty()) {
328
+ synchronized (state.pdfiumLock) {
329
+ selection = PapyrusTextSelect.nativeSelectTextFile(state.sourcePath, pageIndex, (float) x, (float) y, (float) width, (float) height);
330
+ }
331
+ } else {
332
+ long docPtr;
333
+ synchronized (state.pdfiumLock) {
334
+ docPtr = extractNativeDocPointer(state.document);
335
+ }
336
+ if (docPtr != 0) {
337
+ synchronized (state.pdfiumLock) {
338
+ selection = PapyrusTextSelect.nativeSelectText(docPtr, pageIndex, (float) x, (float) y, (float) width, (float) height);
339
+ }
340
+ }
341
+ }
342
+ } catch (Throwable ignored) {
343
+ selection = null;
344
+ }
345
+
346
+ if (selection == null || selection.rects == null || selection.rects.length == 0) {
347
+ promise.resolve(null);
348
+ return;
349
+ }
350
+
351
+ WritableMap result = Arguments.createMap();
352
+ result.putString("text", selection.text != null ? selection.text : "");
353
+ WritableArray rects = Arguments.createArray();
354
+ for (int i = 0; i + 3 < selection.rects.length; i += 4) {
355
+ WritableMap rect = Arguments.createMap();
356
+ rect.putDouble("x", selection.rects[i]);
357
+ rect.putDouble("y", selection.rects[i + 1]);
358
+ rect.putDouble("width", selection.rects[i + 2]);
359
+ rect.putDouble("height", selection.rects[i + 3]);
360
+ rects.pushMap(rect);
361
+ }
362
+ result.putArray("rects", rects);
363
+ promise.resolve(result);
364
+ });
365
+ }
366
+
367
+ private String extractPageText(PapyrusEngineStore.EngineState state, int pageIndex) {
368
+ try {
369
+ state.pdfium.openPage(state.document, pageIndex);
370
+ } catch (Throwable ignored) {
371
+ }
372
+
373
+ try {
374
+ java.lang.reflect.Method method = null;
375
+ try {
376
+ method = state.pdfium.getClass().getDeclaredMethod("getPageText", PdfDocument.class, int.class);
377
+ } catch (NoSuchMethodException ignored) {
378
+ }
379
+
380
+ if (method == null) {
381
+ try {
382
+ method = state.pdfium.getClass().getDeclaredMethod("nativeGetPageText", long.class, int.class);
383
+ } catch (NoSuchMethodException ignored) {
384
+ }
385
+ }
386
+
387
+ if (method != null) {
388
+ method.setAccessible(true);
389
+ Object result;
390
+ if (method.getParameterTypes().length == 2 && method.getParameterTypes()[0] == PdfDocument.class) {
391
+ result = method.invoke(state.pdfium, state.document, pageIndex);
392
+ } else if (method.getParameterTypes().length == 2 && method.getParameterTypes()[0] == long.class) {
393
+ long docPtr = extractNativeDocPointer(state.document);
394
+ result = method.invoke(state.pdfium, docPtr, pageIndex);
395
+ } else {
396
+ result = null;
397
+ }
398
+ return result != null ? result.toString() : "";
399
+ }
400
+ } catch (Throwable ignored) {
401
+ }
402
+
403
+ return "";
404
+ }
405
+
406
+ private long extractNativeDocPointer(PdfDocument document) {
407
+ try {
408
+ java.lang.reflect.Field field = PdfDocument.class.getDeclaredField("mNativeDocPtr");
409
+ field.setAccessible(true);
410
+ Object value = field.get(document);
411
+ if (value instanceof Long) {
412
+ return (Long) value;
413
+ }
414
+ } catch (Throwable ignored) {
415
+ }
416
+ return 0;
417
+ }
418
+
419
+ private WritableMap serializeOutlineItem(PapyrusOutlineItem item) {
420
+ WritableMap map = Arguments.createMap();
421
+ map.putString("title", item.title != null ? item.title : "");
422
+ map.putInt("pageIndex", item.pageIndex);
423
+ if (item.children != null && item.children.length > 0) {
424
+ WritableArray children = Arguments.createArray();
425
+ for (PapyrusOutlineItem child : item.children) {
426
+ children.pushMap(serializeOutlineItem(child));
427
+ }
428
+ map.putArray("children", children);
429
+ }
430
+ return map;
431
+ }
432
+
433
+ private static File materializeSource(ReadableMap source, Context context) throws IOException {
434
+ if (source.hasKey("uri") && source.getType("uri") == ReadableType.String) {
435
+ String uriString = source.getString("uri");
436
+ if (uriString == null) return null;
437
+
438
+ if (uriString.startsWith("http://") || uriString.startsWith("https://")) {
439
+ return downloadToCache(uriString, context);
440
+ }
441
+
442
+ if (uriString.startsWith("asset:/")) {
443
+ return copyFromAsset(uriString.substring("asset:/".length()), context);
444
+ }
445
+
446
+ if (uriString.startsWith("file:///android_asset/")) {
447
+ return copyFromAsset(uriString.substring("file:///android_asset/".length()), context);
448
+ }
449
+
450
+ if (uriString.startsWith("content://")) {
451
+ return copyFromContentUri(Uri.parse(uriString), context);
452
+ }
453
+
454
+ if (uriString.startsWith("file://")) {
455
+ return new File(Uri.parse(uriString).getPath());
385
456
  }
386
457
 
387
- if (method != null) {
388
- method.setAccessible(true);
389
- Object result;
390
- if (method.getParameterTypes().length == 2 && method.getParameterTypes()[0] == PdfDocument.class) {
391
- result = method.invoke(state.pdfium, state.document, pageIndex);
392
- } else if (method.getParameterTypes().length == 2 && method.getParameterTypes()[0] == long.class) {
393
- long docPtr = extractNativeDocPointer(state.document);
394
- result = method.invoke(state.pdfium, docPtr, pageIndex);
395
- } else {
396
- result = null;
458
+ if (uriString.startsWith("res://")) {
459
+ String resourceName = uriString.substring("res://".length());
460
+ File resourceFile = copyFromRawResource(resourceName, context);
461
+ if (resourceFile != null) {
462
+ return resourceFile;
397
463
  }
398
- return result != null ? result.toString() : "";
399
- }
400
- } catch (Throwable ignored) {
401
- }
402
-
403
- return "";
404
- }
405
-
406
- private long extractNativeDocPointer(PdfDocument document) {
407
- try {
408
- java.lang.reflect.Field field = PdfDocument.class.getDeclaredField("mNativeDocPtr");
409
- field.setAccessible(true);
410
- Object value = field.get(document);
411
- if (value instanceof Long) {
412
- return (Long) value;
413
- }
414
- } catch (Throwable ignored) {
415
- }
416
- return 0;
417
- }
418
-
419
- private WritableMap serializeOutlineItem(PapyrusOutlineItem item) {
420
- WritableMap map = Arguments.createMap();
421
- map.putString("title", item.title != null ? item.title : "");
422
- map.putInt("pageIndex", item.pageIndex);
423
- if (item.children != null && item.children.length > 0) {
424
- WritableArray children = Arguments.createArray();
425
- for (PapyrusOutlineItem child : item.children) {
426
- children.pushMap(serializeOutlineItem(child));
427
- }
428
- map.putArray("children", children);
429
- }
430
- return map;
431
- }
432
-
433
- private static File materializeSource(ReadableMap source, Context context) throws IOException {
434
- if (source.hasKey("uri") && source.getType("uri") == ReadableType.String) {
435
- String uriString = source.getString("uri");
436
- if (uriString == null) return null;
437
-
438
- if (uriString.startsWith("http://") || uriString.startsWith("https://")) {
439
- return downloadToCache(uriString, context);
440
- }
441
-
442
- if (uriString.startsWith("asset:/")) {
443
- return copyFromAsset(uriString.substring("asset:/".length()), context);
444
- }
445
-
446
- if (uriString.startsWith("file:///android_asset/")) {
447
- return copyFromAsset(uriString.substring("file:///android_asset/".length()), context);
448
- }
449
-
450
- if (uriString.startsWith("content://")) {
451
- return copyFromContentUri(Uri.parse(uriString), context);
452
464
  }
453
465
 
454
- if (uriString.startsWith("file://")) {
455
- return new File(Uri.parse(uriString).getPath());
466
+ File resourceFile = copyFromRawResource(uriString, context);
467
+ if (resourceFile != null) {
468
+ return resourceFile;
456
469
  }
457
470
 
458
471
  return new File(uriString);
459
472
  }
460
-
461
- if (source.hasKey("data") && source.getType("data") == ReadableType.Array) {
462
- ReadableArray array = source.getArray("data");
463
- if (array == null) return null;
464
- byte[] bytes = new byte[array.size()];
465
- for (int i = 0; i < array.size(); i++) {
466
- bytes[i] = (byte) array.getInt(i);
467
- }
468
- return writeBytesToCache(bytes, context);
469
- }
470
-
471
- return null;
472
- }
473
-
474
- private static File downloadToCache(String uri, Context context) throws IOException {
475
- URL url = new URL(uri);
476
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
477
- connection.connect();
478
- if (connection.getResponseCode() >= 400) {
479
- throw new IOException("Failed to download PDF");
480
- }
481
- InputStream inputStream = connection.getInputStream();
482
- File out = createTempFile(context);
483
- writeStreamToFile(inputStream, out);
484
- connection.disconnect();
485
- return out;
486
- }
487
-
488
- private static File copyFromContentUri(Uri uri, Context context) throws IOException {
489
- ContentResolver resolver = context.getContentResolver();
490
- InputStream inputStream = resolver.openInputStream(uri);
491
- if (inputStream == null) throw new IOException("Unable to read content URI");
492
- File out = createTempFile(context);
493
- writeStreamToFile(inputStream, out);
494
- return out;
495
- }
496
-
473
+
474
+ if (source.hasKey("data") && source.getType("data") == ReadableType.Array) {
475
+ ReadableArray array = source.getArray("data");
476
+ if (array == null) return null;
477
+ byte[] bytes = new byte[array.size()];
478
+ for (int i = 0; i < array.size(); i++) {
479
+ bytes[i] = (byte) array.getInt(i);
480
+ }
481
+ return writeBytesToCache(bytes, context);
482
+ }
483
+
484
+ return null;
485
+ }
486
+
487
+ private static File downloadToCache(String uri, Context context) throws IOException {
488
+ URL url = new URL(uri);
489
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
490
+ connection.connect();
491
+ if (connection.getResponseCode() >= 400) {
492
+ throw new IOException("Failed to download PDF");
493
+ }
494
+ InputStream inputStream = connection.getInputStream();
495
+ File out = createTempFile(context);
496
+ writeStreamToFile(inputStream, out);
497
+ connection.disconnect();
498
+ return out;
499
+ }
500
+
501
+ private static File copyFromContentUri(Uri uri, Context context) throws IOException {
502
+ ContentResolver resolver = context.getContentResolver();
503
+ InputStream inputStream = resolver.openInputStream(uri);
504
+ if (inputStream == null) throw new IOException("Unable to read content URI");
505
+ File out = createTempFile(context);
506
+ writeStreamToFile(inputStream, out);
507
+ return out;
508
+ }
509
+
497
510
  private static File copyFromAsset(String assetPath, Context context) throws IOException {
498
511
  InputStream inputStream = context.getAssets().open(assetPath);
499
512
  File out = createTempFile(context);
@@ -501,29 +514,40 @@ public class PapyrusNativeEngineModule extends ReactContextBaseJavaModule {
501
514
  return out;
502
515
  }
503
516
 
504
- private static File writeBytesToCache(byte[] bytes, Context context) throws IOException {
517
+ private static File copyFromRawResource(String resourceName, Context context) throws IOException {
518
+ int resId = context.getResources().getIdentifier(resourceName, "raw", context.getPackageName());
519
+ if (resId == 0) {
520
+ return null;
521
+ }
522
+ InputStream inputStream = context.getResources().openRawResource(resId);
505
523
  File out = createTempFile(context);
506
- FileOutputStream fos = new FileOutputStream(out);
507
- fos.write(bytes);
508
- fos.flush();
509
- fos.close();
524
+ writeStreamToFile(inputStream, out);
510
525
  return out;
511
526
  }
512
527
 
513
- private static File createTempFile(Context context) throws IOException {
514
- File cacheDir = context.getCacheDir();
515
- return File.createTempFile("papyrus", ".pdf", cacheDir);
516
- }
517
-
518
- private static void writeStreamToFile(InputStream inputStream, File out) throws IOException {
519
- FileOutputStream fos = new FileOutputStream(out);
520
- byte[] buffer = new byte[8192];
521
- int read;
522
- while ((read = inputStream.read(buffer)) != -1) {
523
- fos.write(buffer, 0, read);
524
- }
525
- fos.flush();
526
- fos.close();
527
- inputStream.close();
528
- }
529
- }
528
+ private static File writeBytesToCache(byte[] bytes, Context context) throws IOException {
529
+ File out = createTempFile(context);
530
+ FileOutputStream fos = new FileOutputStream(out);
531
+ fos.write(bytes);
532
+ fos.flush();
533
+ fos.close();
534
+ return out;
535
+ }
536
+
537
+ private static File createTempFile(Context context) throws IOException {
538
+ File cacheDir = context.getCacheDir();
539
+ return File.createTempFile("papyrus", ".pdf", cacheDir);
540
+ }
541
+
542
+ private static void writeStreamToFile(InputStream inputStream, File out) throws IOException {
543
+ FileOutputStream fos = new FileOutputStream(out);
544
+ byte[] buffer = new byte[8192];
545
+ int read;
546
+ while ((read = inputStream.read(buffer)) != -1) {
547
+ fos.write(buffer, 0, read);
548
+ }
549
+ fos.flush();
550
+ fos.close();
551
+ inputStream.close();
552
+ }
553
+ }