@papyrus-sdk/engine-native 0.2.8 → 0.2.10
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 +38 -38
- package/android/build.gradle +41 -39
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/AndroidManifest.xml +1 -3
- package/android/src/main/cpp/CMakeLists.txt +42 -20
- package/android/src/main/cpp/papyrus_outline.cpp +108 -74
- package/android/src/main/cpp/papyrus_outline_loader.cpp +125 -0
- package/android/src/main/cpp/papyrus_outline_loader.h +48 -0
- package/android/src/main/cpp/papyrus_outline_loader_test.cpp +240 -0
- package/android/src/main/cpp/papyrus_text_search.cpp +408 -408
- package/android/src/main/java/android/support/v4/util/ArrayMap.java +18 -18
- package/android/src/main/java/com/papyrus/engine/PapyrusNativeEngineModule.java +37 -37
- package/android/src/main/java/com/papyrus/engine/PapyrusNativeEngineModule.kt +34 -34
- package/android/src/main/java/com/papyrus/engine/PapyrusOutline.java +33 -19
- package/android/src/test/java/com/papyrus/engine/PapyrusOutlineTest.java +40 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/ios/PapyrusEngineStore.h +16 -16
- package/ios/PapyrusEngineStore.m +48 -48
- package/package.json +60 -60
|
@@ -1,408 +1,408 @@
|
|
|
1
|
-
#include <jni.h>
|
|
2
|
-
#include <android/log.h>
|
|
3
|
-
#include <dlfcn.h>
|
|
4
|
-
|
|
5
|
-
#include <algorithm>
|
|
6
|
-
#include <cmath>
|
|
7
|
-
#include <string>
|
|
8
|
-
#include <vector>
|
|
9
|
-
|
|
10
|
-
#define LOG_TAG "PapyrusText"
|
|
11
|
-
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
12
|
-
|
|
13
|
-
typedef void *FPDF_DOCUMENT;
|
|
14
|
-
typedef void *FPDF_PAGE;
|
|
15
|
-
typedef void *FPDF_TEXTPAGE;
|
|
16
|
-
typedef void *FPDF_SCHHANDLE;
|
|
17
|
-
typedef const unsigned short *FPDF_WIDESTRING;
|
|
18
|
-
|
|
19
|
-
constexpr int kMaxHits = 200;
|
|
20
|
-
|
|
21
|
-
struct PdfiumFns {
|
|
22
|
-
FPDF_PAGE (*loadPage)(FPDF_DOCUMENT, int);
|
|
23
|
-
void (*closePage)(FPDF_PAGE);
|
|
24
|
-
double (*getPageWidth)(FPDF_PAGE);
|
|
25
|
-
double (*getPageHeight)(FPDF_PAGE);
|
|
26
|
-
int (*getDocPageCount)(FPDF_DOCUMENT);
|
|
27
|
-
FPDF_DOCUMENT (*loadDocument)(const char *, const char *);
|
|
28
|
-
void (*closeDocument)(FPDF_DOCUMENT);
|
|
29
|
-
FPDF_TEXTPAGE (*textLoadPage)(FPDF_PAGE);
|
|
30
|
-
void (*textClosePage)(FPDF_TEXTPAGE);
|
|
31
|
-
FPDF_SCHHANDLE (*textFindStart)(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int);
|
|
32
|
-
int (*textFindNext)(FPDF_SCHHANDLE);
|
|
33
|
-
void (*textFindClose)(FPDF_SCHHANDLE);
|
|
34
|
-
int (*textGetSchResultIndex)(FPDF_SCHHANDLE);
|
|
35
|
-
int (*textGetSchCount)(FPDF_SCHHANDLE);
|
|
36
|
-
int (*textGetCharBox)(FPDF_TEXTPAGE, int, double *, double *, double *, double *);
|
|
37
|
-
int (*textCountChars)(FPDF_TEXTPAGE);
|
|
38
|
-
int (*textGetText)(FPDF_TEXTPAGE, int, int, unsigned short *);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
static void *g_pdfium = nullptr;
|
|
42
|
-
static PdfiumFns g_fns = {};
|
|
43
|
-
static bool g_loaded = false;
|
|
44
|
-
|
|
45
|
-
static bool LoadPdfium() {
|
|
46
|
-
if (g_loaded) return g_pdfium != nullptr;
|
|
47
|
-
|
|
48
|
-
g_pdfium = dlopen("libmodpdfium.so", RTLD_LAZY);
|
|
49
|
-
if (!g_pdfium) {
|
|
50
|
-
LOGE("Failed to load libmodpdfium.so");
|
|
51
|
-
g_loaded = true;
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
g_fns.loadPage = reinterpret_cast<FPDF_PAGE (*)(FPDF_DOCUMENT, int)>(dlsym(g_pdfium, "FPDF_LoadPage"));
|
|
56
|
-
g_fns.closePage = reinterpret_cast<void (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_ClosePage"));
|
|
57
|
-
g_fns.getPageWidth = reinterpret_cast<double (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_GetPageWidth"));
|
|
58
|
-
g_fns.getPageHeight = reinterpret_cast<double (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_GetPageHeight"));
|
|
59
|
-
g_fns.getDocPageCount = reinterpret_cast<int (*)(FPDF_DOCUMENT)>(dlsym(g_pdfium, "FPDF_GetPageCount"));
|
|
60
|
-
g_fns.loadDocument = reinterpret_cast<FPDF_DOCUMENT (*)(const char *, const char *)>(dlsym(g_pdfium, "FPDF_LoadDocument"));
|
|
61
|
-
g_fns.closeDocument = reinterpret_cast<void (*)(FPDF_DOCUMENT)>(dlsym(g_pdfium, "FPDF_CloseDocument"));
|
|
62
|
-
g_fns.textLoadPage = reinterpret_cast<FPDF_TEXTPAGE (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDFText_LoadPage"));
|
|
63
|
-
g_fns.textClosePage = reinterpret_cast<void (*)(FPDF_TEXTPAGE)>(dlsym(g_pdfium, "FPDFText_ClosePage"));
|
|
64
|
-
g_fns.textFindStart = reinterpret_cast<FPDF_SCHHANDLE (*)(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int)>(dlsym(g_pdfium, "FPDFText_FindStart"));
|
|
65
|
-
g_fns.textFindNext = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_FindNext"));
|
|
66
|
-
g_fns.textFindClose = reinterpret_cast<void (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_FindClose"));
|
|
67
|
-
g_fns.textGetSchResultIndex = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_GetSchResultIndex"));
|
|
68
|
-
g_fns.textGetSchCount = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_GetSchCount"));
|
|
69
|
-
g_fns.textGetCharBox = reinterpret_cast<int (*)(FPDF_TEXTPAGE, int, double *, double *, double *, double *)>(dlsym(g_pdfium, "FPDFText_GetCharBox"));
|
|
70
|
-
g_fns.textCountChars = reinterpret_cast<int (*)(FPDF_TEXTPAGE)>(dlsym(g_pdfium, "FPDFText_CountChars"));
|
|
71
|
-
g_fns.textGetText = reinterpret_cast<int (*)(FPDF_TEXTPAGE, int, int, unsigned short *)>(dlsym(g_pdfium, "FPDFText_GetText"));
|
|
72
|
-
|
|
73
|
-
if (!g_fns.loadPage || !g_fns.closePage || !g_fns.getPageWidth || !g_fns.getPageHeight ||
|
|
74
|
-
!g_fns.getDocPageCount || !g_fns.loadDocument || !g_fns.closeDocument ||
|
|
75
|
-
!g_fns.textLoadPage || !g_fns.textClosePage || !g_fns.textFindStart || !g_fns.textFindNext ||
|
|
76
|
-
!g_fns.textFindClose || !g_fns.textGetSchResultIndex || !g_fns.textGetSchCount ||
|
|
77
|
-
!g_fns.textGetCharBox || !g_fns.textCountChars || !g_fns.textGetText) {
|
|
78
|
-
LOGE("Failed to load required PDFium symbols");
|
|
79
|
-
g_loaded = true;
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
g_loaded = true;
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static jstring BuildPreview(JNIEnv *env, FPDF_TEXTPAGE textPage, int startIndex, int count) {
|
|
88
|
-
int totalChars = g_fns.textCountChars(textPage);
|
|
89
|
-
if (totalChars <= 0) return env->NewStringUTF("");
|
|
90
|
-
|
|
91
|
-
int previewStart = std::max(0, startIndex - 20);
|
|
92
|
-
int previewCount = std::min(totalChars - previewStart, count + 40);
|
|
93
|
-
if (previewCount <= 0) return env->NewStringUTF("");
|
|
94
|
-
|
|
95
|
-
std::vector<unsigned short> buffer(previewCount + 1, 0);
|
|
96
|
-
int written = g_fns.textGetText(textPage, previewStart, previewCount, buffer.data());
|
|
97
|
-
int length = written > 0 ? written - 1 : 0;
|
|
98
|
-
if (length <= 0) return env->NewStringUTF("");
|
|
99
|
-
|
|
100
|
-
return env->NewString(reinterpret_cast<const jchar *>(buffer.data()), length);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
static jfloatArray BuildRect(JNIEnv *env, FPDF_TEXTPAGE textPage, FPDF_PAGE page, int startIndex, int count) {
|
|
104
|
-
double left = 0;
|
|
105
|
-
double right = 0;
|
|
106
|
-
double top = 0;
|
|
107
|
-
double bottom = 0;
|
|
108
|
-
bool hasBox = false;
|
|
109
|
-
|
|
110
|
-
for (int i = 0; i < count; i++) {
|
|
111
|
-
double cLeft = 0;
|
|
112
|
-
double cRight = 0;
|
|
113
|
-
double cTop = 0;
|
|
114
|
-
double cBottom = 0;
|
|
115
|
-
if (!g_fns.textGetCharBox(textPage, startIndex + i, &cLeft, &cRight, &cBottom, &cTop)) continue;
|
|
116
|
-
if (!hasBox) {
|
|
117
|
-
left = cLeft;
|
|
118
|
-
right = cRight;
|
|
119
|
-
top = cTop;
|
|
120
|
-
bottom = cBottom;
|
|
121
|
-
hasBox = true;
|
|
122
|
-
} else {
|
|
123
|
-
left = std::min(left, cLeft);
|
|
124
|
-
right = std::max(right, cRight);
|
|
125
|
-
top = std::max(top, cTop);
|
|
126
|
-
bottom = std::min(bottom, cBottom);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!hasBox) {
|
|
131
|
-
return env->NewFloatArray(0);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
double pageWidth = g_fns.getPageWidth(page);
|
|
135
|
-
double pageHeight = g_fns.getPageHeight(page);
|
|
136
|
-
if (pageWidth <= 0 || pageHeight <= 0) {
|
|
137
|
-
return env->NewFloatArray(0);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
float x = static_cast<float>(left / pageWidth);
|
|
141
|
-
float y = static_cast<float>((pageHeight - top) / pageHeight);
|
|
142
|
-
float w = static_cast<float>((right - left) / pageWidth);
|
|
143
|
-
float h = static_cast<float>((top - bottom) / pageHeight);
|
|
144
|
-
|
|
145
|
-
jfloat rects[4] = {
|
|
146
|
-
std::max(0.0f, std::min(1.0f, x)),
|
|
147
|
-
std::max(0.0f, std::min(1.0f, y)),
|
|
148
|
-
std::max(0.0f, std::min(1.0f, w)),
|
|
149
|
-
std::max(0.0f, std::min(1.0f, h)),
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
jfloatArray array = env->NewFloatArray(4);
|
|
153
|
-
env->SetFloatArrayRegion(array, 0, 4, rects);
|
|
154
|
-
return array;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
static jobjectArray SearchDocument(JNIEnv *env, FPDF_DOCUMENT doc, int pageCount, const unsigned short *wideQuery, jsize queryLen) {
|
|
158
|
-
if (!doc || pageCount <= 0 || !wideQuery || queryLen <= 0) return nullptr;
|
|
159
|
-
|
|
160
|
-
jclass hitClass = env->FindClass("com/papyrus/engine/PapyrusTextHit");
|
|
161
|
-
if (!hitClass) return nullptr;
|
|
162
|
-
jmethodID ctor = env->GetMethodID(hitClass, "<init>", "(IILjava/lang/String;[F)V");
|
|
163
|
-
if (!ctor) return nullptr;
|
|
164
|
-
|
|
165
|
-
std::vector<jobject> hits;
|
|
166
|
-
hits.reserve(32);
|
|
167
|
-
|
|
168
|
-
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
|
169
|
-
FPDF_PAGE page = g_fns.loadPage(doc, pageIndex);
|
|
170
|
-
if (!page) continue;
|
|
171
|
-
FPDF_TEXTPAGE textPage = g_fns.textLoadPage(page);
|
|
172
|
-
if (!textPage) {
|
|
173
|
-
g_fns.closePage(page);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
FPDF_SCHHANDLE handle = g_fns.textFindStart(textPage, wideQuery, 0, 0);
|
|
178
|
-
if (handle) {
|
|
179
|
-
int matchIndex = 0;
|
|
180
|
-
while (g_fns.textFindNext(handle)) {
|
|
181
|
-
int startIndex = g_fns.textGetSchResultIndex(handle);
|
|
182
|
-
int count = g_fns.textGetSchCount(handle);
|
|
183
|
-
int totalChars = g_fns.textCountChars(textPage);
|
|
184
|
-
if (startIndex < 0 || startIndex >= totalChars) continue;
|
|
185
|
-
if (count <= 0) count = 1;
|
|
186
|
-
if (startIndex + count > totalChars) {
|
|
187
|
-
count = std::max(1, totalChars - startIndex);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
jstring preview = BuildPreview(env, textPage, startIndex, count);
|
|
191
|
-
jfloatArray rects = BuildRect(env, textPage, page, startIndex, count);
|
|
192
|
-
jobject hit = env->NewObject(hitClass, ctor, pageIndex, matchIndex, preview, rects);
|
|
193
|
-
if (hit) {
|
|
194
|
-
hits.push_back(hit);
|
|
195
|
-
}
|
|
196
|
-
matchIndex++;
|
|
197
|
-
if (static_cast<int>(hits.size()) >= kMaxHits) break;
|
|
198
|
-
}
|
|
199
|
-
g_fns.textFindClose(handle);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
g_fns.textClosePage(textPage);
|
|
203
|
-
g_fns.closePage(page);
|
|
204
|
-
if (static_cast<int>(hits.size()) >= kMaxHits) break;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
jobjectArray result = env->NewObjectArray(static_cast<jsize>(hits.size()), hitClass, nullptr);
|
|
208
|
-
for (jsize i = 0; i < static_cast<jsize>(hits.size()); i++) {
|
|
209
|
-
env->SetObjectArrayElement(result, i, hits[i]);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
static jobject BuildSelection(JNIEnv *env, FPDF_DOCUMENT doc, int pageIndex, double normX, double normY, double normW, double normH) {
|
|
216
|
-
if (!doc || pageIndex < 0 || normW <= 0 || normH <= 0) return nullptr;
|
|
217
|
-
|
|
218
|
-
jclass selectionClass = env->FindClass("com/papyrus/engine/PapyrusTextSelection");
|
|
219
|
-
if (!selectionClass) return nullptr;
|
|
220
|
-
jmethodID ctor = env->GetMethodID(selectionClass, "<init>", "(Ljava/lang/String;[F)V");
|
|
221
|
-
if (!ctor) return nullptr;
|
|
222
|
-
|
|
223
|
-
FPDF_PAGE page = g_fns.loadPage(doc, pageIndex);
|
|
224
|
-
if (!page) return nullptr;
|
|
225
|
-
FPDF_TEXTPAGE textPage = g_fns.textLoadPage(page);
|
|
226
|
-
if (!textPage) {
|
|
227
|
-
g_fns.closePage(page);
|
|
228
|
-
return nullptr;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
double pageWidth = g_fns.getPageWidth(page);
|
|
232
|
-
double pageHeight = g_fns.getPageHeight(page);
|
|
233
|
-
if (pageWidth <= 0 || pageHeight <= 0) {
|
|
234
|
-
g_fns.textClosePage(textPage);
|
|
235
|
-
g_fns.closePage(page);
|
|
236
|
-
return nullptr;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
double rectLeft = normX * pageWidth;
|
|
240
|
-
double rectTop = pageHeight - (normY * pageHeight);
|
|
241
|
-
double rectRight = rectLeft + (normW * pageWidth);
|
|
242
|
-
double rectBottom = rectTop - (normH * pageHeight);
|
|
243
|
-
|
|
244
|
-
struct LineRect {
|
|
245
|
-
double left;
|
|
246
|
-
double right;
|
|
247
|
-
double top;
|
|
248
|
-
double bottom;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
std::vector<LineRect> lines;
|
|
252
|
-
std::u16string selectedText;
|
|
253
|
-
|
|
254
|
-
int charCount = g_fns.textCountChars(textPage);
|
|
255
|
-
if (charCount <= 0) {
|
|
256
|
-
g_fns.textClosePage(textPage);
|
|
257
|
-
g_fns.closePage(page);
|
|
258
|
-
return nullptr;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const double lineTolerance = 2.5;
|
|
262
|
-
std::vector<unsigned short> charBuffer(2, 0);
|
|
263
|
-
|
|
264
|
-
for (int i = 0; i < charCount; i++) {
|
|
265
|
-
double cLeft = 0;
|
|
266
|
-
double cRight = 0;
|
|
267
|
-
double cTop = 0;
|
|
268
|
-
double cBottom = 0;
|
|
269
|
-
if (!g_fns.textGetCharBox(textPage, i, &cLeft, &cRight, &cBottom, &cTop)) continue;
|
|
270
|
-
|
|
271
|
-
bool intersects = !(cRight < rectLeft || cLeft > rectRight || cTop < rectBottom || cBottom > rectTop);
|
|
272
|
-
if (!intersects) continue;
|
|
273
|
-
|
|
274
|
-
int written = g_fns.textGetText(textPage, i, 1, charBuffer.data());
|
|
275
|
-
if (written > 0 && charBuffer[0] != 0) {
|
|
276
|
-
selectedText.push_back(static_cast<char16_t>(charBuffer[0]));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
bool added = false;
|
|
280
|
-
for (auto &line : lines) {
|
|
281
|
-
if (std::abs(line.top - cTop) <= lineTolerance || std::abs(line.bottom - cBottom) <= lineTolerance) {
|
|
282
|
-
line.left = std::min(line.left, cLeft);
|
|
283
|
-
line.right = std::max(line.right, cRight);
|
|
284
|
-
line.top = std::max(line.top, cTop);
|
|
285
|
-
line.bottom = std::min(line.bottom, cBottom);
|
|
286
|
-
added = true;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (!added) {
|
|
291
|
-
lines.push_back({cLeft, cRight, cTop, cBottom});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
std::vector<jfloat> rects;
|
|
296
|
-
rects.reserve(lines.size() * 4);
|
|
297
|
-
for (const auto &line : lines) {
|
|
298
|
-
float x = static_cast<float>(line.left / pageWidth);
|
|
299
|
-
float y = static_cast<float>((pageHeight - line.top) / pageHeight);
|
|
300
|
-
float w = static_cast<float>((line.right - line.left) / pageWidth);
|
|
301
|
-
float h = static_cast<float>((line.top - line.bottom) / pageHeight);
|
|
302
|
-
|
|
303
|
-
rects.push_back(std::max(0.0f, std::min(1.0f, x)));
|
|
304
|
-
rects.push_back(std::max(0.0f, std::min(1.0f, y)));
|
|
305
|
-
rects.push_back(std::max(0.0f, std::min(1.0f, w)));
|
|
306
|
-
rects.push_back(std::max(0.0f, std::min(1.0f, h)));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
jfloatArray rectArray = env->NewFloatArray(static_cast<jsize>(rects.size()));
|
|
310
|
-
if (rectArray && !rects.empty()) {
|
|
311
|
-
env->SetFloatArrayRegion(rectArray, 0, static_cast<jsize>(rects.size()), rects.data());
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
jstring text = selectedText.empty()
|
|
315
|
-
? env->NewStringUTF("")
|
|
316
|
-
: env->NewString(reinterpret_cast<const jchar *>(selectedText.data()), static_cast<jsize>(selectedText.size()));
|
|
317
|
-
|
|
318
|
-
jobject selection = env->NewObject(selectionClass, ctor, text, rectArray);
|
|
319
|
-
|
|
320
|
-
if (rectArray) env->DeleteLocalRef(rectArray);
|
|
321
|
-
if (text) env->DeleteLocalRef(text);
|
|
322
|
-
|
|
323
|
-
g_fns.textClosePage(textPage);
|
|
324
|
-
g_fns.closePage(page);
|
|
325
|
-
|
|
326
|
-
return selection;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
330
|
-
Java_com_papyrus_engine_PapyrusTextSearch_nativeSearch(JNIEnv *env, jclass, jlong docPtr, jint pageCount, jstring query) {
|
|
331
|
-
if (!LoadPdfium()) return nullptr;
|
|
332
|
-
if (!docPtr || pageCount <= 0 || query == nullptr) return nullptr;
|
|
333
|
-
|
|
334
|
-
const jchar *queryChars = env->GetStringChars(query, nullptr);
|
|
335
|
-
jsize queryLen = env->GetStringLength(query);
|
|
336
|
-
if (!queryChars || queryLen == 0) {
|
|
337
|
-
if (queryChars) env->ReleaseStringChars(query, queryChars);
|
|
338
|
-
return nullptr;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
std::vector<unsigned short> wideQuery(static_cast<size_t>(queryLen) + 1, 0);
|
|
342
|
-
for (jsize i = 0; i < queryLen; i++) {
|
|
343
|
-
wideQuery[i] = static_cast<unsigned short>(queryChars[i]);
|
|
344
|
-
}
|
|
345
|
-
env->ReleaseStringChars(query, queryChars);
|
|
346
|
-
|
|
347
|
-
FPDF_DOCUMENT doc = reinterpret_cast<FPDF_DOCUMENT>(docPtr);
|
|
348
|
-
jobjectArray result = SearchDocument(env, doc, pageCount, wideQuery.data(), queryLen);
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
353
|
-
Java_com_papyrus_engine_PapyrusTextSearch_nativeSearchFile(JNIEnv *env, jclass, jstring filePath, jstring query) {
|
|
354
|
-
if (!LoadPdfium()) return nullptr;
|
|
355
|
-
if (!filePath || !query) return nullptr;
|
|
356
|
-
|
|
357
|
-
const jchar *queryChars = env->GetStringChars(query, nullptr);
|
|
358
|
-
jsize queryLen = env->GetStringLength(query);
|
|
359
|
-
if (!queryChars || queryLen == 0) {
|
|
360
|
-
if (queryChars) env->ReleaseStringChars(query, queryChars);
|
|
361
|
-
return nullptr;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
std::vector<unsigned short> wideQuery(static_cast<size_t>(queryLen) + 1, 0);
|
|
365
|
-
for (jsize i = 0; i < queryLen; i++) {
|
|
366
|
-
wideQuery[i] = static_cast<unsigned short>(queryChars[i]);
|
|
367
|
-
}
|
|
368
|
-
env->ReleaseStringChars(query, queryChars);
|
|
369
|
-
|
|
370
|
-
const char *path = env->GetStringUTFChars(filePath, nullptr);
|
|
371
|
-
if (!path) return nullptr;
|
|
372
|
-
|
|
373
|
-
FPDF_DOCUMENT doc = g_fns.loadDocument(path, nullptr);
|
|
374
|
-
env->ReleaseStringUTFChars(filePath, path);
|
|
375
|
-
if (!doc) return nullptr;
|
|
376
|
-
|
|
377
|
-
int pageCount = g_fns.getDocPageCount(doc);
|
|
378
|
-
jobjectArray result = SearchDocument(env, doc, pageCount, wideQuery.data(), queryLen);
|
|
379
|
-
g_fns.closeDocument(doc);
|
|
380
|
-
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
extern "C" JNIEXPORT jobject JNICALL
|
|
385
|
-
Java_com_papyrus_engine_PapyrusTextSelect_nativeSelectText(JNIEnv *env, jclass, jlong docPtr, jint pageIndex, jfloat x, jfloat y, jfloat width, jfloat height) {
|
|
386
|
-
if (!LoadPdfium()) return nullptr;
|
|
387
|
-
if (!docPtr) return nullptr;
|
|
388
|
-
|
|
389
|
-
FPDF_DOCUMENT doc = reinterpret_cast<FPDF_DOCUMENT>(docPtr);
|
|
390
|
-
return BuildSelection(env, doc, pageIndex, x, y, width, height);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
extern "C" JNIEXPORT jobject JNICALL
|
|
394
|
-
Java_com_papyrus_engine_PapyrusTextSelect_nativeSelectTextFile(JNIEnv *env, jclass, jstring filePath, jint pageIndex, jfloat x, jfloat y, jfloat width, jfloat height) {
|
|
395
|
-
if (!LoadPdfium()) return nullptr;
|
|
396
|
-
if (!filePath) return nullptr;
|
|
397
|
-
|
|
398
|
-
const char *path = env->GetStringUTFChars(filePath, nullptr);
|
|
399
|
-
if (!path) return nullptr;
|
|
400
|
-
|
|
401
|
-
FPDF_DOCUMENT doc = g_fns.loadDocument(path, nullptr);
|
|
402
|
-
env->ReleaseStringUTFChars(filePath, path);
|
|
403
|
-
if (!doc) return nullptr;
|
|
404
|
-
|
|
405
|
-
jobject selection = BuildSelection(env, doc, pageIndex, x, y, width, height);
|
|
406
|
-
g_fns.closeDocument(doc);
|
|
407
|
-
return selection;
|
|
408
|
-
}
|
|
1
|
+
#include <jni.h>
|
|
2
|
+
#include <android/log.h>
|
|
3
|
+
#include <dlfcn.h>
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
#define LOG_TAG "PapyrusText"
|
|
11
|
+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
12
|
+
|
|
13
|
+
typedef void *FPDF_DOCUMENT;
|
|
14
|
+
typedef void *FPDF_PAGE;
|
|
15
|
+
typedef void *FPDF_TEXTPAGE;
|
|
16
|
+
typedef void *FPDF_SCHHANDLE;
|
|
17
|
+
typedef const unsigned short *FPDF_WIDESTRING;
|
|
18
|
+
|
|
19
|
+
constexpr int kMaxHits = 200;
|
|
20
|
+
|
|
21
|
+
struct PdfiumFns {
|
|
22
|
+
FPDF_PAGE (*loadPage)(FPDF_DOCUMENT, int);
|
|
23
|
+
void (*closePage)(FPDF_PAGE);
|
|
24
|
+
double (*getPageWidth)(FPDF_PAGE);
|
|
25
|
+
double (*getPageHeight)(FPDF_PAGE);
|
|
26
|
+
int (*getDocPageCount)(FPDF_DOCUMENT);
|
|
27
|
+
FPDF_DOCUMENT (*loadDocument)(const char *, const char *);
|
|
28
|
+
void (*closeDocument)(FPDF_DOCUMENT);
|
|
29
|
+
FPDF_TEXTPAGE (*textLoadPage)(FPDF_PAGE);
|
|
30
|
+
void (*textClosePage)(FPDF_TEXTPAGE);
|
|
31
|
+
FPDF_SCHHANDLE (*textFindStart)(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int);
|
|
32
|
+
int (*textFindNext)(FPDF_SCHHANDLE);
|
|
33
|
+
void (*textFindClose)(FPDF_SCHHANDLE);
|
|
34
|
+
int (*textGetSchResultIndex)(FPDF_SCHHANDLE);
|
|
35
|
+
int (*textGetSchCount)(FPDF_SCHHANDLE);
|
|
36
|
+
int (*textGetCharBox)(FPDF_TEXTPAGE, int, double *, double *, double *, double *);
|
|
37
|
+
int (*textCountChars)(FPDF_TEXTPAGE);
|
|
38
|
+
int (*textGetText)(FPDF_TEXTPAGE, int, int, unsigned short *);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
static void *g_pdfium = nullptr;
|
|
42
|
+
static PdfiumFns g_fns = {};
|
|
43
|
+
static bool g_loaded = false;
|
|
44
|
+
|
|
45
|
+
static bool LoadPdfium() {
|
|
46
|
+
if (g_loaded) return g_pdfium != nullptr;
|
|
47
|
+
|
|
48
|
+
g_pdfium = dlopen("libmodpdfium.so", RTLD_LAZY);
|
|
49
|
+
if (!g_pdfium) {
|
|
50
|
+
LOGE("Failed to load libmodpdfium.so");
|
|
51
|
+
g_loaded = true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
g_fns.loadPage = reinterpret_cast<FPDF_PAGE (*)(FPDF_DOCUMENT, int)>(dlsym(g_pdfium, "FPDF_LoadPage"));
|
|
56
|
+
g_fns.closePage = reinterpret_cast<void (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_ClosePage"));
|
|
57
|
+
g_fns.getPageWidth = reinterpret_cast<double (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_GetPageWidth"));
|
|
58
|
+
g_fns.getPageHeight = reinterpret_cast<double (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDF_GetPageHeight"));
|
|
59
|
+
g_fns.getDocPageCount = reinterpret_cast<int (*)(FPDF_DOCUMENT)>(dlsym(g_pdfium, "FPDF_GetPageCount"));
|
|
60
|
+
g_fns.loadDocument = reinterpret_cast<FPDF_DOCUMENT (*)(const char *, const char *)>(dlsym(g_pdfium, "FPDF_LoadDocument"));
|
|
61
|
+
g_fns.closeDocument = reinterpret_cast<void (*)(FPDF_DOCUMENT)>(dlsym(g_pdfium, "FPDF_CloseDocument"));
|
|
62
|
+
g_fns.textLoadPage = reinterpret_cast<FPDF_TEXTPAGE (*)(FPDF_PAGE)>(dlsym(g_pdfium, "FPDFText_LoadPage"));
|
|
63
|
+
g_fns.textClosePage = reinterpret_cast<void (*)(FPDF_TEXTPAGE)>(dlsym(g_pdfium, "FPDFText_ClosePage"));
|
|
64
|
+
g_fns.textFindStart = reinterpret_cast<FPDF_SCHHANDLE (*)(FPDF_TEXTPAGE, FPDF_WIDESTRING, int, int)>(dlsym(g_pdfium, "FPDFText_FindStart"));
|
|
65
|
+
g_fns.textFindNext = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_FindNext"));
|
|
66
|
+
g_fns.textFindClose = reinterpret_cast<void (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_FindClose"));
|
|
67
|
+
g_fns.textGetSchResultIndex = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_GetSchResultIndex"));
|
|
68
|
+
g_fns.textGetSchCount = reinterpret_cast<int (*)(FPDF_SCHHANDLE)>(dlsym(g_pdfium, "FPDFText_GetSchCount"));
|
|
69
|
+
g_fns.textGetCharBox = reinterpret_cast<int (*)(FPDF_TEXTPAGE, int, double *, double *, double *, double *)>(dlsym(g_pdfium, "FPDFText_GetCharBox"));
|
|
70
|
+
g_fns.textCountChars = reinterpret_cast<int (*)(FPDF_TEXTPAGE)>(dlsym(g_pdfium, "FPDFText_CountChars"));
|
|
71
|
+
g_fns.textGetText = reinterpret_cast<int (*)(FPDF_TEXTPAGE, int, int, unsigned short *)>(dlsym(g_pdfium, "FPDFText_GetText"));
|
|
72
|
+
|
|
73
|
+
if (!g_fns.loadPage || !g_fns.closePage || !g_fns.getPageWidth || !g_fns.getPageHeight ||
|
|
74
|
+
!g_fns.getDocPageCount || !g_fns.loadDocument || !g_fns.closeDocument ||
|
|
75
|
+
!g_fns.textLoadPage || !g_fns.textClosePage || !g_fns.textFindStart || !g_fns.textFindNext ||
|
|
76
|
+
!g_fns.textFindClose || !g_fns.textGetSchResultIndex || !g_fns.textGetSchCount ||
|
|
77
|
+
!g_fns.textGetCharBox || !g_fns.textCountChars || !g_fns.textGetText) {
|
|
78
|
+
LOGE("Failed to load required PDFium symbols");
|
|
79
|
+
g_loaded = true;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
g_loaded = true;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static jstring BuildPreview(JNIEnv *env, FPDF_TEXTPAGE textPage, int startIndex, int count) {
|
|
88
|
+
int totalChars = g_fns.textCountChars(textPage);
|
|
89
|
+
if (totalChars <= 0) return env->NewStringUTF("");
|
|
90
|
+
|
|
91
|
+
int previewStart = std::max(0, startIndex - 20);
|
|
92
|
+
int previewCount = std::min(totalChars - previewStart, count + 40);
|
|
93
|
+
if (previewCount <= 0) return env->NewStringUTF("");
|
|
94
|
+
|
|
95
|
+
std::vector<unsigned short> buffer(previewCount + 1, 0);
|
|
96
|
+
int written = g_fns.textGetText(textPage, previewStart, previewCount, buffer.data());
|
|
97
|
+
int length = written > 0 ? written - 1 : 0;
|
|
98
|
+
if (length <= 0) return env->NewStringUTF("");
|
|
99
|
+
|
|
100
|
+
return env->NewString(reinterpret_cast<const jchar *>(buffer.data()), length);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static jfloatArray BuildRect(JNIEnv *env, FPDF_TEXTPAGE textPage, FPDF_PAGE page, int startIndex, int count) {
|
|
104
|
+
double left = 0;
|
|
105
|
+
double right = 0;
|
|
106
|
+
double top = 0;
|
|
107
|
+
double bottom = 0;
|
|
108
|
+
bool hasBox = false;
|
|
109
|
+
|
|
110
|
+
for (int i = 0; i < count; i++) {
|
|
111
|
+
double cLeft = 0;
|
|
112
|
+
double cRight = 0;
|
|
113
|
+
double cTop = 0;
|
|
114
|
+
double cBottom = 0;
|
|
115
|
+
if (!g_fns.textGetCharBox(textPage, startIndex + i, &cLeft, &cRight, &cBottom, &cTop)) continue;
|
|
116
|
+
if (!hasBox) {
|
|
117
|
+
left = cLeft;
|
|
118
|
+
right = cRight;
|
|
119
|
+
top = cTop;
|
|
120
|
+
bottom = cBottom;
|
|
121
|
+
hasBox = true;
|
|
122
|
+
} else {
|
|
123
|
+
left = std::min(left, cLeft);
|
|
124
|
+
right = std::max(right, cRight);
|
|
125
|
+
top = std::max(top, cTop);
|
|
126
|
+
bottom = std::min(bottom, cBottom);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!hasBox) {
|
|
131
|
+
return env->NewFloatArray(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
double pageWidth = g_fns.getPageWidth(page);
|
|
135
|
+
double pageHeight = g_fns.getPageHeight(page);
|
|
136
|
+
if (pageWidth <= 0 || pageHeight <= 0) {
|
|
137
|
+
return env->NewFloatArray(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
float x = static_cast<float>(left / pageWidth);
|
|
141
|
+
float y = static_cast<float>((pageHeight - top) / pageHeight);
|
|
142
|
+
float w = static_cast<float>((right - left) / pageWidth);
|
|
143
|
+
float h = static_cast<float>((top - bottom) / pageHeight);
|
|
144
|
+
|
|
145
|
+
jfloat rects[4] = {
|
|
146
|
+
std::max(0.0f, std::min(1.0f, x)),
|
|
147
|
+
std::max(0.0f, std::min(1.0f, y)),
|
|
148
|
+
std::max(0.0f, std::min(1.0f, w)),
|
|
149
|
+
std::max(0.0f, std::min(1.0f, h)),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
jfloatArray array = env->NewFloatArray(4);
|
|
153
|
+
env->SetFloatArrayRegion(array, 0, 4, rects);
|
|
154
|
+
return array;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static jobjectArray SearchDocument(JNIEnv *env, FPDF_DOCUMENT doc, int pageCount, const unsigned short *wideQuery, jsize queryLen) {
|
|
158
|
+
if (!doc || pageCount <= 0 || !wideQuery || queryLen <= 0) return nullptr;
|
|
159
|
+
|
|
160
|
+
jclass hitClass = env->FindClass("com/papyrus/engine/PapyrusTextHit");
|
|
161
|
+
if (!hitClass) return nullptr;
|
|
162
|
+
jmethodID ctor = env->GetMethodID(hitClass, "<init>", "(IILjava/lang/String;[F)V");
|
|
163
|
+
if (!ctor) return nullptr;
|
|
164
|
+
|
|
165
|
+
std::vector<jobject> hits;
|
|
166
|
+
hits.reserve(32);
|
|
167
|
+
|
|
168
|
+
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
|
169
|
+
FPDF_PAGE page = g_fns.loadPage(doc, pageIndex);
|
|
170
|
+
if (!page) continue;
|
|
171
|
+
FPDF_TEXTPAGE textPage = g_fns.textLoadPage(page);
|
|
172
|
+
if (!textPage) {
|
|
173
|
+
g_fns.closePage(page);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
FPDF_SCHHANDLE handle = g_fns.textFindStart(textPage, wideQuery, 0, 0);
|
|
178
|
+
if (handle) {
|
|
179
|
+
int matchIndex = 0;
|
|
180
|
+
while (g_fns.textFindNext(handle)) {
|
|
181
|
+
int startIndex = g_fns.textGetSchResultIndex(handle);
|
|
182
|
+
int count = g_fns.textGetSchCount(handle);
|
|
183
|
+
int totalChars = g_fns.textCountChars(textPage);
|
|
184
|
+
if (startIndex < 0 || startIndex >= totalChars) continue;
|
|
185
|
+
if (count <= 0) count = 1;
|
|
186
|
+
if (startIndex + count > totalChars) {
|
|
187
|
+
count = std::max(1, totalChars - startIndex);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
jstring preview = BuildPreview(env, textPage, startIndex, count);
|
|
191
|
+
jfloatArray rects = BuildRect(env, textPage, page, startIndex, count);
|
|
192
|
+
jobject hit = env->NewObject(hitClass, ctor, pageIndex, matchIndex, preview, rects);
|
|
193
|
+
if (hit) {
|
|
194
|
+
hits.push_back(hit);
|
|
195
|
+
}
|
|
196
|
+
matchIndex++;
|
|
197
|
+
if (static_cast<int>(hits.size()) >= kMaxHits) break;
|
|
198
|
+
}
|
|
199
|
+
g_fns.textFindClose(handle);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
g_fns.textClosePage(textPage);
|
|
203
|
+
g_fns.closePage(page);
|
|
204
|
+
if (static_cast<int>(hits.size()) >= kMaxHits) break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
jobjectArray result = env->NewObjectArray(static_cast<jsize>(hits.size()), hitClass, nullptr);
|
|
208
|
+
for (jsize i = 0; i < static_cast<jsize>(hits.size()); i++) {
|
|
209
|
+
env->SetObjectArrayElement(result, i, hits[i]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static jobject BuildSelection(JNIEnv *env, FPDF_DOCUMENT doc, int pageIndex, double normX, double normY, double normW, double normH) {
|
|
216
|
+
if (!doc || pageIndex < 0 || normW <= 0 || normH <= 0) return nullptr;
|
|
217
|
+
|
|
218
|
+
jclass selectionClass = env->FindClass("com/papyrus/engine/PapyrusTextSelection");
|
|
219
|
+
if (!selectionClass) return nullptr;
|
|
220
|
+
jmethodID ctor = env->GetMethodID(selectionClass, "<init>", "(Ljava/lang/String;[F)V");
|
|
221
|
+
if (!ctor) return nullptr;
|
|
222
|
+
|
|
223
|
+
FPDF_PAGE page = g_fns.loadPage(doc, pageIndex);
|
|
224
|
+
if (!page) return nullptr;
|
|
225
|
+
FPDF_TEXTPAGE textPage = g_fns.textLoadPage(page);
|
|
226
|
+
if (!textPage) {
|
|
227
|
+
g_fns.closePage(page);
|
|
228
|
+
return nullptr;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
double pageWidth = g_fns.getPageWidth(page);
|
|
232
|
+
double pageHeight = g_fns.getPageHeight(page);
|
|
233
|
+
if (pageWidth <= 0 || pageHeight <= 0) {
|
|
234
|
+
g_fns.textClosePage(textPage);
|
|
235
|
+
g_fns.closePage(page);
|
|
236
|
+
return nullptr;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
double rectLeft = normX * pageWidth;
|
|
240
|
+
double rectTop = pageHeight - (normY * pageHeight);
|
|
241
|
+
double rectRight = rectLeft + (normW * pageWidth);
|
|
242
|
+
double rectBottom = rectTop - (normH * pageHeight);
|
|
243
|
+
|
|
244
|
+
struct LineRect {
|
|
245
|
+
double left;
|
|
246
|
+
double right;
|
|
247
|
+
double top;
|
|
248
|
+
double bottom;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
std::vector<LineRect> lines;
|
|
252
|
+
std::u16string selectedText;
|
|
253
|
+
|
|
254
|
+
int charCount = g_fns.textCountChars(textPage);
|
|
255
|
+
if (charCount <= 0) {
|
|
256
|
+
g_fns.textClosePage(textPage);
|
|
257
|
+
g_fns.closePage(page);
|
|
258
|
+
return nullptr;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const double lineTolerance = 2.5;
|
|
262
|
+
std::vector<unsigned short> charBuffer(2, 0);
|
|
263
|
+
|
|
264
|
+
for (int i = 0; i < charCount; i++) {
|
|
265
|
+
double cLeft = 0;
|
|
266
|
+
double cRight = 0;
|
|
267
|
+
double cTop = 0;
|
|
268
|
+
double cBottom = 0;
|
|
269
|
+
if (!g_fns.textGetCharBox(textPage, i, &cLeft, &cRight, &cBottom, &cTop)) continue;
|
|
270
|
+
|
|
271
|
+
bool intersects = !(cRight < rectLeft || cLeft > rectRight || cTop < rectBottom || cBottom > rectTop);
|
|
272
|
+
if (!intersects) continue;
|
|
273
|
+
|
|
274
|
+
int written = g_fns.textGetText(textPage, i, 1, charBuffer.data());
|
|
275
|
+
if (written > 0 && charBuffer[0] != 0) {
|
|
276
|
+
selectedText.push_back(static_cast<char16_t>(charBuffer[0]));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
bool added = false;
|
|
280
|
+
for (auto &line : lines) {
|
|
281
|
+
if (std::abs(line.top - cTop) <= lineTolerance || std::abs(line.bottom - cBottom) <= lineTolerance) {
|
|
282
|
+
line.left = std::min(line.left, cLeft);
|
|
283
|
+
line.right = std::max(line.right, cRight);
|
|
284
|
+
line.top = std::max(line.top, cTop);
|
|
285
|
+
line.bottom = std::min(line.bottom, cBottom);
|
|
286
|
+
added = true;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!added) {
|
|
291
|
+
lines.push_back({cLeft, cRight, cTop, cBottom});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
std::vector<jfloat> rects;
|
|
296
|
+
rects.reserve(lines.size() * 4);
|
|
297
|
+
for (const auto &line : lines) {
|
|
298
|
+
float x = static_cast<float>(line.left / pageWidth);
|
|
299
|
+
float y = static_cast<float>((pageHeight - line.top) / pageHeight);
|
|
300
|
+
float w = static_cast<float>((line.right - line.left) / pageWidth);
|
|
301
|
+
float h = static_cast<float>((line.top - line.bottom) / pageHeight);
|
|
302
|
+
|
|
303
|
+
rects.push_back(std::max(0.0f, std::min(1.0f, x)));
|
|
304
|
+
rects.push_back(std::max(0.0f, std::min(1.0f, y)));
|
|
305
|
+
rects.push_back(std::max(0.0f, std::min(1.0f, w)));
|
|
306
|
+
rects.push_back(std::max(0.0f, std::min(1.0f, h)));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
jfloatArray rectArray = env->NewFloatArray(static_cast<jsize>(rects.size()));
|
|
310
|
+
if (rectArray && !rects.empty()) {
|
|
311
|
+
env->SetFloatArrayRegion(rectArray, 0, static_cast<jsize>(rects.size()), rects.data());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
jstring text = selectedText.empty()
|
|
315
|
+
? env->NewStringUTF("")
|
|
316
|
+
: env->NewString(reinterpret_cast<const jchar *>(selectedText.data()), static_cast<jsize>(selectedText.size()));
|
|
317
|
+
|
|
318
|
+
jobject selection = env->NewObject(selectionClass, ctor, text, rectArray);
|
|
319
|
+
|
|
320
|
+
if (rectArray) env->DeleteLocalRef(rectArray);
|
|
321
|
+
if (text) env->DeleteLocalRef(text);
|
|
322
|
+
|
|
323
|
+
g_fns.textClosePage(textPage);
|
|
324
|
+
g_fns.closePage(page);
|
|
325
|
+
|
|
326
|
+
return selection;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
330
|
+
Java_com_papyrus_engine_PapyrusTextSearch_nativeSearch(JNIEnv *env, jclass, jlong docPtr, jint pageCount, jstring query) {
|
|
331
|
+
if (!LoadPdfium()) return nullptr;
|
|
332
|
+
if (!docPtr || pageCount <= 0 || query == nullptr) return nullptr;
|
|
333
|
+
|
|
334
|
+
const jchar *queryChars = env->GetStringChars(query, nullptr);
|
|
335
|
+
jsize queryLen = env->GetStringLength(query);
|
|
336
|
+
if (!queryChars || queryLen == 0) {
|
|
337
|
+
if (queryChars) env->ReleaseStringChars(query, queryChars);
|
|
338
|
+
return nullptr;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
std::vector<unsigned short> wideQuery(static_cast<size_t>(queryLen) + 1, 0);
|
|
342
|
+
for (jsize i = 0; i < queryLen; i++) {
|
|
343
|
+
wideQuery[i] = static_cast<unsigned short>(queryChars[i]);
|
|
344
|
+
}
|
|
345
|
+
env->ReleaseStringChars(query, queryChars);
|
|
346
|
+
|
|
347
|
+
FPDF_DOCUMENT doc = reinterpret_cast<FPDF_DOCUMENT>(docPtr);
|
|
348
|
+
jobjectArray result = SearchDocument(env, doc, pageCount, wideQuery.data(), queryLen);
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
353
|
+
Java_com_papyrus_engine_PapyrusTextSearch_nativeSearchFile(JNIEnv *env, jclass, jstring filePath, jstring query) {
|
|
354
|
+
if (!LoadPdfium()) return nullptr;
|
|
355
|
+
if (!filePath || !query) return nullptr;
|
|
356
|
+
|
|
357
|
+
const jchar *queryChars = env->GetStringChars(query, nullptr);
|
|
358
|
+
jsize queryLen = env->GetStringLength(query);
|
|
359
|
+
if (!queryChars || queryLen == 0) {
|
|
360
|
+
if (queryChars) env->ReleaseStringChars(query, queryChars);
|
|
361
|
+
return nullptr;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
std::vector<unsigned short> wideQuery(static_cast<size_t>(queryLen) + 1, 0);
|
|
365
|
+
for (jsize i = 0; i < queryLen; i++) {
|
|
366
|
+
wideQuery[i] = static_cast<unsigned short>(queryChars[i]);
|
|
367
|
+
}
|
|
368
|
+
env->ReleaseStringChars(query, queryChars);
|
|
369
|
+
|
|
370
|
+
const char *path = env->GetStringUTFChars(filePath, nullptr);
|
|
371
|
+
if (!path) return nullptr;
|
|
372
|
+
|
|
373
|
+
FPDF_DOCUMENT doc = g_fns.loadDocument(path, nullptr);
|
|
374
|
+
env->ReleaseStringUTFChars(filePath, path);
|
|
375
|
+
if (!doc) return nullptr;
|
|
376
|
+
|
|
377
|
+
int pageCount = g_fns.getDocPageCount(doc);
|
|
378
|
+
jobjectArray result = SearchDocument(env, doc, pageCount, wideQuery.data(), queryLen);
|
|
379
|
+
g_fns.closeDocument(doc);
|
|
380
|
+
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
extern "C" JNIEXPORT jobject JNICALL
|
|
385
|
+
Java_com_papyrus_engine_PapyrusTextSelect_nativeSelectText(JNIEnv *env, jclass, jlong docPtr, jint pageIndex, jfloat x, jfloat y, jfloat width, jfloat height) {
|
|
386
|
+
if (!LoadPdfium()) return nullptr;
|
|
387
|
+
if (!docPtr) return nullptr;
|
|
388
|
+
|
|
389
|
+
FPDF_DOCUMENT doc = reinterpret_cast<FPDF_DOCUMENT>(docPtr);
|
|
390
|
+
return BuildSelection(env, doc, pageIndex, x, y, width, height);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
extern "C" JNIEXPORT jobject JNICALL
|
|
394
|
+
Java_com_papyrus_engine_PapyrusTextSelect_nativeSelectTextFile(JNIEnv *env, jclass, jstring filePath, jint pageIndex, jfloat x, jfloat y, jfloat width, jfloat height) {
|
|
395
|
+
if (!LoadPdfium()) return nullptr;
|
|
396
|
+
if (!filePath) return nullptr;
|
|
397
|
+
|
|
398
|
+
const char *path = env->GetStringUTFChars(filePath, nullptr);
|
|
399
|
+
if (!path) return nullptr;
|
|
400
|
+
|
|
401
|
+
FPDF_DOCUMENT doc = g_fns.loadDocument(path, nullptr);
|
|
402
|
+
env->ReleaseStringUTFChars(filePath, path);
|
|
403
|
+
if (!doc) return nullptr;
|
|
404
|
+
|
|
405
|
+
jobject selection = BuildSelection(env, doc, pageIndex, x, y, width, height);
|
|
406
|
+
g_fns.closeDocument(doc);
|
|
407
|
+
return selection;
|
|
408
|
+
}
|