@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +77 -40
  4. package/README.md +218 -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/rasterizer/index.native.d.ts +8 -12
  19. package/dist/rasterizer/index.native.js +15 -21
  20. package/dist/rasterizer/native-bridge.d.ts +51 -75
  21. package/dist/rasterizer/native-bridge.js +49 -33
  22. package/dist/rasterizer/native.d.ts +45 -0
  23. package/dist/rasterizer/native.js +123 -0
  24. package/dist/react/usePdfDocument.d.ts +5 -5
  25. package/dist/react/usePdfLayer.d.ts +10 -1
  26. package/dist/react/usePdfLayer.js +10 -2
  27. package/dist/testing/scenes.d.ts +3 -3
  28. package/dist/testing/scenes.js +4 -4
  29. package/dist/types.d.ts +36 -42
  30. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  31. package/native/.clangd +5 -0
  32. package/native/CMakeLists.txt +89 -0
  33. package/native/core/include/pdfcanvas/document.h +90 -0
  34. package/native/core/include/pdfcanvas/error.h +47 -0
  35. package/native/core/include/pdfcanvas/jsi.h +18 -0
  36. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  37. package/native/core/include/pdfcanvas/library.h +28 -0
  38. package/native/core/include/pdfcanvas/pixels.h +92 -0
  39. package/native/core/include/pdfcanvas/service.h +87 -0
  40. package/native/core/include/pdfcanvas/slots.h +113 -0
  41. package/native/core/include/pdfcanvas/types.h +110 -0
  42. package/native/core/pdfcanvas-core.cmake +22 -0
  43. package/native/core/src/document.cpp +516 -0
  44. package/native/core/src/error.cpp +29 -0
  45. package/native/core/src/jsi.cpp +66 -0
  46. package/native/core/src/library.cpp +52 -0
  47. package/native/core/src/pixels.cpp +45 -0
  48. package/native/core/src/service.cpp +156 -0
  49. package/native/core/src/slots.cpp +139 -0
  50. package/native/tests/fixtures.cpp +451 -0
  51. package/native/tests/fixtures.h +96 -0
  52. package/native/tests/harness.h +122 -0
  53. package/native/tests/main.cpp +64 -0
  54. package/native/tests/pixels_util.h +126 -0
  55. package/native/tests/test_document.cpp +485 -0
  56. package/native/tests/test_service.cpp +130 -0
  57. package/native/tests/test_slots.cpp +250 -0
  58. package/package.json +27 -7
  59. package/react-native.config.js +5 -2
  60. package/scripts/fetch-pdfium.mjs +559 -0
  61. package/scripts/pdfium-manifest.json +48 -0
  62. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  63. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  64. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  65. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  66. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  67. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  68. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  69. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  72. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  73. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  74. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  75. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  76. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  77. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  78. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  79. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  80. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  81. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  82. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  83. package/dist/rasterizer/android.d.ts +0 -121
  84. package/dist/rasterizer/android.js +0 -126
  85. package/dist/rasterizer/ios.d.ts +0 -84
  86. package/dist/rasterizer/ios.js +0 -155
  87. package/ios/Package.swift +0 -58
  88. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  89. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  90. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  91. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  92. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  93. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  94. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  95. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  98. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  99. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  100. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  101. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  102. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  103. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  104. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  105. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  109. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  110. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  111. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  112. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  113. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  114. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  115. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  116. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  117. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  118. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  119. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  120. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  121. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
