Nucleus
PDF reader

PDF reader

Render PDFs, extract text, and select or copy content from Compose, with one API across Android, iOS, web, and desktop.

The PDF reader is a Kotlin Multiplatform library that renders PDF pages, extracts text, and drives selection, search, links, and thumbnails from Compose. The public API is the same on every target: you write against PdfReaderState and PdfPage once, and the library binds to the native PDF stack of each platform underneath.

Separate repository — versioned independently

The PDF reader ships from its own repository with its own release cycle, like the system tray. Its version number is independent of Nucleus — pin it separately. The artifact is dev.nucleusframework:pdfium, published to Maven Central.

Latest release: 152.0.7947.0

One API instead of one per platform

Each platform has its own PDF engine, with its own API, threading model, and coordinate conventions:

PlatformNative PDF stack
AndroidPdfRenderer (android.graphics.pdf)
iOSPDFKit (PDFView, PDFDocument)
WebPDF.js
DesktopPDFBox and other JVM libraries

Without a shared layer, a multiplatform app integrates and maintains four of these. The PDF reader replaces them with a single Compose surface backed by PDFium on all four, so rendering, text extraction, and selection behave identically everywhere.

Add the dependency

build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("dev.nucleusframework:pdfium:152.0.7947.0")
        }
    }
}

The :pdfium module uses a JVM toolchain of 17. PDFium binaries are fetched at build time from bblanchon/pdfium-binaries; no manual native setup is required.

Show a PDF

Hoist a PdfReaderState, open the document bytes, and drop PdfReader into your layout:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import dev.nucleusframework.pdfium.PdfReader
import dev.nucleusframework.pdfium.rememberPdfReaderState

@Composable
fun HelloPdf(bytes: ByteArray) {
    val reader = rememberPdfReaderState()
    LaunchedEffect(bytes) { reader.open(bytes) }
    PdfReader(state = reader, modifier = Modifier.fillMaxSize())
}

PdfReader stacks every page in a LazyColumn. rememberPdfReaderState disposes the native handles automatically when it leaves composition.

Supported targets

TargetBackend
JVMJNI + Skia (Skiko)
AndroidJNI (NDK AndroidBitmap_*)
iOSKotlin/Native cinterop + Skia (Skiko)
Webpdfium.wasm in a Web Worker + Skiko

JVM covers linux-x64/arm64, macos-x64/arm64, and win-x64/arm64. Android covers arm64-v8a, armeabi-v7a, x86, and x86_64. Web covers Kotlin/WasmJS and Kotlin/JS (IR).

How it works

commonMain holds the whole public API — PdfReaderState, PdfPage, PdfThumbnail, and PdfReader — over an expect class PdfDocument. Each source set provides the actual: JNI glue on JVM and Android, a Kotlin/Native cinterop on iOS, and a pdfium.wasm worker on the web.

Rendering uses a zero-copy path where the platform allows it. On JVM and iOS, PDFium writes pixels straight into a Skia bitmap's memory; on Android it locks the android.graphics.Bitmap; on the web the worker's pixel ArrayBuffer is written into Skia's wasm heap without an intermediate Kotlin ByteArray.

PDFium is single-threaded per document — it relies on FreeType's non-thread-safe FT_Library — so each PdfDocument runs on its own single-threaded dispatcher. Rendering is serialised inside a document.

What the library covers

  • Compose composables: drop PdfReader, PdfPage, or PdfThumbnail into any Compose UI.
  • Progressive rendering (preview to full) with a debounced size flow.
  • A two-tier LRU cache for reader bitmaps and thumbnails, with off-screen prefetch.
  • Text extraction: per-page UTF-8 text, line-level rectangles, and per-character boxes.
  • A selectable-text overlay driven by PDFium's per-character boxes.
  • Clickable links: URI and internal GoTo annotations, plus URLs and e-mail addresses detected in the page text.
  • Password-protected documents through PdfError.PasswordRequired.

Licensing

PDFium is dual-licensed BSD-3-Clause / Apache-2.0, and bblanchon's binaries carry that license forward. If you ship an app built on this library, include the upstream PDFium notices.

What's next

  • Getting started — load bytes, handle loading state, enable selection, links, thumbnails, and zoom.
  • API reference — every public type, its parameters, and defaults.
  • System tray — another Nucleus component shipped from its own repository.