@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
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fetches the pinned PDFium binaries this package renders with.
|
|
4
|
+
*
|
|
5
|
+
* ONE SCRIPT, THREE CONSUMERS, ONE MANIFEST.
|
|
6
|
+
*
|
|
7
|
+
* node scripts/fetch-pdfium.mjs android [--abis arm64-v8a,armeabi-v7a,x86,x86_64]
|
|
8
|
+
* -> native/.pdfium/android/include/*.h
|
|
9
|
+
* native/.pdfium/android/jniLibs/<abi>/libpdfium.so
|
|
10
|
+
* Called by android/build.gradle before CMake configures. The ABI list is
|
|
11
|
+
* whatever the app asked for (`reactNativeArchitectures`), so the AAR never
|
|
12
|
+
* carries an ABI the app does not.
|
|
13
|
+
*
|
|
14
|
+
* node scripts/fetch-pdfium.mjs ios
|
|
15
|
+
* -> native/.pdfium/ios/include/*.h
|
|
16
|
+
* native/.pdfium/ios/PDFium.xcframework (device arm64 + simulator arm64/x64)
|
|
17
|
+
* Called from PdfCanvas.podspec while CocoaPods evaluates it. Needs macOS:
|
|
18
|
+
* the xcframework is assembled with `lipo`, `install_name_tool` and
|
|
19
|
+
* `xcodebuild -create-xcframework`, which exist wherever `pod install` runs.
|
|
20
|
+
*
|
|
21
|
+
* node scripts/fetch-pdfium.mjs host [--target win-x64|mac-arm64|mac-x64|linux-x64]
|
|
22
|
+
* -> native/.pdfium/host/{include,lib,bin}
|
|
23
|
+
* For `native/tests`, the laptop proof of the C++ core. Defaults to the
|
|
24
|
+
* machine it runs on.
|
|
25
|
+
*
|
|
26
|
+
* EVERY DOWNLOAD IS VERIFIED against the SHA-256 in `pdfium-manifest.json`
|
|
27
|
+
* before a single byte of it is extracted, and a mismatch deletes the file and
|
|
28
|
+
* fails the build. The digests come from the release's own SLSA attestation
|
|
29
|
+
* (`pdfium-attestation.json`), not from a first download — a manifest that
|
|
30
|
+
* records whatever the network happened to return is a manifest that pins
|
|
31
|
+
* nothing.
|
|
32
|
+
*
|
|
33
|
+
* IDEMPOTENT AND CHEAP WHEN DONE. A `.stamp` file records the release tag each
|
|
34
|
+
* output was produced from; a run that finds a matching stamp does nothing, so
|
|
35
|
+
* calling this from every Gradle configure and every `pod install` costs a stat.
|
|
36
|
+
*
|
|
37
|
+
* OFFLINE / AIR-GAPPED BUILDS. `PDFCANVAS_PDFIUM_MIRROR` replaces the GitHub
|
|
38
|
+
* base URL (same file names, e.g. an internal artifact store), and
|
|
39
|
+
* `PDFCANVAS_PDFIUM_CACHE` names a directory of already-downloaded `.tgz` files
|
|
40
|
+
* that is consulted before the network. Both still verify the digest.
|
|
41
|
+
*
|
|
42
|
+
* NO DEPENDENCIES, by the same rule as the rest of `scripts/`: this runs from a
|
|
43
|
+
* consumer's install where none of this package's devDependencies exist.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import {createHash} from 'node:crypto';
|
|
47
|
+
import {
|
|
48
|
+
createWriteStream,
|
|
49
|
+
existsSync,
|
|
50
|
+
mkdirSync,
|
|
51
|
+
readFileSync,
|
|
52
|
+
renameSync,
|
|
53
|
+
rmSync,
|
|
54
|
+
statSync,
|
|
55
|
+
writeFileSync,
|
|
56
|
+
cpSync,
|
|
57
|
+
readdirSync,
|
|
58
|
+
} from 'node:fs';
|
|
59
|
+
import {tmpdir} from 'node:os';
|
|
60
|
+
import {dirname, join, resolve} from 'node:path';
|
|
61
|
+
import {execFileSync} from 'node:child_process';
|
|
62
|
+
import {fileURLToPath} from 'node:url';
|
|
63
|
+
import {pipeline} from 'node:stream/promises';
|
|
64
|
+
import {Readable} from 'node:stream';
|
|
65
|
+
|
|
66
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
const root = resolve(here, '..');
|
|
68
|
+
const manifestPath = join(here, 'pdfium-manifest.json');
|
|
69
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
70
|
+
|
|
71
|
+
/** Where every output lands. Gitignored; the stamp files make it self-healing. */
|
|
72
|
+
const outputRoot =
|
|
73
|
+
process.env.PDFCANVAS_PDFIUM_OUT ?? join(root, 'native', '.pdfium');
|
|
74
|
+
|
|
75
|
+
const baseUrl = (
|
|
76
|
+
process.env.PDFCANVAS_PDFIUM_MIRROR ?? manifest.baseUrl
|
|
77
|
+
).replace(/\/+$/, '');
|
|
78
|
+
|
|
79
|
+
/* ------------------------------------------------------------------ *
|
|
80
|
+
* Arguments
|
|
81
|
+
* ------------------------------------------------------------------ */
|
|
82
|
+
|
|
83
|
+
const [, , command, ...rest] = process.argv;
|
|
84
|
+
const flags = new Map();
|
|
85
|
+
for (let i = 0; i < rest.length; i++) {
|
|
86
|
+
const arg = rest[i];
|
|
87
|
+
if (arg.startsWith('--')) {
|
|
88
|
+
const key = arg.slice(2);
|
|
89
|
+
const value =
|
|
90
|
+
rest[i + 1] !== undefined && !rest[i + 1].startsWith('--')
|
|
91
|
+
? rest[++i]
|
|
92
|
+
: 'true';
|
|
93
|
+
flags.set(key, value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function usage(code) {
|
|
98
|
+
console.error(
|
|
99
|
+
'usage: fetch-pdfium.mjs android [--abis a,b] | ios | host [--target t] | print-version',
|
|
100
|
+
);
|
|
101
|
+
process.exit(code);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ------------------------------------------------------------------ *
|
|
105
|
+
* Download + verify
|
|
106
|
+
* ------------------------------------------------------------------ */
|
|
107
|
+
|
|
108
|
+
function sha256File(path) {
|
|
109
|
+
const hash = createHash('sha256');
|
|
110
|
+
hash.update(readFileSync(path));
|
|
111
|
+
return hash.digest('hex');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function artifactUrl(name) {
|
|
115
|
+
return `${baseUrl}/${manifest.release}/pdfium-${name}.tgz`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the path of a verified `.tgz` for `name`, downloading if needed.
|
|
120
|
+
*
|
|
121
|
+
* Downloads go to a temp file and are renamed into place only after the digest
|
|
122
|
+
* matches, so a half-written or tampered file can never be mistaken for a good
|
|
123
|
+
* one on the next run.
|
|
124
|
+
*/
|
|
125
|
+
async function fetchArtifact(name) {
|
|
126
|
+
const entry = manifest.artifacts[name];
|
|
127
|
+
if (!entry) {
|
|
128
|
+
throw new Error(`pdfium-manifest.json has no artifact named "${name}".`);
|
|
129
|
+
}
|
|
130
|
+
const cacheDir =
|
|
131
|
+
process.env.PDFCANVAS_PDFIUM_CACHE ?? join(outputRoot, 'downloads');
|
|
132
|
+
mkdirSync(cacheDir, {recursive: true});
|
|
133
|
+
const fileName = `pdfium-${name}-${manifest.release.replace('/', '-')}.tgz`;
|
|
134
|
+
const cached = join(cacheDir, fileName);
|
|
135
|
+
|
|
136
|
+
if (existsSync(cached)) {
|
|
137
|
+
const digest = sha256File(cached);
|
|
138
|
+
if (digest === entry.sha256) return cached;
|
|
139
|
+
console.warn(
|
|
140
|
+
`[pdfium] cached ${fileName} has digest ${digest}; re-downloading`,
|
|
141
|
+
);
|
|
142
|
+
rmSync(cached);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const url = artifactUrl(name);
|
|
146
|
+
console.log(`[pdfium] downloading ${url}`);
|
|
147
|
+
const response = await fetch(url, {redirect: 'follow'});
|
|
148
|
+
if (!response.ok || response.body === null) {
|
|
149
|
+
throw new Error(`Could not download ${url}: HTTP ${response.status}`);
|
|
150
|
+
}
|
|
151
|
+
const partial = `${cached}.part`;
|
|
152
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(partial));
|
|
153
|
+
|
|
154
|
+
const digest = sha256File(partial);
|
|
155
|
+
if (digest !== entry.sha256) {
|
|
156
|
+
rmSync(partial);
|
|
157
|
+
throw new Error(
|
|
158
|
+
`SHA-256 mismatch for ${url}:\n expected ${entry.sha256}\n got ${digest}\n` +
|
|
159
|
+
'Refusing to use it. If the release was re-uploaded, update ' +
|
|
160
|
+
'scripts/pdfium-manifest.json from the release attestation.',
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
renameSync(partial, cached);
|
|
164
|
+
return cached;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The tar to extract with.
|
|
169
|
+
*
|
|
170
|
+
* On Windows this is ALWAYS the system bsdtar, named by absolute path. A
|
|
171
|
+
* developer shell here usually has MSYS (Git Bash) tar first on PATH, and GNU
|
|
172
|
+
* tar reads `C:\...` as `host:path` and tries to connect to a machine called
|
|
173
|
+
* `C` — the failure reads "Cannot connect to C: resolve failed", which is a
|
|
174
|
+
* long way from "wrong tar". Windows 10 1803+ ships bsdtar at System32.
|
|
175
|
+
*/
|
|
176
|
+
function tarBinary() {
|
|
177
|
+
if (process.platform === 'win32') {
|
|
178
|
+
const system = join(
|
|
179
|
+
process.env.SystemRoot ?? 'C:\\Windows',
|
|
180
|
+
'System32',
|
|
181
|
+
'tar.exe',
|
|
182
|
+
);
|
|
183
|
+
if (existsSync(system)) return system;
|
|
184
|
+
}
|
|
185
|
+
return 'tar';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function extract(tgz, into) {
|
|
189
|
+
mkdirSync(into, {recursive: true});
|
|
190
|
+
// `tar` exists on every platform this runs on: macOS and Linux natively, and
|
|
191
|
+
// Windows 10+ ships bsdtar. Node has no built-in tar and this script may not
|
|
192
|
+
// take a dependency.
|
|
193
|
+
execFileSync(tarBinary(), ['-xzf', tgz, '-C', into], {stdio: 'inherit'});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ------------------------------------------------------------------ *
|
|
197
|
+
* Stamps
|
|
198
|
+
* ------------------------------------------------------------------ */
|
|
199
|
+
|
|
200
|
+
function stampPath(dir) {
|
|
201
|
+
return join(dir, '.stamp');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isFresh(dir, key) {
|
|
205
|
+
const stamp = stampPath(dir);
|
|
206
|
+
return existsSync(stamp) && readFileSync(stamp, 'utf8').trim() === key;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function writeStamp(dir, key) {
|
|
210
|
+
writeFileSync(stampPath(dir), `${key}\n`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resetDir(dir) {
|
|
214
|
+
rmSync(dir, {recursive: true, force: true});
|
|
215
|
+
mkdirSync(dir, {recursive: true});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Copies PDFium's public headers, dropping the `.orig` file the release carries. */
|
|
219
|
+
function copyHeaders(from, to) {
|
|
220
|
+
mkdirSync(to, {recursive: true});
|
|
221
|
+
for (const name of readdirSync(from)) {
|
|
222
|
+
if (name.endsWith('.orig')) continue;
|
|
223
|
+
cpSync(join(from, name), join(to, name), {recursive: true});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ------------------------------------------------------------------ *
|
|
228
|
+
* android
|
|
229
|
+
* ------------------------------------------------------------------ */
|
|
230
|
+
|
|
231
|
+
const ANDROID_ARTIFACT_BY_ABI = Object.fromEntries(
|
|
232
|
+
Object.entries(manifest.artifacts)
|
|
233
|
+
.filter(([, value]) => typeof value.abi === 'string')
|
|
234
|
+
.map(([name, value]) => [value.abi, name]),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
async function android() {
|
|
238
|
+
const abis = (
|
|
239
|
+
flags.get('abis') ?? Object.keys(ANDROID_ARTIFACT_BY_ABI).join(',')
|
|
240
|
+
)
|
|
241
|
+
.split(',')
|
|
242
|
+
.map(s => s.trim())
|
|
243
|
+
.filter(Boolean);
|
|
244
|
+
for (const abi of abis) {
|
|
245
|
+
if (!ANDROID_ARTIFACT_BY_ABI[abi]) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`No PDFium build for ABI "${abi}". Known: ${Object.keys(ANDROID_ARTIFACT_BY_ABI).join(', ')}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const out = join(outputRoot, 'android');
|
|
252
|
+
const key = `${manifest.release} ${[...abis].sort().join(',')}`;
|
|
253
|
+
if (isFresh(out, key)) {
|
|
254
|
+
console.log(
|
|
255
|
+
`[pdfium] android: ${manifest.release} for ${abis.join(', ')} already present`,
|
|
256
|
+
);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
resetDir(out);
|
|
260
|
+
let headersCopied = false;
|
|
261
|
+
for (const abi of abis) {
|
|
262
|
+
const tgz = await fetchArtifact(ANDROID_ARTIFACT_BY_ABI[abi]);
|
|
263
|
+
const scratch = join(out, `.extract-${abi}`);
|
|
264
|
+
extract(tgz, scratch);
|
|
265
|
+
const libDir = join(out, 'jniLibs', abi);
|
|
266
|
+
mkdirSync(libDir, {recursive: true});
|
|
267
|
+
cpSync(join(scratch, 'lib', 'libpdfium.so'), join(libDir, 'libpdfium.so'));
|
|
268
|
+
if (!headersCopied) {
|
|
269
|
+
copyHeaders(join(scratch, 'include'), join(out, 'include'));
|
|
270
|
+
cpSync(join(scratch, 'LICENSE'), join(out, 'LICENSE'));
|
|
271
|
+
cpSync(join(scratch, 'licenses'), join(out, 'licenses'), {
|
|
272
|
+
recursive: true,
|
|
273
|
+
});
|
|
274
|
+
headersCopied = true;
|
|
275
|
+
}
|
|
276
|
+
rmSync(scratch, {recursive: true, force: true});
|
|
277
|
+
}
|
|
278
|
+
writeStamp(out, key);
|
|
279
|
+
console.log(`[pdfium] android: ready at ${out}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* ------------------------------------------------------------------ *
|
|
283
|
+
* ios
|
|
284
|
+
* ------------------------------------------------------------------ */
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The minimum iOS version a Mach-O was built for, read from its LC_BUILD_VERSION
|
|
288
|
+
* (`minos`) via `vtool`. A fat binary prints one block per architecture; every
|
|
289
|
+
* slice must agree or the number is meaningless, so disagreement is an error.
|
|
290
|
+
*
|
|
291
|
+
* THIS IS THE FLOOR, NOT A GUESS. App Store processing compares the
|
|
292
|
+
* `MinimumOSVersion` a framework's Info.plist declares against what its binary
|
|
293
|
+
* was actually built for and rejects the upload (ITMS-90208) when the plist
|
|
294
|
+
* claims a lower floor. The first PDFium release of this package hardcoded
|
|
295
|
+
* 13.0 — a number inherited from the CoreGraphics era and from the upstream
|
|
296
|
+
* xcframework script this mirrors — while the dylib said 17.0, and the very
|
|
297
|
+
* first TestFlight upload was rejected. The plist is now written from the
|
|
298
|
+
* binary, and the manifest's declared value is checked against it, so the two
|
|
299
|
+
* cannot drift apart again.
|
|
300
|
+
*/
|
|
301
|
+
function machOMinimumOS(binary) {
|
|
302
|
+
const text = execFileSync('xcrun', ['vtool', '-show-build', binary], {
|
|
303
|
+
encoding: 'utf8',
|
|
304
|
+
});
|
|
305
|
+
const found = new Set();
|
|
306
|
+
for (const line of text.split('\n')) {
|
|
307
|
+
const m = /^\s*minos\s+(\d+(?:\.\d+)*)\s*$/.exec(line);
|
|
308
|
+
if (m) found.add(m[1]);
|
|
309
|
+
}
|
|
310
|
+
if (found.size === 0) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`${binary} has no LC_BUILD_VERSION minos; cannot determine its iOS floor.`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (found.size > 1) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`${binary} slices disagree about their iOS floor: ${[...found].join(', ')}.`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return [...found][0];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Wraps a bare dylib as an iOS `PDFium.framework` (shallow bundle) so it can be
|
|
325
|
+
* embedded and code-signed like any other framework. Mirrors what
|
|
326
|
+
* espresso3389/pdfium-xcframework's build.sh does, minus the macOS/Catalyst
|
|
327
|
+
* slices this package does not ship.
|
|
328
|
+
*/
|
|
329
|
+
function makeIosFramework(
|
|
330
|
+
dylib,
|
|
331
|
+
frameworkDir,
|
|
332
|
+
headersDir,
|
|
333
|
+
platform,
|
|
334
|
+
minimumOSVersion,
|
|
335
|
+
) {
|
|
336
|
+
resetDir(frameworkDir);
|
|
337
|
+
const binary = join(frameworkDir, 'PDFium');
|
|
338
|
+
cpSync(dylib, binary);
|
|
339
|
+
execFileSync('chmod', ['+w', binary]);
|
|
340
|
+
execFileSync(
|
|
341
|
+
'install_name_tool',
|
|
342
|
+
['-id', '@rpath/PDFium.framework/PDFium', binary],
|
|
343
|
+
{
|
|
344
|
+
stdio: 'inherit',
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
copyHeaders(headersDir, join(frameworkDir, 'Headers'));
|
|
348
|
+
const shortVersion = manifest.version.split('.').slice(0, 3).join('.');
|
|
349
|
+
writeFileSync(
|
|
350
|
+
join(frameworkDir, 'Info.plist'),
|
|
351
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
352
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
353
|
+
<plist version="1.0">
|
|
354
|
+
<dict>
|
|
355
|
+
<key>CFBundleExecutable</key>
|
|
356
|
+
<string>PDFium</string>
|
|
357
|
+
<key>CFBundleIdentifier</key>
|
|
358
|
+
<string>tools.reekon.pdfcanvas.PDFium</string>
|
|
359
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
360
|
+
<string>6.0</string>
|
|
361
|
+
<key>CFBundleName</key>
|
|
362
|
+
<string>PDFium</string>
|
|
363
|
+
<key>CFBundlePackageType</key>
|
|
364
|
+
<string>FMWK</string>
|
|
365
|
+
<key>CFBundleShortVersionString</key>
|
|
366
|
+
<string>${shortVersion}</string>
|
|
367
|
+
<key>CFBundleVersion</key>
|
|
368
|
+
<string>${manifest.version}</string>
|
|
369
|
+
<key>CFBundleSupportedPlatforms</key>
|
|
370
|
+
<array>
|
|
371
|
+
<string>${platform}</string>
|
|
372
|
+
</array>
|
|
373
|
+
<key>MinimumOSVersion</key>
|
|
374
|
+
<string>${minimumOSVersion}</string>
|
|
375
|
+
</dict>
|
|
376
|
+
</plist>
|
|
377
|
+
`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function ios() {
|
|
382
|
+
if (process.platform !== 'darwin') {
|
|
383
|
+
throw new Error(
|
|
384
|
+
'The iOS target assembles an xcframework with lipo/install_name_tool/xcodebuild ' +
|
|
385
|
+
'and must run on macOS (it is invoked by `pod install`).',
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const out = join(outputRoot, 'ios');
|
|
389
|
+
const declaredMinOS = manifest.iosMinimumOSVersion;
|
|
390
|
+
if (
|
|
391
|
+
typeof declaredMinOS !== 'string' ||
|
|
392
|
+
!/^\d+(\.\d+)*$/.test(declaredMinOS)
|
|
393
|
+
) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
'pdfium-manifest.json must declare iosMinimumOSVersion (e.g. "17.0").',
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
// The floor is part of the stamp so an output assembled by an older script
|
|
399
|
+
// (which wrote a hardcoded plist) is rebuilt rather than trusted.
|
|
400
|
+
const key = `${manifest.release} ios-min=${declaredMinOS}`;
|
|
401
|
+
if (isFresh(out, key)) {
|
|
402
|
+
console.log(`[pdfium] ios: ${manifest.release} already present`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
resetDir(out);
|
|
406
|
+
const work = join(out, '.work');
|
|
407
|
+
mkdirSync(work, {recursive: true});
|
|
408
|
+
|
|
409
|
+
const slices = {};
|
|
410
|
+
for (const name of [
|
|
411
|
+
'ios-device-arm64',
|
|
412
|
+
'ios-simulator-arm64',
|
|
413
|
+
'ios-simulator-x64',
|
|
414
|
+
]) {
|
|
415
|
+
const dir = join(work, name);
|
|
416
|
+
extract(await fetchArtifact(name), dir);
|
|
417
|
+
slices[name] = dir;
|
|
418
|
+
}
|
|
419
|
+
// Every slice must have been built for the floor the manifest declares. A
|
|
420
|
+
// mismatch means the release was bumped without re-reading its args.gn: fix the
|
|
421
|
+
// manifest (and tell every consumer, whose deployment target must be >= it).
|
|
422
|
+
for (const name of Object.keys(slices)) {
|
|
423
|
+
const actual = machOMinimumOS(join(slices[name], 'lib', 'libpdfium.dylib'));
|
|
424
|
+
if (actual !== declaredMinOS) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`${name} in ${manifest.release} was built for iOS ${actual}, but ` +
|
|
427
|
+
`pdfium-manifest.json declares iosMinimumOSVersion "${declaredMinOS}". ` +
|
|
428
|
+
'Update the manifest to the real floor; consumers must raise their ' +
|
|
429
|
+
'deployment target to match before they can ship this release.',
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
console.log(`[pdfium] ios: every slice is built for iOS ${declaredMinOS}`);
|
|
434
|
+
|
|
435
|
+
copyHeaders(
|
|
436
|
+
join(slices['ios-device-arm64'], 'include'),
|
|
437
|
+
join(out, 'include'),
|
|
438
|
+
);
|
|
439
|
+
cpSync(join(slices['ios-device-arm64'], 'LICENSE'), join(out, 'LICENSE'));
|
|
440
|
+
cpSync(join(slices['ios-device-arm64'], 'licenses'), join(out, 'licenses'), {
|
|
441
|
+
recursive: true,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Both simulator architectures have to be ONE fat dylib: an xcframework holds
|
|
445
|
+
// one library per (platform, variant), so two simulator slices must be lipo'd.
|
|
446
|
+
const simulatorFat = join(work, 'libpdfium-simulator.dylib');
|
|
447
|
+
execFileSync(
|
|
448
|
+
'lipo',
|
|
449
|
+
[
|
|
450
|
+
'-create',
|
|
451
|
+
join(slices['ios-simulator-arm64'], 'lib', 'libpdfium.dylib'),
|
|
452
|
+
join(slices['ios-simulator-x64'], 'lib', 'libpdfium.dylib'),
|
|
453
|
+
'-output',
|
|
454
|
+
simulatorFat,
|
|
455
|
+
],
|
|
456
|
+
{stdio: 'inherit'},
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const deviceFramework = join(work, 'device', 'PDFium.framework');
|
|
460
|
+
const simulatorFramework = join(work, 'simulator', 'PDFium.framework');
|
|
461
|
+
makeIosFramework(
|
|
462
|
+
join(slices['ios-device-arm64'], 'lib', 'libpdfium.dylib'),
|
|
463
|
+
deviceFramework,
|
|
464
|
+
join(out, 'include'),
|
|
465
|
+
'iPhoneOS',
|
|
466
|
+
declaredMinOS,
|
|
467
|
+
);
|
|
468
|
+
makeIosFramework(
|
|
469
|
+
simulatorFat,
|
|
470
|
+
simulatorFramework,
|
|
471
|
+
join(out, 'include'),
|
|
472
|
+
'iPhoneSimulator',
|
|
473
|
+
declaredMinOS,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const xcframework = join(out, 'PDFium.xcframework');
|
|
477
|
+
execFileSync(
|
|
478
|
+
'xcodebuild',
|
|
479
|
+
[
|
|
480
|
+
'-create-xcframework',
|
|
481
|
+
'-framework',
|
|
482
|
+
deviceFramework,
|
|
483
|
+
'-framework',
|
|
484
|
+
simulatorFramework,
|
|
485
|
+
'-output',
|
|
486
|
+
xcframework,
|
|
487
|
+
],
|
|
488
|
+
{stdio: 'inherit'},
|
|
489
|
+
);
|
|
490
|
+
rmSync(work, {recursive: true, force: true});
|
|
491
|
+
writeStamp(out, key);
|
|
492
|
+
console.log(`[pdfium] ios: ready at ${xcframework}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* ------------------------------------------------------------------ *
|
|
496
|
+
* host
|
|
497
|
+
* ------------------------------------------------------------------ */
|
|
498
|
+
|
|
499
|
+
function defaultHostTarget() {
|
|
500
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
501
|
+
switch (process.platform) {
|
|
502
|
+
case 'win32':
|
|
503
|
+
return 'win-x64';
|
|
504
|
+
case 'darwin':
|
|
505
|
+
return `mac-${arch}`;
|
|
506
|
+
case 'linux':
|
|
507
|
+
return 'linux-x64';
|
|
508
|
+
default:
|
|
509
|
+
throw new Error(
|
|
510
|
+
`No PDFium host build for ${process.platform}/${process.arch}.`,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function host() {
|
|
516
|
+
const target = flags.get('target') ?? defaultHostTarget();
|
|
517
|
+
const out = join(outputRoot, 'host');
|
|
518
|
+
const key = `${manifest.release} ${target}`;
|
|
519
|
+
if (isFresh(out, key)) {
|
|
520
|
+
console.log(`[pdfium] host: ${manifest.release} ${target} already present`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
resetDir(out);
|
|
524
|
+
extract(await fetchArtifact(target), out);
|
|
525
|
+
// The release's headers dir carries `fpdfview.h.orig`; drop it so an include
|
|
526
|
+
// path sweep never picks up the wrong one.
|
|
527
|
+
const orig = join(out, 'include', 'fpdfview.h.orig');
|
|
528
|
+
if (existsSync(orig)) rmSync(orig);
|
|
529
|
+
writeStamp(out, key);
|
|
530
|
+
console.log(`[pdfium] host: ready at ${out}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* ------------------------------------------------------------------ *
|
|
534
|
+
* main
|
|
535
|
+
* ------------------------------------------------------------------ */
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
switch (command) {
|
|
539
|
+
case 'android':
|
|
540
|
+
await android();
|
|
541
|
+
break;
|
|
542
|
+
case 'ios':
|
|
543
|
+
await ios();
|
|
544
|
+
break;
|
|
545
|
+
case 'host':
|
|
546
|
+
await host();
|
|
547
|
+
break;
|
|
548
|
+
case 'print-version':
|
|
549
|
+
process.stdout.write(`${manifest.version}\n`);
|
|
550
|
+
break;
|
|
551
|
+
default:
|
|
552
|
+
usage(command === undefined ? 1 : 2);
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
console.error(
|
|
556
|
+
`[pdfium] ${error instanceof Error ? error.message : String(error)}`,
|
|
557
|
+
);
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "THE ONE PLACE THE PDFIUM BUILD IS NAMED. `scripts/fetch-pdfium.mjs` downloads exactly these artifacts, verifies exactly these digests, and refuses anything else. Bumping PDFium is a one-file change: new tag, new digests (taken from the release's `pdfium-attestation.json` subjects, never from a download you have not verified), then `yarn pdfium:fetch host && yarn native:test` before the bump is trusted. Every platform — Android, iOS, the host test suite — is built from the SAME release, which is what makes a tile byte-identical across all of them rather than merely similar.",
|
|
3
|
+
"release": "chromium/8044",
|
|
4
|
+
"version": "144.0.8044.0",
|
|
5
|
+
"baseUrl": "https://github.com/bblanchon/pdfium-binaries/releases/download",
|
|
6
|
+
"//iosMinimumOSVersion": "THE iOS FLOOR THIS RELEASE'S DYLIB WAS BUILT FOR (`ios_deployment_target` in the tarball's args.gn, `minos` in the Mach-O LC_BUILD_VERSION). It is declared here, not discovered, so that bumping the release forces whoever bumps it to look: fetch-pdfium.mjs reads the real value out of every iOS slice with `vtool` and refuses to assemble the framework if any slice disagrees with this line. The same value is written into PDFium.framework/Info.plist as MinimumOSVersion — App Store processing rejects a framework whose plist claims a lower floor than its binary (ITMS-90208) — and is the floor PdfCanvas.podspec declares, so a host app whose deployment target is lower fails at `pod install` with a CocoaPods platform error instead of at upload. bblanchon pins 17.0 for non-V8 builds since 2026-08-14; before that the floor tracked Chromium's default and was never below 17.4 in 2025-2026. A host that must ship below this value needs a PDFium built with a lower ios_deployment_target, not a smaller number here.",
|
|
7
|
+
"iosMinimumOSVersion": "17.0",
|
|
8
|
+
"//artifacts": "Keyed by the bblanchon artifact name minus the `pdfium-` prefix and `.tgz` suffix. The android/ios/mac/win/linux entries are the plain (non-V8, non-XFA) builds: this package runs no JavaScript inside a PDF and XFA forms are out of scope, and the V8 builds are ~4x the size for nothing.",
|
|
9
|
+
"artifacts": {
|
|
10
|
+
"android-arm": {
|
|
11
|
+
"sha256": "7fba2fd334c9591e659c69263ff6c43b986f3db2200f6c156c848bb625f5364a",
|
|
12
|
+
"abi": "armeabi-v7a"
|
|
13
|
+
},
|
|
14
|
+
"android-arm64": {
|
|
15
|
+
"sha256": "37686e64fa005484d619550a78805500de4c5a6f4df7aa06d8576145bfbee98f",
|
|
16
|
+
"abi": "arm64-v8a"
|
|
17
|
+
},
|
|
18
|
+
"android-x64": {
|
|
19
|
+
"sha256": "e7e3072b8ad6f36ef58db999c2d91a22fa165a904d69385f13db4537c233d736",
|
|
20
|
+
"abi": "x86_64"
|
|
21
|
+
},
|
|
22
|
+
"android-x86": {
|
|
23
|
+
"sha256": "5a7bf631adac4d01c24e234172b8dc98d9e8e611c3d9b52135498d1b8c61e052",
|
|
24
|
+
"abi": "x86"
|
|
25
|
+
},
|
|
26
|
+
"ios-device-arm64": {
|
|
27
|
+
"sha256": "52be735d07b6c498ed8ce68b7f0cb5a209bbe61593c7a86c556756b80abbb938"
|
|
28
|
+
},
|
|
29
|
+
"ios-simulator-arm64": {
|
|
30
|
+
"sha256": "544537fce5257146e0df501c632d490d1aa95768737770f1912c8e23b1dd238d"
|
|
31
|
+
},
|
|
32
|
+
"ios-simulator-x64": {
|
|
33
|
+
"sha256": "8d4e33e415e29894aa98481bc9a9162a42039a1672796db14c9fed9cd545c881"
|
|
34
|
+
},
|
|
35
|
+
"mac-arm64": {
|
|
36
|
+
"sha256": "61424884d4a7f153b808deba6437848e4400834ce30aaf95d3050da44df8f420"
|
|
37
|
+
},
|
|
38
|
+
"mac-x64": {
|
|
39
|
+
"sha256": "a93d44238e05de20028446561b951d50988b849efbbe56fe40c0d376c05b45e8"
|
|
40
|
+
},
|
|
41
|
+
"win-x64": {
|
|
42
|
+
"sha256": "78a17d9a5f14467631c26a3ac8741b27a0471ecc05bd6a119b523598160a0537"
|
|
43
|
+
},
|
|
44
|
+
"linux-x64": {
|
|
45
|
+
"sha256": "eb142f416aed3a72fc5a02dbd5884868a16cb99dc0cf53e6bdd64afbf67b05f4"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|