@@ -1,122 +0,0 @@
1
- #import <CoreGraphics/CoreGraphics.h>
2
- #import <Foundation/Foundation.h>
3
- #import <PDFKit/PDFKit.h>
4
-
5
- #import "PdfCanvasErrorCode.h"
6
-
7
- NS_ASSUME_NONNULL_BEGIN
8
-
9
- /** Ceiling on pooled documents. Mirrored by `MAX_POOL_SIZE` in `src/rasterizer/ios.ts`. */
10
- FOUNDATION_EXPORT const NSInteger kPdfCanvasMaxPoolSize;
11
-
12
- /**
13
- * One lane's worth of parsed document: a `CGPDFDocumentRef` and, once something
14
- * asks for annotations, a `PDFDocument`.
15
- *
16
- * THE PDFKit HALF IS LAZY, and that is the whole reason the two live in one
17
- * object. Every tile the controller asks for passes `annotations: NO` and goes
18
- * through CGPDF, so a `PDFDocument` opened eagerly at `open()` would parse a
19
- * second copy of an ARCH-E sheet that the common path never touches. It is opened
20
- * on the first `annotations: YES` render in this lane and kept, because a thumbnail
21
- * strip asks repeatedly.
22
- *
23
- * ONE LANE, ONE PARSE OF EACH. Neither `CGPDFDocumentRef` nor `PDFDocument` is
24
- * documented thread-safe, and sharing one across lanes would mean a lock this
25
- * library owns — which buys nothing over just leasing. Opening the file once per
26
- * lane is the same argument `PdfRendererPool` makes for opening afresh rather than
27
- * `dup(2)`-ing a descriptor: independent state is the invariant the code needs,
28
- * and one extra parse is what it costs.
29
- */
30
- @interface PdfCanvasDocumentLease : NSObject
31
-
32
- /** Never NULL for a live lease. Owned by this object and released on dealloc. */
33
- @property(nonatomic, readonly) CGPDFDocumentRef cgDocument;
34
-
35
- /**
36
- * The PDFKit document for this lane, opening it if this is the first ask.
37
- *
38
- * Fails with `corrupt` if PDFKit refuses a document CoreGraphics accepted, and
39
- * with `password-required` / `password-incorrect` if it cannot be unlocked — the
40
- * two engines keep separate locks, so the password has to be presented twice.
41
- */
42
- - (nullable PDFDocument *)pdfKitDocumentWithError:
43
- (NSError *_Nullable *_Nullable)error;
44
-
45
- - (instancetype)init NS_UNAVAILABLE;
46
-
47
- @end
48
-
49
- /**
50
- * A fixed set of parsed documents over the same bytes, leased one at a time.
51
- *
52
- * ------------------------------------------------------------------------
53
- * CLOSE-WHILE-LEASED IS SAFE HERE, AND ON ANDROID IT IS NOT.
54
- * ------------------------------------------------------------------------
55
- *
56
- * `PdfRendererPool.close()` closes every renderer including the ones currently
57
- * leased, and its own comment calls that "the only honest option": `close()` comes
58
- * from JS when a document unmounts, and blocking until an in-flight ARCH-D render
59
- * finishes is worse. It leaves a window.
60
- *
61
- * There is no window here, because CoreFoundation refcounts. A lease OWNS its
62
- * `CGPDFDocumentRef` and releases it in `dealloc`, so `close` dropping the pool's
63
- * references cannot free a document a render worker is still drawing from — the
64
- * worker holds the lease, and the last reference goes away when it returns the
65
- * lease to a closed pool. `close` is therefore non-blocking AND has no
66
- * use-after-free, which is strictly better than the Android behaviour rather than
67
- * a different trade.
68
- */
69
- @interface PdfCanvasDocumentPool : NSObject
70
-
71
- /**
72
- * @param password nil unless the document needs one. Both engines are unlocked
73
- * with it — CGPDF eagerly, PDFKit on first use — and a wrong one is
74
- * `password-incorrect` rather than `corrupt`.
75
- * @param size clamped to [1, `kPdfCanvasMaxPoolSize`].
76
- */
77
- + (nullable instancetype)openFileURL:(NSURL *)url
78
- password:(nullable NSString *)password
79
- size:(NSInteger)size
80
- error:(NSError *_Nullable *_Nullable)error
81
- NS_SWIFT_NAME(open(fileURL:password:size:));
82
-
83
- /**
84
- * Opens from bytes with no spool file.
85
- *
86
- * Worth contrasting with Android, where `PdfCanvasRasterizer.openBytes` MUST
87
- * write a temp file first because `PdfRenderer` needs a seekable descriptor and
88
- * reads through a seek-and-read callback. `CGPDFDocumentCreateWithProvider` over a
89
- * `CFData` needs no file at all, and every lane can share the same immutable
90
- * `NSData` because each gets its own provider and its own parse.
91
- */
92
- + (nullable instancetype)openData:(NSData *)data
93
- password:(nullable NSString *)password
94
- size:(NSInteger)size
95
- error:(NSError *_Nullable *_Nullable)error
96
- NS_SWIFT_NAME(open(data:password:size:));
97
-
98
- @property(nonatomic, readonly) NSInteger pageCount;
99
- /** How many renders can genuinely be in flight at once. */
100
- @property(nonatomic, readonly) NSInteger size;
101
- @property(nonatomic, readonly, getter=isClosed) BOOL closed;
102
-
103
- /**
104
- * Blocks until a lane is free.
105
- *
106
- * The wait is where a cancel most often lands — a superseded detail raster
107
- * typically queues behind a base raster — so the caller re-checks its signal on
108
- * the far side of this call.
109
- */
110
- - (nullable PdfCanvasDocumentLease *)leaseWithError:
111
- (NSError *_Nullable *_Nullable)error;
112
-
113
- /** Always call from a `@finally`-shaped path: a leaked lease starves every later render. */
114
- - (void)returnLease:(nullable PdfCanvasDocumentLease *)lease;
115
-
116
- - (void)close;
117
-
118
- - (instancetype)init NS_UNAVAILABLE;
119
-
120
- @end
121
-
122
- NS_ASSUME_NONNULL_END
@@ -1,80 +0,0 @@
1
- #import <Foundation/Foundation.h>
2
-
3
- NS_ASSUME_NONNULL_BEGIN
4
-
5
- /**
6
- * The eight failure kinds, mirroring `PdfErrorCode` in `src/types.ts` and
7
- * `PdfErrorCode.java` on Android.
8
- *
9
- * The WIRE STRINGS are the TypeScript union members verbatim. They are the
10
- * contract across the bridge: the binding rejects a promise with
11
- * `PdfCanvasErrorCodeWire()` and the TS side reconstructs a `PdfError` from it
12
- * with no translation table in between (`toPdfError` in
13
- * `src/rasterizer/native-bridge.ts`). Adding a member here without adding it
14
- * there produces a `PdfError` with a code no consumer can switch on, so the
15
- * lists have to move together — and there are now THREE of them, since Android
16
- * has its own copy.
17
- */
18
- typedef NS_ENUM(NSInteger, PdfCanvasErrorCode) {
19
- PdfCanvasErrorCodeNotFound = 0,
20
- PdfCanvasErrorCodePasswordRequired,
21
- PdfCanvasErrorCodePasswordIncorrect,
22
- PdfCanvasErrorCodeCorrupt,
23
- PdfCanvasErrorCodeUnsupported,
24
- PdfCanvasErrorCodeCancelled,
25
- PdfCanvasErrorCodeOutOfMemory,
26
- PdfCanvasErrorCodeBackendFailure,
27
- };
28
-
29
- /**
30
- * The `NSError` domain every failure in this library carries.
31
- *
32
- * `NSError` rather than an exception subclass, and that is not merely idiomatic:
33
- * Objective-C exceptions are not a control-flow mechanism ARC unwinds safely, and
34
- * this library allocates CoreGraphics objects on every path. Android can afford a
35
- * checked `PdfRasterException` because the JVM unwinds; here the `NSError **`
36
- * out-parameter is what keeps every `CGPDFDocumentRelease` reachable.
37
- */
38
- FOUNDATION_EXPORT NSErrorDomain const PdfCanvasErrorDomain;
39
-
40
- /** The TypeScript union member for a code. Never nil. */
41
- FOUNDATION_EXPORT NSString *PdfCanvasErrorCodeWire(PdfCanvasErrorCode code);
42
-
43
- /**
44
- * Builds an error in `PdfCanvasErrorDomain` whose `code` is the enum and whose
45
- * `localizedDescription` is `message`.
46
- *
47
- * The `underlying` error is always kept when there is one. A `corrupt` verdict
48
- * that turns out to be a truncated download is only diagnosable from whatever
49
- * CoreGraphics or the file system said first.
50
- */
51
- FOUNDATION_EXPORT NSError *PdfCanvasMakeError(
52
- PdfCanvasErrorCode code,
53
- NSString *message,
54
- NSError *_Nullable underlying);
55
-
56
- /**
57
- * Reads a code back out of an error.
58
- *
59
- * Anything not in `PdfCanvasErrorDomain` — an `NSError` from `NSFileManager`, say
60
- * — reads as `PdfCanvasErrorCodeBackendFailure`, because a foreign error reaching
61
- * a caller that wanted a code is a mapping this library forgot rather than a new
62
- * kind of failure.
63
- */
64
- FOUNDATION_EXPORT PdfCanvasErrorCode PdfCanvasErrorCodeOf(NSError *error);
65
-
66
- /**
67
- * Assigns to an `NSError **` out-parameter if the caller supplied one, and
68
- * returns nil so a failing method can `return PdfCanvasFail(error, ...)`.
69
- *
70
- * Exists because the alternative — `if (error) *error = ...; return nil;` — is
71
- * three lines that get shortened to two on a bad day, and the line that gets
72
- * dropped is the assignment.
73
- */
74
- FOUNDATION_EXPORT id _Nullable PdfCanvasFail(
75
- NSError *_Nullable *_Nullable out,
76
- PdfCanvasErrorCode code,
77
- NSString *message,
78
- NSError *_Nullable underlying);
79
-
80
- NS_ASSUME_NONNULL_END
@@ -1,79 +0,0 @@
1
- #import <CoreGraphics/CoreGraphics.h>
2
- #import <Foundation/Foundation.h>
3
-
4
- NS_ASSUME_NONNULL_BEGIN
5
-
6
- /**
7
- * A page's intrinsic size in PDF points, mirroring `PageGeometry` in
8
- * `src/types.ts`.
9
- *
10
- * POST-ROTATION, per that contract: a 612x792 page carrying `/Rotate 90` reports
11
- * 792x612. Unlike Android — where `PdfRenderer.Page.getWidth()` happens to
12
- * already be post-rotation and there is no way to read `/Rotate` at all below
13
- * API 35 — this backend computes it, because CoreGraphics reports the raw box and
14
- * the raw `/Rotate` separately and applies neither.
15
- *
16
- * MEASURED, both halves: `CGPDFPageGetBoxRect(page, kCGPDFCropBox)` on a
17
- * `/Rotate 90` letter page returns `(0, 0, 612, 792)` — unrotated — and
18
- * `CGPDFPageGetRotationAngle` returns 90. So the swap below is this library's
19
- * arithmetic and is pinned by `pageGeometryIsPostRotation` in the Swift suite.
20
- *
21
- * `rotationDegrees` reports the page's REAL `/Rotate`, which is what the web
22
- * PDFium backend does and the opposite of what Android does (it reports 0,
23
- * because it cannot read the value). `PageGeometry.rotation` is informational to
24
- * this package — nothing applies a further rotation on the strength of it — and
25
- * `src/rasterizer/native-bridge.ts` documents the divergence at the field.
26
- *
27
- * `cropBox` is the box the raster is defined against: `kCGPDFCropBox`
28
- * INTERSECTED with `kCGPDFMediaBox`, in PDF user space (y-up), origin NOT
29
- * normalised away. It is exposed rather than kept private because it is the only
30
- * input `PdfCanvasPdfToDocTransform` needs, and that is what makes the transform
31
- * testable without a document.
32
- */
33
- @interface PdfCanvasPageGeometry : NSObject
34
-
35
- @property(nonatomic, readonly) NSInteger index;
36
- /** Width in PDF points, after `/Rotate`. */
37
- @property(nonatomic, readonly) CGFloat widthPoints;
38
- /** Height in PDF points, after `/Rotate`. */
39
- @property(nonatomic, readonly) CGFloat heightPoints;
40
- /** The page's `/Rotate`, normalised to 0, 90, 180 or 270. */
41
- @property(nonatomic, readonly) NSInteger rotationDegrees;
42
- /** CropBox ∩ MediaBox, in PDF user space. NOT rotated, NOT origin-normalised. */
43
- @property(nonatomic, readonly) CGRect cropBox;
44
-
45
- - (instancetype)initWithIndex:(NSInteger)index
46
- cropBox:(CGRect)cropBox
47
- rotationDegrees:(NSInteger)rotationDegrees NS_DESIGNATED_INITIALIZER;
48
- - (instancetype)init NS_UNAVAILABLE;
49
-
50
- @end
51
-
52
- /**
53
- * Normalises any `/Rotate` value to 0, 90, 180 or 270.
54
- *
55
- * The spec allows only multiples of 90 and real files carry negatives (`-90`) and
56
- * out-of-range values (`450`). A value that is not a multiple of 90 after
57
- * normalisation is reported as 0 rather than rounded: rounding 45 to either
58
- * neighbour is a guess, and drawing the page unrotated is the one answer that is
59
- * merely wrong rather than wrong AND surprising.
60
- */
61
- FOUNDATION_EXPORT NSInteger PdfCanvasNormalizeRotation(NSInteger degrees);
62
-
63
- /**
64
- * CropBox ∩ MediaBox for a page, in PDF user space.
65
- *
66
- * `CGPDFPageGetBoxRect(page, kCGPDFCropBox)` already falls back to the media box
67
- * when there is no crop box, so the common case is a no-op — but it does NOT do
68
- * the intersection the PDF spec requires ("the crop box shall be intersected with
69
- * the media box"), and a file whose crop box overhangs its media box would
70
- * otherwise produce a page larger than the paper, with every rect the layout
71
- * function derives from it wrong by the overhang.
72
- *
73
- * A degenerate intersection falls back to the MEDIA box rather than to an empty
74
- * rect. A zero-area page would make `fitScale` non-finite and the planner would
75
- * emit nothing at all, which presents as a blank document with no error anywhere.
76
- */
77
- FOUNDATION_EXPORT CGRect PdfCanvasEffectiveCropBox(CGPDFPageRef page);
78
-
79
- NS_ASSUME_NONNULL_END
@@ -1,129 +0,0 @@
1
- #import <CoreGraphics/CoreGraphics.h>
2
- #import <Foundation/Foundation.h>
3
-
4
- NS_ASSUME_NONNULL_BEGIN
5
-
6
- /**
7
- * The three affine transforms this backend is built out of.
8
- *
9
- * THEIR OWN FILE, WITH NO DOCUMENT IN SIGHT, because they are the part that can
10
- * be wrong in a way that looks plausible. A rotation applied the wrong way round,
11
- * a crop-box origin left in, a y-flip that lands half a pixel off — none of those
12
- * throws, and all of them are checkable by mapping four corners with no PDF, no
13
- * bitmap and no CoreGraphics state. `PdfCanvasTransformTests.swift` does exactly
14
- * that, and only then does `PdfCanvasRasterizerTests.swift` check that a rendered
15
- * page agrees.
16
- *
17
- * ------------------------------------------------------------------------
18
- * THE THREE SPACES
19
- * ------------------------------------------------------------------------
20
- *
21
- * PDF USER SPACE — y-UP, origin wherever the page's boxes say. This is the space
22
- * `CGContextDrawPDFPage` draws in, and it applies neither the crop box's origin
23
- * nor `/Rotate` (both MEASURED — see the notes on each function below).
24
- *
25
- * DOC SPACE — y-DOWN, origin at the ROTATED page's top-left, units of PDF points.
26
- * This is the package's one coordinate system (`src/types.ts`: "PDF points,
27
- * y-down, origin at the page's top-left, exactly what PDFium's
28
- * CPDF_Page::GetDisplayMatrix() emits"). Every rect that crosses the JS boundary
29
- * is in it.
30
- *
31
- * RASTER SPACE — the `CGBitmapContext`'s own: y-UP from the bottom-left, units of
32
- * device pixels, extent exactly the request's `pixelWidth` x `pixelHeight`.
33
- *
34
- * ------------------------------------------------------------------------
35
- * WHY THE PIXEL HEIGHT IN THE FLIP IS THE ROUNDED ONE, AND WHY THAT IS EXACT
36
- * ------------------------------------------------------------------------
37
- *
38
- * `PdfCanvasDocToRasterTransform` flips through `pixelHeight`, which is
39
- * `round(docHeight * scale)` and therefore up to half a pixel away from
40
- * `docHeight * scale`. That looks like it would make two rasters requested at the
41
- * same scale disagree where they overlap — the property the cache and the
42
- * overlapping detail pieces depend on, and the one `tileMatrix` on Android has a
43
- * long comment about.
44
- *
45
- * It does not, and the cancellation is exact. A doc point `dy` lands at raster
46
- * y = `H - (dy - docY) * scale`, and a reader indexing rows from the TOP sees
47
- * `H - y` = `(dy - docY) * scale`. `H` cancels. So the rounding decides only how
48
- * much of the far edge exists at all — at most half a pixel, left showing the
49
- * background fill, exactly as on Android — and never where content lands relative
50
- * to the raster's origin. `tileTransformsAgreeWhereTheyOverlap` pins it with two
51
- * rects whose pixel heights round in opposite directions.
52
- *
53
- * `scale` is likewise used EXACTLY as requested and never re-derived from
54
- * `pixelWidth / docWidth`, for the reason `PdfCanvasRasterizer.java` gives: it is
55
- * what makes two rasters at the same scale agree pixel-for-pixel over their
56
- * overlap. Re-deriving would instead make each raster cover its doc rect to the
57
- * last sub-pixel, at the cost of every pair of neighbours being drawn at
58
- * imperceptibly different scales.
59
- */
60
-
61
- /**
62
- * PDF user space -> doc space.
63
- *
64
- * `cropBox` is CropBox ∩ MediaBox in PDF user space, origin NOT normalised away;
65
- * `rotationDegrees` is the page's `/Rotate`, already normalised to 0/90/180/270.
66
- *
67
- * MEASURED, and this function exists because both halves came out "no":
68
- * `CGContextDrawPDFPage` applies NEITHER the crop box origin NOR `/Rotate`. On a
69
- * `/Rotate 90` letter page, drawing with the rotation in this transform puts the
70
- * marker at the displayed top-right (correct) and drawing without it puts it at
71
- * the top-left, clipped (wrong). On a page with MediaBox `0 0 400 400` and CropBox
72
- * `100 150 300 350`, the `-cropBox.minX` term is what puts the crop box's own
73
- * top-left corner at doc (0, 0).
74
- *
75
- * Every one of the four cases has determinant -1, and that is not a bug: doc space
76
- * is y-down while PDF space is y-up, so a rotation in the DISPLAY sense is a
77
- * rotation composed with a reflection in raw coordinates. A case that came out
78
- * +1 would be the one to distrust.
79
- */
80
- FOUNDATION_EXPORT CGAffineTransform PdfCanvasPdfToDocTransform(
81
- CGRect cropBox, NSInteger rotationDegrees);
82
-
83
- /**
84
- * Doc space -> raster space, for one request.
85
- *
86
- * `pixelHeight` is the destination bitmap's height in device pixels — the
87
- * request's own `pixelHeight`, not `docHeight * scale`. See the file note for why
88
- * that is exact rather than approximate.
89
- */
90
- FOUNDATION_EXPORT CGAffineTransform PdfCanvasDocToRasterTransform(
91
- double docX, double docY, double scale, NSInteger pixelHeight);
92
-
93
- /**
94
- * Doc space -> the y-up, page-sized space PDFKit draws into.
95
- *
96
- * `pageHeightPoints` is the POST-ROTATION crop-box height, i.e.
97
- * `PdfCanvasPageGeometry.heightPoints`.
98
- *
99
- * ------------------------------------------------------------------------
100
- * PDFKit'S ASYMMETRY, MEASURED, AND IT IS A TRAP
101
- * ------------------------------------------------------------------------
102
- *
103
- * `-[PDFPage drawWithBox:toContext:]` DOES apply `/Rotate` and DOES normalise the
104
- * box origin — so, unlike the CGPDF path, nothing here may rotate anything.
105
- * But `-[PDFPage boundsForBox:]` does NOT apply `/Rotate`: on the same
106
- * `/Rotate 90` letter page it returns `(0, 0, 612, 792)` while `drawWithBox:`
107
- * draws a 792 x 612 landscape page. It DOES report the crop box's origin
108
- * (`(100, 150, 200, 200)` for the offset-crop fixture), so it is not simply
109
- * normalising either.
110
- *
111
- * The consequence, and the whole reason this takes the height as an argument
112
- * rather than reading it off the page: flipping by `boundsForBox:.size.height`
113
- * flips a rotated page by the WRONG number, and the result is a page drawn
114
- * rotated, off-centre and clipped, with no error anywhere. The height must come
115
- * from `PdfCanvasPageGeometry`, which computes the post-rotation size from the
116
- * CGPDF crop box and rotation. Widely-copied sample code — flip by
117
- * `page.bounds(for:).size.height`, then draw — is correct only for unrotated
118
- * pages.
119
- *
120
- * With the height right, the two engines agree byte-for-byte on a page with no
121
- * annotations: 0 of 1,280,000 bytes differing, maxDelta 0, at scale 4 over an
122
- * 800x400 raster. That equality is what lets `annotations` pick an engine per
123
- * render without the geometry moving, and it is asserted by
124
- * `bothEnginesAgreeOnAPageWithNoAnnotations`.
125
- */
126
- FOUNDATION_EXPORT CGAffineTransform PdfCanvasDocToPdfKitTransform(
127
- double pageHeightPoints);
128
-
129
- NS_ASSUME_NONNULL_END
@@ -1,100 +0,0 @@
1
- #import <Foundation/Foundation.h>
2
-
3
- NS_ASSUME_NONNULL_BEGIN
4
-
5
- /**
6
- * Where a raster's bytes are to be written.
7
- *
8
- * ------------------------------------------------------------------------
9
- * THIS PROTOCOL IS WHY THE iOS TRANSPORT COSTS ZERO COPIES.
10
- * ------------------------------------------------------------------------
11
- *
12
- * Android's fast path costs ONE full-raster copy and cannot do better: a
13
- * `Bitmap` owns its own pixel memory, `PdfRenderer.render` writes into that, and
14
- * the best `nativeStoreFromBitmap` can do is one `memcpy` out of it. There is no
15
- * way to tell `PdfRenderer` where to put the pixels.
16
- *
17
- * `CGBitmapContextCreate` takes the destination pointer as an argument. So the
18
- * bytes are written ONCE, by CoreGraphics, straight into the buffer the JS
19
- * `ArrayBuffer` will later wrap. There is no bitmap object, no `NSData`, no
20
- * `memcpy`, and nothing for a garbage collector to notice.
21
- *
22
- * The core does not know what a slot is, and must not: `PdfCanvasCore` builds and
23
- * is tested with no React Native and no JSI anywhere near it. So the destination
24
- * is injected, this protocol is the seam, and
25
- * `ios/Sources/PdfCanvasBridge/PdfCanvasSlots.mm` is its only production
26
- * implementation. `PdfCanvasHeapSink` below is the default and is what every
27
- * pixel-reading caller — the whole Swift suite, and the base64 fallback — uses.
28
- *
29
- * ------------------------------------------------------------------------
30
- * THE ORDER IS RESERVE, FILL, PUBLISH, AND IT IS LOAD-BEARING.
31
- * ------------------------------------------------------------------------
32
- *
33
- * The Android JSI file records the bug this shape avoids, in its own words: a
34
- * buffer that is reachable from a process-wide map BEFORE it is filled can be
35
- * freed by a teardown running on another thread while a render worker is still
36
- * writing into it — "silent heap corruption, weeks from the change that caused
37
- * it". So a reserved buffer is owned by the sink object alone, held on the
38
- * rendering thread's stack, and becomes reachable only at `publish`.
39
- *
40
- * A REFUSAL COSTS NOTHING, because it happens before the draw. `reserveBytes:`
41
- * returning NULL means "no fast destination available"; the rasterizer falls
42
- * straight through to a heap sink and the tile still goes out. Contrast Android,
43
- * where the refusal can only be discovered after the raster exists.
44
- */
45
- @protocol PdfCanvasPixelSink <NSObject>
46
-
47
- /**
48
- * Reserve `length` bytes, zeroed, and return a pointer to them — or NULL to
49
- * refuse.
50
- *
51
- * Called AT MOST ONCE per sink, before anything is drawn. The returned memory
52
- * must stay valid and stable until `pdfCanvasPublish` or `pdfCanvasAbandon`, and
53
- * after a publish it must stay valid until whoever took it is done with it.
54
- *
55
- * ZEROED IS PART OF THE CONTRACT, not an accident of the implementation. It is
56
- * what makes `PdfCanvasBackgroundTransparent` free, and it is the invariant that
57
- * stops a published buffer ever handing out memory nobody wrote — the failure
58
- * mode there being a tile of somebody else's heap.
59
- */
60
- - (nullable void *)pdfCanvasReserveBytes:(NSUInteger)length;
61
-
62
- /**
63
- * Publish the reserved buffer and return the identifier a caller can redeem it
64
- * with, or 0 if this sink has no identifier (the heap sink).
65
- *
66
- * MUST NOT FAIL. Every reason a publish could be refused — no memory, an
67
- * identifier already in use — is decided at `reserveBytes:`, before the raster
68
- * exists. That is deliberate: Android's equivalent can refuse after the copy, and
69
- * the only thing a caller can do at that point is throw a finished raster away.
70
- */
71
- - (int64_t)pdfCanvasPublish;
72
-
73
- /**
74
- * Drop the buffer, published or not. Idempotent.
75
- *
76
- * Called on every path that ABANDONS a raster after reserving for it — a
77
- * cancellation observed after the draw finished, a failure between the draw and
78
- * the result being built. Without it each one strands a whole raster of native
79
- * memory that nothing will ever come looking for.
80
- */
81
- - (void)pdfCanvasAbandon;
82
-
83
- @end
84
-
85
- /**
86
- * The default destination: an `NSMutableData`.
87
- *
88
- * `-[NSMutableData dataWithLength:]` is documented to zero its bytes, which is
89
- * the `pdfCanvasReserveBytes:` contract met for free. `pdfCanvasPublish` returns
90
- * 0 and `data` is how the bytes are read back — which is what the base64
91
- * transport, every pixel-reading test, and `PdfCanvasRasterPixels.bytes` use.
92
- */
93
- @interface PdfCanvasHeapSink : NSObject <PdfCanvasPixelSink>
94
-
95
- /** The reserved buffer, or nil before `pdfCanvasReserveBytes:` / after abandon. */
96
- @property(nonatomic, readonly, nullable) NSMutableData *data;
97
-
98
- @end
99
-
100
- NS_ASSUME_NONNULL_END
@@ -1,65 +0,0 @@
1
- #import <Foundation/Foundation.h>
2
-
3
- #import "PdfCanvasErrorCode.h"
4
-
5
- NS_ASSUME_NONNULL_BEGIN
6
-
7
- /**
8
- * A cheap look at a file before CoreGraphics is asked to parse it.
9
- *
10
- * ------------------------------------------------------------------------
11
- * IT IS NOT A POISON GUARD, AND iOS DOES NOT NEED ONE.
12
- * ------------------------------------------------------------------------
13
- *
14
- * `PdfPreflight.java` exists for a hard reason: on Android API 24 a `PdfRenderer`
15
- * constructor that THROWS permanently poisons PDFium for the whole process — one
16
- * failed open plus a garbage collection and the next successful open segfaults in
17
- * `CPDF_Document`. So on Android, preflight is a correctness mechanism and its
18
- * scan window is load-bearing (a bug in it once let a 60 KB encrypted file through
19
- * and cascaded into eight unrelated failures).
20
- *
21
- * CoreGraphics has no such behaviour. MEASURED: `CGPDFDocumentCreateWithProvider`
22
- * over a `%PDF-1.7` header followed by garbage returns NULL — it logs "failed to
23
- * find start of cross-reference table" under `CG_PDF_VERBOSE` and returns cleanly
24
- * — and so does an empty buffer, and a subsequent open of a good document
25
- * succeeds. There is nothing to protect.
26
- *
27
- * ------------------------------------------------------------------------
28
- * WHAT IT IS FOR: TELLING THREE FAILURES APART.
29
- * ------------------------------------------------------------------------
30
- *
31
- * `CGPDFDocumentCreateWithURL` returning NULL is one signal for "no such file",
32
- * "no permission", "zero bytes" and "not a PDF", and those want four different
33
- * `PdfErrorCode`s and four different messages. A host showing "corrupt" for a path
34
- * that simply does not exist sends somebody looking at the wrong thing. So this
35
- * runs first, answers the questions the file system can answer, and gets out of
36
- * the way — every judgement CoreGraphics is better at (encryption, structure,
37
- * page count) is left to it.
38
- */
39
- @interface PdfCanvasPreflight : NSObject
40
-
41
- /**
42
- * Returns YES if `url` is worth handing to CoreGraphics.
43
- *
44
- * Checks, in order: the path exists, it is a file rather than a directory, it is
45
- * readable, it is not empty, and its first bytes contain a `%PDF-` header.
46
- *
47
- * THE HEADER SCAN IS A WINDOW, not the first five bytes. The spec allows leading
48
- * junk before `%PDF-` and real files carry it (an HTTP preamble, a shell wrapper),
49
- * and every reader in existence tolerates it; Acrobat's own limit is 1024 bytes,
50
- * which is the window used here. A file with no header in that window gets
51
- * `corrupt` with a message naming what was looked for, rather than CoreGraphics'
52
- * silence.
53
- */
54
- + (BOOL)checkFileURL:(NSURL *)url
55
- error:(NSError *_Nullable *_Nullable)error
56
- NS_SWIFT_NAME(check(fileURL:));
57
-
58
- /** The same header test, for the in-memory `open` path. */
59
- + (BOOL)checkData:(NSData *)data
60
- error:(NSError *_Nullable *_Nullable)error
61
- NS_SWIFT_NAME(check(_:));
62
-
63
- @end
64
-
65
- NS_ASSUME_NONNULL_END