@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.1
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 +77 -40
- package/README.md +218 -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/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/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 +27 -7
- package/react-native.config.js +5 -2
- package/scripts/fetch-pdfium.mjs +559 -0
- package/scripts/pdfium-manifest.json +48 -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
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import java.lang.annotation.ElementType;
|
|
4
|
-
import java.lang.annotation.Retention;
|
|
5
|
-
import java.lang.annotation.RetentionPolicy;
|
|
6
|
-
import java.lang.annotation.Target;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Marks a test that deliberately triggers the API 24 PDFium poisoning and can
|
|
10
|
-
* therefore only be the LAST thing its process does.
|
|
11
|
-
*
|
|
12
|
-
* `android/build.gradle` passes `notAnnotation` for this type, so
|
|
13
|
-
* `connectedDebugAndroidTest` never runs it by accident. It is run on its own —
|
|
14
|
-
* the command is in {@code PdfCanvasPoisonGuardTest}'s javadoc — which is the
|
|
15
|
-
* only way to observe the guard doing its job, because observing it requires the
|
|
16
|
-
* platform to actually be broken.
|
|
17
|
-
*/
|
|
18
|
-
@Retention(RetentionPolicy.RUNTIME)
|
|
19
|
-
@Target({ElementType.METHOD, ElementType.TYPE})
|
|
20
|
-
public @interface PoisonsTheProcess {}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# The raster transport's native half. See pdfcanvas-jsi.cpp for what it does.
|
|
2
|
-
#
|
|
3
|
-
# PDFCANVAS_WITH_JSI is passed ON only by the autolinked build, where
|
|
4
|
-
# ReactAndroid's prefab publishes jsi/jsi.h and libjsi.so. The standalone build
|
|
5
|
-
# (`cd android && ./gradlew connectedDebugAndroidTest`) passes nothing, has no
|
|
6
|
-
# React Native anywhere on its classpath, and still compiles this library — the
|
|
7
|
-
# slot storage and its JNI surface are React-free, which is what lets the device
|
|
8
|
-
# suite prove the transport's byte fidelity with no app in the picture.
|
|
9
|
-
|
|
10
|
-
cmake_minimum_required(VERSION 3.13)
|
|
11
|
-
project(pdfcanvasjsi)
|
|
12
|
-
|
|
13
|
-
set(CMAKE_CXX_STANDARD 17)
|
|
14
|
-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
15
|
-
|
|
16
|
-
add_library(pdfcanvasjsi SHARED pdfcanvas-jsi.cpp)
|
|
17
|
-
|
|
18
|
-
# jnigraphics is the NDK's Bitmap accessor (AndroidBitmap_getInfo /
|
|
19
|
-
# AndroidBitmap_lockPixels), which is how the one-copy path reads a raster
|
|
20
|
-
# straight out of the Bitmap's own pixel memory. Part of the platform since
|
|
21
|
-
# API 8 and present on every device this package supports, so it is linked in
|
|
22
|
-
# BOTH configurations — the standalone build needs it too, and that is what lets
|
|
23
|
-
# the instrumented suite prove the fill's byte fidelity with no app in the
|
|
24
|
-
# picture.
|
|
25
|
-
target_link_libraries(pdfcanvasjsi jnigraphics)
|
|
26
|
-
|
|
27
|
-
if(PDFCANVAS_WITH_JSI)
|
|
28
|
-
target_compile_definitions(pdfcanvasjsi PRIVATE PDFCANVAS_WITH_JSI=1)
|
|
29
|
-
# CONFIG mode against the react-android AAR's prefab package. `jsi` is the
|
|
30
|
-
# only target needed: nothing here touches TurboModules, fbjni or folly.
|
|
31
|
-
find_package(ReactAndroid REQUIRED CONFIG)
|
|
32
|
-
target_link_libraries(pdfcanvasjsi ReactAndroid::jsi)
|
|
33
|
-
endif()
|
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* The raster transport: a Java `byte[]` becomes a JS `ArrayBuffer` with no
|
|
3
|
-
* base64 anywhere in between.
|
|
4
|
-
*
|
|
5
|
-
* WHY THIS FILE EXISTS. The measured split for one 1024x1024 tile on an LG V60
|
|
6
|
-
* was 100.07 ms of bridge round trip, 1005.46 ms of `decodeBase64` in Hermes and
|
|
7
|
-
* 4.09 ms of Skia ingest. The decode is 90.6% of the cost and it is a
|
|
8
|
-
* 5.6-million-iteration interpreted loop that exists only to undo an encode that
|
|
9
|
-
* happened 3 ms earlier on the other side of the bridge. Deleting BOTH is what
|
|
10
|
-
* this file does; the Skia ingest is untouched and is expected to survive.
|
|
11
|
-
*
|
|
12
|
-
* HOW MANY COPIES. TWO ENTRY POINTS, and they differ by exactly one copy. The
|
|
13
|
-
* count below is of FULL-RASTER copies; the ArrayBuffer hand-off is a pointer
|
|
14
|
-
* wrap in both cases and is not one of them.
|
|
15
|
-
*
|
|
16
|
-
* `nativeStoreFromBitmap` — ONE copy. The `SlotBuffer` is allocated here, then
|
|
17
|
-
* `AndroidBitmap_lockPixels` gives the Bitmap's own pixel memory and one
|
|
18
|
-
* `memcpy` moves it in. The Java heap is never involved: no `byte[]`, and
|
|
19
|
-
* nothing for ART's large-object space to collect afterwards. This is the
|
|
20
|
-
* path `PdfCanvasRasterizer.extract` takes when a slot is asked for.
|
|
21
|
-
*
|
|
22
|
-
* `nativeStore` — TWO copies, and it is kept because the base64 fallback needs
|
|
23
|
-
* a `byte[]` anyway. `Bitmap.copyPixelsToBuffer` has already copied the
|
|
24
|
-
* raster into a Java array (copy 1) before this is called; here
|
|
25
|
-
* `GetByteArrayRegion` copies it again into the `SlotBuffer` (copy 2).
|
|
26
|
-
* THIS COMMENT USED TO CLAIM "exactly one" FOR THIS PATH. It undercounted:
|
|
27
|
-
* it was counting from the `byte[]`, as though the array had arrived free.
|
|
28
|
-
*
|
|
29
|
-
* Both run on the render executor thread, the same worker that just did
|
|
30
|
-
* `PdfRenderer.render`, so their cost is paid in parallel with whatever JS is
|
|
31
|
-
* doing. Then `__pdfCanvasTakePixels` hands the `SlotBuffer` to
|
|
32
|
-
* `jsi::ArrayBuffer(runtime, shared_ptr<MutableBuffer>)`: Hermes retains the
|
|
33
|
-
* shared_ptr and points the ArrayBuffer's storage AT the vector. No copy.
|
|
34
|
-
*
|
|
35
|
-
* WHY THE FILL IS NOT DONE FROM JAVA THROUGH A DIRECT ByteBuffer. That was the
|
|
36
|
-
* obvious shape — `NewDirectByteBuffer` over the `SlotBuffer` and let Java
|
|
37
|
-
* `copyPixelsToBuffer` into it — and it opens a use-after-free this one does not.
|
|
38
|
-
* A slot in `g_slots` is reachable by `nativeReleaseAll`, which runs on ANOTHER
|
|
39
|
-
* thread (`PdfCanvasModule.invalidate`, i.e. every Fast Refresh) and frees the
|
|
40
|
-
* vector. Handing the address to Java means the slot must be published BEFORE the
|
|
41
|
-
* fill, so a reload landing mid-copy frees memory a render worker is still
|
|
42
|
-
* writing into: silent heap corruption, weeks from the change that caused it.
|
|
43
|
-
* Filling inside one JNI call keeps the shape every slot has always had —
|
|
44
|
-
* allocate, fill, publish under the lock, in that order — so the buffer is
|
|
45
|
-
* unreachable by anything else until it is finished.
|
|
46
|
-
*
|
|
47
|
-
* WHO FREES IT. The `SlotBuffer` is a `std::shared_ptr`. The slot map drops its
|
|
48
|
-
* reference the instant the take succeeds, so from that moment the only owner is
|
|
49
|
-
* the JS ArrayBuffer, and the vector is freed when Hermes collects it. A slot
|
|
50
|
-
* that is stored and never taken (a JS teardown between the promise resolving
|
|
51
|
-
* and its continuation running) is freed by `nativeReleaseAll`, which
|
|
52
|
-
* `PdfCanvasModule.invalidate()` calls.
|
|
53
|
-
*
|
|
54
|
-
* TWO BUILDS, ONE FILE — the same split `build.gradle` already draws:
|
|
55
|
-
*
|
|
56
|
-
* PDFCANVAS_WITH_JSI defined — the autolinked build. ReactAndroid's prefab
|
|
57
|
-
* supplies `jsi/jsi.h`, and the host function
|
|
58
|
-
* is installed onto the JS global.
|
|
59
|
-
* PDFCANVAS_WITH_JSI undefined — the standalone build
|
|
60
|
-
* (`./gradlew connectedDebugAndroidTest`).
|
|
61
|
-
* There is no React Native on the classpath and
|
|
62
|
-
* no libjsi.so to link, so `nativeInstall`
|
|
63
|
-
* reports false and the slot storage is
|
|
64
|
-
* compiled on its own.
|
|
65
|
-
*
|
|
66
|
-
* That second configuration is not a stub for its own sake: it is what lets
|
|
67
|
-
* `PdfCanvasJsiTransportTest` prove ON DEVICE that the bytes which become the
|
|
68
|
-
* ArrayBuffer are byte-identical to what the base64 path would have produced.
|
|
69
|
-
* The JNI copy is the only step that could shear a raster, and it is the step
|
|
70
|
-
* both builds share.
|
|
71
|
-
*
|
|
72
|
-
* PLAIN JNI, NO FBJNI. The whole surface is four static methods on one class and
|
|
73
|
-
* one host function; fbjni would add a dependency and a registration dance to
|
|
74
|
-
* express `GetByteArrayRegion`.
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
#include <android/bitmap.h>
|
|
78
|
-
#include <jni.h>
|
|
79
|
-
|
|
80
|
-
#include <cstddef>
|
|
81
|
-
#include <cstdint>
|
|
82
|
-
#include <cstring>
|
|
83
|
-
#include <memory>
|
|
84
|
-
#include <mutex>
|
|
85
|
-
#include <unordered_map>
|
|
86
|
-
#include <vector>
|
|
87
|
-
|
|
88
|
-
#ifdef PDFCANVAS_WITH_JSI
|
|
89
|
-
#include <jsi/jsi.h>
|
|
90
|
-
#endif
|
|
91
|
-
|
|
92
|
-
namespace {
|
|
93
|
-
|
|
94
|
-
#ifdef PDFCANVAS_WITH_JSI
|
|
95
|
-
using SlotBufferBase = facebook::jsi::MutableBuffer;
|
|
96
|
-
#else
|
|
97
|
-
/**
|
|
98
|
-
* The two methods `jsi::MutableBuffer` declares, restated so the standalone
|
|
99
|
-
* build compiles the identical storage code with no JSI header in sight. If
|
|
100
|
-
* these ever drift from jsi.h the autolinked build stops compiling, which is the
|
|
101
|
-
* failure mode worth having.
|
|
102
|
-
*/
|
|
103
|
-
struct SlotBufferBase {
|
|
104
|
-
virtual ~SlotBufferBase() = default;
|
|
105
|
-
virtual size_t size() const = 0;
|
|
106
|
-
virtual uint8_t* data() = 0;
|
|
107
|
-
};
|
|
108
|
-
#endif
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* One raster's bytes, owned outright.
|
|
112
|
-
*
|
|
113
|
-
* NOT TAKEN, AND MEASURED BEFORE IT IS: `std::vector<uint8_t> bytes_(size)`
|
|
114
|
-
* VALUE-initialises, so constructing this memsets the whole raster to zero and
|
|
115
|
-
* then a fill immediately overwrites every byte of it. That is a second
|
|
116
|
-
* full-raster pass per tile, on the same serialised render executor as the copy
|
|
117
|
-
* it precedes, and a `unique_ptr<uint8_t[]>` from `new uint8_t[size]` would
|
|
118
|
-
* delete it outright.
|
|
119
|
-
*
|
|
120
|
-
* It stays because it buys a real invariant for free: a published slot can never
|
|
121
|
-
* hand JS memory that was not written. Every fill path today writes all `size`
|
|
122
|
-
* bytes BEFORE publishing, so the invariant is already structural rather than
|
|
123
|
-
* incidental — but "already structural" is the sort of thing that stops being
|
|
124
|
-
* true quietly, and the failure mode is a tile of somebody else's heap. Take it
|
|
125
|
-
* only with `PdfCanvasPhaseTimingTest`'s LANES-JSI-DIRECT row measured on both
|
|
126
|
-
* sides of the change.
|
|
127
|
-
*/
|
|
128
|
-
class SlotBuffer final : public SlotBufferBase {
|
|
129
|
-
public:
|
|
130
|
-
explicit SlotBuffer(size_t size) : bytes_(size) {}
|
|
131
|
-
|
|
132
|
-
size_t size() const override { return bytes_.size(); }
|
|
133
|
-
uint8_t* data() override { return bytes_.data(); }
|
|
134
|
-
|
|
135
|
-
private:
|
|
136
|
-
std::vector<uint8_t> bytes_;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Slots are written on the render executor and read on the JS thread, so the map
|
|
141
|
-
* is guarded. The lock is held only around the map operation itself — never
|
|
142
|
-
* across the allocation or the copy, and never across a JSI call.
|
|
143
|
-
*/
|
|
144
|
-
std::mutex g_slotsMutex;
|
|
145
|
-
std::unordered_map<int64_t, std::shared_ptr<SlotBuffer>> g_slots;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Publishes a finished buffer, or refuses.
|
|
149
|
-
*
|
|
150
|
-
* REFUSES rather than overwrites when the id is already occupied. Slot ids are
|
|
151
|
-
* monotonic per PROCESS (`PdfCanvasModule.nextSlot` is static for exactly this
|
|
152
|
-
* reason), so a collision cannot happen by design and can only mean the id
|
|
153
|
-
* allocator has been duplicated — in which case overwriting hands one module
|
|
154
|
-
* ANOTHER module's raster, which is a wrong image rather than an error. `false`
|
|
155
|
-
* here means the caller falls back, and the buffer is freed by its shared_ptr.
|
|
156
|
-
*
|
|
157
|
-
* Separate from the fill so the lock is held only around the map operation,
|
|
158
|
-
* never across the allocation or the memcpy.
|
|
159
|
-
*/
|
|
160
|
-
bool publishSlot(int64_t slot, std::shared_ptr<SlotBuffer> buffer) {
|
|
161
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
162
|
-
if (g_slots.find(slot) != g_slots.end()) {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
g_slots.emplace(slot, std::move(buffer));
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
std::shared_ptr<SlotBuffer> takeSlot(int64_t slot) {
|
|
170
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
171
|
-
auto found = g_slots.find(slot);
|
|
172
|
-
if (found == g_slots.end()) {
|
|
173
|
-
return nullptr;
|
|
174
|
-
}
|
|
175
|
-
std::shared_ptr<SlotBuffer> buffer = std::move(found->second);
|
|
176
|
-
g_slots.erase(found);
|
|
177
|
-
return buffer;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Raises a Java throwable, so a C++ failure leaves this JNI method as an
|
|
182
|
-
* exception the caller can catch rather than as an unwind into ART.
|
|
183
|
-
*/
|
|
184
|
-
void throwJava(JNIEnv* env, const char* className, const char* message) {
|
|
185
|
-
if (env->ExceptionCheck()) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
jclass clazz = env->FindClass(className);
|
|
189
|
-
if (clazz == nullptr) {
|
|
190
|
-
// FindClass has already set its own NoClassDefFoundError; nothing better to
|
|
191
|
-
// report and nothing that may throw from here.
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
env->ThrowNew(clazz, message);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
} // namespace
|
|
198
|
-
|
|
199
|
-
extern "C" {
|
|
200
|
-
|
|
201
|
-
JNIEXPORT void JNICALL
|
|
202
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeStore(
|
|
203
|
-
JNIEnv* env,
|
|
204
|
-
jclass,
|
|
205
|
-
jlong slot,
|
|
206
|
-
jbyteArray data) {
|
|
207
|
-
if (data == nullptr) {
|
|
208
|
-
throwJava(
|
|
209
|
-
env, "java/lang/NullPointerException", "nativeStore: bytes is null.");
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const jsize length = env->GetArrayLength(data);
|
|
213
|
-
// ALLOCATES, so it can raise a C++ exception: `std::vector` throws
|
|
214
|
-
// `std::bad_alloc` for a 4 MiB raster under memory pressure, and the map node
|
|
215
|
-
// allocation can too. (`nativeStoreFromBitmap` allocates as well, but reports
|
|
216
|
-
// every failure of its own as `false` rather than as a throwable — see there.)
|
|
217
|
-
//
|
|
218
|
-
// A C++ exception MUST NOT unwind out of a JNI method: the ART frame below is
|
|
219
|
-
// not exception-aware, so the unwind reaches `std::terminate` and aborts the
|
|
220
|
-
// process. It is converted to a Java throwable here because that is what this
|
|
221
|
-
// method's callers are written to catch — `PdfCanvasModule.installJsiTransport`
|
|
222
|
-
// catches `Throwable` and returns -1, which declines the whole transport and
|
|
223
|
-
// leaves base64 in place. So the promised fallback is real.
|
|
224
|
-
try {
|
|
225
|
-
auto buffer = std::make_shared<SlotBuffer>(static_cast<size_t>(length));
|
|
226
|
-
if (length > 0) {
|
|
227
|
-
// GetByteArrayRegion, not GetPrimitiveArrayCritical: the critical variants
|
|
228
|
-
// suspend GC for their duration and this is a 4 MiB copy on a worker
|
|
229
|
-
// thread, which is exactly the shape of call that should not be holding the
|
|
230
|
-
// collector still.
|
|
231
|
-
env->GetByteArrayRegion(
|
|
232
|
-
data, 0, length, reinterpret_cast<jbyte*>(buffer->data()));
|
|
233
|
-
}
|
|
234
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
235
|
-
// Left as an overwriting assignment, deliberately: this is the FALLBACK
|
|
236
|
-
// transport and the instrumented suite proves its current behaviour. It does
|
|
237
|
-
// not go through publishSlot's refusal, which is new-path-only.
|
|
238
|
-
g_slots[static_cast<int64_t>(slot)] = std::move(buffer);
|
|
239
|
-
} catch (...) {
|
|
240
|
-
throwJava(
|
|
241
|
-
env,
|
|
242
|
-
"java/lang/OutOfMemoryError",
|
|
243
|
-
"nativeStore: could not park the raster in native memory.");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* THE ONE-COPY PATH: a `Bitmap`'s pixels straight into a `SlotBuffer`.
|
|
249
|
-
*
|
|
250
|
-
* Deletes, per tile, one full-raster `new byte[]` on the ART heap and one
|
|
251
|
-
* full-raster memcpy out of it. What is left is the single `memcpy` below, which
|
|
252
|
-
* is the same copy `Bitmap.copyPixelsToBuffer` would have performed — it too is
|
|
253
|
-
* a `memcpy` from `bitmap.getPixels()` through Skia's `AutoBufferPointer`.
|
|
254
|
-
*
|
|
255
|
-
* FAILS BY RETURNING FALSE, NEVER BY THROWING. Every refusal here is a tile that
|
|
256
|
-
* goes out on the `byte[]` path instead, which is the transport that has always
|
|
257
|
-
* worked; `PdfCanvasRasterizer.extract` treats false as "use the heap" and does
|
|
258
|
-
* not distinguish the reasons. So an allocation failure becomes a slower correct
|
|
259
|
-
* tile rather than a `PdfErrorCode`, and if the heap allocation then fails too,
|
|
260
|
-
* THAT is what maps to `OUT_OF_MEMORY` — the same mapping as before this path
|
|
261
|
-
* existed.
|
|
262
|
-
*
|
|
263
|
-
* WHY byteCount IS PASSED IN AND ALSO CROSS-CHECKED. Java has already validated
|
|
264
|
-
* `rowBytes >= width*4` and `byteCount >= rowBytes*height` against the Bitmap's
|
|
265
|
-
* own reported layout, and that validation must stay where it is (before any
|
|
266
|
-
* allocation). `AndroidBitmap_getInfo` reports the stride a SECOND time, from the
|
|
267
|
-
* NDK side, and a disagreement between the two would mean copying a number of
|
|
268
|
-
* bytes the source does not have — a short read at best, a shear at worst. So
|
|
269
|
-
* `stride * height` must equal the count Java asked for, exactly; a mismatch is
|
|
270
|
-
* refused rather than reconciled, because guessing which of the two is right is
|
|
271
|
-
* how a plausible-looking skew ships.
|
|
272
|
-
*
|
|
273
|
-
* The format is checked for the same reason. `Bitmap.Config.ARGB_8888` is named
|
|
274
|
-
* for a packed int and laid out R,G,B,A in memory, which is what the NDK calls
|
|
275
|
-
* `ANDROID_BITMAP_FORMAT_RGBA_8888` and what `RasterPixels.FORMAT_RGBA_8888`
|
|
276
|
-
* declares to the TS side. Asserting it here is what stops that agreement from
|
|
277
|
-
* being an assumption.
|
|
278
|
-
*/
|
|
279
|
-
JNIEXPORT jboolean JNICALL
|
|
280
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeStoreFromBitmap(
|
|
281
|
-
JNIEnv* env,
|
|
282
|
-
jclass,
|
|
283
|
-
jlong slot,
|
|
284
|
-
jobject bitmap,
|
|
285
|
-
jint width,
|
|
286
|
-
jint height,
|
|
287
|
-
jint byteCount) {
|
|
288
|
-
if (bitmap == nullptr || width <= 0 || height <= 0 || byteCount <= 0) {
|
|
289
|
-
return JNI_FALSE;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
AndroidBitmapInfo info;
|
|
293
|
-
if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) {
|
|
294
|
-
return JNI_FALSE;
|
|
295
|
-
}
|
|
296
|
-
if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
|
|
297
|
-
return JNI_FALSE;
|
|
298
|
-
}
|
|
299
|
-
// The shape the caller is about to DECLARE to the TS side, against the shape
|
|
300
|
-
// the NDK says this bitmap has. They cannot differ today — the bitmap was
|
|
301
|
-
// created at exactly pxW x pxH — and checking costs two comparisons, which
|
|
302
|
-
// is a better price than a raster that is described as one size and holds
|
|
303
|
-
// another.
|
|
304
|
-
if (info.width != static_cast<uint32_t>(width) ||
|
|
305
|
-
info.height != static_cast<uint32_t>(height)) {
|
|
306
|
-
return JNI_FALSE;
|
|
307
|
-
}
|
|
308
|
-
// In 64-bit arithmetic on purpose: stride and height are uint32_t, and a
|
|
309
|
-
// 32-bit product would wrap before it could disagree with byteCount.
|
|
310
|
-
const uint64_t reported =
|
|
311
|
-
static_cast<uint64_t>(info.stride) * static_cast<uint64_t>(info.height);
|
|
312
|
-
if (reported != static_cast<uint64_t>(byteCount)) {
|
|
313
|
-
return JNI_FALSE;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
std::shared_ptr<SlotBuffer> buffer;
|
|
317
|
-
try {
|
|
318
|
-
buffer = std::make_shared<SlotBuffer>(static_cast<size_t>(byteCount));
|
|
319
|
-
} catch (...) {
|
|
320
|
-
// `std::bad_alloc` for a 4 MiB raster under pressure. A C++ exception must
|
|
321
|
-
// never unwind out of a JNI method — the ART frame below is not
|
|
322
|
-
// exception-aware and the unwind reaches `std::terminate` — so it stops here
|
|
323
|
-
// and becomes a `false`.
|
|
324
|
-
return JNI_FALSE;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
void* pixels = nullptr;
|
|
328
|
-
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) != ANDROID_BITMAP_RESULT_SUCCESS ||
|
|
329
|
-
pixels == nullptr) {
|
|
330
|
-
// Nothing to unlock: a failed lock is not balanced by an unlock. `buffer`
|
|
331
|
-
// is freed by its shared_ptr on the way out and no slot was published.
|
|
332
|
-
return JNI_FALSE;
|
|
333
|
-
}
|
|
334
|
-
std::memcpy(buffer->data(), pixels, static_cast<size_t>(byteCount));
|
|
335
|
-
// Balanced immediately, and before anything that could throw or return early.
|
|
336
|
-
// A leaked lock would pin the Bitmap's pixels for the life of the process.
|
|
337
|
-
AndroidBitmap_unlockPixels(env, bitmap);
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
// PUBLISHED LAST. Until this line the buffer is reachable only from this
|
|
341
|
-
// stack frame, so `nativeReleaseAll` on another thread cannot free memory
|
|
342
|
-
// this method is still writing into. See the header note.
|
|
343
|
-
if (!publishSlot(static_cast<int64_t>(slot), std::move(buffer))) {
|
|
344
|
-
return JNI_FALSE;
|
|
345
|
-
}
|
|
346
|
-
} catch (...) {
|
|
347
|
-
// The map node allocation can throw too, and it is inside the lock.
|
|
348
|
-
return JNI_FALSE;
|
|
349
|
-
}
|
|
350
|
-
return JNI_TRUE;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
JNIEXPORT void JNICALL
|
|
354
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeRelease(
|
|
355
|
-
JNIEnv*,
|
|
356
|
-
jclass,
|
|
357
|
-
jlong slot) {
|
|
358
|
-
takeSlot(static_cast<int64_t>(slot));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
JNIEXPORT void JNICALL
|
|
362
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeReleaseAll(
|
|
363
|
-
JNIEnv*,
|
|
364
|
-
jclass) {
|
|
365
|
-
std::unordered_map<int64_t, std::shared_ptr<SlotBuffer>> drained;
|
|
366
|
-
{
|
|
367
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
368
|
-
drained.swap(g_slots);
|
|
369
|
-
}
|
|
370
|
-
// Freed outside the lock. Dropping several megabytes of vectors is not
|
|
371
|
-
// something to do while the JS thread might be waiting on the same mutex.
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
JNIEXPORT jint JNICALL
|
|
375
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeSlotCount(
|
|
376
|
-
JNIEnv*,
|
|
377
|
-
jclass) {
|
|
378
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
379
|
-
return static_cast<jint>(g_slots.size());
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* The test seam, and the reason the standalone build compiles this file at all.
|
|
384
|
-
*
|
|
385
|
-
* Takes a slot back out as a Java `byte[]` through the SAME storage the
|
|
386
|
-
* ArrayBuffer would have wrapped. An instrumented test can therefore render a
|
|
387
|
-
* tile, store it, take it back, and compare against `Base64.decode` of the same
|
|
388
|
-
* pixels — proving the transport moves the bytes the base64 path moved, without
|
|
389
|
-
* needing a JS runtime or a whole app around it.
|
|
390
|
-
*/
|
|
391
|
-
JNIEXPORT jbyteArray JNICALL
|
|
392
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeTakeForTest(
|
|
393
|
-
JNIEnv* env,
|
|
394
|
-
jclass,
|
|
395
|
-
jlong slot) {
|
|
396
|
-
std::shared_ptr<SlotBuffer> buffer = takeSlot(static_cast<int64_t>(slot));
|
|
397
|
-
if (buffer == nullptr) {
|
|
398
|
-
return nullptr;
|
|
399
|
-
}
|
|
400
|
-
const jsize length = static_cast<jsize>(buffer->size());
|
|
401
|
-
jbyteArray out = env->NewByteArray(length);
|
|
402
|
-
if (out == nullptr) {
|
|
403
|
-
return nullptr;
|
|
404
|
-
}
|
|
405
|
-
if (length > 0) {
|
|
406
|
-
env->SetByteArrayRegion(
|
|
407
|
-
out, 0, length, reinterpret_cast<const jbyte*>(buffer->data()));
|
|
408
|
-
}
|
|
409
|
-
return out;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
JNIEXPORT jboolean JNICALL
|
|
413
|
-
Java_tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge_nativeInstall(
|
|
414
|
-
JNIEnv*,
|
|
415
|
-
jclass,
|
|
416
|
-
jlong runtimePointer) {
|
|
417
|
-
#ifdef PDFCANVAS_WITH_JSI
|
|
418
|
-
if (runtimePointer == 0) {
|
|
419
|
-
return JNI_FALSE;
|
|
420
|
-
}
|
|
421
|
-
auto& runtime = *reinterpret_cast<facebook::jsi::Runtime*>(runtimePointer);
|
|
422
|
-
try {
|
|
423
|
-
auto name =
|
|
424
|
-
facebook::jsi::PropNameID::forAscii(runtime, "__pdfCanvasTakePixels");
|
|
425
|
-
auto take = facebook::jsi::Function::createFromHostFunction(
|
|
426
|
-
runtime,
|
|
427
|
-
name,
|
|
428
|
-
1,
|
|
429
|
-
[](facebook::jsi::Runtime& rt,
|
|
430
|
-
const facebook::jsi::Value&,
|
|
431
|
-
const facebook::jsi::Value* args,
|
|
432
|
-
size_t count) -> facebook::jsi::Value {
|
|
433
|
-
if (count < 1 || !args[0].isNumber()) {
|
|
434
|
-
throw facebook::jsi::JSError(
|
|
435
|
-
rt, "__pdfCanvasTakePixels(slot): slot must be a number.");
|
|
436
|
-
}
|
|
437
|
-
const auto slot = static_cast<int64_t>(args[0].asNumber());
|
|
438
|
-
std::shared_ptr<SlotBuffer> buffer = takeSlot(slot);
|
|
439
|
-
if (buffer == nullptr) {
|
|
440
|
-
// Every slot is taken exactly once, so this means the render that
|
|
441
|
-
// filled it was torn down (a reload drains every slot) or the same
|
|
442
|
-
// slot was taken twice. Named rather than silently empty: an empty
|
|
443
|
-
// ArrayBuffer would reach Skia and draw a black tile.
|
|
444
|
-
throw facebook::jsi::JSError(
|
|
445
|
-
rt,
|
|
446
|
-
"__pdfCanvasTakePixels: no raster is waiting in slot " +
|
|
447
|
-
std::to_string(slot) +
|
|
448
|
-
". It was already taken, or the module was invalidated.");
|
|
449
|
-
}
|
|
450
|
-
// The ArrayBuffer BORROWS this vector and the shared_ptr keeps it
|
|
451
|
-
// alive; the slot map has already dropped its reference, so JS is now
|
|
452
|
-
// the sole owner and the GC is what frees it.
|
|
453
|
-
return facebook::jsi::Value(
|
|
454
|
-
rt, facebook::jsi::ArrayBuffer(rt, std::move(buffer)));
|
|
455
|
-
});
|
|
456
|
-
runtime.global().setProperty(runtime, name, take);
|
|
457
|
-
return JNI_TRUE;
|
|
458
|
-
} catch (...) {
|
|
459
|
-
// A runtime that refuses to install a host function is a runtime this
|
|
460
|
-
// package will not use. The JS side falls back to base64.
|
|
461
|
-
return JNI_FALSE;
|
|
462
|
-
}
|
|
463
|
-
#else
|
|
464
|
-
(void)runtimePointer;
|
|
465
|
-
return JNI_FALSE;
|
|
466
|
-
#endif
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
} // extern "C"
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas.jsi;
|
|
2
|
-
|
|
3
|
-
import android.graphics.Bitmap;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The Java face of the raster transport — see {@code android/src/jsi/cpp/pdfcanvas-jsi.cpp}.
|
|
7
|
-
*
|
|
8
|
-
* <h3>Why this is its own source set</h3>
|
|
9
|
-
*
|
|
10
|
-
* It is compiled by BOTH builds. {@code src/main} is the React-free rasterizer
|
|
11
|
-
* core and {@code src/reactnative} is the React binding; this is a third thing —
|
|
12
|
-
* React-free like the core, but native, and only half of it is reachable without
|
|
13
|
-
* a React Native runtime. Keeping it separate is what lets the standalone build
|
|
14
|
-
* (which has no ReactAndroid, no libjsi.so, and is the build that runs the
|
|
15
|
-
* instrumented suite) compile the slot storage and prove its byte fidelity,
|
|
16
|
-
* while the autolinked build additionally gets {@link #nativeInstall}.
|
|
17
|
-
*
|
|
18
|
-
* <h3>The contract</h3>
|
|
19
|
-
*
|
|
20
|
-
* A slot is a monotonically increasing id chosen by the caller. The pixels reach
|
|
21
|
-
* C++-owned memory on whatever thread stores them — the render executor, never
|
|
22
|
-
* the JS thread — and are taken exactly once, either by the JS host function
|
|
23
|
-
* {@code __pdfCanvasTakePixels} (which wraps them in a zero-copy ArrayBuffer) or
|
|
24
|
-
* by {@link #nativeTakeForTest}. Taking removes the slot; a slot that is never
|
|
25
|
-
* taken is freed by {@link #nativeReleaseAll}.
|
|
26
|
-
*
|
|
27
|
-
* <p>TWO WAYS IN, and they are not equivalent.
|
|
28
|
-
* {@link #nativeStoreFromBitmap} is the one-copy path: it allocates the native
|
|
29
|
-
* buffer and fills it from the {@code Bitmap}'s own pixels, so no {@code byte[]}
|
|
30
|
-
* is ever allocated. {@link #nativeStore} is the two-copy path, kept because the
|
|
31
|
-
* base64 fallback needs that {@code byte[]} regardless. Prefer the former; use
|
|
32
|
-
* the latter when the bytes are already in a Java array.
|
|
33
|
-
*
|
|
34
|
-
* <h3>Degrading</h3>
|
|
35
|
-
*
|
|
36
|
-
* {@link #isLoaded()} is false when {@code libpdfcanvasjsi.so} is absent from the
|
|
37
|
-
* APK — an older app binary, a build that skipped the native build, an ABI with
|
|
38
|
-
* no artifact. Every caller must treat that as "use base64", because rendering
|
|
39
|
-
* correctly matters more than rendering fast. Nothing here throws to say so.
|
|
40
|
-
*/
|
|
41
|
-
public final class PdfCanvasPixelBridge {
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* The self-test payload.
|
|
45
|
-
*
|
|
46
|
-
* Stored by the module during install and taken by JS before the transport is
|
|
47
|
-
* enabled, so the first bytes to cross are ones both sides already agree on.
|
|
48
|
-
* It contains 0x00, 0xFF and a byte above 0x7F on purpose: Java's `byte` is
|
|
49
|
-
* signed, and a sign-extension slip in the JNI copy or in the ArrayBuffer view
|
|
50
|
-
* shows up here rather than as a discoloured tile.
|
|
51
|
-
*/
|
|
52
|
-
public static final byte[] PROBE_BYTES = {
|
|
53
|
-
0x00, 0x01, 0x02, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF, 0x2A, 0x07
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
private static final boolean LOADED = load();
|
|
57
|
-
|
|
58
|
-
private PdfCanvasPixelBridge() {}
|
|
59
|
-
|
|
60
|
-
private static boolean load() {
|
|
61
|
-
try {
|
|
62
|
-
System.loadLibrary("pdfcanvasjsi");
|
|
63
|
-
return true;
|
|
64
|
-
} catch (Throwable t) {
|
|
65
|
-
// UnsatisfiedLinkError on a build without the native library, and
|
|
66
|
-
// deliberately not rethrown: the package must still render.
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Whether {@code libpdfcanvasjsi.so} loaded. False means base64. */
|
|
72
|
-
public static boolean isLoaded() {
|
|
73
|
-
return LOADED;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Installs {@code globalThis.__pdfCanvasTakePixels} into the JSI runtime.
|
|
78
|
-
*
|
|
79
|
-
* MUST be called on the JS thread with a live runtime pointer. Returns false
|
|
80
|
-
* — never throws — when the library was built without JSI (the standalone
|
|
81
|
-
* build), when the pointer is null, or when the runtime refuses the host
|
|
82
|
-
* function.
|
|
83
|
-
*/
|
|
84
|
-
public static native boolean nativeInstall(long runtimePointer);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Copies {@code bytes} into C++-owned memory under {@code slot}.
|
|
88
|
-
*
|
|
89
|
-
* The bytes are already on the Java heap by the time this is called, so this
|
|
90
|
-
* is the SECOND full-raster copy of that raster. Use
|
|
91
|
-
* {@link #nativeStoreFromBitmap} where the source is still a {@code Bitmap}.
|
|
92
|
-
*/
|
|
93
|
-
public static native void nativeStore(long slot, byte[] bytes);
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Copies a {@code Bitmap}'s pixels DIRECTLY into C++-owned memory under
|
|
97
|
-
* {@code slot} — one memcpy, and not one byte of Java heap.
|
|
98
|
-
*
|
|
99
|
-
* <p>{@code width}, {@code height} and {@code byteCount} are the caller's
|
|
100
|
-
* already-validated view of the bitmap, and every one of them is cross-checked
|
|
101
|
-
* against what {@code AndroidBitmap_getInfo} reports for the SAME bitmap. A
|
|
102
|
-
* disagreement is a refusal rather than a reconciliation: copying a length the
|
|
103
|
-
* source does not have, or describing a raster as a shape it is not, would
|
|
104
|
-
* shear the image — which reads as a plausible skew rather than an error.
|
|
105
|
-
*
|
|
106
|
-
* <p>RETURNS FALSE INSTEAD OF THROWING, for every failure — a native
|
|
107
|
-
* allocation that could not be had, a bitmap that would not lock, a format or
|
|
108
|
-
* stride that did not match, an id already occupied. False means "the raster
|
|
109
|
-
* is NOT in the slot; fall back to the {@code byte[]} path", and no slot is
|
|
110
|
-
* left behind to release. The caller must not treat it as an error: the
|
|
111
|
-
* fallback is the transport that has always worked.
|
|
112
|
-
*
|
|
113
|
-
* <p>MUST NOT be called when {@link #isLoaded()} is false — like every native
|
|
114
|
-
* method here, that is an {@code UnsatisfiedLinkError}, not a {@code false}.
|
|
115
|
-
*
|
|
116
|
-
* @param bitmap a mutable, non-recycled {@code ARGB_8888} bitmap. It is locked
|
|
117
|
-
* for the duration of the copy and unlocked before this returns.
|
|
118
|
-
*/
|
|
119
|
-
public static native boolean nativeStoreFromBitmap(
|
|
120
|
-
long slot, Bitmap bitmap, int width, int height, int byteCount);
|
|
121
|
-
|
|
122
|
-
/** Drops one slot if it is still there. */
|
|
123
|
-
public static native void nativeRelease(long slot);
|
|
124
|
-
|
|
125
|
-
/** Drops every slot. Called from {@code PdfCanvasModule.invalidate()}. */
|
|
126
|
-
public static native void nativeReleaseAll();
|
|
127
|
-
|
|
128
|
-
/** Slots currently waiting to be taken. For assertions, not for logic. */
|
|
129
|
-
public static native int nativeSlotCount();
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Takes a slot back out as a Java array, through the identical storage the
|
|
133
|
-
* ArrayBuffer would have wrapped. Null when the slot is empty.
|
|
134
|
-
*
|
|
135
|
-
* The instrumented suite's only way to check the transport without a JS
|
|
136
|
-
* runtime, which is why it ships rather than living in androidTest: the JNI
|
|
137
|
-
* copy under test is the production one.
|
|
138
|
-
*/
|
|
139
|
-
public static native byte[] nativeTakeForTest(long slot);
|
|
140
|
-
}
|