@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +57 -0
- package/PdfCanvas.podspec +68 -39
- package/README.md +213 -1316
- package/android/build.gradle +67 -54
- package/android/consumer-rules.pro +7 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
- package/android/src/main/cpp/CMakeLists.txt +54 -0
- package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
- package/android/tools/compile-gate.sh +81 -101
- package/dist/controller.d.ts +14 -0
- package/dist/controller.js +4 -3
- package/dist/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 +487 -0
- package/scripts/pdfium-manifest.json +46 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
- package/android/src/jsi/cpp/CMakeLists.txt +0 -33
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
- package/dist/rasterizer/android.d.ts +0 -121
- package/dist/rasterizer/android.js +0 -126
- package/dist/rasterizer/ios.d.ts +0 -84
- package/dist/rasterizer/ios.js +0 -155
- package/ios/Package.swift +0 -58
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The value types the core speaks, mirroring `src/types.ts`.
|
|
2
|
+
//
|
|
3
|
+
// Every rect is in PAGE-LOCAL DOC SPACE: PDF points, y-down, origin at the
|
|
4
|
+
// page's top-left, already post-rotation. That is exactly the space PDFium's
|
|
5
|
+
// display matrix emits, which is why a PDFium core is a pass-through and needs
|
|
6
|
+
// no transform of its own — the one thing the CoreGraphics core had to build by
|
|
7
|
+
// hand (see the retired `PdfCanvasPageTransform.h`) does not exist here.
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <atomic>
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
namespace pdfcanvas {
|
|
16
|
+
|
|
17
|
+
enum class Background {
|
|
18
|
+
/// Fill the raster opaque white before drawing.
|
|
19
|
+
White,
|
|
20
|
+
/// Leave it cleared to transparent black before drawing. PDFium composites
|
|
21
|
+
/// onto whatever is there and never clears, so both backgrounds are an
|
|
22
|
+
/// explicit fill and only the value differs.
|
|
23
|
+
Transparent,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/// A page's intrinsic size in PDF points, POST-ROTATION.
|
|
27
|
+
///
|
|
28
|
+
/// `FPDF_GetPageSizeByIndexF` already applies `/Rotate` (measured: a 200x100
|
|
29
|
+
/// MediaBox with `/Rotate 90` reports 100x200), and the render applies it too,
|
|
30
|
+
/// so from this package's vantage point there is no residual rotation left for
|
|
31
|
+
/// anyone to apply. `rotation` is therefore always 0 here. It exists because
|
|
32
|
+
/// `PageGeometry.rotation` is on the TS contract, where it is informational.
|
|
33
|
+
struct PageGeometry {
|
|
34
|
+
int index = 0;
|
|
35
|
+
double width = 0;
|
|
36
|
+
double height = 0;
|
|
37
|
+
int rotation = 0;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/// One raster to produce, mirroring `RasterRequest` in `src/types.ts`.
|
|
41
|
+
struct RasterRequest {
|
|
42
|
+
int page = 0;
|
|
43
|
+
double x = 0;
|
|
44
|
+
double y = 0;
|
|
45
|
+
double width = 0;
|
|
46
|
+
double height = 0;
|
|
47
|
+
/// Device pixels per PDF point.
|
|
48
|
+
double scale = 1;
|
|
49
|
+
/// Draw PDF-embedded annotations (and form fields) into the raster.
|
|
50
|
+
bool annotations = true;
|
|
51
|
+
Background background = Background::White;
|
|
52
|
+
|
|
53
|
+
/// Destination width in device pixels: ROUND, not ceil, and at least 1 —
|
|
54
|
+
/// the rule `planner.ts`, the fake backend and both retired native cores all
|
|
55
|
+
/// share. The planner declares the pixel size the cache is keyed on, so a
|
|
56
|
+
/// backend that rounded the other way would hand back a raster of a
|
|
57
|
+
/// different size than the plan promised.
|
|
58
|
+
int pixelWidth() const;
|
|
59
|
+
int pixelHeight() const;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/// Where a document's bytes come from. Exactly one of `path` / `bytes` is used;
|
|
63
|
+
/// a non-empty path wins.
|
|
64
|
+
struct Source {
|
|
65
|
+
std::string path;
|
|
66
|
+
std::vector<uint8_t> bytes;
|
|
67
|
+
/// Present means "the caller supplied one", including the empty string —
|
|
68
|
+
/// PDFium reports the same error for "no password" and "wrong password", and
|
|
69
|
+
/// only the caller knows which it was.
|
|
70
|
+
bool hasPassword = false;
|
|
71
|
+
std::string password;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/// A cancellation flag, written by whoever owns the render's token and read by
|
|
75
|
+
/// the render at five checkpoints — including MID-RENDER, through PDFium's
|
|
76
|
+
/// progressive API. That last one is what neither retired core could offer:
|
|
77
|
+
/// `CGContextDrawPDFPage` and `PdfRenderer.render` were uninterruptible once
|
|
78
|
+
/// entered, and `RasterPolicy.minEpochIntervalMs` exists because of it.
|
|
79
|
+
class Cancellation {
|
|
80
|
+
public:
|
|
81
|
+
void cancel() noexcept { cancelled_.store(true, std::memory_order_relaxed); }
|
|
82
|
+
bool isCancelled() const noexcept {
|
|
83
|
+
return cancelled_.load(std::memory_order_relaxed);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// TESTING AFFORDANCE. Arms the flag to flip on the N-th poll of `poll()`,
|
|
87
|
+
/// which is what the render's mid-render checkpoint calls. It is the only
|
|
88
|
+
/// deterministic way to land a cancel INSIDE a render rather than before or
|
|
89
|
+
/// after it, and it is a no-op unless armed.
|
|
90
|
+
void cancelAfterPolls(int polls) noexcept {
|
|
91
|
+
pollsUntilCancel_.store(polls, std::memory_order_relaxed);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// The render's view of the flag: `isCancelled()`, plus the poll counter.
|
|
95
|
+
bool poll() noexcept {
|
|
96
|
+
const int remaining = pollsUntilCancel_.load(std::memory_order_relaxed);
|
|
97
|
+
if (remaining > 0) {
|
|
98
|
+
if (pollsUntilCancel_.fetch_sub(1, std::memory_order_relaxed) == 1) {
|
|
99
|
+
cancel();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return isCancelled();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private:
|
|
106
|
+
std::atomic<bool> cancelled_{false};
|
|
107
|
+
std::atomic<int> pollsUntilCancel_{0};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# The core's source list and include directory, for every build that compiles
|
|
2
|
+
# it: the Android library (android/src/main/cpp/CMakeLists.txt), the host test
|
|
3
|
+
# suite (native/CMakeLists.txt). The CocoaPods build lists the same files via
|
|
4
|
+
# `source_files` globs in PdfCanvas.podspec, because CocoaPods does not read
|
|
5
|
+
# CMake. Keep the two in step: a file added here and not there compiles on two
|
|
6
|
+
# platforms and is undefined on the third.
|
|
7
|
+
#
|
|
8
|
+
# `include()` this, then compile `${PDFCANVAS_CORE_SOURCES}` with
|
|
9
|
+
# `${PDFCANVAS_CORE_INCLUDE_DIR}` on the include path and the PDFium headers
|
|
10
|
+
# reachable as `fpdfview.h`.
|
|
11
|
+
|
|
12
|
+
set(PDFCANVAS_CORE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
|
13
|
+
set(PDFCANVAS_CORE_INCLUDE_DIR "${PDFCANVAS_CORE_DIR}/include")
|
|
14
|
+
set(PDFCANVAS_CORE_SOURCES
|
|
15
|
+
"${PDFCANVAS_CORE_DIR}/src/document.cpp"
|
|
16
|
+
"${PDFCANVAS_CORE_DIR}/src/error.cpp"
|
|
17
|
+
"${PDFCANVAS_CORE_DIR}/src/jsi.cpp"
|
|
18
|
+
"${PDFCANVAS_CORE_DIR}/src/library.cpp"
|
|
19
|
+
"${PDFCANVAS_CORE_DIR}/src/pixels.cpp"
|
|
20
|
+
"${PDFCANVAS_CORE_DIR}/src/service.cpp"
|
|
21
|
+
"${PDFCANVAS_CORE_DIR}/src/slots.cpp"
|
|
22
|
+
)
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
#include "pdfcanvas/document.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <cstdlib>
|
|
7
|
+
#include <limits>
|
|
8
|
+
#include <mutex>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <sys/stat.h>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
#include "fpdf_formfill.h"
|
|
14
|
+
#include "fpdf_progressive.h"
|
|
15
|
+
#include "fpdfview.h"
|
|
16
|
+
#include "pdfcanvas/library.h"
|
|
17
|
+
#include "pdfcanvas/slots.h"
|
|
18
|
+
|
|
19
|
+
namespace pdfcanvas {
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
/* ------------------------------------------------------------------ *
|
|
24
|
+
* Constants
|
|
25
|
+
* ------------------------------------------------------------------ */
|
|
26
|
+
|
|
27
|
+
/// ARGB. Both values are symmetric under an R/B swap, which is what makes
|
|
28
|
+
/// filling BEFORE a reverse-byte-order render correct rather than lucky:
|
|
29
|
+
/// `FPDFBitmap_FillRect` writes in the bitmap's own BGRA order.
|
|
30
|
+
constexpr FPDF_DWORD kOpaqueWhite = 0xFFFFFFFFu;
|
|
31
|
+
constexpr FPDF_DWORD kTransparent = 0x00000000u;
|
|
32
|
+
|
|
33
|
+
/// PDFium's ints are 32-bit; a request past this cannot be expressed at all.
|
|
34
|
+
constexpr long long kMaxInt32 = (std::numeric_limits<int>::max)();
|
|
35
|
+
|
|
36
|
+
/* ------------------------------------------------------------------ *
|
|
37
|
+
* Errors
|
|
38
|
+
* ------------------------------------------------------------------ */
|
|
39
|
+
|
|
40
|
+
/// `FPDF_GetLastError()` after a failed load -> the frozen error union.
|
|
41
|
+
///
|
|
42
|
+
/// ONLY MEANINGFUL IMMEDIATELY AFTER A FAILED DOCUMENT LOAD (measured in the
|
|
43
|
+
/// web backend: a later unrelated failure leaves the stale value in place), so
|
|
44
|
+
/// this is consulted in exactly one place. `passwordSupplied` is what tells the
|
|
45
|
+
/// two password outcomes apart: PDFium returns FPDF_ERR_PASSWORD both for an
|
|
46
|
+
/// encrypted document opened with no password and for one opened with the
|
|
47
|
+
/// wrong password, and the distinction matters — one means "prompt", the other
|
|
48
|
+
/// "you got it wrong".
|
|
49
|
+
PdfError loadError(unsigned long error, bool passwordSupplied, const std::string& what) {
|
|
50
|
+
ErrorCode code;
|
|
51
|
+
const char* name;
|
|
52
|
+
switch (error) {
|
|
53
|
+
case FPDF_ERR_FILE:
|
|
54
|
+
code = ErrorCode::NotFound;
|
|
55
|
+
name = "FILE";
|
|
56
|
+
break;
|
|
57
|
+
case FPDF_ERR_FORMAT:
|
|
58
|
+
// Both a truncated document and an eight-byte non-PDF report this.
|
|
59
|
+
code = ErrorCode::Corrupt;
|
|
60
|
+
name = "FORMAT";
|
|
61
|
+
break;
|
|
62
|
+
case FPDF_ERR_PASSWORD:
|
|
63
|
+
code = passwordSupplied ? ErrorCode::PasswordIncorrect : ErrorCode::PasswordRequired;
|
|
64
|
+
name = "PASSWORD";
|
|
65
|
+
break;
|
|
66
|
+
case FPDF_ERR_SECURITY:
|
|
67
|
+
// An encryption scheme this PDFium build cannot handle. Not corruption
|
|
68
|
+
// and not a wrong password — nothing the user can do about it.
|
|
69
|
+
code = ErrorCode::Unsupported;
|
|
70
|
+
name = "SECURITY";
|
|
71
|
+
break;
|
|
72
|
+
case FPDF_ERR_PAGE:
|
|
73
|
+
code = ErrorCode::Corrupt;
|
|
74
|
+
name = "PAGE";
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
code = ErrorCode::BackendFailure;
|
|
78
|
+
name = "UNKNOWN";
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return PdfError(code, "PDFium could not open " + what + " (FPDF_GetLastError = " +
|
|
82
|
+
std::to_string(error) + " " + name +
|
|
83
|
+
(passwordSupplied ? ", a password was supplied" : "") + ").");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// What the file system can say that PDFium's single FPDF_ERR_FILE cannot: the
|
|
87
|
+
/// four failures "no such file", "a directory", "unreadable" and "empty" all
|
|
88
|
+
/// want different messages, because a host showing "corrupt" for a path that
|
|
89
|
+
/// simply does not exist sends somebody looking at the wrong thing.
|
|
90
|
+
void preflightPath(const std::string& path) {
|
|
91
|
+
if (path.empty()) {
|
|
92
|
+
throw PdfError(ErrorCode::NotFound, "No document path was given.");
|
|
93
|
+
}
|
|
94
|
+
struct stat st {};
|
|
95
|
+
if (::stat(path.c_str(), &st) != 0) {
|
|
96
|
+
throw PdfError(ErrorCode::NotFound, "No file at " + path + ".");
|
|
97
|
+
}
|
|
98
|
+
if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
|
99
|
+
throw PdfError(ErrorCode::NotFound, path + " is a directory, not a PDF.");
|
|
100
|
+
}
|
|
101
|
+
if (st.st_size == 0) {
|
|
102
|
+
throw PdfError(ErrorCode::Corrupt, path + " is empty (0 bytes).");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void checkCancelled(Cancellation* signal, const char* stage) {
|
|
107
|
+
if (signal != nullptr && signal->isCancelled()) {
|
|
108
|
+
throw PdfError(ErrorCode::Cancelled, std::string("Render cancelled (") + stage + ").");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ------------------------------------------------------------------ *
|
|
113
|
+
* Geometry
|
|
114
|
+
* ------------------------------------------------------------------ */
|
|
115
|
+
|
|
116
|
+
/// Read every page's size without loading a single page.
|
|
117
|
+
///
|
|
118
|
+
/// `FPDF_GetPageSizeByIndexF` works off the page DICTIONARY — no content
|
|
119
|
+
/// stream is parsed — so the cost is proportional to the page tree and not to
|
|
120
|
+
/// what is drawn on it. It is already post-rotation (measured: a 200x100
|
|
121
|
+
/// MediaBox with `/Rotate 90` reports 100x200; the CropBox intersected with the
|
|
122
|
+
/// MediaBox, after rotation — precisely what `PageGeometry` is defined as).
|
|
123
|
+
///
|
|
124
|
+
/// A page that cannot be measured fails the OPEN rather than being papered
|
|
125
|
+
/// over with a zero size: a `PageGeometry` is what the layout turns into the
|
|
126
|
+
/// document's whole coordinate space, so a wrong one is every page after it in
|
|
127
|
+
/// the wrong place.
|
|
128
|
+
std::vector<PageGeometry> readGeometry(FPDF_DOCUMENT document) {
|
|
129
|
+
const int pageCount = FPDF_GetPageCount(document);
|
|
130
|
+
std::vector<PageGeometry> pages;
|
|
131
|
+
pages.reserve(static_cast<size_t>(std::max(0, pageCount)));
|
|
132
|
+
for (int index = 0; index < pageCount; index++) {
|
|
133
|
+
FS_SIZEF size{};
|
|
134
|
+
if (!FPDF_GetPageSizeByIndexF(document, index, &size)) {
|
|
135
|
+
throw PdfError(ErrorCode::Corrupt, "PDFium could not read the size of page " +
|
|
136
|
+
std::to_string(index) + " of " +
|
|
137
|
+
std::to_string(pageCount) + ".");
|
|
138
|
+
}
|
|
139
|
+
PageGeometry geometry;
|
|
140
|
+
geometry.index = index;
|
|
141
|
+
geometry.width = size.width;
|
|
142
|
+
geometry.height = size.height;
|
|
143
|
+
// Always 0: PDFium pre-applies `/Rotate` to both the size above and the
|
|
144
|
+
// render, so there is no residual rotation for a caller to apply. Reading
|
|
145
|
+
// the raw value would need `FPDF_LoadPage`, which parses the content
|
|
146
|
+
// stream — the one thing `pageGeometry` is forbidden to do.
|
|
147
|
+
geometry.rotation = 0;
|
|
148
|
+
pages.push_back(geometry);
|
|
149
|
+
}
|
|
150
|
+
return pages;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ------------------------------------------------------------------ *
|
|
154
|
+
* The progressive pause
|
|
155
|
+
* ------------------------------------------------------------------ */
|
|
156
|
+
|
|
157
|
+
/// PDFium's progressive renderer calls `NeedToPauseNow` between batches of page
|
|
158
|
+
/// objects. Answering "yes" only when the render has been cancelled means a
|
|
159
|
+
/// live render never pauses at all, and a superseded one stops within one batch
|
|
160
|
+
/// — which is what makes `interruptibleRender: true` honest for the first time
|
|
161
|
+
/// on a native backend.
|
|
162
|
+
struct PauseAdapter {
|
|
163
|
+
IFSDK_PAUSE pause{};
|
|
164
|
+
Cancellation* signal = nullptr;
|
|
165
|
+
|
|
166
|
+
explicit PauseAdapter(Cancellation* s) : signal(s) {
|
|
167
|
+
pause.version = 1;
|
|
168
|
+
pause.user = this;
|
|
169
|
+
pause.NeedToPauseNow = [](IFSDK_PAUSE* self) -> FPDF_BOOL {
|
|
170
|
+
auto* adapter = static_cast<PauseAdapter*>(self->user);
|
|
171
|
+
return adapter->signal != nullptr && adapter->signal->poll() ? 1 : 0;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
} // namespace
|
|
177
|
+
|
|
178
|
+
/* ------------------------------------------------------------------ *
|
|
179
|
+
* Impl
|
|
180
|
+
* ------------------------------------------------------------------ */
|
|
181
|
+
|
|
182
|
+
struct Document::Impl {
|
|
183
|
+
FPDF_DOCUMENT document = nullptr;
|
|
184
|
+
FPDF_FORMHANDLE form = nullptr;
|
|
185
|
+
FPDF_FORMFILLINFO formInfo{};
|
|
186
|
+
/// The bytes a memory document reads from. `FPDF_LoadMemDocument64` does
|
|
187
|
+
/// not copy: PDFium reads lazily for the whole life of the document, so this
|
|
188
|
+
/// is freed after `FPDF_CloseDocument` and never before.
|
|
189
|
+
std::vector<uint8_t> bytes;
|
|
190
|
+
bool closed = false;
|
|
191
|
+
|
|
192
|
+
struct LoadedPage {
|
|
193
|
+
int index = -1;
|
|
194
|
+
FPDF_PAGE page = nullptr;
|
|
195
|
+
uint64_t lastUse = 0;
|
|
196
|
+
};
|
|
197
|
+
std::vector<LoadedPage> cache;
|
|
198
|
+
uint64_t useCounter = 0;
|
|
199
|
+
|
|
200
|
+
/// Requires the library lock.
|
|
201
|
+
FPDF_PAGE loadPage(int index) {
|
|
202
|
+
for (auto& entry : cache) {
|
|
203
|
+
if (entry.index == index) {
|
|
204
|
+
entry.lastUse = ++useCounter;
|
|
205
|
+
return entry.page;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (cache.size() >= static_cast<size_t>(kPageCacheCapacity)) {
|
|
209
|
+
auto oldest = std::min_element(
|
|
210
|
+
cache.begin(), cache.end(),
|
|
211
|
+
[](const LoadedPage& a, const LoadedPage& b) { return a.lastUse < b.lastUse; });
|
|
212
|
+
unloadPage(*oldest);
|
|
213
|
+
cache.erase(oldest);
|
|
214
|
+
}
|
|
215
|
+
// `FPDF_LoadPage` is the call that parses the content stream; an epoch is
|
|
216
|
+
// several tiles of the same page, so the cache above turns N parses into
|
|
217
|
+
// one.
|
|
218
|
+
FPDF_PAGE page = FPDF_LoadPage(document, index);
|
|
219
|
+
if (page == nullptr) {
|
|
220
|
+
// Not `FPDF_GetLastError` — it is stale here. The index was already
|
|
221
|
+
// bounds-checked against the geometry read at open, so a failure at this
|
|
222
|
+
// point is a page whose content PDFium refuses.
|
|
223
|
+
throw PdfError(ErrorCode::Corrupt,
|
|
224
|
+
"PDFium could not load page " + std::to_string(index) +
|
|
225
|
+
". The page exists (its size was read at open) but its "
|
|
226
|
+
"content could not be parsed.");
|
|
227
|
+
}
|
|
228
|
+
if (form != nullptr) {
|
|
229
|
+
// Required for FPDF_FFLDraw to know the page's widgets; balanced by
|
|
230
|
+
// FORM_OnBeforeClosePage in unloadPage.
|
|
231
|
+
FORM_OnAfterLoadPage(page, form);
|
|
232
|
+
}
|
|
233
|
+
cache.push_back({index, page, ++useCounter});
|
|
234
|
+
return page;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
void unloadPage(LoadedPage& entry) {
|
|
238
|
+
if (entry.page == nullptr) return;
|
|
239
|
+
if (form != nullptr) FORM_OnBeforeClosePage(entry.page, form);
|
|
240
|
+
FPDF_ClosePage(entry.page);
|
|
241
|
+
entry.page = nullptr;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void closeAll() {
|
|
245
|
+
if (closed) return;
|
|
246
|
+
closed = true;
|
|
247
|
+
for (auto& entry : cache) unloadPage(entry);
|
|
248
|
+
cache.clear();
|
|
249
|
+
if (form != nullptr) {
|
|
250
|
+
FPDFDOC_ExitFormFillEnvironment(form);
|
|
251
|
+
form = nullptr;
|
|
252
|
+
}
|
|
253
|
+
if (document != nullptr) {
|
|
254
|
+
FPDF_CloseDocument(document);
|
|
255
|
+
document = nullptr;
|
|
256
|
+
}
|
|
257
|
+
// AFTER the close, never before.
|
|
258
|
+
bytes.clear();
|
|
259
|
+
bytes.shrink_to_fit();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/* ------------------------------------------------------------------ *
|
|
264
|
+
* Open / close
|
|
265
|
+
* ------------------------------------------------------------------ */
|
|
266
|
+
|
|
267
|
+
Document::Document(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {}
|
|
268
|
+
|
|
269
|
+
Document::~Document() {
|
|
270
|
+
std::lock_guard<std::mutex> lock(Library::mutex());
|
|
271
|
+
impl_->closeAll();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
std::unique_ptr<Document> Document::open(const Source& source) {
|
|
275
|
+
const bool fromPath = !source.path.empty();
|
|
276
|
+
if (fromPath) {
|
|
277
|
+
preflightPath(source.path);
|
|
278
|
+
} else if (source.bytes.empty()) {
|
|
279
|
+
throw PdfError(ErrorCode::Corrupt, "The document is empty (0 bytes).");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
auto impl = std::make_unique<Impl>();
|
|
283
|
+
std::lock_guard<std::mutex> lock(Library::mutex());
|
|
284
|
+
Library::ensureInitialized();
|
|
285
|
+
|
|
286
|
+
const char* password = source.hasPassword ? source.password.c_str() : nullptr;
|
|
287
|
+
if (fromPath) {
|
|
288
|
+
impl->document = FPDF_LoadDocument(source.path.c_str(), password);
|
|
289
|
+
} else {
|
|
290
|
+
impl->bytes = source.bytes;
|
|
291
|
+
impl->document = FPDF_LoadMemDocument64(impl->bytes.data(), impl->bytes.size(), password);
|
|
292
|
+
}
|
|
293
|
+
if (impl->document == nullptr) {
|
|
294
|
+
throw loadError(FPDF_GetLastError(), source.hasPassword,
|
|
295
|
+
fromPath ? source.path : "the document bytes");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
std::vector<PageGeometry> pages;
|
|
299
|
+
try {
|
|
300
|
+
pages = readGeometry(impl->document);
|
|
301
|
+
} catch (...) {
|
|
302
|
+
impl->closeAll();
|
|
303
|
+
throw;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The form-fill environment is what draws WIDGET annotations — form fields
|
|
307
|
+
// — which `FPDF_RenderPageBitmap` skips even with `FPDF_ANNOT`. Version 1 of
|
|
308
|
+
// the info struct, with every callback null: PDFium null-checks them all,
|
|
309
|
+
// and this build has no V8, so nothing here ever needs to answer a
|
|
310
|
+
// JavaScript action. Opened eagerly because it is cheap and because a page
|
|
311
|
+
// must be registered with it at load time, before anyone knows whether an
|
|
312
|
+
// annotations render will come.
|
|
313
|
+
impl->formInfo = FPDF_FORMFILLINFO{};
|
|
314
|
+
impl->formInfo.version = 1;
|
|
315
|
+
impl->form = FPDFDOC_InitFormFillEnvironment(impl->document, &impl->formInfo);
|
|
316
|
+
if (impl->form != nullptr) {
|
|
317
|
+
// No field highlight. Chrome and Acrobat tint fillable fields light blue
|
|
318
|
+
// on screen; under an annotation canvas that wash would read as content,
|
|
319
|
+
// and it is not what a printed or flattened copy of the page looks like.
|
|
320
|
+
FPDF_SetFormFieldHighlightColor(impl->form, FPDF_FORMFIELD_UNKNOWN, 0xFFE4DD);
|
|
321
|
+
FPDF_SetFormFieldHighlightAlpha(impl->form, 0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
std::unique_ptr<Document> document(new Document(std::move(impl)));
|
|
325
|
+
document->pages_ = std::move(pages);
|
|
326
|
+
return document;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
void Document::close() {
|
|
330
|
+
std::lock_guard<std::mutex> lock(Library::mutex());
|
|
331
|
+
impl_->closeAll();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
bool Document::isClosed() const noexcept { return impl_->closed; }
|
|
335
|
+
|
|
336
|
+
const PageGeometry& Document::geometry(int index) const {
|
|
337
|
+
if (index < 0 || static_cast<size_t>(index) >= pages_.size()) {
|
|
338
|
+
throw PdfError(ErrorCode::NotFound, "Document has " + std::to_string(pages_.size()) +
|
|
339
|
+
" pages; asked for " + std::to_string(index) + ".");
|
|
340
|
+
}
|
|
341
|
+
return pages_[static_cast<size_t>(index)];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* ------------------------------------------------------------------ *
|
|
345
|
+
* Render
|
|
346
|
+
* ------------------------------------------------------------------ */
|
|
347
|
+
|
|
348
|
+
RasterPixels Document::render(const RasterRequest& request, Cancellation* signal,
|
|
349
|
+
PixelSink* sink) {
|
|
350
|
+
checkCancelled(signal, "before start");
|
|
351
|
+
if (impl_->closed) {
|
|
352
|
+
throw PdfError(ErrorCode::BackendFailure, "Document handle is closed.");
|
|
353
|
+
}
|
|
354
|
+
const PageGeometry& geometry = this->geometry(request.page);
|
|
355
|
+
|
|
356
|
+
if (!std::isfinite(request.scale) || request.scale <= 0) {
|
|
357
|
+
throw PdfError(ErrorCode::BackendFailure,
|
|
358
|
+
"Scale must be a positive finite number, got " + std::to_string(request.scale) + ".");
|
|
359
|
+
}
|
|
360
|
+
if (!std::isfinite(request.x) || !std::isfinite(request.y) || !(request.width > 0) ||
|
|
361
|
+
!(request.height > 0)) {
|
|
362
|
+
throw PdfError(ErrorCode::BackendFailure,
|
|
363
|
+
"Doc rect must be finite and have positive extent, got " +
|
|
364
|
+
std::to_string(request.width) + "x" + std::to_string(request.height) +
|
|
365
|
+
" at (" + std::to_string(request.x) + ", " + std::to_string(request.y) + ").");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const int pxW = request.pixelWidth();
|
|
369
|
+
const int pxH = request.pixelHeight();
|
|
370
|
+
const long long pixels = static_cast<long long>(pxW) * pxH;
|
|
371
|
+
// THE RECT MECHANISM — negative offsets, not a matrix. PDFium draws the WHOLE
|
|
372
|
+
// page at the target scale slid by (-x*s, -y*s) into a bitmap that is only
|
|
373
|
+
// the tile's size; everything outside is clipped by the bitmap itself.
|
|
374
|
+
// `-round(x*s)`, not `round(-x*s)`: the tile's origin inside a hypothetical
|
|
375
|
+
// full-page render is `round(x*s)`, and negating the rounded origin makes the
|
|
376
|
+
// two the same integer by construction. The web backend measured this
|
|
377
|
+
// byte-identical to the matrix form on every rect it tried.
|
|
378
|
+
const long long startX = -std::llround(request.x * request.scale);
|
|
379
|
+
const long long startY = -std::llround(request.y * request.scale);
|
|
380
|
+
const long long pageW = std::llround(geometry.width * request.scale);
|
|
381
|
+
const long long pageH = std::llround(geometry.height * request.scale);
|
|
382
|
+
if (pixels > kMaxRasterPixels) {
|
|
383
|
+
throw PdfError(ErrorCode::OutOfMemory,
|
|
384
|
+
"Refused a " + std::to_string(pxW) + "x" + std::to_string(pxH) + " raster (" +
|
|
385
|
+
std::to_string(pixels) + " pixels > " + std::to_string(kMaxRasterPixels) + ").");
|
|
386
|
+
}
|
|
387
|
+
if (pageW > kMaxInt32 || pageH > kMaxInt32 || std::llabs(startX) > kMaxInt32 ||
|
|
388
|
+
std::llabs(startY) > kMaxInt32) {
|
|
389
|
+
throw PdfError(ErrorCode::OutOfMemory,
|
|
390
|
+
"A " + std::to_string(pxW) + "x" + std::to_string(pxH) + " raster of a " +
|
|
391
|
+
std::to_string(pageW) + "x" + std::to_string(pageH) +
|
|
392
|
+
" page cannot be expressed in PDFium's 32-bit ints. Lower "
|
|
393
|
+
"`RasterPolicy.maxPieceDimension` or `maxRasterPixels`.");
|
|
394
|
+
}
|
|
395
|
+
const int rowBytes = pxW * 4;
|
|
396
|
+
const size_t byteCount = static_cast<size_t>(rowBytes) * static_cast<size_t>(pxH);
|
|
397
|
+
|
|
398
|
+
std::lock_guard<std::mutex> lock(Library::mutex());
|
|
399
|
+
// The wait above is where a cancel most often lands — a superseded detail
|
|
400
|
+
// raster typically queues behind a base raster — so the signal is re-read on
|
|
401
|
+
// the far side of it.
|
|
402
|
+
checkCancelled(signal, "after lock");
|
|
403
|
+
if (impl_->closed) {
|
|
404
|
+
throw PdfError(ErrorCode::BackendFailure, "Document handle is closed.");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
FPDF_PAGE page = impl_->loadPage(request.page);
|
|
408
|
+
|
|
409
|
+
// RESERVE BEFORE DRAWING, and fall back before drawing too. A sink refusal
|
|
410
|
+
// here costs nothing, which is the point of deciding it before the raster
|
|
411
|
+
// exists.
|
|
412
|
+
HeapSink heap;
|
|
413
|
+
PixelSink* destination = sink;
|
|
414
|
+
uint8_t* buffer = destination != nullptr ? destination->reserve(byteCount) : nullptr;
|
|
415
|
+
if (buffer == nullptr) {
|
|
416
|
+
destination = &heap;
|
|
417
|
+
buffer = heap.reserve(byteCount);
|
|
418
|
+
}
|
|
419
|
+
if (buffer == nullptr) {
|
|
420
|
+
throw PdfError(ErrorCode::OutOfMemory, "Could not allocate " + std::to_string(byteCount) +
|
|
421
|
+
" bytes for a " + std::to_string(pxW) + "x" +
|
|
422
|
+
std::to_string(pxH) + " raster.");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(pxW, pxH, FPDFBitmap_BGRA, buffer, rowBytes);
|
|
426
|
+
if (bitmap == nullptr) {
|
|
427
|
+
destination->abandon();
|
|
428
|
+
throw PdfError(ErrorCode::BackendFailure, "PDFium refused a " + std::to_string(pxW) + "x" +
|
|
429
|
+
std::to_string(pxH) + " bitmap over the sink's buffer.");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// THE FILL IS NOT OPTIONAL. PDFium composites onto the destination and never
|
|
433
|
+
// clears it; a zeroed RGBA buffer is transparent BLACK, which under the app's
|
|
434
|
+
// annotation layers reads as a black page rather than a blank one, and ingest
|
|
435
|
+
// cannot tell "the PDF painted black here" from "nothing painted here".
|
|
436
|
+
if (!FPDFBitmap_FillRect(bitmap, 0, 0, pxW, pxH,
|
|
437
|
+
request.background == Background::White ? kOpaqueWhite : kTransparent)) {
|
|
438
|
+
FPDFBitmap_Destroy(bitmap);
|
|
439
|
+
destination->abandon();
|
|
440
|
+
throw PdfError(ErrorCode::BackendFailure,
|
|
441
|
+
"PDFium refused to clear the destination before rendering page " +
|
|
442
|
+
std::to_string(request.page) + ".");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// RGBA out (Skia's universally-accepted colour type) instead of PDFium's
|
|
446
|
+
// native BGRA; annotations only when asked. NO `FPDF_LCD_TEXT`: subpixel text
|
|
447
|
+
// is calibrated to a specific pixel grid, and every tile here is scaled and
|
|
448
|
+
// composited by Skia afterwards.
|
|
449
|
+
const int flags = FPDF_REVERSE_BYTE_ORDER | (request.annotations ? FPDF_ANNOT : 0);
|
|
450
|
+
const int sx = static_cast<int>(startX);
|
|
451
|
+
const int sy = static_cast<int>(startY);
|
|
452
|
+
const int pw = static_cast<int>(pageW);
|
|
453
|
+
const int ph = static_cast<int>(pageH);
|
|
454
|
+
|
|
455
|
+
auto abandonWith = [&](PdfError error) -> PdfError {
|
|
456
|
+
FPDFBitmap_Destroy(bitmap);
|
|
457
|
+
destination->abandon();
|
|
458
|
+
return error;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
if (signal != nullptr && signal->isCancelled()) {
|
|
462
|
+
throw abandonWith(PdfError(ErrorCode::Cancelled, "Render cancelled (before render)."));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// THE PROGRESSIVE API, so a cancel lands mid-draw. `Start` returns
|
|
466
|
+
// TOBECONTINUED when the pause adapter asked to stop, which it does only
|
|
467
|
+
// once the signal is set; a live render runs to DONE in one call.
|
|
468
|
+
PauseAdapter pause(signal);
|
|
469
|
+
int status = FPDF_RenderPageBitmap_Start(bitmap, page, sx, sy, pw, ph, /*rotate=*/0, flags,
|
|
470
|
+
&pause.pause);
|
|
471
|
+
while (status == FPDF_RENDER_TOBECONTINUED) {
|
|
472
|
+
if (signal != nullptr && signal->isCancelled()) {
|
|
473
|
+
FPDF_RenderPage_Close(page);
|
|
474
|
+
throw abandonWith(PdfError(ErrorCode::Cancelled, "Render cancelled (mid-render)."));
|
|
475
|
+
}
|
|
476
|
+
status = FPDF_RenderPage_Continue(page, &pause.pause);
|
|
477
|
+
}
|
|
478
|
+
FPDF_RenderPage_Close(page);
|
|
479
|
+
if (status == FPDF_RENDER_FAILED) {
|
|
480
|
+
throw abandonWith(PdfError(ErrorCode::BackendFailure,
|
|
481
|
+
"PDFium failed to render page " + std::to_string(request.page) + "."));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Form fields are WIDGET annotations, and `FPDF_RenderPageBitmap` skips
|
|
485
|
+
// widgets even with FPDF_ANNOT (they belong to the form-fill environment).
|
|
486
|
+
// This is the pass that draws them — the filled-in value, the checkbox
|
|
487
|
+
// state — with the same offsets, so they land exactly where the page put
|
|
488
|
+
// them.
|
|
489
|
+
if (request.annotations && impl_->form != nullptr) {
|
|
490
|
+
FPDF_FFLDraw(impl_->form, bitmap, page, sx, sy, pw, ph, /*rotate=*/0, flags);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
FPDFBitmap_Destroy(bitmap);
|
|
494
|
+
|
|
495
|
+
// Post-check. This saves no drawing — the pixels are already finished — it
|
|
496
|
+
// only stops a superseded raster being handed back and ingested into Skia,
|
|
497
|
+
// which is the expensive half on the JS side. THE ABANDON IS NOT OPTIONAL: a
|
|
498
|
+
// slot-backed raster is about to be published to a process-wide map where
|
|
499
|
+
// nothing else will ever look for it.
|
|
500
|
+
if (signal != nullptr && signal->isCancelled()) {
|
|
501
|
+
destination->abandon();
|
|
502
|
+
throw PdfError(ErrorCode::Cancelled, "Render cancelled (after render).");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
RasterPixels out;
|
|
506
|
+
out.width = pxW;
|
|
507
|
+
out.height = pxH;
|
|
508
|
+
out.rowBytes = rowBytes;
|
|
509
|
+
out.slot = destination->publish();
|
|
510
|
+
if (destination == &heap) {
|
|
511
|
+
out.bytes = heap.takeBytes();
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
} // namespace pdfcanvas
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#include "pdfcanvas/error.h"
|
|
2
|
+
|
|
3
|
+
namespace pdfcanvas {
|
|
4
|
+
|
|
5
|
+
const char* wireName(ErrorCode code) {
|
|
6
|
+
switch (code) {
|
|
7
|
+
case ErrorCode::NotFound:
|
|
8
|
+
return "not-found";
|
|
9
|
+
case ErrorCode::PasswordRequired:
|
|
10
|
+
return "password-required";
|
|
11
|
+
case ErrorCode::PasswordIncorrect:
|
|
12
|
+
return "password-incorrect";
|
|
13
|
+
case ErrorCode::Corrupt:
|
|
14
|
+
return "corrupt";
|
|
15
|
+
case ErrorCode::Unsupported:
|
|
16
|
+
return "unsupported";
|
|
17
|
+
case ErrorCode::Cancelled:
|
|
18
|
+
return "cancelled";
|
|
19
|
+
case ErrorCode::OutOfMemory:
|
|
20
|
+
return "out-of-memory";
|
|
21
|
+
case ErrorCode::BackendFailure:
|
|
22
|
+
return "backend-failure";
|
|
23
|
+
}
|
|
24
|
+
// No `default:` above, so a new member is a compiler warning rather than a
|
|
25
|
+
// silent fall-through to this line.
|
|
26
|
+
return "backend-failure";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
} // namespace pdfcanvas
|