@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +68 -39
  4. package/README.md +213 -1316
  5. package/android/build.gradle +67 -54
  6. package/android/consumer-rules.pro +7 -0
  7. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
  8. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
  9. package/android/src/main/cpp/CMakeLists.txt +54 -0
  10. package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
  11. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
  12. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
  13. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
  14. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
  15. package/android/tools/compile-gate.sh +81 -101
  16. package/dist/controller.d.ts +14 -0
  17. package/dist/controller.js +4 -3
  18. package/dist/rasterizer/index.native.d.ts +8 -12
  19. package/dist/rasterizer/index.native.js +15 -21
  20. package/dist/rasterizer/native-bridge.d.ts +51 -75
  21. package/dist/rasterizer/native-bridge.js +49 -33
  22. package/dist/rasterizer/native.d.ts +45 -0
  23. package/dist/rasterizer/native.js +123 -0
  24. package/dist/react/usePdfDocument.d.ts +5 -5
  25. package/dist/react/usePdfLayer.d.ts +10 -1
  26. package/dist/react/usePdfLayer.js +10 -2
  27. package/dist/testing/scenes.d.ts +3 -3
  28. package/dist/testing/scenes.js +4 -4
  29. package/dist/types.d.ts +36 -42
  30. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  31. package/native/.clangd +5 -0
  32. package/native/CMakeLists.txt +89 -0
  33. package/native/core/include/pdfcanvas/document.h +90 -0
  34. package/native/core/include/pdfcanvas/error.h +47 -0
  35. package/native/core/include/pdfcanvas/jsi.h +18 -0
  36. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  37. package/native/core/include/pdfcanvas/library.h +28 -0
  38. package/native/core/include/pdfcanvas/pixels.h +92 -0
  39. package/native/core/include/pdfcanvas/service.h +87 -0
  40. package/native/core/include/pdfcanvas/slots.h +113 -0
  41. package/native/core/include/pdfcanvas/types.h +110 -0
  42. package/native/core/pdfcanvas-core.cmake +22 -0
  43. package/native/core/src/document.cpp +516 -0
  44. package/native/core/src/error.cpp +29 -0
  45. package/native/core/src/jsi.cpp +66 -0
  46. package/native/core/src/library.cpp +52 -0
  47. package/native/core/src/pixels.cpp +45 -0
  48. package/native/core/src/service.cpp +156 -0
  49. package/native/core/src/slots.cpp +139 -0
  50. package/native/tests/fixtures.cpp +451 -0
  51. package/native/tests/fixtures.h +96 -0
  52. package/native/tests/harness.h +122 -0
  53. package/native/tests/main.cpp +64 -0
  54. package/native/tests/pixels_util.h +126 -0
  55. package/native/tests/test_document.cpp +485 -0
  56. package/native/tests/test_service.cpp +130 -0
  57. package/native/tests/test_slots.cpp +250 -0
  58. package/package.json +27 -7
  59. package/react-native.config.js +5 -2
  60. package/scripts/fetch-pdfium.mjs +487 -0
  61. package/scripts/pdfium-manifest.json +46 -0
  62. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  63. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  64. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  65. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  66. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  67. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  68. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  69. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  72. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  73. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  74. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  75. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  76. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  77. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  78. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  79. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  80. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  81. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  82. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  83. package/dist/rasterizer/android.d.ts +0 -121
  84. package/dist/rasterizer/android.js +0 -126
  85. package/dist/rasterizer/ios.d.ts +0 -84
  86. package/dist/rasterizer/ios.js +0 -155
  87. package/ios/Package.swift +0 -58
  88. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  89. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  90. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  91. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  92. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  93. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  94. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  95. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  98. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  99. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  100. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  101. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  102. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  103. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  104. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  105. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  109. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  110. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  111. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  112. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  113. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  114. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  115. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  116. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  117. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  118. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  119. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  120. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  121. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
