@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
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#import <Foundation/Foundation.h>
|
|
2
|
-
|
|
3
|
-
#import "PdfCanvasPixelSink.h"
|
|
4
|
-
|
|
5
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The only format this backend emits: 8 bits per channel, R G B A in memory.
|
|
9
|
-
*
|
|
10
|
-
* It is `kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big`, which is
|
|
11
|
-
* what Skia calls `kRGBA_8888` and what `PixelFormat` in `src/types.ts` calls
|
|
12
|
-
* `'rgba8888'`. Matching Android's byte order rather than reaching for the
|
|
13
|
-
* Apple-native BGRA is deliberate: cost is flat in scale on this engine
|
|
14
|
-
* (measured: a 512² tile costs 0.49 / 0.39 / 0.43 / 0.39 ms at scale 1 / 2 / 4 /
|
|
15
|
-
* 8), so rasterizing dominates and the byte order is not where the time goes —
|
|
16
|
-
* whereas one byte order across both native backends is one fewer thing for a
|
|
17
|
-
* cross-platform pixel test to have to account for.
|
|
18
|
-
*/
|
|
19
|
-
FOUNDATION_EXPORT NSString *const PdfCanvasFormatRGBA8888;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A finished raster, mirroring `RasterPixels` in `src/types.ts`.
|
|
23
|
-
*
|
|
24
|
-
* `rowBytes` is STATED, never assumed to be `width * 4` by whoever reads it — a
|
|
25
|
-
* wrong stride does not fail, it SHEARS the image, one pixel further per row,
|
|
26
|
-
* which reads as a plausible skew rather than an error. On this backend it is
|
|
27
|
-
* tight by construction, because this library chooses the stride it hands
|
|
28
|
-
* `CGBitmapContextCreate`; the value still travels because
|
|
29
|
-
* `imageFromPixels` needs it and because "tight today" is a fact about today.
|
|
30
|
-
*
|
|
31
|
-
* MEASURED, and it constrains what a future alignment change may do:
|
|
32
|
-
* `CGBitmapContextCreate` REFUSES a `bytesPerRow` that is not a multiple of 4 at
|
|
33
|
-
* 32 bits per pixel (`width * 4 + 1`, `+ 2`, `+ 3` and `+ 15` all return NULL;
|
|
34
|
-
* `+ 0`, `+ 4`, `+ 8`, `+ 12`, `+ 16`, `+ 64` are accepted). So row padding is
|
|
35
|
-
* expressible if it ever pays for itself, but only in whole pixels.
|
|
36
|
-
*
|
|
37
|
-
* <h3>Where the pixels actually are</h3>
|
|
38
|
-
*
|
|
39
|
-
* ONE OF TWO PLACES, and `bytes` being nil is how you tell:
|
|
40
|
-
*
|
|
41
|
-
* - ON THE HEAP — `bytes` is the raster and `slot` is 0. Every caller that reads
|
|
42
|
-
* pixels wants this, and it is what a nil sink produces.
|
|
43
|
-
* - IN A NATIVE SLOT — `bytes` is nil and `slot` names a buffer in
|
|
44
|
-
* `PdfCanvasSlots.mm` waiting to become a JS `ArrayBuffer`. Nothing can read
|
|
45
|
-
* the pixels back from Objective-C; the whole point is that they were written
|
|
46
|
-
* once, by CoreGraphics, and are never copied again.
|
|
47
|
-
*
|
|
48
|
-
* That is a deliberately awkward shape rather than two classes, because the one
|
|
49
|
-
* caller that asks for a slot has to handle both anyway: a sink is allowed to
|
|
50
|
-
* refuse its reservation, and the heap is the fallback. `-channelAtX:y:channel:`
|
|
51
|
-
* therefore fails LOUDLY and by name on a slot-backed raster instead of throwing
|
|
52
|
-
* a bare exception from somewhere further in.
|
|
53
|
-
*
|
|
54
|
-
* <h3>Who frees the slot</h3>
|
|
55
|
-
*
|
|
56
|
-
* Normally nobody: the buffer is taken exactly once by `__pdfCanvasTakePixels`
|
|
57
|
-
* and freed when the JS `ArrayBuffer` is collected. `-releaseSlot` exists for the
|
|
58
|
-
* paths where the raster is ABANDONED after it was drawn — a cancellation
|
|
59
|
-
* observed after the draw, or a throw between the render finishing and the
|
|
60
|
-
* promise resolving. Without it those megabytes sit in the slot map until the
|
|
61
|
-
* next teardown, which on a dev build is the next Fast Refresh and in a release
|
|
62
|
-
* build is never.
|
|
63
|
-
*
|
|
64
|
-
* NOT NAMED `-release`. ARC forbids declaring that selector, and a category
|
|
65
|
-
* hiding a retain-count method behind a domain meaning would be a trap even if it
|
|
66
|
-
* were allowed.
|
|
67
|
-
*/
|
|
68
|
-
@interface PdfCanvasRasterPixels : NSObject
|
|
69
|
-
|
|
70
|
-
/** The raster, or nil when it is in a native slot. See the class note. */
|
|
71
|
-
@property(nonatomic, readonly, nullable) NSData *bytes;
|
|
72
|
-
@property(nonatomic, readonly) NSInteger width;
|
|
73
|
-
@property(nonatomic, readonly) NSInteger height;
|
|
74
|
-
@property(nonatomic, readonly) NSInteger rowBytes;
|
|
75
|
-
@property(nonatomic, readonly, copy) NSString *format;
|
|
76
|
-
/** 0, or the native slot holding the raster. */
|
|
77
|
-
@property(nonatomic, readonly) int64_t slot;
|
|
78
|
-
|
|
79
|
-
- (instancetype)initWithSink:(id<PdfCanvasPixelSink>)sink
|
|
80
|
-
slot:(int64_t)slot
|
|
81
|
-
bytes:(nullable NSData *)bytes
|
|
82
|
-
width:(NSInteger)width
|
|
83
|
-
height:(NSInteger)height
|
|
84
|
-
rowBytes:(NSInteger)rowBytes
|
|
85
|
-
format:(NSString *)format NS_DESIGNATED_INITIALIZER;
|
|
86
|
-
- (instancetype)init NS_UNAVAILABLE;
|
|
87
|
-
|
|
88
|
-
/** Whether the pixels are in native memory and unreadable from here. */
|
|
89
|
-
@property(nonatomic, readonly) BOOL isInNativeSlot;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Frees the native slot if this raster is in one. Idempotent, and a no-op for a
|
|
93
|
-
* heap-backed raster.
|
|
94
|
-
*
|
|
95
|
-
* Call this on any path that DROPS the raster after it was produced. A taken slot
|
|
96
|
-
* is already gone from the map, so releasing after a successful take is harmless.
|
|
97
|
-
*/
|
|
98
|
-
- (void)releaseSlot;
|
|
99
|
-
|
|
100
|
-
/** Byte offset of pixel (x, y), honouring the real stride. */
|
|
101
|
-
- (NSInteger)offsetOfX:(NSInteger)x y:(NSInteger)y NS_SWIFT_NAME(offset(x:y:));
|
|
102
|
-
|
|
103
|
-
/** One channel of one pixel, as an unsigned 0..255 value. */
|
|
104
|
-
- (NSInteger)channelAtX:(NSInteger)x
|
|
105
|
-
y:(NSInteger)y
|
|
106
|
-
channel:(NSInteger)channel
|
|
107
|
-
NS_SWIFT_NAME(channelAt(x:y:channel:));
|
|
108
|
-
|
|
109
|
-
@end
|
|
110
|
-
|
|
111
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#import <CoreGraphics/CoreGraphics.h>
|
|
2
|
-
#import <Foundation/Foundation.h>
|
|
3
|
-
|
|
4
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
-
|
|
6
|
-
typedef NS_ENUM(NSInteger, PdfCanvasBackground) {
|
|
7
|
-
/** Fill the buffer opaque white before drawing. */
|
|
8
|
-
PdfCanvasBackgroundWhite = 0,
|
|
9
|
-
/**
|
|
10
|
-
* Leave the buffer cleared to transparent before drawing.
|
|
11
|
-
*
|
|
12
|
-
* This means the CALLER accepts undrawn area coming back transparent. It is not
|
|
13
|
-
* a claim that CoreGraphics clears anything: a `CGBitmapContext` draws into
|
|
14
|
-
* whatever memory it was handed, so both backgrounds are an explicit fill and
|
|
15
|
-
* only the value differs. The transparent case happens to be free, because both
|
|
16
|
-
* destinations this library allocates arrive zeroed.
|
|
17
|
-
*/
|
|
18
|
-
PdfCanvasBackgroundTransparent = 1,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* One raster to produce, mirroring `RasterRequest` in `src/types.ts` and
|
|
23
|
-
* `RasterRequest.java` on Android.
|
|
24
|
-
*
|
|
25
|
-
* The doc rect is in PAGE-LOCAL points — y-down, origin at the page's top-left,
|
|
26
|
-
* already post-rotation (see `PdfCanvasPageGeometry`). It is NOT in whatever
|
|
27
|
-
* document-wide space the JS layout function laid the pages out in; the TS
|
|
28
|
-
* adapter subtracts the page origin before it gets here. A backend that had to
|
|
29
|
-
* know the page layout would be a backend the layout function could break.
|
|
30
|
-
*
|
|
31
|
-
* `double` rather than Android's `float`, because the values arrive from JS as
|
|
32
|
-
* doubles and narrowing them buys nothing. It matters in one place: at a large
|
|
33
|
-
* doc offset and a high scale, `docX * scale` in float has a quantum well above a
|
|
34
|
-
* pixel, and that offset is exactly what the detail layer is made of.
|
|
35
|
-
*/
|
|
36
|
-
@interface PdfCanvasRasterRequest : NSObject
|
|
37
|
-
|
|
38
|
-
@property(nonatomic, readonly) NSInteger page;
|
|
39
|
-
@property(nonatomic, readonly) double docX;
|
|
40
|
-
@property(nonatomic, readonly) double docY;
|
|
41
|
-
@property(nonatomic, readonly) double docWidth;
|
|
42
|
-
@property(nonatomic, readonly) double docHeight;
|
|
43
|
-
/** Device pixels per PDF point. */
|
|
44
|
-
@property(nonatomic, readonly) double scale;
|
|
45
|
-
/**
|
|
46
|
-
* Whether PDF-embedded annotations are drawn.
|
|
47
|
-
*
|
|
48
|
-
* HONOURED, unlike on Android. YES routes the render through PDFKit
|
|
49
|
-
* (`-[PDFPage drawWithBox:toContext:]`, which composites annotation appearance
|
|
50
|
-
* streams); NO routes it through `CGContextDrawPDFPage`, which draws the content
|
|
51
|
-
* stream and nothing else. That makes iOS the only backend where the tile path
|
|
52
|
-
* — which passes NO for every tile — genuinely does not double-draw an
|
|
53
|
-
* annotation the app is also rendering on its Skia layer.
|
|
54
|
-
*/
|
|
55
|
-
@property(nonatomic, readonly) BOOL annotations;
|
|
56
|
-
@property(nonatomic, readonly) PdfCanvasBackground background;
|
|
57
|
-
|
|
58
|
-
- (instancetype)initWithPage:(NSInteger)page
|
|
59
|
-
docX:(double)docX
|
|
60
|
-
docY:(double)docY
|
|
61
|
-
docWidth:(double)docWidth
|
|
62
|
-
docHeight:(double)docHeight
|
|
63
|
-
scale:(double)scale
|
|
64
|
-
annotations:(BOOL)annotations
|
|
65
|
-
background:(PdfCanvasBackground)background
|
|
66
|
-
NS_DESIGNATED_INITIALIZER;
|
|
67
|
-
- (instancetype)init NS_UNAVAILABLE;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Destination width in device pixels.
|
|
71
|
-
*
|
|
72
|
-
* ROUND, not ceil, and at least 1 — the same rule `planner.ts` applies
|
|
73
|
-
* (`Math.max(1, Math.round(pageRect.width * scale))`), the deterministic fake
|
|
74
|
-
* backend documents, and `RasterRequest.java` implements. The three have to agree
|
|
75
|
-
* exactly: the planner declares the pixel size in the `RasterPlanItem` the cache
|
|
76
|
-
* is keyed on, and a backend that rounded the other way would return a raster of
|
|
77
|
-
* a different size than the plan promised.
|
|
78
|
-
*/
|
|
79
|
-
@property(nonatomic, readonly) NSInteger pixelWidth;
|
|
80
|
-
/** Destination height in device pixels. See `pixelWidth`. */
|
|
81
|
-
@property(nonatomic, readonly) NSInteger pixelHeight;
|
|
82
|
-
|
|
83
|
-
@end
|
|
84
|
-
|
|
85
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
#import <Foundation/Foundation.h>
|
|
2
|
-
|
|
3
|
-
#import "PdfCanvasCancellation.h"
|
|
4
|
-
#import "PdfCanvasErrorCode.h"
|
|
5
|
-
#import "PdfCanvasPageGeometry.h"
|
|
6
|
-
#import "PdfCanvasPixelSink.h"
|
|
7
|
-
#import "PdfCanvasRasterPixels.h"
|
|
8
|
-
#import "PdfCanvasRasterRequest.h"
|
|
9
|
-
|
|
10
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The iOS page rasterizer. CoreGraphics and PDFKit, and NOTHING from React
|
|
14
|
-
* Native, JSI or Nitro — so it builds and is proven by `swift test` from
|
|
15
|
-
* `ios/Package.swift` on a Mac, with no device, no simulator and no app around
|
|
16
|
-
* it. That is the same split `android/src/main` draws against
|
|
17
|
-
* `android/src/reactnative`, and it exists for the same reason: a bug in the
|
|
18
|
-
* binding must never be able to hide as a bug in rasterizing.
|
|
19
|
-
*
|
|
20
|
-
* Shaped to serve `RasterizerHandle` in `src/types.ts`: `pageCount`,
|
|
21
|
-
* `pageGeometry(index)`, `render(request, signal)`, `close()`.
|
|
22
|
-
* `ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm` is a marshalling layer over
|
|
23
|
-
* this and adds no behaviour.
|
|
24
|
-
*
|
|
25
|
-
* ------------------------------------------------------------------------
|
|
26
|
-
* HOW A TILE IS DRAWN
|
|
27
|
-
* ------------------------------------------------------------------------
|
|
28
|
-
*
|
|
29
|
-
* `PdfCanvasDocToRasterTransform` composed with `PdfCanvasPdfToDocTransform`, into
|
|
30
|
-
* a `CGBitmapContext` of exactly `pixelWidth` x `pixelHeight`. MEASURED: a tile
|
|
31
|
-
* rendered that way is pixel-identical to the same region cropped out of a
|
|
32
|
-
* full-page render — 0 of 41,984 pixels differing, max channel delta 0, for a
|
|
33
|
-
* 64x41pt tile at doc (37, 23) at scale 4 against an 800x400 whole-page raster.
|
|
34
|
-
* That is the same property spike S1 measured for Android's `PdfRenderer` and S3
|
|
35
|
-
* for PDFium on web, so the off-page-origin transform the whole detail layer
|
|
36
|
-
* depends on is now exact on all three engines.
|
|
37
|
-
*
|
|
38
|
-
* ALSO MEASURED: cost is FLAT IN SCALE. A 512² tile of a dense 540-line ARCH-D
|
|
39
|
-
* page costs 0.49 / 0.39 / 0.43 / 0.39 ms at scale 1 / 2 / 4 / 8 (best of five, on
|
|
40
|
-
* an Apple-silicon Mac). CoreGraphics clips rather than rasterizing the page and
|
|
41
|
-
* discarding most of it — which is what makes a viewport-sized detail raster
|
|
42
|
-
* constant in zoom, the premise the entire render model rests on, and it now holds
|
|
43
|
-
* on every engine this package has.
|
|
44
|
-
*
|
|
45
|
-
* ------------------------------------------------------------------------
|
|
46
|
-
* TWO ENGINES, SELECTED BY `annotations`
|
|
47
|
-
* ------------------------------------------------------------------------
|
|
48
|
-
*
|
|
49
|
-
* `annotations: NO` — every tile — draws with `CGContextDrawPDFPage`, which
|
|
50
|
-
* composites the page's content stream and not its annotation appearance streams.
|
|
51
|
-
* `annotations: YES` draws with `-[PDFPage drawWithBox:toContext:]`, which
|
|
52
|
-
* composites both. On a page with no annotations the two agree byte-for-byte
|
|
53
|
-
* (0 of 1,280,000 bytes differing, maxDelta 0, at scale 4), which is what makes
|
|
54
|
-
* choosing per render safe: the geometry does not move, only what is composited.
|
|
55
|
-
*
|
|
56
|
-
* The two need DIFFERENT transforms, because PDFKit applies `/Rotate` and the
|
|
57
|
-
* crop-box origin itself and CoreGraphics applies neither. See
|
|
58
|
-
* `PdfCanvasPageTransform.h`, which has the measurements and the trap.
|
|
59
|
-
*/
|
|
60
|
-
@interface PdfCanvasRasterizer : NSObject
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Refuse absurd rasters instead of dying on an allocation failure.
|
|
64
|
-
*
|
|
65
|
-
* 64 Mpx is 256 MB at 4 bytes per pixel — already past what a device will hand
|
|
66
|
-
* out in one allocation, so anything above it is a planner bug, and a named error
|
|
67
|
-
* against a raster key is a far better diagnostic than a jetsam kill with no key
|
|
68
|
-
* attached. The TS policy has its own, much lower, ceilings (`maxRasterPixels`);
|
|
69
|
-
* this is the backstop for a request that reaches here anyway. Same number as
|
|
70
|
-
* `PdfCanvasRasterizer.MAX_RASTER_PIXELS` on Android.
|
|
71
|
-
*/
|
|
72
|
-
@property(class, nonatomic, readonly) NSInteger maxRasterPixels;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @param password nil unless the document needs one. Unlike Android — which
|
|
76
|
-
* rejects any password with `unsupported`, because `LoadParams` landed at API
|
|
77
|
-
* 35 — encrypted documents genuinely work here.
|
|
78
|
-
* @param poolSize concurrent renders, clamped to [1, `kPdfCanvasMaxPoolSize`].
|
|
79
|
-
*/
|
|
80
|
-
+ (nullable instancetype)openFileURL:(NSURL *)url
|
|
81
|
-
password:(nullable NSString *)password
|
|
82
|
-
poolSize:(NSInteger)poolSize
|
|
83
|
-
error:(NSError *_Nullable *_Nullable)error
|
|
84
|
-
NS_SWIFT_NAME(open(fileURL:password:poolSize:));
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Opens from bytes, with NO spool file.
|
|
88
|
-
*
|
|
89
|
-
* Android's equivalent must write a temp file first — `PdfRenderer` needs a
|
|
90
|
-
* seekable descriptor and reads through a seek-and-read callback — and deletes it
|
|
91
|
-
* in `close()`. `CGPDFDocumentCreateWithProvider` needs no file at all.
|
|
92
|
-
*
|
|
93
|
-
* Reachable only from a caller that already has the bytes in memory. The JS
|
|
94
|
-
* binding deliberately does NOT expose it: getting bytes across the bridge means
|
|
95
|
-
* base64-encoding the whole document, which the spec's measured rules forbid.
|
|
96
|
-
*/
|
|
97
|
-
+ (nullable instancetype)openData:(NSData *)data
|
|
98
|
-
password:(nullable NSString *)password
|
|
99
|
-
poolSize:(NSInteger)poolSize
|
|
100
|
-
error:(NSError *_Nullable *_Nullable)error
|
|
101
|
-
NS_SWIFT_NAME(open(data:password:poolSize:));
|
|
102
|
-
|
|
103
|
-
@property(nonatomic, readonly) NSInteger pageCount;
|
|
104
|
-
/** How many renders can genuinely be in flight at once. */
|
|
105
|
-
@property(nonatomic, readonly) NSInteger poolSize;
|
|
106
|
-
@property(nonatomic, readonly, getter=isClosed) BOOL closed;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* An array read.
|
|
110
|
-
*
|
|
111
|
-
* Every page's geometry is probed ONCE, at open. The `RasterizerHandle` contract
|
|
112
|
-
* says `pageGeometry` is cheap and "must not load a page", and reading a page's
|
|
113
|
-
* crop box and rotation means asking CoreGraphics for a `CGPDFPageRef` — so the
|
|
114
|
-
* asking happens at open and this becomes a lookup. Note that a `CGPDFPageRef` is
|
|
115
|
-
* built from the page DICTIONARY and parses no content stream, so the open cost is
|
|
116
|
-
* proportional to the page tree rather than to what is drawn.
|
|
117
|
-
*/
|
|
118
|
-
- (nullable PdfCanvasPageGeometry *)pageGeometryAtIndex:(NSInteger)index
|
|
119
|
-
error:
|
|
120
|
-
(NSError *_Nullable
|
|
121
|
-
*_Nullable)error
|
|
122
|
-
NS_SWIFT_NAME(pageGeometry(at:));
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Renders one raster.
|
|
126
|
-
*
|
|
127
|
-
* @param sink where the bytes go. Nil means the heap, which is what every
|
|
128
|
-
* pixel-reading caller wants and what the whole Swift suite uses. A non-nil
|
|
129
|
-
* sink is the zero-copy transport: CoreGraphics writes straight into the
|
|
130
|
-
* buffer that becomes a JS `ArrayBuffer`. A sink is allowed to REFUSE its
|
|
131
|
-
* reservation, in which case this falls through to the heap and the result is
|
|
132
|
-
* heap-backed — so check `isInNativeSlot` on the way out rather than assuming.
|
|
133
|
-
*
|
|
134
|
-
* @return nil on failure, with `error` carrying a `PdfCanvasErrorCode`. A
|
|
135
|
-
* cancellation observed AFTER the draw finished releases the sink itself
|
|
136
|
-
* before returning; every abandon path after this method returns belongs to
|
|
137
|
-
* the caller.
|
|
138
|
-
*/
|
|
139
|
-
- (nullable PdfCanvasRasterPixels *)render:(PdfCanvasRasterRequest *)request
|
|
140
|
-
cancellation:
|
|
141
|
-
(nullable PdfCanvasCancellation *)signal
|
|
142
|
-
sink:(nullable id<PdfCanvasPixelSink>)sink
|
|
143
|
-
error:(NSError *_Nullable *_Nullable)error;
|
|
144
|
-
|
|
145
|
-
- (void)close;
|
|
146
|
-
|
|
147
|
-
- (instancetype)init NS_UNAVAILABLE;
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Whether this platform draws a page carrying `/Rotate` upright.
|
|
151
|
-
*
|
|
152
|
-
* Always `@"supported"`, and it is a constant rather than a probe because there is
|
|
153
|
-
* no device variable: `PdfCanvasPdfToDocTransform` applies the rotation and
|
|
154
|
-
* PDFKit applies its own, on every OS version this library builds for. It exists
|
|
155
|
-
* only because it is part of the shared wire format — Android has to probe it,
|
|
156
|
-
* since API 24 reports rotated DIMENSIONS while drawing the content unrotated and
|
|
157
|
-
* clipped, and a host should not have to know which platform it is on to ask.
|
|
158
|
-
*/
|
|
159
|
-
@property(class, nonatomic, readonly) NSString *rotationSupport;
|
|
160
|
-
|
|
161
|
-
@end
|
|
162
|
-
|
|
163
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
#import "PdfCanvasSlots.h"
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* TWO BUILDS, ONE FILE — the same split `android/src/jsi/cpp/pdfcanvas-jsi.cpp`
|
|
5
|
-
* draws, and for the same reason.
|
|
6
|
-
*
|
|
7
|
-
* jsi.h present — the podspec's build. The host function is installed onto the JS
|
|
8
|
-
* global and a taken slot becomes a `jsi::ArrayBuffer` over the
|
|
9
|
-
* very bytes CoreGraphics wrote.
|
|
10
|
-
* jsi.h absent — the SwiftPM build (`ios/Package.swift`), which has no React
|
|
11
|
-
* Native and no `jsi.h` to include. `PdfCanvasSlotsInstall`
|
|
12
|
-
* reports NO and the slot storage compiles on its own.
|
|
13
|
-
*
|
|
14
|
-
* That second configuration is not a stub for its own sake: it is what lets
|
|
15
|
-
* `SlotsTests.swift` prove ON A LAPTOP that the bytes which become the ArrayBuffer
|
|
16
|
-
* are the bytes CoreGraphics wrote, that a slot is taken exactly once, and that every
|
|
17
|
-
* abandon path frees. Those are the failure modes that are a leak or a
|
|
18
|
-
* use-after-free rather than a wrong pixel, and a device session does not show them.
|
|
19
|
-
* The storage under test is the production storage — the only thing the two builds do
|
|
20
|
-
* not share is the JSI call itself.
|
|
21
|
-
*/
|
|
22
|
-
#if __has_include(<jsi/jsi.h>)
|
|
23
|
-
#define PDFCANVAS_WITH_JSI 1
|
|
24
|
-
#import <jsi/jsi.h>
|
|
25
|
-
#endif
|
|
26
|
-
|
|
27
|
-
#import <atomic>
|
|
28
|
-
#import <cstdint>
|
|
29
|
-
#import <cstring>
|
|
30
|
-
#import <memory>
|
|
31
|
-
#import <mutex>
|
|
32
|
-
#import <string>
|
|
33
|
-
#import <unordered_map>
|
|
34
|
-
#import <vector>
|
|
35
|
-
|
|
36
|
-
namespace {
|
|
37
|
-
|
|
38
|
-
#ifdef PDFCANVAS_WITH_JSI
|
|
39
|
-
using SlotBufferBase = facebook::jsi::MutableBuffer;
|
|
40
|
-
#else
|
|
41
|
-
/**
|
|
42
|
-
* The two methods `jsi::MutableBuffer` declares, restated so the SwiftPM build
|
|
43
|
-
* compiles the identical storage code with no JSI header in sight.
|
|
44
|
-
*
|
|
45
|
-
* If these ever drift from `jsi.h` the podspec build stops compiling, which is the
|
|
46
|
-
* failure mode worth having — a silent divergence would mean the tested storage and
|
|
47
|
-
* the shipped storage were different types.
|
|
48
|
-
*/
|
|
49
|
-
struct SlotBufferBase {
|
|
50
|
-
virtual ~SlotBufferBase() = default;
|
|
51
|
-
virtual size_t size() const = 0;
|
|
52
|
-
virtual uint8_t *data() = 0;
|
|
53
|
-
};
|
|
54
|
-
#endif
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* One raster's bytes, owned outright, and shaped so Hermes can wrap it.
|
|
58
|
-
*
|
|
59
|
-
* `std::vector<uint8_t> bytes_(size)` VALUE-initialises, so constructing this
|
|
60
|
-
* zeroes the whole raster. On Android the equivalent line carries a long note
|
|
61
|
-
* about that being a second full-raster pass whose only justification is an
|
|
62
|
-
* invariant; here it is not optional and not merely an invariant — the
|
|
63
|
-
* `PdfCanvasPixelSink` contract promises zeroed memory, and that promise is what
|
|
64
|
-
* makes `background: 'transparent'` free. Removing it would break a documented
|
|
65
|
-
* behaviour rather than shaving a pass.
|
|
66
|
-
*/
|
|
67
|
-
class SlotBuffer final : public SlotBufferBase {
|
|
68
|
-
public:
|
|
69
|
-
explicit SlotBuffer(size_t size) : bytes_(size) {}
|
|
70
|
-
|
|
71
|
-
size_t size() const override { return bytes_.size(); }
|
|
72
|
-
uint8_t *data() override { return bytes_.data(); }
|
|
73
|
-
|
|
74
|
-
private:
|
|
75
|
-
std::vector<uint8_t> bytes_;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Slots are published on a render worker and taken on the JS thread, so the map is
|
|
80
|
-
* guarded. The lock is held only around the map operation itself — never across an
|
|
81
|
-
* allocation, never across a fill, and never across a JSI call.
|
|
82
|
-
*/
|
|
83
|
-
std::mutex g_slotsMutex;
|
|
84
|
-
std::unordered_map<int64_t, std::shared_ptr<SlotBuffer>> g_slots;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Slot ids, from one counter per PROCESS.
|
|
88
|
-
*
|
|
89
|
-
* Starting at 1 because 0 is `PdfCanvasRasterPixels`' "heap-backed" sentinel. It is
|
|
90
|
-
* a process-wide `std::atomic` rather than per-module state for the reason
|
|
91
|
-
* Android's equivalent is `static`: React Native builds a fresh set of native
|
|
92
|
-
* modules on every reload while the outgoing one may still have rasters parked, and
|
|
93
|
-
* a per-module counter starting at 1 would hand the SAME id to two modules. The
|
|
94
|
-
* second store would silently replace the first module's buffer and that module's
|
|
95
|
-
* take would return ANOTHER tile's pixels — a wrong image, not an error.
|
|
96
|
-
*/
|
|
97
|
-
std::atomic<int64_t> g_nextSlot{1};
|
|
98
|
-
|
|
99
|
-
std::shared_ptr<SlotBuffer> takeSlot(int64_t slot) {
|
|
100
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
101
|
-
auto found = g_slots.find(slot);
|
|
102
|
-
if (found == g_slots.end()) {
|
|
103
|
-
return nullptr;
|
|
104
|
-
}
|
|
105
|
-
std::shared_ptr<SlotBuffer> buffer = std::move(found->second);
|
|
106
|
-
g_slots.erase(found);
|
|
107
|
-
return buffer;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
} // namespace
|
|
111
|
-
|
|
112
|
-
/* ------------------------------------------------------------------ *
|
|
113
|
-
* The sink
|
|
114
|
-
* ------------------------------------------------------------------ */
|
|
115
|
-
|
|
116
|
-
@implementation PdfCanvasSlotSink {
|
|
117
|
-
/**
|
|
118
|
-
* Held HERE and nowhere else until `pdfCanvasPublish`. That is the whole safety
|
|
119
|
-
* property: until the buffer is in `g_slots` it is unreachable from
|
|
120
|
-
* `PdfCanvasSlotsReleaseAll`, which runs on another thread on every Fast
|
|
121
|
-
* Refresh, so a reload landing mid-draw cannot free memory CoreGraphics is
|
|
122
|
-
* writing into.
|
|
123
|
-
*/
|
|
124
|
-
std::shared_ptr<SlotBuffer> _buffer;
|
|
125
|
-
int64_t _slot;
|
|
126
|
-
BOOL _published;
|
|
127
|
-
BOOL _reserved;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
- (nullable void *)pdfCanvasReserveBytes:(NSUInteger)length {
|
|
131
|
-
if (_reserved || length == 0) {
|
|
132
|
-
return NULL;
|
|
133
|
-
}
|
|
134
|
-
_reserved = YES;
|
|
135
|
-
try {
|
|
136
|
-
_buffer = std::make_shared<SlotBuffer>(static_cast<size_t>(length));
|
|
137
|
-
} catch (...) {
|
|
138
|
-
// `std::bad_alloc` for a multi-megabyte raster under memory pressure. Reported
|
|
139
|
-
// as a refusal rather than propagated: the rasterizer falls straight through to
|
|
140
|
-
// a heap sink and the tile still goes out, which is the point of refusing
|
|
141
|
-
// BEFORE anything is drawn. A C++ exception escaping into ARC-managed
|
|
142
|
-
// Objective-C would also be a very bad way to say "no memory".
|
|
143
|
-
_buffer = nullptr;
|
|
144
|
-
return NULL;
|
|
145
|
-
}
|
|
146
|
-
// Allocated next to the map it goes into, from a process-wide counter, so a
|
|
147
|
-
// collision at publish is unrepresentable. See the class note in the header.
|
|
148
|
-
_slot = g_nextSlot.fetch_add(1, std::memory_order_relaxed);
|
|
149
|
-
return _buffer->data();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
- (int64_t)pdfCanvasPublish {
|
|
153
|
-
if (_buffer == nullptr || _published) {
|
|
154
|
-
return 0;
|
|
155
|
-
}
|
|
156
|
-
{
|
|
157
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
158
|
-
// ASSIGNMENT, not `emplace`, and it cannot collide: the id came from the
|
|
159
|
-
// process-wide counter a moment ago. Android's equivalent has to refuse a
|
|
160
|
-
// collision because its ids are handed in from Java.
|
|
161
|
-
g_slots[_slot] = _buffer;
|
|
162
|
-
}
|
|
163
|
-
_published = YES;
|
|
164
|
-
// The map holds the only reference this object needs to care about from here on.
|
|
165
|
-
// Dropping the local one means a successful take is the sole owner, so the buffer
|
|
166
|
-
// dies with the JS ArrayBuffer rather than with this sink.
|
|
167
|
-
_buffer = nullptr;
|
|
168
|
-
return _slot;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
- (void)pdfCanvasAbandon {
|
|
172
|
-
if (_published) {
|
|
173
|
-
// Already in the map. Taking it back out is what frees it — and finding
|
|
174
|
-
// nothing is the normal case when JS has already redeemed it, which is why
|
|
175
|
-
// this is a lookup rather than an assertion.
|
|
176
|
-
takeSlot(_slot);
|
|
177
|
-
_published = NO;
|
|
178
|
-
_slot = 0;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
// Never published: the buffer is still ours and the shared_ptr frees it.
|
|
182
|
-
_buffer = nullptr;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
@end
|
|
186
|
-
|
|
187
|
-
/* ------------------------------------------------------------------ *
|
|
188
|
-
* The probe
|
|
189
|
-
* ------------------------------------------------------------------ */
|
|
190
|
-
|
|
191
|
-
NSData *PdfCanvasSlotsProbeBytes(void) {
|
|
192
|
-
// Chosen, not arbitrary. 0x00 and 0xFF pin both ends of the range, and
|
|
193
|
-
// 0xFD/0xFE/0xFF sit above 0x7F where a signed-byte slip would show up. The
|
|
194
|
-
// sign-extension hazard does not exist on this platform — the whole path is
|
|
195
|
-
// `uint8_t` — but the list is shared with Android and with the TS side, and
|
|
196
|
-
// three copies that agree are worth more than one that is locally optimal.
|
|
197
|
-
static const uint8_t bytes[] = {0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF, 0x2A, 0x07};
|
|
198
|
-
return [NSData dataWithBytes:bytes length:sizeof(bytes)];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
int64_t PdfCanvasSlotsStoreProbe(void) {
|
|
202
|
-
NSData *probe = PdfCanvasSlotsProbeBytes();
|
|
203
|
-
PdfCanvasSlotSink *sink = [[PdfCanvasSlotSink alloc] init];
|
|
204
|
-
void *bytes = [sink pdfCanvasReserveBytes:probe.length];
|
|
205
|
-
if (bytes == NULL) {
|
|
206
|
-
return -1;
|
|
207
|
-
}
|
|
208
|
-
std::memcpy(bytes, probe.bytes, probe.length);
|
|
209
|
-
return [sink pdfCanvasPublish];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* ------------------------------------------------------------------ *
|
|
213
|
-
* Install
|
|
214
|
-
* ------------------------------------------------------------------ */
|
|
215
|
-
|
|
216
|
-
BOOL PdfCanvasSlotsInstall(void *runtimePointer) {
|
|
217
|
-
#ifdef PDFCANVAS_WITH_JSI
|
|
218
|
-
if (runtimePointer == nullptr) {
|
|
219
|
-
return NO;
|
|
220
|
-
}
|
|
221
|
-
auto &runtime = *reinterpret_cast<facebook::jsi::Runtime *>(runtimePointer);
|
|
222
|
-
try {
|
|
223
|
-
auto name =
|
|
224
|
-
facebook::jsi::PropNameID::forAscii(runtime, "__pdfCanvasTakePixels");
|
|
225
|
-
auto take = facebook::jsi::Function::createFromHostFunction(
|
|
226
|
-
runtime, name, 1,
|
|
227
|
-
[](facebook::jsi::Runtime &rt, const facebook::jsi::Value &,
|
|
228
|
-
const facebook::jsi::Value *args,
|
|
229
|
-
size_t count) -> facebook::jsi::Value {
|
|
230
|
-
if (count < 1 || !args[0].isNumber()) {
|
|
231
|
-
throw facebook::jsi::JSError(
|
|
232
|
-
rt, "__pdfCanvasTakePixels(slot): slot must be a number.");
|
|
233
|
-
}
|
|
234
|
-
const auto slot = static_cast<int64_t>(args[0].asNumber());
|
|
235
|
-
std::shared_ptr<SlotBuffer> buffer = takeSlot(slot);
|
|
236
|
-
if (buffer == nullptr) {
|
|
237
|
-
// Every slot is taken exactly once, so this means the render that
|
|
238
|
-
// filled it was torn down (a reload drains every slot) or the same
|
|
239
|
-
// slot was taken twice. Named rather than silently empty: an empty
|
|
240
|
-
// ArrayBuffer would reach Skia and draw a black tile.
|
|
241
|
-
throw facebook::jsi::JSError(
|
|
242
|
-
rt,
|
|
243
|
-
"__pdfCanvasTakePixels: no raster is waiting in slot " +
|
|
244
|
-
std::to_string(slot) +
|
|
245
|
-
". It was already taken, or the module was invalidated.");
|
|
246
|
-
}
|
|
247
|
-
// The ArrayBuffer BORROWS this buffer and the shared_ptr keeps it alive;
|
|
248
|
-
// the slot map has already dropped its reference, so JS is now the sole
|
|
249
|
-
// owner and the GC is what frees it.
|
|
250
|
-
return facebook::jsi::Value(
|
|
251
|
-
rt, facebook::jsi::ArrayBuffer(rt, std::move(buffer)));
|
|
252
|
-
});
|
|
253
|
-
runtime.global().setProperty(runtime, name, take);
|
|
254
|
-
return YES;
|
|
255
|
-
} catch (...) {
|
|
256
|
-
// A runtime that refuses to install a host function is a runtime this package
|
|
257
|
-
// will not use. The JS side falls back to base64.
|
|
258
|
-
return NO;
|
|
259
|
-
}
|
|
260
|
-
#else
|
|
261
|
-
// The SwiftPM build. There is no runtime to install into and nothing that could
|
|
262
|
-
// call the host function, so this is the honest answer rather than a stub.
|
|
263
|
-
(void)runtimePointer;
|
|
264
|
-
return NO;
|
|
265
|
-
#endif
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/* ------------------------------------------------------------------ *
|
|
269
|
-
* Teardown and diagnostics
|
|
270
|
-
* ------------------------------------------------------------------ */
|
|
271
|
-
|
|
272
|
-
void PdfCanvasSlotsRelease(int64_t slot) {
|
|
273
|
-
takeSlot(slot);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
void PdfCanvasSlotsReleaseAll(void) {
|
|
277
|
-
std::unordered_map<int64_t, std::shared_ptr<SlotBuffer>> drained;
|
|
278
|
-
{
|
|
279
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
280
|
-
drained.swap(g_slots);
|
|
281
|
-
}
|
|
282
|
-
// Freed outside the lock. Dropping several megabytes of buffers is not something
|
|
283
|
-
// to do while the JS thread might be waiting on the same mutex.
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
NSInteger PdfCanvasSlotsCount(void) {
|
|
287
|
-
std::lock_guard<std::mutex> lock(g_slotsMutex);
|
|
288
|
-
return static_cast<NSInteger>(g_slots.size());
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
NSData *_Nullable PdfCanvasSlotsTakeForTest(int64_t slot) {
|
|
292
|
-
std::shared_ptr<SlotBuffer> buffer = takeSlot(slot);
|
|
293
|
-
if (buffer == nullptr) {
|
|
294
|
-
return nil;
|
|
295
|
-
}
|
|
296
|
-
return [NSData dataWithBytes:buffer->data() length:buffer->size()];
|
|
297
|
-
}
|