@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,98 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import static org.junit.Assert.assertEquals;
|
|
4
|
-
import static org.junit.Assert.assertTrue;
|
|
5
|
-
import static org.junit.Assert.fail;
|
|
6
|
-
|
|
7
|
-
import android.content.Context;
|
|
8
|
-
import android.os.Build;
|
|
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
|
-
* The end-to-end proof of the poison guard: BREAK the platform for real, then
|
|
21
|
-
* show that the backend refuses instead of segfaulting.
|
|
22
|
-
*
|
|
23
|
-
* EXCLUDED from `connectedDebugAndroidTest` by the `notAnnotation` filter in
|
|
24
|
-
* `android/build.gradle`, because on API 24 it leaves PDFium unusable for the
|
|
25
|
-
* rest of the process and every test scheduled after it would die. Run it alone:
|
|
26
|
-
*
|
|
27
|
-
* <pre>
|
|
28
|
-
* ./gradlew installDebugAndroidTest
|
|
29
|
-
* adb -s <device> shell am instrument -w \
|
|
30
|
-
* -e class tools.reekon.pdfcanvas.PdfCanvasPoisonGuardTest \
|
|
31
|
-
* -e annotation tools.reekon.pdfcanvas.PoisonsTheProcess \
|
|
32
|
-
* tools.reekon.pdfcanvas.test/androidx.test.runner.AndroidJUnitRunner
|
|
33
|
-
* </pre>
|
|
34
|
-
*
|
|
35
|
-
* The fixture is a file with a real {@code %PDF-} header and unparseable
|
|
36
|
-
* contents — a truncated download, in other words. Preflight cannot reject it,
|
|
37
|
-
* which is the point: it is precisely the residue preflight leaves behind, and
|
|
38
|
-
* therefore the only case the latch has to handle.
|
|
39
|
-
*/
|
|
40
|
-
@RunWith(AndroidJUnit4.class)
|
|
41
|
-
public class PdfCanvasPoisonGuardTest {
|
|
42
|
-
|
|
43
|
-
private static final String TAG = "PDFCANVAS";
|
|
44
|
-
|
|
45
|
-
private static Context ctx() {
|
|
46
|
-
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@Test
|
|
50
|
-
@PoisonsTheProcess
|
|
51
|
-
public void aFileThatPreflightCannotJudgeIsHandledWithoutCrashing() throws Exception {
|
|
52
|
-
File garbage = TestPdfs.headerOnlyGarbage(ctx(), "poison-header-only.pdf");
|
|
53
|
-
File good = TestPdfs.pattern(ctx(), "poison-good.pdf", 100, 100);
|
|
54
|
-
|
|
55
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(false);
|
|
56
|
-
|
|
57
|
-
// Opening it must fail as CORRUPT — preflight let it through, PdfRenderer
|
|
58
|
-
// rejected it.
|
|
59
|
-
try {
|
|
60
|
-
PdfCanvasRasterizer.openFile(garbage, null, 1).close();
|
|
61
|
-
fail("header-only garbage should not have opened");
|
|
62
|
-
} catch (PdfRasterException e) {
|
|
63
|
-
assertEquals(PdfErrorCode.CORRUPT, e.code());
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
boolean poisoned = PdfPlatformQuirks.isPdfiumPoisoned();
|
|
67
|
-
Log.i(TAG, "poison-guard: API " + Build.VERSION.SDK_INT + " latched=" + poisoned);
|
|
68
|
-
assertEquals(
|
|
69
|
-
"the latch should have been set exactly on the platforms treated as affected",
|
|
70
|
-
PdfPlatformQuirks.failedNativeOpenIsPoisonous(),
|
|
71
|
-
poisoned);
|
|
72
|
-
|
|
73
|
-
// Force the collection that actually corrupts PDFium's reference count. On
|
|
74
|
-
// an unguarded build, the next open is the SIGSEGV.
|
|
75
|
-
for (int i = 0; i < 3; i++) {
|
|
76
|
-
System.gc();
|
|
77
|
-
System.runFinalization();
|
|
78
|
-
Thread.sleep(150);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (poisoned) {
|
|
82
|
-
try {
|
|
83
|
-
PdfCanvasRasterizer.openFile(good, null, 1).close();
|
|
84
|
-
fail("a poisoned process must refuse rather than segfault");
|
|
85
|
-
} catch (PdfRasterException e) {
|
|
86
|
-
assertEquals(PdfErrorCode.BACKEND_FAILURE, e.code());
|
|
87
|
-
assertTrue(e.getMessage(), e.getMessage().contains("PDFium"));
|
|
88
|
-
}
|
|
89
|
-
Log.i(TAG, "poison-guard: refused after real poisoning, process alive");
|
|
90
|
-
} else {
|
|
91
|
-
// Unaffected platform: opening must still work after the collection.
|
|
92
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(good, null, 1);
|
|
93
|
-
assertEquals(1, r.pageCount());
|
|
94
|
-
r.close();
|
|
95
|
-
Log.i(TAG, "poison-guard: platform unaffected, opened normally after GC");
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
package tools.reekon.pdfcanvas;
|
|
2
|
-
|
|
3
|
-
import static org.junit.Assert.assertEquals;
|
|
4
|
-
import static org.junit.Assert.assertFalse;
|
|
5
|
-
import static org.junit.Assert.assertNotEquals;
|
|
6
|
-
import static org.junit.Assert.assertNull;
|
|
7
|
-
import static org.junit.Assert.assertTrue;
|
|
8
|
-
import static org.junit.Assert.fail;
|
|
9
|
-
|
|
10
|
-
import android.content.Context;
|
|
11
|
-
import android.os.Build;
|
|
12
|
-
import android.util.Log;
|
|
13
|
-
|
|
14
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
15
|
-
import androidx.test.platform.app.InstrumentationRegistry;
|
|
16
|
-
|
|
17
|
-
import org.junit.Test;
|
|
18
|
-
import org.junit.runner.RunWith;
|
|
19
|
-
|
|
20
|
-
import java.io.File;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The two measured platform quirks, and the guards built for them.
|
|
24
|
-
*
|
|
25
|
-
* These tests are the reason the guards are not superstition. Each one either
|
|
26
|
-
* asserts a fact measured on a device in this suite's own run, or asserts that
|
|
27
|
-
* the guard reports that fact truthfully.
|
|
28
|
-
*/
|
|
29
|
-
@RunWith(AndroidJUnit4.class)
|
|
30
|
-
public class PdfCanvasQuirksTest {
|
|
31
|
-
|
|
32
|
-
private static final String TAG = "PDFCANVAS";
|
|
33
|
-
|
|
34
|
-
private static Context ctx() {
|
|
35
|
-
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* THE test that justifies {@link PdfPreflight} existing.
|
|
40
|
-
*
|
|
41
|
-
* CATCHES: a bad file reaching {@code new PdfRenderer(...)}. On API 24 that
|
|
42
|
-
* constructor throwing is a delayed SIGSEGV for the whole process, so
|
|
43
|
-
* "produces the right error code" is not enough — the file must be turned away
|
|
44
|
-
* before the platform class sees it, and the observable proof is that the
|
|
45
|
-
* poison latch is still clear afterwards.
|
|
46
|
-
*
|
|
47
|
-
* A backend that dropped preflight would still return the same three codes
|
|
48
|
-
* here, and would still pass every assertion in PdfCanvasFailureTest. Only the
|
|
49
|
-
* latch assertion notices.
|
|
50
|
-
*/
|
|
51
|
-
@Test
|
|
52
|
-
public void preflightTurnsBadFilesAwayBeforePdfRendererSeesThem() throws Exception {
|
|
53
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(false);
|
|
54
|
-
|
|
55
|
-
assertEquals(PdfErrorCode.CORRUPT, openCode(TestPdfs.notAPdf(ctx(), "q-notes.txt")));
|
|
56
|
-
assertFalse("a non-PDF reached PdfRenderer", PdfPlatformQuirks.isPdfiumPoisoned());
|
|
57
|
-
|
|
58
|
-
assertEquals(PdfErrorCode.CORRUPT, openCode(TestPdfs.empty(ctx(), "q-empty.pdf")));
|
|
59
|
-
assertFalse("an empty file reached PdfRenderer", PdfPlatformQuirks.isPdfiumPoisoned());
|
|
60
|
-
|
|
61
|
-
assertEquals(
|
|
62
|
-
PdfErrorCode.PASSWORD_REQUIRED, openCode(TestPdfs.encrypted(ctx(), "q-locked.pdf")));
|
|
63
|
-
assertFalse("an encrypted file reached PdfRenderer", PdfPlatformQuirks.isPdfiumPoisoned());
|
|
64
|
-
|
|
65
|
-
Log.i(TAG, "preflight: three bad files rejected, latch still clear on API "
|
|
66
|
-
+ Build.VERSION.SDK_INT);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* CATCHES: a preflight that never looks at the trailer of a MID-SIZED file.
|
|
71
|
-
*
|
|
72
|
-
* Preflight reads a 32 KB head and a 256 KB tail. Every other fixture in this
|
|
73
|
-
* suite is a few hundred bytes, so the head covers them end to end and the
|
|
74
|
-
* tail branch is never exercised at all — which is how
|
|
75
|
-
* {@code tailStart < head.length} survived: for any length between the two
|
|
76
|
-
* windows {@code tailStart} is 0, the tail is dropped as "already covered",
|
|
77
|
-
* and everything past 32 KB — INCLUDING THE TRAILER AND ITS /Encrypt
|
|
78
|
-
* REFERENCE — is never read. A 60 KB encrypted drawing is an ordinary
|
|
79
|
-
* document, and on API 24 it then reaches {@code new PdfRenderer(...)} and
|
|
80
|
-
* poisons PDFium for the session.
|
|
81
|
-
*
|
|
82
|
-
* The discriminator is the CAUSE, not the code. Both preflight and the
|
|
83
|
-
* {@code SecurityException} path in {@link PdfRendererPool} report
|
|
84
|
-
* {@code password-required} with the same user-facing sentence, deliberately
|
|
85
|
-
* — so the code alone cannot say which one answered. Preflight throws from the
|
|
86
|
-
* bytes and has no cause; the platform path always wraps the
|
|
87
|
-
* {@code SecurityException} it caught. A null cause therefore means the file
|
|
88
|
-
* was turned away BEFORE PdfRenderer saw it, which is the whole claim, and it
|
|
89
|
-
* is observable on every API level rather than only on the one where the latch
|
|
90
|
-
* arms.
|
|
91
|
-
*/
|
|
92
|
-
@Test
|
|
93
|
-
public void preflightFindsTheEncryptTrailerBeyondTheHeadWindow() throws Exception {
|
|
94
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(false);
|
|
95
|
-
|
|
96
|
-
File pdf = TestPdfs.encrypted(ctx(), "q-locked-60k.pdf", 60 * 1024);
|
|
97
|
-
long length = pdf.length();
|
|
98
|
-
Log.i(TAG, "preflight tail scan: fixture is " + length + " bytes");
|
|
99
|
-
assertTrue(
|
|
100
|
-
"the fixture must land between the 32 KB head and the 256 KB tail window, "
|
|
101
|
-
+ "which is the band the bug covered: " + length,
|
|
102
|
-
length > 32 * 1024 && length < 256 * 1024);
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
PdfCanvasRasterizer.openFile(pdf, null, 1).close();
|
|
106
|
-
fail("a " + length + "-byte encrypted document should have been refused");
|
|
107
|
-
} catch (PdfRasterException e) {
|
|
108
|
-
assertEquals(PdfErrorCode.PASSWORD_REQUIRED, e.code());
|
|
109
|
-
assertNull(
|
|
110
|
-
"preflight must have answered from the bytes; a cause means the file "
|
|
111
|
-
+ "reached PdfRenderer and the SecurityException path answered instead: "
|
|
112
|
-
+ e.getCause(),
|
|
113
|
-
e.getCause());
|
|
114
|
-
}
|
|
115
|
-
assertFalse(
|
|
116
|
-
"a " + length + "-byte encrypted file reached PdfRenderer",
|
|
117
|
-
PdfPlatformQuirks.isPdfiumPoisoned());
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* CATCHES: a preflight so eager it refuses good documents.
|
|
122
|
-
*
|
|
123
|
-
* The encryption test looks for {@code /Encrypt} followed by an indirect
|
|
124
|
-
* reference precisely so that the string appearing in page CONTENT does not
|
|
125
|
-
* trip it. This fixture puts that string in an uncompressed content stream,
|
|
126
|
-
* where a naive substring search finds it.
|
|
127
|
-
*/
|
|
128
|
-
@Test
|
|
129
|
-
public void preflightDoesNotMistakeContentForEncryption() throws Exception {
|
|
130
|
-
File pdf = TestPdfs.withLiteralEncryptInContent(ctx(), "q-decoy.pdf");
|
|
131
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
132
|
-
try {
|
|
133
|
-
assertEquals(1, r.pageCount());
|
|
134
|
-
RasterPixels px = r.render(
|
|
135
|
-
new RasterRequest(0, 0f, 0f, 200f, 200f, 1f, false, RasterRequest.BACKGROUND_WHITE),
|
|
136
|
-
null);
|
|
137
|
-
assertTrue(
|
|
138
|
-
"the decoy document should render normally",
|
|
139
|
-
PdfCanvasRasterizerTest.isRed(PdfCanvasRasterizerTest.rgba(px, 100, 100)));
|
|
140
|
-
} finally {
|
|
141
|
-
r.close();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* CATCHES: a latch that is set but never consulted, and one that cannot be
|
|
147
|
-
* distinguished from an ordinary backend failure.
|
|
148
|
-
*
|
|
149
|
-
* The real poisoning is not triggered here — doing so would take the rest of
|
|
150
|
-
* the run down with it, which is the whole point of the bug. See
|
|
151
|
-
* {@code PdfCanvasPoisonGuardTest}, which does trigger it and is excluded from
|
|
152
|
-
* the default run for exactly that reason.
|
|
153
|
-
*/
|
|
154
|
-
@Test
|
|
155
|
-
public void poisonLatchRefusesInsteadOfOpening() throws Exception {
|
|
156
|
-
File good = TestPdfs.pattern(ctx(), "q-good.pdf", 100, 100);
|
|
157
|
-
try {
|
|
158
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(true);
|
|
159
|
-
try {
|
|
160
|
-
PdfCanvasRasterizer.openFile(good, null, 1).close();
|
|
161
|
-
fail("a poisoned process must refuse to open a document");
|
|
162
|
-
} catch (PdfRasterException e) {
|
|
163
|
-
assertEquals(PdfErrorCode.BACKEND_FAILURE, e.code());
|
|
164
|
-
assertTrue(
|
|
165
|
-
"the message must name the cause, not just fail: " + e.getMessage(),
|
|
166
|
-
e.getMessage().contains("PDFium") && e.getMessage().contains("Restarting"));
|
|
167
|
-
}
|
|
168
|
-
} finally {
|
|
169
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(false);
|
|
170
|
-
}
|
|
171
|
-
// And the guard lifts cleanly, so a stuck latch is caught too.
|
|
172
|
-
PdfCanvasRasterizer.openFile(good, null, 1).close();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The MEASUREMENTS, written down as literals, and the guard checked against
|
|
177
|
-
* them.
|
|
178
|
-
*
|
|
179
|
-
* CATCHES: the poison bound drifting away from what was measured.
|
|
180
|
-
*
|
|
181
|
-
* The previous version of this test could not: it compared
|
|
182
|
-
* {@code failedNativeOpenIsPoisonous()} — whose entire body is
|
|
183
|
-
* {@code SDK_INT < LOWEST_SDK_MEASURED_SAFE_AFTER_FAILED_OPEN} — against
|
|
184
|
-
* {@code SDK_INT < LOWEST_SDK_MEASURED_SAFE_AFTER_FAILED_OPEN}. Both sides
|
|
185
|
-
* move with the constant, so editing it from 31 to 25 (which would silently
|
|
186
|
-
* declare five never-tested API levels safe from a process-killing bug) left
|
|
187
|
-
* every assertion green. A test whose two sides are the same expression is
|
|
188
|
-
* asserting that {@code ==} works.
|
|
189
|
-
*
|
|
190
|
-
* So the expected values are LITERALS here, taken from the measurement in
|
|
191
|
-
* {@link PdfPreflight}'s class comment: API 24 died after one failed
|
|
192
|
-
* construction plus a collection, API 31 and 33 survived twelve. The bound is
|
|
193
|
-
* the lowest level measured GOOD — 31 — deliberately rather than one above the
|
|
194
|
-
* level measured bad, because 25 to 30 were never tested and treating untested
|
|
195
|
-
* territory as safe is how a SIGSEGV ships. Changing either the constant or
|
|
196
|
-
* the rule now requires changing this test, which is the point: the edit
|
|
197
|
-
* becomes visible in review instead of silent.
|
|
198
|
-
*/
|
|
199
|
-
@Test
|
|
200
|
-
public void poisonBoundMatchesWhatWasMeasured() {
|
|
201
|
-
assertEquals(
|
|
202
|
-
"the bound must stay at the lowest API level MEASURED to survive a failed "
|
|
203
|
-
+ "PdfRenderer construction. 24 was measured to die; 31 and 33 survived. "
|
|
204
|
-
+ "25-30 were never tested and are deliberately treated as affected.",
|
|
205
|
-
31,
|
|
206
|
-
PdfPlatformQuirks.LOWEST_SDK_MEASURED_SAFE_AFTER_FAILED_OPEN);
|
|
207
|
-
|
|
208
|
-
boolean poisonous = PdfPlatformQuirks.failedNativeOpenIsPoisonous();
|
|
209
|
-
Log.i(TAG, "quirk: API " + Build.VERSION.SDK_INT + " failedNativeOpenIsPoisonous="
|
|
210
|
-
+ poisonous);
|
|
211
|
-
|
|
212
|
-
Boolean measured = measuredPoisonous(Build.VERSION.SDK_INT);
|
|
213
|
-
if (measured == null) {
|
|
214
|
-
// Not one of the levels this suite has ever run against, so there is no
|
|
215
|
-
// measurement to check the guard against. Say so rather than inventing a
|
|
216
|
-
// rule and asserting the code against itself again.
|
|
217
|
-
Log.i(TAG, "quirk: API " + Build.VERSION.SDK_INT + " was never measured; the "
|
|
218
|
-
+ "verdict is derived from the bound and is not pinned here");
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
assertEquals(
|
|
222
|
-
"API " + Build.VERSION.SDK_INT + " was MEASURED as "
|
|
223
|
-
+ (measured ? "poisonous" : "safe") + " after a failed open",
|
|
224
|
-
measured.booleanValue(),
|
|
225
|
-
poisonous);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* @return what a failed {@code PdfRenderer} construction was OBSERVED to do on
|
|
230
|
-
* this API level, or null for a level that was never put on a device. See
|
|
231
|
-
* the transcript in {@link PdfPreflight}'s class comment.
|
|
232
|
-
*/
|
|
233
|
-
private static Boolean measuredPoisonous(int sdkInt) {
|
|
234
|
-
switch (sdkInt) {
|
|
235
|
-
case 24:
|
|
236
|
-
// SIGSEGV in CPDF_Document after one failed open plus System.gc().
|
|
237
|
-
return Boolean.TRUE;
|
|
238
|
-
case 31:
|
|
239
|
-
case 33:
|
|
240
|
-
// SURVIVED 12 failed opens plus forced collections.
|
|
241
|
-
return Boolean.FALSE;
|
|
242
|
-
default:
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* CATCHES: a rotation probe that is really a version check in disguise, or one
|
|
249
|
-
* that reports UNKNOWN because it silently failed.
|
|
250
|
-
*
|
|
251
|
-
* The probe builds its own /Rotate 90 document and looks at where the ink
|
|
252
|
-
* landed, so its answer is about the device rather than about
|
|
253
|
-
* {@code Build.VERSION}. Asserting it is never UNKNOWN is what catches a probe
|
|
254
|
-
* whose scratch write, parse or render failed — that failure mode is otherwise
|
|
255
|
-
* indistinguishable from a genuinely unknown platform.
|
|
256
|
-
*/
|
|
257
|
-
@Test
|
|
258
|
-
public void rotationProbeAnswersAndCaches() {
|
|
259
|
-
PdfPlatformQuirks.resetRotationSupportForTesting();
|
|
260
|
-
PdfPlatformQuirks.RotationSupport first =
|
|
261
|
-
PdfPlatformQuirks.rotationSupport(ctx().getCacheDir());
|
|
262
|
-
PdfPlatformQuirks.RotationSupport second =
|
|
263
|
-
PdfPlatformQuirks.rotationSupport(ctx().getCacheDir());
|
|
264
|
-
Log.i(TAG, "quirk: API " + Build.VERSION.SDK_INT + " rotationSupport=" + first);
|
|
265
|
-
|
|
266
|
-
assertNotEquals(
|
|
267
|
-
"the probe should have reached a verdict, not failed silently",
|
|
268
|
-
PdfPlatformQuirks.RotationSupport.UNKNOWN,
|
|
269
|
-
first);
|
|
270
|
-
assertEquals("the verdict must be cached, not re-probed differently", first, second);
|
|
271
|
-
|
|
272
|
-
// The probe file must not be left behind in the cache directory.
|
|
273
|
-
assertFalse(
|
|
274
|
-
new File(ctx().getCacheDir(), "pdfcanvas-rotation-probe.pdf").exists());
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* CATCHES: the rotation probe constructing a {@code PdfRenderer} on a process
|
|
279
|
-
* whose PDFium is already poisoned.
|
|
280
|
-
*
|
|
281
|
-
* The probe was the ONE construction site that neither consulted the latch nor
|
|
282
|
-
* armed it — it built a renderer inside a bare
|
|
283
|
-
* {@code catch (Throwable) { return UNKNOWN; }}, so on an affected build it
|
|
284
|
-
* was a segfault waiting for whichever screen asked about rotation first, and
|
|
285
|
-
* a throw there was swallowed rather than latched. Refusing to probe is the
|
|
286
|
-
* right answer because UNKNOWN is already a value callers must handle: the
|
|
287
|
-
* enum's own comment says it is treated as unsupported.
|
|
288
|
-
*
|
|
289
|
-
* Asserting UNKNOWN is what makes this observable from outside. A probe that
|
|
290
|
-
* ignored the latch would return SUPPORTED on API 31/33 and UNSUPPORTED on
|
|
291
|
-
* API 24, so no single wrong constant passes on all three.
|
|
292
|
-
*/
|
|
293
|
-
@Test
|
|
294
|
-
public void theRotationProbeRefusesToRunOnAPoisonedProcess() {
|
|
295
|
-
PdfPlatformQuirks.resetRotationSupportForTesting();
|
|
296
|
-
try {
|
|
297
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(true);
|
|
298
|
-
assertEquals(
|
|
299
|
-
"a poisoned process must not construct a PdfRenderer to answer a "
|
|
300
|
-
+ "rotation question",
|
|
301
|
-
PdfPlatformQuirks.RotationSupport.UNKNOWN,
|
|
302
|
-
PdfPlatformQuirks.rotationSupport(ctx().getCacheDir()));
|
|
303
|
-
} finally {
|
|
304
|
-
PdfPlatformQuirks.setPdfiumPoisonedForTesting(false);
|
|
305
|
-
// The UNKNOWN above is now CACHED, and every other test that asks about
|
|
306
|
-
// rotation would inherit it. Drop it so the next caller probes for real.
|
|
307
|
-
PdfPlatformQuirks.resetRotationSupportForTesting();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// And the guard lifts: with the latch clear the probe reaches a real verdict.
|
|
311
|
-
assertNotEquals(
|
|
312
|
-
PdfPlatformQuirks.RotationSupport.UNKNOWN,
|
|
313
|
-
PdfPlatformQuirks.rotationSupport(ctx().getCacheDir()));
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
private static PdfErrorCode openCode(File file) {
|
|
317
|
-
try {
|
|
318
|
-
PdfCanvasRasterizer.openFile(file, null, 1).close();
|
|
319
|
-
return null;
|
|
320
|
-
} catch (PdfRasterException e) {
|
|
321
|
-
return e.code();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|