@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,548 +0,0 @@
|
|
|
1
|
-
#import "PdfCanvasRasterizer.h"
|
|
2
|
-
|
|
3
|
-
#import <PDFKit/PDFKit.h>
|
|
4
|
-
#import <math.h>
|
|
5
|
-
|
|
6
|
-
#import "PdfCanvasDocumentPool.h"
|
|
7
|
-
#import "PdfCanvasPageTransform.h"
|
|
8
|
-
|
|
9
|
-
static const NSInteger kPdfCanvasMaxRasterPixels = 64000000;
|
|
10
|
-
|
|
11
|
-
/* ------------------------------------------------------------------ *
|
|
12
|
-
* Private surfaces
|
|
13
|
-
* ------------------------------------------------------------------ */
|
|
14
|
-
|
|
15
|
-
@interface PdfCanvasRasterizer ()
|
|
16
|
-
+ (nullable instancetype)withPool:(PdfCanvasDocumentPool *)pool
|
|
17
|
-
error:(NSError *_Nullable *_Nullable)error;
|
|
18
|
-
- (instancetype)initWithPool:(PdfCanvasDocumentPool *)pool
|
|
19
|
-
pages:(NSArray<PdfCanvasPageGeometry *> *)pages;
|
|
20
|
-
- (nullable PdfCanvasRasterPixels *)
|
|
21
|
-
renderLeased:(PdfCanvasDocumentLease *)lease
|
|
22
|
-
request:(PdfCanvasRasterRequest *)request
|
|
23
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
24
|
-
pxW:(NSInteger)pxW
|
|
25
|
-
pxH:(NSInteger)pxH
|
|
26
|
-
signal:(nullable PdfCanvasCancellation *)signal
|
|
27
|
-
sink:(nullable id<PdfCanvasPixelSink>)sink
|
|
28
|
-
error:(NSError *_Nullable *_Nullable)error;
|
|
29
|
-
- (BOOL)drawWithCoreGraphics:(PdfCanvasDocumentLease *)lease
|
|
30
|
-
request:(PdfCanvasRasterRequest *)request
|
|
31
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
32
|
-
context:(CGContextRef)ctx
|
|
33
|
-
error:(NSError *_Nullable *_Nullable)error;
|
|
34
|
-
- (BOOL)drawWithPdfKit:(PdfCanvasDocumentLease *)lease
|
|
35
|
-
request:(PdfCanvasRasterRequest *)request
|
|
36
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
37
|
-
context:(CGContextRef)ctx
|
|
38
|
-
error:(NSError *_Nullable *_Nullable)error;
|
|
39
|
-
@end
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The one bitmap configuration this library creates, stated once.
|
|
43
|
-
*
|
|
44
|
-
* `kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big` is RGBA with
|
|
45
|
-
* premultiplied alpha — `PdfCanvasFormatRGBA8888`, and `AlphaType.Premul` after
|
|
46
|
-
* `ingest.ts` maps it.
|
|
47
|
-
*
|
|
48
|
-
* PREMULTIPLIED IS NOT A CHOICE. MEASURED: `CGBitmapContextCreate` REFUSES
|
|
49
|
-
* `kCGImageAlphaLast` and `kCGImageAlphaFirst` — straight alpha, either byte order
|
|
50
|
-
* — returning NULL, while `premultipliedLast | 32Big`,
|
|
51
|
-
* `premultipliedFirst | 32Little` and `noneSkipLast` are all accepted. So the only
|
|
52
|
-
* two things an 8-bit RGBA context can be are premultiplied or alpha-less, and
|
|
53
|
-
* alpha-less would break `background: 'transparent'`, which is in the
|
|
54
|
-
* `RasterRequest` contract. This is the cell the spec's alpha table had marked
|
|
55
|
-
* NOT MEASURED; `src/rasterizer/ios.ts` declares the answer and
|
|
56
|
-
* `alphaIsPremultiplied` in the Swift suite reads the bytes back off an
|
|
57
|
-
* antialiased edge to confirm it (a 62%-covered opaque-white edge reads
|
|
58
|
-
* `[a, a, a, a]`, not `[255, 255, 255, a]`).
|
|
59
|
-
*
|
|
60
|
-
* RGBA RATHER THAN THE APPLE-NATIVE BGRA, deliberately. Both are accepted, and
|
|
61
|
-
* BGRA is the platform's preferred order — but cost is flat in scale on this
|
|
62
|
-
* engine, so rasterizing dominates and the byte order is not where the time goes,
|
|
63
|
-
* whereas matching Android's order means one fewer axis for a cross-platform pixel
|
|
64
|
-
* test to account for.
|
|
65
|
-
*/
|
|
66
|
-
static const CGBitmapInfo kPdfCanvasBitmapInfo =
|
|
67
|
-
(CGBitmapInfo)kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* sRGB, named explicitly rather than `CGColorSpaceCreateDeviceRGB()`.
|
|
71
|
-
*
|
|
72
|
-
* `Skia.Image.MakeImage` is handed an `SkImageInfo` with no colour space, which
|
|
73
|
-
* Skia treats as legacy/sRGB, and Android's `ARGB_8888` bitmaps are sRGB. A
|
|
74
|
-
* wide-gamut context here would make the identical PDF read differently on iOS
|
|
75
|
-
* than on Android with nothing in the pipeline saying so — the numbers would
|
|
76
|
-
* simply be different, and every cross-platform pixel comparison would drift.
|
|
77
|
-
*/
|
|
78
|
-
static CGColorSpaceRef PdfCanvasColorSpace(void) {
|
|
79
|
-
static CGColorSpaceRef space;
|
|
80
|
-
static dispatch_once_t once;
|
|
81
|
-
dispatch_once(&once, ^{
|
|
82
|
-
space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
83
|
-
});
|
|
84
|
-
return space;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@implementation PdfCanvasRasterizer {
|
|
88
|
-
PdfCanvasDocumentPool *_pool;
|
|
89
|
-
NSArray<PdfCanvasPageGeometry *> *_pages;
|
|
90
|
-
BOOL _closed;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
+ (NSInteger)maxRasterPixels {
|
|
94
|
-
return kPdfCanvasMaxRasterPixels;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
+ (NSString *)rotationSupport {
|
|
98
|
-
return @"supported";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* ---------------------------------------------------------------- *
|
|
102
|
-
* Opening
|
|
103
|
-
* ---------------------------------------------------------------- */
|
|
104
|
-
|
|
105
|
-
+ (nullable instancetype)openFileURL:(NSURL *)url
|
|
106
|
-
password:(nullable NSString *)password
|
|
107
|
-
poolSize:(NSInteger)poolSize
|
|
108
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
109
|
-
PdfCanvasDocumentPool *pool = [PdfCanvasDocumentPool openFileURL:url
|
|
110
|
-
password:password
|
|
111
|
-
size:poolSize
|
|
112
|
-
error:error];
|
|
113
|
-
return pool == nil ? nil : [self withPool:pool error:error];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
+ (nullable instancetype)openData:(NSData *)data
|
|
117
|
-
password:(nullable NSString *)password
|
|
118
|
-
poolSize:(NSInteger)poolSize
|
|
119
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
120
|
-
PdfCanvasDocumentPool *pool = [PdfCanvasDocumentPool openData:data
|
|
121
|
-
password:password
|
|
122
|
-
size:poolSize
|
|
123
|
-
error:error];
|
|
124
|
-
return pool == nil ? nil : [self withPool:pool error:error];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
+ (nullable instancetype)withPool:(PdfCanvasDocumentPool *)pool
|
|
128
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
129
|
-
PdfCanvasDocumentLease *lease = [pool leaseWithError:error];
|
|
130
|
-
if (lease == nil) {
|
|
131
|
-
[pool close];
|
|
132
|
-
return nil;
|
|
133
|
-
}
|
|
134
|
-
NSMutableArray<PdfCanvasPageGeometry *> *pages =
|
|
135
|
-
[NSMutableArray arrayWithCapacity:(NSUInteger)pool.pageCount];
|
|
136
|
-
for (NSInteger i = 0; i < pool.pageCount; i++) {
|
|
137
|
-
// CGPDFDocumentGetPage is 1-BASED. Every other index in this package is
|
|
138
|
-
// 0-based, so the conversion happens here, once, and nowhere else.
|
|
139
|
-
CGPDFPageRef page = CGPDFDocumentGetPage(lease.cgDocument, (size_t)(i + 1));
|
|
140
|
-
if (page == NULL) {
|
|
141
|
-
[pool returnLease:lease];
|
|
142
|
-
[pool close];
|
|
143
|
-
// A page the document's own page count promised and cannot produce. Failing
|
|
144
|
-
// the OPEN rather than papering over it with a zero size is deliberate: a
|
|
145
|
-
// `PageGeometry` is what the layout function turns into the document's whole
|
|
146
|
-
// coordinate space, so a wrong one is not a missing page, it is every page
|
|
147
|
-
// after it in the wrong place.
|
|
148
|
-
return PdfCanvasFail(
|
|
149
|
-
error, PdfCanvasErrorCodeCorrupt,
|
|
150
|
-
[NSString stringWithFormat:
|
|
151
|
-
@"Could not read page %ld of %ld — the page tree "
|
|
152
|
-
@"promises it and CoreGraphics cannot produce it.",
|
|
153
|
-
(long)i, (long)pool.pageCount],
|
|
154
|
-
nil);
|
|
155
|
-
}
|
|
156
|
-
[pages addObject:[[PdfCanvasPageGeometry alloc]
|
|
157
|
-
initWithIndex:i
|
|
158
|
-
cropBox:PdfCanvasEffectiveCropBox(page)
|
|
159
|
-
rotationDegrees:CGPDFPageGetRotationAngle(page)]];
|
|
160
|
-
}
|
|
161
|
-
[pool returnLease:lease];
|
|
162
|
-
return [[self alloc] initWithPool:pool pages:pages];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
- (instancetype)initWithPool:(PdfCanvasDocumentPool *)pool
|
|
166
|
-
pages:(NSArray<PdfCanvasPageGeometry *> *)pages {
|
|
167
|
-
self = [super init];
|
|
168
|
-
if (self != nil) {
|
|
169
|
-
_pool = pool;
|
|
170
|
-
_pages = pages;
|
|
171
|
-
}
|
|
172
|
-
return self;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/* ---------------------------------------------------------------- *
|
|
176
|
-
* Cheap accessors
|
|
177
|
-
* ---------------------------------------------------------------- */
|
|
178
|
-
|
|
179
|
-
- (NSInteger)pageCount {
|
|
180
|
-
return (NSInteger)_pages.count;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
- (NSInteger)poolSize {
|
|
184
|
-
return _pool.size;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
- (BOOL)isClosed {
|
|
188
|
-
return _closed || _pool.isClosed;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
- (nullable PdfCanvasPageGeometry *)pageGeometryAtIndex:(NSInteger)index
|
|
192
|
-
error:
|
|
193
|
-
(NSError *_Nullable
|
|
194
|
-
*_Nullable)error {
|
|
195
|
-
if (index < 0 || index >= (NSInteger)_pages.count) {
|
|
196
|
-
return PdfCanvasFail(
|
|
197
|
-
error, PdfCanvasErrorCodeNotFound,
|
|
198
|
-
[NSString stringWithFormat:@"Document has %lu pages; asked for %ld.",
|
|
199
|
-
(unsigned long)_pages.count, (long)index],
|
|
200
|
-
nil);
|
|
201
|
-
}
|
|
202
|
-
return _pages[(NSUInteger)index];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/* ---------------------------------------------------------------- *
|
|
206
|
-
* Rendering
|
|
207
|
-
* ---------------------------------------------------------------- */
|
|
208
|
-
|
|
209
|
-
- (nullable PdfCanvasRasterPixels *)render:(PdfCanvasRasterRequest *)request
|
|
210
|
-
cancellation:
|
|
211
|
-
(nullable PdfCanvasCancellation *)signal
|
|
212
|
-
sink:(nullable id<PdfCanvasPixelSink>)sink
|
|
213
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
214
|
-
if (!PdfCanvasCheckCancelled(signal, @"before start", error)) {
|
|
215
|
-
return nil;
|
|
216
|
-
}
|
|
217
|
-
if (self.isClosed) {
|
|
218
|
-
return PdfCanvasFail(error, PdfCanvasErrorCodeBackendFailure,
|
|
219
|
-
@"Document handle is closed.", nil);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Validates the page index before anything expensive is allocated or leased.
|
|
223
|
-
PdfCanvasPageGeometry *geometry = [self pageGeometryAtIndex:request.page
|
|
224
|
-
error:error];
|
|
225
|
-
if (geometry == nil) {
|
|
226
|
-
return nil;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (!(request.scale > 0) || !isfinite(request.scale)) {
|
|
230
|
-
return PdfCanvasFail(
|
|
231
|
-
error, PdfCanvasErrorCodeBackendFailure,
|
|
232
|
-
[NSString stringWithFormat:@"Scale must be finite and positive: %g.",
|
|
233
|
-
request.scale],
|
|
234
|
-
nil);
|
|
235
|
-
}
|
|
236
|
-
if (!(request.docWidth > 0) || !(request.docHeight > 0) ||
|
|
237
|
-
!isfinite(request.docWidth) || !isfinite(request.docHeight)) {
|
|
238
|
-
return PdfCanvasFail(
|
|
239
|
-
error, PdfCanvasErrorCodeBackendFailure,
|
|
240
|
-
[NSString stringWithFormat:@"Doc rect has no area: %gx%g.",
|
|
241
|
-
request.docWidth, request.docHeight],
|
|
242
|
-
nil);
|
|
243
|
-
}
|
|
244
|
-
if (!isfinite(request.docX) || !isfinite(request.docY)) {
|
|
245
|
-
return PdfCanvasFail(
|
|
246
|
-
error, PdfCanvasErrorCodeBackendFailure,
|
|
247
|
-
[NSString stringWithFormat:@"Doc rect origin is not finite: %g,%g.",
|
|
248
|
-
request.docX, request.docY],
|
|
249
|
-
nil);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const NSInteger pxW = request.pixelWidth;
|
|
253
|
-
const NSInteger pxH = request.pixelHeight;
|
|
254
|
-
const long long pixels = (long long)pxW * (long long)pxH;
|
|
255
|
-
if (pixels > kPdfCanvasMaxRasterPixels) {
|
|
256
|
-
return PdfCanvasFail(
|
|
257
|
-
error, PdfCanvasErrorCodeOutOfMemory,
|
|
258
|
-
[NSString stringWithFormat:
|
|
259
|
-
@"Refused a %ldx%ld raster (%lld pixels > %ld).",
|
|
260
|
-
(long)pxW, (long)pxH, pixels,
|
|
261
|
-
(long)kPdfCanvasMaxRasterPixels],
|
|
262
|
-
nil);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
PdfCanvasDocumentLease *lease = [_pool leaseWithError:error];
|
|
266
|
-
if (lease == nil) {
|
|
267
|
-
return nil;
|
|
268
|
-
}
|
|
269
|
-
@try {
|
|
270
|
-
// The lease can block for the length of another render, so this is the check
|
|
271
|
-
// that actually saves work when a raster is superseded mid-queue.
|
|
272
|
-
if (!PdfCanvasCheckCancelled(signal, @"after pool wait", error)) {
|
|
273
|
-
return nil;
|
|
274
|
-
}
|
|
275
|
-
if (self.isClosed) {
|
|
276
|
-
return PdfCanvasFail(error, PdfCanvasErrorCodeBackendFailure,
|
|
277
|
-
@"Document handle is closed.", nil);
|
|
278
|
-
}
|
|
279
|
-
return [self renderLeased:lease
|
|
280
|
-
request:request
|
|
281
|
-
geometry:geometry
|
|
282
|
-
pxW:pxW
|
|
283
|
-
pxH:pxH
|
|
284
|
-
signal:signal
|
|
285
|
-
sink:sink
|
|
286
|
-
error:error];
|
|
287
|
-
} @finally {
|
|
288
|
-
[_pool returnLease:lease];
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
- (nullable PdfCanvasRasterPixels *)
|
|
293
|
-
renderLeased:(PdfCanvasDocumentLease *)lease
|
|
294
|
-
request:(PdfCanvasRasterRequest *)request
|
|
295
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
296
|
-
pxW:(NSInteger)pxW
|
|
297
|
-
pxH:(NSInteger)pxH
|
|
298
|
-
signal:(nullable PdfCanvasCancellation *)signal
|
|
299
|
-
sink:(nullable id<PdfCanvasPixelSink>)sink
|
|
300
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
301
|
-
// TIGHT, and `CGBitmapContextCreate` constrains what else is possible: MEASURED,
|
|
302
|
-
// it refuses a `bytesPerRow` that is not a multiple of 4 at 32 bits per pixel.
|
|
303
|
-
// `width * 4` always is.
|
|
304
|
-
const NSInteger rowBytes = pxW * 4;
|
|
305
|
-
const NSUInteger byteCount = (NSUInteger)(rowBytes * pxH);
|
|
306
|
-
|
|
307
|
-
// RESERVE BEFORE DRAWING, and fall back before drawing too. A sink refusal here
|
|
308
|
-
// costs nothing; on Android the equivalent refusal can only be discovered after
|
|
309
|
-
// the raster exists, which is why that code has a heap path it falls into
|
|
310
|
-
// afterwards.
|
|
311
|
-
id<PdfCanvasPixelSink> destination = sink;
|
|
312
|
-
void *bytes = destination == nil
|
|
313
|
-
? NULL
|
|
314
|
-
: [destination pdfCanvasReserveBytes:byteCount];
|
|
315
|
-
if (bytes == NULL) {
|
|
316
|
-
destination = [[PdfCanvasHeapSink alloc] init];
|
|
317
|
-
bytes = [destination pdfCanvasReserveBytes:byteCount];
|
|
318
|
-
}
|
|
319
|
-
if (bytes == NULL) {
|
|
320
|
-
return PdfCanvasFail(
|
|
321
|
-
error, PdfCanvasErrorCodeOutOfMemory,
|
|
322
|
-
[NSString stringWithFormat:
|
|
323
|
-
@"Could not allocate %lu bytes for a %ldx%ld raster.",
|
|
324
|
-
(unsigned long)byteCount, (long)pxW, (long)pxH],
|
|
325
|
-
nil);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
CGContextRef ctx = CGBitmapContextCreate(
|
|
329
|
-
bytes, (size_t)pxW, (size_t)pxH, 8, (size_t)rowBytes,
|
|
330
|
-
PdfCanvasColorSpace(), kPdfCanvasBitmapInfo);
|
|
331
|
-
if (ctx == NULL) {
|
|
332
|
-
[destination pdfCanvasAbandon];
|
|
333
|
-
return PdfCanvasFail(
|
|
334
|
-
error, PdfCanvasErrorCodeBackendFailure,
|
|
335
|
-
[NSString stringWithFormat:
|
|
336
|
-
@"CGBitmapContextCreate refused %ldx%ld at rowBytes %ld.",
|
|
337
|
-
(long)pxW, (long)pxH, (long)rowBytes],
|
|
338
|
-
nil);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// THE FILL IS NOT OPTIONAL AND IT IS NOT DEFENSIVE. Neither engine clears the
|
|
342
|
-
// destination, and a zeroed RGBA buffer is transparent BLACK — which composites
|
|
343
|
-
// under the app's annotation layers as a black page, not a blank one. Ingest
|
|
344
|
-
// cannot repair it afterwards: it has no way to tell "the PDF painted black
|
|
345
|
-
// here" from "nothing painted here".
|
|
346
|
-
//
|
|
347
|
-
// `memset` rather than `CGContextFillRect`: 0xFF across the whole buffer is
|
|
348
|
-
// exactly opaque white in this configuration, it costs one pass with no CG
|
|
349
|
-
// state involved, and it covers any row padding a future alignment change might
|
|
350
|
-
// add. The transparent case needs nothing, because both sinks reserve zeroed
|
|
351
|
-
// memory — which is a documented part of the sink contract rather than luck.
|
|
352
|
-
if (request.background == PdfCanvasBackgroundWhite) {
|
|
353
|
-
memset(bytes, 0xFF, byteCount);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
|
|
357
|
-
CGContextSetShouldAntialias(ctx, true);
|
|
358
|
-
// Explicitly OFF. Subpixel text positioning changes glyph rasterization in a
|
|
359
|
-
// way that depends on the destination's pixel grid, and every seam in this
|
|
360
|
-
// package is two rasters of the same geometry into differently-sized surfaces.
|
|
361
|
-
// Leaving it on would widen the sub-pixel disagreement the overlap rule already
|
|
362
|
-
// has to tolerate. It has no effect on a bitmap context on iOS in any case, so
|
|
363
|
-
// this is a statement of intent that costs nothing.
|
|
364
|
-
CGContextSetShouldSubpixelPositionFonts(ctx, false);
|
|
365
|
-
CGContextSetShouldSubpixelQuantizeFonts(ctx, false);
|
|
366
|
-
|
|
367
|
-
CGContextConcatCTM(
|
|
368
|
-
ctx, PdfCanvasDocToRasterTransform(request.docX, request.docY,
|
|
369
|
-
request.scale, pxH));
|
|
370
|
-
|
|
371
|
-
BOOL drawn = NO;
|
|
372
|
-
if (request.annotations) {
|
|
373
|
-
drawn = [self drawWithPdfKit:lease
|
|
374
|
-
request:request
|
|
375
|
-
geometry:geometry
|
|
376
|
-
context:ctx
|
|
377
|
-
error:error];
|
|
378
|
-
} else {
|
|
379
|
-
drawn = [self drawWithCoreGraphics:lease
|
|
380
|
-
request:request
|
|
381
|
-
geometry:geometry
|
|
382
|
-
context:ctx
|
|
383
|
-
error:error];
|
|
384
|
-
}
|
|
385
|
-
// FLUSHED BEFORE THE BYTES ARE READ BY ANYBODY. A bitmap context is documented
|
|
386
|
-
// to be safe to read after the drawing calls return, but a flush is what makes
|
|
387
|
-
// that true across an OS that may have deferred work, and it is free relative
|
|
388
|
-
// to the draw. It must happen before the context is released, not after.
|
|
389
|
-
CGContextFlush(ctx);
|
|
390
|
-
CGContextRelease(ctx);
|
|
391
|
-
|
|
392
|
-
if (!drawn) {
|
|
393
|
-
[destination pdfCanvasAbandon];
|
|
394
|
-
return nil;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Post-check. This saves no drawing — the pixels above are already finished — it
|
|
398
|
-
// only stops a superseded raster being handed back and ingested into Skia, which
|
|
399
|
-
// is the expensive half on the JS side.
|
|
400
|
-
//
|
|
401
|
-
// THE ABANDON IS NOT OPTIONAL. A heap-backed raster would simply be collected,
|
|
402
|
-
// but a slot-backed one is about to be published to a process-wide map where
|
|
403
|
-
// nothing else will ever look for it. A superseded epoch produces a stream of
|
|
404
|
-
// these, so leaking here is megabytes per gesture. The check stays AFTER the
|
|
405
|
-
// draw rather than moving in front of it so the cancel window it covers does not
|
|
406
|
-
// shrink.
|
|
407
|
-
if (!PdfCanvasCheckCancelled(signal, @"after render", error)) {
|
|
408
|
-
[destination pdfCanvasAbandon];
|
|
409
|
-
return nil;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const int64_t slot = [destination pdfCanvasPublish];
|
|
413
|
-
NSData *heapBytes = [destination isKindOfClass:PdfCanvasHeapSink.class]
|
|
414
|
-
? ((PdfCanvasHeapSink *)destination).data
|
|
415
|
-
: nil;
|
|
416
|
-
if (slot == 0 && heapBytes == nil) {
|
|
417
|
-
// A sink that reserved successfully, published no identifier, and is not the
|
|
418
|
-
// heap sink — so the raster exists and there is no way to reach it. Unreachable
|
|
419
|
-
// with the two sinks that exist, and named here anyway: without this the failure
|
|
420
|
-
// surfaces two layers up as "the native module returned neither `base64` nor
|
|
421
|
-
// `pixelsSlot`", which sends a reader looking at the bridge instead of at their
|
|
422
|
-
// sink.
|
|
423
|
-
[destination pdfCanvasAbandon];
|
|
424
|
-
return PdfCanvasFail(
|
|
425
|
-
error, PdfCanvasErrorCodeBackendFailure,
|
|
426
|
-
@"A PdfCanvasPixelSink published slot 0 without being a PdfCanvasHeapSink, "
|
|
427
|
-
@"so the finished raster is unreachable. A sink must either return a "
|
|
428
|
-
@"non-zero identifier from pdfCanvasPublish or expose its bytes the way "
|
|
429
|
-
@"PdfCanvasHeapSink does.",
|
|
430
|
-
nil);
|
|
431
|
-
}
|
|
432
|
-
return [[PdfCanvasRasterPixels alloc] initWithSink:destination
|
|
433
|
-
slot:slot
|
|
434
|
-
bytes:heapBytes
|
|
435
|
-
width:pxW
|
|
436
|
-
height:pxH
|
|
437
|
-
rowBytes:rowBytes
|
|
438
|
-
format:PdfCanvasFormatRGBA8888];
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* The tile path: content stream only, no annotations.
|
|
443
|
-
*
|
|
444
|
-
* `CGContextDrawPDFPage` applies NEITHER the crop box origin NOR `/Rotate`
|
|
445
|
-
* (measured — see `PdfCanvasPageTransform.h`), so both are in the transform, and
|
|
446
|
-
* the clip is what keeps content outside the crop box out of the raster. The clip
|
|
447
|
-
* is applied AFTER the page transform, so its rect is in PDF user space.
|
|
448
|
-
*/
|
|
449
|
-
- (BOOL)drawWithCoreGraphics:(PdfCanvasDocumentLease *)lease
|
|
450
|
-
request:(PdfCanvasRasterRequest *)request
|
|
451
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
452
|
-
context:(CGContextRef)ctx
|
|
453
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
454
|
-
CGPDFPageRef page =
|
|
455
|
-
CGPDFDocumentGetPage(lease.cgDocument, (size_t)(request.page + 1));
|
|
456
|
-
if (page == NULL) {
|
|
457
|
-
PdfCanvasFail(
|
|
458
|
-
error, PdfCanvasErrorCodeNotFound,
|
|
459
|
-
[NSString stringWithFormat:@"No page %ld in this document.",
|
|
460
|
-
(long)request.page],
|
|
461
|
-
nil);
|
|
462
|
-
return NO;
|
|
463
|
-
}
|
|
464
|
-
CGContextConcatCTM(
|
|
465
|
-
ctx, PdfCanvasPdfToDocTransform(geometry.cropBox,
|
|
466
|
-
geometry.rotationDegrees));
|
|
467
|
-
CGContextClipToRect(ctx, geometry.cropBox);
|
|
468
|
-
CGContextDrawPDFPage(ctx, page);
|
|
469
|
-
return YES;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* The annotation path: PDFKit, which composites appearance streams.
|
|
474
|
-
*
|
|
475
|
-
* NOTHING HERE ROTATES ANYTHING. PDFKit applies `/Rotate` and normalises the box
|
|
476
|
-
* origin itself, so the only transform is a y-flip — and it must be through the
|
|
477
|
-
* POST-ROTATION height, which is `geometry.heightPoints` and emphatically not
|
|
478
|
-
* `-[PDFPage boundsForBox:].size.height`. That method does not apply `/Rotate`
|
|
479
|
-
* while `drawWithBox:` does, and flipping by the wrong one draws the page rotated,
|
|
480
|
-
* off-centre and clipped with no error anywhere. See `PdfCanvasPageTransform.h`.
|
|
481
|
-
*
|
|
482
|
-
* NO CLIP. PDFKit draws the box it is given and nothing outside it, so the clip
|
|
483
|
-
* the CoreGraphics path needs would be redundant here — and a clip in the wrong
|
|
484
|
-
* space (this one is doc space, not PDF user space) would silently cut the page.
|
|
485
|
-
*/
|
|
486
|
-
- (BOOL)drawWithPdfKit:(PdfCanvasDocumentLease *)lease
|
|
487
|
-
request:(PdfCanvasRasterRequest *)request
|
|
488
|
-
geometry:(PdfCanvasPageGeometry *)geometry
|
|
489
|
-
context:(CGContextRef)ctx
|
|
490
|
-
error:(NSError *_Nullable *_Nullable)error {
|
|
491
|
-
PDFDocument *document = [lease pdfKitDocumentWithError:error];
|
|
492
|
-
if (document == nil) {
|
|
493
|
-
return NO;
|
|
494
|
-
}
|
|
495
|
-
PDFPage *page = [document pageAtIndex:(NSUInteger)request.page];
|
|
496
|
-
if (page == nil) {
|
|
497
|
-
PdfCanvasFail(
|
|
498
|
-
error, PdfCanvasErrorCodeNotFound,
|
|
499
|
-
[NSString stringWithFormat:@"PDFKit has no page %ld in this document.",
|
|
500
|
-
(long)request.page],
|
|
501
|
-
nil);
|
|
502
|
-
return NO;
|
|
503
|
-
}
|
|
504
|
-
CGContextConcatCTM(
|
|
505
|
-
ctx, PdfCanvasDocToPdfKitTransform(geometry.heightPoints));
|
|
506
|
-
[page drawWithBox:kPDFDisplayBoxCropBox toContext:ctx];
|
|
507
|
-
return YES;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/* ---------------------------------------------------------------- *
|
|
511
|
-
* Teardown
|
|
512
|
-
* ---------------------------------------------------------------- */
|
|
513
|
-
|
|
514
|
-
- (void)close {
|
|
515
|
-
if (_closed) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
_closed = YES;
|
|
519
|
-
[_pool close];
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
@end
|
|
523
|
-
|
|
524
|
-
/* ------------------------------------------------------------------ *
|
|
525
|
-
* The crop box
|
|
526
|
-
* ------------------------------------------------------------------ */
|
|
527
|
-
|
|
528
|
-
CGRect PdfCanvasEffectiveCropBox(CGPDFPageRef page) {
|
|
529
|
-
const CGRect media = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
|
|
530
|
-
const CGRect crop = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
|
|
531
|
-
// INTERSECTED, per the PDF spec: "the crop box shall be intersected with the
|
|
532
|
-
// media box". CoreGraphics returns the media box when there is no crop box, so
|
|
533
|
-
// the common case is a no-op, and it does NOT do the intersection itself — a
|
|
534
|
-
// file whose crop box overhangs its media box would otherwise produce a page
|
|
535
|
-
// larger than the paper, and every rect the layout function derives from it
|
|
536
|
-
// would be wrong by the overhang.
|
|
537
|
-
//
|
|
538
|
-
// A degenerate result falls back to the media box rather than to an empty rect:
|
|
539
|
-
// a zero-area page would make `fitScale` produce a non-finite number and the
|
|
540
|
-
// planner would emit nothing at all, which presents as a blank document with no
|
|
541
|
-
// error. CGRectIntersection returns CGRectNull for non-overlapping rects, and
|
|
542
|
-
// CGRectNull's width and height are 0.
|
|
543
|
-
const CGRect intersected = CGRectIntersection(media, crop);
|
|
544
|
-
if (CGRectIsNull(intersected) || CGRectIsEmpty(intersected)) {
|
|
545
|
-
return media;
|
|
546
|
-
}
|
|
547
|
-
return intersected;
|
|
548
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#import <Foundation/Foundation.h>
|
|
2
|
-
|
|
3
|
-
#import "PdfCanvasErrorCode.h"
|
|
4
|
-
|
|
5
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Best-effort cancellation, and the emphasis is on BEST-EFFORT.
|
|
9
|
-
*
|
|
10
|
-
* `CGContextDrawPDFPage` and `-[PDFPage drawWithBox:toContext:]` are synchronous
|
|
11
|
-
* and uninterruptible once entered. There is no cancel token, no progress
|
|
12
|
-
* callback, and no safe way to kill the thread — CoreGraphics is mid-way through
|
|
13
|
-
* writing into a buffer this process owns, and on the fast transport that buffer
|
|
14
|
-
* is about to become a JS `ArrayBuffer`. So this class cannot stop a render; it
|
|
15
|
-
* can only stop the NEXT one starting, which is exactly the shape the TS contract
|
|
16
|
-
* already assumes for this platform — `RasterPolicy.minEpochIntervalMs` names
|
|
17
|
-
* PDFKit in its own doc comment as the reason it exists.
|
|
18
|
-
*
|
|
19
|
-
* Concretely, a cancel is observed at four points and no others: on entry, after
|
|
20
|
-
* the pool lease (the wait can be long when every document is busy), immediately
|
|
21
|
-
* before the draw, and immediately after it. The last one does not save any work —
|
|
22
|
-
* the pixels are already drawn — it only stops a superseded raster being handed
|
|
23
|
-
* back and ingested into Skia, and it is the one that must release the slot.
|
|
24
|
-
*
|
|
25
|
-
* `_Atomic(bool)` rather than a lock: it is written by the JS thread through
|
|
26
|
-
* `cancel(handle, token)` and read by a render worker, and there is nothing to
|
|
27
|
-
* order it against. C11 atomics are available in a plain `.m` file, which keeps
|
|
28
|
-
* `PdfCanvasCore` free of C++ and therefore importable from Swift.
|
|
29
|
-
*/
|
|
30
|
-
@interface PdfCanvasCancellation : NSObject
|
|
31
|
-
|
|
32
|
-
- (void)cancel;
|
|
33
|
-
@property(nonatomic, readonly, getter=isCancelled) BOOL cancelled;
|
|
34
|
-
|
|
35
|
-
@end
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Returns NO and fills `error` with a `cancelled` code when the signal is set.
|
|
39
|
-
*
|
|
40
|
-
* @param stage named in the message so a cancellation reported from the
|
|
41
|
-
* post-render check is distinguishable from one that saved real work.
|
|
42
|
-
*/
|
|
43
|
-
FOUNDATION_EXPORT BOOL PdfCanvasCheckCancelled(
|
|
44
|
-
PdfCanvasCancellation *_Nullable signal,
|
|
45
|
-
NSString *stage,
|
|
46
|
-
NSError *_Nullable *_Nullable error);
|
|
47
|
-
|
|
48
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The umbrella header for `PdfCanvasCore` — the React-free iOS rasterizer.
|
|
3
|
-
*
|
|
4
|
-
* It exists so that both build systems land on the same module: SwiftPM prefers
|
|
5
|
-
* `include/<TargetName>.h` as a target's umbrella header when it is present (and
|
|
6
|
-
* would otherwise synthesise an umbrella DIRECTORY module, whose contents depend
|
|
7
|
-
* on the file listing), and CocoaPods needs a modulemap to make
|
|
8
|
-
* `import PdfCanvasCore` work from Swift at all. One hand-written list is
|
|
9
|
-
* deterministic; two generated ones are not.
|
|
10
|
-
*
|
|
11
|
-
* NOTHING HERE IMPORTS UIKit, JSI, OR REACT NATIVE, and that is enforced by the
|
|
12
|
-
* fact that `ios/Package.swift` builds this target for macOS so `swift test` can
|
|
13
|
-
* run it on a laptop. A UIKit import would break that build immediately, which is
|
|
14
|
-
* the failure mode worth having.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
#import "PdfCanvasCancellation.h"
|
|
18
|
-
#import "PdfCanvasDocumentPool.h"
|
|
19
|
-
#import "PdfCanvasErrorCode.h"
|
|
20
|
-
#import "PdfCanvasPageGeometry.h"
|
|
21
|
-
#import "PdfCanvasPageTransform.h"
|
|
22
|
-
#import "PdfCanvasPixelSink.h"
|
|
23
|
-
#import "PdfCanvasPreflight.h"
|
|
24
|
-
#import "PdfCanvasRasterPixels.h"
|
|
25
|
-
#import "PdfCanvasRasterRequest.h"
|
|
26
|
-
#import "PdfCanvasRasterizer.h"
|