@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.
Files changed (129) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +68 -39
  4. package/README.md +213 -1316
  5. package/android/build.gradle +67 -54
  6. package/android/consumer-rules.pro +7 -0
  7. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
  8. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
  9. package/android/src/main/cpp/CMakeLists.txt +54 -0
  10. package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
  11. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
  12. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
  13. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
  14. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
  15. package/android/tools/compile-gate.sh +81 -101
  16. package/dist/controller.d.ts +14 -0
  17. package/dist/controller.js +4 -3
  18. package/dist/index.native.d.ts +5 -0
  19. package/dist/index.native.js +5 -0
  20. package/dist/index.web.d.ts +10 -0
  21. package/dist/index.web.js +10 -0
  22. package/dist/rasterizer/index.native.d.ts +8 -12
  23. package/dist/rasterizer/index.native.js +15 -21
  24. package/dist/rasterizer/native-bridge.d.ts +51 -75
  25. package/dist/rasterizer/native-bridge.js +49 -33
  26. package/dist/rasterizer/native.d.ts +45 -0
  27. package/dist/rasterizer/native.js +123 -0
  28. package/dist/react/usePdfDocument.d.ts +5 -5
  29. package/dist/react/usePdfLayer.d.ts +10 -1
  30. package/dist/react/usePdfLayer.js +10 -2
  31. package/dist/skia/index.native.d.ts +7 -3
  32. package/dist/skia/index.native.js +7 -3
  33. package/dist/skia/index.web.d.ts +4 -2
  34. package/dist/skia/index.web.js +4 -2
  35. package/dist/testing/scenes.d.ts +3 -3
  36. package/dist/testing/scenes.js +4 -4
  37. package/dist/types.d.ts +36 -42
  38. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  39. package/native/.clangd +5 -0
  40. package/native/CMakeLists.txt +89 -0
  41. package/native/core/include/pdfcanvas/document.h +90 -0
  42. package/native/core/include/pdfcanvas/error.h +47 -0
  43. package/native/core/include/pdfcanvas/jsi.h +18 -0
  44. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  45. package/native/core/include/pdfcanvas/library.h +28 -0
  46. package/native/core/include/pdfcanvas/pixels.h +92 -0
  47. package/native/core/include/pdfcanvas/service.h +87 -0
  48. package/native/core/include/pdfcanvas/slots.h +113 -0
  49. package/native/core/include/pdfcanvas/types.h +110 -0
  50. package/native/core/pdfcanvas-core.cmake +22 -0
  51. package/native/core/src/document.cpp +516 -0
  52. package/native/core/src/error.cpp +29 -0
  53. package/native/core/src/jsi.cpp +66 -0
  54. package/native/core/src/library.cpp +52 -0
  55. package/native/core/src/pixels.cpp +45 -0
  56. package/native/core/src/service.cpp +156 -0
  57. package/native/core/src/slots.cpp +139 -0
  58. package/native/tests/fixtures.cpp +451 -0
  59. package/native/tests/fixtures.h +96 -0
  60. package/native/tests/harness.h +122 -0
  61. package/native/tests/main.cpp +64 -0
  62. package/native/tests/pixels_util.h +126 -0
  63. package/native/tests/test_document.cpp +485 -0
  64. package/native/tests/test_service.cpp +130 -0
  65. package/native/tests/test_slots.cpp +250 -0
  66. package/package.json +38 -8
  67. package/react-native.config.js +5 -2
  68. package/scripts/fetch-pdfium.mjs +487 -0
  69. package/scripts/pdfium-manifest.json +46 -0
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  72. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  73. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  74. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  75. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  76. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  77. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  78. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  79. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  80. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  81. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  82. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  83. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  84. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  85. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  86. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  87. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  88. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  89. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  90. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  91. package/dist/rasterizer/android.d.ts +0 -121
  92. package/dist/rasterizer/android.js +0 -126
  93. package/dist/rasterizer/ios.d.ts +0 -84
  94. package/dist/rasterizer/ios.js +0 -155
  95. package/ios/Package.swift +0 -58
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  98. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  99. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  100. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  101. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  102. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  103. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  104. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  105. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  109. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  110. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  111. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  112. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  113. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  114. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  115. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  116. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  117. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  118. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  119. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  120. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  121. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  122. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  123. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  124. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  125. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  126. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  127. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  128. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  129. 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. Adding a member here without adding it there produces a
10
- * {@code PdfError} with a code no consumer can switch on, so the two lists have
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 this backend can fail, carrying a {@link PdfErrorCode}.
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
  }