@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.1
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 +77 -40
- package/README.md +218 -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/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/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 +27 -7
- package/react-native.config.js +5 -2
- package/scripts/fetch-pdfium.mjs +559 -0
- package/scripts/pdfium-manifest.json +48 -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,211 +1,40 @@
|
|
|
1
1
|
package tools.reekon.pdfcanvas;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
-
import android.graphics.Canvas;
|
|
5
|
-
import android.graphics.Color;
|
|
6
|
-
import android.graphics.Paint;
|
|
7
|
-
import android.graphics.pdf.PdfDocument;
|
|
8
4
|
|
|
9
5
|
import java.io.ByteArrayOutputStream;
|
|
10
6
|
import java.io.File;
|
|
11
7
|
import java.io.FileOutputStream;
|
|
12
8
|
import java.io.IOException;
|
|
13
9
|
import java.nio.charset.Charset;
|
|
14
|
-
import java.security.MessageDigest;
|
|
15
10
|
import java.util.ArrayList;
|
|
16
11
|
import java.util.List;
|
|
17
12
|
|
|
18
13
|
/**
|
|
19
|
-
* PDFs
|
|
20
|
-
* exactly rather than eyeballed.
|
|
14
|
+
* Hand-written PDFs for the JNI seam test.
|
|
21
15
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* rotated() — hand-written, because PdfDocument.PageInfo has no way to set
|
|
31
|
-
* /Rotate. Post-rotation geometry cannot be tested without it.
|
|
32
|
-
*
|
|
33
|
-
* encrypted() — hand-written, because nothing in the platform writes an
|
|
34
|
-
* encrypted PDF. Without it "password-protected produces a
|
|
35
|
-
* distinguishable error" is an assertion about code that has
|
|
36
|
-
* never met an encrypted file.
|
|
16
|
+
* DELIBERATELY SMALL. Rendering itself — geometry, registration, backgrounds,
|
|
17
|
+
* alpha, annotations, forms, rotation, encryption, cancellation — is proven on a
|
|
18
|
+
* laptop by {@code native/tests} against the same PDFium build, with a much
|
|
19
|
+
* richer fixture set. What is left to prove ON DEVICE is that the bytes cross
|
|
20
|
+
* JNI intact and the transport's slot store hands back what PDFium wrote, and
|
|
21
|
+
* two fixtures are enough for that: one whose channels are all saturated (a
|
|
22
|
+
* channel swap or a stride shear cannot hide) and one whose green exists only
|
|
23
|
+
* inside an annotation.
|
|
37
24
|
*/
|
|
38
25
|
final class TestPdfs {
|
|
39
26
|
|
|
40
|
-
static final int LETTER_W = 612;
|
|
41
|
-
static final int LETTER_H = 792;
|
|
42
|
-
/** ARCH-D, the sheet size this package exists for. */
|
|
43
|
-
static final int ARCH_D_W = 1728;
|
|
44
|
-
static final int ARCH_D_H = 2592;
|
|
45
|
-
|
|
46
27
|
private static final Charset LATIN1 = Charset.forName("ISO-8859-1");
|
|
47
28
|
|
|
48
29
|
private TestPdfs() {}
|
|
49
30
|
|
|
50
|
-
|
|
51
|
-
* PdfDocument-authored pages
|
|
52
|
-
* ---------------------------------------------------------------- */
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Detail at every frequency: an 8pt grid catches a scale error, 64pt-pitch
|
|
56
|
-
* colour blocks catch a translation error, and the two diagonals catch an x/y
|
|
57
|
-
* transposition.
|
|
58
|
-
*/
|
|
59
|
-
private static void drawPattern(Canvas canvas, int wPt, int hPt) {
|
|
60
|
-
canvas.drawColor(Color.WHITE);
|
|
61
|
-
|
|
62
|
-
Paint grid = new Paint();
|
|
63
|
-
grid.setColor(Color.BLACK);
|
|
64
|
-
grid.setStrokeWidth(0.5f);
|
|
65
|
-
grid.setAntiAlias(true);
|
|
66
|
-
for (int x = 0; x <= wPt; x += 8) canvas.drawLine(x, 0, x, hPt, grid);
|
|
67
|
-
for (int y = 0; y <= hPt; y += 8) canvas.drawLine(0, y, wPt, y, grid);
|
|
68
|
-
|
|
69
|
-
Paint block = new Paint();
|
|
70
|
-
block.setAntiAlias(true);
|
|
71
|
-
int i = 0;
|
|
72
|
-
for (int y = 0; y < hPt; y += 64) {
|
|
73
|
-
for (int x = 0; x < wPt; x += 64) {
|
|
74
|
-
block.setColor(Color.rgb((x * 7 + i) % 256, (y * 5 + i) % 256, (i * 13) % 256));
|
|
75
|
-
canvas.drawRect(x + 4, y + 4, x + 40, y + 40, block);
|
|
76
|
-
i++;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
Paint diag = new Paint();
|
|
81
|
-
diag.setColor(Color.RED);
|
|
82
|
-
diag.setStrokeWidth(2f);
|
|
83
|
-
diag.setAntiAlias(true);
|
|
84
|
-
canvas.drawLine(0, 0, wPt, hPt, diag);
|
|
85
|
-
canvas.drawLine(wPt, 0, 0, hPt, diag);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static File pattern(Context ctx, String name, int wPt, int hPt) throws IOException {
|
|
89
|
-
return pattern(ctx, name, wPt, hPt, 1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
static File pattern(Context ctx, String name, int wPt, int hPt, int pageCount)
|
|
93
|
-
throws IOException {
|
|
94
|
-
PdfDocument doc = new PdfDocument();
|
|
95
|
-
for (int p = 0; p < pageCount; p++) {
|
|
96
|
-
PdfDocument.PageInfo info =
|
|
97
|
-
new PdfDocument.PageInfo.Builder(wPt, hPt, p).create();
|
|
98
|
-
PdfDocument.Page page = doc.startPage(info);
|
|
99
|
-
drawPattern(page.getCanvas(), wPt, hPt);
|
|
100
|
-
doc.finishPage(page);
|
|
101
|
-
}
|
|
102
|
-
File out = new File(ctx.getCacheDir(), name);
|
|
103
|
-
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
104
|
-
doc.writeTo(fos);
|
|
105
|
-
}
|
|
106
|
-
doc.close();
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Pages of DIFFERENT sizes in one document, so a geometry test can catch an
|
|
112
|
-
* implementation that probes page 0 and reports its size for every page.
|
|
113
|
-
*/
|
|
114
|
-
static File mixedSizes(Context ctx, String name) throws IOException {
|
|
115
|
-
int[][] sizes = {{LETTER_W, LETTER_H}, {ARCH_D_W, ARCH_D_H}, {200, 900}};
|
|
116
|
-
PdfDocument doc = new PdfDocument();
|
|
117
|
-
for (int p = 0; p < sizes.length; p++) {
|
|
118
|
-
PdfDocument.PageInfo info =
|
|
119
|
-
new PdfDocument.PageInfo.Builder(sizes[p][0], sizes[p][1], p).create();
|
|
120
|
-
PdfDocument.Page page = doc.startPage(info);
|
|
121
|
-
page.getCanvas().drawColor(Color.WHITE);
|
|
122
|
-
doc.finishPage(page);
|
|
123
|
-
}
|
|
124
|
-
File out = new File(ctx.getCacheDir(), name);
|
|
125
|
-
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
126
|
-
doc.writeTo(fos);
|
|
127
|
-
}
|
|
128
|
-
doc.close();
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
static int[] mixedSizeExpectation(int page) {
|
|
133
|
-
int[][] sizes = {{LETTER_W, LETTER_H}, {ARCH_D_W, ARCH_D_H}, {200, 900}};
|
|
134
|
-
return sizes[page];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* One flat colour per page. Catches a backend that ignores
|
|
139
|
-
* {@code RasterRequest.page} and renders page 0 for every request — which a
|
|
140
|
-
* single-page fixture cannot catch at all, and a multi-page fixture with
|
|
141
|
-
* identical pages catches only by luck.
|
|
142
|
-
*/
|
|
143
|
-
static File coloredPages(Context ctx, String name, int[] colors, int wPt, int hPt)
|
|
144
|
-
throws IOException {
|
|
145
|
-
PdfDocument doc = new PdfDocument();
|
|
146
|
-
for (int p = 0; p < colors.length; p++) {
|
|
147
|
-
PdfDocument.PageInfo info = new PdfDocument.PageInfo.Builder(wPt, hPt, p).create();
|
|
148
|
-
PdfDocument.Page page = doc.startPage(info);
|
|
149
|
-
page.getCanvas().drawColor(colors[p]);
|
|
150
|
-
doc.finishPage(page);
|
|
151
|
-
}
|
|
152
|
-
File out = new File(ctx.getCacheDir(), name);
|
|
153
|
-
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
154
|
-
doc.writeTo(fos);
|
|
155
|
-
}
|
|
156
|
-
doc.close();
|
|
157
|
-
return out;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Four saturated quadrants — red, green, blue, black — which is what makes a
|
|
162
|
-
* channel-order test possible at all. A grey or white page reads the same in
|
|
163
|
-
* RGBA and BGRA, so the two orderings are indistinguishable on it; pure red
|
|
164
|
-
* differs in the first byte.
|
|
165
|
-
*
|
|
166
|
-
* Quadrants also make a STRIDE error visible: a raster read back with the
|
|
167
|
-
* wrong row stride walks diagonally across the quadrant boundaries, so the
|
|
168
|
-
* sampled colours come out wrong rather than merely shifted.
|
|
169
|
-
*/
|
|
170
|
-
static File quadrants(Context ctx, String name, int wPt, int hPt) throws IOException {
|
|
171
|
-
PdfDocument doc = new PdfDocument();
|
|
172
|
-
PdfDocument.PageInfo info = new PdfDocument.PageInfo.Builder(wPt, hPt, 0).create();
|
|
173
|
-
PdfDocument.Page page = doc.startPage(info);
|
|
174
|
-
Canvas canvas = page.getCanvas();
|
|
175
|
-
Paint paint = new Paint();
|
|
176
|
-
paint.setAntiAlias(false);
|
|
177
|
-
int halfW = wPt / 2;
|
|
178
|
-
int halfH = hPt / 2;
|
|
179
|
-
paint.setColor(Color.RED);
|
|
180
|
-
canvas.drawRect(0, 0, halfW, halfH, paint);
|
|
181
|
-
paint.setColor(Color.GREEN);
|
|
182
|
-
canvas.drawRect(halfW, 0, wPt, halfH, paint);
|
|
183
|
-
paint.setColor(Color.BLUE);
|
|
184
|
-
canvas.drawRect(0, halfH, halfW, hPt, paint);
|
|
185
|
-
paint.setColor(Color.BLACK);
|
|
186
|
-
canvas.drawRect(halfW, halfH, wPt, hPt, paint);
|
|
187
|
-
doc.finishPage(page);
|
|
188
|
-
|
|
189
|
-
File out = new File(ctx.getCacheDir(), name);
|
|
190
|
-
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
191
|
-
doc.writeTo(fos);
|
|
192
|
-
}
|
|
193
|
-
doc.close();
|
|
194
|
-
return out;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/* ---------------------------------------------------------------- *
|
|
198
|
-
* Hand-written PDFs
|
|
199
|
-
* ---------------------------------------------------------------- */
|
|
200
|
-
|
|
201
|
-
/** Assembles objects and writes a correct xref table, which PDFium prefers. */
|
|
31
|
+
/** Assembles objects and writes a correct xref table. */
|
|
202
32
|
private static final class Writer {
|
|
203
33
|
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
204
34
|
private final List<Integer> offsets = new ArrayList<>();
|
|
205
35
|
|
|
206
36
|
Writer() {
|
|
207
37
|
write("%PDF-1.4\n");
|
|
208
|
-
// A binary comment line marks the file as binary, per the spec's advice.
|
|
209
38
|
out.write(0x25);
|
|
210
39
|
out.write(0xE2);
|
|
211
40
|
out.write(0xE3);
|
|
@@ -219,16 +48,11 @@ final class TestPdfs {
|
|
|
219
48
|
out.write(b, 0, b.length);
|
|
220
49
|
}
|
|
221
50
|
|
|
222
|
-
|
|
223
|
-
|
|
51
|
+
int count() {
|
|
52
|
+
return offsets.size();
|
|
224
53
|
}
|
|
225
54
|
|
|
226
|
-
/** @return the object number, 1-based. */
|
|
227
55
|
int add(String body) {
|
|
228
|
-
return add(body.getBytes(LATIN1));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
int add(byte[] body) {
|
|
232
56
|
int number = offsets.size() + 1;
|
|
233
57
|
offsets.add(out.size());
|
|
234
58
|
write(number + " 0 obj\n");
|
|
@@ -237,26 +61,7 @@ final class TestPdfs {
|
|
|
237
61
|
return number;
|
|
238
62
|
}
|
|
239
63
|
|
|
240
|
-
|
|
241
|
-
* Writes at least {@code atLeast} bytes of PDF COMMENT lines.
|
|
242
|
-
*
|
|
243
|
-
* A `%` line is legal anywhere between objects and is never drawn, so this
|
|
244
|
-
* changes the file's LENGTH and nothing else — which is the only lever a
|
|
245
|
-
* preflight scan-window test needs. Call it after the last {@code add()} and
|
|
246
|
-
* before {@code finish()}, so the trailer is pushed past the head window
|
|
247
|
-
* while every recorded xref offset stays correct.
|
|
248
|
-
*/
|
|
249
|
-
void pad(int atLeast) {
|
|
250
|
-
StringBuilder sb = new StringBuilder(atLeast + 64);
|
|
251
|
-
int line = 0;
|
|
252
|
-
while (sb.length() < atLeast) {
|
|
253
|
-
sb.append("% padding line ").append(line++)
|
|
254
|
-
.append(" - legal PDF comment, never drawn\n");
|
|
255
|
-
}
|
|
256
|
-
write(sb.toString());
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
byte[] finish(int rootObject, String extraTrailerEntries) {
|
|
64
|
+
byte[] finish(int rootObject) {
|
|
260
65
|
int xref = out.size();
|
|
261
66
|
int size = offsets.size() + 1;
|
|
262
67
|
write("xref\n0 " + size + "\n");
|
|
@@ -264,271 +69,62 @@ final class TestPdfs {
|
|
|
264
69
|
for (int offset : offsets) {
|
|
265
70
|
write(String.format("%010d 00000 n \n", offset));
|
|
266
71
|
}
|
|
267
|
-
write("trailer\n<< /Size " + size + " /Root " + rootObject + " 0 R "
|
|
268
|
-
+
|
|
72
|
+
write("trailer\n<< /Size " + size + " /Root " + rootObject + " 0 R >>\nstartxref\n"
|
|
73
|
+
+ xref + "\n%%EOF\n");
|
|
269
74
|
return out.toByteArray();
|
|
270
75
|
}
|
|
271
76
|
}
|
|
272
77
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* top-left 112x100 points of the UNROTATED page.
|
|
276
|
-
*
|
|
277
|
-
* /Rotate is "degrees the page shall be rotated CLOCKWISE when displayed", so
|
|
278
|
-
* a correct renderer reports the page as 792x612 and puts that marker in the
|
|
279
|
-
* displayed TOP-RIGHT. Both halves matter: swapped dimensions alone would also
|
|
280
|
-
* be produced by an implementation that swapped width and height and drew the
|
|
281
|
-
* content unrotated.
|
|
282
|
-
*
|
|
283
|
-
* PDF user space is y-UP from the bottom-left, so the top-left 112x100 of the
|
|
284
|
-
* page is the rect (0, 692) to (112, 792).
|
|
285
|
-
*/
|
|
286
|
-
static File rotated90(Context ctx, String name) throws IOException {
|
|
287
|
-
String content =
|
|
288
|
-
"1 1 1 rg 0 0 " + LETTER_W + " " + LETTER_H + " re f\n"
|
|
289
|
-
+ "1 0 0 rg 0 692 112 100 re f\n"
|
|
290
|
-
// A blue marker at the unrotated BOTTOM-left, so an implementation
|
|
291
|
-
// that rotated the wrong way round is caught too: 180 degrees out
|
|
292
|
-
// would put red where blue is expected.
|
|
293
|
-
+ "0 0 1 rg 0 0 112 100 re f\n";
|
|
294
|
-
byte[] stream = content.getBytes(LATIN1);
|
|
295
|
-
|
|
296
|
-
Writer w = new Writer();
|
|
297
|
-
int contents = w.add("<< /Length " + stream.length + " >>\nstream\n"
|
|
298
|
-
+ content + "endstream");
|
|
299
|
-
int page = w.add("<< /Type /Page /Parent 3 0 R /MediaBox [0 0 " + LETTER_W + " "
|
|
300
|
-
+ LETTER_H + "] /Rotate 90 /Contents " + contents + " 0 R "
|
|
301
|
-
+ "/Resources << >> >>");
|
|
302
|
-
int pages = w.add("<< /Type /Pages /Kids [" + page + " 0 R] /Count 1 >>");
|
|
303
|
-
int catalog = w.add("<< /Type /Catalog /Pages " + pages + " 0 R >>");
|
|
304
|
-
// /Parent was written as "3 0 R" above, which is the number `pages` gets;
|
|
305
|
-
// assert rather than hope, because a silent mismatch makes PDFium rebuild
|
|
306
|
-
// the tree and the test would still pass for the wrong reason.
|
|
307
|
-
if (pages != 3) {
|
|
308
|
-
throw new IllegalStateException("Pages object is " + pages + ", not 3");
|
|
309
|
-
}
|
|
310
|
-
return writeFile(ctx, name, w.finish(catalog, ""));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* A perfectly ordinary PDF whose content stream contains the literal text
|
|
315
|
-
* {@code /Encrypt}.
|
|
316
|
-
*
|
|
317
|
-
* The decoy for {@link PdfPreflight}: a naive substring scan for the name
|
|
318
|
-
* would refuse this document with "needs a password", which is the most
|
|
319
|
-
* confusing thing a preflight can say. The name appears WITHOUT a following
|
|
320
|
-
* indirect reference, which is exactly the shape the real check requires — so
|
|
321
|
-
* this passes only if the check looks at the whole reference.
|
|
322
|
-
*/
|
|
323
|
-
static File withLiteralEncryptInContent(Context ctx, String name) throws IOException {
|
|
324
|
-
// A comment inside a content stream is legal and is never drawn, so the
|
|
325
|
-
// bytes are present in the file without changing what the page looks like.
|
|
326
|
-
String content = "% /Encrypt and /Encrypt again, as decoys\n1 0 0 rg 0 0 200 200 re f\n";
|
|
327
|
-
|
|
328
|
-
Writer w = new Writer();
|
|
329
|
-
int contents = w.add("<< /Length " + content.length() + " >>\nstream\n"
|
|
330
|
-
+ content + "endstream");
|
|
331
|
-
int page = w.add("<< /Type /Page /Parent 3 0 R /MediaBox [0 0 200 200] /Contents "
|
|
332
|
-
+ contents + " 0 R /Resources << >> >>");
|
|
333
|
-
int pages = w.add("<< /Type /Pages /Kids [" + page + " 0 R] /Count 1 >>");
|
|
334
|
-
int catalog = w.add("<< /Type /Catalog /Pages " + pages + " 0 R >>");
|
|
335
|
-
if (pages != 3) {
|
|
336
|
-
throw new IllegalStateException("Pages object is " + pages + ", not 3");
|
|
337
|
-
}
|
|
338
|
-
return writeFile(ctx, name, w.finish(catalog, ""));
|
|
78
|
+
private static String stream(String content) {
|
|
79
|
+
return "<< /Length " + content.getBytes(LATIN1).length + " >>\nstream\n" + content + "endstream";
|
|
339
80
|
}
|
|
340
81
|
|
|
341
82
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* Preflight cannot reject it — it claims to be a PDF and is not encrypted — so
|
|
345
|
-
* it is the shape that still reaches {@code new PdfRenderer(...)} and, on API
|
|
346
|
-
* 24, still poisons the process. Used only by the excluded poison-guard test.
|
|
83
|
+
* Four saturated quadrants in PDF user space (y-up): red bottom-left, green
|
|
84
|
+
* bottom-right, blue top-left, black top-right.
|
|
347
85
|
*/
|
|
348
|
-
static File
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
sb.append("garbage line ").append(i).append('\n');
|
|
352
|
-
}
|
|
353
|
-
return writeFile(ctx, name, sb.toString().getBytes(LATIN1));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/* ---------------------------------------------------------------- *
|
|
357
|
-
* Encryption (standard security handler, R=2, V=1, RC4-40)
|
|
358
|
-
* ---------------------------------------------------------------- */
|
|
359
|
-
|
|
360
|
-
private static final byte[] PAD = {
|
|
361
|
-
(byte) 0x28, (byte) 0xBF, (byte) 0x4E, (byte) 0x5E, (byte) 0x4E, (byte) 0x75,
|
|
362
|
-
(byte) 0x8A, (byte) 0x41, (byte) 0x64, (byte) 0x00, (byte) 0x4E, (byte) 0x56,
|
|
363
|
-
(byte) 0xFF, (byte) 0xFA, (byte) 0x01, (byte) 0x08, (byte) 0x2E, (byte) 0x2E,
|
|
364
|
-
(byte) 0x00, (byte) 0xB6, (byte) 0xD0, (byte) 0x68, (byte) 0x3E, (byte) 0x80,
|
|
365
|
-
(byte) 0x2F, (byte) 0x0C, (byte) 0xA9, (byte) 0xFE, (byte) 0x64, (byte) 0x53,
|
|
366
|
-
(byte) 0x69, (byte) 0x7A
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
static final String USER_PASSWORD = "secret";
|
|
370
|
-
|
|
371
|
-
private static byte[] pad(String password) {
|
|
372
|
-
byte[] pw = password.getBytes(LATIN1);
|
|
373
|
-
byte[] out = new byte[32];
|
|
374
|
-
int n = Math.min(pw.length, 32);
|
|
375
|
-
System.arraycopy(pw, 0, out, 0, n);
|
|
376
|
-
System.arraycopy(PAD, 0, out, n, 32 - n);
|
|
377
|
-
return out;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
private static byte[] md5(byte[]... parts) {
|
|
381
|
-
try {
|
|
382
|
-
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
383
|
-
for (byte[] part : parts) {
|
|
384
|
-
md.update(part);
|
|
385
|
-
}
|
|
386
|
-
return md.digest();
|
|
387
|
-
} catch (Exception e) {
|
|
388
|
-
throw new IllegalStateException("MD5 is mandatory on every JRE", e);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/** RC4, 25 lines, because javax.crypto's ARCFOUR is not guaranteed present. */
|
|
393
|
-
private static byte[] rc4(byte[] key, byte[] data) {
|
|
394
|
-
int[] s = new int[256];
|
|
395
|
-
for (int i = 0; i < 256; i++) {
|
|
396
|
-
s[i] = i;
|
|
397
|
-
}
|
|
398
|
-
for (int i = 0, j = 0; i < 256; i++) {
|
|
399
|
-
j = (j + s[i] + (key[i % key.length] & 0xff)) & 0xff;
|
|
400
|
-
int t = s[i];
|
|
401
|
-
s[i] = s[j];
|
|
402
|
-
s[j] = t;
|
|
403
|
-
}
|
|
404
|
-
byte[] out = new byte[data.length];
|
|
405
|
-
for (int n = 0, i = 0, j = 0; n < data.length; n++) {
|
|
406
|
-
i = (i + 1) & 0xff;
|
|
407
|
-
j = (j + s[i]) & 0xff;
|
|
408
|
-
int t = s[i];
|
|
409
|
-
s[i] = s[j];
|
|
410
|
-
s[j] = t;
|
|
411
|
-
out[n] = (byte) (data[n] ^ s[(s[i] + s[j]) & 0xff]);
|
|
412
|
-
}
|
|
413
|
-
return out;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private static byte[] leInt(int value) {
|
|
417
|
-
return new byte[] {
|
|
418
|
-
(byte) value, (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24)
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
private static String hex(byte[] b) {
|
|
423
|
-
StringBuilder sb = new StringBuilder(b.length * 2);
|
|
424
|
-
for (byte x : b) {
|
|
425
|
-
sb.append(String.format("%02X", x));
|
|
426
|
-
}
|
|
427
|
-
return sb.toString();
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* A PDF encrypted with the standard security handler and a NON-EMPTY user
|
|
432
|
-
* password, so opening it with no password must fail.
|
|
433
|
-
*
|
|
434
|
-
* The empty-user-password case would open cleanly on every viewer and prove
|
|
435
|
-
* nothing. The O and U values are computed properly (Algorithms 2, 3 and 4 of
|
|
436
|
-
* the spec) rather than filled with plausible-looking bytes, because a viewer
|
|
437
|
-
* that validates them and one that merely notices /Encrypt would then give
|
|
438
|
-
* different answers and the test would only be pinning the weaker of the two.
|
|
439
|
-
*/
|
|
440
|
-
static File encrypted(Context ctx, String name) throws IOException {
|
|
441
|
-
return encrypted(ctx, name, 0);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* @param paddingBytes comment bytes inserted between the last object and the
|
|
446
|
-
* xref table, so the file — and therefore its TRAILER, which is where
|
|
447
|
-
* {@code /Encrypt} lives — can be pushed to any chosen length.
|
|
448
|
-
* {@link PdfPreflight} reads a 32 KB head and a 256 KB tail, so a value
|
|
449
|
-
* around 60 KB lands the trailer in the band that only the tail scan
|
|
450
|
-
* reaches. Nothing else in the document changes.
|
|
451
|
-
*/
|
|
452
|
-
static File encrypted(Context ctx, String name, int paddingBytes) throws IOException {
|
|
453
|
-
byte[] id = new byte[16];
|
|
454
|
-
for (int i = 0; i < id.length; i++) {
|
|
455
|
-
id[i] = (byte) (i * 17 + 3);
|
|
456
|
-
}
|
|
457
|
-
int permissions = -1;
|
|
458
|
-
|
|
459
|
-
// Algorithm 3: the owner-password entry.
|
|
460
|
-
byte[] ownerKey = new byte[5];
|
|
461
|
-
System.arraycopy(md5(pad("owner")), 0, ownerKey, 0, 5);
|
|
462
|
-
byte[] o = rc4(ownerKey, pad(USER_PASSWORD));
|
|
463
|
-
|
|
464
|
-
// Algorithm 2: the file encryption key.
|
|
465
|
-
byte[] fileKey = new byte[5];
|
|
466
|
-
System.arraycopy(md5(pad(USER_PASSWORD), o, leInt(permissions), id), 0, fileKey, 0, 5);
|
|
467
|
-
|
|
468
|
-
// Algorithm 4 (R=2): the user-password entry.
|
|
469
|
-
byte[] u = rc4(fileKey, PAD);
|
|
470
|
-
|
|
86
|
+
static File quadrants(Context ctx, String name, int w, int h) throws IOException {
|
|
87
|
+
int hw = w / 2;
|
|
88
|
+
int hh = h / 2;
|
|
471
89
|
String content =
|
|
472
|
-
"1
|
|
473
|
-
+ "0
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
Writer
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
int
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
w.add("<< /Filter /Standard /V 1 /R 2 /Length 40 /P " + permissions
|
|
511
|
-
+ " /O <" + hex(o) + "> /U <" + hex(u) + "> >>");
|
|
512
|
-
if (pages != 3) {
|
|
513
|
-
throw new IllegalStateException("Pages object is " + pages + ", not 3");
|
|
514
|
-
}
|
|
515
|
-
if (paddingBytes > 0) {
|
|
516
|
-
w.pad(paddingBytes);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
String trailer = "/Encrypt " + encrypt + " 0 R /ID [<" + hex(id) + "> <" + hex(id) + ">]";
|
|
520
|
-
return writeFile(ctx, name, w.finish(catalog, trailer));
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/* ---------------------------------------------------------------- *
|
|
524
|
-
* Non-PDFs
|
|
525
|
-
* ---------------------------------------------------------------- */
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Deliberately has no %PDF header AND no recoverable objects. PDFium rebuilds
|
|
529
|
-
* a broken xref rather than giving up, so a file that merely has a bad trailer
|
|
530
|
-
* would open fine and the "corrupt" branch would never be reached.
|
|
531
|
-
*/
|
|
90
|
+
"1 0 0 rg 0 0 " + hw + " " + hh + " re f\n"
|
|
91
|
+
+ "0 1 0 rg " + hw + " 0 " + hw + " " + hh + " re f\n"
|
|
92
|
+
+ "0 0 1 rg 0 " + hh + " " + hw + " " + hh + " re f\n"
|
|
93
|
+
+ "0 0 0 rg " + hw + " " + hh + " " + hw + " " + hh + " re f\n";
|
|
94
|
+
Writer wr = new Writer();
|
|
95
|
+
int contents = wr.add(stream(content));
|
|
96
|
+
int pagesNumber = wr.count() + 2;
|
|
97
|
+
int page = wr.add("<< /Type /Page /Parent " + pagesNumber + " 0 R /MediaBox [0 0 " + w + " " + h
|
|
98
|
+
+ "] /Contents " + contents + " 0 R /Resources << >> >>");
|
|
99
|
+
int pages = wr.add("<< /Type /Pages /Kids [" + page + " 0 R] /Count 1 >>");
|
|
100
|
+
int catalog = wr.add("<< /Type /Catalog /Pages " + pages + " 0 R >>");
|
|
101
|
+
if (pages != pagesNumber) {
|
|
102
|
+
throw new IllegalStateException("Pages object is " + pages + ", not " + pagesNumber);
|
|
103
|
+
}
|
|
104
|
+
return writeFile(ctx, name, wr.finish(catalog));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A 200x200 white page whose only GREEN is a /Square annotation's appearance stream. */
|
|
108
|
+
static File annotated(Context ctx, String name) throws IOException {
|
|
109
|
+
Writer wr = new Writer();
|
|
110
|
+
int contents = wr.add(stream("1 1 1 rg 0 0 200 200 re f\n1 0 0 rg 10 10 30 30 re f\n"));
|
|
111
|
+
String appearance = "0 1 0 rg 0 0 100 100 re f\n";
|
|
112
|
+
int ap = wr.add("<< /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Length "
|
|
113
|
+
+ appearance.getBytes(LATIN1).length + " >>\nstream\n" + appearance + "endstream");
|
|
114
|
+
int annot = wr.add("<< /Type /Annot /Subtype /Square /Rect [50 50 150 150] /F 4 /AP << /N "
|
|
115
|
+
+ ap + " 0 R >> >>");
|
|
116
|
+
int pagesNumber = wr.count() + 2;
|
|
117
|
+
int page = wr.add("<< /Type /Page /Parent " + pagesNumber + " 0 R /MediaBox [0 0 200 200] /Annots ["
|
|
118
|
+
+ annot + " 0 R] /Contents " + contents + " 0 R /Resources << >> >>");
|
|
119
|
+
int pages = wr.add("<< /Type /Pages /Kids [" + page + " 0 R] /Count 1 >>");
|
|
120
|
+
int catalog = wr.add("<< /Type /Catalog /Pages " + pages + " 0 R >>");
|
|
121
|
+
if (pages != pagesNumber) {
|
|
122
|
+
throw new IllegalStateException("Pages object is " + pages + ", not " + pagesNumber);
|
|
123
|
+
}
|
|
124
|
+
return writeFile(ctx, name, wr.finish(catalog));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** No %PDF header and no recoverable objects. */
|
|
532
128
|
static File notAPdf(Context ctx, String name) throws IOException {
|
|
533
129
|
StringBuilder sb = new StringBuilder();
|
|
534
130
|
for (int i = 0; i < 200; i++) {
|
|
@@ -537,10 +133,6 @@ final class TestPdfs {
|
|
|
537
133
|
return writeFile(ctx, name, sb.toString().getBytes(LATIN1));
|
|
538
134
|
}
|
|
539
135
|
|
|
540
|
-
static File empty(Context ctx, String name) throws IOException {
|
|
541
|
-
return writeFile(ctx, name, new byte[0]);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
136
|
static File missing(Context ctx, String name) {
|
|
545
137
|
File f = new File(ctx.getCacheDir(), name);
|
|
546
138
|
//noinspection ResultOfMethodCallIgnored
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# libpdfcanvas.so: the C++ core from `native/core`, the JNI surface, and — in
|
|
2
|
+
# the autolinked build — the JSI transport install. Links the prebuilt PDFium
|
|
3
|
+
# for this ABI, which AGP then packages alongside it (an IMPORTED SHARED library
|
|
4
|
+
# a target links is a runtime file in AGP's model, which is also why
|
|
5
|
+
# build.gradle has to EXCLUDE the prefab-imported libjsi.so).
|
|
6
|
+
#
|
|
7
|
+
# PDFCANVAS_PDFIUM_ROOT is passed by build.gradle and is what
|
|
8
|
+
# `scripts/fetch-pdfium.mjs android` produced: `include/` plus
|
|
9
|
+
# `jniLibs/<abi>/libpdfium.so` for every ABI the app asked for.
|
|
10
|
+
#
|
|
11
|
+
# PDFCANVAS_WITH_JSI is passed ON only by the autolinked build, where
|
|
12
|
+
# ReactAndroid's prefab publishes jsi/jsi.h and libjsi.so. The standalone build
|
|
13
|
+
# (`cd android && ./gradlew connectedDebugAndroidTest`) passes nothing, has no
|
|
14
|
+
# React Native anywhere on its classpath, and still compiles the identical core
|
|
15
|
+
# — the slot storage and its JNI surface are React-free.
|
|
16
|
+
|
|
17
|
+
cmake_minimum_required(VERSION 3.13)
|
|
18
|
+
project(pdfcanvas CXX)
|
|
19
|
+
|
|
20
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
21
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
22
|
+
|
|
23
|
+
if(NOT PDFCANVAS_PDFIUM_ROOT)
|
|
24
|
+
message(FATAL_ERROR "PDFCANVAS_PDFIUM_ROOT was not passed; build.gradle's fetchPdfium task sets it.")
|
|
25
|
+
endif()
|
|
26
|
+
set(PDFIUM_SO "${PDFCANVAS_PDFIUM_ROOT}/jniLibs/${ANDROID_ABI}/libpdfium.so")
|
|
27
|
+
if(NOT EXISTS "${PDFIUM_SO}")
|
|
28
|
+
message(FATAL_ERROR
|
|
29
|
+
"No PDFium for ${ANDROID_ABI} at ${PDFIUM_SO}. Run "
|
|
30
|
+
"`node scripts/fetch-pdfium.mjs android --abis ${ANDROID_ABI}` from the package root.")
|
|
31
|
+
endif()
|
|
32
|
+
|
|
33
|
+
add_library(pdfium SHARED IMPORTED)
|
|
34
|
+
set_target_properties(pdfium PROPERTIES
|
|
35
|
+
IMPORTED_LOCATION "${PDFIUM_SO}"
|
|
36
|
+
INTERFACE_INCLUDE_DIRECTORIES "${PDFCANVAS_PDFIUM_ROOT}/include")
|
|
37
|
+
|
|
38
|
+
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../../native/core/pdfcanvas-core.cmake")
|
|
39
|
+
|
|
40
|
+
add_library(pdfcanvas SHARED
|
|
41
|
+
${PDFCANVAS_CORE_SOURCES}
|
|
42
|
+
pdfcanvas-jni.cpp)
|
|
43
|
+
target_include_directories(pdfcanvas PRIVATE "${PDFCANVAS_CORE_INCLUDE_DIR}")
|
|
44
|
+
# `log` for __android_log_print should anyone add diagnostics; `pdfium` for the
|
|
45
|
+
# engine. No jnigraphics any more: nothing here touches a Bitmap.
|
|
46
|
+
target_link_libraries(pdfcanvas pdfium log)
|
|
47
|
+
|
|
48
|
+
if(PDFCANVAS_WITH_JSI)
|
|
49
|
+
target_compile_definitions(pdfcanvas PRIVATE PDFCANVAS_WITH_JSI=1)
|
|
50
|
+
# CONFIG mode against the react-android AAR's prefab package. `jsi` is the
|
|
51
|
+
# only target needed: nothing here touches TurboModules, fbjni or folly.
|
|
52
|
+
find_package(ReactAndroid REQUIRED CONFIG)
|
|
53
|
+
target_link_libraries(pdfcanvas ReactAndroid::jsi)
|
|
54
|
+
endif()
|