From c46414dbe4651a6a9a6ea26a4f005905f56585c8 Mon Sep 17 00:00:00 2001 From: Elie Gambache Date: Sun, 30 Aug 2026 07:19:00 +0300 Subject: [PATCH 1/2] fix: ship the iOS and web PDFium binaries inside the published artefacts Maven consumers got `ld: library 'pdfium' not found` on iOS and `Can't resolve './pdfium_glue.mjs'` on web: the published klibs carried the Kotlin bindings but none of the binaries the linker and the bundler need. iOS now links a static libpdfium.a that cinterop packs into the klib (`staticLibraries` in pdfium.def), so nothing has to be embedded, signed or pointed at with -L. bblanchon ships Apple platforms as a dylib only, and the one prebuilt static archive available (paulocoutinhox/pdfium-lib) is compiled against Chromium's bundled libc++ without shipping its objects, so it fails to link with undefined `std::__Cr::` symbols. NucleusFramework/pdfium-binaries therefore runs bblanchon's own harness with `build_type=static` (`pdf_is_complete_lib = true`, `use_custom_libcxx = false`) and publishes an `ios-static-` release per PDFium version, which installPdfiumIos downloads for the current pin. Web serves pdfium.wasm, the worker, the runtime and the glue from the archive root and evals the glue from Kotlin, so webpack no longer has to resolve `./pdfium_glue.mjs` and consumers need no copy task. PackagedBinariesTest locks both in, and the auto-release defers a version bump until the matching static iOS build is published. --- .github/workflows/pdfium-auto-release.yaml | 27 +- .gitignore | 10 +- README.md | 48 +++- example/build.gradle.kts | 8 +- gradle/libs.versions.toml | 3 + iosApp/Configuration/Config.xcconfig | 7 +- iosApp/iosApp.xcodeproj/project.pbxproj | 2 +- pdfium/build.gradle.kts | 264 ++++++++++++------ .../nucleusframework/pdfium/GlueEval.js.kt | 6 + .../pdfium/native/win32-arm64/pdfium.dll.lib | Bin 111384 -> 113624 bytes .../pdfium/native/win32-x86-64/pdfium.dll.lib | Bin 111384 -> 113624 bytes .../pdfium/PackagedBinariesTest.kt | 94 +++++++ pdfium/src/nativeInterop/cinterop/pdfium.def | 16 +- .../pdfium/GlueEval.wasmJs.kt | 3 + .../dev/nucleusframework/pdfium/PdfiumGlue.kt | 78 +++++- .../resources/{pdfium => }/pdfium_glue.mjs | 31 +- .../resources/{pdfium => }/pdfium_worker.mjs | 0 17 files changed, 447 insertions(+), 150 deletions(-) create mode 100644 pdfium/src/jsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.js.kt create mode 100644 pdfium/src/jvmTest/kotlin/dev/nucleusframework/pdfium/PackagedBinariesTest.kt create mode 100644 pdfium/src/wasmJsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.wasmJs.kt rename pdfium/src/webMain/resources/{pdfium => }/pdfium_glue.mjs (79%) rename pdfium/src/webMain/resources/{pdfium => }/pdfium_worker.mjs (100%) diff --git a/.github/workflows/pdfium-auto-release.yaml b/.github/workflows/pdfium-auto-release.yaml index 291145e..abb7593 100644 --- a/.github/workflows/pdfium-auto-release.yaml +++ b/.github/workflows/pdfium-auto-release.yaml @@ -16,7 +16,7 @@ jobs: detect: runs-on: ubuntu-latest outputs: - needs_bump: ${{ steps.compare.outputs.needs_bump }} + needs_bump: ${{ steps.compare.outputs.needs_bump == 'true' && steps.ios_static.outputs.ready == 'true' }} chromium_tag: ${{ steps.upstream.outputs.tag_name }} full_version: ${{ steps.upstream.outputs.full_version }} branch: ${{ steps.push.outputs.branch }} @@ -57,8 +57,29 @@ jobs: echo "needs_bump=true" >> "$GITHUB_OUTPUT" fi - - name: Check tag does not already exist + - name: Require the static iOS build to be published + # iOS links against a static libpdfium.a that bblanchon doesn't ship; + # NucleusFramework/pdfium-binaries builds it from the same tag and + # publishes it as `ios-static-` at 01:00 UTC. If it isn't there + # yet, skip this run rather than bumping to a version the iOS cinterop + # cannot resolve — the next cron run picks it up. if: steps.compare.outputs.needs_bump == 'true' + id: ios_static + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + build="${{ steps.upstream.outputs.chromium_branch }}" + if gh release view "ios-static-$build" --repo NucleusFramework/pdfium-binaries >/dev/null 2>&1; then + echo "ready=true" >> "$GITHUB_OUTPUT" + echo "Static iOS build ios-static-$build is available." + else + echo "ready=false" >> "$GITHUB_OUTPUT" + echo "::notice::ios-static-$build not published yet — deferring the bump to the next run." + fi + + - name: Check tag does not already exist + if: steps.compare.outputs.needs_bump == 'true' && steps.ios_static.outputs.ready == 'true' run: | set -euo pipefail tag="v${{ steps.upstream.outputs.full_version }}" @@ -68,7 +89,7 @@ jobs: fi - name: Push bump branch - if: steps.compare.outputs.needs_bump == 'true' + if: steps.compare.outputs.needs_bump == 'true' && steps.ios_static.outputs.ready == 'true' id: push run: | set -euo pipefail diff --git a/.gitignore b/.gitignore index 61e14ec..d1bdf8e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,12 +21,16 @@ node_modules/ *.dylib *.dll +# iOS PDFium static archives staged by :pdfium:installPdfiumIos from the +# paulocoutinhox/pdfium-lib release. Redownloaded on every build. +pdfium/src/nativeInterop/libs/ + # PDFium wasm artefacts staged by the :pdfium:installPdfiumWasm and # :pdfium:generatePdfiumWasmRuntime Gradle tasks (source is the bblanchon # pdfium-wasm tarball downloaded at build time). Regenerated on every build. -pdfium/src/webMain/resources/pdfium/pdfium.wasm -pdfium/src/webMain/resources/pdfium/pdfium.js -pdfium/src/webMain/resources/pdfium/pdfium_runtime.mjs +pdfium/src/webMain/resources/pdfium.wasm +pdfium/src/webMain/resources/pdfium.js +pdfium/src/webMain/resources/pdfium_runtime.mjs # Root-level test PDFs. /*.pdf diff --git a/README.md b/README.md index 2d09b1a..d42b940 100644 --- a/README.md +++ b/README.md @@ -75,15 +75,36 @@ compose.desktop { } ``` +### iOS packaging + +Nothing to do. A static `libpdfium.a` is packed into the published iOS cinterop +klib, so it is linked straight into your framework — no `linkerOpts`, no +`LIBRARY_SEARCH_PATHS`, no `ThirdParty/` drop, nothing to embed or codesign, and +no extra Xcode build phase. The stock Compose one is enough: + +``` +./gradlew :composeApp:embedAndSignAppleFrameworkForXcode +``` + +Both `isStatic = true` and `isStatic = false` frameworks work. + +The static archive is not something bblanchon publishes (they ship Apple +platforms as a `.dylib`, which a klib cannot carry). It is built from their own +harness with `build_type=static` by +[NucleusFramework/pdfium-binaries](https://github.com/NucleusFramework/pdfium-binaries), +a fork that tracks upstream releases daily and publishes an `ios-static-` +release per PDFium version; `:pdfium:installPdfiumIos` downloads the archive +matching the `pdfium-bblanchon` pin. + ### Web packaging (wasmJS / JS) -For the browser targets, the `pdfium.wasm` + worker assets are published as -classpath resources inside the library artifact and served from the module -root. If you bundle your app with the default Kotlin/JS webpack pipeline, no -extra configuration is needed — the `@JsModule("./pdfium_glue.mjs")` imports -resolve against your webpack output directory. Remember to serve the site over -HTTPS (or `localhost`): the Web Clipboard API used by text copy only works in -secure contexts. +For the browser targets, `pdfium.wasm`, `pdfium_worker.mjs`, +`pdfium_runtime.mjs` and `pdfium_glue.mjs` are published as classpath +resources at the artefact root and served from the bundle root. The glue is +eval'd from Kotlin (no webpack `./pdfium_glue.mjs` resolution), so a +consumer using the default Kotlin/JS webpack pipeline needs no extra copy +task. Remember to serve the site over HTTPS (or `localhost`): the Web +Clipboard API used by text copy only works in secure contexts. ## Getting started — a tour @@ -670,9 +691,12 @@ Key facts: - **Native binary delivery.** `pdfium/build.gradle.kts` registers a set of Gradle tasks that download the bblanchon archives, extract them, and stage - them as classpath resources (JVM) / jniLibs (Android) / static libs - (iOS cinterop). The JNI glue is rebuilt from `pdfium_jni.cpp` via - `build-linux.sh` / `build-macos.sh` / `build-windows.bat`. + them as classpath resources (JVM) / jniLibs (Android) / root-level wasm+JS + assets (web). iOS instead downloads the static archive from the + `pdfium-binaries` fork and lets cinterop pack it into the klib + (`staticLibraries` in `pdfium.def`). The JNI glue is rebuilt from + `pdfium_jni.cpp` via `build-linux.sh` / `build-macos.sh` / + `build-windows.bat`. - **Shared document buffer.** The JVM/Android path copies the PDF bytes into a native buffer once via `nAllocBuffer`, then hands that buffer address to @@ -739,7 +763,9 @@ drops `libpdfium.so` into `src/androidMain/jniLibs//`. ### iOS Open `iosApp/` in Xcode and run. The Gradle side has to run on a macOS host -for the cinterop + framework link to succeed. +for the framework link to succeed (the cinterop itself cross-compiles from any +host). The Xcode "Compile Kotlin Framework" phase is the stock +`embedAndSignAppleFrameworkForXcode` — PDFium is already inside the framework. ### Smoke test diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 69b9c22..cc7f9fd 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -127,10 +127,10 @@ dependencies { } // The :pdfium module ships pdfium_glue.mjs, pdfium_runtime.mjs (wrapped emscripten JS), -// and pdfium.wasm. We copy the trio flat into composeApp's incremental sync dir (next -// to composeApp.mjs) so `@JsModule("./pdfium_glue.mjs")` resolves against it — the same -// layout kotlin-wasm-examples/browser-c-interop uses for its own .mjs/.wasm pair. -val pdfiumWasmDir = project(":pdfium").layout.projectDirectory.dir("src/webMain/resources/pdfium") +// pdfium_worker.mjs and pdfium.wasm at the resource root. Copy them flat into +// composeApp's incremental sync dir (next to composeApp.mjs) and processedResources +// so the worker URL `pdfium_worker.mjs` and wasm fetch resolve at the bundle root. +val pdfiumWasmDir = project(":pdfium").layout.projectDirectory.dir("src/webMain/resources") val isProductionWeb = project.hasProperty("isProduction") || project.gradle.startParameter.taskNames.any { it.endsWith("Distribution") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 71860c4..c2d30ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,9 @@ junit = "4.13.2" kotlin = "2.3.20" kotlinx-coroutines = "1.10.2" material3 = "1.10.0-alpha05" +# Drives every platform. iOS additionally needs a static build, which +# NucleusFramework/pdfium-binaries publishes per chromium build as +# `ios-static-` (see :pdfium:installPdfiumIos). pdfium-bblanchon = "chromium/8021" download-plugin = "5.6.0" okio = "3.9.1" diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index 0a46cb3..57a9961 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -6,8 +6,5 @@ PRODUCT_BUNDLE_IDENTIFIER=dev.nucleusframework.pdf.pdf$(TEAM_ID) CURRENT_PROJECT_VERSION=1 MARKETING_VERSION=1.0 -// PDFium dylib is staged by Gradle (:pdfium:installPdfiumIos) and embedded at build time -// by :pdfium:embedPdfiumDylibForXcode. These search paths let Xcode's linker resolve -// the -lpdfium directive that Kotlin/Native bakes into ComposeApp.framework. -LIBRARY_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) $(SRCROOT)/../pdfium/src/nativeInterop/libs/ios-arm64 -LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) $(SRCROOT)/../pdfium/src/nativeInterop/libs/ios-simulator-arm64 \ No newline at end of file +// No PDFium linker setup: libpdfium.a is packed inside the cinterop klib and +// statically linked into ComposeApp.framework. diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 9e29e41..b2a104b 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -152,7 +152,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :example:embedAndSignAppleFrameworkForXcode :pdfium:embedPdfiumDylibForXcode\n"; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :example:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/pdfium/build.gradle.kts b/pdfium/build.gradle.kts index 26f9993..41ff11f 100644 --- a/pdfium/build.gradle.kts +++ b/pdfium/build.gradle.kts @@ -6,15 +6,17 @@ import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.gradle.process.CommandLineArgumentProvider import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -64,17 +66,23 @@ val androidTriplets: List> = listOf( "x86" to "pdfium-android-x86", ) -val iosTriplets: List> = listOf( - "ios-arm64" to "pdfium-ios-device-arm64", - "ios-simulator-arm64" to "pdfium-ios-simulator-arm64", -) +// iOS needs a *static* libpdfium so cinterop can pack it into the klib and Maven +// consumers link without any -L or embedded framework (issue #11). bblanchon ships +// Apple platforms as a dylib only, so NucleusFramework/pdfium-binaries (a fork of +// their build harness) runs the same steps with `build_type=static` and publishes +// the archives per chromium build. +val pdfiumIosBuild = pdfiumVersion.substringAfterLast('/') +val pdfiumIosArchive = "pdfium-ios-static-$pdfiumIosBuild" +val pdfiumIosUrl = "https://github.com/NucleusFramework/pdfium-binaries/releases/download/" + + "ios-static-$pdfiumIosBuild/$pdfiumIosArchive.tgz" + +val iosTriplets: List = listOf("ios-arm64", "ios-simulator-arm64") val wasmArchive = "pdfium-wasm" val allArchives: Set = (jvmTriplets.map { it.archive } + androidTriplets.map { it.second } + - iosTriplets.map { it.second } + listOf(wasmArchive)).toSet() val downloadTasks: Map> = allArchives.associateWith { archive -> @@ -96,6 +104,21 @@ val extractTasks: Map> = allArchives.associateWith { } } +val downloadPdfiumIos = tasks.register("downloadPdfiumIos") { + src(pdfiumIosUrl) + dest(pdfiumDownloadsDir.map { it.file("$pdfiumIosArchive.tgz") }) + overwrite(false) + onlyIfModified(true) + retries(2) +} + +// The archive unpacks to `/{include,lib/}`. +val extractPdfiumIos = tasks.register("extractPdfiumIos") { + dependsOn(downloadPdfiumIos) + from({ tarTree(resources.gzip(downloadPdfiumIos.get().dest)) }) + into(pdfiumExtractDir.map { it.dir("ios-static") }) +} + kotlin { jvmToolchain(17) @@ -275,84 +298,48 @@ abstract class InstallHeadersTask : DefaultTask() { } } -abstract class EmbedPdfiumDylibTask : DefaultTask() { - @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - abstract val stagedLibsRoot: DirectoryProperty - - @get:Input abstract val platformName: Property - @get:Input abstract val targetBuildDir: Property - @get:Input abstract val frameworksFolderPath: Property - @get:Input @get:Optional abstract val signIdentity: Property - @get:Input @get:Optional abstract val signingRequired: Property - - @get:javax.inject.Inject abstract val execOps: org.gradle.process.ExecOperations - - @TaskAction - fun run() { - val platform = platformName.get() - require(platform.isNotBlank()) { "PLATFORM_NAME not set — run this task from Xcode." } - val triplet = when { - platform.startsWith("iphonesimulator") -> "ios-simulator-arm64" - platform.startsWith("iphoneos") -> "ios-arm64" - else -> error("Unsupported PLATFORM_NAME: '$platform'") - } - val dylib = stagedLibsRoot.get().dir(triplet).file("libpdfium.dylib").asFile - require(dylib.exists()) { "libpdfium.dylib missing at ${dylib.absolutePath}" } - - val buildDir = targetBuildDir.get() - val frameworks = frameworksFolderPath.get() - require(buildDir.isNotBlank() && frameworks.isNotBlank()) { - "TARGET_BUILD_DIR / FRAMEWORKS_FOLDER_PATH must be set by Xcode." - } - val dest = File("$buildDir/$frameworks").apply { mkdirs() }.resolve(dylib.name) - dylib.copyTo(dest, overwrite = true) - - if (signingRequired.orNull != "NO") { - val identity = signIdentity.orNull?.takeIf { it.isNotBlank() } ?: "-" - execOps.exec { - commandLine("codesign", "--force", "--sign", identity, dest.absolutePath) - } - } - } -} - +/** + * Stages one static `libpdfium.a` per Konan target plus the matching public + * headers. cinterop then packs the archive into the klib, so the published + * library carries the iOS machine code and consumers need no linker + * configuration at all (issue #11). + */ abstract class InstallIosTask : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val sources: ConfigurableFileCollection - @get:Input abstract val triplets: ListProperty // "triplet|archive" - @get:Input abstract val archiveToDir: org.gradle.api.provider.MapProperty + @get:Input abstract val releaseRoot: Property + @get:Input abstract val triplets: ListProperty @get:OutputDirectory abstract val outputRoot: DirectoryProperty - - @get:javax.inject.Inject abstract val execOps: org.gradle.process.ExecOperations + @get:OutputDirectory abstract val headersDir: DirectoryProperty @TaskAction fun run() { + val release = File(releaseRoot.get()) val root = outputRoot.get().asFile - val mapping = archiveToDir.get() - triplets.get().forEach { pair -> - val (triplet, archive) = pair.split('|', limit = 2) - val libDirPath = mapping[archive] ?: return@forEach - val libDir = File("$libDirPath/lib") - val target = root.resolve(triplet).apply { mkdirs() } - libDir.listFiles()?.forEach { src -> - if (src.name.endsWith(".dylib")) { - val dst = target.resolve(src.name) - src.copyTo(dst, overwrite = true) - // bblanchon ships the dylib with install_name "./libpdfium.dylib". - // Rewrite to @rpath so consumer apps can embed it under Frameworks/. - execOps.exec { - commandLine("install_name_tool", "-id", "@rpath/${src.name}", dst.absolutePath) - } - } - } + triplets.get().forEach { triplet -> + val archive = release.resolve("lib/$triplet/libpdfium.a") + require(archive.exists()) { "pdfium: ${archive.absolutePath} missing" } + val target = root.resolve(triplet) + target.deleteRecursively() + target.mkdirs() + archive.copyTo(target.resolve("libpdfium.a"), overwrite = true) } + // Bind the cinterop to the headers the staged archives were built from, + // so no stub can reference a symbol the archive doesn't define. + val include = release.resolve("include") + require(include.isDirectory) { "pdfium: ${include.absolutePath} missing" } + include.copyRecursively(headersDir.get().asFile.apply { mkdirs() }, overwrite = true) } } val nativeJniResourceDir = layout.projectDirectory.dir("src/jvmMain/resources/pdfium/native") val androidJniLibsDir = layout.projectDirectory.dir("src/androidMain/jniLibs") val iosStaticLibsDir = layout.projectDirectory.dir("src/nativeInterop/libs") -val wasmResourceDir = layout.projectDirectory.dir("src/webMain/resources/pdfium") +val iosHeadersDir = layout.buildDirectory.dir("pdfium/ios-include") +// Flat resource root so webpack/dev-server resolve `./pdfium_glue.mjs` and +// `pdfium_worker.mjs` from the bundle root (issue #11). Nested `pdfium/` made +// the files invisible to `@JsModule("./pdfium_glue.mjs")`. +val wasmResourceDir = layout.projectDirectory.dir("src/webMain/resources") val stagedHeadersDir = layout.buildDirectory.dir("pdfium/include") fun extractedDir(archive: String) = pdfiumExtractDir.map { it.dir(archive) } @@ -454,30 +441,70 @@ val generatePdfiumWasmRuntime = tasks.register("generatePdfiumWasmRuntime") { } } -val installPdfiumIos = tasks.register("installPdfiumIos") { - group = "pdfium" - description = "Install iOS PDFium dynamic libs for cinterop." - iosTriplets.forEach { (_, archive) -> - sources.from(extractTasks.getValue(archive).map { it.outputs.files }) +abstract class GeneratePdfiumGlueSourceTask : DefaultTask() { + @get:InputFile @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val glueMjs: RegularFileProperty + + @get:org.gradle.api.tasks.OutputFile + abstract val outputKt: RegularFileProperty + + @TaskAction + fun run() { + val jsText = glueMjs.get().asFile.readText() + val escaped = buildString(jsText.length + 64) { + append('"') + for (c in jsText) when (c) { + '\\' -> append("\\\\") + '"' -> append("\\\"") + '\n' -> append("\\n") + '\r' -> append("\\r") + '\t' -> append("\\t") + '\b' -> append("\\b") + '\u000C' -> append("\\f") + in '\u0000'..'\u001F' -> append("\\u").append(c.code.toString(16).padStart(4, '0')) + '\u2028' -> append("\\u2028") + '\u2029' -> append("\\u2029") + else -> append(c) + } + append('"') + } + val out = outputKt.get().asFile + out.parentFile.mkdirs() + out.writeText( + """ + |package dev.nucleusframework.pdfium + | + |internal const val PDFIUM_GLUE_JS: String = $escaped + | + """.trimMargin(), + ) } - dependsOn(installPdfiumHeaders) - triplets.set(iosTriplets.map { "${it.first}|${it.second}" }) - archiveToDir.set(iosTriplets.associate { it.second to pdfiumExtractDir.get().dir(it.second).asFile.absolutePath }) - outputRoot.set(iosStaticLibsDir) } -val embedPdfiumDylibForXcode = tasks.register("embedPdfiumDylibForXcode") { +val pdfiumGlueGeneratedDir = layout.buildDirectory.dir("generated/pdfiumGlue/kotlin") +val generatePdfiumGlueSource = tasks.register("generatePdfiumGlueSource") { group = "pdfium" - description = "Copy & sign libpdfium.dylib into the iOS app bundle. Invoked from Xcode build phase." - dependsOn(installPdfiumIos) - outputs.upToDateWhen { false } + description = "Embed pdfium_glue.mjs as a Kotlin string so web targets eval it without webpack (issue #11)." + glueMjs.set(layout.projectDirectory.file("src/webMain/resources/pdfium_glue.mjs")) + outputKt.set(pdfiumGlueGeneratedDir.map { it.file("dev/nucleusframework/pdfium/PdfiumGlueSource.kt") }) + mustRunAfter(installPdfiumWasm, generatePdfiumWasmRuntime) +} - stagedLibsRoot.set(iosStaticLibsDir) - platformName.set(providers.environmentVariable("PLATFORM_NAME").orElse("")) - targetBuildDir.set(providers.environmentVariable("TARGET_BUILD_DIR").orElse("")) - frameworksFolderPath.set(providers.environmentVariable("FRAMEWORKS_FOLDER_PATH").orElse("")) - signIdentity.set(providers.environmentVariable("EXPANDED_CODE_SIGN_IDENTITY").orElse("")) - signingRequired.set(providers.environmentVariable("CODE_SIGNING_REQUIRED").orElse("")) +kotlin.sourceSets.getByName("webMain").kotlin.srcDir(pdfiumGlueGeneratedDir) +tasks.withType>().configureEach { + if ("Js" in name || "Wasm" in name || "Web" in name || "Metadata" in name) { + dependsOn(generatePdfiumGlueSource) + } +} + +val installPdfiumIos = tasks.register("installPdfiumIos") { + group = "pdfium" + description = "Stage the static PDFium archives + headers for the iOS cinterop." + sources.from(extractPdfiumIos.map { it.outputs.files }) + releaseRoot.set(pdfiumExtractDir.get().dir("ios-static/$pdfiumIosArchive").asFile.absolutePath) + triplets.set(iosTriplets) + outputRoot.set(iosStaticLibsDir) + headersDir.set(iosHeadersDir) } // ---------- JNI glue compilation ---------- @@ -563,12 +590,67 @@ tasks.matching { it.name == "preBuild" || it.name == "preDebugBuild" || it.name dependsOn(installPdfiumAndroidJniLibs, installPdfiumHeaders) } -if (Os.isFamily(Os.FAMILY_MAC)) { - tasks.matching { it.name.startsWith("cinteropPdfium") }.configureEach { - dependsOn(installPdfiumIos) +tasks.matching { it.name.startsWith("cinteropPdfium") }.configureEach { + dependsOn(installPdfiumIos) +} + +// ---------- Packaged-binaries regression (issue #11) ---------- + +abstract class PackagedBinariesArgumentProvider : CommandLineArgumentProvider { + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val iosKlibs: ConfigurableFileCollection + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val webRoots: ConfigurableFileCollection + + override fun asArguments(): MutableIterable = mutableListOf( + "-Dpdfium.ios.cinterop.klibs=${iosKlibs.files.joinToString(File.pathSeparator)}", + "-Dpdfium.web.klibs=${webRoots.files.joinToString(File.pathSeparator)}", + ) +} + +tasks.named("jvmTest") { + filter { + excludeTestsMatching("dev.nucleusframework.pdfium.PackagedBinariesTest") } } +// The klibs Maven consumers receive are the packed ones in build/libs, produced +// by the `…Cinterop-pdfiumKlib` tasks — not by cinterop itself, which only writes +// the unpacked directory under build/classes. Assert against the former. +val iosCinteropKlibTaskNames = listOf( + "iosArm64Cinterop-pdfiumKlib", + "iosSimulatorArm64Cinterop-pdfiumKlib", +) + +tasks.register("packagingTest") { + group = "verification" + description = "Assert iOS/web PDFium binaries are packaged into published klibs (issue #11)." + val jvmTest = tasks.named("jvmTest") + testClassesDirs = jvmTest.map { it.testClassesDirs }.get() + classpath = jvmTest.map { it.classpath }.get() + filter { includeTestsMatching("dev.nucleusframework.pdfium.PackagedBinariesTest") } + + iosCinteropKlibTaskNames.forEach { dependsOn(it) } + dependsOn("wasmJsProcessResources", "jsProcessResources") + + val args = objects.newInstance() + args.iosKlibs.from( + layout.buildDirectory.dir("libs").map { dir -> + dir.asFileTree.matching { include("*Cinterop*.klib") } + }, + ) + args.iosKlibs.builtBy(iosCinteropKlibTaskNames.map { tasks.named(it) }) + args.webRoots.from(layout.buildDirectory.dir("processedResources/wasmJs/main")) + args.webRoots.from(layout.buildDirectory.dir("processedResources/js/main")) + args.webRoots.builtBy("wasmJsProcessResources", "jsProcessResources") + jvmArgumentProviders.add(args) +} + +tasks.named("check") { dependsOn("packagingTest") } + // ---------- Smoke test ---------- val smokeTestRuntime by configurations.creating { diff --git a/pdfium/src/jsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.js.kt b/pdfium/src/jsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.js.kt new file mode 100644 index 0000000..5f80162 --- /dev/null +++ b/pdfium/src/jsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.js.kt @@ -0,0 +1,6 @@ +package dev.nucleusframework.pdfium + +internal actual fun evalJs(source: String) { + val src = source + js("eval(src)") +} diff --git a/pdfium/src/jvmMain/resources/pdfium/native/win32-arm64/pdfium.dll.lib b/pdfium/src/jvmMain/resources/pdfium/native/win32-arm64/pdfium.dll.lib index e7e65d9bf3bca84b10a846e8db6691919d118ebe..644411cac9163c3801adda1d712cb9d6cec067f5 100644 GIT binary patch delta 6809 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 diff --git a/pdfium/src/jvmMain/resources/pdfium/native/win32-x86-64/pdfium.dll.lib b/pdfium/src/jvmMain/resources/pdfium/native/win32-x86-64/pdfium.dll.lib index 549fe58a0b2c0be7f5691f08d0b686484d949264..4b053e75d4c8beff5958e8036dcd0061f35a4ce4 100644 GIT binary patch delta 6809 zcmZwL4^Wj={>Sm}5kf@7n<5e#5h4=Hx`(PHLlaTjpO)p(`|Q{*4gprxMnVqnQJb~+V6eNbNsFSapv`W?w{wL z=bZ03&-GkdE(CRb9khO;XX5yj)END*G(;p|hsfV|ihNoqa&ZVQJ3cECF^-V9lwxfs z@sE*K6RGt%x~oNuGbBFGvksH!8SBvpv^l!f9nZ%WTRt>3f zG3_fpXLOLbI?~!m;-8)%4w~CKPvTmkb&SN# z-8?{GthJfMZ7ypSsaLd}IWNjreAlzC>YS$bgm7+o_Jsi3m19L?g zM@fVxTQm}iLDAMm5@DfMBdO6rZE%T2yMy7PgGL95+vBV@(!*g0BQ@5L8a>p8&bR(T zBAkYd4@eA~U~MN65o0xz7#?QPx#3!`BeGhQaY2+PlEWR$opFRjREpJ3Vnn3XL~4DG zku{=>GbHZJvksAnPPCZLXxtTIEhDu{;?V~cGs`+g;_ftSH;GtEZ>%FRD$r^mwH`-Y znJD8liP6~>gBp!^DrV4TJjNt2|6>xc&5;l#%2-coeU5t=sd14+VzG6S#E-J9_emtB zTJ0o$Jj7}wwX2S?o^nxRHRf0Q2@M$sNF>KwEhNTKLSqG~bvsf@MH!4b1>+gH!8#d_ z2^rRY68BOG&2#TQ9^k$NYb%NSM_MeT`;qFhnn+9(tAW&*0xhl7>LT$#fpwI`q*RNl zOloKTAEe~Q8dAIFNT(_WRY}J~nN|mhjAZK#5|hKMMpC0PT4sqw8=07rYaJtxG5=YV z%AibHc$md!93qiTj}5AnjYkqJF8T;^7=W>n#G|1WRe4mSDq1cBGCm>kSUx=ZaKw?9 zBFflKVk!+A>qt!NXDuPMOOENqqKuD7FIa$C>{pj*FT_!$t>*C*!Ra62-xm zo7B!ao?_$%oqGx;>DE3HvtzCGBuW{mv53^Vng3EoI){b~Ix`1lnHIN78Rjxlqm@KC zBQ;pCoAF@ldU!q^E_c3v=vqliK-IoB#HS{$e`@=v49R4lzjoJ z>5#FK)EJbukamnuNYvz7he>#96Ga)#Bo+l(jih$n@l3ragU&sJx_Q zvA!CxJYHk|m(v?ft)kVHTZPt#vzIoF&RQN@5M)8%8^cmm)0|=1W?yqiMb<;}a4u=P)3B zc=?25?F3QAUJ}1dur`xe=dxCl+I7b(l-@W?Vtu~#K8aTun8Esb6&nJrdhY)X^*p)g z@D__Qj+5A!YPFO2RfN?n_{dD%>SkhqBhebV+pC9 zbG$}F#s?%?>5xGqt$01kqBE~+eU7$DQN~FUTlk#8cgYrPjki1-d4OMsS<6VR+wn%7 zD5HnOn>1{6k=T}Jb&~i^g0+prc1CKfBf$?W2kq{FXUA0@pxtd@MtHWYO{6*fY^ST^y>nn2tkuuSD}M zJeTLc81`2c|44;&i_c+WPrkBm1J-NL6=6l{fX~X@^Y5QEsnH9^bD(a@x*0}3xtMhBCYTS$MhurgY8NU0z zKl!mu0p9vf#ohEc$b^mX^>Pmm*4LFKoz5;a0R77Nas(++O4<=QLP-<)P%~1=8RXrmx=s{VWlGDf@&BZVwo^z2fM#(lrB`8^sKGfW!(3?s022l@Eeq2L&I-No0Lrf8pGnBl6u*q~7 z-6+ZA9OO=+YRJx_Q%HN55+gO6P9fnDK8vUv)&fEwr8DSx)Wd~xm3)Hy$0#RK@|b5t zO{MhcH%-YBTte}5B_AOpp9>)Ar<5E~1(X~zLrFclQ8JTrk@Yx>7D7(n}Z>VrO$vNU4%V=tl7z+CgR+(}JYA zN?H+KPG!-DnhF*xrd2ZkhdCra&FV+kJmwiaD5+w>AZtFoN74d%kML@4Idr3BA(caJ z4Yx27YngKdEn>vsiHsg|BfpAhnfq5cN9kpbwR8R2ZJrEi4qoZ{^lN*smEix>5HAbBD?|sSxtE zu@;c<8&*AHwsVI-c5p7P!rjhD(eM@t_fBqS)bHYbEZR-mn74;c?qUAR_cEU-dz;at zd>yUa2Dd-)w49dNbl*{aSoL@Xd{ezpd?|LEr1L|J1JDAL#Dy z_MGp3YuCJbXZB=ltoIM^Z2s2ko>9@>_IHN)pY4e4=e-aSrgvQ4^<#qkTRNAA1mEnv z-dTBfrj7Qe|2}?z>$@HICO>wUf8mKs$~z@;s6X#yP~d+%oqqbw2=85=#tpos*1pTJ zYKrfb1Qp{~pDhjc@y&PlV7)=TrRw^eOn?2CjdwOrRB_EN71rPG7=2&zj&FLXiu1iP zSPeRuI9RRiXHOa!^5321x6cdl=C27p`1$SXfdSs7Uk~=peqD{z7mo9-9ihyK4zArKIe5+WKR5t5Rk5gHNU%?we=l#I;Gi+>_AHS$$3 zGb}PQGevJB=DJPeHm>88>$;9PZPS(+kr@${l9}!IocHX^I`esd7kTek%T1k8tV>Nhr(I9fc&zeVSr(EA>i5S~ST#T~nN$nTc4-9CWAkoSDjGZKY46*7+ zt;f~ntq|$b&bWS>C}Qj((LKakPij|PKQ9n5+DZIUVx1=OYrb`a#HCb=W-j4!q|1A` zju+ZBS5K9QLD3%kR$-kbaizdwg|6WD$<|R4SM#k8N&Jy*9UyTn#cCm2#4CTYqz2pY zXM-r|WmS{fmo8N-$~Z>CFVosh!av5^NTQdYRYPj+uHGf0jN{~Suc&}LtCd6_3K`8L z0>iDfB!a}ExghNeS1^SQnhQqX3~M`ye$f__?1%n=@ahZQ85c-|%&;gDf&pVKW;p<% z3D#B;18LBxBefn^7|j_p7>4U6TOW`JPZ#CYmvFk%21Qu)q;|y>Q7OteOXB)`>i~&J z8ZsJ5+z?>ZkXpMds#uh9l*Hgn@#>2jywf!#T9iSNAs8BHEhDvyu4s=a<1-S&3N4Bb zLrkv4EMssZlQedbh>fu7+5cE(I-J8|(Cu)o!xdL9$~Z$JzQ{U4A|b`vLgJkoFI{uV|n-U0?BdKW)e3CSW8KbnQJK?>vIyflv*E?NM$9A<0NinC5%HPMr2qV z^bzomOcrIZ|09tWWi^txjV&;il3KSby-Jkv1&L9tjKQjmLIz82w2&AbYOyM#HC6^* zm02dPlO)CzScgfBO|-U65JnVG0XckYG|c2WSl4QK(WOnA3#|)`(Ks~W~n`xY&DZ854Gw^t;;o&!)2Tz z;mNUDNjwx|HISGk)RPJ&6hm85F6|n3XnXn#DPo zgUS?Z2Z_1S7L%HbdEqYaJc?@b{j6$IqdTq2W1S@NXo2++i3MpE+pqwSg;}+v*6CVU zCdxQYVo|o?Rf}4Af$A8mk;LP4Xw;Efk85$IDB~=NCuqp{fJ6;5H#U=aGSFI1YTd3S z<)W69v;RvzcRf`o%Am+oSejw&An`N}8EZ(b$F;0NltGoXG1PP2}ZsLQnWFm4@}=U959 zfz+gS0v&X9OM&pJqAO`^4x#0$aJ5>o4Mt(_stI8I_+wzZq*f8B0T>zSF+NaDpn zYZ<9sbTxQH8K+3RL_@{_5*uPIYHWbRp*Cp7(Ws+sEVcB%|2ERmrYwtn-h{>wi#IiD z-L9qzQO0Q!n+vT&BwkLj_LJC>YEg3wUWv5UlA5=N3%0Un#(5I2GE?IOiRK)OqRrTr zZoNn1wM^?UiP!ns;Crv5CBr&M;*E5d_YEFBZ9APCJ4w9BQX1Py?1-~?#&_VYM2qje zg`JVsdJ=DkTANAiinjD?yu^A1dTPLiZGpb4(}Mf>GD|**;=^qE|31uq ztVBY-5;=5Eq~>dp;`1Vh5%-NqBZAvSYS9VLw<0Hz(;?D|nD0ay;CDe}9!??ady(yk zx+qeQU*P#cLm&x@-lUB4OKndi;CYw&Z6K74Po-{B1e(WC(;j*{RbcB4*VgKa!sTKQGYTE^!mTg zam7l>F=YBF*^L-~B^%+_OGypdQPNw_S8=gTik(h0%fXI3;IL6tCn6QWBJGLC8%?mZJ;a@(N9ip}K1N|G%Z=Pyl^jCG2$mhmBUu4Nr7-~n-bM{{qbi-%LeVIiL3)Og zmJH>U(9v{*E|g^|If;TXN)97&EFB}@b|p*Dh1pq3&cQQ|rcj)%bCh zD_IYj!o6rmX(9I_cPjgh#A%!xgxsTKHLjqlh5OQZz2yu@v7^H%Ga30g)0^QfT*m5X_dQ1Aq&9w{{(QUpH9*+DnT zmvA0X_!RdbV<`a+7dXu5zzpwNW{>Q3 z{GdV9dS;Kn7dfrC2u}lhhrE~A7Q}Ag9{4#F!bOyBqz8c0@;!8;qKVUv!p+PY zDKGP=BXtY=zk^HUE1Y`tpmHnw49}~a1>`hyGt##yc@LSdvH!??ov)G6!t9X#2HS^} z?fei!?3+AMh}*%QBk?W1M&wS;2}0jy3*T0S61|HT2z^J%1_bZsaG)Di@3KrN-@^(Z zZ!d!*vz3`3dmn=%bw7s?n|yPZZu0kqE^qXA4zBp8Z`;Zm<-2)xQ2@W7H|yWa&L?qU zO(!B%KWA!vZ{JfdG%8>Gx>bSBn5==$(^-9eUvA3pOOE!f`Rnc=XG&JAZ|UC?f*3l; z_m7hol<(71zx(?-&%7An`|*dSKwrPY<`Y*}GuLS@A diff --git a/pdfium/src/jvmTest/kotlin/dev/nucleusframework/pdfium/PackagedBinariesTest.kt b/pdfium/src/jvmTest/kotlin/dev/nucleusframework/pdfium/PackagedBinariesTest.kt new file mode 100644 index 0000000..4a6379e --- /dev/null +++ b/pdfium/src/jvmTest/kotlin/dev/nucleusframework/pdfium/PackagedBinariesTest.kt @@ -0,0 +1,94 @@ +package dev.nucleusframework.pdfium + +import java.io.File +import java.util.zip.ZipFile +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * Regression for https://github.com/NucleusFramework/ComposePdfReader/issues/11 + * + * Maven consumers of `:pdfium` were getting `ld: library 'pdfium' not found` on iOS + * and webpack `Can't resolve './pdfium_glue.mjs'` on web, because the published + * klibs shipped Kotlin bindings but not the native/wasm binaries the linker and + * bundler actually need. Android / JVM already embed their natives; this test + * locks in the same for iOS cinterop and the JS/Wasm artefacts. + */ +class PackagedBinariesTest { + + @Test + fun iosCinteropKlibsEmbedLibpdfium() { + val klibs = artefactFiles("pdfium.ios.cinterop.klibs") + assertTrue(klibs.isNotEmpty(), "no iOS cinterop klibs were handed to the test") + klibs.forEach { klib -> + val names = artefactNames(klib) + assertTrue( + names.any { it.substringAfterLast('/') == "libpdfium.a" }, + "${klib.name} is missing libpdfium.a (issue #11). Entries:\n${names.joinToString("\n")}", + ) + val manifest = artefactText(klib, "default/manifest") + assertTrue( + !manifest.contains("-lpdfium"), + "${klib.name} exports `-lpdfium` instead of relying on the packed archive, " + + "so Maven consumers fail with `ld: library 'pdfium' not found`.\n$manifest", + ) + } + } + + @Test + fun webKlibsShipGlueWorkerRuntimeAndWasmAtArchiveRoot() { + val roots = artefactFiles("pdfium.web.klibs") + assertTrue(roots.isNotEmpty(), "no JS/Wasm resource roots were handed to the test") + val required = listOf( + "pdfium_glue.mjs", + "pdfium_worker.mjs", + "pdfium_runtime.mjs", + "pdfium.wasm", + ) + roots.forEach { root -> + val names = artefactNames(root) + required.forEach { file -> + // Webpack resolves `@JsModule("./pdfium_glue.mjs")` against the kotlin + // package dir / dist root. A nested `pdfium/pdfium_glue.mjs` is + // invisible to that import (issue #11). + assertTrue( + names.contains(file), + "${root.name} is missing root entry `$file` (issue #11). Entries:\n" + + names.filter { + it.contains("pdfium") || it.endsWith(".mjs") || it.endsWith(".wasm") + }.joinToString("\n"), + ) + } + } + } +} + +private fun artefactFiles(property: String): List { + val raw = System.getProperty(property) + ?: fail("system property `$property` is not set — run via :pdfium:packagingTest") + return raw.split(File.pathSeparator) + .filter { it.isNotBlank() } + .map { File(it) } + .filter { it.exists() } +} + +private fun artefactNames(file: File): List = when { + file.isDirectory -> file.walkTopDown() + .filter { it.isFile } + .map { it.relativeTo(file).invariantSeparatorsPath } + .toList() + else -> ZipFile(file).use { zip -> zip.entries().toList().map { it.name } } +} + +private fun artefactText(file: File, name: String): String = when { + file.isDirectory -> { + val child = file.resolve(name) + if (!child.isFile) fail("${file.name} has no `$name`") + child.readText() + } + else -> ZipFile(file).use { zip -> + val entry = zip.getEntry(name) ?: fail("${file.name} has no `$name`") + zip.getInputStream(entry).bufferedReader().readText() + } +} diff --git a/pdfium/src/nativeInterop/cinterop/pdfium.def b/pdfium/src/nativeInterop/cinterop/pdfium.def index 3d095c5..e025972 100644 --- a/pdfium/src/nativeInterop/cinterop/pdfium.def +++ b/pdfium/src/nativeInterop/cinterop/pdfium.def @@ -3,15 +3,17 @@ language = C headers = fpdfview.h fpdf_doc.h fpdf_formfill.h fpdf_text.h headerFilter = fpdf*.h -# Headers are staged by the Gradle task `installPdfiumHeaders` under build/pdfium/include. -compilerOpts = -Ibuild/pdfium/include +# Headers and archives are staged by `installPdfiumIos` from the +# paulocoutinhox/pdfium-lib release — the only PDFium build that publishes a +# *static* iOS library (bblanchon is dylib-only on Apple platforms). +compilerOpts = -Ibuild/pdfium/ios-include -# iOS device (arm64). bblanchon ships a dynamic lib; consumer app must embed libpdfium.dylib. +# cinterop packs the archive into the klib (default/targets//included/), +# so the published artefact carries the machine code and consumers link with no +# -L, no ThirdParty drop and nothing to embed or sign (issue #11). +staticLibraries = libpdfium.a libraryPaths.ios_arm64 = src/nativeInterop/libs/ios-arm64 -linkerOpts.ios_arm64 = -lpdfium - -# iOS simulator (arm64). libraryPaths.ios_simulator_arm64 = src/nativeInterop/libs/ios-simulator-arm64 -linkerOpts.ios_simulator_arm64 = -lpdfium +# PDFium only needs CoreGraphics/CoreFoundation plus the C++ runtime. linkerOpts = -framework CoreGraphics -framework Foundation -lc++ diff --git a/pdfium/src/wasmJsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.wasmJs.kt b/pdfium/src/wasmJsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.wasmJs.kt new file mode 100644 index 0000000..1bddafb --- /dev/null +++ b/pdfium/src/wasmJsMain/kotlin/dev/nucleusframework/pdfium/GlueEval.wasmJs.kt @@ -0,0 +1,3 @@ +package dev.nucleusframework.pdfium + +internal actual fun evalJs(source: String): Unit = js("eval(source)") diff --git a/pdfium/src/webMain/kotlin/dev/nucleusframework/pdfium/PdfiumGlue.kt b/pdfium/src/webMain/kotlin/dev/nucleusframework/pdfium/PdfiumGlue.kt index 4f16676..832f835 100644 --- a/pdfium/src/webMain/kotlin/dev/nucleusframework/pdfium/PdfiumGlue.kt +++ b/pdfium/src/webMain/kotlin/dev/nucleusframework/pdfium/PdfiumGlue.kt @@ -1,4 +1,3 @@ -@file:JsModule("./pdfium_glue.mjs") @file:OptIn(ExperimentalWasmJsInterop::class) package dev.nucleusframework.pdfium @@ -6,6 +5,7 @@ package dev.nucleusframework.pdfium import kotlin.js.ExperimentalWasmJsInterop import kotlin.js.JsAny import kotlin.js.JsArray +import kotlin.js.JsName import kotlin.js.JsString import kotlin.js.Promise import org.khronos.webgl.ArrayBuffer @@ -13,9 +13,13 @@ import org.khronos.webgl.Float32Array import org.khronos.webgl.Int32Array /** - * External bindings to `pdfium_glue.mjs`. Shared between jsMain and wasmJsMain via the - * `webMain` source set — all interfaces extend [JsAny] so the declarations are valid on - * both platforms. The RPC functions uniformly return `Promise` because + * Bindings to the pdfium Web Worker RPC. Shared between jsMain and wasmJsMain via + * `webMain`. The glue script is eval'd into globalThis (see [evalJs] / [PDFIUM_GLUE_JS]) + * instead of `@JsModule("./pdfium_glue.mjs")`, because that relative import is resolved + * by webpack against the consumer's kotlin package dir and is invisible when the file + * only exists as a klib resource (issue #11). + * + * The RPC functions uniformly return `Promise` because * `kotlinx.coroutines.await` on wasmJs is declared on that receiver type; call sites go * through [awaitTyped] to recover the typed fulfilment value. */ @@ -62,10 +66,62 @@ internal external interface PageLinksResult : JsAny { val destPages: Int32Array } -internal external fun openDocument(buffer: ArrayBuffer, password: String?): Promise -internal external fun closeDocument(doc: Int): Promise -internal external fun pageSize(doc: Int, pageIndex: Int): Promise -internal external fun renderPage(doc: Int, pageIndex: Int, w: Int, h: Int, flags: Int): Promise -internal external fun pageText(doc: Int, pageIndex: Int): Promise -internal external fun pageTextLayout(doc: Int, pageIndex: Int): Promise -internal external fun pageLinks(doc: Int, pageIndex: Int): Promise +internal expect fun evalJs(source: String) + +private val pdfiumGlueLoaded: Unit by lazy { evalJs(PDFIUM_GLUE_JS) } + +internal fun openDocument(buffer: ArrayBuffer, password: String?): Promise { + pdfiumGlueLoaded + return pdfiumOpenDocument(buffer, password) +} + +internal fun closeDocument(doc: Int): Promise { + pdfiumGlueLoaded + return pdfiumCloseDocument(doc) +} + +internal fun pageSize(doc: Int, pageIndex: Int): Promise { + pdfiumGlueLoaded + return pdfiumPageSize(doc, pageIndex) +} + +internal fun renderPage(doc: Int, pageIndex: Int, w: Int, h: Int, flags: Int): Promise { + pdfiumGlueLoaded + return pdfiumRenderPage(doc, pageIndex, w, h, flags) +} + +internal fun pageText(doc: Int, pageIndex: Int): Promise { + pdfiumGlueLoaded + return pdfiumPageText(doc, pageIndex) +} + +internal fun pageTextLayout(doc: Int, pageIndex: Int): Promise { + pdfiumGlueLoaded + return pdfiumPageTextLayout(doc, pageIndex) +} + +internal fun pageLinks(doc: Int, pageIndex: Int): Promise { + pdfiumGlueLoaded + return pdfiumPageLinks(doc, pageIndex) +} + +@JsName("pdfiumOpenDocument") +internal external fun pdfiumOpenDocument(buffer: ArrayBuffer, password: String?): Promise + +@JsName("pdfiumCloseDocument") +internal external fun pdfiumCloseDocument(doc: Int): Promise + +@JsName("pdfiumPageSize") +internal external fun pdfiumPageSize(doc: Int, pageIndex: Int): Promise + +@JsName("pdfiumRenderPage") +internal external fun pdfiumRenderPage(doc: Int, pageIndex: Int, w: Int, h: Int, flags: Int): Promise + +@JsName("pdfiumPageText") +internal external fun pdfiumPageText(doc: Int, pageIndex: Int): Promise + +@JsName("pdfiumPageTextLayout") +internal external fun pdfiumPageTextLayout(doc: Int, pageIndex: Int): Promise + +@JsName("pdfiumPageLinks") +internal external fun pdfiumPageLinks(doc: Int, pageIndex: Int): Promise diff --git a/pdfium/src/webMain/resources/pdfium/pdfium_glue.mjs b/pdfium/src/webMain/resources/pdfium_glue.mjs similarity index 79% rename from pdfium/src/webMain/resources/pdfium/pdfium_glue.mjs rename to pdfium/src/webMain/resources/pdfium_glue.mjs index 6b19a76..1af1e9e 100644 --- a/pdfium/src/webMain/resources/pdfium/pdfium_glue.mjs +++ b/pdfium/src/webMain/resources/pdfium_glue.mjs @@ -52,32 +52,35 @@ async function rpc(op, args, transfer) { // ---- exported API --------------------------------------------------------------------- -export function openDocument(buffer, password) { +// Exposed on globalThis so Kotlin/JS and Kotlin/Wasm can call them without a +// webpack `@JsModule("./pdfium_glue.mjs")` resolution (issue #11). The script +// is eval'd once from Kotlin; ES-module `export` is invisible to that path. +globalThis.pdfiumOpenDocument = function (buffer, password) { // `buffer` is the raw PDF ArrayBuffer — transfer it so the main thread doesn't keep // a second copy alive for the document's lifetime. return rpc('open', { buffer, password }, [buffer]); -} +}; -export function closeDocument(doc) { +globalThis.pdfiumCloseDocument = function (doc) { return rpc('close', { doc }); -} +}; -export function pageSize(doc, pageIndex) { +globalThis.pdfiumPageSize = function (doc, pageIndex) { return rpc('pageSize', { doc, pageIndex }); -} +}; -export function renderPage(doc, pageIndex, w, h, flags) { +globalThis.pdfiumRenderPage = function (doc, pageIndex, w, h, flags) { return rpc('render', { doc, pageIndex, w, h, flags }); -} +}; -export function pageText(doc, pageIndex) { +globalThis.pdfiumPageText = function (doc, pageIndex) { return rpc('text', { doc, pageIndex }); -} +}; -export function pageTextLayout(doc, pageIndex) { +globalThis.pdfiumPageTextLayout = function (doc, pageIndex) { return rpc('layout', { doc, pageIndex }); -} +}; -export function pageLinks(doc, pageIndex) { +globalThis.pdfiumPageLinks = function (doc, pageIndex) { return rpc('links', { doc, pageIndex }); -} +}; diff --git a/pdfium/src/webMain/resources/pdfium/pdfium_worker.mjs b/pdfium/src/webMain/resources/pdfium_worker.mjs similarity index 100% rename from pdfium/src/webMain/resources/pdfium/pdfium_worker.mjs rename to pdfium/src/webMain/resources/pdfium_worker.mjs From 9466c23fcd457eef969e891fb46cd0ecef604342 Mon Sep 17 00:00:00 2001 From: Elie Gambache Date: Sun, 30 Aug 2026 07:29:00 +0300 Subject: [PATCH 2/2] ci: run the iOS packaging assertion on macOS, untrack the import libs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple cinterop cannot run on a Linux host — KGP disables cross compilation for targets that declare cinterops — so :pdfium:check on ubuntu produced no iOS klibs and PackagedBinariesTest failed on an empty file collection. The iOS half now reports as skipped there and runs in a dedicated macOS pre-merge job, which is also the host that publishes. pdfium.dll.lib was tracked only because the `*.dll` ignore rule doesn't match it; installPdfiumJvmResources stages both Windows import libraries from the bblanchon archive on every build, so they were pure churn on each version bump. --- .github/workflows/pre-merge.yaml | 21 ++++++++++++++ .gitignore | 3 ++ pdfium/build.gradle.kts | 26 +++++++++++++----- .../pdfium/native/win32-arm64/pdfium.dll.lib | Bin 113624 -> 0 bytes .../pdfium/native/win32-x86-64/pdfium.dll.lib | Bin 113624 -> 0 bytes .../pdfium/PackagedBinariesTest.kt | 6 ++++ 6 files changed, 49 insertions(+), 7 deletions(-) delete mode 100644 pdfium/src/jvmMain/resources/pdfium/native/win32-arm64/pdfium.dll.lib delete mode 100644 pdfium/src/jvmMain/resources/pdfium/native/win32-x86-64/pdfium.dll.lib diff --git a/.github/workflows/pre-merge.yaml b/.github/workflows/pre-merge.yaml index 5e45b64..ea83c73 100644 --- a/.github/workflows/pre-merge.yaml +++ b/.github/workflows/pre-merge.yaml @@ -58,3 +58,24 @@ jobs: - name: Run :pdfium:check run: ./gradlew :pdfium:check --continue + + packaging-ios: + # Apple cinterop cannot run on Linux (KGP disables cross compilation for + # targets declaring cinterops), so the iOS half of the packaging regression + # — libpdfium.a inside the published klib, issue #11 — only runs here. + runs-on: macos-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Run :pdfium:packagingTest + run: ./gradlew :pdfium:packagingTest diff --git a/.gitignore b/.gitignore index d1bdf8e..f1063ee 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ node_modules/ *.so *.dylib *.dll +# Windows import libraries staged by :pdfium:installPdfiumJvmResources — `*.dll` +# doesn't match `pdfium.dll.lib`, which is why they used to be committed. +*.dll.lib # iOS PDFium static archives staged by :pdfium:installPdfiumIos from the # paulocoutinhox/pdfium-lib release. Redownloaded on every build. diff --git a/pdfium/build.gradle.kts b/pdfium/build.gradle.kts index 41ff11f..d8e2eb3 100644 --- a/pdfium/build.gradle.kts +++ b/pdfium/build.gradle.kts @@ -605,7 +605,11 @@ abstract class PackagedBinariesArgumentProvider : CommandLineArgumentProvider { @get:PathSensitive(PathSensitivity.RELATIVE) abstract val webRoots: ConfigurableFileCollection + @get:Input + abstract val iosSupported: Property + override fun asArguments(): MutableIterable = mutableListOf( + "-Dpdfium.ios.supported=${iosSupported.get()}", "-Dpdfium.ios.cinterop.klibs=${iosKlibs.files.joinToString(File.pathSeparator)}", "-Dpdfium.web.klibs=${webRoots.files.joinToString(File.pathSeparator)}", ) @@ -625,6 +629,11 @@ val iosCinteropKlibTaskNames = listOf( "iosSimulatorArm64Cinterop-pdfiumKlib", ) +// KGP disables cross compilation for targets that declare cinterops, so the iOS +// klibs simply don't exist off a macOS host. The web half still runs everywhere; +// the iOS half is covered by the `packaging-ios` CI job and by the publish run. +val iosPackagingSupported = Os.isFamily(Os.FAMILY_MAC) + tasks.register("packagingTest") { group = "verification" description = "Assert iOS/web PDFium binaries are packaged into published klibs (issue #11)." @@ -633,16 +642,19 @@ tasks.register("packagingTest") { classpath = jvmTest.map { it.classpath }.get() filter { includeTestsMatching("dev.nucleusframework.pdfium.PackagedBinariesTest") } - iosCinteropKlibTaskNames.forEach { dependsOn(it) } + if (iosPackagingSupported) iosCinteropKlibTaskNames.forEach { dependsOn(it) } dependsOn("wasmJsProcessResources", "jsProcessResources") val args = objects.newInstance() - args.iosKlibs.from( - layout.buildDirectory.dir("libs").map { dir -> - dir.asFileTree.matching { include("*Cinterop*.klib") } - }, - ) - args.iosKlibs.builtBy(iosCinteropKlibTaskNames.map { tasks.named(it) }) + args.iosSupported.set(iosPackagingSupported) + if (iosPackagingSupported) { + args.iosKlibs.from( + layout.buildDirectory.dir("libs").map { dir -> + dir.asFileTree.matching { include("*Cinterop*.klib") } + }, + ) + args.iosKlibs.builtBy(iosCinteropKlibTaskNames.map { tasks.named(it) }) + } args.webRoots.from(layout.buildDirectory.dir("processedResources/wasmJs/main")) args.webRoots.from(layout.buildDirectory.dir("processedResources/js/main")) args.webRoots.builtBy("wasmJsProcessResources", "jsProcessResources") diff --git a/pdfium/src/jvmMain/resources/pdfium/native/win32-arm64/pdfium.dll.lib b/pdfium/src/jvmMain/resources/pdfium/native/win32-arm64/pdfium.dll.lib deleted file mode 100644 index 644411cac9163c3801adda1d712cb9d6cec067f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113624 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 z zL3@KV*m`Ig_nk*a-5a+zRdbOf1;%_Gpit)c35=+3MSQondSk7MZfZWZgVhn)+&HZ- zER^+&Mwaz31i8V@(JA#&IL5E}g^?z|D=;ku_%t?tA6sa3`E87;WjviV=J%>2D}j1Y zQ$HpbU=4@%2;&DbdE$3CMcsjOo*b%u7D?1HF651kNW|SK~ zHBR9MeTcF=q7!XwyFu<++gOT;7ik;kZlN_jgSV!aPj9_q>&(p7Df;$2xw_nKk9Q+) zVT2s4NCYPbPlOt>= zT~p)OSFJ#d2!UA~8mq0Y+R2@UVRiw#*T)+vT~JsncBkTICoh}Fy4&_XYz=V{(9~)V wZ-9l@A2m+%HNWW^t90{F&S2wGw$}K=*xS>aUB;+m_n?nsGAm{$QjoL%8+Uq9f&c&j diff --git a/pdfium/src/jvmMain/resources/pdfium/native/win32-x86-64/pdfium.dll.lib b/pdfium/src/jvmMain/resources/pdfium/native/win32-x86-64/pdfium.dll.lib deleted file mode 100644 index 4b053e75d4c8beff5958e8036dcd0061f35a4ce4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113624 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