@reekon-tools/react-native-pdf-canvas 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +57 -0
- package/PdfCanvas.podspec +68 -39
- package/README.md +213 -1316
- package/android/build.gradle +67 -54
- package/android/consumer-rules.pro +7 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
- package/android/src/main/cpp/CMakeLists.txt +54 -0
- package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
- package/android/tools/compile-gate.sh +81 -101
- package/dist/controller.d.ts +14 -0
- package/dist/controller.js +4 -3
- package/dist/index.native.d.ts +5 -0
- package/dist/index.native.js +5 -0
- package/dist/index.web.d.ts +10 -0
- package/dist/index.web.js +10 -0
- package/dist/rasterizer/index.native.d.ts +8 -12
- package/dist/rasterizer/index.native.js +15 -21
- package/dist/rasterizer/native-bridge.d.ts +51 -75
- package/dist/rasterizer/native-bridge.js +49 -33
- package/dist/rasterizer/native.d.ts +45 -0
- package/dist/rasterizer/native.js +123 -0
- package/dist/react/usePdfDocument.d.ts +5 -5
- package/dist/react/usePdfLayer.d.ts +10 -1
- package/dist/react/usePdfLayer.js +10 -2
- package/dist/skia/index.native.d.ts +7 -3
- package/dist/skia/index.native.js +7 -3
- package/dist/skia/index.web.d.ts +4 -2
- package/dist/skia/index.web.js +4 -2
- package/dist/testing/scenes.d.ts +3 -3
- package/dist/testing/scenes.js +4 -4
- package/dist/types.d.ts +36 -42
- package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
- package/native/.clangd +5 -0
- package/native/CMakeLists.txt +89 -0
- package/native/core/include/pdfcanvas/document.h +90 -0
- package/native/core/include/pdfcanvas/error.h +47 -0
- package/native/core/include/pdfcanvas/jsi.h +18 -0
- package/native/core/include/pdfcanvas/jsi_config.h +30 -0
- package/native/core/include/pdfcanvas/library.h +28 -0
- package/native/core/include/pdfcanvas/pixels.h +92 -0
- package/native/core/include/pdfcanvas/service.h +87 -0
- package/native/core/include/pdfcanvas/slots.h +113 -0
- package/native/core/include/pdfcanvas/types.h +110 -0
- package/native/core/pdfcanvas-core.cmake +22 -0
- package/native/core/src/document.cpp +516 -0
- package/native/core/src/error.cpp +29 -0
- package/native/core/src/jsi.cpp +66 -0
- package/native/core/src/library.cpp +52 -0
- package/native/core/src/pixels.cpp +45 -0
- package/native/core/src/service.cpp +156 -0
- package/native/core/src/slots.cpp +139 -0
- package/native/tests/fixtures.cpp +451 -0
- package/native/tests/fixtures.h +96 -0
- package/native/tests/harness.h +122 -0
- package/native/tests/main.cpp +64 -0
- package/native/tests/pixels_util.h +126 -0
- package/native/tests/test_document.cpp +485 -0
- package/native/tests/test_service.cpp +130 -0
- package/native/tests/test_slots.cpp +250 -0
- package/package.json +38 -8
- package/react-native.config.js +5 -2
- package/scripts/fetch-pdfium.mjs +487 -0
- package/scripts/pdfium-manifest.json +46 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
- package/android/src/jsi/cpp/CMakeLists.txt +0 -33
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
- package/dist/rasterizer/android.d.ts +0 -121
- package/dist/rasterizer/android.js +0 -126
- package/dist/rasterizer/ios.d.ts +0 -84
- package/dist/rasterizer/ios.js +0 -155
- package/ios/Package.swift +0 -58
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import android.graphics.pdf.PdfRenderer;
|
|
4
|
-
import android.os.ParcelFileDescriptor;
|
|
5
|
-
|
|
6
|
-
import java.io.File;
|
|
7
|
-
import java.io.IOException;
|
|
8
|
-
import java.util.ArrayList;
|
|
9
|
-
import java.util.List;
|
|
10
|
-
import java.util.concurrent.ArrayBlockingQueue;
|
|
11
|
-
import java.util.concurrent.BlockingQueue;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A fixed set of {@link PdfRenderer} instances over the same file, leased one at
|
|
15
|
-
* a time.
|
|
16
|
-
*
|
|
17
|
-
* WHY A POOL EXISTS AT ALL. {@code PdfRenderer} permits exactly ONE open page
|
|
18
|
-
* per instance: {@code openPage} throws {@code IllegalStateException}
|
|
19
|
-
* ("Current page not closed") if another is already open. That is a per-instance
|
|
20
|
-
* rule, not a per-process one, so the thing that does not work is TWO THREADS ON
|
|
21
|
-
* ONE RENDERER — not two renderers. Renderer-per-concurrent-render is the only
|
|
22
|
-
* way to have more than one raster in flight, and it is what this class hands
|
|
23
|
-
* out. Nothing in here makes a single renderer thread-safe, and nothing should
|
|
24
|
-
* try: the lease is the mutual exclusion.
|
|
25
|
-
*
|
|
26
|
-
* WHY NOT {@code ParcelFileDescriptor.dup()}. It is the obvious way to give each
|
|
27
|
-
* renderer its own fd, and it is wrong here. {@code dup()} is {@code dup(2)},
|
|
28
|
-
* which produces a new descriptor onto the SAME open file description — and the
|
|
29
|
-
* file offset lives in the description, so two dup'd fds share one seek
|
|
30
|
-
* position. AOSP's PdfRenderer reads through an {@code FPDF_FILEACCESS} callback
|
|
31
|
-
* that seeks then reads, so two renderers reading concurrently through dup'd fds
|
|
32
|
-
* are racing on that offset. Today AOSP happens to serialise every PdfRenderer
|
|
33
|
-
* native call behind a process-wide PDFium lock, which hides it; that is a
|
|
34
|
-
* property of one implementation, not of the API. Opening the file afresh per
|
|
35
|
-
* renderer costs one extra {@code open(2)} and gives each renderer an
|
|
36
|
-
* independent file description, which is the invariant the code actually needs.
|
|
37
|
-
*/
|
|
38
|
-
final class PdfRendererPool {
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Ceiling on renderers. Each one holds an fd and PDFium's parsed document
|
|
42
|
-
* structure for the same file, so the memory cost is per-instance and real on
|
|
43
|
-
* a large sheet. Two in flight is already enough to keep a detail raster from
|
|
44
|
-
* queueing behind a base raster, which is the only concurrency the cadence
|
|
45
|
-
* ever asks for.
|
|
46
|
-
*/
|
|
47
|
-
static final int MAX_POOL_SIZE = 4;
|
|
48
|
-
|
|
49
|
-
private final File file;
|
|
50
|
-
private final List<PdfRenderer> all;
|
|
51
|
-
private final BlockingQueue<PdfRenderer> free;
|
|
52
|
-
private final int pageCount;
|
|
53
|
-
private volatile boolean closed;
|
|
54
|
-
|
|
55
|
-
private PdfRendererPool(File file, List<PdfRenderer> renderers, int pageCount) {
|
|
56
|
-
this.file = file;
|
|
57
|
-
this.all = renderers;
|
|
58
|
-
this.free = new ArrayBlockingQueue<>(renderers.size());
|
|
59
|
-
this.free.addAll(renderers);
|
|
60
|
-
this.pageCount = pageCount;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static PdfRendererPool open(File file, String password, int requestedSize)
|
|
64
|
-
throws PdfRasterException {
|
|
65
|
-
// ORDER IS LOAD-BEARING. Preflight runs before anything can construct a
|
|
66
|
-
// PdfRenderer, because on API 24 a throwing constructor poisons PDFium for
|
|
67
|
-
// the whole process — see PdfPreflight for the measurement. The latch then
|
|
68
|
-
// covers files preflight cannot judge.
|
|
69
|
-
PdfPlatformQuirks.requireUsablePdfium();
|
|
70
|
-
PdfPreflight.check(file);
|
|
71
|
-
if (password != null) {
|
|
72
|
-
// compileSdk 34 has no `android.graphics.pdf.LoadParams`, which is where
|
|
73
|
-
// a password became expressible (API 35). Below that the platform class
|
|
74
|
-
// offers no way to supply one at all, so accepting the argument and
|
|
75
|
-
// silently ignoring it would turn "wrong password" into "corrupt file".
|
|
76
|
-
throw new PdfRasterException(
|
|
77
|
-
PdfErrorCode.UNSUPPORTED,
|
|
78
|
-
"A password was supplied, but android.graphics.pdf.PdfRenderer cannot "
|
|
79
|
-
+ "accept one below API 35 (LoadParams). Use a PDFium backend for "
|
|
80
|
-
+ "encrypted documents.");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
int size = Math.max(1, Math.min(MAX_POOL_SIZE, requestedSize));
|
|
84
|
-
List<PdfRenderer> renderers = new ArrayList<>(size);
|
|
85
|
-
int pages = 0;
|
|
86
|
-
try {
|
|
87
|
-
for (int i = 0; i < size; i++) {
|
|
88
|
-
renderers.add(openRenderer(file));
|
|
89
|
-
}
|
|
90
|
-
pages = renderers.get(0).getPageCount();
|
|
91
|
-
} catch (PdfRasterException | RuntimeException e) {
|
|
92
|
-
closeAll(renderers);
|
|
93
|
-
throw e;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (pages <= 0) {
|
|
97
|
-
closeAll(renderers);
|
|
98
|
-
throw new PdfRasterException(
|
|
99
|
-
PdfErrorCode.CORRUPT, "Document reports " + pages + " pages.");
|
|
100
|
-
}
|
|
101
|
-
return new PdfRendererPool(file, renderers, pages);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private static PdfRenderer openRenderer(File file) throws PdfRasterException {
|
|
105
|
-
ParcelFileDescriptor pfd = null;
|
|
106
|
-
try {
|
|
107
|
-
pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
108
|
-
// PdfRenderer takes ownership of the descriptor and closes it in close().
|
|
109
|
-
return new PdfRenderer(pfd);
|
|
110
|
-
} catch (SecurityException e) {
|
|
111
|
-
// Reaching here means preflight missed an encryption scheme it does not
|
|
112
|
-
// recognise. The damage on an affected build is already done — the
|
|
113
|
-
// half-built PdfRenderer is unreachable and finalizable — so all that is
|
|
114
|
-
// left is to stop the next open walking into the segfault.
|
|
115
|
-
PdfPlatformQuirks.recordFailedNativeOpen();
|
|
116
|
-
closeQuietly(pfd);
|
|
117
|
-
// The platform's documented signal for "this file requires a password or
|
|
118
|
-
// uses a security scheme we do not support". No password could have been
|
|
119
|
-
// supplied (open() rejects that above), so this is always "required".
|
|
120
|
-
throw new PdfRasterException(
|
|
121
|
-
PdfErrorCode.PASSWORD_REQUIRED,
|
|
122
|
-
"The document is encrypted: " + file.getName() + ".",
|
|
123
|
-
e);
|
|
124
|
-
} catch (IOException e) {
|
|
125
|
-
PdfPlatformQuirks.recordFailedNativeOpen();
|
|
126
|
-
closeQuietly(pfd);
|
|
127
|
-
throw new PdfRasterException(
|
|
128
|
-
PdfErrorCode.CORRUPT,
|
|
129
|
-
"Not a readable PDF: " + file.getName() + " (" + e.getMessage() + ").",
|
|
130
|
-
e);
|
|
131
|
-
} catch (RuntimeException e) {
|
|
132
|
-
// IllegalArgumentException ("file descriptor not seekable") and anything
|
|
133
|
-
// else the constructor can throw land here. Same reasoning as above: the
|
|
134
|
-
// constructor threw, so the process is on borrowed time.
|
|
135
|
-
PdfPlatformQuirks.recordFailedNativeOpen();
|
|
136
|
-
closeQuietly(pfd);
|
|
137
|
-
throw new PdfRasterException(
|
|
138
|
-
PdfErrorCode.BACKEND_FAILURE,
|
|
139
|
-
"PdfRenderer refused " + file.getName() + ": " + e.getMessage(),
|
|
140
|
-
e);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
int pageCount() {
|
|
145
|
-
return pageCount;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
File file() {
|
|
149
|
-
return file;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
int size() {
|
|
153
|
-
return all.size();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Blocks until a renderer is free. The wait is where a cancel most often lands
|
|
158
|
-
* — a superseded detail raster typically queues behind a base raster — so the
|
|
159
|
-
* caller re-checks its signal on the far side of this call.
|
|
160
|
-
*/
|
|
161
|
-
PdfRenderer lease() throws PdfRasterException {
|
|
162
|
-
if (closed) {
|
|
163
|
-
throw new PdfRasterException(
|
|
164
|
-
PdfErrorCode.BACKEND_FAILURE, "Document handle is closed.");
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
PdfRenderer renderer = free.take();
|
|
168
|
-
if (closed) {
|
|
169
|
-
// close() raced us. Do not hand out a renderer that close() has already
|
|
170
|
-
// closed, and do not put it back either — closeAll owns it now.
|
|
171
|
-
throw new PdfRasterException(
|
|
172
|
-
PdfErrorCode.BACKEND_FAILURE, "Document handle was closed while waiting.");
|
|
173
|
-
}
|
|
174
|
-
return renderer;
|
|
175
|
-
} catch (InterruptedException e) {
|
|
176
|
-
Thread.currentThread().interrupt();
|
|
177
|
-
throw new PdfRasterException(
|
|
178
|
-
PdfErrorCode.CANCELLED, "Interrupted waiting for a PdfRenderer.", e);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Always call from a finally: a leaked lease deadlocks every later render. */
|
|
183
|
-
void release(PdfRenderer renderer) {
|
|
184
|
-
if (renderer != null) {
|
|
185
|
-
free.offer(renderer);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Closes every renderer, including ones currently leased.
|
|
191
|
-
*
|
|
192
|
-
* The leased case is deliberate and it is the only honest option: close() is
|
|
193
|
-
* called from JS when a document unmounts, and blocking the caller until an
|
|
194
|
-
* in-flight ARCH-D render finishes is worse than the alternative. The renderer
|
|
195
|
-
* whose {@code render()} is mid-flight is not closed out from under it —
|
|
196
|
-
* {@code closed} is set first, {@code PdfCanvasRasterizer} re-checks it, and
|
|
197
|
-
* the renderers are closed after. The remaining window is the same one every
|
|
198
|
-
* native handle has.
|
|
199
|
-
*/
|
|
200
|
-
void close() {
|
|
201
|
-
if (closed) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
closed = true;
|
|
205
|
-
free.clear();
|
|
206
|
-
closeAll(all);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
boolean isClosed() {
|
|
210
|
-
return closed;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private static void closeAll(List<PdfRenderer> renderers) {
|
|
214
|
-
for (PdfRenderer renderer : renderers) {
|
|
215
|
-
try {
|
|
216
|
-
renderer.close();
|
|
217
|
-
} catch (RuntimeException ignored) {
|
|
218
|
-
// close() throws IllegalStateException if a page is still open. There is
|
|
219
|
-
// nothing useful to do about it during teardown, and letting it escape
|
|
220
|
-
// would skip closing the remaining renderers and leak their fds.
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private static void closeQuietly(ParcelFileDescriptor pfd) {
|
|
226
|
-
if (pfd != null) {
|
|
227
|
-
try {
|
|
228
|
-
pfd.close();
|
|
229
|
-
} catch (IOException ignored) {
|
|
230
|
-
// Nothing to salvage; the throwing path already has the real cause.
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import java.util.concurrent.atomic.AtomicBoolean;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Best-effort cancellation, and the emphasis is on BEST-EFFORT.
|
|
7
|
-
*
|
|
8
|
-
* {@code PdfRenderer.Page.render} is synchronous and uninterruptible once
|
|
9
|
-
* entered. There is no cancel token, no progress callback, and no safe way to
|
|
10
|
-
* kill the thread — PDFium is mid-way through writing into a Bitmap this process
|
|
11
|
-
* owns. So this class cannot stop a render; it can only stop the NEXT one
|
|
12
|
-
* starting, which is the same shape the TS contract already assumes for PDFKit
|
|
13
|
-
* ({@code RasterPolicy.minEpochIntervalMs} exists because of it).
|
|
14
|
-
*
|
|
15
|
-
* Concretely, a cancel is observed at four points and no others:
|
|
16
|
-
* on entry, after the pool lease (the wait can be long when every renderer is
|
|
17
|
-
* busy), immediately before {@code render()}, and immediately after it. The last
|
|
18
|
-
* one does not save any work — the pixels are already drawn — it only stops a
|
|
19
|
-
* superseded raster being handed back and ingested into Skia. The
|
|
20
|
-
* {@code cancelDuringAnInFlightRenderIsObservedOnlyAfterItFinishes} test asserts
|
|
21
|
-
* that shape rather than pretending otherwise. (It was named here as
|
|
22
|
-
* {@code cancellationCannotInterruptAnInFlightRender}, which does not exist.)
|
|
23
|
-
*/
|
|
24
|
-
public final class RasterCancellation {
|
|
25
|
-
|
|
26
|
-
private final AtomicBoolean cancelled = new AtomicBoolean(false);
|
|
27
|
-
|
|
28
|
-
public void cancel() {
|
|
29
|
-
cancelled.set(true);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public boolean isCancelled() {
|
|
33
|
-
return cancelled.get();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @param stage named in the message so a cancellation reported from the
|
|
38
|
-
* post-render check is distinguishable from one that saved real work.
|
|
39
|
-
*/
|
|
40
|
-
static void throwIfCancelled(RasterCancellation signal, String stage)
|
|
41
|
-
throws PdfRasterException {
|
|
42
|
-
if (signal != null && signal.isCancelled()) {
|
|
43
|
-
throw new PdfRasterException(
|
|
44
|
-
PdfErrorCode.CANCELLED, "Render cancelled (" + stage + ").");
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import tools.reekon.pdfcanvas.jsi.PdfCanvasPixelBridge;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A finished raster, mirroring {@code RasterPixels} in {@code src/types.ts}.
|
|
7
|
-
*
|
|
8
|
-
* {@link #rowBytes} is STATED, never assumed to be {@code width * 4}. It is read
|
|
9
|
-
* back from {@code Bitmap.getRowBytes()}, because a wrong stride does not fail —
|
|
10
|
-
* it shears the image, one pixel further per row, which reads as a plausible
|
|
11
|
-
* skew rather than an error.
|
|
12
|
-
*
|
|
13
|
-
* HOW FAR THAT IS PROVEN, precisely. {@code rowBytesIsReportedNotAssumed} checks
|
|
14
|
-
* the value against the stride the platform itself chose for an identically
|
|
15
|
-
* sized {@code ARGB_8888} bitmap, at seven widths including odd and prime ones.
|
|
16
|
-
* What it CANNOT show is the difference between reading the stride and assuming
|
|
17
|
-
* {@code width * 4}: {@code Bitmap.createBitmap} allocates through
|
|
18
|
-
* {@code SkImageInfo::minRowBytes()}, which is exactly {@code width * 4} with no
|
|
19
|
-
* alignment, so on API 24, 31 and 33 every measured stride is tight and the two
|
|
20
|
-
* implementations produce identical output. The padded case is exercised on the
|
|
21
|
-
* JS side instead, where the fake backend pads on demand. Reading the value is
|
|
22
|
-
* therefore the correct thing to do and is what happens here — but treat "we
|
|
23
|
-
* read it" as a design decision that the device suite agrees with, not as
|
|
24
|
-
* something the device suite could have falsified.
|
|
25
|
-
*
|
|
26
|
-
* <h3>Where the pixels actually are</h3>
|
|
27
|
-
*
|
|
28
|
-
* ONE OF TWO PLACES, and {@link #bytes} being null is how you tell:
|
|
29
|
-
*
|
|
30
|
-
* <ul>
|
|
31
|
-
* <li>ON THE JAVA HEAP — {@code bytes} is the raster and {@link #slot} is
|
|
32
|
-
* {@link #NO_SLOT}. Every caller that reads pixels wants this, and it is
|
|
33
|
-
* what {@link PdfCanvasRasterizer#render} returns.
|
|
34
|
-
* <li>IN A NATIVE SLOT — {@code bytes} is null and {@code slot} names a buffer
|
|
35
|
-
* in {@code pdfcanvas-jsi.cpp} waiting to become a JS {@code ArrayBuffer}.
|
|
36
|
-
* Only {@link PdfCanvasRasterizer#renderToNativeSlot} produces this, only
|
|
37
|
-
* the React binding asks for it, and NOTHING can read the pixels back from
|
|
38
|
-
* Java — the whole point is that they never touch the Java heap.
|
|
39
|
-
* </ul>
|
|
40
|
-
*
|
|
41
|
-
* That is a deliberately awkward shape rather than two classes, because the one
|
|
42
|
-
* caller that asks for a slot has to handle both anyway: the native store is
|
|
43
|
-
* allowed to refuse (see
|
|
44
|
-
* {@link PdfCanvasPixelBridge#nativeStoreFromBitmap}) and the heap is the
|
|
45
|
-
* fallback, so a slot request can legitimately come back heap-backed.
|
|
46
|
-
* {@link #channel} and {@link #offsetOf} therefore fail LOUDLY and by name on a
|
|
47
|
-
* slot-backed raster instead of throwing a bare NPE from somewhere further in.
|
|
48
|
-
*
|
|
49
|
-
* <h3>Who frees the slot</h3>
|
|
50
|
-
*
|
|
51
|
-
* Normally nobody here: the buffer is taken exactly once by
|
|
52
|
-
* {@code __pdfCanvasTakePixels} and freed when the JS {@code ArrayBuffer} is
|
|
53
|
-
* collected. {@link #release()} exists for the paths where the raster is
|
|
54
|
-
* ABANDONED after it was filled — a cancellation observed after the draw, or a
|
|
55
|
-
* throw between the render finishing and the promise resolving. Without it those
|
|
56
|
-
* megabytes would sit in the slot map until the next
|
|
57
|
-
* {@code nativeReleaseAll()}, which only happens on a reload.
|
|
58
|
-
*/
|
|
59
|
-
public final class RasterPixels {
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The only format this backend emits.
|
|
63
|
-
*
|
|
64
|
-
* {@code Bitmap.Config.ARGB_8888} is named for the packed 32-bit int that
|
|
65
|
-
* {@code getPixel} returns, but the IN-MEMORY byte order is R, G, B, A —
|
|
66
|
-
* which is what {@code copyPixelsToBuffer} produces and what Skia calls
|
|
67
|
-
* {@code kRGBA_8888}. The name and the layout disagree, which is precisely the
|
|
68
|
-
* kind of thing that gets assumed instead of checked, so it is measured.
|
|
69
|
-
*/
|
|
70
|
-
public static final String FORMAT_RGBA_8888 = "rgba8888";
|
|
71
|
-
|
|
72
|
-
/** {@link #slot} when the pixels are on the Java heap. Never a real slot id. */
|
|
73
|
-
public static final long NO_SLOT = 0L;
|
|
74
|
-
|
|
75
|
-
/** The raster, or null when it is in a native slot. See the class note. */
|
|
76
|
-
public final byte[] bytes;
|
|
77
|
-
public final int width;
|
|
78
|
-
public final int height;
|
|
79
|
-
public final int rowBytes;
|
|
80
|
-
public final String format;
|
|
81
|
-
/** {@link #NO_SLOT}, or the native slot holding the raster. */
|
|
82
|
-
public final long slot;
|
|
83
|
-
|
|
84
|
-
RasterPixels(byte[] bytes, int width, int height, int rowBytes, String format) {
|
|
85
|
-
this(bytes, width, height, rowBytes, format, NO_SLOT);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private RasterPixels(
|
|
89
|
-
byte[] bytes, int width, int height, int rowBytes, String format, long slot) {
|
|
90
|
-
this.bytes = bytes;
|
|
91
|
-
this.width = width;
|
|
92
|
-
this.height = height;
|
|
93
|
-
this.rowBytes = rowBytes;
|
|
94
|
-
this.format = format;
|
|
95
|
-
this.slot = slot;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
static RasterPixels inNativeSlot(
|
|
99
|
-
long slot, int width, int height, int rowBytes, String format) {
|
|
100
|
-
return new RasterPixels(null, width, height, rowBytes, format, slot);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Whether the pixels are in native memory and unreadable from Java. */
|
|
104
|
-
public boolean isInNativeSlot() {
|
|
105
|
-
return slot != NO_SLOT;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Frees the native slot if this raster is in one. Idempotent, and a no-op for
|
|
110
|
-
* a heap-backed raster.
|
|
111
|
-
*
|
|
112
|
-
* Call this on any path that DROPS the raster after it was produced. A taken
|
|
113
|
-
* slot is already gone from the map, so releasing after a successful take is
|
|
114
|
-
* harmless — {@code nativeRelease} is a lookup that finds nothing.
|
|
115
|
-
*/
|
|
116
|
-
public void release() {
|
|
117
|
-
if (isInNativeSlot() && PdfCanvasPixelBridge.isLoaded()) {
|
|
118
|
-
PdfCanvasPixelBridge.nativeRelease(slot);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Byte offset of pixel (x, y), honouring the real stride. */
|
|
123
|
-
public int offsetOf(int x, int y) {
|
|
124
|
-
requireHeapBacked();
|
|
125
|
-
return y * rowBytes + x * 4;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** One channel of one pixel, as an unsigned 0..255 value. */
|
|
129
|
-
public int channel(int x, int y, int channel) {
|
|
130
|
-
requireHeapBacked();
|
|
131
|
-
return bytes[offsetOf(x, y) + channel] & 0xff;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private void requireHeapBacked() {
|
|
135
|
-
if (bytes == null) {
|
|
136
|
-
throw new IllegalStateException(
|
|
137
|
-
"This raster's pixels are in native slot " + slot + ", not on the Java "
|
|
138
|
-
+ "heap, so they cannot be read from Java. Ask "
|
|
139
|
-
+ "PdfCanvasRasterizer.render() for a heap-backed raster instead of "
|
|
140
|
-
+ "renderToNativeSlot().");
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* One raster to produce, mirroring {@code RasterRequest} in {@code src/types.ts}.
|
|
5
|
-
*
|
|
6
|
-
* The doc rect is in PAGE-LOCAL points — y-down, origin at the page's top-left,
|
|
7
|
-
* already post-rotation (see {@link PageGeometry}). It is NOT in whatever
|
|
8
|
-
* document-wide space the JS layout function laid the pages out in; the TS
|
|
9
|
-
* adapter subtracts the page origin before it gets here. A backend that had to
|
|
10
|
-
* know the page layout would be a backend the layout function could break.
|
|
11
|
-
*/
|
|
12
|
-
public final class RasterRequest {
|
|
13
|
-
|
|
14
|
-
/** Fill the bitmap opaque white before drawing. */
|
|
15
|
-
public static final int BACKGROUND_WHITE = 0;
|
|
16
|
-
/**
|
|
17
|
-
* Leave the bitmap cleared to transparent before drawing.
|
|
18
|
-
*
|
|
19
|
-
* This means the CALLER accepts undrawn area coming back transparent. It is
|
|
20
|
-
* emphatically NOT a claim that {@code PdfRenderer.render} clears anything —
|
|
21
|
-
* spike S1 measured that it does not (finding 3): untouched pixels keep
|
|
22
|
-
* whatever the caller left in the bitmap. The {@code eraseColor} call happens
|
|
23
|
-
* on both paths; only the colour differs.
|
|
24
|
-
*/
|
|
25
|
-
public static final int BACKGROUND_TRANSPARENT = 1;
|
|
26
|
-
|
|
27
|
-
public final int page;
|
|
28
|
-
public final float docX;
|
|
29
|
-
public final float docY;
|
|
30
|
-
public final float docWidth;
|
|
31
|
-
public final float docHeight;
|
|
32
|
-
/** Device pixels per PDF point. */
|
|
33
|
-
public final float scale;
|
|
34
|
-
/**
|
|
35
|
-
* Whether PDF-embedded annotations should be drawn.
|
|
36
|
-
*
|
|
37
|
-
* RECORDED, NOT HONOURED — and it cannot be. {@code PdfRenderer.Page.render}
|
|
38
|
-
* takes a render MODE ({@code FOR_DISPLAY} / {@code FOR_PRINT}) and no
|
|
39
|
-
* annotation switch; PDFium's {@code FPDF_ANNOT} flag is not reachable through
|
|
40
|
-
* the platform class at any API level. Annotations are always composited.
|
|
41
|
-
* {@code FOR_PRINT} is not a suppression flag either — it selects an
|
|
42
|
-
* annotation's /Print appearance stream, which is still an appearance.
|
|
43
|
-
*
|
|
44
|
-
* The consequence for the package is real and belongs in the open: an app that
|
|
45
|
-
* draws its OWN copy of a PDF-embedded annotation on the Skia layer will see it
|
|
46
|
-
* twice on Android. The fix is a PDFium backend, not a different mode here.
|
|
47
|
-
*/
|
|
48
|
-
public final boolean annotations;
|
|
49
|
-
public final int background;
|
|
50
|
-
|
|
51
|
-
public RasterRequest(
|
|
52
|
-
int page,
|
|
53
|
-
float docX,
|
|
54
|
-
float docY,
|
|
55
|
-
float docWidth,
|
|
56
|
-
float docHeight,
|
|
57
|
-
float scale,
|
|
58
|
-
boolean annotations,
|
|
59
|
-
int background) {
|
|
60
|
-
this.page = page;
|
|
61
|
-
this.docX = docX;
|
|
62
|
-
this.docY = docY;
|
|
63
|
-
this.docWidth = docWidth;
|
|
64
|
-
this.docHeight = docHeight;
|
|
65
|
-
this.scale = scale;
|
|
66
|
-
this.annotations = annotations;
|
|
67
|
-
this.background = background;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Destination width in device pixels.
|
|
72
|
-
*
|
|
73
|
-
* ROUND, not ceil, and at least 1 — the same rule the deterministic fake
|
|
74
|
-
* backend documents. The half-pixel this can shave off the far edge is
|
|
75
|
-
* absorbed by the fact that a raster is drawn to fill its doc rect; going the
|
|
76
|
-
* other way (ceil) would make two rasters requested at the same scale
|
|
77
|
-
* disagree on size for rects of equal width, which the cache keys off.
|
|
78
|
-
*/
|
|
79
|
-
public int pixelWidth() {
|
|
80
|
-
return Math.max(1, Math.round(docWidth * scale));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Destination height in device pixels. See {@link #pixelWidth()}. */
|
|
84
|
-
public int pixelHeight() {
|
|
85
|
-
return Math.max(1, Math.round(docHeight * scale));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@Override
|
|
89
|
-
public String toString() {
|
|
90
|
-
return "RasterRequest{p=" + page + " rect=" + docX + "," + docY + " "
|
|
91
|
-
+ docWidth + "x" + docHeight + " @" + scale + " -> "
|
|
92
|
-
+ pixelWidth() + "x" + pixelHeight()
|
|
93
|
-
+ " bg=" + (background == BACKGROUND_WHITE ? "white" : "transparent") + "}";
|
|
94
|
-
}
|
|
95
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
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;
|