
Single standalone PDF engine: parser, renderer, writer, encryption, fonts, text extraction and true redaction; build, edit, incremental saving, composable viewer and headless rasterizers for thumbnails and exports.
One pure-Kotlin PDF engine for Kotlin Multiplatform. Read, create, edit and render PDFs from commonMain, with the exact same code on every target.
Getting started, guides, recipes, and the full API reference. If you read one thing, read this.
KitePDF is a complete PDF engine written from scratch in Kotlin: parser, renderer, writer, editor, encryption, fonts, the whole stack. You call it from common code and it runs unchanged on Android, iOS, desktop (JVM), the web (JS / Wasm) and Kotlin/Native. There is no platform PDF engine underneath, no expect/actual, no JNI, no native binary.
// commonMain. Nothing platform-specific. This runs everywhere Kotlin runs.
val doc = PdfDocument.open(bytes)
val pages = doc.pageCount // inspect
val text = doc.pages[0].extractText() // read
// edit in place, then save
val edited = doc.edit().apply {
redactRegion(doc.pages[0], Rectangle(72.0, 700.0, 320.0, 720.0))
}.saveRewritten()
// or build a brand-new document
val fresh = PdfBuilder()
.page { text(StandardFont.Helvetica, 24.0, x = 72.0, y = 720.0, "Hello, world!") }
.build()Almost every other Kotlin / KMP "PDF library" is not really a PDF library. It is a thin expect/actual (or JNI) wrapper around the platform's own engine: PdfRenderer on Android, PDFKit on iOS, PDF.js in the browser, PDFBox on the JVM. You inherit four different engines, four sets of bugs, and four feature sets that never quite line up.
KitePDF is the opposite. It is a single standalone engine, 100% common Kotlin, with zero expect/actual in the core. Write your PDF code once in commonMain and it behaves identically on every target, because it is literally the same code. When something renders wrong, it is one bug in one place, and it is ours to fix.
Rendering to a screen is the only thing that needs a platform. KitePDF keeps that cleanly separate (see Putting pixels on screen), so the engine stays pure and portable.
The engine is one dependency. Add it to commonMain and you have everything except drawing to a screen:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.yuroyami:kitepdf:0.1.0")
}
}
}That is it for reading, writing, editing, redacting, encryption, text extraction and building PDFs, on every Kotlin target. Its only dependency is kotlin-stdlib.
Not using Kotlin Multiplatform? The same artifact works in a plain Android or JVM project. Add io.github.yuroyami:kitepdf:0.1.0 to your normal dependencies { } block.
Rendering bindings are opt-in and covered under Putting pixels on screen.
Everything below is pure common code from the kitepdf artifact. Each item links to its guide.
PdfView, drawn straight into a Compose DrawScope.A few quick tastes:
// Read text
val text = doc.pages[0].extractText()
// Open a password-protected PDF
val doc = PdfDocument.open(bytes, password = "secret".encodeToByteArray())
require(doc.isAuthenticated)
// Fill a form field and save (append-only)
val out = doc.edit()
.apply { setTextFieldValue(doc.formField("ApplicantName")!!, "Jane Doe") }
.saveIncremental()
// Watermark every page
val stamped = doc.edit().apply {
doc.pages.forEach { page ->
stampPage(page) {
setFillRgb(0.8, 0.1, 0.1)
text(StandardFont.HelveticaBold, 48.0, x = 120.0, y = 400.0, "DRAFT")
}
}
}.saveIncremental()See the full documentation for the complete API and worked examples.
The engine is headless. Showing a PDF is the one job that needs a platform, so it lives in separate, optional artifacts. Pick the one that matches how you draw.
A PDF page is just another composable. PdfView draws into a Compose DrawScope, so it scrolls, zooms and composes with your UI like anything else on a Canvas. No AndroidView, no UIKitView, no embedded web view.
// commonMain of a Compose Multiplatform app
val state = rememberPdfViewState(doc)
PdfView(
state = state,
layout = PdfLayout.Paged(Orientation.Horizontal), // or Continuous / SinglePage
zoomSpec = PdfZoomSpec(maxZoom = 6f), // pinch, double-tap, pan
renderSpec = PdfRenderSpec.Rasterized(), // or Vectorized()
overlay = { PdfNavigationControls(it, Modifier.align(Alignment.BottomCenter)) },
)
// The same state drives widgets anywhere in your UI
PdfPageIndicator(state) // "3 / 12"
PdfThumbnailStrip(state) // tappable thumbnailsSee the viewer guide for layouts, zoom, render modes, navigation and export.
For servers, CI, thumbnails, or non-Compose UIs, render a page straight to image bytes.
// kitepdf-native-renderer, JVM (AWT). Also CoreGraphics (Apple), android.graphics, Canvas2D (JS).
val png = AwtPdfRasterizer.encodeToPng(doc.pages[0], scale = 2.0)
// kitepdf-skia, one common API via Skiko (JVM, Android, Apple, Linux, web).
val png = PdfPageRasterizer.encodeToPng(doc.pages[0], scale = 2.0)See the rendering guide to choose between them.
The engine runs on every target Kotlin supports, with no per-platform code. Only the rendering bindings are limited by what their underlying toolkit ships.
| Target | Engine (kitepdf) |
Compose (-compose) |
Native renderer (-native-renderer) |
Skia (-skia) |
|---|---|---|---|---|
| Android | ✓ | ✓ | ✓ | ✓ |
| iOS (arm64 / simulator) | ✓ | ✓ | ✓ | ✓ |
| JVM (Desktop / Server) | ✓ | ✓ | ✓ | ✓ |
| macOS (Apple Silicon) | ✓ | ✓ | ✓ | ✓ |
| JS (Browser / Node) | ✓ | ✓ | ✓ | ✓ |
| wasmJs | ✓ | ✓ | – | ✓ |
| tvOS | ✓ | – | ✓ | ✓ |
| watchOS | ✓ | – | – | – |
| Linux (x64 / arm64) | ✓ | – | – | ✓ |
| Windows (mingwX64) | ✓ | – | – | – |
| Android Native, WASI | ✓ | – | – | – |
Full details and the reasons behind each gap are in the platform support guide.
KitePDF is pre-1.0 and actively developed.
Working today: reading and text extraction, metadata, outlines, annotations, form fields, encrypted documents, the Compose viewer (continuous / paged scroll, pinch zoom), headless rendering, editing and saving, true redaction, and building PDFs from scratch.
On the way: digital signatures, the JBIG2 and JPEG 2000 image codecs, less common form widgets, and advanced colour management.
If a PDF renders incorrectly, please open an issue with the file attached. The project ships a pixel-diff harness against MuPDF, and every fix lands as a regression test.
The sample/ module is a runnable Compose Multiplatform app that opens a PDF and exercises the API across Android, iOS and Desktop. Use it as a copy-paste starting point.
Apache License 2.0. A few encoding tables and font data are derived from MuPDF and keep their AGPL-3.0 headers in those specific source files; see the comments there.
Architectural reference: MuPDF by Artifex Software. Standard 14 font metrics derive from URW++ AFM files. Thanks to everyone who published the PDF specification and the tools that made this possible.
One pure-Kotlin PDF engine for Kotlin Multiplatform. Read, create, edit and render PDFs from commonMain, with the exact same code on every target.
Getting started, guides, recipes, and the full API reference. If you read one thing, read this.
KitePDF is a complete PDF engine written from scratch in Kotlin: parser, renderer, writer, editor, encryption, fonts, the whole stack. You call it from common code and it runs unchanged on Android, iOS, desktop (JVM), the web (JS / Wasm) and Kotlin/Native. There is no platform PDF engine underneath, no expect/actual, no JNI, no native binary.
// commonMain. Nothing platform-specific. This runs everywhere Kotlin runs.
val doc = PdfDocument.open(bytes)
val pages = doc.pageCount // inspect
val text = doc.pages[0].extractText() // read
// edit in place, then save
val edited = doc.edit().apply {
redactRegion(doc.pages[0], Rectangle(72.0, 700.0, 320.0, 720.0))
}.saveRewritten()
// or build a brand-new document
val fresh = PdfBuilder()
.page { text(StandardFont.Helvetica, 24.0, x = 72.0, y = 720.0, "Hello, world!") }
.build()Almost every other Kotlin / KMP "PDF library" is not really a PDF library. It is a thin expect/actual (or JNI) wrapper around the platform's own engine: PdfRenderer on Android, PDFKit on iOS, PDF.js in the browser, PDFBox on the JVM. You inherit four different engines, four sets of bugs, and four feature sets that never quite line up.
KitePDF is the opposite. It is a single standalone engine, 100% common Kotlin, with zero expect/actual in the core. Write your PDF code once in commonMain and it behaves identically on every target, because it is literally the same code. When something renders wrong, it is one bug in one place, and it is ours to fix.
Rendering to a screen is the only thing that needs a platform. KitePDF keeps that cleanly separate (see Putting pixels on screen), so the engine stays pure and portable.
The engine is one dependency. Add it to commonMain and you have everything except drawing to a screen:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.yuroyami:kitepdf:0.1.0")
}
}
}That is it for reading, writing, editing, redacting, encryption, text extraction and building PDFs, on every Kotlin target. Its only dependency is kotlin-stdlib.
Not using Kotlin Multiplatform? The same artifact works in a plain Android or JVM project. Add io.github.yuroyami:kitepdf:0.1.0 to your normal dependencies { } block.
Rendering bindings are opt-in and covered under Putting pixels on screen.
Everything below is pure common code from the kitepdf artifact. Each item links to its guide.
PdfView, drawn straight into a Compose DrawScope.A few quick tastes:
// Read text
val text = doc.pages[0].extractText()
// Open a password-protected PDF
val doc = PdfDocument.open(bytes, password = "secret".encodeToByteArray())
require(doc.isAuthenticated)
// Fill a form field and save (append-only)
val out = doc.edit()
.apply { setTextFieldValue(doc.formField("ApplicantName")!!, "Jane Doe") }
.saveIncremental()
// Watermark every page
val stamped = doc.edit().apply {
doc.pages.forEach { page ->
stampPage(page) {
setFillRgb(0.8, 0.1, 0.1)
text(StandardFont.HelveticaBold, 48.0, x = 120.0, y = 400.0, "DRAFT")
}
}
}.saveIncremental()See the full documentation for the complete API and worked examples.
The engine is headless. Showing a PDF is the one job that needs a platform, so it lives in separate, optional artifacts. Pick the one that matches how you draw.
A PDF page is just another composable. PdfView draws into a Compose DrawScope, so it scrolls, zooms and composes with your UI like anything else on a Canvas. No AndroidView, no UIKitView, no embedded web view.
// commonMain of a Compose Multiplatform app
val state = rememberPdfViewState(doc)
PdfView(
state = state,
layout = PdfLayout.Paged(Orientation.Horizontal), // or Continuous / SinglePage
zoomSpec = PdfZoomSpec(maxZoom = 6f), // pinch, double-tap, pan
renderSpec = PdfRenderSpec.Rasterized(), // or Vectorized()
overlay = { PdfNavigationControls(it, Modifier.align(Alignment.BottomCenter)) },
)
// The same state drives widgets anywhere in your UI
PdfPageIndicator(state) // "3 / 12"
PdfThumbnailStrip(state) // tappable thumbnailsSee the viewer guide for layouts, zoom, render modes, navigation and export.
For servers, CI, thumbnails, or non-Compose UIs, render a page straight to image bytes.
// kitepdf-native-renderer, JVM (AWT). Also CoreGraphics (Apple), android.graphics, Canvas2D (JS).
val png = AwtPdfRasterizer.encodeToPng(doc.pages[0], scale = 2.0)
// kitepdf-skia, one common API via Skiko (JVM, Android, Apple, Linux, web).
val png = PdfPageRasterizer.encodeToPng(doc.pages[0], scale = 2.0)See the rendering guide to choose between them.
The engine runs on every target Kotlin supports, with no per-platform code. Only the rendering bindings are limited by what their underlying toolkit ships.
| Target | Engine (kitepdf) |
Compose (-compose) |
Native renderer (-native-renderer) |
Skia (-skia) |
|---|---|---|---|---|
| Android | ✓ | ✓ | ✓ | ✓ |
| iOS (arm64 / simulator) | ✓ | ✓ | ✓ | ✓ |
| JVM (Desktop / Server) | ✓ | ✓ | ✓ | ✓ |
| macOS (Apple Silicon) | ✓ | ✓ | ✓ | ✓ |
| JS (Browser / Node) | ✓ | ✓ | ✓ | ✓ |
| wasmJs | ✓ | ✓ | – | ✓ |
| tvOS | ✓ | – | ✓ | ✓ |
| watchOS | ✓ | – | – | – |
| Linux (x64 / arm64) | ✓ | – | – | ✓ |
| Windows (mingwX64) | ✓ | – | – | – |
| Android Native, WASI | ✓ | – | – | – |
Full details and the reasons behind each gap are in the platform support guide.
KitePDF is pre-1.0 and actively developed.
Working today: reading and text extraction, metadata, outlines, annotations, form fields, encrypted documents, the Compose viewer (continuous / paged scroll, pinch zoom), headless rendering, editing and saving, true redaction, and building PDFs from scratch.
On the way: digital signatures, the JBIG2 and JPEG 2000 image codecs, less common form widgets, and advanced colour management.
If a PDF renders incorrectly, please open an issue with the file attached. The project ships a pixel-diff harness against MuPDF, and every fix lands as a regression test.
The sample/ module is a runnable Compose Multiplatform app that opens a PDF and exercises the API across Android, iOS and Desktop. Use it as a copy-paste starting point.
Apache License 2.0. A few encoding tables and font data are derived from MuPDF and keep their AGPL-3.0 headers in those specific source files; see the comments there.
Architectural reference: MuPDF by Artifex Software. Standard 14 font metrics derive from URW++ AFM files. Thanks to everyone who published the PDF specification and the tools that made this possible.