@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
package/dist/policy.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cadence constants, in one place, each with the reasoning that produced
|
|
3
|
+
* it. None of these are measured on this codebase yet — where a value is a
|
|
4
|
+
* placeholder for a measurement, the comment says so out loud.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_POLICY = Object.freeze({
|
|
7
|
+
/**
|
|
8
|
+
* 1.0 — the base is rendered at exactly the fit-zoom display scale, so at
|
|
9
|
+
* fit zoom it is pixel-for-pixel and never magnified or minified. Anything
|
|
10
|
+
* above 1.0 buys sharpness only while zoomed in, which is the DETAIL layer's
|
|
11
|
+
* job; anything below draws the page minified, which is the aliasing regime
|
|
12
|
+
* (kStrict_SrcRectConstraint disables mipmaps, so it cannot be filtered
|
|
13
|
+
* away). A consumer whose base must survive a 2x zoom without the detail
|
|
14
|
+
* layer landing raises this to ~1.5 and pays for it in `baseMaxPixels`.
|
|
15
|
+
*/
|
|
16
|
+
baseQuality: 1.0,
|
|
17
|
+
/**
|
|
18
|
+
* 4 MP. The base is the anti-blank mechanism and is NEVER released, so it is
|
|
19
|
+
* resident for the life of the document — one per visible page. At RGBA8888
|
|
20
|
+
* that is 16 MB per page, which is a sixth of `cacheBudgetBytes` and leaves
|
|
21
|
+
* room for the two or three pages a continuous layout keeps live. It also
|
|
22
|
+
* comfortably clears every real fit-zoom target: ARCH-D on a 1170x2532
|
|
23
|
+
* phone needs 2.05 MP, so the clamp does not bind on the case that matters
|
|
24
|
+
* and only catches the pathological sheet.
|
|
25
|
+
*/
|
|
26
|
+
baseMaxPixels: 4000000,
|
|
27
|
+
/**
|
|
28
|
+
* 0.25 — a quarter of the visible extent added on EACH side, so a detail
|
|
29
|
+
* raster is 1.5x the viewport per axis, 2.25x its area. That is the price of
|
|
30
|
+
* a flick that outruns the quiet timer: a pan of up to a quarter viewport
|
|
31
|
+
* stays sharp with no new raster at all. Larger multiplies raster cost
|
|
32
|
+
* quadratically for padding that is usually thrown away.
|
|
33
|
+
*/
|
|
34
|
+
detailOverflow: 0.25,
|
|
35
|
+
/**
|
|
36
|
+
* 4096 is the only near-universal GPU texture limit — it is the floor across
|
|
37
|
+
* the GLES3 and Metal feature sets this ships to, and exceeding it does not
|
|
38
|
+
* degrade, it fails the texture upload and draws nothing. Devices reporting
|
|
39
|
+
* 8192 or 16384 exist; planning to the floor costs one extra piece on a
|
|
40
|
+
* desktop-sized viewport and removes an entire class of blank-page report.
|
|
41
|
+
*/
|
|
42
|
+
maxTextureDimension: 4096,
|
|
43
|
+
/**
|
|
44
|
+
* 1024 device px — a MEMORY policy, not a hardware one, and the single biggest
|
|
45
|
+
* low-end lever in the package.
|
|
46
|
+
*
|
|
47
|
+
* Measured on the un-tiled detail raster this replaced: 1755x3798 (25.4 MiB of
|
|
48
|
+
* pixels) had a ~237 MiB whole-flow transient peak across the Java, native and
|
|
49
|
+
* JS heaps, because one piece's bytes exist at once as a platform bitmap, a
|
|
50
|
+
* base64 string on the bridge, a JS `Uint8Array` and an `SkData`. Tiling the
|
|
51
|
+
* same area at 1024 drops that peak to ~29.7 MiB; at 512 to ~7.4 MiB. The
|
|
52
|
+
* RESIDENT bytes do not change — it is the same pixels either way.
|
|
53
|
+
*
|
|
54
|
+
* Why not 512, then: every piece is a separate uninterruptible platform render
|
|
55
|
+
* with its own fixed overhead, and each interior seam costs an overlap strip,
|
|
56
|
+
* so halving again quadruples the piece count for a peak that is already well
|
|
57
|
+
* under any device's budget. 1024 is also comfortably inside
|
|
58
|
+
* `maxTextureDimension` on every device, so the two caps do not fight.
|
|
59
|
+
*
|
|
60
|
+
* Raise it on a big-memory tablet; lower it to 512 on a low-RAM Android device
|
|
61
|
+
* where the transient, not the total, is what gets the app killed.
|
|
62
|
+
*/
|
|
63
|
+
maxPieceDimension: 1024,
|
|
64
|
+
/**
|
|
65
|
+
* 24 MP for a single detail request, matching the cap the consuming app
|
|
66
|
+
* already established for its own PDF rendering. It is a TIME budget as much
|
|
67
|
+
* as a memory one: PDFKit's draw and Android's PdfRenderer.render are
|
|
68
|
+
* synchronous and uninterruptible once entered, so an oversized request is
|
|
69
|
+
* an un-abortable stall on whichever thread it runs on.
|
|
70
|
+
*/
|
|
71
|
+
maxRasterPixels: 24000000,
|
|
72
|
+
/**
|
|
73
|
+
* sqrt(2). Re-raster once the on-screen scale has drifted a full half-octave
|
|
74
|
+
* from what the bytes were rendered at — i.e. tolerate up to 41% magnifi-
|
|
75
|
+
* cation (mild softness) or 29% minification before spending a raster.
|
|
76
|
+
* Halving this to 1.2 is correct on a DPR-1 display, where individual pixels
|
|
77
|
+
* are resolvable before any magnification is applied.
|
|
78
|
+
*/
|
|
79
|
+
sharpnessBand: 1.414,
|
|
80
|
+
/**
|
|
81
|
+
* pdf.js's MOVEMENT_THRESHOLD, adopted verbatim rather than re-derived. Fire
|
|
82
|
+
* when the padding remaining ahead of the motion falls below half the
|
|
83
|
+
* padding already consumed behind it — a directional look-ahead that starts
|
|
84
|
+
* the next raster while the current one still covers the screen.
|
|
85
|
+
*/
|
|
86
|
+
movementThreshold: 0.5,
|
|
87
|
+
/**
|
|
88
|
+
* 250 ms after a scale change. Zoom is coarse and self-correcting — users
|
|
89
|
+
* overshoot and come back — so rastering mid-pinch is usually wasted, and
|
|
90
|
+
* the base layer covers the gap at any scale. Long enough that a two-finger
|
|
91
|
+
* adjust-and-settle produces one raster, not three.
|
|
92
|
+
*/
|
|
93
|
+
quietZoomMs: 250,
|
|
94
|
+
/**
|
|
95
|
+
* 120 ms after a pan-only change, roughly half the zoom delay. Pan is
|
|
96
|
+
* monotonic: content that scrolls off does not come back, so waiting buys
|
|
97
|
+
* nothing, and `detailOverflow` has already covered the first quarter
|
|
98
|
+
* viewport of travel. Comfortably above one 60Hz frame so a wheel notch
|
|
99
|
+
* train still coalesces.
|
|
100
|
+
*/
|
|
101
|
+
quietPanMs: 120,
|
|
102
|
+
/**
|
|
103
|
+
* 700 ms. Under continued input the quiet timer never fires, so without this
|
|
104
|
+
* a slow continuous drag shows the base layer indefinitely. Chosen to sit
|
|
105
|
+
* above `minEpochIntervalMs` plus a typical raster, so the forced raster is
|
|
106
|
+
* never itself immediately superseded.
|
|
107
|
+
*/
|
|
108
|
+
maxStaleMs: 700,
|
|
109
|
+
/**
|
|
110
|
+
* 300 ms — A PLACEHOLDER for the measured p95 raster time on the target
|
|
111
|
+
* device, which has not been measured yet. It should be replaced with a real
|
|
112
|
+
* number, not tuned by feel: the floor exists because an abort cannot
|
|
113
|
+
* interrupt a synchronous platform render, so setting it below the actual
|
|
114
|
+
* raster duration means every raster is superseded before it lands and the
|
|
115
|
+
* detail layer never appears at all.
|
|
116
|
+
*/
|
|
117
|
+
minEpochIntervalMs: 300,
|
|
118
|
+
/**
|
|
119
|
+
* 96 MB. A SOFT cap on cached raster bytes used to pick eviction victims —
|
|
120
|
+
* not a memory limit, because the base rasters and the live epoch are pinned
|
|
121
|
+
* regardless.
|
|
122
|
+
*
|
|
123
|
+
* THE VALUE IS UNCHANGED AND ITS MEANING HAS TIGHTENED. A raster now books ~2x
|
|
124
|
+
* its pixels (`PdfRaster.bytes`, and `rasterByteLength` for why), so the same
|
|
125
|
+
* 96 MB covers half as many pixels as it nominally did before. That is the
|
|
126
|
+
* point: it was always covering half as many, it just did not know, so
|
|
127
|
+
* eviction was picking victims on numbers that were wrong by 2x. A 4 MP base
|
|
128
|
+
* is 32 MB against this budget, not 16 MB.
|
|
129
|
+
*/
|
|
130
|
+
cacheBudgetBytes: 96 * 1024 * 1024,
|
|
131
|
+
/**
|
|
132
|
+
* false. The retiring set is already held across the swap, which is what
|
|
133
|
+
* prevents a one-frame flash; keeping it mounted beyond that doubles the
|
|
134
|
+
* resident detail bytes to defend against a case the base layer already
|
|
135
|
+
* covers. Consumers on a big-memory tablet may prefer true.
|
|
136
|
+
*/
|
|
137
|
+
retainPreviousDetail: false,
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Merge a partial override over the defaults. An explicitly-`undefined` field
|
|
141
|
+
* is IGNORED rather than allowed to erase a default — `{quietPanMs: maybe}`
|
|
142
|
+
* with an unset `maybe` is the common way a host would otherwise nuke a
|
|
143
|
+
* constant and get a zero-delay raster storm.
|
|
144
|
+
*/
|
|
145
|
+
export function resolvePolicy(partial) {
|
|
146
|
+
if (!partial)
|
|
147
|
+
return DEFAULT_POLICY;
|
|
148
|
+
// Spelled out field by field rather than looped: a keyed loop needs an index
|
|
149
|
+
// signature RasterPolicy does not have, and every escape from that is a
|
|
150
|
+
// cast. `??` gives the ignore-undefined behaviour for free, and correctly
|
|
151
|
+
// keeps an explicit `false` for retainPreviousDetail.
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
baseQuality: partial.baseQuality ?? DEFAULT_POLICY.baseQuality,
|
|
154
|
+
baseMaxPixels: partial.baseMaxPixels ?? DEFAULT_POLICY.baseMaxPixels,
|
|
155
|
+
detailOverflow: partial.detailOverflow ?? DEFAULT_POLICY.detailOverflow,
|
|
156
|
+
maxTextureDimension: partial.maxTextureDimension ?? DEFAULT_POLICY.maxTextureDimension,
|
|
157
|
+
maxPieceDimension: partial.maxPieceDimension ?? DEFAULT_POLICY.maxPieceDimension,
|
|
158
|
+
maxRasterPixels: partial.maxRasterPixels ?? DEFAULT_POLICY.maxRasterPixels,
|
|
159
|
+
sharpnessBand: partial.sharpnessBand ?? DEFAULT_POLICY.sharpnessBand,
|
|
160
|
+
movementThreshold: partial.movementThreshold ?? DEFAULT_POLICY.movementThreshold,
|
|
161
|
+
quietZoomMs: partial.quietZoomMs ?? DEFAULT_POLICY.quietZoomMs,
|
|
162
|
+
quietPanMs: partial.quietPanMs ?? DEFAULT_POLICY.quietPanMs,
|
|
163
|
+
maxStaleMs: partial.maxStaleMs ?? DEFAULT_POLICY.maxStaleMs,
|
|
164
|
+
minEpochIntervalMs: partial.minEpochIntervalMs ?? DEFAULT_POLICY.minEpochIntervalMs,
|
|
165
|
+
cacheBudgetBytes: partial.cacheBudgetBytes ?? DEFAULT_POLICY.cacheBudgetBytes,
|
|
166
|
+
retainPreviousDetail: partial.retainPreviousDetail ?? DEFAULT_POLICY.retainPreviousDetail,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript half of the Android backend.
|
|
3
|
+
*
|
|
4
|
+
* THIN, AND IT USED TO BE THE WHOLE THING. Everything that is not
|
|
5
|
+
* Android-specific — error mapping, page-geometry marshalling, the transport
|
|
6
|
+
* negotiation, the base64 decode, the cancellation wiring, the phase timings —
|
|
7
|
+
* moved to `./native-bridge.ts` when the iOS backend landed, because the two
|
|
8
|
+
* platforms speak the identical wire format and the spec's claim for the iOS step
|
|
9
|
+
* is that the seam is a seam. What is left here is the four things that are
|
|
10
|
+
* genuinely Android's: the id, the capabilities, the alpha encoding, and what
|
|
11
|
+
* `PdfSource` it can accept.
|
|
12
|
+
*
|
|
13
|
+
* The names exported from `./native-bridge.ts` are re-exported below under their
|
|
14
|
+
* original `Android*` spellings, so nothing that imported them from here breaks.
|
|
15
|
+
*
|
|
16
|
+
* PURE ON PURPOSE. Nothing here imports `react-native`, which is what lets the
|
|
17
|
+
* whole adapter be tested in vitest against a fake native module.
|
|
18
|
+
* `./index.native.ts` is the only file that resolves the real one, and it does
|
|
19
|
+
* nothing except hand it to `createAndroidRasterizer`.
|
|
20
|
+
*
|
|
21
|
+
* The native side is `android/src/main/java/tools/reekon/pdfcanvas/`, a
|
|
22
|
+
* React-free Android library that is proven on device by
|
|
23
|
+
* `./gradlew connectedDebugAndroidTest`. That split is the reason a bug here is
|
|
24
|
+
* a bug in marshalling and never a bug in rasterizing.
|
|
25
|
+
*/
|
|
26
|
+
import type { NativeRasterizerHandle, NativeRotationSupport, NativeOpenResult, NativePdfPage, NativeRenderRequest, NativeRenderResult, NativeSource, NativeTransport, PdfCanvasNativeModule } from './native-bridge.js';
|
|
27
|
+
import type { PageRasterizer } from '../types.js';
|
|
28
|
+
export { TRANSPORT_PROBE_BYTES, bytesFromResult, decodeBase64, lookupTakePixels, negotiateTransport, toPageGeometry, } from './native-bridge.js';
|
|
29
|
+
export type { NativeRasterizerHandle, NativeRotationSupport, NativeOpenResult, NativePdfPage, NativeRenderRequest, NativeRenderResult, NativeSource, NativeTransport, PdfCanvasNativeModule, TakePixels, } from './native-bridge.js';
|
|
30
|
+
/**
|
|
31
|
+
* ALIASES, NOT COPIES. These were the shipped names before the wire format was
|
|
32
|
+
* shared with iOS, and a consumer that imported one should not have to care that
|
|
33
|
+
* a second backend now speaks it too.
|
|
34
|
+
*/
|
|
35
|
+
export type AndroidRotationSupport = NativeRotationSupport;
|
|
36
|
+
export type AndroidNativePage = NativePdfPage;
|
|
37
|
+
export type AndroidNativeOpenResult = NativeOpenResult;
|
|
38
|
+
export type AndroidNativeRenderResult = NativeRenderResult;
|
|
39
|
+
export type AndroidNativeRenderRequest = NativeRenderRequest;
|
|
40
|
+
export type AndroidNativeSource = NativeSource;
|
|
41
|
+
export type AndroidPdfNativeModule = PdfCanvasNativeModule;
|
|
42
|
+
export type AndroidTransport = NativeTransport;
|
|
43
|
+
export type AndroidRasterizerHandle = NativeRasterizerHandle;
|
|
44
|
+
export interface AndroidRasterizerOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Concurrent renders. Clamped to [1, 4] natively — each slot is one more
|
|
47
|
+
* `PdfRenderer` holding a file descriptor and a parsed document.
|
|
48
|
+
*
|
|
49
|
+
* ONE. It is also what the rasterizer reports as
|
|
50
|
+
* `RasterizerCapabilities.maxConcurrentRenders`, so raising it does two things
|
|
51
|
+
* at once: it opens more native renderers AND it widens the controller's lane
|
|
52
|
+
* bound. Both of those are measured below and both come out negative.
|
|
53
|
+
*
|
|
54
|
+
* THE POOL DOES NOT PARALLELISE PDFium. AOSP serialises `PdfRenderer` behind a
|
|
55
|
+
* process-wide PDFium lock, and the lock does not merely serialise, it CONVOYS.
|
|
56
|
+
* On an LG V60 (Snapdragon 865, 8 cores, API 33) at the field report's scale of
|
|
57
|
+
* 131.25 device px/pt with 1024 px tiles, over a real field-measure drawing,
|
|
58
|
+
* four threads inflate the per-tile PDFium phases `openPage` 16.2x,
|
|
59
|
+
* `PdfRenderer.render` 10.9x and `recycle+close` 33.6x, for a whole-tile
|
|
60
|
+
* inflation of 5.84x, against 1.1x-5.3x for every non-PDFium phase. On a dense
|
|
61
|
+
* electrical schematic it is worse still: `render` 30.9x, `openPage` 11.2x.
|
|
62
|
+
* A pure-Java control on the same threads scales 2.8x. So the native calls do
|
|
63
|
+
* not overlap, and contending for them makes each one dramatically slower.
|
|
64
|
+
*
|
|
65
|
+
* NOTHING ABOUT THIS CARRIES OVER TO iOS, and the iOS option says so on its own
|
|
66
|
+
* doc comment: the lock being measured here is AOSP's, in
|
|
67
|
+
* `android_graphics_pdf_PdfRenderer.cpp`, and CoreGraphics has no counterpart
|
|
68
|
+
* to it that anyone has measured.
|
|
69
|
+
*
|
|
70
|
+
* WHY THIS WAS 2, AND WHY THAT REASON IS GONE. The second slot never existed to
|
|
71
|
+
* rasterize in parallel; it existed to PIPELINE, hiding the JS thread's
|
|
72
|
+
* `decodeBase64` (~1005 ms per tile, 90.6% of the tile) behind the next tile's
|
|
73
|
+
* native work. The JSI transport deleted that cost outright — the JS thread's
|
|
74
|
+
* whole share is now a pointer wrap — so there is nothing left to hide. Worse,
|
|
75
|
+
* deleting the base64 ENCODE from the Java side deleted the only phase that was
|
|
76
|
+
* genuinely parallel: on the real drawing a tile was 72.7% parallelisable work
|
|
77
|
+
* (19.3 ms of encode inside 30.6 ms) and is now 35.6% (4.6 ms of bitmap fill,
|
|
78
|
+
* pixel copy and JNI store inside 13.0 ms), with the PDFium-serialised part
|
|
79
|
+
* risen from 25.5% to 60.2%. Amdahl caps that at 1.67x before the convoy is
|
|
80
|
+
* counted, and the convoy is what turns the remaining headroom negative.
|
|
81
|
+
*
|
|
82
|
+
* MEASURED, on the JSI transport, by `laneScheduleOnTheJsiTransport` in
|
|
83
|
+
* `PdfCanvasPhaseTimingTest` — N lanes over one shared cursor of 6 plan items,
|
|
84
|
+
* which is the schedule the controller actually runs, 21 interleaved rounds per
|
|
85
|
+
* lane count. Mean epoch wall clock relative to one lane, higher is better:
|
|
86
|
+
*
|
|
87
|
+
* - real field-measure drawing, 4 independent passes:
|
|
88
|
+
* 2 lanes 0.82x / 0.81x / 0.95x / 0.76x,
|
|
89
|
+
* 3 lanes 0.54x / 0.61x / 0.85x / 0.58x,
|
|
90
|
+
* 4 lanes 0.59x / 0.58x / 0.92x / 0.55x. One lane is never beaten.
|
|
91
|
+
* - dense electrical schematic: 2 lanes 0.48x, 3 lanes 0.41x, 4 lanes 0.38x.
|
|
92
|
+
* - THE TAIL IS WHERE IT HURTS MOST, and the tail is what a user sees as a
|
|
93
|
+
* stutter. Worst-of-21 on the drawing: 126-172 ms at one lane against
|
|
94
|
+
* 177-238 ms at two. On the schematic: 134 ms against 321 ms.
|
|
95
|
+
*
|
|
96
|
+
* ONE IS ALSO THE CHEAPER OBJECT: one file descriptor and one parsed document
|
|
97
|
+
* instead of two, which on a large drawing is the larger of the two costs.
|
|
98
|
+
*
|
|
99
|
+
* DO NOT READ THE SYNTHETIC FIXTURE'S NUMBERS AS AGREEING OR DISAGREEING. With
|
|
100
|
+
* no `customer.pdf` pushed, `PdfCanvasPhaseTimingTest` falls back to a
|
|
101
|
+
* vector-rects-and-lines page whose `PdfRenderer.render` is 0.8 ms rather than
|
|
102
|
+
* 6.5 ms; that shrinks the serialised fraction, and two lanes duly measure
|
|
103
|
+
* 1.06x-1.25x there. The fixture warns that it is an optimistic bound, and this
|
|
104
|
+
* is exactly the decision it would have inverted. Push a real drawing before
|
|
105
|
+
* re-running this.
|
|
106
|
+
*/
|
|
107
|
+
poolSize?: number;
|
|
108
|
+
id?: string;
|
|
109
|
+
/**
|
|
110
|
+
* How to find the installed JSI host function. Injected only by tests; the
|
|
111
|
+
* default reads `globalThis`.
|
|
112
|
+
*/
|
|
113
|
+
lookupTakePixels?: () => import('./native-bridge.js').TakePixels | null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Wraps a native module in a `PageRasterizer`.
|
|
117
|
+
*
|
|
118
|
+
* The native module is INJECTED rather than imported so this file stays pure.
|
|
119
|
+
* `getDefaultRasterizer()` in `./index.native.ts` supplies the real one.
|
|
120
|
+
*/
|
|
121
|
+
export declare function createAndroidRasterizer(native: AndroidPdfNativeModule, options?: AndroidRasterizerOptions): PageRasterizer;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript half of the Android backend.
|
|
3
|
+
*
|
|
4
|
+
* THIN, AND IT USED TO BE THE WHOLE THING. Everything that is not
|
|
5
|
+
* Android-specific — error mapping, page-geometry marshalling, the transport
|
|
6
|
+
* negotiation, the base64 decode, the cancellation wiring, the phase timings —
|
|
7
|
+
* moved to `./native-bridge.ts` when the iOS backend landed, because the two
|
|
8
|
+
* platforms speak the identical wire format and the spec's claim for the iOS step
|
|
9
|
+
* is that the seam is a seam. What is left here is the four things that are
|
|
10
|
+
* genuinely Android's: the id, the capabilities, the alpha encoding, and what
|
|
11
|
+
* `PdfSource` it can accept.
|
|
12
|
+
*
|
|
13
|
+
* The names exported from `./native-bridge.ts` are re-exported below under their
|
|
14
|
+
* original `Android*` spellings, so nothing that imported them from here breaks.
|
|
15
|
+
*
|
|
16
|
+
* PURE ON PURPOSE. Nothing here imports `react-native`, which is what lets the
|
|
17
|
+
* whole adapter be tested in vitest against a fake native module.
|
|
18
|
+
* `./index.native.ts` is the only file that resolves the real one, and it does
|
|
19
|
+
* nothing except hand it to `createAndroidRasterizer`.
|
|
20
|
+
*
|
|
21
|
+
* The native side is `android/src/main/java/tools/reekon/pdfcanvas/`, a
|
|
22
|
+
* React-free Android library that is proven on device by
|
|
23
|
+
* `./gradlew connectedDebugAndroidTest`. That split is the reason a bug here is
|
|
24
|
+
* a bug in marshalling and never a bug in rasterizing.
|
|
25
|
+
*/
|
|
26
|
+
import { createNativeRasterizer } from './native-bridge.js';
|
|
27
|
+
import { PdfError } from '../types.js';
|
|
28
|
+
export { TRANSPORT_PROBE_BYTES, bytesFromResult, decodeBase64, lookupTakePixels, negotiateTransport, toPageGeometry, } from './native-bridge.js';
|
|
29
|
+
/* ------------------------------------------------------------------ *
|
|
30
|
+
* What is actually Android's
|
|
31
|
+
* ------------------------------------------------------------------ */
|
|
32
|
+
/**
|
|
33
|
+
* A CONSTANT AND NOT PART OF THE WIRE RESULT, unlike `format`.
|
|
34
|
+
*
|
|
35
|
+
* `format` is per-render because the Java side could in principle hand back
|
|
36
|
+
* either byte order; the alpha encoding cannot vary, because it is a property of
|
|
37
|
+
* `Bitmap` itself. `PdfCanvasRasterizer.extract` reads an ARGB_8888 Bitmap, and
|
|
38
|
+
* Android bitmaps are PREMULTIPLIED unless somebody calls
|
|
39
|
+
* `setPremultiplied(false)` — which that code deliberately never does, since
|
|
40
|
+
* un-premultiplying would make a `background: 'transparent'` raster composite
|
|
41
|
+
* wrongly rather than fail visibly. So there is nothing for the native side to
|
|
42
|
+
* report and no reason to widen the wire format to carry it.
|
|
43
|
+
*
|
|
44
|
+
* IT MATCHES iOS AND THAT IS A COINCIDENCE OF MECHANISM, not a shared choice.
|
|
45
|
+
* `CGBitmapContext` has no non-premultiplied 8-bit RGBA configuration at all —
|
|
46
|
+
* Apple's supported-formats table offers premultiplied or alpha-none and nothing
|
|
47
|
+
* between — so `ios.ts` declares the same value for a completely different
|
|
48
|
+
* reason. PDFium and pdf.js, meanwhile, both emit STRAIGHT alpha (measured — see
|
|
49
|
+
* `AlphaEncoding`), which is the whole reason `RasterPixels.alpha` exists rather
|
|
50
|
+
* than a single `AlphaType.Premul` in `ingest.ts`. Do not "align" any of these
|
|
51
|
+
* four values with each other.
|
|
52
|
+
*/
|
|
53
|
+
const ANDROID_ALPHA = 'premultiplied';
|
|
54
|
+
const CAPABILITIES = {
|
|
55
|
+
/**
|
|
56
|
+
* True in the sense the field means — annotations ARE drawn into the raster.
|
|
57
|
+
*
|
|
58
|
+
* What is NOT true, and has no field to say so: they cannot be turned OFF.
|
|
59
|
+
* `PdfRenderer.Page.render` takes a render mode and no annotation switch, and
|
|
60
|
+
* PDFium's `FPDF_ANNOT` flag is unreachable through the platform class at
|
|
61
|
+
* every API level. So `RasterRequest.annotations: false` — which is what the
|
|
62
|
+
* controller passes for every tile — is recorded and ignored, and an app that
|
|
63
|
+
* draws its own copy of a PDF-embedded annotation will see it twice on
|
|
64
|
+
* Android. The fix is a PDFium backend, not a different mode here.
|
|
65
|
+
*
|
|
66
|
+
* iOS DOES HONOUR THE FALSE CASE, which is worth knowing when a bug report
|
|
67
|
+
* says annotations appear twice on one platform only: `ios.ts` picks
|
|
68
|
+
* `CGContextDrawPDFPage` for `false` and PDFKit for `true`, and the two really
|
|
69
|
+
* do differ in what they composite.
|
|
70
|
+
*/
|
|
71
|
+
annotations: true,
|
|
72
|
+
/**
|
|
73
|
+
* False, and honestly so. `PdfRenderer.Page.render` is synchronous and
|
|
74
|
+
* uninterruptible once entered; an abort can only stop the NEXT piece
|
|
75
|
+
* starting, or discard a finished raster that is no longer wanted.
|
|
76
|
+
*/
|
|
77
|
+
interruptibleRender: false,
|
|
78
|
+
text: false,
|
|
79
|
+
search: false,
|
|
80
|
+
links: false,
|
|
81
|
+
// Overwritten per rasterizer with the pool size it was actually opened with —
|
|
82
|
+
// see `createNativeRasterizer`. The constant carries the conservative value so
|
|
83
|
+
// a partially-constructed object is never the fast one.
|
|
84
|
+
maxConcurrentRenders: 1,
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Mirrors `PdfRendererPool.MAX_POOL_SIZE` on the Java side, which is where the
|
|
88
|
+
* clamp is actually enforced (`PdfRendererPool.open`). Restating it here is not
|
|
89
|
+
* a second source of truth: it is what makes the DECLARED capability match the
|
|
90
|
+
* pool the native side will really hand out, rather than promising four lanes to
|
|
91
|
+
* a pool that silently opened with four... or with one.
|
|
92
|
+
*/
|
|
93
|
+
const MAX_POOL_SIZE = 4;
|
|
94
|
+
const clampPoolSize = (size) => Math.max(1, Math.min(MAX_POOL_SIZE, Math.floor(size) || 1));
|
|
95
|
+
function toNativeSource(source) {
|
|
96
|
+
const password = source.password;
|
|
97
|
+
if ('uri' in source) {
|
|
98
|
+
return password === undefined
|
|
99
|
+
? { uri: source.uri }
|
|
100
|
+
: { uri: source.uri, password };
|
|
101
|
+
}
|
|
102
|
+
throw new PdfError('unsupported', 'Passing a document as bytes is not wired through the Android binding yet. ' +
|
|
103
|
+
'The native side supports it (PdfCanvasRasterizer.openBytes spools to a ' +
|
|
104
|
+
'file, because PdfRenderer needs a seekable descriptor), but sending the ' +
|
|
105
|
+
'bytes over the bridge costs a base64 round trip of the whole document. ' +
|
|
106
|
+
'Pass a `uri` instead.');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Wraps a native module in a `PageRasterizer`.
|
|
110
|
+
*
|
|
111
|
+
* The native module is INJECTED rather than imported so this file stays pure.
|
|
112
|
+
* `getDefaultRasterizer()` in `./index.native.ts` supplies the real one.
|
|
113
|
+
*/
|
|
114
|
+
export function createAndroidRasterizer(native, options = {}) {
|
|
115
|
+
return createNativeRasterizer(native, {
|
|
116
|
+
id: options.id ?? 'android-pdfrenderer',
|
|
117
|
+
capabilities: CAPABILITIES,
|
|
118
|
+
alpha: ANDROID_ALPHA,
|
|
119
|
+
poolSize: clampPoolSize(options.poolSize ?? 1),
|
|
120
|
+
label: 'Android',
|
|
121
|
+
toNativeSource,
|
|
122
|
+
...(options.lookupTakePixels === undefined
|
|
123
|
+
? {}
|
|
124
|
+
: { lookupTakePixels: options.lookupTakePixels }),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tight-buffer predicate, stated ONCE.
|
|
3
|
+
*
|
|
4
|
+
* `Skia.Data.fromBytes` on native reads `array.getProperty("buffer")` and copies
|
|
5
|
+
* the WHOLE ArrayBuffer, ignoring `byteOffset` and `byteLength`
|
|
6
|
+
* (JsiSkDataFactory.h). So a Uint8Array that is a VIEW into something larger
|
|
7
|
+
* ingests shifted bytes and shears the image, and `ingest.ts` copies it to a
|
|
8
|
+
* tight buffer first (`tighten`).
|
|
9
|
+
*
|
|
10
|
+
* This predicate is also the thing a new TRANSPORT has to satisfy to make that
|
|
11
|
+
* copy disappear: a native path that hands back an ArrayBuffer which is exactly
|
|
12
|
+
* the raster and nothing more is tight by construction, and `tighten` becomes a
|
|
13
|
+
* no-op. That makes the predicate a fact worth REPORTING (the backend puts it in
|
|
14
|
+
* `RasterPhaseTimings.tight`), not merely a branch — which is why it lives in
|
|
15
|
+
* its own pure module rather than inline in `tighten`, where the transport work
|
|
16
|
+
* could not see it without importing Skia.
|
|
17
|
+
*/
|
|
18
|
+
export declare const isTightBuffer: (bytes: Uint8Array) => boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tight-buffer predicate, stated ONCE.
|
|
3
|
+
*
|
|
4
|
+
* `Skia.Data.fromBytes` on native reads `array.getProperty("buffer")` and copies
|
|
5
|
+
* the WHOLE ArrayBuffer, ignoring `byteOffset` and `byteLength`
|
|
6
|
+
* (JsiSkDataFactory.h). So a Uint8Array that is a VIEW into something larger
|
|
7
|
+
* ingests shifted bytes and shears the image, and `ingest.ts` copies it to a
|
|
8
|
+
* tight buffer first (`tighten`).
|
|
9
|
+
*
|
|
10
|
+
* This predicate is also the thing a new TRANSPORT has to satisfy to make that
|
|
11
|
+
* copy disappear: a native path that hands back an ArrayBuffer which is exactly
|
|
12
|
+
* the raster and nothing more is tight by construction, and `tighten` becomes a
|
|
13
|
+
* no-op. That makes the predicate a fact worth REPORTING (the backend puts it in
|
|
14
|
+
* `RasterPhaseTimings.tight`), not merely a branch — which is why it lives in
|
|
15
|
+
* its own pure module rather than inline in `tighten`, where the transport work
|
|
16
|
+
* could not see it without importing Skia.
|
|
17
|
+
*/
|
|
18
|
+
export const isTightBuffer = (bytes) => bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deterministic PageRasterizer with no Skia, no native module and no PDF.
|
|
3
|
+
*
|
|
4
|
+
* It exists to drive the entire pipeline — planner, cadence, epoch swap, cache
|
|
5
|
+
* — in vitest and in a demo app, and to be the fixture that CATCHES
|
|
6
|
+
* REGISTRATION BUGS. The pattern is chosen for that second job:
|
|
7
|
+
*
|
|
8
|
+
* - the checkerboard's cell size is in DOC units, not pixels, so two
|
|
9
|
+
* correctly-placed pieces show one continuous board across their overlap
|
|
10
|
+
* and a piece drawn at the wrong doc rect (or squashed by a src-rect
|
|
11
|
+
* mistake) shows an obvious seam;
|
|
12
|
+
* - the board is INFINITE in doc space and is not clipped to the page box, so
|
|
13
|
+
* a detail piece that overflows the page edge still shows continuous
|
|
14
|
+
* checks and an accidental clip is visible as a blank margin;
|
|
15
|
+
* - each piece carries a 1px border, so a piece that has been inset or
|
|
16
|
+
* stretched shows its border in the wrong place;
|
|
17
|
+
* - each piece encodes its own doc rect into a corner swatch, so a test can
|
|
18
|
+
* read back what a raster BELIEVES it is without any Skia in the loop.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is a pure function of the request, so the same request always
|
|
21
|
+
* produces byte-identical output.
|
|
22
|
+
*/
|
|
23
|
+
import type { DocRect, DocSize, PageRasterizer, PageRotation, PdfErrorCode, PixelFormat, PixelSize, RasterPixels, RasterRequest } from '../types.js';
|
|
24
|
+
export declare const LETTER: DocSize;
|
|
25
|
+
/** ARCH D, 24x36in — the sheet the base-raster sizing rule is calibrated on. */
|
|
26
|
+
export declare const ARCH_D: DocSize;
|
|
27
|
+
/** ARCH E, 36x48in. */
|
|
28
|
+
export declare const ARCH_E: DocSize;
|
|
29
|
+
export interface FakeFailureModes {
|
|
30
|
+
/** `open()` rejects with this code. */
|
|
31
|
+
open?: PdfErrorCode;
|
|
32
|
+
/**
|
|
33
|
+
* `render()` rejects with this code. The function form lets a test fail one
|
|
34
|
+
* page, one scale or one call and let the rest succeed.
|
|
35
|
+
*/
|
|
36
|
+
render?: PdfErrorCode | ((request: RasterRequest) => PdfErrorCode | null);
|
|
37
|
+
}
|
|
38
|
+
export interface FakeRasterizerOptions {
|
|
39
|
+
/** Surfaced as `PageRasterizer.id` and in diagnostics. */
|
|
40
|
+
id?: string;
|
|
41
|
+
/** Cycled if `pageCount` exceeds its length. Defaults to a single LETTER. */
|
|
42
|
+
pageSizes?: readonly DocSize[];
|
|
43
|
+
/** Defaults to `pageSizes.length`, or 3. */
|
|
44
|
+
pageCount?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Cycled, defaults to 0. Informational only: per the contract, page width and
|
|
47
|
+
* height are already post-rotation, so the fake does not swap them.
|
|
48
|
+
*/
|
|
49
|
+
pageRotations?: readonly PageRotation[];
|
|
50
|
+
/** Checker cell size in DOC units. */
|
|
51
|
+
checkerDocSize?: number;
|
|
52
|
+
format?: PixelFormat;
|
|
53
|
+
/** Artificial delay on `render()` only; `open()` always resolves promptly. */
|
|
54
|
+
latencyMs?: number;
|
|
55
|
+
/** When set, `open()` enforces it against `source.password`. */
|
|
56
|
+
password?: string;
|
|
57
|
+
fail?: FakeFailureModes;
|
|
58
|
+
/** Refuse absurd requests so a planner bug is a test failure, not an OOM. */
|
|
59
|
+
maxPixels?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Reported as `RasterizerCapabilities.maxConcurrentRenders`.
|
|
62
|
+
*
|
|
63
|
+
* DEFAULTS TO 1, which is the honest answer for the pixel loop below: it is
|
|
64
|
+
* synchronous, so a second `render` cannot make progress while the first is
|
|
65
|
+
* running whatever the caller does. Raise it in a test or a demo that wants
|
|
66
|
+
* the controller's LANES exercised — with `latencyMs` set, the fake's awaits
|
|
67
|
+
* genuinely interleave and the schedule is what is under test, not the pixels.
|
|
68
|
+
*/
|
|
69
|
+
maxConcurrentRenders?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The pixel size the fake renders a request at.
|
|
73
|
+
*
|
|
74
|
+
* Exported because RasterRequest carries no PixelSize — a backend derives it —
|
|
75
|
+
* and a test that wants to index into the output needs the same arithmetic.
|
|
76
|
+
* Rounding (rather than ceiling) keeps the raster closest to the nominal
|
|
77
|
+
* scale; either is safe, because `PdfRaster.docRect` is the request's doc rect
|
|
78
|
+
* regardless and `fit="fill"` absorbs the sub-pixel difference.
|
|
79
|
+
*/
|
|
80
|
+
export declare const fakePixelSize: (request: RasterRequest) => PixelSize;
|
|
81
|
+
/**
|
|
82
|
+
* A distinct flat colour per page. Golden-angle hue rotation puts consecutive
|
|
83
|
+
* pages maximally far apart in hue, so page 4 is never mistaken for page 5 in a
|
|
84
|
+
* screenshot of a scrolling document.
|
|
85
|
+
*/
|
|
86
|
+
export declare function fakePageColor(page: number): [number, number, number];
|
|
87
|
+
/**
|
|
88
|
+
* Read the doc rect a fake raster believes it covers, or null when the raster
|
|
89
|
+
* was too small to carry a swatch.
|
|
90
|
+
*/
|
|
91
|
+
export declare function decodeFakeDocRect(pixels: RasterPixels): DocRect | null;
|
|
92
|
+
export declare function createFakeRasterizer(options?: FakeRasterizerOptions): PageRasterizer;
|