@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.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +77 -40
  4. package/README.md +218 -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 +559 -0
  61. package/scripts/pdfium-manifest.json +48 -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,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
+ }