@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,775 +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.graphics.Bitmap;
|
|
9
|
-
import android.graphics.Color;
|
|
10
|
-
import android.util.Log;
|
|
11
|
-
|
|
12
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
13
|
-
import androidx.test.platform.app.InstrumentationRegistry;
|
|
14
|
-
|
|
15
|
-
import org.junit.Test;
|
|
16
|
-
import org.junit.runner.RunWith;
|
|
17
|
-
|
|
18
|
-
import java.io.File;
|
|
19
|
-
import java.io.FileInputStream;
|
|
20
|
-
import java.io.IOException;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Geometry, registration, background and pixel layout for
|
|
24
|
-
* {@link PdfCanvasRasterizer}.
|
|
25
|
-
*
|
|
26
|
-
* Every test here names the bug it catches. That is not decoration: a rasterizer
|
|
27
|
-
* fails by producing a PLAUSIBLE WRONG IMAGE — sheared, offset, black where it
|
|
28
|
-
* should be white, channels swapped — and none of those throw. A test that only
|
|
29
|
-
* asserts "some bytes came back" passes for all of them.
|
|
30
|
-
*/
|
|
31
|
-
@RunWith(AndroidJUnit4.class)
|
|
32
|
-
public class PdfCanvasRasterizerTest {
|
|
33
|
-
|
|
34
|
-
private static final String TAG = "PDFCANVAS";
|
|
35
|
-
|
|
36
|
-
private static Context ctx() {
|
|
37
|
-
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private static RasterRequest req(
|
|
41
|
-
int page, float x, float y, float w, float h, float scale, int background) {
|
|
42
|
-
return new RasterRequest(page, x, y, w, h, scale, false, background);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private static RasterRequest white(
|
|
46
|
-
int page, float x, float y, float w, float h, float scale) {
|
|
47
|
-
return req(page, x, y, w, h, scale, RasterRequest.BACKGROUND_WHITE);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* ================================================================ *
|
|
51
|
-
* 1. The S1 property, now against THIS code
|
|
52
|
-
* ================================================================ */
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* A tile must BE the corresponding region of a larger render at the same
|
|
56
|
-
* scale.
|
|
57
|
-
*
|
|
58
|
-
* CATCHES: a sign error in {@code postTranslate} (the tile would show the
|
|
59
|
-
* mirror-image offset), an x/y transposition (the origin here has x != y and
|
|
60
|
-
* the region offset inside the reference has dx != dy, so neither can hide),
|
|
61
|
-
* scale applied to the translate twice, and any inset/rounding of the doc rect
|
|
62
|
-
* before it reaches the matrix. All four produce a perfectly valid-looking
|
|
63
|
-
* image, and all four break the overlap registration that the detail rasters
|
|
64
|
-
* and the cache both depend on.
|
|
65
|
-
*
|
|
66
|
-
* Note that BOTH renders here use a non-zero origin. Spike S1 compared a tile
|
|
67
|
-
* against a full-page render, so its reference had a zero translate and could
|
|
68
|
-
* not have caught a translate that is wrong only for a moving origin.
|
|
69
|
-
*/
|
|
70
|
-
@Test
|
|
71
|
-
public void tileIsPixelIdenticalToTheSameRegionOfALargerRender() throws Exception {
|
|
72
|
-
File pdf = TestPdfs.pattern(ctx(), "s1-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
73
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
74
|
-
try {
|
|
75
|
-
final float scale = 4f;
|
|
76
|
-
// Reference region and tile, both interior, both with x != y.
|
|
77
|
-
RasterPixels reference = r.render(white(0, 100f, 300f, 400f, 400f, scale), null);
|
|
78
|
-
RasterPixels tile = r.render(white(0, 200f, 450f, 128f, 128f, scale), null);
|
|
79
|
-
|
|
80
|
-
int dx = Math.round((200f - 100f) * scale);
|
|
81
|
-
int dy = Math.round((450f - 300f) * scale);
|
|
82
|
-
|
|
83
|
-
Diff diff = compareRegion(tile, reference, dx, dy);
|
|
84
|
-
String summary = "tile 512^2 @" + scale + "x inside " + reference.width + "x"
|
|
85
|
-
+ reference.height + " at (" + dx + "," + dy + ") | " + diff;
|
|
86
|
-
Log.i(TAG, "S1-property " + summary);
|
|
87
|
-
|
|
88
|
-
// Same bar spike S1 used: anti-aliasing on a stroke edge can differ by a
|
|
89
|
-
// hair, but a wrong ORIGIN or SCALE moves a large share of this
|
|
90
|
-
// high-frequency pattern and cannot sneak under it.
|
|
91
|
-
assertTrue(summary, diff.mismatchPercent < 0.5);
|
|
92
|
-
assertTrue(summary, diff.maxDelta <= 64);
|
|
93
|
-
} finally {
|
|
94
|
-
r.close();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Two rasters asked for at the SAME scale must agree pixel-for-pixel wherever
|
|
100
|
-
* they overlap, even when neither doc rect lands on a whole number of pixels.
|
|
101
|
-
*
|
|
102
|
-
* CATCHES: re-deriving the scale from the rounded pixel size
|
|
103
|
-
* ({@code pixelWidth() / docWidth}) instead of using the requested
|
|
104
|
-
* {@code scale} verbatim. That change is tempting — it makes each raster cover
|
|
105
|
-
* its doc rect to the last sub-pixel — and it is invisible to
|
|
106
|
-
* {@link #tileIsPixelIdenticalToTheSameRegionOfALargerRender}, whose rects are
|
|
107
|
-
* 400 and 128 points at 4x and therefore land on exact pixel counts, so the
|
|
108
|
-
* derived scale comes out to exactly 4 for both. {@code tileMatrix}'s comment
|
|
109
|
-
* used to claim this property "rests on" that test. It did not, and nothing
|
|
110
|
-
* pinned it until this.
|
|
111
|
-
*
|
|
112
|
-
* The rects here are chosen so the rounding goes OPPOSITE ways:
|
|
113
|
-
* 128.125 x 4 = 512.5, which rounds UP to 513, and 128.1 x 4 = 512.4, which
|
|
114
|
-
* rounds DOWN to 512. Under exact scale both rasters map pixel i to doc
|
|
115
|
-
* {@code origin + i/4} and are identical over the 512-pixel overlap. Under a
|
|
116
|
-
* derived scale one is drawn at 513/128.125 and the other at 512/128.1, and
|
|
117
|
-
* the two drift apart by close to a full pixel by the far corner — which on
|
|
118
|
-
* this high-frequency pattern moves a large share of the pixels.
|
|
119
|
-
*
|
|
120
|
-
* Both rects share an origin, so the overlap starts at (0, 0) in each and no
|
|
121
|
-
* offset arithmetic can absorb the error.
|
|
122
|
-
*/
|
|
123
|
-
@Test
|
|
124
|
-
public void rastersAtTheSameScaleAgreeWhereTheyOverlap() throws Exception {
|
|
125
|
-
File pdf = TestPdfs.pattern(ctx(), "scale-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
126
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
127
|
-
try {
|
|
128
|
-
final float scale = 4f;
|
|
129
|
-
final float originX = 100f;
|
|
130
|
-
final float originY = 200f;
|
|
131
|
-
|
|
132
|
-
RasterPixels roundsUp =
|
|
133
|
-
r.render(white(0, originX, originY, 128.125f, 128.125f, scale), null);
|
|
134
|
-
RasterPixels roundsDown =
|
|
135
|
-
r.render(white(0, originX, originY, 128.1f, 128.1f, scale), null);
|
|
136
|
-
|
|
137
|
-
// The rounding is the whole point of the fixture, so assert it happened.
|
|
138
|
-
assertEquals("128.125pt @4x must round UP to 513px", 513, roundsUp.width);
|
|
139
|
-
assertEquals("128.1pt @4x must round DOWN to 512px", 512, roundsDown.width);
|
|
140
|
-
|
|
141
|
-
Diff diff = compareRegion(roundsDown, roundsUp, 0, 0);
|
|
142
|
-
String summary = "overlap 512^2 inside 513^2 @" + scale + "x | " + diff;
|
|
143
|
-
Log.i(TAG, "same-scale " + summary);
|
|
144
|
-
|
|
145
|
-
assertTrue(summary, diff.mismatchPercent < 0.5);
|
|
146
|
-
assertTrue(summary, diff.maxDelta <= 64);
|
|
147
|
-
} finally {
|
|
148
|
-
r.close();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* ================================================================ *
|
|
153
|
-
* 2. Background — S1 finding 3
|
|
154
|
-
* ================================================================ */
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* CATCHES: a missing pre-fill. Spike S1 measured that
|
|
158
|
-
* {@code PdfRenderer.render} PRESERVES whatever was in the bitmap and never
|
|
159
|
-
* clears it, so area the page does not cover keeps the allocation's zeros —
|
|
160
|
-
* transparent black — and composites under the app's annotation layers as a
|
|
161
|
-
* BLACK page rather than a blank one. This test samples a pixel beyond the
|
|
162
|
-
* page edge, which the renderer provably never touches, so a white result
|
|
163
|
-
* there can only have come from the pre-fill.
|
|
164
|
-
*/
|
|
165
|
-
@Test
|
|
166
|
-
public void whiteBackgroundFillsAreaThePageDoesNotCover() throws Exception {
|
|
167
|
-
File pdf = TestPdfs.pattern(ctx(), "bg-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
168
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
169
|
-
try {
|
|
170
|
-
// Straddles the page's bottom-right corner: doc x 500..700, y 700..900,
|
|
171
|
-
// against a 612x792 page.
|
|
172
|
-
RasterPixels px = r.render(white(0, 500f, 700f, 200f, 200f, 1f), null);
|
|
173
|
-
assertEquals(200, px.width);
|
|
174
|
-
assertEquals(200, px.height);
|
|
175
|
-
|
|
176
|
-
// (190, 190) is doc (690, 890) — outside the page on both axes.
|
|
177
|
-
int[] outside = rgba(px, 190, 190);
|
|
178
|
-
assertEquals("R outside page: " + describe(outside), 255, outside[0]);
|
|
179
|
-
assertEquals("G outside page: " + describe(outside), 255, outside[1]);
|
|
180
|
-
assertEquals("B outside page: " + describe(outside), 255, outside[2]);
|
|
181
|
-
assertEquals("A outside page: " + describe(outside), 255, outside[3]);
|
|
182
|
-
} finally {
|
|
183
|
-
r.close();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* CATCHES: a backend that ignores {@code background} and always fills white.
|
|
189
|
-
* That failure is invisible on an opaque page and only shows up when the host
|
|
190
|
-
* composites the raster over something, at which point the "transparent"
|
|
191
|
-
* raster hides the layer beneath it.
|
|
192
|
-
*
|
|
193
|
-
* Also catches the opposite mistake — a bitmap that was never rendered into at
|
|
194
|
-
* all — because the in-page sample must be OPAQUE.
|
|
195
|
-
*/
|
|
196
|
-
@Test
|
|
197
|
-
public void transparentBackgroundLeavesUncoveredAreaTransparent() throws Exception {
|
|
198
|
-
File pdf = TestPdfs.pattern(ctx(), "bg2-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
199
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
200
|
-
try {
|
|
201
|
-
RasterPixels px =
|
|
202
|
-
r.render(
|
|
203
|
-
req(0, 500f, 700f, 200f, 200f, 1f, RasterRequest.BACKGROUND_TRANSPARENT), null);
|
|
204
|
-
|
|
205
|
-
int[] outside = rgba(px, 190, 190);
|
|
206
|
-
assertEquals("alpha outside page: " + describe(outside), 0, outside[3]);
|
|
207
|
-
|
|
208
|
-
// (10, 10) is doc (510, 710) — well inside the page, which the pattern
|
|
209
|
-
// paints opaque white.
|
|
210
|
-
int[] inside = rgba(px, 10, 10);
|
|
211
|
-
assertEquals("alpha inside page: " + describe(inside), 255, inside[3]);
|
|
212
|
-
} finally {
|
|
213
|
-
r.close();
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/* ================================================================ *
|
|
218
|
-
* 3. Page geometry
|
|
219
|
-
* ================================================================ */
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* CATCHES: probing page 0 and reporting its size for every page — which a
|
|
223
|
-
* single-size document cannot catch, and which is the natural shape of a
|
|
224
|
-
* "cache the first geometry" bug.
|
|
225
|
-
*/
|
|
226
|
-
@Test
|
|
227
|
-
public void pageGeometryIsPerPage() throws Exception {
|
|
228
|
-
File pdf = TestPdfs.mixedSizes(ctx(), "mixed.pdf");
|
|
229
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
230
|
-
try {
|
|
231
|
-
assertEquals(3, r.pageCount());
|
|
232
|
-
for (int p = 0; p < 3; p++) {
|
|
233
|
-
int[] expected = TestPdfs.mixedSizeExpectation(p);
|
|
234
|
-
PageGeometry g = r.pageGeometry(p);
|
|
235
|
-
assertEquals("page " + p + " width", expected[0], g.widthPoints);
|
|
236
|
-
assertEquals("page " + p + " height", expected[1], g.heightPoints);
|
|
237
|
-
assertEquals("page " + p + " index", p, g.index);
|
|
238
|
-
}
|
|
239
|
-
} finally {
|
|
240
|
-
r.close();
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Page geometry is post-rotation on EVERY level tested, including the API 24
|
|
246
|
-
* floor.
|
|
247
|
-
*
|
|
248
|
-
* CATCHES: reporting the MediaBox instead of the displayed box. This half is
|
|
249
|
-
* unconditional — {@code getWidth()} forwards to PDFium's
|
|
250
|
-
* {@code FPDF_GetPageWidth}, and {@code CPDF_Page::UpdateDimensions} swaps the
|
|
251
|
-
* media box's axes for /Rotate 90 and 270 on every build measured.
|
|
252
|
-
*/
|
|
253
|
-
@Test
|
|
254
|
-
public void rotatedPageGeometryIsPostRotation() throws Exception {
|
|
255
|
-
File pdf = TestPdfs.rotated90(ctx(), "rotated-geom.pdf");
|
|
256
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
257
|
-
try {
|
|
258
|
-
PageGeometry g = r.pageGeometry(0);
|
|
259
|
-
Log.i(TAG, "rotation geometry " + g);
|
|
260
|
-
assertEquals("width should be the 792pt long edge", TestPdfs.LETTER_H, g.widthPoints);
|
|
261
|
-
assertEquals("height should be the 612pt short edge", TestPdfs.LETTER_W, g.heightPoints);
|
|
262
|
-
assertEquals("residual rotation the caller must apply", 0, g.rotationDegrees);
|
|
263
|
-
} finally {
|
|
264
|
-
r.close();
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Whether a rotated page is DRAWN upright is a platform capability, and this
|
|
270
|
-
* asserts the backend's report of it against what the platform actually does.
|
|
271
|
-
*
|
|
272
|
-
* The fixture is a 612x792 page with {@code /Rotate 90} and two markers: red
|
|
273
|
-
* at the unrotated top-left, blue at the unrotated bottom-left. A clockwise
|
|
274
|
-
* quarter turn — which is what /Rotate means — puts red at the displayed
|
|
275
|
-
* top-RIGHT and blue at the displayed top-LEFT. An unrotated render leaves red
|
|
276
|
-
* top-left; a counter-clockwise render puts red bottom-left. All three are
|
|
277
|
-
* distinguishable here, which a dimensions-only assertion could not do:
|
|
278
|
-
* swapping width and height while drawing the content unrotated would pass
|
|
279
|
-
* that, and is exactly what API 24 does.
|
|
280
|
-
*
|
|
281
|
-
* CATCHES, on a platform that rotates: any of those three wrong placements.
|
|
282
|
-
* CATCHES, on a platform that does not: a {@link PdfPlatformQuirks} probe that
|
|
283
|
-
* lies. The UNSUPPORTED branch asserts the MEASURED broken layout — content
|
|
284
|
-
* drawn unrotated with the y-flip taken from the post-rotation height, so blue
|
|
285
|
-
* lands at y = 612 - 100 and red is pushed off the top — rather than merely
|
|
286
|
-
* skipping. A probe stuck on SUPPORTED fails on API 24 and one stuck on
|
|
287
|
-
* UNSUPPORTED fails on API 31/33, so neither constant passes this.
|
|
288
|
-
*/
|
|
289
|
-
@Test
|
|
290
|
-
public void rotatedPageRendersUprightWhereThePlatformSupportsIt() throws Exception {
|
|
291
|
-
PdfPlatformQuirks.RotationSupport support =
|
|
292
|
-
PdfCanvasRasterizer.rotationSupport(ctx().getCacheDir());
|
|
293
|
-
File pdf = TestPdfs.rotated90(ctx(), "rotated-render.pdf");
|
|
294
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
295
|
-
try {
|
|
296
|
-
PageGeometry g = r.pageGeometry(0);
|
|
297
|
-
RasterPixels px = r.render(white(0, 0f, 0f, g.widthPoints, g.heightPoints, 1f), null);
|
|
298
|
-
assertEquals(TestPdfs.LETTER_H, px.width);
|
|
299
|
-
assertEquals(TestPdfs.LETTER_W, px.height);
|
|
300
|
-
|
|
301
|
-
int[] topRight = rgba(px, px.width - 40, 40);
|
|
302
|
-
int[] topLeft = rgba(px, 40, 40);
|
|
303
|
-
int[] middle = rgba(px, px.width / 2, px.height / 2);
|
|
304
|
-
Log.i(TAG, "rotation support=" + support + " samples tr=" + describe(topRight)
|
|
305
|
-
+ " tl=" + describe(topLeft) + " mid=" + describe(middle));
|
|
306
|
-
|
|
307
|
-
if (support == PdfPlatformQuirks.RotationSupport.SUPPORTED) {
|
|
308
|
-
assertTrue("top-right should be the red marker: " + describe(topRight), isRed(topRight));
|
|
309
|
-
assertTrue("top-left should be the blue marker: " + describe(topLeft), isBlue(topLeft));
|
|
310
|
-
assertTrue("middle should be blank page: " + describe(middle), isWhite(middle));
|
|
311
|
-
} else {
|
|
312
|
-
// The measured API 24 layout: content unrotated, y-flip using the
|
|
313
|
-
// post-rotation height of 612, so blue (PDF y 0..100) lands at
|
|
314
|
-
// 612-100..612 and red (PDF y 692..792) is translated off the top.
|
|
315
|
-
assertTrue(
|
|
316
|
-
"probe says " + support + ", so red must NOT be at the top-right: "
|
|
317
|
-
+ describe(topRight),
|
|
318
|
-
!isRed(topRight));
|
|
319
|
-
int[] blue = rgba(px, 40, 560);
|
|
320
|
-
assertTrue(
|
|
321
|
-
"probe says " + support + ", so blue must be at the measured broken "
|
|
322
|
-
+ "position (x40, y560): " + describe(blue),
|
|
323
|
-
isBlue(blue));
|
|
324
|
-
}
|
|
325
|
-
} finally {
|
|
326
|
-
r.close();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* CATCHES: a lazy {@code pageGeometry} that opens the page on demand.
|
|
332
|
-
*
|
|
333
|
-
* The contract says the call is cheap and must not load a page, and the only
|
|
334
|
-
* way to observe that from outside is to ask when loading a page is
|
|
335
|
-
* impossible. After {@code close()} every PdfRenderer is shut; a lazy
|
|
336
|
-
* implementation would throw or crash here, while a precomputed one returns
|
|
337
|
-
* the same numbers it did before. Page 2 is read ONLY after close, so an
|
|
338
|
-
* implementation that lazily cached page 0 during open cannot pass by
|
|
339
|
-
* accident.
|
|
340
|
-
*/
|
|
341
|
-
@Test
|
|
342
|
-
public void pageGeometryIsPrecomputedAtOpenNotLoadedOnDemand() throws Exception {
|
|
343
|
-
File pdf = TestPdfs.mixedSizes(ctx(), "mixed-lazy.pdf");
|
|
344
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
345
|
-
PageGeometry beforeClose = r.pageGeometry(0);
|
|
346
|
-
r.close();
|
|
347
|
-
|
|
348
|
-
PageGeometry afterClose = r.pageGeometry(0);
|
|
349
|
-
assertEquals(beforeClose.widthPoints, afterClose.widthPoints);
|
|
350
|
-
assertEquals(beforeClose.heightPoints, afterClose.heightPoints);
|
|
351
|
-
|
|
352
|
-
int[] expected = TestPdfs.mixedSizeExpectation(2);
|
|
353
|
-
PageGeometry neverAskedBefore = r.pageGeometry(2);
|
|
354
|
-
assertEquals(expected[0], neverAskedBefore.widthPoints);
|
|
355
|
-
assertEquals(expected[1], neverAskedBefore.heightPoints);
|
|
356
|
-
assertEquals(3, r.pageCount());
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/* ================================================================ *
|
|
360
|
-
* 4. Pixel layout — byte order and stride
|
|
361
|
-
* ================================================================ */
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* CATCHES: reporting {@code bgra8888}, or emitting ARGB (the order the config
|
|
365
|
-
* is NAMED for) while claiming RGBA.
|
|
366
|
-
*
|
|
367
|
-
* {@code Bitmap.Config.ARGB_8888} is named after the packed int that
|
|
368
|
-
* {@code getPixel} returns, not after the in-memory byte order, which is
|
|
369
|
-
* R,G,B,A. Getting this backwards does not fail — it swaps red and blue in
|
|
370
|
-
* every raster, which looks like a colour-management problem and gets chased
|
|
371
|
-
* in the wrong place for a day.
|
|
372
|
-
*/
|
|
373
|
-
@Test
|
|
374
|
-
public void pixelBytesAreRgbaNotBgraOrArgb() throws Exception {
|
|
375
|
-
File pdf = TestPdfs.quadrants(ctx(), "quads.pdf", 200, 300);
|
|
376
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
377
|
-
try {
|
|
378
|
-
RasterPixels px = r.render(white(0, 0f, 0f, 200f, 300f, 1f), null);
|
|
379
|
-
assertEquals(RasterPixels.FORMAT_RGBA_8888, px.format);
|
|
380
|
-
|
|
381
|
-
int[] red = rgba(px, 50, 75);
|
|
382
|
-
int[] green = rgba(px, 150, 75);
|
|
383
|
-
int[] blue = rgba(px, 50, 225);
|
|
384
|
-
int[] black = rgba(px, 150, 225);
|
|
385
|
-
Log.i(TAG, "channel order r=" + describe(red) + " g=" + describe(green)
|
|
386
|
-
+ " b=" + describe(blue) + " k=" + describe(black));
|
|
387
|
-
|
|
388
|
-
assertTrue("first byte of the red quadrant must be R: " + describe(red), isRed(red));
|
|
389
|
-
assertTrue("green quadrant: " + describe(green), isGreen(green));
|
|
390
|
-
assertTrue("blue quadrant: " + describe(blue), isBlue(blue));
|
|
391
|
-
assertTrue("black quadrant: " + describe(black), isBlack(black));
|
|
392
|
-
} finally {
|
|
393
|
-
r.close();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* The reported {@code rowBytes} must be the stride the PLATFORM chose for a
|
|
399
|
-
* bitmap of these dimensions and config — not a number this backend derived.
|
|
400
|
-
*
|
|
401
|
-
* CATCHES: any stride the backend states that the platform did not use. The
|
|
402
|
-
* reference is an independently allocated {@code Bitmap} of the same width,
|
|
403
|
-
* height and {@code ARGB_8888} config, asked for its own
|
|
404
|
-
* {@code getRowBytes()} / {@code getByteCount()}. That is the only claim that
|
|
405
|
-
* can be checked from outside, and it is checked at seven widths including odd
|
|
406
|
-
* and prime ones, which is where a platform would pad if it padded anywhere.
|
|
407
|
-
*
|
|
408
|
-
* WHAT IT CANNOT CATCH, stated plainly because the previous version of this
|
|
409
|
-
* comment claimed otherwise. {@code Bitmap.createBitmap} goes to
|
|
410
|
-
* {@code SkImageInfo::minRowBytes()}, which is {@code width * bytesPerPixel}
|
|
411
|
-
* with no alignment, so on every Android build measured here the stride IS
|
|
412
|
-
* {@code width * 4} at every width — 1, 3, 17, 33, 127, 200 and 201 all come
|
|
413
|
-
* back tight. On such a platform an implementation that ASSUMED
|
|
414
|
-
* {@code width * 4} is indistinguishable from one that read
|
|
415
|
-
* {@code getRowBytes()}, by this test or by any other, because the two
|
|
416
|
-
* quantities are equal. The old buffer-length assertion
|
|
417
|
-
* ({@code rowBytes * height == bytes.length}) did not close that gap either:
|
|
418
|
-
* both sides move together when the buffer is sized from
|
|
419
|
-
* {@code getByteCount()}.
|
|
420
|
-
*
|
|
421
|
-
* So the honest statement of what is defended: the backend states a stride the
|
|
422
|
-
* platform agrees with, and the moment a device DOES pad — a bitmap that came
|
|
423
|
-
* from a hardware buffer, or a future allocator that aligns rows — the
|
|
424
|
-
* assumption becomes wrong and this test is the thing that notices. The
|
|
425
|
-
* padded-stride path itself is covered on the JS side, where the fake backend
|
|
426
|
-
* can pad on demand (`reports the stride the backend gave, padding and all`).
|
|
427
|
-
*/
|
|
428
|
-
@Test
|
|
429
|
-
public void rowBytesIsReportedNotAssumed() throws Exception {
|
|
430
|
-
File pdf = TestPdfs.quadrants(ctx(), "quads-stride.pdf", 200, 300);
|
|
431
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
432
|
-
StringBuilder measured = new StringBuilder("rowBytes measured:");
|
|
433
|
-
try {
|
|
434
|
-
for (int targetWidth : new int[] {1, 3, 17, 33, 127, 200, 201}) {
|
|
435
|
-
float scale = targetWidth / 200f;
|
|
436
|
-
RasterPixels px = r.render(white(0, 0f, 0f, 200f, 300f, scale), null);
|
|
437
|
-
assertEquals(targetWidth, px.width);
|
|
438
|
-
|
|
439
|
-
// The platform's own answer for this exact allocation. Same call the
|
|
440
|
-
// backend makes, on a bitmap this test owns, so the comparison is
|
|
441
|
-
// against the platform rather than against the backend's arithmetic.
|
|
442
|
-
Bitmap probe = Bitmap.createBitmap(px.width, px.height, Bitmap.Config.ARGB_8888);
|
|
443
|
-
int platformRowBytes = probe.getRowBytes();
|
|
444
|
-
int platformByteCount = probe.getByteCount();
|
|
445
|
-
probe.recycle();
|
|
446
|
-
|
|
447
|
-
measured
|
|
448
|
-
.append(' ')
|
|
449
|
-
.append(px.width)
|
|
450
|
-
.append('x')
|
|
451
|
-
.append(px.height)
|
|
452
|
-
.append("->")
|
|
453
|
-
.append(px.rowBytes)
|
|
454
|
-
.append(px.rowBytes == px.width * 4 ? "(tight)" : "(PADDED)");
|
|
455
|
-
|
|
456
|
-
assertEquals(
|
|
457
|
-
"reported rowBytes must be the platform's own stride for a "
|
|
458
|
-
+ px.width + "x" + px.height + " ARGB_8888 bitmap",
|
|
459
|
-
platformRowBytes,
|
|
460
|
-
px.rowBytes);
|
|
461
|
-
assertEquals(
|
|
462
|
-
"buffer length must be the platform's own byteCount for a "
|
|
463
|
-
+ px.width + "x" + px.height + " ARGB_8888 bitmap",
|
|
464
|
-
platformByteCount,
|
|
465
|
-
px.bytes.length);
|
|
466
|
-
assertTrue(
|
|
467
|
-
"rowBytes " + px.rowBytes + " cannot hold " + px.width + " pixels",
|
|
468
|
-
px.rowBytes >= px.width * 4);
|
|
469
|
-
assertEquals(
|
|
470
|
-
"buffer length must be rowBytes*height for width " + px.width,
|
|
471
|
-
px.rowBytes * px.height,
|
|
472
|
-
px.bytes.length);
|
|
473
|
-
|
|
474
|
-
// Quadrant sampling only means anything once the raster is big enough
|
|
475
|
-
// for a quadrant to have interior pixels.
|
|
476
|
-
if (px.width >= 16 && px.height >= 16) {
|
|
477
|
-
int qx = px.width / 4;
|
|
478
|
-
int qy = px.height / 4;
|
|
479
|
-
assertTrue("red quadrant at width " + px.width + ": "
|
|
480
|
-
+ describe(rgba(px, qx, qy)), isRed(rgba(px, qx, qy)));
|
|
481
|
-
assertTrue("green quadrant at width " + px.width + ": "
|
|
482
|
-
+ describe(rgba(px, px.width - 1 - qx, qy)),
|
|
483
|
-
isGreen(rgba(px, px.width - 1 - qx, qy)));
|
|
484
|
-
assertTrue("blue quadrant at width " + px.width + ": "
|
|
485
|
-
+ describe(rgba(px, qx, px.height - 1 - qy)),
|
|
486
|
-
isBlue(rgba(px, qx, px.height - 1 - qy)));
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
} finally {
|
|
490
|
-
r.close();
|
|
491
|
-
}
|
|
492
|
-
Log.i(TAG, measured.toString());
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/* ================================================================ *
|
|
496
|
-
* 5. Request plumbing
|
|
497
|
-
* ================================================================ */
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* CATCHES: ignoring {@code RasterRequest.page} and rendering page 0 every
|
|
501
|
-
* time. Identical-looking pages make that bug invisible, so each page here is
|
|
502
|
-
* a different flat colour.
|
|
503
|
-
*/
|
|
504
|
-
@Test
|
|
505
|
-
public void eachRequestRendersItsOwnPage() throws Exception {
|
|
506
|
-
int[] colors = {Color.RED, Color.GREEN, Color.BLUE};
|
|
507
|
-
File pdf = TestPdfs.coloredPages(ctx(), "colored.pdf", colors, 100, 100);
|
|
508
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
509
|
-
try {
|
|
510
|
-
assertEquals(3, r.pageCount());
|
|
511
|
-
assertTrue(isRed(rgba(r.render(white(0, 0f, 0f, 100f, 100f, 1f), null), 50, 50)));
|
|
512
|
-
assertTrue(isGreen(rgba(r.render(white(1, 0f, 0f, 100f, 100f, 1f), null), 50, 50)));
|
|
513
|
-
assertTrue(isBlue(rgba(r.render(white(2, 0f, 0f, 100f, 100f, 1f), null), 50, 50)));
|
|
514
|
-
} finally {
|
|
515
|
-
r.close();
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* CATCHES: a spool path that truncates, reorders, or reopens the wrong file.
|
|
521
|
-
* Byte-for-byte equality against the same document opened from disk is the
|
|
522
|
-
* only assertion that catches a spool which is merely CLOSE to right.
|
|
523
|
-
*/
|
|
524
|
-
@Test
|
|
525
|
-
public void openBytesMatchesOpenFile() throws Exception {
|
|
526
|
-
File pdf = TestPdfs.quadrants(ctx(), "quads-bytes.pdf", 200, 300);
|
|
527
|
-
byte[] data = readAll(pdf);
|
|
528
|
-
|
|
529
|
-
RasterRequest request = white(0, 20f, 30f, 150f, 150f, 2f);
|
|
530
|
-
PdfCanvasRasterizer fromFile = PdfCanvasRasterizer.openFile(pdf, null, 1);
|
|
531
|
-
RasterPixels a;
|
|
532
|
-
try {
|
|
533
|
-
a = fromFile.render(request, null);
|
|
534
|
-
} finally {
|
|
535
|
-
fromFile.close();
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
PdfCanvasRasterizer fromBytes =
|
|
539
|
-
PdfCanvasRasterizer.openBytes(ctx().getCacheDir(), data, null, 1);
|
|
540
|
-
RasterPixels b;
|
|
541
|
-
try {
|
|
542
|
-
assertEquals(fromFile.pageCount(), fromBytes.pageCount());
|
|
543
|
-
b = fromBytes.render(request, null);
|
|
544
|
-
} finally {
|
|
545
|
-
fromBytes.close();
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
assertEquals(a.width, b.width);
|
|
549
|
-
assertEquals(a.height, b.height);
|
|
550
|
-
assertEquals(a.rowBytes, b.rowBytes);
|
|
551
|
-
Diff diff = compareRegion(b, a, 0, 0);
|
|
552
|
-
assertEquals("openBytes must match openFile exactly: " + diff, 0, diff.mismatches);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* CATCHES: an {@code openBytes} spool file that outlives its rasterizer.
|
|
557
|
-
*
|
|
558
|
-
* Nothing asserted this before, and it is the kind of leak nobody ever
|
|
559
|
-
* notices: a whole extra copy of every document opened from bytes, left in the
|
|
560
|
-
* cache directory until the OS decides to reclaim it. On the ARCH-D drawings
|
|
561
|
-
* this package exists for that is tens of megabytes per document, and the copy
|
|
562
|
-
* is invisible — the app has a working rasterizer and no error anywhere.
|
|
563
|
-
*
|
|
564
|
-
* Both exits are checked, because they are different code paths: {@code close()}
|
|
565
|
-
* deletes the spool it was handed, and the failure path inside
|
|
566
|
-
* {@code openBytes} has to delete a spool no rasterizer ever took ownership of.
|
|
567
|
-
* A private directory is used rather than the shared cache dir so the count is
|
|
568
|
-
* an assertion rather than a guess about what else is in there.
|
|
569
|
-
*/
|
|
570
|
-
@Test
|
|
571
|
-
public void openBytesDeletesItsSpoolFile() throws Exception {
|
|
572
|
-
File spoolDir = new File(ctx().getCacheDir(), "spool-lifecycle");
|
|
573
|
-
emptyDirectory(spoolDir);
|
|
574
|
-
|
|
575
|
-
byte[] good = readAll(TestPdfs.quadrants(ctx(), "spool-quads.pdf", 200, 300));
|
|
576
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openBytes(spoolDir, good, null, 1);
|
|
577
|
-
assertEquals(
|
|
578
|
-
"the document must be spooled to disk while it is open — PdfRenderer "
|
|
579
|
-
+ "needs a seekable descriptor",
|
|
580
|
-
1,
|
|
581
|
-
countFiles(spoolDir));
|
|
582
|
-
r.close();
|
|
583
|
-
assertEquals("close() must delete the spool file", 0, countFiles(spoolDir));
|
|
584
|
-
|
|
585
|
-
// A failed open: the spool exists, and nothing else will ever delete it.
|
|
586
|
-
byte[] garbage = readAll(TestPdfs.notAPdf(ctx(), "spool-notes.txt"));
|
|
587
|
-
try {
|
|
588
|
-
PdfCanvasRasterizer.openBytes(spoolDir, garbage, null, 1).close();
|
|
589
|
-
fail("garbage bytes should not have opened");
|
|
590
|
-
} catch (PdfRasterException e) {
|
|
591
|
-
assertEquals(PdfErrorCode.CORRUPT, e.code());
|
|
592
|
-
}
|
|
593
|
-
assertEquals("a failed openBytes must delete its spool file", 0, countFiles(spoolDir));
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
private static void emptyDirectory(File dir) {
|
|
597
|
-
File[] existing = dir.listFiles();
|
|
598
|
-
if (existing != null) {
|
|
599
|
-
for (File f : existing) {
|
|
600
|
-
//noinspection ResultOfMethodCallIgnored
|
|
601
|
-
f.delete();
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
if (!dir.exists() && !dir.mkdirs()) {
|
|
605
|
-
fail("could not create " + dir);
|
|
606
|
-
}
|
|
607
|
-
assertEquals("the spool directory must start empty", 0, countFiles(dir));
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
private static int countFiles(File dir) {
|
|
611
|
-
File[] files = dir.listFiles();
|
|
612
|
-
return files == null ? 0 : files.length;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/* ================================================================ *
|
|
616
|
-
* 6. Timings — reported, not asserted
|
|
617
|
-
* ================================================================ */
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* No assertion beyond "it completed". These numbers exist to be READ: the
|
|
621
|
-
* package's {@code minEpochIntervalMs} is meant to be the measured p95 raster
|
|
622
|
-
* time, and an emulator can only give the SHAPE of that. Asserting a wall time
|
|
623
|
-
* here would produce a test that fails on a loaded CI machine and tells nobody
|
|
624
|
-
* anything.
|
|
625
|
-
*/
|
|
626
|
-
@Test
|
|
627
|
-
public void reportsTimings() throws Exception {
|
|
628
|
-
File letter =
|
|
629
|
-
TestPdfs.pattern(ctx(), "t-letter.pdf", TestPdfs.LETTER_W, TestPdfs.LETTER_H);
|
|
630
|
-
File arch = TestPdfs.pattern(ctx(), "t-archd.pdf", TestPdfs.ARCH_D_W, TestPdfs.ARCH_D_H);
|
|
631
|
-
|
|
632
|
-
StringBuilder sb = new StringBuilder("timings (ms, best of 3):");
|
|
633
|
-
PdfCanvasRasterizer r = PdfCanvasRasterizer.openFile(arch, null, 1);
|
|
634
|
-
try {
|
|
635
|
-
sb.append(" archD-tile512@1x=").append(best(r, white(0, 400f, 600f, 512f, 512f, 1f)));
|
|
636
|
-
sb.append(" @4x=").append(best(r, white(0, 400f, 600f, 128f, 128f, 4f)));
|
|
637
|
-
sb.append(" @8x=").append(best(r, white(0, 400f, 600f, 64f, 64f, 8f)));
|
|
638
|
-
// The fit-scale whole-page base raster: 1170x1755 for an ARCH-D sheet.
|
|
639
|
-
sb.append(" archD-base1170x1755=")
|
|
640
|
-
.append(best(r, white(0, 0f, 0f, TestPdfs.ARCH_D_W, TestPdfs.ARCH_D_H, 0.677f)));
|
|
641
|
-
} finally {
|
|
642
|
-
r.close();
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
PdfCanvasRasterizer r2 = PdfCanvasRasterizer.openFile(letter, null, 1);
|
|
646
|
-
try {
|
|
647
|
-
sb.append(" letter-full@2x=")
|
|
648
|
-
.append(best(r2, white(0, 0f, 0f, TestPdfs.LETTER_W, TestPdfs.LETTER_H, 2f)));
|
|
649
|
-
long t0 = System.nanoTime();
|
|
650
|
-
PdfCanvasRasterizer probe = PdfCanvasRasterizer.openFile(letter, null, 2);
|
|
651
|
-
long openMs = (System.nanoTime() - t0) / 1_000_000;
|
|
652
|
-
probe.close();
|
|
653
|
-
sb.append(" open+geometry(1page,pool2)=").append(openMs);
|
|
654
|
-
} finally {
|
|
655
|
-
r2.close();
|
|
656
|
-
}
|
|
657
|
-
Log.i(TAG, sb.toString());
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
private static long best(PdfCanvasRasterizer r, RasterRequest request)
|
|
661
|
-
throws PdfRasterException {
|
|
662
|
-
long best = Long.MAX_VALUE;
|
|
663
|
-
for (int i = 0; i < 3; i++) {
|
|
664
|
-
long t = System.nanoTime();
|
|
665
|
-
r.render(request, null);
|
|
666
|
-
long ms = (System.nanoTime() - t) / 1_000_000;
|
|
667
|
-
if (ms < best) {
|
|
668
|
-
best = ms;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
return best;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/* ================================================================ *
|
|
675
|
-
* Helpers
|
|
676
|
-
* ================================================================ */
|
|
677
|
-
|
|
678
|
-
/** RGBA at (x, y), read through the REPORTED stride. */
|
|
679
|
-
static int[] rgba(RasterPixels px, int x, int y) {
|
|
680
|
-
return new int[] {
|
|
681
|
-
px.channel(x, y, 0), px.channel(x, y, 1), px.channel(x, y, 2), px.channel(x, y, 3)
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
static String describe(int[] rgba) {
|
|
686
|
-
return "rgba(" + rgba[0] + "," + rgba[1] + "," + rgba[2] + "," + rgba[3] + ")";
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
private static final int TOLERANCE = 6;
|
|
690
|
-
|
|
691
|
-
private static boolean near(int actual, int expected) {
|
|
692
|
-
return Math.abs(actual - expected) <= TOLERANCE;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
static boolean isRed(int[] c) {
|
|
696
|
-
return near(c[0], 255) && near(c[1], 0) && near(c[2], 0) && near(c[3], 255);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
static boolean isGreen(int[] c) {
|
|
700
|
-
return near(c[0], 0) && near(c[1], 255) && near(c[2], 0) && near(c[3], 255);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
static boolean isBlue(int[] c) {
|
|
704
|
-
return near(c[0], 0) && near(c[1], 0) && near(c[2], 255) && near(c[3], 255);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
static boolean isBlack(int[] c) {
|
|
708
|
-
return near(c[0], 0) && near(c[1], 0) && near(c[2], 0) && near(c[3], 255);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
static boolean isWhite(int[] c) {
|
|
712
|
-
return near(c[0], 255) && near(c[1], 255) && near(c[2], 255) && near(c[3], 255);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
static final class Diff {
|
|
716
|
-
final int mismatches;
|
|
717
|
-
final int maxDelta;
|
|
718
|
-
final int total;
|
|
719
|
-
final double mismatchPercent;
|
|
720
|
-
|
|
721
|
-
Diff(int mismatches, int maxDelta, int total) {
|
|
722
|
-
this.mismatches = mismatches;
|
|
723
|
-
this.maxDelta = maxDelta;
|
|
724
|
-
this.total = total;
|
|
725
|
-
this.mismatchPercent = total == 0 ? 0 : 100.0 * mismatches / total;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
@Override
|
|
729
|
-
public String toString() {
|
|
730
|
-
return "mismatches=" + mismatches + "/" + total
|
|
731
|
-
+ String.format(" (%.4f%%)", mismatchPercent) + " maxDelta=" + maxDelta;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Compares `tile` against the region of `reference` starting at (dx, dy).
|
|
737
|
-
* A per-channel delta of 2 or less is not counted as a mismatch — that is
|
|
738
|
-
* anti-aliasing noise on a stroke edge, which S1 also measured (maxDelta 1).
|
|
739
|
-
*/
|
|
740
|
-
static Diff compareRegion(RasterPixels tile, RasterPixels reference, int dx, int dy) {
|
|
741
|
-
int mismatches = 0;
|
|
742
|
-
int maxDelta = 0;
|
|
743
|
-
int total = tile.width * tile.height;
|
|
744
|
-
for (int y = 0; y < tile.height; y++) {
|
|
745
|
-
for (int x = 0; x < tile.width; x++) {
|
|
746
|
-
for (int c = 0; c < 4; c++) {
|
|
747
|
-
int d = Math.abs(tile.channel(x, y, c) - reference.channel(x + dx, y + dy, c));
|
|
748
|
-
if (d > maxDelta) {
|
|
749
|
-
maxDelta = d;
|
|
750
|
-
}
|
|
751
|
-
if (d > 2) {
|
|
752
|
-
mismatches++;
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
return new Diff(mismatches, maxDelta, total);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private static byte[] readAll(File file) throws IOException {
|
|
762
|
-
byte[] out = new byte[(int) file.length()];
|
|
763
|
-
try (FileInputStream in = new FileInputStream(file)) {
|
|
764
|
-
int read = 0;
|
|
765
|
-
while (read < out.length) {
|
|
766
|
-
int n = in.read(out, read, out.length - read);
|
|
767
|
-
if (n < 0) {
|
|
768
|
-
fail("Short read on " + file);
|
|
769
|
-
}
|
|
770
|
-
read += n;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
return out;
|
|
774
|
-
}
|
|
775
|
-
}
|