@reekon-tools/react-native-pdf-canvas 0.1.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/PdfCanvas.podspec +80 -0
- package/README.md +1391 -0
- package/android/build.gradle +175 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +2 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +98 -0
- package/android/settings.gradle +34 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +226 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +239 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +251 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +221 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +783 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +1388 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +98 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +324 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +775 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +20 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +558 -0
- package/android/src/jsi/cpp/CMakeLists.txt +33 -0
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +469 -0
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +140 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +45 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +564 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +33 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +277 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +214 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +30 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +234 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +47 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +143 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +95 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +626 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasPackage.java +33 -0
- package/android/tools/compile-gate.sh +289 -0
- package/dist/cache.d.ts +161 -0
- package/dist/cache.js +389 -0
- package/dist/clock.d.ts +21 -0
- package/dist/clock.js +31 -0
- package/dist/controller.d.ts +131 -0
- package/dist/controller.js +1002 -0
- package/dist/geometry.d.ts +96 -0
- package/dist/geometry.js +248 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +50 -0
- package/dist/index.native.d.ts +21 -0
- package/dist/index.native.js +23 -0
- package/dist/index.web.d.ts +24 -0
- package/dist/index.web.js +26 -0
- package/dist/layout.d.ts +78 -0
- package/dist/layout.js +152 -0
- package/dist/peer-check.d.ts +83 -0
- package/dist/peer-check.js +183 -0
- package/dist/planner.d.ts +35 -0
- package/dist/planner.js +530 -0
- package/dist/policy.d.ts +14 -0
- package/dist/policy.js +168 -0
- package/dist/rasterizer/android.d.ts +121 -0
- package/dist/rasterizer/android.js +126 -0
- package/dist/rasterizer/buffer.d.ts +18 -0
- package/dist/rasterizer/buffer.js +18 -0
- package/dist/rasterizer/fake.d.ts +92 -0
- package/dist/rasterizer/fake.js +433 -0
- package/dist/rasterizer/index.d.ts +38 -0
- package/dist/rasterizer/index.js +69 -0
- package/dist/rasterizer/index.native.d.ts +19 -0
- package/dist/rasterizer/index.native.js +80 -0
- package/dist/rasterizer/index.web.d.ts +63 -0
- package/dist/rasterizer/index.web.js +75 -0
- package/dist/rasterizer/ingest.d.ts +180 -0
- package/dist/rasterizer/ingest.js +307 -0
- package/dist/rasterizer/ios.d.ts +84 -0
- package/dist/rasterizer/ios.js +155 -0
- package/dist/rasterizer/native-bridge.d.ts +261 -0
- package/dist/rasterizer/native-bridge.js +413 -0
- package/dist/rasterizer/web/client.d.ts +84 -0
- package/dist/rasterizer/web/client.js +359 -0
- package/dist/rasterizer/web/engine.d.ts +69 -0
- package/dist/rasterizer/web/engine.js +308 -0
- package/dist/rasterizer/web/index.d.ts +26 -0
- package/dist/rasterizer/web/index.js +21 -0
- package/dist/rasterizer/web/pdfium.d.ts +213 -0
- package/dist/rasterizer/web/pdfium.js +170 -0
- package/dist/rasterizer/web/protocol.d.ts +138 -0
- package/dist/rasterizer/web/protocol.js +37 -0
- package/dist/rasterizer/web/session.d.ts +106 -0
- package/dist/rasterizer/web/session.js +413 -0
- package/dist/rasterizer/web/worker.d.ts +44 -0
- package/dist/rasterizer/web/worker.js +76 -0
- package/dist/react/PdfContentView.d.ts +98 -0
- package/dist/react/PdfContentView.js +189 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +3 -0
- package/dist/react/usePdfDocument.d.ts +134 -0
- package/dist/react/usePdfDocument.js +275 -0
- package/dist/react/usePdfLayer.d.ts +94 -0
- package/dist/react/usePdfLayer.js +388 -0
- package/dist/skia/index.d.ts +26 -0
- package/dist/skia/index.js +25 -0
- package/dist/skia/index.native.d.ts +14 -0
- package/dist/skia/index.native.js +16 -0
- package/dist/skia/index.web.d.ts +12 -0
- package/dist/skia/index.web.js +14 -0
- package/dist/skia/resolve.native.d.ts +18 -0
- package/dist/skia/resolve.native.js +22 -0
- package/dist/skia/resolve.web.d.ts +41 -0
- package/dist/skia/resolve.web.js +62 -0
- package/dist/skia/skia-api.d.ts +94 -0
- package/dist/skia/skia-api.js +113 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +28 -0
- package/dist/testing/scenes.d.ts +100 -0
- package/dist/testing/scenes.js +267 -0
- package/dist/types.d.ts +611 -0
- package/dist/types.js +21 -0
- package/dist/web-init.d.ts +68 -0
- package/dist/web-init.js +64 -0
- package/ios/Package.swift +58 -0
- package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +743 -0
- package/ios/Sources/PdfCanvasBridge/include/PdfCanvasModule.h +36 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +38 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +389 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +57 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +44 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +82 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +37 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +158 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +80 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +65 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +548 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +48 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +26 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +122 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +80 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +79 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +129 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +100 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +65 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +111 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +85 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +163 -0
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +297 -0
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +140 -0
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +358 -0
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +262 -0
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +173 -0
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +114 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +158 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +544 -0
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +287 -0
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +396 -0
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +639 -0
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +179 -0
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +283 -0
- package/package.json +144 -0
- package/react-native.config.js +40 -0
- package/scripts/check-skia-peer.mjs +86 -0
- package/scripts/skia-peer-check.mjs +363 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript half of the iOS backend.
|
|
3
|
+
*
|
|
4
|
+
* FOUR THINGS, and nothing else. The wire format, the error mapping, the
|
|
5
|
+
* transport negotiation, the base64 fallback, the cancellation wiring and the
|
|
6
|
+
* phase timings are all in `./native-bridge.ts`, shared byte-for-byte with
|
|
7
|
+
* Android. What is iOS's own is the id, the capabilities, the alpha encoding and
|
|
8
|
+
* which `PdfSource` shapes it accepts — which is the whole point of the seam
|
|
9
|
+
* existing.
|
|
10
|
+
*
|
|
11
|
+
* PURE ON PURPOSE, like `./android.ts`. Nothing here imports `react-native`, so
|
|
12
|
+
* the adapter is testable in vitest against a fake native module.
|
|
13
|
+
* `./index.native.ts` is the only file that resolves the real one.
|
|
14
|
+
*
|
|
15
|
+
* The native side is `ios/Sources/PdfCanvasCore/`, a React-free Objective-C++
|
|
16
|
+
* library that also builds under SwiftPM (`ios/Package.swift`) so its geometry,
|
|
17
|
+
* rotation, stride, alpha and error mapping are provable with `swift test` on a
|
|
18
|
+
* Mac — no device, no app, no simulator. `ios/Sources/PdfCanvasBridge/` is the
|
|
19
|
+
* React binding and the JSI slot store, and joins the build only through the
|
|
20
|
+
* podspec.
|
|
21
|
+
*
|
|
22
|
+
* ------------------------------------------------------------------------
|
|
23
|
+
* TWO ENGINES BEHIND ONE BACKEND, chosen per render by `annotations`.
|
|
24
|
+
* ------------------------------------------------------------------------
|
|
25
|
+
*
|
|
26
|
+
* `annotations: false` — every tile the controller asks for — draws through
|
|
27
|
+
* `CGContextDrawPDFPage`, which composites the page's content stream and NOT its
|
|
28
|
+
* annotation appearance streams. `annotations: true` — `renderPage`'s default,
|
|
29
|
+
* which is the thumbnail and print path — draws through PDFKit's
|
|
30
|
+
* `-[PDFPage drawWithBox:toContext:]`, which composites both.
|
|
31
|
+
*
|
|
32
|
+
* THAT MAKES iOS THE ONLY BACKEND THAT HONOURS `annotations: false`. Android
|
|
33
|
+
* cannot (`PdfRenderer.Page.render` has no annotation switch at any API level)
|
|
34
|
+
* and the PDFium web backend does not pass `FPDF_ANNOT` either. So an app that
|
|
35
|
+
* draws its own copy of a PDF-embedded annotation on the Skia layer sees it once
|
|
36
|
+
* on iOS and twice on Android — a real cross-platform difference, and the right
|
|
37
|
+
* way round.
|
|
38
|
+
*/
|
|
39
|
+
import type { TakePixels } from './native-bridge.js';
|
|
40
|
+
import type { PageRasterizer } from '../types.js';
|
|
41
|
+
export interface IosRasterizerOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Concurrent renders. Clamped to [1, 4] natively — each slot is one more
|
|
44
|
+
* `CGPDFDocumentRef` over its own `CGDataProvider`, plus, once an
|
|
45
|
+
* `annotations: true` render asks for one, its own lazily-opened `PDFDocument`.
|
|
46
|
+
*
|
|
47
|
+
* ONE, AND FOR A WEAKER REASON THAN ANDROID'S. Android declares 1 because
|
|
48
|
+
* lanes above 1 were measured to make things WORSE — AOSP serialises
|
|
49
|
+
* `PdfRenderer` behind a process-wide PDFium lock that convoys, so four threads
|
|
50
|
+
* inflate the per-tile PDFium phases by up to 30x. None of that evidence is
|
|
51
|
+
* about CoreGraphics, and it must not be cited as though it were: the lock
|
|
52
|
+
* being measured there lives in `android_graphics_pdf_PdfRenderer.cpp`.
|
|
53
|
+
*
|
|
54
|
+
* WHAT IS ACTUALLY KNOWN ABOUT iOS. The pool exists so that concurrency is
|
|
55
|
+
* expressible at all: `CGPDFDocumentRef` is not documented thread-safe, and a
|
|
56
|
+
* single one shared across lanes would have to be serialised by a lock we own,
|
|
57
|
+
* which buys nothing. Opening the file once per lane gives each lane an
|
|
58
|
+
* independent parse — the same argument `PdfRendererPool` makes for not using
|
|
59
|
+
* `dup(2)`. Whether that then converts into wall-clock is unmeasured, which is
|
|
60
|
+
* why the default is the conservative 1 rather than the optimistic 2: an
|
|
61
|
+
* unmeasured speedup that is really a slowdown is exactly the mistake Android's
|
|
62
|
+
* `poolSize: 2` was, and it survived for months.
|
|
63
|
+
*
|
|
64
|
+
* Raising it does two things at once, as on Android: it opens more documents
|
|
65
|
+
* AND it widens the controller's lane bound, because it is also what the
|
|
66
|
+
* rasterizer reports as `RasterizerCapabilities.maxConcurrentRenders`. Measure
|
|
67
|
+
* before raising it, on a real drawing rather than a synthetic fixture — the
|
|
68
|
+
* Android note explains why that distinction inverted the answer there.
|
|
69
|
+
*/
|
|
70
|
+
poolSize?: number;
|
|
71
|
+
id?: string;
|
|
72
|
+
/**
|
|
73
|
+
* How to find the installed JSI host function. Injected only by tests; the
|
|
74
|
+
* default reads `globalThis`.
|
|
75
|
+
*/
|
|
76
|
+
lookupTakePixels?: () => TakePixels | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wraps a native module in a `PageRasterizer`.
|
|
80
|
+
*
|
|
81
|
+
* The native module is INJECTED rather than imported so this file stays pure.
|
|
82
|
+
* `getDefaultRasterizer()` in `./index.native.ts` supplies the real one.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createIosRasterizer(native: import('./native-bridge.js').PdfCanvasNativeModule, options?: IosRasterizerOptions): PageRasterizer;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript half of the iOS backend.
|
|
3
|
+
*
|
|
4
|
+
* FOUR THINGS, and nothing else. The wire format, the error mapping, the
|
|
5
|
+
* transport negotiation, the base64 fallback, the cancellation wiring and the
|
|
6
|
+
* phase timings are all in `./native-bridge.ts`, shared byte-for-byte with
|
|
7
|
+
* Android. What is iOS's own is the id, the capabilities, the alpha encoding and
|
|
8
|
+
* which `PdfSource` shapes it accepts — which is the whole point of the seam
|
|
9
|
+
* existing.
|
|
10
|
+
*
|
|
11
|
+
* PURE ON PURPOSE, like `./android.ts`. Nothing here imports `react-native`, so
|
|
12
|
+
* the adapter is testable in vitest against a fake native module.
|
|
13
|
+
* `./index.native.ts` is the only file that resolves the real one.
|
|
14
|
+
*
|
|
15
|
+
* The native side is `ios/Sources/PdfCanvasCore/`, a React-free Objective-C++
|
|
16
|
+
* library that also builds under SwiftPM (`ios/Package.swift`) so its geometry,
|
|
17
|
+
* rotation, stride, alpha and error mapping are provable with `swift test` on a
|
|
18
|
+
* Mac — no device, no app, no simulator. `ios/Sources/PdfCanvasBridge/` is the
|
|
19
|
+
* React binding and the JSI slot store, and joins the build only through the
|
|
20
|
+
* podspec.
|
|
21
|
+
*
|
|
22
|
+
* ------------------------------------------------------------------------
|
|
23
|
+
* TWO ENGINES BEHIND ONE BACKEND, chosen per render by `annotations`.
|
|
24
|
+
* ------------------------------------------------------------------------
|
|
25
|
+
*
|
|
26
|
+
* `annotations: false` — every tile the controller asks for — draws through
|
|
27
|
+
* `CGContextDrawPDFPage`, which composites the page's content stream and NOT its
|
|
28
|
+
* annotation appearance streams. `annotations: true` — `renderPage`'s default,
|
|
29
|
+
* which is the thumbnail and print path — draws through PDFKit's
|
|
30
|
+
* `-[PDFPage drawWithBox:toContext:]`, which composites both.
|
|
31
|
+
*
|
|
32
|
+
* THAT MAKES iOS THE ONLY BACKEND THAT HONOURS `annotations: false`. Android
|
|
33
|
+
* cannot (`PdfRenderer.Page.render` has no annotation switch at any API level)
|
|
34
|
+
* and the PDFium web backend does not pass `FPDF_ANNOT` either. So an app that
|
|
35
|
+
* draws its own copy of a PDF-embedded annotation on the Skia layer sees it once
|
|
36
|
+
* on iOS and twice on Android — a real cross-platform difference, and the right
|
|
37
|
+
* way round.
|
|
38
|
+
*/
|
|
39
|
+
import { createNativeRasterizer } from './native-bridge.js';
|
|
40
|
+
import { PdfError } from '../types.js';
|
|
41
|
+
/**
|
|
42
|
+
* A CONSTANT AND NOT PART OF THE WIRE RESULT, unlike `format`, and — unusually
|
|
43
|
+
* for this package — a fact about an API rather than a measurement.
|
|
44
|
+
*
|
|
45
|
+
* There is no non-premultiplied 8-bit RGBA `CGBitmapContext`. Apple's supported
|
|
46
|
+
* pixel formats for a 32-bit-per-pixel, 8-bits-per-component RGB bitmap context
|
|
47
|
+
* are `kCGImageAlphaPremultipliedFirst`, `kCGImageAlphaPremultipliedLast`,
|
|
48
|
+
* `kCGImageAlphaNoneSkipFirst` and `kCGImageAlphaNoneSkipLast` — premultiplied,
|
|
49
|
+
* or no alpha at all. `kCGImageAlphaLast` (straight) is not in the table and
|
|
50
|
+
* `CGBitmapContextCreate` returns NULL for it. So the only two choices a backend
|
|
51
|
+
* has here are "premultiplied" and "throw away the alpha channel", and throwing
|
|
52
|
+
* it away would break `background: 'transparent'`.
|
|
53
|
+
*
|
|
54
|
+
* `ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.mm` creates exactly one context
|
|
55
|
+
* configuration and this constant states it. The `alphaIsPremultiplied` case in
|
|
56
|
+
* the Swift suite reads the bytes back and checks the encoding on an antialiased
|
|
57
|
+
* edge over a transparent page — the one cell the spec's alpha table had marked
|
|
58
|
+
* NOT MEASURED — so this is asserted rather than believed.
|
|
59
|
+
*
|
|
60
|
+
* IT AGREES WITH ANDROID BY COINCIDENCE OF MECHANISM. Android's value comes from
|
|
61
|
+
* `Bitmap` being premultiplied by default; this one comes from CoreGraphics
|
|
62
|
+
* having no other option. PDFium and pdf.js both emit STRAIGHT alpha. Four
|
|
63
|
+
* backends, three independent reasons, no default that would be right — which is
|
|
64
|
+
* why `RasterPixels.alpha` is a required field.
|
|
65
|
+
*/
|
|
66
|
+
const IOS_ALPHA = 'premultiplied';
|
|
67
|
+
const CAPABILITIES = {
|
|
68
|
+
/**
|
|
69
|
+
* True, and — alone among the four backends — true in BOTH directions.
|
|
70
|
+
*
|
|
71
|
+
* `annotations: true` draws them (PDFKit) and `annotations: false` does not
|
|
72
|
+
* (`CGContextDrawPDFPage`). Android records the flag and ignores it; the web
|
|
73
|
+
* PDFium backend does not pass `FPDF_ANNOT`. The field only claims "can render
|
|
74
|
+
* PDF-embedded annotations into the raster", so all three answer true — the
|
|
75
|
+
* asymmetry is documented at the top of this file rather than inventable from
|
|
76
|
+
* the capability.
|
|
77
|
+
*/
|
|
78
|
+
annotations: true,
|
|
79
|
+
/**
|
|
80
|
+
* False, and honestly so. `CGContextDrawPDFPage` and
|
|
81
|
+
* `-[PDFPage drawWithBox:toContext:]` are both synchronous and uninterruptible
|
|
82
|
+
* once entered; an abort can only stop the NEXT piece starting, or discard a
|
|
83
|
+
* finished raster that is no longer wanted. This is the platform
|
|
84
|
+
* `RasterPolicy.minEpochIntervalMs` was written for — PDFKit is named in its
|
|
85
|
+
* doc comment.
|
|
86
|
+
*/
|
|
87
|
+
interruptibleRender: false,
|
|
88
|
+
/**
|
|
89
|
+
* FALSE, AND THIS ONE IS A DECLARATION ABOUT THE PACKAGE, NOT THE PLATFORM.
|
|
90
|
+
* PDFKit really does have `-[PDFPage string]`, `-[PDFDocument findString:]` and
|
|
91
|
+
* link annotations, so iOS could answer true for all three the day a text
|
|
92
|
+
* surface exists. It does not exist — see the Non-goals — and a capability that
|
|
93
|
+
* says "yes" about an API no consumer can reach is worse than one that says
|
|
94
|
+
* "no". Every backend declares false today for the same reason.
|
|
95
|
+
*/
|
|
96
|
+
text: false,
|
|
97
|
+
search: false,
|
|
98
|
+
links: false,
|
|
99
|
+
// Overwritten per rasterizer with the pool size it was actually opened with —
|
|
100
|
+
// see `createNativeRasterizer`.
|
|
101
|
+
maxConcurrentRenders: 1,
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Mirrors `kPdfCanvasMaxPoolSize` in
|
|
105
|
+
* `ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.mm`, which is where the clamp
|
|
106
|
+
* is actually enforced. Restating it here is what makes the DECLARED capability
|
|
107
|
+
* match the pool the native side will really hand out.
|
|
108
|
+
*/
|
|
109
|
+
const MAX_POOL_SIZE = 4;
|
|
110
|
+
const clampPoolSize = (size) => Math.max(1, Math.min(MAX_POOL_SIZE, Math.floor(size) || 1));
|
|
111
|
+
function toNativeSource(source) {
|
|
112
|
+
const password = source.password;
|
|
113
|
+
if ('uri' in source) {
|
|
114
|
+
return password === undefined
|
|
115
|
+
? { uri: source.uri }
|
|
116
|
+
: { uri: source.uri, password };
|
|
117
|
+
}
|
|
118
|
+
// REFUSED FOR THE SAME REASON AS ANDROID, and it is the bridge's reason rather
|
|
119
|
+
// than the platform's. iOS could take bytes with no spool at all —
|
|
120
|
+
// `CGPDFDocumentCreateWithProvider` over a `CFData` needs no file, and the
|
|
121
|
+
// native `open` does implement the `base64` field of the shared wire format for
|
|
122
|
+
// exactly that — but getting the bytes THERE means base64-encoding the whole
|
|
123
|
+
// document across the bridge, which is the one thing the spec's "two rules that
|
|
124
|
+
// are now measured" says never to do.
|
|
125
|
+
throw new PdfError('unsupported', 'Passing a document as bytes is not wired through the iOS binding. The ' +
|
|
126
|
+
'native side supports it (no spool file is needed — CoreGraphics reads a ' +
|
|
127
|
+
'CGDataProvider over the bytes directly), but sending them over the ' +
|
|
128
|
+
'bridge costs a base64 round trip of the whole document. Pass a `uri` ' +
|
|
129
|
+
'instead: a plain path, a `file://` URL, or a security-scoped URL the app ' +
|
|
130
|
+
'can still read.');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Wraps a native module in a `PageRasterizer`.
|
|
134
|
+
*
|
|
135
|
+
* The native module is INJECTED rather than imported so this file stays pure.
|
|
136
|
+
* `getDefaultRasterizer()` in `./index.native.ts` supplies the real one.
|
|
137
|
+
*/
|
|
138
|
+
export function createIosRasterizer(native, options = {}) {
|
|
139
|
+
return createNativeRasterizer(native, {
|
|
140
|
+
// NOT `ios-pdfkit`, though the README's plan said PDFKit and an earlier draft
|
|
141
|
+
// of this file used that id. Half the renders never touch PDFKit — every tile
|
|
142
|
+
// goes through `CGContextDrawPDFPage` — and an id is what a bug report
|
|
143
|
+
// quotes, so naming the framework that handles the minority of renders would
|
|
144
|
+
// misdirect every investigation of a tile.
|
|
145
|
+
id: options.id ?? 'ios-coregraphics',
|
|
146
|
+
capabilities: CAPABILITIES,
|
|
147
|
+
alpha: IOS_ALPHA,
|
|
148
|
+
poolSize: clampPoolSize(options.poolSize ?? 1),
|
|
149
|
+
label: 'iOS',
|
|
150
|
+
toNativeSource,
|
|
151
|
+
...(options.lookupTakePixels === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { lookupTakePixels: options.lookupTakePixels }),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The part of a platform backend that is not platform-specific.
|
|
3
|
+
*
|
|
4
|
+
* TWO NATIVE BACKENDS, ONE WIRE FORMAT. `android.ts` and `ios.ts` are both thin
|
|
5
|
+
* over this file: they choose an `id`, a `RasterizerCapabilities`, an
|
|
6
|
+
* `AlphaEncoding` and how a `PdfSource` becomes a native source, and everything
|
|
7
|
+
* else — error mapping, page-geometry marshalling, the transport negotiation, the
|
|
8
|
+
* base64 decode, the cancellation wiring, the phase timings — is here and is
|
|
9
|
+
* identical. That is not tidiness: the spec's whole claim for the iOS step is
|
|
10
|
+
* "the seam is a seam and not an Android-shaped hole", and the cheapest way to
|
|
11
|
+
* be held to it is for the second backend to be unable to drift.
|
|
12
|
+
*
|
|
13
|
+
* PURE ON PURPOSE, exactly as `android.ts` was before it was split. Nothing here
|
|
14
|
+
* imports `react-native`, which is what lets the whole adapter be tested in
|
|
15
|
+
* vitest against a fake native module. `./index.native.ts` is the only file that
|
|
16
|
+
* resolves a real one.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE WIRE FORMAT IS SHARED RATHER THAN MERELY SIMILAR. The two native
|
|
19
|
+
* modules are written independently — `android/src/.../rn/PdfCanvasModule.java`
|
|
20
|
+
* and `ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm` — so the one thing that
|
|
21
|
+
* can silently diverge is the shape of the maps they resolve with. Sharing this
|
|
22
|
+
* file makes a divergence a compile error on one side or a failing marshalling
|
|
23
|
+
* test on the other, instead of a wrong image on one platform.
|
|
24
|
+
*
|
|
25
|
+
* `globalThis` is the only ambient thing this file reads, and it is universal —
|
|
26
|
+
* the `__pdfCanvasTakePixels` lookup finds nothing under vitest and the adapter
|
|
27
|
+
* carries on.
|
|
28
|
+
*/
|
|
29
|
+
import { PdfError } from '../types.js';
|
|
30
|
+
import type { AlphaEncoding, PageGeometry, PageRasterizer, PdfSource, PixelFormat, RasterizerCapabilities, RasterizerHandle, RasterTransport } from '../types.js';
|
|
31
|
+
/**
|
|
32
|
+
* Whether this DEVICE draws a page carrying `/Rotate` upright.
|
|
33
|
+
*
|
|
34
|
+
* A THREE-STATE ANSWER BECAUSE ANDROID GENUINELY HAS THREE. API 24 reports the
|
|
35
|
+
* rotated DIMENSIONS but draws the content unrotated and clipped, while 31 and
|
|
36
|
+
* 33 are correct, and the verdict is measured on device rather than derived from
|
|
37
|
+
* `Build.VERSION` — see `PdfPlatformQuirks`. iOS answers `'supported'`
|
|
38
|
+
* unconditionally: CoreGraphics applies the transform this backend hands it, and
|
|
39
|
+
* `ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.mm` is where `/Rotate`
|
|
40
|
+
* becomes part of that transform, so there is no device variable to probe.
|
|
41
|
+
*
|
|
42
|
+
* It stays on the shared wire format rather than being an Android extension
|
|
43
|
+
* precisely so a host does not have to know which platform it is on to ask.
|
|
44
|
+
*/
|
|
45
|
+
export type NativeRotationSupport = 'supported' | 'unsupported' | 'unknown';
|
|
46
|
+
export interface NativePdfPage {
|
|
47
|
+
index: number;
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
/**
|
|
51
|
+
* The page's `/Rotate`, in degrees, or 0.
|
|
52
|
+
*
|
|
53
|
+
* THE TWO BACKENDS REPORT THIS DIFFERENTLY AND BOTH ARE RIGHT, which is worth
|
|
54
|
+
* knowing before treating a difference as a bug. `PageGeometry.rotation` is
|
|
55
|
+
* informational — `width` and `height` are already post-rotation on every
|
|
56
|
+
* backend, and no consumer applies a further rotation — so:
|
|
57
|
+
*
|
|
58
|
+
* - iOS reports the real `/Rotate` (`CGPDFPageGetRotationAngle`), matching
|
|
59
|
+
* the web PDFium backend.
|
|
60
|
+
* - Android reports 0, because `PdfRenderer` exposes no way to read `/Rotate`
|
|
61
|
+
* below API 35. That is an inability, not a policy.
|
|
62
|
+
*/
|
|
63
|
+
rotation: number;
|
|
64
|
+
}
|
|
65
|
+
export interface NativeOpenResult {
|
|
66
|
+
handle: number;
|
|
67
|
+
pageCount: number;
|
|
68
|
+
pages: NativePdfPage[];
|
|
69
|
+
rotationSupport: NativeRotationSupport;
|
|
70
|
+
}
|
|
71
|
+
export interface NativeRenderResult {
|
|
72
|
+
/**
|
|
73
|
+
* Raw pixels, base64. Present on the FALLBACK transport only.
|
|
74
|
+
*
|
|
75
|
+
* Exactly one of `base64` and `pixelsSlot` is set; neither native side ever
|
|
76
|
+
* sends both, because building the string is the single largest cost the fast
|
|
77
|
+
* path exists to delete.
|
|
78
|
+
*/
|
|
79
|
+
base64?: string;
|
|
80
|
+
/**
|
|
81
|
+
* The JSI slot holding this raster's bytes, on the fast transport.
|
|
82
|
+
*
|
|
83
|
+
* A number rather than the bytes themselves because a bridge result map has no
|
|
84
|
+
* binary type — that is the whole problem, and it is the same problem on both
|
|
85
|
+
* platforms (measured: RN's `convertObjCObjectToJSIValue` handles NSString,
|
|
86
|
+
* NSNumber, NSDictionary, NSArray and nothing else, so `NSData` converts to
|
|
87
|
+
* `undefined`). The number is a receipt: JS redeems it once, synchronously,
|
|
88
|
+
* through `__pdfCanvasTakePixels`, which hands back an ArrayBuffer wrapping the
|
|
89
|
+
* native memory and forgets the slot.
|
|
90
|
+
*/
|
|
91
|
+
pixelsSlot?: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
rowBytes: number;
|
|
95
|
+
format: PixelFormat;
|
|
96
|
+
}
|
|
97
|
+
export interface NativeRenderRequest {
|
|
98
|
+
page: number;
|
|
99
|
+
x: number;
|
|
100
|
+
y: number;
|
|
101
|
+
width: number;
|
|
102
|
+
height: number;
|
|
103
|
+
scale: number;
|
|
104
|
+
annotations: boolean;
|
|
105
|
+
background: 'white' | 'transparent';
|
|
106
|
+
}
|
|
107
|
+
export interface NativeSource {
|
|
108
|
+
uri?: string;
|
|
109
|
+
base64?: string;
|
|
110
|
+
password?: string;
|
|
111
|
+
}
|
|
112
|
+
export interface PdfCanvasNativeModule {
|
|
113
|
+
open(source: NativeSource, poolSize: number): Promise<NativeOpenResult>;
|
|
114
|
+
render(handle: number, request: NativeRenderRequest, token: number): Promise<NativeRenderResult>;
|
|
115
|
+
/** Best-effort. Cannot interrupt a render already inside the platform engine. */
|
|
116
|
+
cancel(handle: number, token: number): void;
|
|
117
|
+
close(handle: number): void;
|
|
118
|
+
/**
|
|
119
|
+
* Installs the JSI host function and parks a probe raster. Returns its slot,
|
|
120
|
+
* or a negative number if the fast transport is unavailable.
|
|
121
|
+
*
|
|
122
|
+
* OPTIONAL, and its absence is the normal case for an older app binary whose
|
|
123
|
+
* native module predates the transport. `negotiateTransport` treats a missing
|
|
124
|
+
* method exactly as it treats a refusal.
|
|
125
|
+
*/
|
|
126
|
+
installJsiTransport?(): number;
|
|
127
|
+
/** Arms or disarms the fast transport. Returns what is now live. */
|
|
128
|
+
enableJsiTransport?(enabled: boolean): boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* `globalThis.__pdfCanvasTakePixels`, installed from C++.
|
|
132
|
+
*
|
|
133
|
+
* Redeems a slot for the ArrayBuffer holding that raster, exactly once. Throws
|
|
134
|
+
* if the slot is empty — which is a lost raster, never an empty one, because a
|
|
135
|
+
* zero-length buffer would reach Skia and draw a black tile.
|
|
136
|
+
*
|
|
137
|
+
* ONE NAME FOR BOTH PLATFORMS, deliberately. The two installers are different
|
|
138
|
+
* files in different languages, but a runtime only ever has one of them, and a
|
|
139
|
+
* shared name is what lets `lookupTakePixels` be platform-blind.
|
|
140
|
+
*/
|
|
141
|
+
export type TakePixels = (slot: number) => ArrayBuffer;
|
|
142
|
+
/**
|
|
143
|
+
* React Native's `promise.reject(code, message)` surfaces `code` as a string
|
|
144
|
+
* property on the rejection, which is why `PdfErrorCode`'s members are the wire
|
|
145
|
+
* format on the native side too (`PdfErrorCode.wire()` in Java,
|
|
146
|
+
* `PdfCanvasErrorCodeWire()` in Objective-C). No translation table exists in
|
|
147
|
+
* between, deliberately: a table is a place for the lists to silently drift
|
|
148
|
+
* apart.
|
|
149
|
+
*/
|
|
150
|
+
export declare function toPdfError(error: unknown, fallback: string): PdfError;
|
|
151
|
+
/**
|
|
152
|
+
* Hand-rolled rather than `atob` or `Buffer`.
|
|
153
|
+
*
|
|
154
|
+
* `Buffer` is a node global that RN does not ship, and `atob` — added to Hermes
|
|
155
|
+
* relatively recently — decodes to a STRING, so recovering bytes means a second
|
|
156
|
+
* pass building a Uint8Array from char codes, doubling the peak footprint of a
|
|
157
|
+
* buffer that is already megabytes. This writes straight into the output array,
|
|
158
|
+
* and being pure means the padded-stride and odd-length cases are covered in
|
|
159
|
+
* vitest instead of on a device.
|
|
160
|
+
*/
|
|
161
|
+
export declare function decodeBase64(input: string): Uint8Array;
|
|
162
|
+
/**
|
|
163
|
+
* Mirrors `PdfCanvasPixelBridge.PROBE_BYTES` on the Java side and
|
|
164
|
+
* `kPdfCanvasProbeBytes` in `ios/Sources/PdfCanvasBridge/PdfCanvasSlots.mm`.
|
|
165
|
+
*
|
|
166
|
+
* The three lists are restated rather than derived, which is the point: they are
|
|
167
|
+
* the only places the transport's byte fidelity is asserted, and if one is edited
|
|
168
|
+
* without the others the probe fails and that platform degrades to base64 rather
|
|
169
|
+
* than shipping a shear.
|
|
170
|
+
*
|
|
171
|
+
* The contents are chosen, not arbitrary. 0x00 and 0xFF pin both ends of the
|
|
172
|
+
* range; 0xFD/0xFE/0xFF are above 0x7F, where Java's SIGNED byte and a
|
|
173
|
+
* JS `Uint8Array` disagree unless every step of the copy is right. (On iOS the
|
|
174
|
+
* sign-extension hazard does not exist — the whole path is `uint8_t` — but the
|
|
175
|
+
* probe's other job does: it proves the runtime will build an ArrayBuffer over
|
|
176
|
+
* external memory at all, which is a claim about a JS engine version and not
|
|
177
|
+
* about a language.)
|
|
178
|
+
*/
|
|
179
|
+
export declare const TRANSPORT_PROBE_BYTES: readonly number[];
|
|
180
|
+
/**
|
|
181
|
+
* Reads the installed host function off the global.
|
|
182
|
+
*
|
|
183
|
+
* Through `globalThis`, which is universal — unlike `react-native`, whose import
|
|
184
|
+
* is what this whole file is written to avoid. So the pure core still loads
|
|
185
|
+
* under vitest and in a browser: the lookup simply finds nothing.
|
|
186
|
+
*/
|
|
187
|
+
export declare const lookupTakePixels: () => TakePixels | null;
|
|
188
|
+
/**
|
|
189
|
+
* The resolved transport, and the function that redeems a slot on it.
|
|
190
|
+
*
|
|
191
|
+
* `take` is non-null if and only if `kind` is `jsi-arraybuffer`, which is what
|
|
192
|
+
* lets the render path branch once on a value it can trust.
|
|
193
|
+
*/
|
|
194
|
+
export interface NativeTransport {
|
|
195
|
+
readonly kind: RasterTransport;
|
|
196
|
+
readonly take: TakePixels | null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Decides ONCE, at construction, which transport this rasterizer uses — and
|
|
200
|
+
* proves the fast one before choosing it.
|
|
201
|
+
*
|
|
202
|
+
* THE PROBE IS THE POINT. Every step below can fail on a real device for a
|
|
203
|
+
* reason no type system sees: the app binary may predate the native transport
|
|
204
|
+
* (no `installJsiTransport`), the build may lack the native library for this ABI
|
|
205
|
+
* or slice (install returns -1), the runtime may refuse a host function, or the
|
|
206
|
+
* JS engine may not support an ArrayBuffer backed by external memory at all —
|
|
207
|
+
* Hermes does, but "does" is a claim about a version. So instead of inferring
|
|
208
|
+
* availability from any of those, the negotiation moves eight known bytes across
|
|
209
|
+
* the real transport and compares them. Only a byte-for-byte match arms it.
|
|
210
|
+
*
|
|
211
|
+
* Every failure returns `base64`, and none of them throws. A build that cannot
|
|
212
|
+
* do this fast renders exactly as it did before.
|
|
213
|
+
*/
|
|
214
|
+
export declare function negotiateTransport(native: PdfCanvasNativeModule, lookup?: () => TakePixels | null): NativeTransport;
|
|
215
|
+
export declare function toPageGeometry(page: NativePdfPage): PageGeometry;
|
|
216
|
+
/**
|
|
217
|
+
* Turns one native render result into bytes, on whichever transport produced it.
|
|
218
|
+
*
|
|
219
|
+
* Branches on the RESULT rather than on the negotiated transport, because the
|
|
220
|
+
* two can disagree for exactly one render: a native side clears its own flag
|
|
221
|
+
* when a slot store fails and falls that tile back to base64 mid-flight. What
|
|
222
|
+
* arrived is the only thing that can be believed.
|
|
223
|
+
*/
|
|
224
|
+
export declare function bytesFromResult(result: NativeRenderResult, transport: NativeTransport): {
|
|
225
|
+
bytes: Uint8Array;
|
|
226
|
+
payloadChars: number;
|
|
227
|
+
used: RasterTransport;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Surfaced so a host can decide what to do about a device that does not draw
|
|
231
|
+
* rotated pages upright — warn, fall back, or accept it.
|
|
232
|
+
*/
|
|
233
|
+
export interface NativeRasterizerHandle extends RasterizerHandle {
|
|
234
|
+
readonly rotationSupport: NativeRotationSupport;
|
|
235
|
+
}
|
|
236
|
+
export declare function createNativeHandle(native: PdfCanvasNativeModule, opened: NativeOpenResult, transport: NativeTransport, alpha: AlphaEncoding, label: string): NativeRasterizerHandle;
|
|
237
|
+
export interface NativeRasterizerSpec {
|
|
238
|
+
/** Surfaced as `PageRasterizer.id`, so a bug report names a backend. */
|
|
239
|
+
id: string;
|
|
240
|
+
/**
|
|
241
|
+
* Everything except `maxConcurrentRenders` and `transport`, both of which are
|
|
242
|
+
* resolved here — the first from the pool size actually asked for, the second
|
|
243
|
+
* from the negotiation.
|
|
244
|
+
*/
|
|
245
|
+
capabilities: RasterizerCapabilities;
|
|
246
|
+
/** What this platform's bitmaps encode. Never defaulted — see `AlphaEncoding`. */
|
|
247
|
+
alpha: AlphaEncoding;
|
|
248
|
+
/** Clamped concurrent renders, already resolved by the caller. */
|
|
249
|
+
poolSize: number;
|
|
250
|
+
/** Named in the "handle is closed" message. */
|
|
251
|
+
label: string;
|
|
252
|
+
toNativeSource: (source: PdfSource) => NativeSource;
|
|
253
|
+
lookupTakePixels?: () => TakePixels | null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Wraps a native module in a `PageRasterizer`.
|
|
257
|
+
*
|
|
258
|
+
* The native module is INJECTED rather than imported so this file stays pure.
|
|
259
|
+
* `getDefaultRasterizer()` in `./index.native.ts` supplies the real one.
|
|
260
|
+
*/
|
|
261
|
+
export declare function createNativeRasterizer(native: PdfCanvasNativeModule, spec: NativeRasterizerSpec): PageRasterizer;
|