Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/pdfium-auto-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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-<build>` 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 }}"
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/pre-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 10 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ 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.
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
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<build>`
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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -739,7 +763,9 @@ drops `libpdfium.so` into `src/androidMain/jniLibs/<abi>/`.
### 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

Expand Down
8 changes: 4 additions & 4 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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-<build>` (see :pdfium:installPdfiumIos).
pdfium-bblanchon = "chromium/8021"
download-plugin = "5.6.0"
okio = "3.9.1"
Expand Down
7 changes: 2 additions & 5 deletions iosApp/Configuration/Config.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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 PDFium linker setup: libpdfium.a is packed inside the cinterop klib and
// statically linked into ComposeApp.framework.
2 changes: 1 addition & 1 deletion iosApp/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
Loading
Loading