@reekon-tools/react-native-pdf-canvas 0.1.1 → 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.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +68 -39
  4. package/README.md +213 -1316
  5. package/android/build.gradle +67 -54
  6. package/android/consumer-rules.pro +7 -0
  7. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
  8. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
  9. package/android/src/main/cpp/CMakeLists.txt +54 -0
  10. package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
  11. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
  12. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
  13. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
  14. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
  15. package/android/tools/compile-gate.sh +81 -101
  16. package/dist/controller.d.ts +14 -0
  17. package/dist/controller.js +4 -3
  18. package/dist/rasterizer/index.native.d.ts +8 -12
  19. package/dist/rasterizer/index.native.js +15 -21
  20. package/dist/rasterizer/native-bridge.d.ts +51 -75
  21. package/dist/rasterizer/native-bridge.js +49 -33
  22. package/dist/rasterizer/native.d.ts +45 -0
  23. package/dist/rasterizer/native.js +123 -0
  24. package/dist/react/usePdfDocument.d.ts +5 -5
  25. package/dist/react/usePdfLayer.d.ts +10 -1
  26. package/dist/react/usePdfLayer.js +10 -2
  27. package/dist/testing/scenes.d.ts +3 -3
  28. package/dist/testing/scenes.js +4 -4
  29. package/dist/types.d.ts +36 -42
  30. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  31. package/native/.clangd +5 -0
  32. package/native/CMakeLists.txt +89 -0
  33. package/native/core/include/pdfcanvas/document.h +90 -0
  34. package/native/core/include/pdfcanvas/error.h +47 -0
  35. package/native/core/include/pdfcanvas/jsi.h +18 -0
  36. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  37. package/native/core/include/pdfcanvas/library.h +28 -0
  38. package/native/core/include/pdfcanvas/pixels.h +92 -0
  39. package/native/core/include/pdfcanvas/service.h +87 -0
  40. package/native/core/include/pdfcanvas/slots.h +113 -0
  41. package/native/core/include/pdfcanvas/types.h +110 -0
  42. package/native/core/pdfcanvas-core.cmake +22 -0
  43. package/native/core/src/document.cpp +516 -0
  44. package/native/core/src/error.cpp +29 -0
  45. package/native/core/src/jsi.cpp +66 -0
  46. package/native/core/src/library.cpp +52 -0
  47. package/native/core/src/pixels.cpp +45 -0
  48. package/native/core/src/service.cpp +156 -0
  49. package/native/core/src/slots.cpp +139 -0
  50. package/native/tests/fixtures.cpp +451 -0
  51. package/native/tests/fixtures.h +96 -0
  52. package/native/tests/harness.h +122 -0
  53. package/native/tests/main.cpp +64 -0
  54. package/native/tests/pixels_util.h +126 -0
  55. package/native/tests/test_document.cpp +485 -0
  56. package/native/tests/test_service.cpp +130 -0
  57. package/native/tests/test_slots.cpp +250 -0
  58. package/package.json +27 -7
  59. package/react-native.config.js +5 -2
  60. package/scripts/fetch-pdfium.mjs +487 -0
  61. package/scripts/pdfium-manifest.json +46 -0
  62. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  63. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  64. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  65. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  66. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  67. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  68. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  69. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  72. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  73. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  74. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  75. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  76. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  77. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  78. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  79. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  80. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  81. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  82. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  83. package/dist/rasterizer/android.d.ts +0 -121
  84. package/dist/rasterizer/android.js +0 -126
  85. package/dist/rasterizer/ios.d.ts +0 -84
  86. package/dist/rasterizer/ios.js +0 -155
  87. package/ios/Package.swift +0 -58
  88. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  89. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  90. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  91. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  92. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  93. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  94. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  95. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  98. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  99. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  100. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  101. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  102. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  103. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  104. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  105. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  109. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  110. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  111. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  112. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  113. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  114. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  115. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  116. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  117. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  118. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  119. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  120. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  121. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
@@ -1,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
- }