@papyrus-sdk/engine-native 0.2.9 → 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 -40
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/cpp/CMakeLists.txt +42 -21
- 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
package/README.md
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# Papyrus Engine Native
|
|
2
|
-
|
|
3
|
-
React Native native engine for PDF rendering (PDFKit on iOS, PDFium on Android),
|
|
4
|
-
plus a WebView runtime for EPUB/TXT via `MobileDocumentEngine`.
|
|
5
|
-
|
|
6
|
-
## Install
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install @papyrus-sdk/engine-native @papyrus-sdk/core @papyrus-sdk/types
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
`@papyrus-sdk/core` and `@papyrus-sdk/types` are required peer dependencies.
|
|
13
|
-
|
|
14
|
-
For EPUB/TXT on mobile, also install:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install react-native-webview
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { MobileDocumentEngine, PapyrusPageView } from '@papyrus-sdk/engine-native';
|
|
24
|
-
import { findNodeHandle } from 'react-native';
|
|
25
|
-
|
|
26
|
-
const engine = new MobileDocumentEngine();
|
|
27
|
-
await engine.load({ type: 'pdf', source: { uri: 'https://example.com/book.pdf' } });
|
|
28
|
-
|
|
29
|
-
// Render the first page into a native view
|
|
30
|
-
const viewTag = findNodeHandle(pageViewRef.current);
|
|
31
|
-
if (viewTag) {
|
|
32
|
-
await engine.renderPage(0, viewTag, 2);
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Notes:
|
|
37
|
-
- Requires a native build (not Expo Go).
|
|
38
|
-
- Use `PapyrusPageView` for native PDF rendering.
|
|
1
|
+
# Papyrus Engine Native
|
|
2
|
+
|
|
3
|
+
React Native native engine for PDF rendering (PDFKit on iOS, PDFium on Android),
|
|
4
|
+
plus a WebView runtime for EPUB/TXT via `MobileDocumentEngine`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @papyrus-sdk/engine-native @papyrus-sdk/core @papyrus-sdk/types
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`@papyrus-sdk/core` and `@papyrus-sdk/types` are required peer dependencies.
|
|
13
|
+
|
|
14
|
+
For EPUB/TXT on mobile, also install:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install react-native-webview
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { MobileDocumentEngine, PapyrusPageView } from '@papyrus-sdk/engine-native';
|
|
24
|
+
import { findNodeHandle } from 'react-native';
|
|
25
|
+
|
|
26
|
+
const engine = new MobileDocumentEngine();
|
|
27
|
+
await engine.load({ type: 'pdf', source: { uri: 'https://example.com/book.pdf' } });
|
|
28
|
+
|
|
29
|
+
// Render the first page into a native view
|
|
30
|
+
const viewTag = findNodeHandle(pageViewRef.current);
|
|
31
|
+
if (viewTag) {
|
|
32
|
+
await engine.renderPage(0, viewTag, 2);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Notes:
|
|
37
|
+
- Requires a native build (not Expo Go).
|
|
38
|
+
- Use `PapyrusPageView` for native PDF rendering.
|
package/android/build.gradle
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
apply plugin: 'com.android.library'
|
|
2
|
-
|
|
3
|
-
def safeExtGet(prop, fallback) {
|
|
4
|
-
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
android {
|
|
8
|
-
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
9
|
-
ndkVersion safeExtGet('ndkVersion', '26.1.10909125')
|
|
10
|
-
namespace 'com.papyrus.engine'
|
|
11
|
-
|
|
12
|
-
defaultConfig {
|
|
13
|
-
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
14
|
-
targetSdkVersion safeExtGet('targetSdkVersion', 35)
|
|
15
|
-
consumerProguardFiles 'consumer-rules.pro'
|
|
16
|
-
externalNativeBuild {
|
|
17
|
-
cmake {
|
|
18
|
-
cppFlags "-std=c++17"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
sourceSets {
|
|
24
|
-
main {
|
|
25
|
-
java.srcDirs = ['src/main/java']
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
externalNativeBuild {
|
|
30
|
-
cmake {
|
|
31
|
-
path "src/main/cpp/CMakeLists.txt"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
dependencies {
|
|
37
|
-
implementation 'com.facebook.react:react-android'
|
|
38
|
-
implementation('io.github.oothp:pdfium-android:1.9.5-beta01') {
|
|
39
|
-
exclude group: 'com.android.support'
|
|
40
|
-
}
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
|
|
3
|
+
def safeExtGet(prop, fallback) {
|
|
4
|
+
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
android {
|
|
8
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
9
|
+
ndkVersion safeExtGet('ndkVersion', '26.1.10909125')
|
|
10
|
+
namespace 'com.papyrus.engine'
|
|
11
|
+
|
|
12
|
+
defaultConfig {
|
|
13
|
+
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
14
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 35)
|
|
15
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
16
|
+
externalNativeBuild {
|
|
17
|
+
cmake {
|
|
18
|
+
cppFlags "-std=c++17"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
sourceSets {
|
|
24
|
+
main {
|
|
25
|
+
java.srcDirs = ['src/main/java']
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
externalNativeBuild {
|
|
30
|
+
cmake {
|
|
31
|
+
path "src/main/cpp/CMakeLists.txt"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
dependencies {
|
|
37
|
+
implementation 'com.facebook.react:react-android'
|
|
38
|
+
implementation('io.github.oothp:pdfium-android:1.9.5-beta01') {
|
|
39
|
+
exclude group: 'com.android.support'
|
|
40
|
+
}
|
|
41
|
+
testImplementation 'junit:junit:4.13.2'
|
|
41
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
# Keep rules can be added here if needed.
|
|
1
|
+
# Keep rules can be added here if needed.
|
|
@@ -1,24 +1,45 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.18.1)
|
|
2
2
|
project(papyrus_text)
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
4
|
+
option(PAPYRUS_OUTLINE_HOST_TESTS "Build host tests for the outline loader" OFF)
|
|
5
|
+
|
|
6
|
+
function(papyrus_set_cxx17 target_name)
|
|
7
|
+
set_target_properties(${target_name} PROPERTIES
|
|
8
|
+
CXX_STANDARD 17
|
|
9
|
+
CXX_STANDARD_REQUIRED YES
|
|
10
|
+
)
|
|
11
|
+
endfunction()
|
|
12
|
+
|
|
13
|
+
if(ANDROID)
|
|
14
|
+
add_library(papyrus_text SHARED
|
|
15
|
+
papyrus_text_search.cpp
|
|
16
|
+
papyrus_outline.cpp
|
|
17
|
+
papyrus_outline_loader.cpp
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
papyrus_set_cxx17(papyrus_text)
|
|
21
|
+
target_compile_options(papyrus_text PRIVATE -Wall -Werror)
|
|
22
|
+
target_link_options(papyrus_text PRIVATE "-Wl,-z,max-page-size=16384")
|
|
23
|
+
|
|
24
|
+
find_library(log-lib log)
|
|
25
|
+
find_library(android-lib android)
|
|
26
|
+
find_library(dl-lib dl)
|
|
27
|
+
|
|
28
|
+
target_link_libraries(papyrus_text
|
|
29
|
+
${log-lib}
|
|
30
|
+
${android-lib}
|
|
31
|
+
${dl-lib}
|
|
32
|
+
)
|
|
33
|
+
endif()
|
|
34
|
+
|
|
35
|
+
if(PAPYRUS_OUTLINE_HOST_TESTS)
|
|
36
|
+
enable_testing()
|
|
37
|
+
|
|
38
|
+
add_executable(papyrus_outline_loader_test
|
|
39
|
+
papyrus_outline_loader.cpp
|
|
40
|
+
papyrus_outline_loader_test.cpp
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
papyrus_set_cxx17(papyrus_outline_loader_test)
|
|
44
|
+
add_test(NAME papyrus_outline_loader_test COMMAND papyrus_outline_loader_test)
|
|
45
|
+
endif()
|
|
@@ -1,71 +1,68 @@
|
|
|
1
|
+
#include "papyrus_outline_loader.h"
|
|
2
|
+
|
|
1
3
|
#include <jni.h>
|
|
2
4
|
#include <android/log.h>
|
|
3
5
|
#include <dlfcn.h>
|
|
4
6
|
|
|
5
|
-
#include <
|
|
6
|
-
#include <string>
|
|
7
|
+
#include <cstring>
|
|
7
8
|
#include <vector>
|
|
8
9
|
|
|
9
10
|
#define LOG_TAG "PapyrusOutline"
|
|
10
11
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
typedef void *FPDF_BOOKMARK;
|
|
14
|
-
typedef void *FPDF_DEST;
|
|
15
|
-
|
|
16
|
-
struct PdfiumFns {
|
|
17
|
-
FPDF_DOCUMENT (*loadDocument)(const char *, const char *);
|
|
18
|
-
void (*closeDocument)(FPDF_DOCUMENT);
|
|
19
|
-
FPDF_BOOKMARK (*bookmarkGetFirstChild)(FPDF_DOCUMENT, FPDF_BOOKMARK);
|
|
20
|
-
FPDF_BOOKMARK (*bookmarkGetNextSibling)(FPDF_DOCUMENT, FPDF_BOOKMARK);
|
|
21
|
-
unsigned long (*bookmarkGetTitle)(FPDF_BOOKMARK, void *, unsigned long);
|
|
22
|
-
FPDF_DEST (*bookmarkGetDest)(FPDF_DOCUMENT, FPDF_BOOKMARK);
|
|
23
|
-
int (*destGetPageIndex)(FPDF_DOCUMENT, FPDF_DEST);
|
|
24
|
-
};
|
|
13
|
+
namespace {
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
static PdfiumFns g_fns = {};
|
|
28
|
-
static bool g_loaded = false;
|
|
15
|
+
OutlineLoaderState g_outline_loader = {};
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
void *OutlineDlopen(void *, const char *filename, int flags) {
|
|
18
|
+
return dlopen(filename, flags);
|
|
19
|
+
}
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
OutlineLoaderDeps::PdfiumSymbol OutlineDlsym(void *, void *handle, const char *symbol_name) {
|
|
22
|
+
void *raw_symbol = dlsym(handle, symbol_name);
|
|
23
|
+
OutlineLoaderDeps::PdfiumSymbol symbol = nullptr;
|
|
24
|
+
static_assert(sizeof(symbol) == sizeof(raw_symbol),
|
|
25
|
+
"Expected dlsym results to match function pointer size");
|
|
26
|
+
std::memcpy(&symbol, &raw_symbol, sizeof(symbol));
|
|
27
|
+
return symbol;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
int OutlineDlclose(void *, void *handle) {
|
|
31
|
+
return dlclose(handle);
|
|
32
|
+
}
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
const OutlineLoaderDeps kOutlineLoaderDeps = {
|
|
35
|
+
nullptr,
|
|
36
|
+
&OutlineDlopen,
|
|
37
|
+
&OutlineDlsym,
|
|
38
|
+
&OutlineDlclose,
|
|
39
|
+
"libmodpdfium.so",
|
|
40
|
+
RTLD_LAZY,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
bool LoadPdfium() {
|
|
44
|
+
const OutlineLoadAttemptResult result =
|
|
45
|
+
EnsureOutlinePdfiumLoaded(&g_outline_loader, &kOutlineLoaderDeps);
|
|
46
|
+
if (!result.loaded && result.transitioned_to_unsupported) {
|
|
51
47
|
LOGE("Failed to load required PDFium symbols for outline");
|
|
52
|
-
g_loaded = true;
|
|
53
|
-
return false;
|
|
54
48
|
}
|
|
49
|
+
return result.loaded;
|
|
50
|
+
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
return
|
|
52
|
+
PdfiumOutlineFns &OutlineFns() {
|
|
53
|
+
return g_outline_loader.fns;
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
unsigned long length =
|
|
56
|
+
jstring GetBookmarkTitle(JNIEnv *env, FPDF_BOOKMARK bookmark) {
|
|
57
|
+
unsigned long length = OutlineFns().bookmarkGetTitle(bookmark, nullptr, 0);
|
|
62
58
|
if (length <= 2) {
|
|
63
59
|
return env->NewStringUTF("");
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
std::vector<unsigned short> buffer((length / 2) + 1, 0);
|
|
67
|
-
unsigned long written =
|
|
68
|
-
int chars = written > 0 ? static_cast<int>(written / 2) - 1
|
|
63
|
+
unsigned long written = OutlineFns().bookmarkGetTitle(bookmark, buffer.data(), length);
|
|
64
|
+
int chars = written > 0 ? static_cast<int>(written / 2) - 1
|
|
65
|
+
: static_cast<int>(length / 2) - 1;
|
|
69
66
|
if (chars <= 0) {
|
|
70
67
|
return env->NewStringUTF("");
|
|
71
68
|
}
|
|
@@ -73,30 +70,39 @@ static jstring GetBookmarkTitle(JNIEnv *env, FPDF_BOOKMARK bookmark) {
|
|
|
73
70
|
return env->NewString(reinterpret_cast<const jchar *>(buffer.data()), chars);
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
|
|
73
|
+
jobjectArray BuildOutlineItems(JNIEnv *env,
|
|
74
|
+
FPDF_DOCUMENT doc,
|
|
75
|
+
FPDF_BOOKMARK parent,
|
|
76
|
+
jclass item_class,
|
|
77
|
+
jmethodID ctor) {
|
|
77
78
|
std::vector<jobject> items;
|
|
78
79
|
|
|
79
|
-
for (FPDF_BOOKMARK child =
|
|
80
|
-
child =
|
|
80
|
+
for (FPDF_BOOKMARK child = OutlineFns().bookmarkGetFirstChild(doc, parent); child;
|
|
81
|
+
child = OutlineFns().bookmarkGetNextSibling(doc, child)) {
|
|
81
82
|
jstring title = GetBookmarkTitle(env, child);
|
|
82
|
-
int
|
|
83
|
-
FPDF_DEST dest =
|
|
84
|
-
if (dest) {
|
|
85
|
-
|
|
83
|
+
int page_index = -1;
|
|
84
|
+
FPDF_DEST dest = OutlineFns().bookmarkGetDest(doc, child);
|
|
85
|
+
if (dest != nullptr) {
|
|
86
|
+
page_index = OutlineFns().destGetPageIndex(doc, dest);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
jobjectArray children = BuildOutlineItems(env, doc, child,
|
|
89
|
-
jobject item = env->NewObject(
|
|
89
|
+
jobjectArray children = BuildOutlineItems(env, doc, child, item_class, ctor);
|
|
90
|
+
jobject item = env->NewObject(item_class, ctor, title, page_index, children);
|
|
90
91
|
|
|
91
|
-
if (title)
|
|
92
|
-
|
|
92
|
+
if (title != nullptr) {
|
|
93
|
+
env->DeleteLocalRef(title);
|
|
94
|
+
}
|
|
95
|
+
if (children != nullptr) {
|
|
96
|
+
env->DeleteLocalRef(children);
|
|
97
|
+
}
|
|
93
98
|
|
|
94
|
-
if (item) {
|
|
99
|
+
if (item != nullptr) {
|
|
95
100
|
items.push_back(item);
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
jobjectArray array =
|
|
104
|
+
jobjectArray array =
|
|
105
|
+
env->NewObjectArray(static_cast<jsize>(items.size()), item_class, nullptr);
|
|
100
106
|
for (jsize i = 0; i < static_cast<jsize>(items.size()); i++) {
|
|
101
107
|
env->SetObjectArrayElement(array, i, items[i]);
|
|
102
108
|
}
|
|
@@ -104,36 +110,64 @@ static jobjectArray BuildOutlineItems(JNIEnv *env, FPDF_DOCUMENT doc, FPDF_BOOKM
|
|
|
104
110
|
return array;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
jclass
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
jobjectArray BuildOutline(JNIEnv *env, FPDF_DOCUMENT doc) {
|
|
114
|
+
jclass item_class = env->FindClass("com/papyrus/engine/PapyrusOutlineItem");
|
|
115
|
+
if (item_class == nullptr) {
|
|
116
|
+
return nullptr;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
jmethodID ctor = env->GetMethodID(
|
|
120
|
+
item_class, "<init>", "(Ljava/lang/String;I[Lcom/papyrus/engine/PapyrusOutlineItem;)V");
|
|
121
|
+
if (ctor == nullptr) {
|
|
122
|
+
return nullptr;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return BuildOutlineItems(env, doc, nullptr, item_class, ctor);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} // namespace
|
|
129
|
+
|
|
130
|
+
extern "C" JNIEXPORT jboolean JNICALL
|
|
131
|
+
Java_com_papyrus_engine_PapyrusOutline_nativeIsOutlineSupported(JNIEnv *, jclass) {
|
|
132
|
+
return LoadPdfium() ? JNI_TRUE : JNI_FALSE;
|
|
113
133
|
}
|
|
114
134
|
|
|
115
135
|
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
116
136
|
Java_com_papyrus_engine_PapyrusOutline_nativeGetOutline(JNIEnv *env, jclass, jlong docPtr) {
|
|
117
|
-
if (!LoadPdfium())
|
|
118
|
-
|
|
137
|
+
if (!LoadPdfium()) {
|
|
138
|
+
return nullptr;
|
|
139
|
+
}
|
|
140
|
+
if (!docPtr) {
|
|
141
|
+
return nullptr;
|
|
142
|
+
}
|
|
119
143
|
|
|
120
144
|
FPDF_DOCUMENT doc = reinterpret_cast<FPDF_DOCUMENT>(docPtr);
|
|
121
145
|
return BuildOutline(env, doc);
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
125
|
-
Java_com_papyrus_engine_PapyrusOutline_nativeGetOutlineFile(JNIEnv *env,
|
|
126
|
-
|
|
127
|
-
|
|
149
|
+
Java_com_papyrus_engine_PapyrusOutline_nativeGetOutlineFile(JNIEnv *env,
|
|
150
|
+
jclass,
|
|
151
|
+
jstring filePath) {
|
|
152
|
+
if (!LoadPdfium()) {
|
|
153
|
+
return nullptr;
|
|
154
|
+
}
|
|
155
|
+
if (filePath == nullptr) {
|
|
156
|
+
return nullptr;
|
|
157
|
+
}
|
|
128
158
|
|
|
129
159
|
const char *path = env->GetStringUTFChars(filePath, nullptr);
|
|
130
|
-
if (
|
|
160
|
+
if (path == nullptr) {
|
|
161
|
+
return nullptr;
|
|
162
|
+
}
|
|
131
163
|
|
|
132
|
-
FPDF_DOCUMENT doc =
|
|
164
|
+
FPDF_DOCUMENT doc = OutlineFns().loadDocument(path, nullptr);
|
|
133
165
|
env->ReleaseStringUTFChars(filePath, path);
|
|
134
|
-
if (
|
|
166
|
+
if (doc == nullptr) {
|
|
167
|
+
return nullptr;
|
|
168
|
+
}
|
|
135
169
|
|
|
136
170
|
jobjectArray result = BuildOutline(env, doc);
|
|
137
|
-
|
|
171
|
+
OutlineFns().closeDocument(doc);
|
|
138
172
|
return result;
|
|
139
|
-
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#include "papyrus_outline_loader.h"
|
|
2
|
+
|
|
3
|
+
#include <cstring>
|
|
4
|
+
|
|
5
|
+
namespace {
|
|
6
|
+
|
|
7
|
+
void ClearOutlineFns(PdfiumOutlineFns *fns) {
|
|
8
|
+
if (fns == nullptr) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
*fns = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void CloseHandleIfPresent(OutlineLoaderState *state, const OutlineLoaderDeps *deps) {
|
|
16
|
+
if (state == nullptr || state->handle == nullptr) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (deps != nullptr && deps->dlclose_fn != nullptr) {
|
|
21
|
+
deps->dlclose_fn(deps->user_data, state->handle);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
state->handle = nullptr;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void TransitionToUnsupported(OutlineLoaderState *state, const OutlineLoaderDeps *deps) {
|
|
28
|
+
if (state == nullptr) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ClearOutlineFns(&state->fns);
|
|
33
|
+
CloseHandleIfPresent(state, deps);
|
|
34
|
+
state->load_state = OutlineLoadState::kUnsupported;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
template <typename FnType>
|
|
38
|
+
FnType ResolveOutlineSymbol(const OutlineLoaderDeps *deps, void *handle, const char *symbol_name) {
|
|
39
|
+
const OutlineLoaderDeps::PdfiumSymbol raw_symbol =
|
|
40
|
+
deps->dlsym_fn(deps->user_data, handle, symbol_name);
|
|
41
|
+
|
|
42
|
+
FnType resolved_symbol = nullptr;
|
|
43
|
+
static_assert(sizeof(resolved_symbol) == sizeof(raw_symbol),
|
|
44
|
+
"Expected function pointers to share the same size");
|
|
45
|
+
std::memcpy(&resolved_symbol, &raw_symbol, sizeof(resolved_symbol));
|
|
46
|
+
return resolved_symbol;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
PdfiumOutlineFns LoadOutlineFns(void *handle, const OutlineLoaderDeps *deps) {
|
|
50
|
+
PdfiumOutlineFns fns = {};
|
|
51
|
+
fns.loadDocument =
|
|
52
|
+
ResolveOutlineSymbol<decltype(fns.loadDocument)>(deps, handle, "FPDF_LoadDocument");
|
|
53
|
+
fns.closeDocument =
|
|
54
|
+
ResolveOutlineSymbol<decltype(fns.closeDocument)>(deps, handle, "FPDF_CloseDocument");
|
|
55
|
+
fns.bookmarkGetFirstChild = ResolveOutlineSymbol<decltype(fns.bookmarkGetFirstChild)>(
|
|
56
|
+
deps, handle, "FPDFBookmark_GetFirstChild");
|
|
57
|
+
fns.bookmarkGetNextSibling = ResolveOutlineSymbol<decltype(fns.bookmarkGetNextSibling)>(
|
|
58
|
+
deps, handle, "FPDFBookmark_GetNextSibling");
|
|
59
|
+
fns.bookmarkGetTitle =
|
|
60
|
+
ResolveOutlineSymbol<decltype(fns.bookmarkGetTitle)>(deps, handle, "FPDFBookmark_GetTitle");
|
|
61
|
+
fns.bookmarkGetDest =
|
|
62
|
+
ResolveOutlineSymbol<decltype(fns.bookmarkGetDest)>(deps, handle, "FPDFBookmark_GetDest");
|
|
63
|
+
fns.destGetPageIndex =
|
|
64
|
+
ResolveOutlineSymbol<decltype(fns.destGetPageIndex)>(deps, handle, "FPDFDest_GetPageIndex");
|
|
65
|
+
return fns;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bool HasRequiredDeps(const OutlineLoaderDeps *deps) {
|
|
69
|
+
return deps != nullptr && deps->dlopen_fn != nullptr && deps->dlsym_fn != nullptr &&
|
|
70
|
+
deps->dlclose_fn != nullptr && deps->library_name != nullptr;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} // namespace
|
|
74
|
+
|
|
75
|
+
bool HasCompleteOutlineFns(const PdfiumOutlineFns &fns) {
|
|
76
|
+
return fns.loadDocument != nullptr && fns.closeDocument != nullptr &&
|
|
77
|
+
fns.bookmarkGetFirstChild != nullptr && fns.bookmarkGetNextSibling != nullptr &&
|
|
78
|
+
fns.bookmarkGetTitle != nullptr && fns.bookmarkGetDest != nullptr &&
|
|
79
|
+
fns.destGetPageIndex != nullptr;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
OutlineLoadAttemptResult EnsureOutlinePdfiumLoaded(OutlineLoaderState *state,
|
|
83
|
+
const OutlineLoaderDeps *deps) {
|
|
84
|
+
if (state == nullptr) {
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (state->load_state == OutlineLoadState::kSupported) {
|
|
89
|
+
return {true, false};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (state->load_state == OutlineLoadState::kUnsupported) {
|
|
93
|
+
return {false, false};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!HasRequiredDeps(deps)) {
|
|
97
|
+
TransitionToUnsupported(state, deps);
|
|
98
|
+
return {false, true};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
state->handle = deps->dlopen_fn(deps->user_data, deps->library_name, deps->dlopen_flags);
|
|
102
|
+
if (state->handle == nullptr) {
|
|
103
|
+
TransitionToUnsupported(state, deps);
|
|
104
|
+
return {false, true};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
state->fns = LoadOutlineFns(state->handle, deps);
|
|
108
|
+
if (!HasCompleteOutlineFns(state->fns)) {
|
|
109
|
+
TransitionToUnsupported(state, deps);
|
|
110
|
+
return {false, true};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
state->load_state = OutlineLoadState::kSupported;
|
|
114
|
+
return {true, false};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
void ResetOutlineLoaderState(OutlineLoaderState *state, const OutlineLoaderDeps *deps) {
|
|
118
|
+
if (state == nullptr) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ClearOutlineFns(&state->fns);
|
|
123
|
+
CloseHandleIfPresent(state, deps);
|
|
124
|
+
state->load_state = OutlineLoadState::kUninitialized;
|
|
125
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
using FPDF_DOCUMENT = void *;
|
|
4
|
+
using FPDF_BOOKMARK = void *;
|
|
5
|
+
using FPDF_DEST = void *;
|
|
6
|
+
|
|
7
|
+
enum class OutlineLoadState {
|
|
8
|
+
kUninitialized,
|
|
9
|
+
kSupported,
|
|
10
|
+
kUnsupported,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
struct PdfiumOutlineFns {
|
|
14
|
+
FPDF_DOCUMENT (*loadDocument)(const char *, const char *) = nullptr;
|
|
15
|
+
void (*closeDocument)(FPDF_DOCUMENT) = nullptr;
|
|
16
|
+
FPDF_BOOKMARK (*bookmarkGetFirstChild)(FPDF_DOCUMENT, FPDF_BOOKMARK) = nullptr;
|
|
17
|
+
FPDF_BOOKMARK (*bookmarkGetNextSibling)(FPDF_DOCUMENT, FPDF_BOOKMARK) = nullptr;
|
|
18
|
+
unsigned long (*bookmarkGetTitle)(FPDF_BOOKMARK, void *, unsigned long) = nullptr;
|
|
19
|
+
FPDF_DEST (*bookmarkGetDest)(FPDF_DOCUMENT, FPDF_BOOKMARK) = nullptr;
|
|
20
|
+
int (*destGetPageIndex)(FPDF_DOCUMENT, FPDF_DEST) = nullptr;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct OutlineLoaderDeps {
|
|
24
|
+
using PdfiumSymbol = void (*)();
|
|
25
|
+
|
|
26
|
+
void *user_data = nullptr;
|
|
27
|
+
void *(*dlopen_fn)(void *user_data, const char *filename, int flags) = nullptr;
|
|
28
|
+
PdfiumSymbol (*dlsym_fn)(void *user_data, void *handle, const char *symbol_name) = nullptr;
|
|
29
|
+
int (*dlclose_fn)(void *user_data, void *handle) = nullptr;
|
|
30
|
+
const char *library_name = nullptr;
|
|
31
|
+
int dlopen_flags = 0;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
struct OutlineLoaderState {
|
|
35
|
+
void *handle = nullptr;
|
|
36
|
+
PdfiumOutlineFns fns = {};
|
|
37
|
+
OutlineLoadState load_state = OutlineLoadState::kUninitialized;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
struct OutlineLoadAttemptResult {
|
|
41
|
+
bool loaded = false;
|
|
42
|
+
bool transitioned_to_unsupported = false;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
bool HasCompleteOutlineFns(const PdfiumOutlineFns &fns);
|
|
46
|
+
OutlineLoadAttemptResult EnsureOutlinePdfiumLoaded(OutlineLoaderState *state,
|
|
47
|
+
const OutlineLoaderDeps *deps);
|
|
48
|
+
void ResetOutlineLoaderState(OutlineLoaderState *state, const OutlineLoaderDeps *deps);
|