@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,251 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import android.content.Context;
|
|
4
|
-
import android.util.Log;
|
|
5
|
-
|
|
6
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
7
|
-
import androidx.test.platform.app.InstrumentationRegistry;
|
|
8
|
-
|
|
9
|
-
import org.junit.Test;
|
|
10
|
-
import org.junit.runner.RunWith;
|
|
11
|
-
|
|
12
|
-
import java.io.File;
|
|
13
|
-
import java.io.FileOutputStream;
|
|
14
|
-
import java.io.IOException;
|
|
15
|
-
import java.io.InputStream;
|
|
16
|
-
import java.util.Arrays;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* MEASUREMENT, NOT VERIFICATION. Nothing here asserts a wall time — a timing
|
|
20
|
-
* assertion fails on a loaded machine and tells nobody anything. These tests
|
|
21
|
-
* exist so a physical-device run PRINTS the numbers every timing constant in
|
|
22
|
-
* `src/policy.ts` is currently a placeholder for.
|
|
23
|
-
*
|
|
24
|
-
* <h3>Why this is separate from {@code PdfCanvasRasterizerTest.reportsTimings}</h3>
|
|
25
|
-
*
|
|
26
|
-
* That test reports BEST OF 3 at a 512 px tile. Both halves are now wrong for the
|
|
27
|
-
* decision they feed:
|
|
28
|
-
*
|
|
29
|
-
* - {@code minEpochIntervalMs} wants a p95, not a best. A best-of-3 is a FLOOR;
|
|
30
|
-
* it is the number that makes a cadence constant look achievable and then
|
|
31
|
-
* misses one frame in twenty on a warm phone.
|
|
32
|
-
* - The planner's {@code maxPieceDimension} now defaults to 1024, so 512 is no
|
|
33
|
-
* longer the size that gets rendered. A 1024 tile is 4x the destination area,
|
|
34
|
-
* and spike S1 measured cost as tracking DESTINATION AREA — so the 512 number
|
|
35
|
-
* cannot simply be scaled by eye.
|
|
36
|
-
*
|
|
37
|
-
* <h3>The one number that matters</h3>
|
|
38
|
-
*
|
|
39
|
-
* p95 of {@code PdfRenderer.render} for a 1024 px tile, on real hardware, on a
|
|
40
|
-
* real drawing. Everything in the cadence is downstream of it. Note that this
|
|
41
|
-
* measures the NATIVE render only — no bridge, no base64, no Skia ingest — which
|
|
42
|
-
* is exactly what makes it useful: subtract it from the app probe's round-trip
|
|
43
|
-
* p95 and the remainder is the transport tax.
|
|
44
|
-
*/
|
|
45
|
-
@RunWith(AndroidJUnit4.class)
|
|
46
|
-
public class PdfCanvasDeviceTimingTest {
|
|
47
|
-
|
|
48
|
-
private static final String TAG = "PDFCANVAS";
|
|
49
|
-
|
|
50
|
-
/** Enough samples that the 95th percentile is a real observation, not the max. */
|
|
51
|
-
private static final int SAMPLES = 20;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Where to drop a real customer PDF. The test app's own external files dir,
|
|
55
|
-
* because it is the one location adb's shell can write to AND an app UID can
|
|
56
|
-
* read at every level from 24 to 36 — {@code /data/local/tmp} is mode 0771
|
|
57
|
-
* shell:shell, so an app cannot traverse it.
|
|
58
|
-
*/
|
|
59
|
-
private static final String REAL_PDF = "customer.pdf";
|
|
60
|
-
|
|
61
|
-
private static Context ctx() {
|
|
62
|
-
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private static RasterRequest white(
|
|
66
|
-
int page, float x, float y, float w, float h, float scale) {
|
|
67
|
-
return new RasterRequest(
|
|
68
|
-
page, x, y, w, h, scale, false, RasterRequest.BACKGROUND_WHITE);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* ================================================================ *
|
|
72
|
-
* Synthetic sheet — comparable across devices
|
|
73
|
-
* ================================================================ */
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The piece sizes the planner can actually ask for, on the sheet size this
|
|
77
|
-
* package exists for.
|
|
78
|
-
*
|
|
79
|
-
* 1024 is the current {@code maxPieceDimension} default, 512 is the candidate
|
|
80
|
-
* the audit named as the next step down, and 2048 is what the plan degenerates
|
|
81
|
-
* to if the memory cap is ever raised to the hardware ceiling. Reading all
|
|
82
|
-
* three in one run is what makes the constant a choice rather than a guess.
|
|
83
|
-
*/
|
|
84
|
-
@Test
|
|
85
|
-
public void tilePercentilesOnSyntheticArchD() throws Exception {
|
|
86
|
-
File arch =
|
|
87
|
-
TestPdfs.pattern(ctx(), "timing-archd.pdf", TestPdfs.ARCH_D_W, TestPdfs.ARCH_D_H);
|
|
88
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(arch, null, 1);
|
|
89
|
-
try {
|
|
90
|
-
for (int px : new int[] {512, 1024, 2048}) {
|
|
91
|
-
// scale 2 is a mid-zoom detail raster: the audit's ARCH-D phone case is
|
|
92
|
-
// scale 3 at fit, and a detail epoch is requested at the display scale.
|
|
93
|
-
float scale = 2f;
|
|
94
|
-
float side = px / scale;
|
|
95
|
-
report("synthetic archD " + px + "px@" + scale + "x", r, side, scale);
|
|
96
|
-
}
|
|
97
|
-
// The base layer, now tiled: one 1024 piece of a fit-scale base.
|
|
98
|
-
report("synthetic archD base-piece 1024px@0.677x", r, 1024f / 0.677f, 0.677f);
|
|
99
|
-
} finally {
|
|
100
|
-
r.close();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/* ================================================================ *
|
|
105
|
-
* Real drawing — the number that is actually true
|
|
106
|
-
* ================================================================ */
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* The synthetic pattern is vector rectangles and lines. A real drawing carries
|
|
110
|
-
* embedded fonts, CCITT or DCT images, transparency groups and annotations, and
|
|
111
|
-
* PDFium's cost is dominated by exactly those. Every timing in this file taken
|
|
112
|
-
* from {@code TestPdfs.pattern} is therefore an OPTIMISTIC bound, and the gap
|
|
113
|
-
* is not a small constant.
|
|
114
|
-
*
|
|
115
|
-
* Skips loudly rather than failing when no file has been pushed: a missing
|
|
116
|
-
* fixture is a setup step, not a defect, and a red test would train people to
|
|
117
|
-
* ignore it.
|
|
118
|
-
*/
|
|
119
|
-
@Test
|
|
120
|
-
public void tilePercentilesOnARealDrawing() throws Exception {
|
|
121
|
-
File dir = ctx().getExternalFilesDir(null);
|
|
122
|
-
File pdf = dir == null ? null : new File(dir, REAL_PDF);
|
|
123
|
-
if (pdf == null || !pdf.isFile()) {
|
|
124
|
-
Log.w(
|
|
125
|
-
TAG,
|
|
126
|
-
"SKIP tilePercentilesOnARealDrawing — no "
|
|
127
|
-
+ REAL_PDF
|
|
128
|
-
+ ". Push one and re-run:\n"
|
|
129
|
-
+ " adb -s <serial> shell mkdir -p /sdcard/Android/data/"
|
|
130
|
-
+ ctx().getPackageName()
|
|
131
|
-
+ "/files\n"
|
|
132
|
-
+ " adb -s <serial> push your-drawing.pdf /sdcard/Android/data/"
|
|
133
|
-
+ ctx().getPackageName()
|
|
134
|
-
+ "/files/"
|
|
135
|
-
+ REAL_PDF);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
long openStart = System.nanoTime();
|
|
140
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
141
|
-
long openMs = (System.nanoTime() - openStart) / 1_000_000L;
|
|
142
|
-
try {
|
|
143
|
-
PageGeometry g = r.pageGeometry(0);
|
|
144
|
-
Log.i(
|
|
145
|
-
TAG,
|
|
146
|
-
"real pdf: "
|
|
147
|
-
+ pdf.length()
|
|
148
|
-
+ " bytes, "
|
|
149
|
-
+ r.pageCount()
|
|
150
|
-
+ " pages, page0 "
|
|
151
|
-
+ g.widthPoints
|
|
152
|
-
+ "x"
|
|
153
|
-
+ g.heightPoints
|
|
154
|
-
+ "pt, open+geometry="
|
|
155
|
-
+ openMs
|
|
156
|
-
+ "ms, rotationSupport="
|
|
157
|
-
+ PdfCanvasRasterizer.rotationSupport(ctx().getCacheDir()));
|
|
158
|
-
|
|
159
|
-
for (int px : new int[] {512, 1024, 2048}) {
|
|
160
|
-
float scale = 2f;
|
|
161
|
-
report("real " + px + "px@" + scale + "x", r, px / scale, scale);
|
|
162
|
-
}
|
|
163
|
-
// Fit-scale base piece, sized from the real page rather than ARCH-D.
|
|
164
|
-
float fit = 1170f / g.widthPoints;
|
|
165
|
-
report("real base-piece 1024px@fit(" + fit + ")", r, 1024f / fit, fit);
|
|
166
|
-
} finally {
|
|
167
|
-
r.close();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/* ================================================================ *
|
|
172
|
-
* Harness
|
|
173
|
-
* ================================================================ */
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Walks the tile across the page between samples.
|
|
177
|
-
*
|
|
178
|
-
* Rendering the SAME rect twenty times measures whatever PDFium cached the
|
|
179
|
-
* first time — page parse, decoded image objects, rasterised glyphs — and on a
|
|
180
|
-
* real drawing that is most of the cost. Walking it means each sample pays for
|
|
181
|
-
* content it has not seen, which is what panning actually does.
|
|
182
|
-
*/
|
|
183
|
-
private static void report(String label, PdfCanvasRasterizer r, float side, float scale)
|
|
184
|
-
throws PdfRasterException {
|
|
185
|
-
PageGeometry g = r.pageGeometry(0);
|
|
186
|
-
float maxX = Math.max(0f, g.widthPoints - side);
|
|
187
|
-
float maxY = Math.max(0f, g.heightPoints - side);
|
|
188
|
-
long[] ms = new long[SAMPLES];
|
|
189
|
-
int pxW = 0;
|
|
190
|
-
int pxH = 0;
|
|
191
|
-
|
|
192
|
-
for (int i = 0; i < SAMPLES; i++) {
|
|
193
|
-
float x = maxX == 0f ? 0f : (maxX * i) / (SAMPLES - 1);
|
|
194
|
-
float y = maxY == 0f ? 0f : (maxY * ((i * 7) % SAMPLES)) / (SAMPLES - 1);
|
|
195
|
-
RasterRequest request = white(0, x, y, side, side, scale);
|
|
196
|
-
long t0 = System.nanoTime();
|
|
197
|
-
RasterPixels pixels = r.render(request, null);
|
|
198
|
-
ms[i] = (System.nanoTime() - t0) / 1_000_000L;
|
|
199
|
-
pxW = pixels.width;
|
|
200
|
-
pxH = pixels.height;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
long[] sorted = ms.clone();
|
|
204
|
-
Arrays.sort(sorted);
|
|
205
|
-
Log.i(
|
|
206
|
-
TAG,
|
|
207
|
-
"TIMING "
|
|
208
|
-
+ label
|
|
209
|
-
+ " dest="
|
|
210
|
-
+ pxW
|
|
211
|
-
+ "x"
|
|
212
|
-
+ pxH
|
|
213
|
-
+ " n="
|
|
214
|
-
+ SAMPLES
|
|
215
|
-
+ " min="
|
|
216
|
-
+ sorted[0]
|
|
217
|
-
+ " p50="
|
|
218
|
-
+ percentile(sorted, 50)
|
|
219
|
-
+ " p95="
|
|
220
|
-
+ percentile(sorted, 95)
|
|
221
|
-
+ " max="
|
|
222
|
-
+ sorted[SAMPLES - 1]
|
|
223
|
-
+ "ms raw="
|
|
224
|
-
+ Arrays.toString(ms));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Nearest-rank percentile. No interpolation: these are integer milliseconds. */
|
|
228
|
-
private static long percentile(long[] sorted, int p) {
|
|
229
|
-
int rank = (int) Math.ceil((p / 100.0) * sorted.length) - 1;
|
|
230
|
-
return sorted[Math.max(0, Math.min(sorted.length - 1, rank))];
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/* ================================================================ *
|
|
234
|
-
* Unused-but-kept helper
|
|
235
|
-
* ================================================================ */
|
|
236
|
-
|
|
237
|
-
/** Kept so a fixture can be staged from an APK asset instead of adb, if that
|
|
238
|
-
* ever becomes the more convenient route. */
|
|
239
|
-
@SuppressWarnings("unused")
|
|
240
|
-
private static File copyAsset(String assetName, File into) throws IOException {
|
|
241
|
-
try (InputStream in = ctx().getAssets().open(assetName);
|
|
242
|
-
FileOutputStream out = new FileOutputStream(into)) {
|
|
243
|
-
byte[] buffer = new byte[64 * 1024];
|
|
244
|
-
int n;
|
|
245
|
-
while ((n = in.read(buffer)) > 0) {
|
|
246
|
-
out.write(buffer, 0, n);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return into;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import static org.junit.Assert.assertEquals;
|
|
4
|
-
import static org.junit.Assert.assertNotEquals;
|
|
5
|
-
import static org.junit.Assert.assertTrue;
|
|
6
|
-
import static org.junit.Assert.fail;
|
|
7
|
-
|
|
8
|
-
import android.content.Context;
|
|
9
|
-
import android.util.Log;
|
|
10
|
-
|
|
11
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
12
|
-
import androidx.test.platform.app.InstrumentationRegistry;
|
|
13
|
-
|
|
14
|
-
import org.junit.Test;
|
|
15
|
-
import org.junit.runner.RunWith;
|
|
16
|
-
|
|
17
|
-
import java.io.File;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Failure modes.
|
|
21
|
-
*
|
|
22
|
-
* The requirement is DISTINGUISHABILITY, not merely "does not crash". A backend
|
|
23
|
-
* that maps every bad input to {@code backend-failure} passes a
|
|
24
|
-
* "throws something" test and is useless to the UI above it, which has to tell a
|
|
25
|
-
* user "that file is encrypted" apart from "that file did not download". So each
|
|
26
|
-
* test asserts a SPECIFIC code, and {@link #openFailuresAreDistinguishable}
|
|
27
|
-
* asserts they are not all the same code — the check that actually fails if
|
|
28
|
-
* someone collapses the mapping later.
|
|
29
|
-
*/
|
|
30
|
-
@RunWith(AndroidJUnit4.class)
|
|
31
|
-
public class PdfCanvasFailureTest {
|
|
32
|
-
|
|
33
|
-
private static final String TAG = "PDFCANVAS";
|
|
34
|
-
|
|
35
|
-
private static Context ctx() {
|
|
36
|
-
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private static PdfErrorCode codeFromOpeningFile(File file, String password) {
|
|
40
|
-
try {
|
|
41
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(file, password, 1);
|
|
42
|
-
r.close();
|
|
43
|
-
return null;
|
|
44
|
-
} catch (PdfRasterException e) {
|
|
45
|
-
Log.i(TAG, "open(" + file.getName() + ") -> " + e.code().wire() + ": " + e.getMessage());
|
|
46
|
-
return e.code();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** CATCHES: a missing file reported as "corrupt", which sends a user hunting a good file. */
|
|
51
|
-
@Test
|
|
52
|
-
public void missingFileIsNotFound() {
|
|
53
|
-
assertEquals(
|
|
54
|
-
PdfErrorCode.NOT_FOUND, codeFromOpeningFile(TestPdfs.missing(ctx(), "gone.pdf"), null));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* CATCHES: a native crash on garbage input, and a garbage file reported as
|
|
59
|
-
* "not found". PDFium REBUILDS a broken xref rather than giving up, so the
|
|
60
|
-
* fixture has no %PDF header and no recoverable objects at all — a file with
|
|
61
|
-
* merely a bad trailer would open fine and this branch would never be reached.
|
|
62
|
-
*/
|
|
63
|
-
@Test
|
|
64
|
-
public void nonPdfIsCorrupt() throws Exception {
|
|
65
|
-
assertEquals(
|
|
66
|
-
PdfErrorCode.CORRUPT, codeFromOpeningFile(TestPdfs.notAPdf(ctx(), "notes.txt"), null));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** CATCHES: a zero-byte download reported as "not a PDF" instead of a truncated file. */
|
|
70
|
-
@Test
|
|
71
|
-
public void emptyFileIsCorrupt() throws Exception {
|
|
72
|
-
assertEquals(
|
|
73
|
-
PdfErrorCode.CORRUPT, codeFromOpeningFile(TestPdfs.empty(ctx(), "empty.pdf"), null));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* CATCHES: an encrypted document reported as corrupt — the single most
|
|
78
|
-
* misleading mapping available here, because the UI's correct response is a
|
|
79
|
-
* password prompt and "corrupt" sends the user to re-download a perfectly good
|
|
80
|
-
* file.
|
|
81
|
-
*
|
|
82
|
-
* The fixture has a NON-EMPTY user password. An empty one would open cleanly
|
|
83
|
-
* on every viewer and this test would be asserting nothing.
|
|
84
|
-
*/
|
|
85
|
-
@Test
|
|
86
|
-
public void encryptedDocumentRequiresAPassword() throws Exception {
|
|
87
|
-
File pdf = TestPdfs.encrypted(ctx(), "locked.pdf");
|
|
88
|
-
assertEquals(PdfErrorCode.PASSWORD_REQUIRED, codeFromOpeningFile(pdf, null));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* CATCHES: silently ignoring a supplied password.
|
|
93
|
-
*
|
|
94
|
-
* {@code PdfRenderer} has no way to accept one below API 35 — {@code LoadParams}
|
|
95
|
-
* is where that landed, and this module compiles against SDK 34. Accepting the
|
|
96
|
-
* argument and dropping it would turn "wrong password" into "corrupt file" for
|
|
97
|
-
* every encrypted document, so it is refused loudly instead.
|
|
98
|
-
*/
|
|
99
|
-
@Test
|
|
100
|
-
public void supplyingAPasswordIsUnsupportedNotIgnored() throws Exception {
|
|
101
|
-
File pdf = TestPdfs.pattern(ctx(), "pw-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
102
|
-
assertEquals(PdfErrorCode.UNSUPPORTED, codeFromOpeningFile(pdf, "hunter2"));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** The check that fails if the mapping is ever collapsed to one code. */
|
|
106
|
-
@Test
|
|
107
|
-
public void openFailuresAreDistinguishable() throws Exception {
|
|
108
|
-
PdfErrorCode missing = codeFromOpeningFile(TestPdfs.missing(ctx(), "gone2.pdf"), null);
|
|
109
|
-
PdfErrorCode garbage = codeFromOpeningFile(TestPdfs.notAPdf(ctx(), "notes2.txt"), null);
|
|
110
|
-
PdfErrorCode locked = codeFromOpeningFile(TestPdfs.encrypted(ctx(), "locked2.pdf"), null);
|
|
111
|
-
|
|
112
|
-
assertNotEquals(missing, garbage);
|
|
113
|
-
assertNotEquals(garbage, locked);
|
|
114
|
-
assertNotEquals(missing, locked);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** CATCHES: an out-of-range page index reaching PdfRenderer and crashing natively. */
|
|
118
|
-
@Test
|
|
119
|
-
public void outOfRangePageIsNotFound() throws Exception {
|
|
120
|
-
File pdf = TestPdfs.pattern(ctx(), "range-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
121
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
122
|
-
try {
|
|
123
|
-
assertEquals(1, r.pageCount());
|
|
124
|
-
for (int page : new int[] {-1, 1, 99}) {
|
|
125
|
-
try {
|
|
126
|
-
r.pageGeometry(page);
|
|
127
|
-
fail("pageGeometry(" + page + ") should have thrown");
|
|
128
|
-
} catch (PdfRasterException e) {
|
|
129
|
-
assertEquals(PdfErrorCode.NOT_FOUND, e.code());
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
r.render(
|
|
133
|
-
new RasterRequest(
|
|
134
|
-
page, 0f, 0f, 100f, 100f, 1f, false, RasterRequest.BACKGROUND_WHITE),
|
|
135
|
-
null);
|
|
136
|
-
fail("render(page=" + page + ") should have thrown");
|
|
137
|
-
} catch (PdfRasterException e) {
|
|
138
|
-
assertEquals(PdfErrorCode.NOT_FOUND, e.code());
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
} finally {
|
|
142
|
-
r.close();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* CATCHES: use-after-close reaching native code. A closed {@code PdfRenderer}
|
|
148
|
-
* whose {@code openPage} is called is a native crash, not an exception, so
|
|
149
|
-
* this is the difference between an error a promise can reject with and a dead
|
|
150
|
-
* process.
|
|
151
|
-
*/
|
|
152
|
-
@Test
|
|
153
|
-
public void renderAfterCloseFailsInsteadOfCrashing() throws Exception {
|
|
154
|
-
File pdf = TestPdfs.pattern(ctx(), "closed-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
155
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
156
|
-
r.close();
|
|
157
|
-
// Idempotent: a double close must not throw either, because close() is
|
|
158
|
-
// driven by a React unmount and can genuinely arrive twice.
|
|
159
|
-
r.close();
|
|
160
|
-
try {
|
|
161
|
-
r.render(
|
|
162
|
-
new RasterRequest(0, 0f, 0f, 100f, 100f, 1f, false, RasterRequest.BACKGROUND_WHITE),
|
|
163
|
-
null);
|
|
164
|
-
fail("render after close should have thrown");
|
|
165
|
-
} catch (PdfRasterException e) {
|
|
166
|
-
assertEquals(PdfErrorCode.BACKEND_FAILURE, e.code());
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* CATCHES: an absurd raster reaching {@code Bitmap.createBitmap} and killing
|
|
172
|
-
* the process with an {@code OutOfMemoryError} that carries no raster key and
|
|
173
|
-
* cannot be turned into a diagnostic.
|
|
174
|
-
*/
|
|
175
|
-
@Test
|
|
176
|
-
public void absurdRasterIsRefusedRatherThanAllocated() throws Exception {
|
|
177
|
-
File pdf = TestPdfs.pattern(ctx(), "oom-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
178
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
179
|
-
try {
|
|
180
|
-
r.render(
|
|
181
|
-
new RasterRequest(
|
|
182
|
-
0, 0f, 0f, TestPdfs.LETTER_W, TestPdfs.LETTER_H, 40f, false,
|
|
183
|
-
RasterRequest.BACKGROUND_WHITE),
|
|
184
|
-
null);
|
|
185
|
-
fail("a 24480x31680 raster should have been refused");
|
|
186
|
-
} catch (PdfRasterException e) {
|
|
187
|
-
assertEquals(PdfErrorCode.OUT_OF_MEMORY, e.code());
|
|
188
|
-
assertTrue(e.getMessage(), e.getMessage().contains("Refused"));
|
|
189
|
-
} finally {
|
|
190
|
-
r.close();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** CATCHES: a degenerate request producing a zero-size bitmap, which throws natively. */
|
|
195
|
-
@Test
|
|
196
|
-
public void degenerateRequestsAreRejected() throws Exception {
|
|
197
|
-
File pdf = TestPdfs.pattern(ctx(), "degenerate.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
198
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
199
|
-
try {
|
|
200
|
-
float[][] bad = {
|
|
201
|
-
{0f, 100f, 1f}, // zero width
|
|
202
|
-
{100f, 0f, 1f}, // zero height
|
|
203
|
-
{100f, 100f, 0f}, // zero scale
|
|
204
|
-
{100f, 100f, -1f}, // negative scale
|
|
205
|
-
};
|
|
206
|
-
for (float[] b : bad) {
|
|
207
|
-
try {
|
|
208
|
-
r.render(
|
|
209
|
-
new RasterRequest(
|
|
210
|
-
0, 0f, 0f, b[0], b[1], b[2], false, RasterRequest.BACKGROUND_WHITE),
|
|
211
|
-
null);
|
|
212
|
-
fail("expected rejection for " + b[0] + "x" + b[1] + " @" + b[2]);
|
|
213
|
-
} catch (PdfRasterException e) {
|
|
214
|
-
assertEquals(PdfErrorCode.BACKEND_FAILURE, e.code());
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} finally {
|
|
218
|
-
r.close();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|