@reekon-tools/react-native-pdf-canvas 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +57 -0
- package/PdfCanvas.podspec +68 -39
- package/README.md +213 -1316
- package/android/build.gradle +67 -54
- package/android/consumer-rules.pro +7 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
- package/android/src/main/cpp/CMakeLists.txt +54 -0
- package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
- package/android/tools/compile-gate.sh +81 -101
- package/dist/controller.d.ts +14 -0
- package/dist/controller.js +4 -3
- package/dist/index.native.d.ts +5 -0
- package/dist/index.native.js +5 -0
- package/dist/index.web.d.ts +10 -0
- package/dist/index.web.js +10 -0
- package/dist/rasterizer/index.native.d.ts +8 -12
- package/dist/rasterizer/index.native.js +15 -21
- package/dist/rasterizer/native-bridge.d.ts +51 -75
- package/dist/rasterizer/native-bridge.js +49 -33
- package/dist/rasterizer/native.d.ts +45 -0
- package/dist/rasterizer/native.js +123 -0
- package/dist/react/usePdfDocument.d.ts +5 -5
- package/dist/react/usePdfLayer.d.ts +10 -1
- package/dist/react/usePdfLayer.js +10 -2
- package/dist/skia/index.native.d.ts +7 -3
- package/dist/skia/index.native.js +7 -3
- package/dist/skia/index.web.d.ts +4 -2
- package/dist/skia/index.web.js +4 -2
- package/dist/testing/scenes.d.ts +3 -3
- package/dist/testing/scenes.js +4 -4
- package/dist/types.d.ts +36 -42
- package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
- package/native/.clangd +5 -0
- package/native/CMakeLists.txt +89 -0
- package/native/core/include/pdfcanvas/document.h +90 -0
- package/native/core/include/pdfcanvas/error.h +47 -0
- package/native/core/include/pdfcanvas/jsi.h +18 -0
- package/native/core/include/pdfcanvas/jsi_config.h +30 -0
- package/native/core/include/pdfcanvas/library.h +28 -0
- package/native/core/include/pdfcanvas/pixels.h +92 -0
- package/native/core/include/pdfcanvas/service.h +87 -0
- package/native/core/include/pdfcanvas/slots.h +113 -0
- package/native/core/include/pdfcanvas/types.h +110 -0
- package/native/core/pdfcanvas-core.cmake +22 -0
- package/native/core/src/document.cpp +516 -0
- package/native/core/src/error.cpp +29 -0
- package/native/core/src/jsi.cpp +66 -0
- package/native/core/src/library.cpp +52 -0
- package/native/core/src/pixels.cpp +45 -0
- package/native/core/src/service.cpp +156 -0
- package/native/core/src/slots.cpp +139 -0
- package/native/tests/fixtures.cpp +451 -0
- package/native/tests/fixtures.h +96 -0
- package/native/tests/harness.h +122 -0
- package/native/tests/main.cpp +64 -0
- package/native/tests/pixels_util.h +126 -0
- package/native/tests/test_document.cpp +485 -0
- package/native/tests/test_service.cpp +130 -0
- package/native/tests/test_slots.cpp +250 -0
- package/package.json +38 -8
- package/react-native.config.js +5 -2
- package/scripts/fetch-pdfium.mjs +487 -0
- package/scripts/pdfium-manifest.json +46 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
- package/android/src/jsi/cpp/CMakeLists.txt +0 -33
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
- package/dist/rasterizer/android.d.ts +0 -121
- package/dist/rasterizer/android.js +0 -126
- package/dist/rasterizer/ios.d.ts +0 -84
- package/dist/rasterizer/ios.js +0 -155
- package/ios/Package.swift +0 -58
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The JNI surface of `tools.reekon.pdfcanvas.PdfCanvasNative`.
|
|
3
|
+
*
|
|
4
|
+
* MARSHALLING ONLY, in the most literal sense: every function below converts
|
|
5
|
+
* Java values to the C++ core's types, calls one method on
|
|
6
|
+
* `pdfcanvas::Service` (or the slot store), and converts the answer back.
|
|
7
|
+
* Nothing here decides anything about rasterizing — that is `native/core`,
|
|
8
|
+
* proven on a laptop by `native/tests` against the same PDFium build.
|
|
9
|
+
*
|
|
10
|
+
* A C++ EXCEPTION MUST NOT UNWIND OUT OF A JNI METHOD: the ART frame below is
|
|
11
|
+
* not exception-aware, so the unwind reaches `std::terminate` and aborts the
|
|
12
|
+
* process. Every entry point is therefore wrapped by `guarded()`, which turns a
|
|
13
|
+
* `PdfError` into a `PdfRasterException` carrying the same wire code, and
|
|
14
|
+
* anything else into `backend-failure`.
|
|
15
|
+
*
|
|
16
|
+
* PLAIN JNI, NO FBJNI. The whole surface is a dozen static methods on one
|
|
17
|
+
* class; fbjni would add a dependency and a registration dance to express
|
|
18
|
+
* `SetByteArrayRegion`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
#include <jni.h>
|
|
22
|
+
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <exception>
|
|
25
|
+
#include <string>
|
|
26
|
+
#include <vector>
|
|
27
|
+
|
|
28
|
+
#include "pdfcanvas/error.h"
|
|
29
|
+
#include "pdfcanvas/jsi.h"
|
|
30
|
+
#include "pdfcanvas/service.h"
|
|
31
|
+
#include "pdfcanvas/slots.h"
|
|
32
|
+
|
|
33
|
+
namespace {
|
|
34
|
+
|
|
35
|
+
using pdfcanvas::ErrorCode;
|
|
36
|
+
using pdfcanvas::PdfError;
|
|
37
|
+
using pdfcanvas::Service;
|
|
38
|
+
|
|
39
|
+
/// Raises a `PdfRasterException` through its `fromWire` factory. Leaves the
|
|
40
|
+
/// exception pending; the caller must return immediately afterwards.
|
|
41
|
+
void throwPdfError(JNIEnv* env, ErrorCode code, const std::string& message) {
|
|
42
|
+
if (env->ExceptionCheck()) return;
|
|
43
|
+
jclass clazz = env->FindClass("tools/reekon/pdfcanvas/PdfRasterException");
|
|
44
|
+
if (clazz == nullptr) return; // NoClassDefFoundError is already pending.
|
|
45
|
+
jmethodID factory = env->GetStaticMethodID(
|
|
46
|
+
clazz, "fromWire",
|
|
47
|
+
"(Ljava/lang/String;Ljava/lang/String;)Ltools/reekon/pdfcanvas/PdfRasterException;");
|
|
48
|
+
if (factory == nullptr) return;
|
|
49
|
+
jstring wire = env->NewStringUTF(pdfcanvas::wireName(code));
|
|
50
|
+
jstring text = env->NewStringUTF(message.c_str());
|
|
51
|
+
if (wire == nullptr || text == nullptr) return;
|
|
52
|
+
jobject exception = env->CallStaticObjectMethod(clazz, factory, wire, text);
|
|
53
|
+
if (exception != nullptr && !env->ExceptionCheck()) {
|
|
54
|
+
env->Throw(static_cast<jthrowable>(exception));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Runs `body`, converting every C++ failure into a pending Java exception.
|
|
59
|
+
/// `fallback` is what the JNI method returns while the exception is pending.
|
|
60
|
+
template <typename T, typename Body>
|
|
61
|
+
T guarded(JNIEnv* env, T fallback, Body&& body) {
|
|
62
|
+
try {
|
|
63
|
+
return body();
|
|
64
|
+
} catch (const PdfError& error) {
|
|
65
|
+
throwPdfError(env, error.code(), error.message());
|
|
66
|
+
} catch (const std::exception& error) {
|
|
67
|
+
throwPdfError(env, ErrorCode::BackendFailure, std::string("Native failure: ") + error.what());
|
|
68
|
+
} catch (...) {
|
|
69
|
+
throwPdfError(env, ErrorCode::BackendFailure, "Native failure of an unknown kind.");
|
|
70
|
+
}
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Service* serviceOf(jlong pointer) { return reinterpret_cast<Service*>(pointer); }
|
|
75
|
+
|
|
76
|
+
std::string toString(JNIEnv* env, jstring value) {
|
|
77
|
+
if (value == nullptr) return {};
|
|
78
|
+
const char* chars = env->GetStringUTFChars(value, nullptr);
|
|
79
|
+
if (chars == nullptr) return {};
|
|
80
|
+
std::string out(chars);
|
|
81
|
+
env->ReleaseStringUTFChars(value, chars);
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
std::vector<uint8_t> toBytes(JNIEnv* env, jbyteArray value) {
|
|
86
|
+
if (value == nullptr) return {};
|
|
87
|
+
const jsize length = env->GetArrayLength(value);
|
|
88
|
+
std::vector<uint8_t> out(static_cast<size_t>(length));
|
|
89
|
+
if (length > 0) {
|
|
90
|
+
// GetByteArrayRegion, not GetPrimitiveArrayCritical: the critical variants
|
|
91
|
+
// suspend GC for their duration, and a document can be tens of megabytes.
|
|
92
|
+
env->GetByteArrayRegion(value, 0, length, reinterpret_cast<jbyte*>(out.data()));
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
jbyteArray toJavaBytes(JNIEnv* env, const std::vector<uint8_t>& bytes) {
|
|
98
|
+
const jsize length = static_cast<jsize>(bytes.size());
|
|
99
|
+
jbyteArray out = env->NewByteArray(length);
|
|
100
|
+
if (out == nullptr) return nullptr;
|
|
101
|
+
if (length > 0) {
|
|
102
|
+
env->SetByteArrayRegion(out, 0, length, reinterpret_cast<const jbyte*>(bytes.data()));
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
} // namespace
|
|
108
|
+
|
|
109
|
+
extern "C" {
|
|
110
|
+
|
|
111
|
+
/* ------------------------------------------------------------------ *
|
|
112
|
+
* Service lifecycle
|
|
113
|
+
* ------------------------------------------------------------------ */
|
|
114
|
+
|
|
115
|
+
JNIEXPORT jlong JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_createService(JNIEnv* env, jclass) {
|
|
116
|
+
return guarded<jlong>(env, 0, [] { return reinterpret_cast<jlong>(new Service()); });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_invalidateService(JNIEnv* env, jclass,
|
|
120
|
+
jlong service) {
|
|
121
|
+
guarded<int>(env, 0, [&] {
|
|
122
|
+
if (Service* s = serviceOf(service)) s->invalidate();
|
|
123
|
+
return 0;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ------------------------------------------------------------------ *
|
|
128
|
+
* Documents
|
|
129
|
+
* ------------------------------------------------------------------ */
|
|
130
|
+
|
|
131
|
+
JNIEXPORT jdoubleArray JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_open(
|
|
132
|
+
JNIEnv* env, jclass, jlong service, jstring path, jbyteArray bytes, jstring password) {
|
|
133
|
+
return guarded<jdoubleArray>(env, nullptr, [&]() -> jdoubleArray {
|
|
134
|
+
Service* s = serviceOf(service);
|
|
135
|
+
if (s == nullptr) throw PdfError(ErrorCode::BackendFailure, "The native service is gone.");
|
|
136
|
+
|
|
137
|
+
pdfcanvas::Source source;
|
|
138
|
+
source.path = toString(env, path);
|
|
139
|
+
if (source.path.empty()) source.bytes = toBytes(env, bytes);
|
|
140
|
+
if (password != nullptr) {
|
|
141
|
+
source.hasPassword = true;
|
|
142
|
+
source.password = toString(env, password);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const Service::Opened opened = s->open(source);
|
|
146
|
+
std::vector<jdouble> flat;
|
|
147
|
+
flat.reserve(2 + opened.pages.size() * 3);
|
|
148
|
+
flat.push_back(static_cast<jdouble>(opened.handle));
|
|
149
|
+
flat.push_back(static_cast<jdouble>(opened.pages.size()));
|
|
150
|
+
for (const auto& page : opened.pages) {
|
|
151
|
+
flat.push_back(page.width);
|
|
152
|
+
flat.push_back(page.height);
|
|
153
|
+
flat.push_back(static_cast<jdouble>(page.rotation));
|
|
154
|
+
}
|
|
155
|
+
jdoubleArray out = env->NewDoubleArray(static_cast<jsize>(flat.size()));
|
|
156
|
+
if (out == nullptr) {
|
|
157
|
+
s->close(opened.handle);
|
|
158
|
+
return nullptr;
|
|
159
|
+
}
|
|
160
|
+
env->SetDoubleArrayRegion(out, 0, static_cast<jsize>(flat.size()), flat.data());
|
|
161
|
+
return out;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_registerCancellation(
|
|
166
|
+
JNIEnv* env, jclass, jlong service, jint handle, jlong token) {
|
|
167
|
+
guarded<int>(env, 0, [&] {
|
|
168
|
+
if (Service* s = serviceOf(service)) s->registerCancellation(handle, token);
|
|
169
|
+
return 0;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
JNIEXPORT jbyteArray JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_render(
|
|
174
|
+
JNIEnv* env, jclass, jlong service, jint handle, jint page, jdouble x, jdouble y, jdouble width,
|
|
175
|
+
jdouble height, jdouble scale, jboolean annotations, jboolean transparent, jlong token,
|
|
176
|
+
jboolean useSlot, jlongArray out) {
|
|
177
|
+
return guarded<jbyteArray>(env, nullptr, [&]() -> jbyteArray {
|
|
178
|
+
Service* s = serviceOf(service);
|
|
179
|
+
if (s == nullptr) throw PdfError(ErrorCode::BackendFailure, "The native service is gone.");
|
|
180
|
+
if (out == nullptr || env->GetArrayLength(out) < 4) {
|
|
181
|
+
throw PdfError(ErrorCode::BackendFailure, "render() needs a long[4] to report the raster's shape.");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pdfcanvas::RasterRequest request;
|
|
185
|
+
request.page = page;
|
|
186
|
+
request.x = x;
|
|
187
|
+
request.y = y;
|
|
188
|
+
request.width = width;
|
|
189
|
+
request.height = height;
|
|
190
|
+
request.scale = scale;
|
|
191
|
+
request.annotations = annotations == JNI_TRUE;
|
|
192
|
+
request.background = transparent == JNI_TRUE ? pdfcanvas::Background::Transparent
|
|
193
|
+
: pdfcanvas::Background::White;
|
|
194
|
+
|
|
195
|
+
pdfcanvas::RasterPixels pixels = s->render(handle, request, token, useSlot == JNI_TRUE);
|
|
196
|
+
|
|
197
|
+
const jlong shape[4] = {static_cast<jlong>(pixels.slot), pixels.width, pixels.height, pixels.rowBytes};
|
|
198
|
+
env->SetLongArrayRegion(out, 0, 4, shape);
|
|
199
|
+
if (pixels.inSlot()) return nullptr;
|
|
200
|
+
|
|
201
|
+
jbyteArray bytes = toJavaBytes(env, pixels.bytes);
|
|
202
|
+
if (bytes == nullptr) {
|
|
203
|
+
throw PdfError(ErrorCode::OutOfMemory, "Could not allocate a Java array for " +
|
|
204
|
+
std::to_string(pixels.bytes.size()) + " raster bytes.");
|
|
205
|
+
}
|
|
206
|
+
return bytes;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_cancel(JNIEnv* env, jclass, jlong service,
|
|
211
|
+
jint handle, jlong token) {
|
|
212
|
+
guarded<int>(env, 0, [&] {
|
|
213
|
+
if (Service* s = serviceOf(service)) s->cancel(handle, token);
|
|
214
|
+
return 0;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_close(JNIEnv* env, jclass, jlong service,
|
|
219
|
+
jint handle) {
|
|
220
|
+
guarded<int>(env, 0, [&] {
|
|
221
|
+
if (Service* s = serviceOf(service)) s->close(handle);
|
|
222
|
+
return 0;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* ------------------------------------------------------------------ *
|
|
227
|
+
* Transport
|
|
228
|
+
* ------------------------------------------------------------------ */
|
|
229
|
+
|
|
230
|
+
JNIEXPORT jboolean JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_installJsi(JNIEnv* env, jclass,
|
|
231
|
+
jlong runtimePointer) {
|
|
232
|
+
return guarded<jboolean>(env, JNI_FALSE, [&] {
|
|
233
|
+
return pdfcanvas::installTakePixels(reinterpret_cast<void*>(runtimePointer)) ? JNI_TRUE : JNI_FALSE;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
JNIEXPORT jlong JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_storeProbe(JNIEnv* env, jclass) {
|
|
238
|
+
return guarded<jlong>(env, -1, [] { return static_cast<jlong>(pdfcanvas::slots::storeProbe()); });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_releaseSlot(JNIEnv*, jclass, jlong slot) {
|
|
242
|
+
pdfcanvas::slots::release(slot);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
JNIEXPORT void JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_releaseAllSlots(JNIEnv*, jclass) {
|
|
246
|
+
pdfcanvas::slots::releaseAll();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
JNIEXPORT jint JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_slotCount(JNIEnv*, jclass) {
|
|
250
|
+
return static_cast<jint>(pdfcanvas::slots::count());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
JNIEXPORT jbyteArray JNICALL Java_tools_reekon_pdfcanvas_PdfCanvasNative_takeSlotForTest(JNIEnv* env, jclass,
|
|
254
|
+
jlong slot) {
|
|
255
|
+
return guarded<jbyteArray>(env, nullptr, [&]() -> jbyteArray {
|
|
256
|
+
std::shared_ptr<pdfcanvas::SlotBuffer> buffer = pdfcanvas::slots::take(slot);
|
|
257
|
+
if (buffer == nullptr) return nullptr;
|
|
258
|
+
return toJavaBytes(env, std::vector<uint8_t>(buffer->data(), buffer->data() + buffer->size()));
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
} // extern "C"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
package tools.reekon.pdfcanvas;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Java face of the C++ core in {@code native/core}. EVERY method here is a
|
|
5
|
+
* one-line JNI hop; nothing in this package decides anything about rendering
|
|
6
|
+
* any more.
|
|
7
|
+
*
|
|
8
|
+
* <h3>Why the rasterizer is no longer Java</h3>
|
|
9
|
+
*
|
|
10
|
+
* It used to be {@code android.graphics.pdf.PdfRenderer}: PDFium behind a
|
|
11
|
+
* platform wrapper that exposed no annotation switch (embedded annotations were
|
|
12
|
+
* invisible on every API level below 35 and only two kinds on 35+), no
|
|
13
|
+
* password ({@code LoadParams} landed at API 35), no cancel, a poisoning bug on
|
|
14
|
+
* API 24, and a process-wide lock that convoyed. The core now links PDFium
|
|
15
|
+
* itself — the same release iOS and the web backend use — so a tile is
|
|
16
|
+
* byte-identical across all three platforms, annotations and form fields draw
|
|
17
|
+
* when asked, encrypted documents open, and a superseded render stops
|
|
18
|
+
* mid-draw.
|
|
19
|
+
*
|
|
20
|
+
* <h3>The contract</h3>
|
|
21
|
+
*
|
|
22
|
+
* A {@code service} is an opaque native pointer from {@link #createService()},
|
|
23
|
+
* owned by one {@code PdfCanvasModule}. Handles, cancellation tokens and the
|
|
24
|
+
* transport flag all live behind it in C++ (see {@code service.h}); this class
|
|
25
|
+
* only carries values across. It is never freed: {@link #invalidateService}
|
|
26
|
+
* closes everything it holds, and a worker still inside a render when a reload
|
|
27
|
+
* lands keeps a pointer that must stay valid. One small struct per reload is the
|
|
28
|
+
* price, dev-only.
|
|
29
|
+
*
|
|
30
|
+
* <h3>Degrading</h3>
|
|
31
|
+
*
|
|
32
|
+
* {@link #isLoaded()} is false when {@code libpdfcanvas.so} — or the
|
|
33
|
+
* {@code libpdfium.so} it links — is absent from the APK: a build that skipped
|
|
34
|
+
* the native build, an ABI with no artifact. There is no Java fallback engine
|
|
35
|
+
* any more, so the binding reports {@code backend-failure} and says why rather
|
|
36
|
+
* than pretending.
|
|
37
|
+
*/
|
|
38
|
+
public final class PdfCanvasNative {
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The transport self-test payload, mirroring {@code TRANSPORT_PROBE_BYTES} in
|
|
42
|
+
* {@code src/rasterizer/native-bridge.ts} and {@code slots::probeBytes()} in
|
|
43
|
+
* C++. Restated rather than derived on purpose: a mismatch has to make the
|
|
44
|
+
* probe FAIL and the platform fall back to base64.
|
|
45
|
+
*/
|
|
46
|
+
public static final byte[] PROBE_BYTES = {
|
|
47
|
+
0x00, 0x01, 0x02, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF, 0x2A, 0x07
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
private static final boolean LOADED = load();
|
|
51
|
+
|
|
52
|
+
private PdfCanvasNative() {}
|
|
53
|
+
|
|
54
|
+
private static boolean load() {
|
|
55
|
+
try {
|
|
56
|
+
// Pulls libpdfium.so in through DT_NEEDED; both sit in the APK's lib dir.
|
|
57
|
+
System.loadLibrary("pdfcanvas");
|
|
58
|
+
return true;
|
|
59
|
+
} catch (Throwable t) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Whether the native library loaded. False means nothing here may be called. */
|
|
65
|
+
public static boolean isLoaded() {
|
|
66
|
+
return LOADED;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ---------------------------------------------------------------- *
|
|
70
|
+
* Service lifecycle
|
|
71
|
+
* ---------------------------------------------------------------- */
|
|
72
|
+
|
|
73
|
+
/** A fresh {@code pdfcanvas::Service}. Never freed — see the class note. */
|
|
74
|
+
public static native long createService();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Closes every document the service holds (waiting for an in-flight render
|
|
78
|
+
* to reach its next cancellation checkpoint), drains every parked slot, and
|
|
79
|
+
* makes every further call fail with {@code backend-failure}.
|
|
80
|
+
*/
|
|
81
|
+
public static native void invalidateService(long service);
|
|
82
|
+
|
|
83
|
+
/* ---------------------------------------------------------------- *
|
|
84
|
+
* Documents
|
|
85
|
+
* ---------------------------------------------------------------- */
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opens a document from a path or from bytes (exactly one non-null).
|
|
89
|
+
*
|
|
90
|
+
* @param password null when none was supplied. An EMPTY string counts as
|
|
91
|
+
* supplied: PDFium reports the same error for "no password" and "wrong
|
|
92
|
+
* password", and only the caller knows which it was.
|
|
93
|
+
* @return {@code [handle, pageCount, w0, h0, rot0, w1, h1, rot1, ...]} —
|
|
94
|
+
* flat doubles rather than a Java object, so the JNI side constructs
|
|
95
|
+
* nothing it would have to look up by signature.
|
|
96
|
+
* @throws PdfRasterException with the failure's {@link PdfErrorCode}.
|
|
97
|
+
*/
|
|
98
|
+
public static native double[] open(long service, String path, byte[] bytes, String password)
|
|
99
|
+
throws PdfRasterException;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Registers the cancellation signal for {@code (handle, token)} BEFORE the
|
|
103
|
+
* render is dispatched, so a cancel arriving during the dispatch hop lands.
|
|
104
|
+
*/
|
|
105
|
+
public static native void registerCancellation(long service, int handle, long token);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Renders one raster on the calling thread.
|
|
109
|
+
*
|
|
110
|
+
* @param useSlot ask for the zero-copy transport. The store may still refuse,
|
|
111
|
+
* so the result says where the pixels ARE: a non-null return is heap
|
|
112
|
+
* bytes, a null return means {@code out[0]} names a slot.
|
|
113
|
+
* @param out receives {@code [slot, width, height, rowBytes]}.
|
|
114
|
+
* @return the RGBA bytes when heap-backed, null when slot-backed.
|
|
115
|
+
*/
|
|
116
|
+
public static native byte[] render(
|
|
117
|
+
long service,
|
|
118
|
+
int handle,
|
|
119
|
+
int page,
|
|
120
|
+
double x,
|
|
121
|
+
double y,
|
|
122
|
+
double width,
|
|
123
|
+
double height,
|
|
124
|
+
double scale,
|
|
125
|
+
boolean annotations,
|
|
126
|
+
boolean transparent,
|
|
127
|
+
long token,
|
|
128
|
+
boolean useSlot,
|
|
129
|
+
long[] out)
|
|
130
|
+
throws PdfRasterException;
|
|
131
|
+
|
|
132
|
+
/** Sets the signal a render polls, including mid-draw. A pure lookup. */
|
|
133
|
+
public static native void cancel(long service, int handle, long token);
|
|
134
|
+
|
|
135
|
+
/** Closes one document. Idempotent. */
|
|
136
|
+
public static native void close(long service, int handle);
|
|
137
|
+
|
|
138
|
+
/* ---------------------------------------------------------------- *
|
|
139
|
+
* Transport
|
|
140
|
+
* ---------------------------------------------------------------- */
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Installs {@code globalThis.__pdfCanvasTakePixels}. MUST be called on the JS
|
|
144
|
+
* thread with a live runtime pointer; returns false rather than throwing when
|
|
145
|
+
* the pointer is null, this build has no JSI, or the runtime refuses.
|
|
146
|
+
*/
|
|
147
|
+
public static native boolean installJsi(long runtimePointer);
|
|
148
|
+
|
|
149
|
+
/** Parks {@link #PROBE_BYTES} in a fresh slot and returns its id. */
|
|
150
|
+
public static native long storeProbe();
|
|
151
|
+
|
|
152
|
+
/** Drops one slot if it is still there. */
|
|
153
|
+
public static native void releaseSlot(long slot);
|
|
154
|
+
|
|
155
|
+
/** Drops every slot. */
|
|
156
|
+
public static native void releaseAllSlots();
|
|
157
|
+
|
|
158
|
+
/** Slots waiting to be taken. Diagnostics only. */
|
|
159
|
+
public static native int slotCount();
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Takes a slot back out as a Java array through the identical storage the
|
|
163
|
+
* ArrayBuffer would have wrapped. Null when empty. The instrumented suite's
|
|
164
|
+
* only way to check the transport without a JS runtime.
|
|
165
|
+
*/
|
|
166
|
+
public static native byte[] takeSlotForTest(long slot);
|
|
167
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
package tools.reekon.pdfcanvas;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The eight failure kinds, mirroring {@code PdfErrorCode} in {@code src/types.ts}
|
|
4
|
+
* The eight failure kinds, mirroring {@code PdfErrorCode} in {@code src/types.ts}
|
|
5
|
+
* and {@code pdfcanvas::ErrorCode} in the C++ core.
|
|
5
6
|
*
|
|
6
7
|
* The wire strings are the TypeScript union members verbatim. They are the
|
|
7
8
|
* contract across the bridge: the binding rejects a promise with {@link #wire()}
|
|
8
9
|
* and the TS side constructs a {@code PdfError} from it with no translation
|
|
9
|
-
* table in between.
|
|
10
|
-
* {@
|
|
11
|
-
* to move together.
|
|
10
|
+
* table in between. The C++ core throws with the same strings, and
|
|
11
|
+
* {@link #fromWire} is how a JNI-raised exception gets its typed code back.
|
|
12
12
|
*/
|
|
13
13
|
public enum PdfErrorCode {
|
|
14
14
|
NOT_FOUND("not-found"),
|
|
@@ -30,4 +30,14 @@ public enum PdfErrorCode {
|
|
|
30
30
|
public String wire() {
|
|
31
31
|
return wire;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/** The code for a wire string; {@link #BACKEND_FAILURE} for one nobody knows. */
|
|
35
|
+
public static PdfErrorCode fromWire(String wire) {
|
|
36
|
+
for (PdfErrorCode code : values()) {
|
|
37
|
+
if (code.wire.equals(wire)) {
|
|
38
|
+
return code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return BACKEND_FAILURE;
|
|
42
|
+
}
|
|
33
43
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
package tools.reekon.pdfcanvas;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Every way
|
|
4
|
+
* Every way the native core can fail, carrying a {@link PdfErrorCode}.
|
|
5
5
|
*
|
|
6
6
|
* CHECKED on purpose. The whole point of the code is that a caller distinguishes
|
|
7
7
|
* "wrong password" from "not a PDF" from "cancelled", and an unchecked exception
|
|
8
8
|
* lets the binding forget to map it — at which point every failure reaches JS as
|
|
9
9
|
* a generic {@code Error} and the {@code PdfErrorCode} union is decorative.
|
|
10
|
-
*
|
|
11
|
-
* The {@code cause} is always kept. A {@code corrupt} verdict that turns out to
|
|
12
|
-
* be a truncated download is only diagnosable from the underlying IOException.
|
|
13
10
|
*/
|
|
14
11
|
public class PdfRasterException extends Exception {
|
|
15
12
|
|
|
@@ -24,6 +21,15 @@ public class PdfRasterException extends Exception {
|
|
|
24
21
|
this.code = code;
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
/**
|
|
25
|
+
* How the JNI layer raises one: it has a wire string from
|
|
26
|
+
* {@code pdfcanvas::wireName} and a message, and constructing an enum from C++
|
|
27
|
+
* is a signature lookup nobody should have to get right twice.
|
|
28
|
+
*/
|
|
29
|
+
public static PdfRasterException fromWire(String wire, String message) {
|
|
30
|
+
return new PdfRasterException(PdfErrorCode.fromWire(wire), message);
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
public PdfErrorCode code() {
|
|
28
34
|
return code;
|
|
29
35
|
}
|