@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
package/native/.clangd
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# THE LAPTOP PROOF OF THE C++ CORE.
|
|
2
|
+
#
|
|
3
|
+
# Builds `native/core` against the pinned PDFium release for whatever machine
|
|
4
|
+
# this is — Windows, macOS or Linux — and runs `native/tests` against it. No
|
|
5
|
+
# device, no emulator, no simulator, no React Native, no Xcode, no Gradle: the
|
|
6
|
+
# same sources the Android library and the CocoaPods build compile, proven here
|
|
7
|
+
# with real PDFium in a few seconds. It replaces the Swift suite (`swift test`)
|
|
8
|
+
# and the instrumented Android suite as the place rendering is proven, because
|
|
9
|
+
# both platforms now share this one engine.
|
|
10
|
+
#
|
|
11
|
+
# yarn pdfium:fetch host # once; downloads + verifies PDFium
|
|
12
|
+
# cmake -S native -B native/build -G Ninja
|
|
13
|
+
# cmake --build native/build
|
|
14
|
+
# ctest --test-dir native/build --output-on-failure
|
|
15
|
+
#
|
|
16
|
+
# (`yarn native:test` does all four.) On Windows use the MinGW g++ or an LLVM
|
|
17
|
+
# clang that targets the GNU ABI — PDFium's DLL exports a plain C surface, so
|
|
18
|
+
# any toolchain that can link a DLL works.
|
|
19
|
+
|
|
20
|
+
cmake_minimum_required(VERSION 3.20)
|
|
21
|
+
project(pdfcanvas_native CXX)
|
|
22
|
+
|
|
23
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
24
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
25
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
26
|
+
if(NOT CMAKE_BUILD_TYPE)
|
|
27
|
+
set(CMAKE_BUILD_TYPE Release)
|
|
28
|
+
endif()
|
|
29
|
+
|
|
30
|
+
# Where `scripts/fetch-pdfium.mjs host` put the release. Overridable so a
|
|
31
|
+
# vendored copy can be pointed at.
|
|
32
|
+
set(PDFCANVAS_PDFIUM_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.pdfium/host"
|
|
33
|
+
CACHE PATH "Extracted pdfium-<host>.tgz (include/, lib/, bin/)")
|
|
34
|
+
if(NOT EXISTS "${PDFCANVAS_PDFIUM_DIR}/include/fpdfview.h")
|
|
35
|
+
message(FATAL_ERROR
|
|
36
|
+
"No PDFium at ${PDFCANVAS_PDFIUM_DIR}. Run `node scripts/fetch-pdfium.mjs host` "
|
|
37
|
+
"from the package root first (or set PDFCANVAS_PDFIUM_DIR).")
|
|
38
|
+
endif()
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------- PDFium
|
|
41
|
+
add_library(pdfium SHARED IMPORTED)
|
|
42
|
+
if(WIN32)
|
|
43
|
+
set_target_properties(pdfium PROPERTIES
|
|
44
|
+
IMPORTED_LOCATION "${PDFCANVAS_PDFIUM_DIR}/bin/pdfium.dll"
|
|
45
|
+
IMPORTED_IMPLIB "${PDFCANVAS_PDFIUM_DIR}/lib/pdfium.dll.lib")
|
|
46
|
+
elseif(APPLE)
|
|
47
|
+
set_target_properties(pdfium PROPERTIES
|
|
48
|
+
IMPORTED_LOCATION "${PDFCANVAS_PDFIUM_DIR}/lib/libpdfium.dylib")
|
|
49
|
+
else()
|
|
50
|
+
set_target_properties(pdfium PROPERTIES
|
|
51
|
+
IMPORTED_LOCATION "${PDFCANVAS_PDFIUM_DIR}/lib/libpdfium.so")
|
|
52
|
+
endif()
|
|
53
|
+
target_include_directories(pdfium INTERFACE "${PDFCANVAS_PDFIUM_DIR}/include")
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------- core
|
|
56
|
+
include(core/pdfcanvas-core.cmake)
|
|
57
|
+
add_library(pdfcanvas_core STATIC ${PDFCANVAS_CORE_SOURCES})
|
|
58
|
+
target_include_directories(pdfcanvas_core PUBLIC "${PDFCANVAS_CORE_INCLUDE_DIR}")
|
|
59
|
+
target_link_libraries(pdfcanvas_core PUBLIC pdfium)
|
|
60
|
+
target_compile_options(pdfcanvas_core PRIVATE
|
|
61
|
+
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -Werror>)
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------- tests
|
|
64
|
+
file(GLOB PDFCANVAS_TEST_SOURCES CONFIGURE_DEPENDS "tests/*.cpp")
|
|
65
|
+
add_executable(pdfcanvas_tests ${PDFCANVAS_TEST_SOURCES})
|
|
66
|
+
target_link_libraries(pdfcanvas_tests PRIVATE pdfcanvas_core)
|
|
67
|
+
target_compile_options(pdfcanvas_tests PRIVATE
|
|
68
|
+
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -Werror>)
|
|
69
|
+
if(WIN32)
|
|
70
|
+
# The DLL has to sit next to the executable; there is no rpath on Windows.
|
|
71
|
+
add_custom_command(TARGET pdfcanvas_tests POST_BUILD
|
|
72
|
+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
73
|
+
"${PDFCANVAS_PDFIUM_DIR}/bin/pdfium.dll" "$<TARGET_FILE_DIR:pdfcanvas_tests>")
|
|
74
|
+
if(MINGW)
|
|
75
|
+
# Static C++ runtime, so the test binary runs from ctest without the MinGW
|
|
76
|
+
# bin directory on PATH (a missing libstdc++-6.dll exits silently with
|
|
77
|
+
# 0xC0000135 and no output — which reads as "the suite printed nothing").
|
|
78
|
+
target_link_options(pdfcanvas_tests PRIVATE -static-libstdc++ -static-libgcc -static)
|
|
79
|
+
endif()
|
|
80
|
+
elseif(APPLE)
|
|
81
|
+
set_target_properties(pdfcanvas_tests PROPERTIES
|
|
82
|
+
BUILD_RPATH "${PDFCANVAS_PDFIUM_DIR}/lib")
|
|
83
|
+
else()
|
|
84
|
+
set_target_properties(pdfcanvas_tests PROPERTIES
|
|
85
|
+
BUILD_RPATH "${PDFCANVAS_PDFIUM_DIR}/lib")
|
|
86
|
+
endif()
|
|
87
|
+
|
|
88
|
+
enable_testing()
|
|
89
|
+
add_test(NAME pdfcanvas_tests COMMAND pdfcanvas_tests)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// One open PDF, rendered by PDFium.
|
|
2
|
+
//
|
|
3
|
+
// THE SAME ENGINE ON EVERY PLATFORM. Android's `PdfRenderer` was PDFium behind
|
|
4
|
+
// a Java wrapper that exposed no annotation switch, no cancel, no password and
|
|
5
|
+
// a process-wide lock that convoyed; iOS was CoreGraphics, which antialiases a
|
|
6
|
+
// 0.25pt CAD line at fit zoom into 10% grey where PDFium clamps every stroke to
|
|
7
|
+
// a device pixel. This class replaces both with the engine the web backend
|
|
8
|
+
// already used, so a tile is byte-identical across all three platforms rather
|
|
9
|
+
// than merely similar, and `annotations` means the same thing everywhere.
|
|
10
|
+
//
|
|
11
|
+
// THREADING. PDFium is not thread-safe — not across documents, not across
|
|
12
|
+
// threads, not at all — so every entry point below takes the process-wide
|
|
13
|
+
// library lock. That is not a regression on either platform: Android declared
|
|
14
|
+
// one render lane because AOSP's lock convoyed, and iOS declared one because a
|
|
15
|
+
// second was never measured to help. Cancellation is what makes one lane
|
|
16
|
+
// cheap: a superseded tile stops MID-RENDER (see `Cancellation`), so a stale
|
|
17
|
+
// render never holds the lock for longer than one progressive pause.
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <memory>
|
|
21
|
+
#include <vector>
|
|
22
|
+
|
|
23
|
+
#include "pdfcanvas/error.h"
|
|
24
|
+
#include "pdfcanvas/pixels.h"
|
|
25
|
+
#include "pdfcanvas/types.h"
|
|
26
|
+
|
|
27
|
+
namespace pdfcanvas {
|
|
28
|
+
|
|
29
|
+
class Document {
|
|
30
|
+
public:
|
|
31
|
+
/// Refuse absurd rasters instead of dying on an allocation failure. 64 Mpx
|
|
32
|
+
/// is 256 MB at 4 bytes per pixel — past what a device hands out in one
|
|
33
|
+
/// allocation — so anything above it is a planner bug, and a named error
|
|
34
|
+
/// against a raster key beats a jetsam kill with no key attached. The TS
|
|
35
|
+
/// policy has its own, much lower, ceilings (`maxRasterPixels`).
|
|
36
|
+
static constexpr long long kMaxRasterPixels = 64'000'000;
|
|
37
|
+
|
|
38
|
+
/// How many parsed pages one document keeps. An epoch is several tiles of
|
|
39
|
+
/// the same page, and a continuous layout keeps two or three pages live, so
|
|
40
|
+
/// alternating between them must not re-parse a dense drawing every tile.
|
|
41
|
+
static constexpr int kPageCacheCapacity = 3;
|
|
42
|
+
|
|
43
|
+
/// Opens a document. Throws `PdfError` with `not-found` (no such file, or
|
|
44
|
+
/// unreadable), `corrupt` (empty, not a PDF, unparseable page tree),
|
|
45
|
+
/// `password-required` / `password-incorrect` (decided by whether the caller
|
|
46
|
+
/// supplied one — PDFium reports the same code for both), `unsupported` (an
|
|
47
|
+
/// encryption scheme this build cannot handle) or `backend-failure`.
|
|
48
|
+
static std::unique_ptr<Document> open(const Source& source);
|
|
49
|
+
|
|
50
|
+
~Document();
|
|
51
|
+
Document(const Document&) = delete;
|
|
52
|
+
Document& operator=(const Document&) = delete;
|
|
53
|
+
|
|
54
|
+
int pageCount() const noexcept { return static_cast<int>(pages_.size()); }
|
|
55
|
+
|
|
56
|
+
/// Every page's geometry, measured once at open from the page dictionary —
|
|
57
|
+
/// no content stream is parsed, so this is proportional to the page tree and
|
|
58
|
+
/// not to what is drawn on it, which is what keeps `pageGeometry` cheap.
|
|
59
|
+
const std::vector<PageGeometry>& pages() const noexcept { return pages_; }
|
|
60
|
+
|
|
61
|
+
/// Throws `not-found` for an index the document does not have.
|
|
62
|
+
const PageGeometry& geometry(int index) const;
|
|
63
|
+
|
|
64
|
+
/// Renders one raster.
|
|
65
|
+
///
|
|
66
|
+
/// `signal` may be null. It is observed before the lock, after it, before
|
|
67
|
+
/// the draw, DURING the draw (PDFium's progressive API pauses and asks), and
|
|
68
|
+
/// after it — the last one only stops a superseded raster being handed back.
|
|
69
|
+
///
|
|
70
|
+
/// `sink` may be null, which means the heap. A non-null sink may REFUSE its
|
|
71
|
+
/// reservation, in which case the render falls through to the heap and the
|
|
72
|
+
/// result is heap-backed — check `RasterPixels::inSlot()` rather than
|
|
73
|
+
/// assuming. A cancellation observed after the draw abandons the sink itself.
|
|
74
|
+
RasterPixels render(const RasterRequest& request, Cancellation* signal,
|
|
75
|
+
PixelSink* sink);
|
|
76
|
+
|
|
77
|
+
/// Releases every PDFium object. Blocks until an in-flight render on this
|
|
78
|
+
/// document finishes (it holds the library lock), and is therefore free of
|
|
79
|
+
/// the use-after-free window the Android pool's close had. Idempotent.
|
|
80
|
+
void close();
|
|
81
|
+
bool isClosed() const noexcept;
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
struct Impl;
|
|
85
|
+
explicit Document(std::unique_ptr<Impl> impl);
|
|
86
|
+
std::unique_ptr<Impl> impl_;
|
|
87
|
+
std::vector<PageGeometry> pages_;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The eight failure kinds, mirroring `PdfErrorCode` in `src/types.ts`.
|
|
2
|
+
//
|
|
3
|
+
// THE WIRE STRINGS ARE THE TYPESCRIPT UNION MEMBERS VERBATIM. Both bindings
|
|
4
|
+
// reject a promise with `wireName(code)` and `native-bridge.ts` reconstructs a
|
|
5
|
+
// `PdfError` from it with no translation table in between. There used to be
|
|
6
|
+
// three copies of this list (Java, Objective-C, TypeScript); there are now two,
|
|
7
|
+
// and this is the native one.
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <exception>
|
|
11
|
+
#include <string>
|
|
12
|
+
|
|
13
|
+
namespace pdfcanvas {
|
|
14
|
+
|
|
15
|
+
enum class ErrorCode {
|
|
16
|
+
NotFound,
|
|
17
|
+
PasswordRequired,
|
|
18
|
+
PasswordIncorrect,
|
|
19
|
+
Corrupt,
|
|
20
|
+
Unsupported,
|
|
21
|
+
Cancelled,
|
|
22
|
+
OutOfMemory,
|
|
23
|
+
BackendFailure,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/// The TypeScript union member for a code. Never null.
|
|
27
|
+
const char* wireName(ErrorCode code);
|
|
28
|
+
|
|
29
|
+
/// The one exception type the core throws. Bindings catch it at the JNI /
|
|
30
|
+
/// Objective-C boundary and turn it into a promise rejection; nothing else may
|
|
31
|
+
/// escape the core, because a C++ exception unwinding into ART or ARC is a
|
|
32
|
+
/// process abort rather than an error anyone can read.
|
|
33
|
+
class PdfError : public std::exception {
|
|
34
|
+
public:
|
|
35
|
+
PdfError(ErrorCode code, std::string message)
|
|
36
|
+
: code_(code), message_(std::move(message)) {}
|
|
37
|
+
|
|
38
|
+
ErrorCode code() const noexcept { return code_; }
|
|
39
|
+
const char* what() const noexcept override { return message_.c_str(); }
|
|
40
|
+
const std::string& message() const noexcept { return message_; }
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
ErrorCode code_;
|
|
44
|
+
std::string message_;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The one JSI touch point: installing `globalThis.__pdfCanvasTakePixels`.
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
namespace pdfcanvas {
|
|
5
|
+
|
|
6
|
+
/// Installs the host function into a `facebook::jsi::Runtime`.
|
|
7
|
+
///
|
|
8
|
+
/// MUST be called on the JS thread with a live runtime pointer. Returns false —
|
|
9
|
+
/// never throws — when the pointer is null, when this build has no JSI
|
|
10
|
+
/// (`PDFCANVAS_HAS_JSI == 0`), or when the runtime refuses the host function.
|
|
11
|
+
/// The JS side then falls back to base64: rendering correctly is worth more
|
|
12
|
+
/// than rendering fast.
|
|
13
|
+
///
|
|
14
|
+
/// Takes `void*` so the declaration is includable from plain C++, Objective-C++
|
|
15
|
+
/// and JNI code alike without dragging `<jsi/jsi.h>` into every consumer.
|
|
16
|
+
bool installTakePixels(void* runtimePointer);
|
|
17
|
+
|
|
18
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Whether this translation unit is being built where `<jsi/jsi.h>` exists.
|
|
2
|
+
//
|
|
3
|
+
// TWO BUILDS, ONE CORE, decided here and nowhere else:
|
|
4
|
+
//
|
|
5
|
+
// PDFCANVAS_HAS_JSI == 1 the autolinked Android library (ReactAndroid's
|
|
6
|
+
// prefab publishes jsi/jsi.h and libjsi.so) and the
|
|
7
|
+
// CocoaPods build (React-jsi is a dependency). The
|
|
8
|
+
// slot buffer IS a `jsi::MutableBuffer` and
|
|
9
|
+
// `installTakePixels` puts the host function on the
|
|
10
|
+
// JS global.
|
|
11
|
+
// PDFCANVAS_HAS_JSI == 0 the host test suite and the standalone Android
|
|
12
|
+
// library. No React Native anywhere; the storage
|
|
13
|
+
// compiles against a restated interface and
|
|
14
|
+
// `installTakePixels` honestly reports false.
|
|
15
|
+
//
|
|
16
|
+
// Every TU in one target must agree, which they do: the include path is a
|
|
17
|
+
// property of the target, and the explicit define is passed target-wide.
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#if defined(PDFCANVAS_WITH_JSI)
|
|
21
|
+
#define PDFCANVAS_HAS_JSI 1
|
|
22
|
+
#elif defined(__has_include)
|
|
23
|
+
#if __has_include(<jsi/jsi.h>)
|
|
24
|
+
#define PDFCANVAS_HAS_JSI 1
|
|
25
|
+
#else
|
|
26
|
+
#define PDFCANVAS_HAS_JSI 0
|
|
27
|
+
#endif
|
|
28
|
+
#else
|
|
29
|
+
#define PDFCANVAS_HAS_JSI 0
|
|
30
|
+
#endif
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// PDFium's process-wide state: one initialisation, one lock.
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <mutex>
|
|
5
|
+
|
|
6
|
+
namespace pdfcanvas {
|
|
7
|
+
|
|
8
|
+
class Library {
|
|
9
|
+
public:
|
|
10
|
+
/// The lock every PDFium call is made under. PDFium keeps global state
|
|
11
|
+
/// (`CPDF_ModuleMgr`, the font cache, the AGG rasterizer's scratch buffers)
|
|
12
|
+
/// and is documented not thread-safe across documents or threads, so the
|
|
13
|
+
/// only correct shape is one mutex around the whole library. Held for the
|
|
14
|
+
/// duration of a render — which is why the render polls for cancellation
|
|
15
|
+
/// while it holds it.
|
|
16
|
+
static std::mutex& mutex();
|
|
17
|
+
|
|
18
|
+
/// Initialises PDFium exactly once. Callers hold `mutex()` when they call
|
|
19
|
+
/// this; it is cheap after the first time.
|
|
20
|
+
static void ensureInitialized();
|
|
21
|
+
|
|
22
|
+
/// Font directories handed to `FPDF_InitLibraryWithConfig`. Platform-specific
|
|
23
|
+
/// and null-terminated; may be ignored by platforms whose font mapper does
|
|
24
|
+
/// its own scanning (Android's does — `/system/fonts` is built in).
|
|
25
|
+
static const char** userFontPaths();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Where a raster's bytes go, and what comes back.
|
|
2
|
+
//
|
|
3
|
+
// THE SINK IS WHY THE TRANSPORT COSTS ZERO COPIES ON BOTH PLATFORMS NOW.
|
|
4
|
+
// `FPDFBitmap_CreateEx` takes the destination pointer as an argument, so PDFium
|
|
5
|
+
// writes the tile straight into the buffer that becomes the JS `ArrayBuffer`.
|
|
6
|
+
// The iOS CoreGraphics core had this property; the Android `PdfRenderer` core
|
|
7
|
+
// could not (a `Bitmap` owns its own pixels and had to be copied out), and that
|
|
8
|
+
// asymmetry is gone.
|
|
9
|
+
//
|
|
10
|
+
// RESERVE, FILL, PUBLISH — in that order, and the order is the safety property.
|
|
11
|
+
// A buffer reachable from the process-wide slot map BEFORE it is filled can be
|
|
12
|
+
// freed by `slots::releaseAll()` — which runs on another thread on every Fast
|
|
13
|
+
// Refresh — while PDFium is still writing into it. So a reserved buffer is
|
|
14
|
+
// owned by the sink object alone, on the rendering thread's stack, and becomes
|
|
15
|
+
// reachable only at `publish()`.
|
|
16
|
+
#pragma once
|
|
17
|
+
|
|
18
|
+
#include <cstddef>
|
|
19
|
+
#include <cstdint>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
namespace pdfcanvas {
|
|
23
|
+
|
|
24
|
+
class PixelSink {
|
|
25
|
+
public:
|
|
26
|
+
virtual ~PixelSink() = default;
|
|
27
|
+
|
|
28
|
+
/// Reserve `length` bytes, ZEROED, and return them — or nullptr to refuse.
|
|
29
|
+
/// Called at most once per sink, before anything is drawn. Zeroed is part of
|
|
30
|
+
/// the contract: it is what makes `Background::Transparent` a fill of zeros
|
|
31
|
+
/// over zeros, and it is the invariant that stops a published buffer ever
|
|
32
|
+
/// handing out memory nobody wrote.
|
|
33
|
+
virtual uint8_t* reserve(size_t length) = 0;
|
|
34
|
+
|
|
35
|
+
/// Publish the reserved buffer and return the id a caller redeems it with,
|
|
36
|
+
/// or 0 for a sink with no id (the heap). Must not fail: every reason a
|
|
37
|
+
/// publish could be refused is decided at `reserve()`, before the raster
|
|
38
|
+
/// exists.
|
|
39
|
+
virtual int64_t publish() = 0;
|
|
40
|
+
|
|
41
|
+
/// Drop the buffer, published or not. Idempotent. Called on every path that
|
|
42
|
+
/// abandons a raster after reserving for it.
|
|
43
|
+
virtual void abandon() = 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/// The default destination: an owned, zero-initialised vector. What every
|
|
47
|
+
/// pixel-reading caller — the host test suite, the base64 fallback — uses.
|
|
48
|
+
class HeapSink final : public PixelSink {
|
|
49
|
+
public:
|
|
50
|
+
uint8_t* reserve(size_t length) override;
|
|
51
|
+
int64_t publish() override { return 0; }
|
|
52
|
+
void abandon() override { bytes_.clear(); bytes_.shrink_to_fit(); }
|
|
53
|
+
|
|
54
|
+
/// Moves the bytes out. Valid once per sink, after `publish()`.
|
|
55
|
+
std::vector<uint8_t> takeBytes() { return std::move(bytes_); }
|
|
56
|
+
|
|
57
|
+
private:
|
|
58
|
+
std::vector<uint8_t> bytes_;
|
|
59
|
+
bool reserved_ = false;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/// A finished raster, mirroring `RasterPixels` in `src/types.ts`.
|
|
63
|
+
///
|
|
64
|
+
/// The pixels are in ONE OF TWO PLACES, and `slot` tells you which:
|
|
65
|
+
/// - `slot == 0`: on the heap, in `bytes`.
|
|
66
|
+
/// - `slot != 0`: in the process-wide slot store, waiting to become a JS
|
|
67
|
+
/// `ArrayBuffer` through `__pdfCanvasTakePixels`; `bytes` is empty.
|
|
68
|
+
///
|
|
69
|
+
/// Format is always RGBA8888 with STRAIGHT (un-premultiplied) alpha —
|
|
70
|
+
/// `FPDF_REVERSE_BYTE_ORDER` on a `FPDFBitmap_BGRA` bitmap — which the TS
|
|
71
|
+
/// adapter declares as `format: 'rgba8888', alpha: 'straight'`. Measured in the
|
|
72
|
+
/// web backend: an antialiased opaque-white edge over a transparent page reads
|
|
73
|
+
/// `[255, 255, 255, a]`, not `[a, a, a, a]`.
|
|
74
|
+
struct RasterPixels {
|
|
75
|
+
int width = 0;
|
|
76
|
+
int height = 0;
|
|
77
|
+
/// Always `width * 4` here — this core chooses the stride it hands PDFium —
|
|
78
|
+
/// and still carried explicitly, because a consumer that assumed it would
|
|
79
|
+
/// shear the image rather than fail the day it changed.
|
|
80
|
+
int rowBytes = 0;
|
|
81
|
+
int64_t slot = 0;
|
|
82
|
+
std::vector<uint8_t> bytes;
|
|
83
|
+
|
|
84
|
+
bool inSlot() const noexcept { return slot != 0; }
|
|
85
|
+
|
|
86
|
+
/// One channel of one pixel as 0..255. Heap-backed rasters only.
|
|
87
|
+
int channel(int x, int y, int c) const {
|
|
88
|
+
return bytes[static_cast<size_t>(y) * rowBytes + static_cast<size_t>(x) * 4 + c];
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Everything a React Native binding needs that is not marshalling.
|
|
2
|
+
//
|
|
3
|
+
// Both bindings used to carry their own handle map, their own (handle, token)
|
|
4
|
+
// cancellation registry, their own "read the transport flag once" dance and
|
|
5
|
+
// their own invalidate ordering, in two languages, with the same long comments
|
|
6
|
+
// twice. This class is that logic once, in the language both bindings can call.
|
|
7
|
+
// `PdfCanvasModule.java` and `PdfCanvasModule.mm` are now what their headers
|
|
8
|
+
// always claimed: type conversion, URI resolution, a worker queue, and promises.
|
|
9
|
+
//
|
|
10
|
+
// THREAD-SAFE. Bindings call `open`/`render` from a worker and `cancel`/`close`
|
|
11
|
+
// /`invalidate` from the bridge or JS thread. Rendering itself is serialised by
|
|
12
|
+
// the library lock inside `Document`; this class only guards its own maps, and
|
|
13
|
+
// never holds its mutex across a render.
|
|
14
|
+
#pragma once
|
|
15
|
+
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <memory>
|
|
18
|
+
#include <mutex>
|
|
19
|
+
#include <unordered_map>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
#include "pdfcanvas/document.h"
|
|
23
|
+
#include "pdfcanvas/pixels.h"
|
|
24
|
+
#include "pdfcanvas/types.h"
|
|
25
|
+
|
|
26
|
+
namespace pdfcanvas {
|
|
27
|
+
|
|
28
|
+
class Service {
|
|
29
|
+
public:
|
|
30
|
+
struct Opened {
|
|
31
|
+
int handle = 0;
|
|
32
|
+
std::vector<PageGeometry> pages;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
Service() = default;
|
|
36
|
+
Service(const Service&) = delete;
|
|
37
|
+
Service& operator=(const Service&) = delete;
|
|
38
|
+
|
|
39
|
+
/// Opens a document and returns its handle plus every page's geometry.
|
|
40
|
+
/// Throws `PdfError`; after `invalidate()` it throws `backend-failure`.
|
|
41
|
+
Opened open(const Source& source);
|
|
42
|
+
|
|
43
|
+
/// Registers a cancellation signal for (handle, token) BEFORE the render is
|
|
44
|
+
/// dispatched to a worker. That ordering is what lets `cancel()` be a pure
|
|
45
|
+
/// lookup: a cancel cannot arrive for a token that was never issued, so a
|
|
46
|
+
/// missing entry means the render already finished and was cleaned up.
|
|
47
|
+
void registerCancellation(int handle, int64_t token);
|
|
48
|
+
|
|
49
|
+
/// Renders one raster on the calling thread. The (handle, token) signal is
|
|
50
|
+
/// consumed and unregistered on every path out, success or throw.
|
|
51
|
+
///
|
|
52
|
+
/// `useSlot` asks for the zero-copy transport; the result may still be
|
|
53
|
+
/// heap-backed if the store refused, so bindings branch on
|
|
54
|
+
/// `RasterPixels::inSlot()` and not on what they asked for.
|
|
55
|
+
RasterPixels render(int handle, const RasterRequest& request, int64_t token,
|
|
56
|
+
bool useSlot);
|
|
57
|
+
|
|
58
|
+
/// Best-effort, and genuinely mid-render: sets the signal a render polls.
|
|
59
|
+
void cancel(int handle, int64_t token);
|
|
60
|
+
|
|
61
|
+
/// Closes one document. Blocks until its in-flight render finishes.
|
|
62
|
+
void close(int handle);
|
|
63
|
+
|
|
64
|
+
/// Closes every document and drains every parked slot. Called from a
|
|
65
|
+
/// binding's invalidate hook — a reload drops the JS side without ever
|
|
66
|
+
/// calling `close`, so this is what stops file descriptors and parsed pages
|
|
67
|
+
/// leaking across dozens of Fast Refreshes a day. Further calls fail with
|
|
68
|
+
/// `backend-failure` rather than reopening anything.
|
|
69
|
+
void invalidate();
|
|
70
|
+
|
|
71
|
+
bool isInvalidated() const;
|
|
72
|
+
|
|
73
|
+
/// Open documents, for diagnostics and tests.
|
|
74
|
+
size_t openCount() const;
|
|
75
|
+
|
|
76
|
+
private:
|
|
77
|
+
static int64_t signalKey(int handle, int64_t token) noexcept;
|
|
78
|
+
std::shared_ptr<Document> documentFor(int handle);
|
|
79
|
+
|
|
80
|
+
mutable std::mutex mutex_;
|
|
81
|
+
std::unordered_map<int, std::shared_ptr<Document>> documents_;
|
|
82
|
+
std::unordered_map<int64_t, std::shared_ptr<Cancellation>> signals_;
|
|
83
|
+
int nextHandle_ = 1;
|
|
84
|
+
bool invalidated_ = false;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// The raster transport's storage: PDFium writes a tile, and JS gets an
|
|
2
|
+
// `ArrayBuffer` over those exact bytes with no base64 and no copy anywhere.
|
|
3
|
+
//
|
|
4
|
+
// ONE STORE FOR BOTH PLATFORMS. `android/src/jsi/cpp/pdfcanvas-jsi.cpp` and
|
|
5
|
+
// `ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm` used to be two near-identical
|
|
6
|
+
// files with two sets of notes about the same hazards; this is the one that
|
|
7
|
+
// replaced them. The JSI half — installing `__pdfCanvasTakePixels` into a
|
|
8
|
+
// runtime — lives in `jsi.h` and is compiled only where `<jsi/jsi.h>` exists.
|
|
9
|
+
//
|
|
10
|
+
// WHY THE FALLBACK IS BASE64. Neither the legacy bridge nor new-architecture
|
|
11
|
+
// codegen has a binary type (measured: RN's `convertObjCObjectToJSIValue`
|
|
12
|
+
// handles NSString, NSNumber, NSDictionary, NSArray and nothing else, and a
|
|
13
|
+
// `WritableMap` has no bytes either). So a raster crosses as a slot NUMBER that
|
|
14
|
+
// JS redeems once, synchronously, for an `ArrayBuffer` wrapping this memory —
|
|
15
|
+
// or, when the transport could not be negotiated, as a base64 string, which on
|
|
16
|
+
// an LG V60 cost 1005 ms of Hermes decode per 1024x1024 tile.
|
|
17
|
+
//
|
|
18
|
+
// WHO FREES A SLOT. The map drops its reference the instant a take succeeds, so
|
|
19
|
+
// from that moment the only owner is the JS `ArrayBuffer` and the buffer dies
|
|
20
|
+
// when Hermes collects it. A slot that is stored and never taken — a teardown
|
|
21
|
+
// between the promise resolving and its continuation running — is freed by
|
|
22
|
+
// `releaseAll()`, which both bindings call from their invalidate hook.
|
|
23
|
+
#pragma once
|
|
24
|
+
|
|
25
|
+
#include <array>
|
|
26
|
+
#include <cstddef>
|
|
27
|
+
#include <cstdint>
|
|
28
|
+
#include <memory>
|
|
29
|
+
#include <vector>
|
|
30
|
+
|
|
31
|
+
#include "pdfcanvas/jsi_config.h"
|
|
32
|
+
#include "pdfcanvas/pixels.h"
|
|
33
|
+
|
|
34
|
+
#if PDFCANVAS_HAS_JSI
|
|
35
|
+
#include <jsi/jsi.h>
|
|
36
|
+
#endif
|
|
37
|
+
|
|
38
|
+
namespace pdfcanvas {
|
|
39
|
+
|
|
40
|
+
#if PDFCANVAS_HAS_JSI
|
|
41
|
+
using SlotBufferBase = facebook::jsi::MutableBuffer;
|
|
42
|
+
#else
|
|
43
|
+
/// The two methods `jsi::MutableBuffer` declares, restated so a build with no
|
|
44
|
+
/// JSI header (the host test suite, the standalone Android library) compiles
|
|
45
|
+
/// the identical storage code. If these ever drift from jsi.h the JSI builds
|
|
46
|
+
/// stop compiling, which is the failure mode worth having.
|
|
47
|
+
struct SlotBufferBase {
|
|
48
|
+
virtual ~SlotBufferBase() = default;
|
|
49
|
+
virtual size_t size() const = 0;
|
|
50
|
+
virtual uint8_t* data() = 0;
|
|
51
|
+
};
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
/// One raster's bytes, owned outright, shaped so Hermes can wrap it.
|
|
55
|
+
///
|
|
56
|
+
/// `std::vector<uint8_t> bytes_(size)` VALUE-initialises, so constructing this
|
|
57
|
+
/// zeroes the raster. That is the sink contract (`PixelSink::reserve`) and not
|
|
58
|
+
/// an accident of the implementation: it is what makes `Background::Transparent`
|
|
59
|
+
/// free and what stops a published buffer ever handing out somebody else's heap.
|
|
60
|
+
class SlotBuffer final : public SlotBufferBase {
|
|
61
|
+
public:
|
|
62
|
+
explicit SlotBuffer(size_t size) : bytes_(size) {}
|
|
63
|
+
size_t size() const override { return bytes_.size(); }
|
|
64
|
+
uint8_t* data() override { return bytes_.data(); }
|
|
65
|
+
|
|
66
|
+
private:
|
|
67
|
+
std::vector<uint8_t> bytes_;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
namespace slots {
|
|
71
|
+
|
|
72
|
+
/// The self-test payload, mirroring `TRANSPORT_PROBE_BYTES` in
|
|
73
|
+
/// `src/rasterizer/native-bridge.ts`. Restated there rather than derived: the
|
|
74
|
+
/// two lists are the only place the transport's byte fidelity is asserted, and
|
|
75
|
+
/// a mismatch has to make the probe FAIL and the platform fall back to base64.
|
|
76
|
+
const std::array<uint8_t, 8>& probeBytes();
|
|
77
|
+
|
|
78
|
+
/// Parks the probe bytes in a fresh slot and returns its id.
|
|
79
|
+
int64_t storeProbe();
|
|
80
|
+
|
|
81
|
+
/// Takes a slot out of the store. nullptr if it is not there. Exactly-once.
|
|
82
|
+
std::shared_ptr<SlotBuffer> take(int64_t slot);
|
|
83
|
+
|
|
84
|
+
/// Drops one slot if it is still there. Returns whether it was. Idempotent.
|
|
85
|
+
bool release(int64_t slot);
|
|
86
|
+
|
|
87
|
+
/// Drops every slot. Called from the bindings' invalidate hooks.
|
|
88
|
+
void releaseAll();
|
|
89
|
+
|
|
90
|
+
/// Slots currently waiting to be taken. For assertions and diagnostics.
|
|
91
|
+
size_t count();
|
|
92
|
+
|
|
93
|
+
} // namespace slots
|
|
94
|
+
|
|
95
|
+
/// A `PixelSink` that reserves store-owned memory and publishes it under a
|
|
96
|
+
/// fresh, process-wide, monotonic id — so a collision at publish is
|
|
97
|
+
/// unrepresentable and `publish()` cannot fail. (The retired Android store had
|
|
98
|
+
/// to REFUSE colliding ids because Java allocated them; allocating next to the
|
|
99
|
+
/// map removes that whole failure class.)
|
|
100
|
+
class SlotSink final : public PixelSink {
|
|
101
|
+
public:
|
|
102
|
+
uint8_t* reserve(size_t length) override;
|
|
103
|
+
int64_t publish() override;
|
|
104
|
+
void abandon() override;
|
|
105
|
+
|
|
106
|
+
private:
|
|
107
|
+
std::shared_ptr<SlotBuffer> buffer_;
|
|
108
|
+
int64_t slot_ = 0;
|
|
109
|
+
bool reserved_ = false;
|
|
110
|
+
bool published_ = false;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
} // namespace pdfcanvas
|