@@ -0,0 +1,487 @@
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
+ * Wraps a bare dylib as an iOS `PDFium.framework` (shallow bundle) so it can be
288
+ * embedded and code-signed like any other framework. Mirrors what
289
+ * espresso3389/pdfium-xcframework's build.sh does, minus the macOS/Catalyst
290
+ * slices this package does not ship.
291
+ */
292
+ function makeIosFramework(dylib, frameworkDir, headersDir, platform) {
293
+ resetDir(frameworkDir);
294
+ const binary = join(frameworkDir, 'PDFium');
295
+ cpSync(dylib, binary);
296
+ execFileSync('chmod', ['+w', binary]);
297
+ execFileSync(
298
+ 'install_name_tool',
299
+ ['-id', '@rpath/PDFium.framework/PDFium', binary],
300
+ {
301
+ stdio: 'inherit',
302
+ },
303
+ );
304
+ copyHeaders(headersDir, join(frameworkDir, 'Headers'));
305
+ const shortVersion = manifest.version.split('.').slice(0, 3).join('.');
306
+ writeFileSync(
307
+ join(frameworkDir, 'Info.plist'),
308
+ `<?xml version="1.0" encoding="UTF-8"?>
309
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
310
+ <plist version="1.0">
311
+ <dict>
312
+ <key>CFBundleExecutable</key>
313
+ <string>PDFium</string>
314
+ <key>CFBundleIdentifier</key>
315
+ <string>tools.reekon.pdfcanvas.PDFium</string>
316
+ <key>CFBundleInfoDictionaryVersion</key>
317
+ <string>6.0</string>
318
+ <key>CFBundleName</key>
319
+ <string>PDFium</string>
320
+ <key>CFBundlePackageType</key>
321
+ <string>FMWK</string>
322
+ <key>CFBundleShortVersionString</key>
323
+ <string>${shortVersion}</string>
324
+ <key>CFBundleVersion</key>
325
+ <string>${manifest.version}</string>
326
+ <key>CFBundleSupportedPlatforms</key>
327
+ <array>
328
+ <string>${platform}</string>
329
+ </array>
330
+ <key>MinimumOSVersion</key>
331
+ <string>13.0</string>
332
+ </dict>
333
+ </plist>
334
+ `,
335
+ );
336
+ }
337
+
338
+ async function ios() {
339
+ if (process.platform !== 'darwin') {
340
+ throw new Error(
341
+ 'The iOS target assembles an xcframework with lipo/install_name_tool/xcodebuild ' +
342
+ 'and must run on macOS (it is invoked by `pod install`).',
343
+ );
344
+ }
345
+ const out = join(outputRoot, 'ios');
346
+ const key = manifest.release;
347
+ if (isFresh(out, key)) {
348
+ console.log(`[pdfium] ios: ${manifest.release} already present`);
349
+ return;
350
+ }
351
+ resetDir(out);
352
+ const work = join(out, '.work');
353
+ mkdirSync(work, {recursive: true});
354
+
355
+ const slices = {};
356
+ for (const name of [
357
+ 'ios-device-arm64',
358
+ 'ios-simulator-arm64',
359
+ 'ios-simulator-x64',
360
+ ]) {
361
+ const dir = join(work, name);
362
+ extract(await fetchArtifact(name), dir);
363
+ slices[name] = dir;
364
+ }
365
+ copyHeaders(
366
+ join(slices['ios-device-arm64'], 'include'),
367
+ join(out, 'include'),
368
+ );
369
+ cpSync(join(slices['ios-device-arm64'], 'LICENSE'), join(out, 'LICENSE'));
370
+ cpSync(join(slices['ios-device-arm64'], 'licenses'), join(out, 'licenses'), {
371
+ recursive: true,
372
+ });
373
+
374
+ // Both simulator architectures have to be ONE fat dylib: an xcframework holds
375
+ // one library per (platform, variant), so two simulator slices must be lipo'd.
376
+ const simulatorFat = join(work, 'libpdfium-simulator.dylib');
377
+ execFileSync(
378
+ 'lipo',
379
+ [
380
+ '-create',
381
+ join(slices['ios-simulator-arm64'], 'lib', 'libpdfium.dylib'),
382
+ join(slices['ios-simulator-x64'], 'lib', 'libpdfium.dylib'),
383
+ '-output',
384
+ simulatorFat,
385
+ ],
386
+ {stdio: 'inherit'},
387
+ );
388
+
389
+ const deviceFramework = join(work, 'device', 'PDFium.framework');
390
+ const simulatorFramework = join(work, 'simulator', 'PDFium.framework');
391
+ makeIosFramework(
392
+ join(slices['ios-device-arm64'], 'lib', 'libpdfium.dylib'),
393
+ deviceFramework,
394
+ join(out, 'include'),
395
+ 'iPhoneOS',
396
+ );
397
+ makeIosFramework(
398
+ simulatorFat,
399
+ simulatorFramework,
400
+ join(out, 'include'),
401
+ 'iPhoneSimulator',
402
+ );
403
+
404
+ const xcframework = join(out, 'PDFium.xcframework');
405
+ execFileSync(
406
+ 'xcodebuild',
407
+ [
408
+ '-create-xcframework',
409
+ '-framework',
410
+ deviceFramework,
411
+ '-framework',
412
+ simulatorFramework,
413
+ '-output',
414
+ xcframework,
415
+ ],
416
+ {stdio: 'inherit'},
417
+ );
418
+ rmSync(work, {recursive: true, force: true});
419
+ writeStamp(out, key);
420
+ console.log(`[pdfium] ios: ready at ${xcframework}`);
421
+ }
422
+
423
+ /* ------------------------------------------------------------------ *
424
+ * host
425
+ * ------------------------------------------------------------------ */
426
+
427
+ function defaultHostTarget() {
428
+ const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
429
+ switch (process.platform) {
430
+ case 'win32':
431
+ return 'win-x64';
432
+ case 'darwin':
433
+ return `mac-${arch}`;
434
+ case 'linux':
435
+ return 'linux-x64';
436
+ default:
437
+ throw new Error(
438
+ `No PDFium host build for ${process.platform}/${process.arch}.`,
439
+ );
440
+ }
441
+ }
442
+
443
+ async function host() {
444
+ const target = flags.get('target') ?? defaultHostTarget();
445
+ const out = join(outputRoot, 'host');
446
+ const key = `${manifest.release} ${target}`;
447
+ if (isFresh(out, key)) {
448
+ console.log(`[pdfium] host: ${manifest.release} ${target} already present`);
449
+ return;
450
+ }
451
+ resetDir(out);
452
+ extract(await fetchArtifact(target), out);
453
+ // The release's headers dir carries `fpdfview.h.orig`; drop it so an include
454
+ // path sweep never picks up the wrong one.
455
+ const orig = join(out, 'include', 'fpdfview.h.orig');
456
+ if (existsSync(orig)) rmSync(orig);
457
+ writeStamp(out, key);
458
+ console.log(`[pdfium] host: ready at ${out}`);
459
+ }
460
+
461
+ /* ------------------------------------------------------------------ *
462
+ * main
463
+ * ------------------------------------------------------------------ */
464
+
465
+ try {
466
+ switch (command) {
467
+ case 'android':
468
+ await android();
469
+ break;
470
+ case 'ios':
471
+ await ios();
472
+ break;
473
+ case 'host':
474
+ await host();
475
+ break;
476
+ case 'print-version':
477
+ process.stdout.write(`${manifest.version}\n`);
478
+ break;
479
+ default:
480
+ usage(command === undefined ? 1 : 2);
481
+ }
482
+ } catch (error) {
483
+ console.error(
484
+ `[pdfium] ${error instanceof Error ? error.message : String(error)}`,
485
+ );
486
+ process.exit(1);
487
+ }
@@ -0,0 +1,46 @@
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
+ "//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.",
7
+ "artifacts": {
8
+ "android-arm": {
9
+ "sha256": "7fba2fd334c9591e659c69263ff6c43b986f3db2200f6c156c848bb625f5364a",
10
+ "abi": "armeabi-v7a"
11
+ },
12
+ "android-arm64": {
13
+ "sha256": "37686e64fa005484d619550a78805500de4c5a6f4df7aa06d8576145bfbee98f",
14
+ "abi": "arm64-v8a"
15
+ },
16
+ "android-x64": {
17
+ "sha256": "e7e3072b8ad6f36ef58db999c2d91a22fa165a904d69385f13db4537c233d736",
18
+ "abi": "x86_64"
19
+ },
20
+ "android-x86": {
21
+ "sha256": "5a7bf631adac4d01c24e234172b8dc98d9e8e611c3d9b52135498d1b8c61e052",
22
+ "abi": "x86"
23
+ },
24
+ "ios-device-arm64": {
25
+ "sha256": "52be735d07b6c498ed8ce68b7f0cb5a209bbe61593c7a86c556756b80abbb938"
26
+ },
27
+ "ios-simulator-arm64": {
28
+ "sha256": "544537fce5257146e0df501c632d490d1aa95768737770f1912c8e23b1dd238d"
29
+ },
30
+ "ios-simulator-x64": {
31
+ "sha256": "8d4e33e415e29894aa98481bc9a9162a42039a1672796db14c9fed9cd545c881"
32
+ },
33
+ "mac-arm64": {
34
+ "sha256": "61424884d4a7f153b808deba6437848e4400834ce30aaf95d3050da44df8f420"
35
+ },
36
+ "mac-x64": {
37
+ "sha256": "a93d44238e05de20028446561b951d50988b849efbbe56fe40c0d376c05b45e8"
38
+ },
39
+ "win-x64": {
40
+ "sha256": "78a17d9a5f14467631c26a3ac8741b27a0471ecc05bd6a119b523598160a0537"
41
+ },
42
+ "linux-x64": {
43
+ "sha256": "eb142f416aed3a72fc5a02dbd5884868a16cb99dc0cf53e6bdd64afbf67b05f4"
44
+ }
45
+ }
46
+ }