@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 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.
@@ -1,40 +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', 34)
9
- namespace 'com.papyrus.engine'
10
-
11
- defaultConfig {
12
- minSdkVersion safeExtGet('minSdkVersion', 21)
13
- targetSdkVersion safeExtGet('targetSdkVersion', 34)
14
- consumerProguardFiles 'consumer-rules.pro'
15
- externalNativeBuild {
16
- cmake {
17
- cppFlags "-std=c++17"
18
- }
19
- }
20
- }
21
-
22
- sourceSets {
23
- main {
24
- java.srcDirs = ['src/main/java']
25
- }
26
- }
27
-
28
- externalNativeBuild {
29
- cmake {
30
- path "src/main/cpp/CMakeLists.txt"
31
- }
32
- }
33
- }
34
-
35
- dependencies {
36
- implementation 'com.facebook.react:react-android'
37
- implementation('com.github.barteksc:pdfium-android:1.9.0') {
38
- exclude group: 'com.android.support'
39
- }
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'
40
42
  }
@@ -1 +1 @@
1
- # Keep rules can be added here if needed.
1
+ # Keep rules can be added here if needed.
@@ -1,3 +1 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- package="com.papyrus.engine">
3
- </manifest>
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -1,23 +1,45 @@
1
1
  cmake_minimum_required(VERSION 3.18.1)
2
2
  project(papyrus_text)
3
3
 
4
- add_library(papyrus_text SHARED
5
- papyrus_text_search.cpp
6
- papyrus_outline.cpp
7
- )
8
-
9
- target_compile_options(papyrus_text PRIVATE -Wall -Werror)
10
- set_target_properties(papyrus_text PROPERTIES
11
- CXX_STANDARD 17
12
- CXX_STANDARD_REQUIRED YES
13
- )
14
-
15
- find_library(log-lib log)
16
- find_library(android-lib android)
17
- find_library(dl-lib dl)
18
-
19
- target_link_libraries(papyrus_text
20
- ${log-lib}
21
- ${android-lib}
22
- ${dl-lib}
23
- )
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 <algorithm>
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
- typedef void *FPDF_DOCUMENT;
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
- static void *g_pdfium = nullptr;
27
- static PdfiumFns g_fns = {};
28
- static bool g_loaded = false;
15
+ OutlineLoaderState g_outline_loader = {};
29
16
 
30
- static bool LoadPdfium() {
31
- if (g_loaded) return g_pdfium != nullptr;
17
+ void *OutlineDlopen(void *, const char *filename, int flags) {
18
+ return dlopen(filename, flags);
19
+ }
32
20
 
33
- g_pdfium = dlopen("libmodpdfium.so", RTLD_LAZY);
34
- if (!g_pdfium) {
35
- LOGE("Failed to load libmodpdfium.so");
36
- g_loaded = true;
37
- return false;
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
- g_fns.loadDocument = reinterpret_cast<FPDF_DOCUMENT (*)(const char *, const char *)>(dlsym(g_pdfium, "FPDF_LoadDocument"));
41
- g_fns.closeDocument = reinterpret_cast<void (*)(FPDF_DOCUMENT)>(dlsym(g_pdfium, "FPDF_CloseDocument"));
42
- g_fns.bookmarkGetFirstChild = reinterpret_cast<FPDF_BOOKMARK (*)(FPDF_DOCUMENT, FPDF_BOOKMARK)>(dlsym(g_pdfium, "FPDFBookmark_GetFirstChild"));
43
- g_fns.bookmarkGetNextSibling = reinterpret_cast<FPDF_BOOKMARK (*)(FPDF_DOCUMENT, FPDF_BOOKMARK)>(dlsym(g_pdfium, "FPDFBookmark_GetNextSibling"));
44
- g_fns.bookmarkGetTitle = reinterpret_cast<unsigned long (*)(FPDF_BOOKMARK, void *, unsigned long)>(dlsym(g_pdfium, "FPDFBookmark_GetTitle"));
45
- g_fns.bookmarkGetDest = reinterpret_cast<FPDF_DEST (*)(FPDF_DOCUMENT, FPDF_BOOKMARK)>(dlsym(g_pdfium, "FPDFBookmark_GetDest"));
46
- g_fns.destGetPageIndex = reinterpret_cast<int (*)(FPDF_DOCUMENT, FPDF_DEST)>(dlsym(g_pdfium, "FPDFDest_GetPageIndex"));
47
-
48
- if (!g_fns.loadDocument || !g_fns.closeDocument || !g_fns.bookmarkGetFirstChild ||
49
- !g_fns.bookmarkGetNextSibling || !g_fns.bookmarkGetTitle || !g_fns.bookmarkGetDest ||
50
- !g_fns.destGetPageIndex) {
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
- g_loaded = true;
57
- return true;
52
+ PdfiumOutlineFns &OutlineFns() {
53
+ return g_outline_loader.fns;
58
54
  }
59
55
 
60
- static jstring GetBookmarkTitle(JNIEnv *env, FPDF_BOOKMARK bookmark) {
61
- unsigned long length = g_fns.bookmarkGetTitle(bookmark, nullptr, 0);
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 = g_fns.bookmarkGetTitle(bookmark, buffer.data(), length);
68
- int chars = written > 0 ? static_cast<int>(written / 2) - 1 : static_cast<int>(length / 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
- static jobjectArray BuildOutlineItems(JNIEnv *env, FPDF_DOCUMENT doc, FPDF_BOOKMARK parent, jclass itemClass, jmethodID ctor) {
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 = g_fns.bookmarkGetFirstChild(doc, parent); child;
80
- child = g_fns.bookmarkGetNextSibling(doc, 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 pageIndex = -1;
83
- FPDF_DEST dest = g_fns.bookmarkGetDest(doc, child);
84
- if (dest) {
85
- pageIndex = g_fns.destGetPageIndex(doc, dest);
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, itemClass, ctor);
89
- jobject item = env->NewObject(itemClass, ctor, title, pageIndex, children);
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) env->DeleteLocalRef(title);
92
- if (children) env->DeleteLocalRef(children);
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 = env->NewObjectArray(static_cast<jsize>(items.size()), itemClass, nullptr);
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
- static jobjectArray BuildOutline(JNIEnv *env, FPDF_DOCUMENT doc) {
108
- jclass itemClass = env->FindClass("com/papyrus/engine/PapyrusOutlineItem");
109
- if (!itemClass) return nullptr;
110
- jmethodID ctor = env->GetMethodID(itemClass, "<init>", "(Ljava/lang/String;I[Lcom/papyrus/engine/PapyrusOutlineItem;)V");
111
- if (!ctor) return nullptr;
112
- return BuildOutlineItems(env, doc, nullptr, itemClass, ctor);
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()) return nullptr;
118
- if (!docPtr) return nullptr;
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, jclass, jstring filePath) {
126
- if (!LoadPdfium()) return nullptr;
127
- if (!filePath) return nullptr;
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 (!path) return nullptr;
160
+ if (path == nullptr) {
161
+ return nullptr;
162
+ }
131
163
 
132
- FPDF_DOCUMENT doc = g_fns.loadDocument(path, nullptr);
164
+ FPDF_DOCUMENT doc = OutlineFns().loadDocument(path, nullptr);
133
165
  env->ReleaseStringUTFChars(filePath, path);
134
- if (!doc) return nullptr;
166
+ if (doc == nullptr) {
167
+ return nullptr;
168
+ }
135
169
 
136
170
  jobjectArray result = BuildOutline(env, doc);
137
- g_fns.closeDocument(doc);
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);