@reekon-tools/react-native-pdf-canvas 0.1.0 → 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.
Files changed (129) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +68 -39
  4. package/README.md +213 -1316
  5. package/android/build.gradle +67 -54
  6. package/android/consumer-rules.pro +7 -0
  7. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
  8. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
  9. package/android/src/main/cpp/CMakeLists.txt +54 -0
  10. package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
  11. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
  12. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
  13. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
  14. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
  15. package/android/tools/compile-gate.sh +81 -101
  16. package/dist/controller.d.ts +14 -0
  17. package/dist/controller.js +4 -3
  18. package/dist/index.native.d.ts +5 -0
  19. package/dist/index.native.js +5 -0
  20. package/dist/index.web.d.ts +10 -0
  21. package/dist/index.web.js +10 -0
  22. package/dist/rasterizer/index.native.d.ts +8 -12
  23. package/dist/rasterizer/index.native.js +15 -21
  24. package/dist/rasterizer/native-bridge.d.ts +51 -75
  25. package/dist/rasterizer/native-bridge.js +49 -33
  26. package/dist/rasterizer/native.d.ts +45 -0
  27. package/dist/rasterizer/native.js +123 -0
  28. package/dist/react/usePdfDocument.d.ts +5 -5
  29. package/dist/react/usePdfLayer.d.ts +10 -1
  30. package/dist/react/usePdfLayer.js +10 -2
  31. package/dist/skia/index.native.d.ts +7 -3
  32. package/dist/skia/index.native.js +7 -3
  33. package/dist/skia/index.web.d.ts +4 -2
  34. package/dist/skia/index.web.js +4 -2
  35. package/dist/testing/scenes.d.ts +3 -3
  36. package/dist/testing/scenes.js +4 -4
  37. package/dist/types.d.ts +36 -42
  38. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  39. package/native/.clangd +5 -0
  40. package/native/CMakeLists.txt +89 -0
  41. package/native/core/include/pdfcanvas/document.h +90 -0
  42. package/native/core/include/pdfcanvas/error.h +47 -0
  43. package/native/core/include/pdfcanvas/jsi.h +18 -0
  44. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  45. package/native/core/include/pdfcanvas/library.h +28 -0
  46. package/native/core/include/pdfcanvas/pixels.h +92 -0
  47. package/native/core/include/pdfcanvas/service.h +87 -0
  48. package/native/core/include/pdfcanvas/slots.h +113 -0
  49. package/native/core/include/pdfcanvas/types.h +110 -0
  50. package/native/core/pdfcanvas-core.cmake +22 -0
  51. package/native/core/src/document.cpp +516 -0
  52. package/native/core/src/error.cpp +29 -0
  53. package/native/core/src/jsi.cpp +66 -0
  54. package/native/core/src/library.cpp +52 -0
  55. package/native/core/src/pixels.cpp +45 -0
  56. package/native/core/src/service.cpp +156 -0
  57. package/native/core/src/slots.cpp +139 -0
  58. package/native/tests/fixtures.cpp +451 -0
  59. package/native/tests/fixtures.h +96 -0
  60. package/native/tests/harness.h +122 -0
  61. package/native/tests/main.cpp +64 -0
  62. package/native/tests/pixels_util.h +126 -0
  63. package/native/tests/test_document.cpp +485 -0
  64. package/native/tests/test_service.cpp +130 -0
  65. package/native/tests/test_slots.cpp +250 -0
  66. package/package.json +38 -8
  67. package/react-native.config.js +5 -2
  68. package/scripts/fetch-pdfium.mjs +487 -0
  69. package/scripts/pdfium-manifest.json +46 -0
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  72. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  73. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  74. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  75. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  76. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  77. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  78. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  79. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  80. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  81. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  82. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  83. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  84. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  85. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  86. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  87. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  88. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  89. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  90. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  91. package/dist/rasterizer/android.d.ts +0 -121
  92. package/dist/rasterizer/android.js +0 -126
  93. package/dist/rasterizer/ios.d.ts +0 -84
  94. package/dist/rasterizer/ios.js +0 -155
  95. package/ios/Package.swift +0 -58
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  98. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  99. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  100. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  101. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  102. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  103. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  104. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  105. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  109. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  110. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  111. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  112. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  113. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  114. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  115. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  116. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  117. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  118. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  119. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  120. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  121. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  122. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  123. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  124. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  125. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  126. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  127. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  128. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  129. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
@@ -1,389 +0,0 @@
1
- #import "PdfCanvasDocumentPool.h"
2
-
3
- #import "PdfCanvasPreflight.h"
4
-
5
- const NSInteger kPdfCanvasMaxPoolSize = 4;
6
-
7
- /* ------------------------------------------------------------------ *
8
- * Private surfaces
9
- * ------------------------------------------------------------------ */
10
-
11
- @interface PdfCanvasDocumentLease ()
12
- /**
13
- * @param document retained by the lease and released on dealloc, which is what
14
- * makes close-while-leased safe — see the pool's header note. The caller
15
- * keeps its own reference and must balance its own create.
16
- */
17
- - (instancetype)initWithDocument:(CGPDFDocumentRef)document
18
- fileURL:(nullable NSURL *)fileURL
19
- data:(nullable NSData *)data
20
- password:(nullable NSString *)password;
21
- @end
22
-
23
- @interface PdfCanvasDocumentPool ()
24
- + (NSInteger)clampSize:(NSInteger)size;
25
- + (nullable instancetype)poolWithLeases:
26
- (NSArray<PdfCanvasDocumentLease *> *)leases
27
- label:(NSString *)label
28
- error:(NSError *_Nullable *_Nullable)error;
29
- - (instancetype)initWithLeases:(NSArray<PdfCanvasDocumentLease *> *)leases
30
- pageCount:(NSInteger)pageCount;
31
- @end
32
-
33
- /* ------------------------------------------------------------------ *
34
- * Opening one document
35
- * ------------------------------------------------------------------ */
36
-
37
- /**
38
- * Unlocks a CGPDF document, or explains why it could not be.
39
- *
40
- * The three-way answer is the point, and CoreGraphics gives it cleanly where
41
- * Android has to infer it from a `SecurityException`:
42
- *
43
- * - not encrypted, or encrypted with an EMPTY user password (owner-password-only
44
- * files, which are common) — `CGPDFDocumentIsUnlocked` is already true and
45
- * there is nothing to do. Refusing these because `IsEncrypted` is true would
46
- * reject a large share of real documents that every reader opens.
47
- * - encrypted, password supplied, `CGPDFDocumentUnlockWithPassword` false →
48
- * `password-incorrect`.
49
- * - encrypted, no password → `password-required`.
50
- *
51
- * Note what this backend can do that Android cannot do at all: `PdfRendererPool`
52
- * REJECTS a non-nil password with `unsupported`, because
53
- * `android.graphics.pdf.LoadParams` — the only way to supply one — landed at API
54
- * 35. Encrypted documents work here.
55
- */
56
- static BOOL UnlockCGDocument(
57
- CGPDFDocumentRef document,
58
- NSString *_Nullable password,
59
- NSString *label,
60
- NSError *_Nullable *_Nullable error) {
61
- if (CGPDFDocumentIsUnlocked(document)) {
62
- return YES;
63
- }
64
- if (password.length > 0) {
65
- if (CGPDFDocumentUnlockWithPassword(document, password.UTF8String)) {
66
- return YES;
67
- }
68
- PdfCanvasFail(
69
- error, PdfCanvasErrorCodePasswordIncorrect,
70
- [NSString stringWithFormat:@"The password for %@ was rejected.", label],
71
- nil);
72
- return NO;
73
- }
74
- PdfCanvasFail(
75
- error, PdfCanvasErrorCodePasswordRequired,
76
- [NSString stringWithFormat:@"%@ is encrypted and needs a password.", label],
77
- nil);
78
- return NO;
79
- }
80
-
81
- /* ------------------------------------------------------------------ *
82
- * The lease
83
- * ------------------------------------------------------------------ */
84
-
85
- @implementation PdfCanvasDocumentLease {
86
- CGPDFDocumentRef _cgDocument;
87
- /** Whichever of the two the pool was opened from; exactly one is non-nil. */
88
- NSURL *_fileURL;
89
- NSData *_data;
90
- NSString *_password;
91
- PDFDocument *_pdfKitDocument;
92
- }
93
-
94
- - (instancetype)initWithDocument:(CGPDFDocumentRef)document
95
- fileURL:(nullable NSURL *)fileURL
96
- data:(nullable NSData *)data
97
- password:(nullable NSString *)password {
98
- self = [super init];
99
- if (self != nil) {
100
- _cgDocument = CGPDFDocumentRetain(document);
101
- _fileURL = fileURL;
102
- _data = data;
103
- _password = [password copy];
104
- }
105
- return self;
106
- }
107
-
108
- - (void)dealloc {
109
- if (_cgDocument != NULL) {
110
- CGPDFDocumentRelease(_cgDocument);
111
- _cgDocument = NULL;
112
- }
113
- }
114
-
115
- - (CGPDFDocumentRef)cgDocument {
116
- return _cgDocument;
117
- }
118
-
119
- - (nullable PDFDocument *)pdfKitDocumentWithError:
120
- (NSError *_Nullable *_Nullable)error {
121
- if (_pdfKitDocument != nil) {
122
- return _pdfKitDocument;
123
- }
124
-
125
- PDFDocument *document = _fileURL != nil
126
- ? [[PDFDocument alloc] initWithURL:_fileURL]
127
- : [[PDFDocument alloc] initWithData:_data];
128
- if (document == nil) {
129
- // CoreGraphics accepted this file and PDFKit did not, which does happen — the
130
- // two have separate parsers. Named as such rather than reported as a generic
131
- // failure, because the actionable answer is "ask for this raster without
132
- // annotations", and only a caller can decide that.
133
- return PdfCanvasFail(
134
- error, PdfCanvasErrorCodeCorrupt,
135
- @"PDFKit refused a document CoreGraphics accepted, so annotations "
136
- @"cannot be drawn for it. Request the raster with annotations: false to "
137
- @"use the CoreGraphics path instead.",
138
- nil);
139
- }
140
- if (document.isLocked) {
141
- if (_password.length > 0) {
142
- if (![document unlockWithPassword:_password]) {
143
- return PdfCanvasFail(
144
- error, PdfCanvasErrorCodePasswordIncorrect,
145
- @"PDFKit rejected the password, though CoreGraphics accepted it.",
146
- nil);
147
- }
148
- } else {
149
- return PdfCanvasFail(
150
- error, PdfCanvasErrorCodePasswordRequired,
151
- @"PDFKit reports this document as locked and no password was given.",
152
- nil);
153
- }
154
- }
155
- _pdfKitDocument = document;
156
- return document;
157
- }
158
-
159
- @end
160
-
161
- /* ------------------------------------------------------------------ *
162
- * The pool
163
- * ------------------------------------------------------------------ */
164
-
165
- @implementation PdfCanvasDocumentPool {
166
- NSMutableArray<PdfCanvasDocumentLease *> *_free;
167
- /** Guards `_free` and `_closed`. Never held across a render or an open. */
168
- NSLock *_lock;
169
- /** Counts available lanes, so `lease` blocks rather than spins. */
170
- dispatch_semaphore_t _available;
171
- NSInteger _pageCount;
172
- NSInteger _size;
173
- BOOL _closed;
174
- }
175
-
176
- + (NSInteger)clampSize:(NSInteger)size {
177
- if (size < 1) return 1;
178
- if (size > kPdfCanvasMaxPoolSize) return kPdfCanvasMaxPoolSize;
179
- return size;
180
- }
181
-
182
- + (nullable instancetype)openFileURL:(NSURL *)url
183
- password:(nullable NSString *)password
184
- size:(NSInteger)size
185
- error:(NSError *_Nullable *_Nullable)error {
186
- if (![PdfCanvasPreflight checkFileURL:url error:error]) {
187
- return nil;
188
- }
189
- const NSInteger lanes = [self clampSize:size];
190
- NSMutableArray<PdfCanvasDocumentLease *> *leases =
191
- [NSMutableArray arrayWithCapacity:(NSUInteger)lanes];
192
- NSString *label = url.lastPathComponent.length > 0 ? url.lastPathComponent
193
- : url.path;
194
-
195
- for (NSInteger i = 0; i < lanes; i++) {
196
- CGPDFDocumentRef document =
197
- CGPDFDocumentCreateWithURL((__bridge CFURLRef)url);
198
- if (document == NULL) {
199
- // Preflight already ruled out "missing", "unreadable", "empty" and "no
200
- // header", so what is left is structure CoreGraphics could not parse.
201
- PdfCanvasFail(
202
- error, PdfCanvasErrorCodeCorrupt,
203
- [NSString stringWithFormat:
204
- @"CoreGraphics could not parse %@ — it has a %%PDF- "
205
- @"header but no usable cross-reference table or page "
206
- @"tree.",
207
- label],
208
- nil);
209
- return nil;
210
- }
211
- const BOOL unlocked = UnlockCGDocument(document, password, label, error);
212
- if (!unlocked) {
213
- CGPDFDocumentRelease(document);
214
- return nil;
215
- }
216
- [leases addObject:[[PdfCanvasDocumentLease alloc] initWithDocument:document
217
- fileURL:url
218
- data:nil
219
- password:password]];
220
- // The lease retained it; this balances the create above.
221
- CGPDFDocumentRelease(document);
222
- }
223
-
224
- return [self poolWithLeases:leases label:label error:error];
225
- }
226
-
227
- + (nullable instancetype)openData:(NSData *)data
228
- password:(nullable NSString *)password
229
- size:(NSInteger)size
230
- error:(NSError *_Nullable *_Nullable)error {
231
- if (![PdfCanvasPreflight checkData:data error:error]) {
232
- return nil;
233
- }
234
- const NSInteger lanes = [self clampSize:size];
235
- NSMutableArray<PdfCanvasDocumentLease *> *leases =
236
- [NSMutableArray arrayWithCapacity:(NSUInteger)lanes];
237
-
238
- for (NSInteger i = 0; i < lanes; i++) {
239
- // One provider per lane over the SAME immutable NSData. The provider retains
240
- // the data, so the bytes outlive this scope, and nothing writes to them.
241
- CGDataProviderRef provider =
242
- CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
243
- if (provider == NULL) {
244
- PdfCanvasFail(error, PdfCanvasErrorCodeOutOfMemory,
245
- @"Could not wrap the document bytes in a CGDataProvider.",
246
- nil);
247
- return nil;
248
- }
249
- CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
250
- CGDataProviderRelease(provider);
251
- if (document == NULL) {
252
- PdfCanvasFail(
253
- error, PdfCanvasErrorCodeCorrupt,
254
- @"CoreGraphics could not parse the document bytes — they carry a "
255
- @"%PDF- header but no usable cross-reference table or page tree.",
256
- nil);
257
- return nil;
258
- }
259
- const BOOL unlocked =
260
- UnlockCGDocument(document, password, @"the document", error);
261
- if (!unlocked) {
262
- CGPDFDocumentRelease(document);
263
- return nil;
264
- }
265
- [leases addObject:[[PdfCanvasDocumentLease alloc] initWithDocument:document
266
- fileURL:nil
267
- data:data
268
- password:password]];
269
- CGPDFDocumentRelease(document);
270
- }
271
-
272
- return [self poolWithLeases:leases label:@"the document" error:error];
273
- }
274
-
275
- + (nullable instancetype)poolWithLeases:
276
- (NSArray<PdfCanvasDocumentLease *> *)leases
277
- label:(NSString *)label
278
- error:(NSError *_Nullable *_Nullable)error {
279
- const size_t pages =
280
- CGPDFDocumentGetNumberOfPages(leases.firstObject.cgDocument);
281
- if (pages == 0) {
282
- // Reachable for a document that parsed but has an empty or unreadable page
283
- // tree, and for one that is still locked — `CGPDFDocumentGetNumberOfPages`
284
- // reports 0 rather than failing. The unlock above has already ruled the
285
- // second out, so this is the first.
286
- return PdfCanvasFail(
287
- error, PdfCanvasErrorCodeCorrupt,
288
- [NSString stringWithFormat:@"%@ reports 0 pages.", label], nil);
289
- }
290
- return [[self alloc] initWithLeases:leases pageCount:(NSInteger)pages];
291
- }
292
-
293
- - (instancetype)initWithLeases:(NSArray<PdfCanvasDocumentLease *> *)leases
294
- pageCount:(NSInteger)pageCount {
295
- self = [super init];
296
- if (self != nil) {
297
- _free = [leases mutableCopy];
298
- _lock = [[NSLock alloc] init];
299
- _available = dispatch_semaphore_create((long)leases.count);
300
- _pageCount = pageCount;
301
- _size = (NSInteger)leases.count;
302
- }
303
- return self;
304
- }
305
-
306
- - (NSInteger)pageCount {
307
- return _pageCount;
308
- }
309
-
310
- - (NSInteger)size {
311
- return _size;
312
- }
313
-
314
- - (BOOL)isClosed {
315
- [_lock lock];
316
- const BOOL closed = _closed;
317
- [_lock unlock];
318
- return closed;
319
- }
320
-
321
- - (nullable PdfCanvasDocumentLease *)leaseWithError:
322
- (NSError *_Nullable *_Nullable)error {
323
- if (self.isClosed) {
324
- return PdfCanvasFail(error, PdfCanvasErrorCodeBackendFailure,
325
- @"Document handle is closed.", nil);
326
- }
327
- dispatch_semaphore_wait(_available, DISPATCH_TIME_FOREVER);
328
-
329
- [_lock lock];
330
- PdfCanvasDocumentLease *lease = _free.lastObject;
331
- if (lease != nil) {
332
- [_free removeLastObject];
333
- }
334
- const BOOL closed = _closed;
335
- [_lock unlock];
336
-
337
- if (closed || lease == nil) {
338
- // `close` raced us and emptied the free list. The count consumed above is
339
- // handed back so a second waiter is not left blocked forever.
340
- dispatch_semaphore_signal(_available);
341
- return PdfCanvasFail(error, PdfCanvasErrorCodeBackendFailure,
342
- @"Document handle was closed while waiting for a lane.",
343
- nil);
344
- }
345
- return lease;
346
- }
347
-
348
- - (void)returnLease:(nullable PdfCanvasDocumentLease *)lease {
349
- if (lease == nil) {
350
- return;
351
- }
352
- [_lock lock];
353
- // A lease returned to a CLOSED pool is dropped rather than re-queued, which is
354
- // where its last reference goes and therefore where the parsed document is
355
- // finally freed. The semaphore is still signalled: an unbalanced wait would
356
- // wedge any render that raced the close.
357
- const BOOL requeue = !_closed;
358
- if (requeue) {
359
- [_free addObject:lease];
360
- }
361
- [_lock unlock];
362
- dispatch_semaphore_signal(_available);
363
- }
364
-
365
- - (void)close {
366
- // Held OUTSIDE the lock, deliberately: `dropped` keeps the leases alive past
367
- // the unlock so that releasing several parsed ARCH-E documents does not happen
368
- // while a render worker might be waiting on the same lock. Assigning nil after
369
- // the unlock is what forces that — ARC is free to shorten a local's lifetime to
370
- // its last use, so relying on end-of-scope here would be relying on the
371
- // optimiser not to do its job.
372
- NSArray<PdfCanvasDocumentLease *> *dropped = nil;
373
- [_lock lock];
374
- if (_closed) {
375
- [_lock unlock];
376
- return;
377
- }
378
- _closed = YES;
379
- // Dropping the FREE leases releases their documents. A lease that is currently
380
- // OUT is untouched: the render worker owns it, and it is freed when
381
- // `returnLease:` declines to re-queue it. Non-blocking, and with no window —
382
- // see the header.
383
- dropped = [_free copy];
384
- [_free removeAllObjects];
385
- [_lock unlock];
386
- dropped = nil;
387
- }
388
-
389
- @end
@@ -1,57 +0,0 @@
1
- #import "PdfCanvasErrorCode.h"
2
-
3
- NSErrorDomain const PdfCanvasErrorDomain = @"tools.reekon.pdfcanvas";
4
-
5
- NSString *PdfCanvasErrorCodeWire(PdfCanvasErrorCode code) {
6
- switch (code) {
7
- case PdfCanvasErrorCodeNotFound:
8
- return @"not-found";
9
- case PdfCanvasErrorCodePasswordRequired:
10
- return @"password-required";
11
- case PdfCanvasErrorCodePasswordIncorrect:
12
- return @"password-incorrect";
13
- case PdfCanvasErrorCodeCorrupt:
14
- return @"corrupt";
15
- case PdfCanvasErrorCodeUnsupported:
16
- return @"unsupported";
17
- case PdfCanvasErrorCodeCancelled:
18
- return @"cancelled";
19
- case PdfCanvasErrorCodeOutOfMemory:
20
- return @"out-of-memory";
21
- case PdfCanvasErrorCodeBackendFailure:
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. This exists only because the enum is an
26
- // NSInteger and a caller could cast anything into it.
27
- return @"backend-failure";
28
- }
29
-
30
- NSError *PdfCanvasMakeError(
31
- PdfCanvasErrorCode code, NSString *message, NSError *_Nullable underlying) {
32
- NSMutableDictionary<NSErrorUserInfoKey, id> *info =
33
- [NSMutableDictionary dictionaryWithCapacity:3];
34
- info[NSLocalizedDescriptionKey] = message ?: @"PDF Canvas failed.";
35
- if (underlying != nil) {
36
- info[NSUnderlyingErrorKey] = underlying;
37
- }
38
- return [NSError errorWithDomain:PdfCanvasErrorDomain code:code userInfo:info];
39
- }
40
-
41
- PdfCanvasErrorCode PdfCanvasErrorCodeOf(NSError *error) {
42
- if (![error.domain isEqualToString:PdfCanvasErrorDomain]) {
43
- return PdfCanvasErrorCodeBackendFailure;
44
- }
45
- return (PdfCanvasErrorCode)error.code;
46
- }
47
-
48
- id _Nullable PdfCanvasFail(
49
- NSError *_Nullable *_Nullable out,
50
- PdfCanvasErrorCode code,
51
- NSString *message,
52
- NSError *_Nullable underlying) {
53
- if (out != NULL) {
54
- *out = PdfCanvasMakeError(code, message, underlying);
55
- }
56
- return nil;
57
- }
@@ -1,44 +0,0 @@
1
- #import "PdfCanvasPageGeometry.h"
2
-
3
- NSInteger PdfCanvasNormalizeRotation(NSInteger degrees) {
4
- NSInteger wrapped = degrees % 360;
5
- if (wrapped < 0) {
6
- wrapped += 360;
7
- }
8
- return (wrapped % 90 == 0) ? wrapped : 0;
9
- }
10
-
11
- @implementation PdfCanvasPageGeometry
12
-
13
- - (instancetype)initWithIndex:(NSInteger)index
14
- cropBox:(CGRect)cropBox
15
- rotationDegrees:(NSInteger)rotationDegrees {
16
- self = [super init];
17
- if (self != nil) {
18
- _index = index;
19
- _cropBox = cropBox;
20
- _rotationDegrees = PdfCanvasNormalizeRotation(rotationDegrees);
21
- // The one line of arithmetic that makes this type post-rotation. See the
22
- // header: CoreGraphics reports the box unrotated, so nobody else does it.
23
- const BOOL quarterTurn = (_rotationDegrees == 90 || _rotationDegrees == 270);
24
- _widthPoints = quarterTurn ? cropBox.size.height : cropBox.size.width;
25
- _heightPoints = quarterTurn ? cropBox.size.width : cropBox.size.height;
26
- }
27
- return self;
28
- }
29
-
30
- - (NSString *)description {
31
- // The rect is formatted by hand rather than with `NSStringFromCGRect`, which
32
- // lives in UIKit's UIGeometry.h and therefore does not exist on macOS — where
33
- // this whole target is compiled by `ios/Package.swift` so that `swift test`
34
- // can prove it without a device. Nothing in PdfCanvasCore may reach for UIKit.
35
- return [NSString
36
- stringWithFormat:@"PdfCanvasPageGeometry{%ld %gx%gpt rot=%ld "
37
- @"crop=(%g,%g %gx%g)}",
38
- (long)_index, _widthPoints, _heightPoints,
39
- (long)_rotationDegrees, _cropBox.origin.x,
40
- _cropBox.origin.y, _cropBox.size.width,
41
- _cropBox.size.height];
42
- }
43
-
44
- @end
@@ -1,82 +0,0 @@
1
- #import "PdfCanvasPageTransform.h"
2
-
3
- #import "PdfCanvasPageGeometry.h"
4
-
5
- /*
6
- * Every case below was derived by mapping the page's four corners and then
7
- * checked against a rendered fixture, so the derivation is worth keeping next to
8
- * the code.
9
- *
10
- * Write the unrotated page in y-DOWN box-local coordinates:
11
- *
12
- * u = px - bx in [0, bw]
13
- * v = (by + bh) - py in [0, bh]
14
- *
15
- * `/Rotate r` means "rotate the page r degrees CLOCKWISE when displayed", so the
16
- * displayed image is the (u, v) image turned clockwise by r. Turning an image
17
- * clockwise by 90 sends a source pixel at (u, v) to (H_src - v, u), where H_src is
18
- * the source HEIGHT, which is the rotated image's WIDTH. Substituting u and v
19
- * back gives each case:
20
- *
21
- * r = 0 doc = (u, v) = (px - bx, by + bh - py)
22
- * r = 90 doc = (bh - v, u) = (py - by, px - bx)
23
- * r = 180 doc = (bw - u, bh - v) = (bw + bx - px, py - by)
24
- * r = 270 doc = (v, bw - u) = (by + bh - py, bw + bx - px)
25
- *
26
- * CGAffineTransform applies as `x' = a*x + c*y + tx`, `y' = b*x + d*y + ty`, so
27
- * `a` and `b` are the px column and `c` and `d` are the py column. Reading the
28
- * four expressions off that way is what the literals below are.
29
- *
30
- * Corner checks, for a 200 x 100 box at the origin:
31
- *
32
- * r = 90, PDF bottom-left (0, 0) -> doc (0, 0) displayed TOP-LEFT
33
- * r = 90, PDF bottom-right (200, 0) -> doc (0, 200) displayed BOTTOM-LEFT
34
- * r = 180, PDF bottom-left (0, 0) -> doc (200, 0) displayed TOP-RIGHT
35
- * r = 270, PDF top-left (0, 100) -> doc (0, 200) displayed BOTTOM-LEFT
36
- *
37
- * All four match what turning a sheet of paper does, and all four are asserted
38
- * individually in the Swift suite rather than being inferred from a render.
39
- */
40
- CGAffineTransform PdfCanvasPdfToDocTransform(
41
- CGRect cropBox, NSInteger rotationDegrees) {
42
- const CGFloat bx = CGRectGetMinX(cropBox);
43
- const CGFloat by = CGRectGetMinY(cropBox);
44
- const CGFloat bw = CGRectGetWidth(cropBox);
45
- const CGFloat bh = CGRectGetHeight(cropBox);
46
-
47
- switch (PdfCanvasNormalizeRotation(rotationDegrees)) {
48
- case 90:
49
- // doc_x = py - by ; doc_y = px - bx
50
- return CGAffineTransformMake(0, 1, 1, 0, -by, -bx);
51
- case 180:
52
- // doc_x = (bw + bx) - px ; doc_y = py - by
53
- return CGAffineTransformMake(-1, 0, 0, 1, bw + bx, -by);
54
- case 270:
55
- // doc_x = (by + bh) - py ; doc_y = (bw + bx) - px
56
- return CGAffineTransformMake(0, -1, -1, 0, by + bh, bw + bx);
57
- case 0:
58
- default:
59
- // doc_x = px - bx ; doc_y = (by + bh) - py
60
- return CGAffineTransformMake(1, 0, 0, -1, -bx, by + bh);
61
- }
62
- }
63
-
64
- CGAffineTransform PdfCanvasDocToRasterTransform(
65
- double docX, double docY, double scale, NSInteger pixelHeight) {
66
- // The CG-op sequence this is the closed form of, for anyone checking it by
67
- // hand: translate(0, H); scale(1, -1); scale(s, s); translate(-docX, -docY).
68
- //
69
- // raster_x = (dx - docX) * s
70
- // raster_y = H - (dy - docY) * s
71
- return CGAffineTransformMake(
72
- (CGFloat)scale, 0, 0, (CGFloat)-scale, (CGFloat)(-docX * scale),
73
- (CGFloat)((double)pixelHeight + docY * scale));
74
- }
75
-
76
- CGAffineTransform PdfCanvasDocToPdfKitTransform(double pageHeightPoints) {
77
- // A pure y-flip through the page's own height, and nothing else: PDFKit
78
- // applies the rotation and the box origin itself. See the header for the
79
- // measurement that establishes both halves, and for why the height cannot come
80
- // from `-[PDFPage boundsForBox:]`.
81
- return CGAffineTransformMake(1, 0, 0, -1, 0, (CGFloat)pageHeightPoints);
82
- }
@@ -1,37 +0,0 @@
1
- #import "PdfCanvasPixelSink.h"
2
-
3
- @implementation PdfCanvasHeapSink {
4
- NSMutableData *_data;
5
- BOOL _reserved;
6
- }
7
-
8
- - (NSMutableData *)data {
9
- return _data;
10
- }
11
-
12
- - (nullable void *)pdfCanvasReserveBytes:(NSUInteger)length {
13
- if (_reserved || length == 0) {
14
- return NULL;
15
- }
16
- _reserved = YES;
17
- // `dataWithLength:` is documented to zero the bytes, which is the sink
18
- // contract. It returns nil rather than raising when the allocation cannot be
19
- // had, and NULL here is what the rasterizer maps to `out-of-memory`.
20
- _data = [NSMutableData dataWithLength:length];
21
- if (_data == nil) {
22
- return NULL;
23
- }
24
- return _data.mutableBytes;
25
- }
26
-
27
- - (int64_t)pdfCanvasPublish {
28
- // 0 is "heap-backed" on the wire — see `PdfCanvasRasterPixels.slot`. This sink
29
- // has no identifier because `data` is how its bytes are reached.
30
- return 0;
31
- }
32
-
33
- - (void)pdfCanvasAbandon {
34
- _data = nil;
35
- }
36
-
37
- @end