
Compose-style DSL for building vector-first, type-safe PDF documents with bundled Inter font, rich layout, text shaping (CJK/Arabic support), SVG/images, pagination and optional viewer.
Kotlin Multiplatform PDF generator for Android, iOS, Desktop (JVM) & Web (Wasm) — vector-first, type-safe, DSL-driven.
📖 Full documentation: conamobiledev.github.io/PdfKmp — 20 hand-written guides, every sample, and the Dokka API reference.
| Android | Desktop — macOS · Windows · Linux (JVM) |
![]() |
![]() |
| iOS | Web (Kotlin/Wasm) |
![]() |
![]() |
The same brochure document rendered on Android, iOS and Desktop — pixel-identical vector output — plus the browser sample generating it live on the Web.
PdfKmp lets you build PDF documents from a Compose-style DSL that runs identically on Android, iOS, Desktop (JVM — macOS, Windows, Linux), and the browser (Kotlin/Wasm). Text becomes glyph paths, shapes become path operators — every page stays sharp at any zoom level. The library ships the Inter font for cross-platform Latin parity and exposes opt-in references to system CJK / Arabic / Persian fonts so non-Latin scripts render natively on Android and iOS (on Desktop, supply a custom font — see i18n fonts).
What's in the box:
maxLines + ellipsis, soft-hyphen + mid-word breaking, super/subscript spans, right-to-left support with bidi reorder + Arabic shaping (incl. kashida justification), and orphan/widow control.keepTogether, multi-column (columns) and uniform grid, mixed page orientations.freeDraw).<vector> and W3C <svg> (kept vector inside the PDF).See the platform feature parity table for what each backend supports.
Under the hood — native / pure-Java PDF stacks per platform:
| Platform | Backend |
|---|---|
| Android |
android.graphics.pdf.PdfDocument + android.graphics.Canvas
|
| iOS |
UIGraphicsBeginPDFContextToData + Core Graphics (CGContext) |
| Desktop (JVM) | Apache PDFBox — pure-Java, runs on macOS / Windows / Linux with no native libraries |
| Web (Wasm) |
kmpwriter — PdfKmp's own pure-Kotlin PDF 1.7 writer (no browser PDF API exists), with TrueType subsetting + Flate compression |
Every DSL node funnels into these PDF backends, so the resulting bytes are real, vector PDFs — readable in Preview, Adobe Reader, Chrome, and any spec-compliant viewer. On Desktop the embedded TrueType fonts are subsetted to only the glyphs you draw, so text stays vector and the file stays small.
PdfKmp is published to Maven Central. The library exposes:
aar (io.github.conamobiledev:pdfkmp-android),PdfKmp for arm64 (device) and simulator-arm64 (Apple-Silicon simulator),jar (io.github.conamobiledev:pdfkmp-jvm) for macOS, Windows, and Linux,io.github.conamobiledev:pdfkmp-wasm-js) — see Web (Kotlin/Wasm) for what the browser backend covers,io.github.conamobiledev:pdfkmp),io.github.conamobiledev:pdfkmp-compose-resources, with -android / -jvm / -wasm-js platform variants),io.github.conamobiledev:pdfkmp-markdown, same platform variants),io.github.conamobiledev:pdfkmp-viewer, with -android / -jvm platform variants — not on web; see below).The browser exposes no PDF engine to Wasm, so the web target renders through PdfKmp's own pure-Kotlin PDF writer — everything vector works exactly like the other platforms, custom TrueType fonts embed as subsets, and Cyrillic works out of the box via the bundled Inter. Current limits and font details: see the Web guide.
Viewing needs no library at all — browsers ship excellent PDF viewers, so hand them the bytes:
val doc = pdf { page { text("Hello, Wasm!") } }
doc.openInNewTab() // browser's own viewer
doc.save(StorageLocation.Downloads, "hello.pdf") // browser downloadMake sure Maven Central is in your repository list:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}Pick the section that matches your project. The paths are mutually exclusive — KMP projects depend on pdfkmp only (Gradle resolves the right platform variant automatically, including for Android and Desktop targets), while plain Android projects depend on pdfkmp-android and plain JVM/Desktop projects on pdfkmp-jvm. Inside each path, use whichever dependency style your project already uses; the libs.versions.toml form is recommended for new projects.
Depend on pdfkmp from commonMain. Do not add pdfkmp-android separately — Gradle picks the Android variant of pdfkmp for your Android target on its own. (Version catalogs work as usual — map the same coordinates in libs.versions.toml.)
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.conamobiledev:pdfkmp:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-compose-resources:1.2.0") // optional
implementation("io.github.conamobiledev:pdfkmp-viewer:1.2.0") // optional
}
}
}Both companions are opt-in:
pdfkmp-compose-resources — add it only if your project uses Compose Multiplatform Resources (Res.drawable.*) and you want typed resource references inside the PdfKmp DSL. Full usage in the Compose Resources guide.pdfkmp-viewer — Compose Multiplatform PDF viewer screen with topbar, search, share, save, hyperlinks, gesture-driven zoom & text selection. Skip it if you only need to generate PDFs and let users view them in their system default reader. Full usage in the viewer guide.The core pdfkmp artifact itself stays Compose-free — non-Compose consumers don't pay for either companion.
For projects that don't use Kotlin Multiplatform (plain com.android.application or com.android.library), depend on pdfkmp-android. KMP consumers should use the section above instead — they should not add this artifact.
// app/build.gradle.kts
dependencies {
implementation("io.github.conamobiledev:pdfkmp-android:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-viewer:1.2.0") // optional — Compose viewer screen
}pdfkmp-viewer brings in Compose Multiplatform — skip it if your Android project still uses the legacy View system. The viewer assumes Compose hosting (a ComponentActivity with setContent { … } or an embedded ComposeView).
KMP projects with a jvm() target need no extra setup — Gradle resolves the Desktop variant of pdfkmp for the JVM target automatically, exactly like it does for Android. For a plain JVM project (a standalone Compose for Desktop app or any non-KMP Gradle JVM module), depend on pdfkmp-jvm directly:
// build.gradle.kts
dependencies {
implementation("io.github.conamobiledev:pdfkmp-jvm:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-viewer-jvm:1.2.0") // optional — Compose for Desktop viewer
}The Desktop backend is built on Apache PDFBox (pulled in transitively) and is pure-Java, so the same artifact runs on macOS, Windows, and Linux with no native libraries to bundle. The generation API (pdf { … }, document.save(...), toByteArray()) is identical across all platforms. pdfkmp-viewer on Desktop hosts the viewer in a Compose for Desktop window and rasterises pages with PDFBox's PDFRenderer.
compileSdk 34+ (Android targets)R8 is fully supported — no additional keep rules required.
import com.conamobile.pdfkmp.pdf
import com.conamobile.pdfkmp.storage.StorageLocation
import com.conamobile.pdfkmp.storage.save
import com.conamobile.pdfkmp.style.PdfColor
import com.conamobile.pdfkmp.unit.sp
// 1. Build the document
// Use `pdf { }` for a synchronous build, or `pdfAsync { }` (suspend) when
// the tree contains typed `Res.drawable.*` references — see Compose
// Multiplatform Resources below.
val document = pdf {
metadata { title = "Hello, PdfKmp" }
page {
text("Hello, world!") {
fontSize = 24.sp
bold = true
color = PdfColor.Blue
}
}
}
// 2. Pick what to do with it:
val bytes: ByteArray = document.toByteArray() // raw bytes
// or
val saved = document.save(StorageLocation.Cache, "hello.pdf") // returns SavedPdf (synchronous)
println(saved.path) // absolute filesystem path you can hand to a viewer / share intentAfter save(...) you get a SavedPdf with a real filesystem path you can hand to a PDF viewer or a share sheet — the Getting started guide has ready-made view / share / open snippets for every platform.
This README covers installation and your first document only — everything else lives on the documentation site:
📖 conamobiledev.github.io/PdfKmp
For repo conventions and AI-agent guidance see CLAUDE.md (working on this codebase) and AGENTS.md / .claude/skills/pdfkmp/SKILL.md (using the library).
Apache License 2.0 — see LICENSE.
The bundled Inter font is licensed separately under the SIL Open Font License 1.1 — see pdfkmp/fonts/Inter-LICENSE.txt.
If PdfKmp saved you some time, don't forget to click ⭐ — it helps others find the project :)
Kotlin Multiplatform PDF generator for Android, iOS, Desktop (JVM) & Web (Wasm) — vector-first, type-safe, DSL-driven.
📖 Full documentation: conamobiledev.github.io/PdfKmp — 20 hand-written guides, every sample, and the Dokka API reference.
| Android | Desktop — macOS · Windows · Linux (JVM) |
![]() |
![]() |
| iOS | Web (Kotlin/Wasm) |
![]() |
![]() |
The same brochure document rendered on Android, iOS and Desktop — pixel-identical vector output — plus the browser sample generating it live on the Web.
PdfKmp lets you build PDF documents from a Compose-style DSL that runs identically on Android, iOS, Desktop (JVM — macOS, Windows, Linux), and the browser (Kotlin/Wasm). Text becomes glyph paths, shapes become path operators — every page stays sharp at any zoom level. The library ships the Inter font for cross-platform Latin parity and exposes opt-in references to system CJK / Arabic / Persian fonts so non-Latin scripts render natively on Android and iOS (on Desktop, supply a custom font — see i18n fonts).
What's in the box:
maxLines + ellipsis, soft-hyphen + mid-word breaking, super/subscript spans, right-to-left support with bidi reorder + Arabic shaping (incl. kashida justification), and orphan/widow control.keepTogether, multi-column (columns) and uniform grid, mixed page orientations.freeDraw).<vector> and W3C <svg> (kept vector inside the PDF).See the platform feature parity table for what each backend supports.
Under the hood — native / pure-Java PDF stacks per platform:
| Platform | Backend |
|---|---|
| Android |
android.graphics.pdf.PdfDocument + android.graphics.Canvas
|
| iOS |
UIGraphicsBeginPDFContextToData + Core Graphics (CGContext) |
| Desktop (JVM) | Apache PDFBox — pure-Java, runs on macOS / Windows / Linux with no native libraries |
| Web (Wasm) |
kmpwriter — PdfKmp's own pure-Kotlin PDF 1.7 writer (no browser PDF API exists), with TrueType subsetting + Flate compression |
Every DSL node funnels into these PDF backends, so the resulting bytes are real, vector PDFs — readable in Preview, Adobe Reader, Chrome, and any spec-compliant viewer. On Desktop the embedded TrueType fonts are subsetted to only the glyphs you draw, so text stays vector and the file stays small.
PdfKmp is published to Maven Central. The library exposes:
aar (io.github.conamobiledev:pdfkmp-android),PdfKmp for arm64 (device) and simulator-arm64 (Apple-Silicon simulator),jar (io.github.conamobiledev:pdfkmp-jvm) for macOS, Windows, and Linux,io.github.conamobiledev:pdfkmp-wasm-js) — see Web (Kotlin/Wasm) for what the browser backend covers,io.github.conamobiledev:pdfkmp),io.github.conamobiledev:pdfkmp-compose-resources, with -android / -jvm / -wasm-js platform variants),io.github.conamobiledev:pdfkmp-markdown, same platform variants),io.github.conamobiledev:pdfkmp-viewer, with -android / -jvm platform variants — not on web; see below).The browser exposes no PDF engine to Wasm, so the web target renders through PdfKmp's own pure-Kotlin PDF writer — everything vector works exactly like the other platforms, custom TrueType fonts embed as subsets, and Cyrillic works out of the box via the bundled Inter. Current limits and font details: see the Web guide.
Viewing needs no library at all — browsers ship excellent PDF viewers, so hand them the bytes:
val doc = pdf { page { text("Hello, Wasm!") } }
doc.openInNewTab() // browser's own viewer
doc.save(StorageLocation.Downloads, "hello.pdf") // browser downloadMake sure Maven Central is in your repository list:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}Pick the section that matches your project. The paths are mutually exclusive — KMP projects depend on pdfkmp only (Gradle resolves the right platform variant automatically, including for Android and Desktop targets), while plain Android projects depend on pdfkmp-android and plain JVM/Desktop projects on pdfkmp-jvm. Inside each path, use whichever dependency style your project already uses; the libs.versions.toml form is recommended for new projects.
Depend on pdfkmp from commonMain. Do not add pdfkmp-android separately — Gradle picks the Android variant of pdfkmp for your Android target on its own. (Version catalogs work as usual — map the same coordinates in libs.versions.toml.)
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.conamobiledev:pdfkmp:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-compose-resources:1.2.0") // optional
implementation("io.github.conamobiledev:pdfkmp-viewer:1.2.0") // optional
}
}
}Both companions are opt-in:
pdfkmp-compose-resources — add it only if your project uses Compose Multiplatform Resources (Res.drawable.*) and you want typed resource references inside the PdfKmp DSL. Full usage in the Compose Resources guide.pdfkmp-viewer — Compose Multiplatform PDF viewer screen with topbar, search, share, save, hyperlinks, gesture-driven zoom & text selection. Skip it if you only need to generate PDFs and let users view them in their system default reader. Full usage in the viewer guide.The core pdfkmp artifact itself stays Compose-free — non-Compose consumers don't pay for either companion.
For projects that don't use Kotlin Multiplatform (plain com.android.application or com.android.library), depend on pdfkmp-android. KMP consumers should use the section above instead — they should not add this artifact.
// app/build.gradle.kts
dependencies {
implementation("io.github.conamobiledev:pdfkmp-android:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-viewer:1.2.0") // optional — Compose viewer screen
}pdfkmp-viewer brings in Compose Multiplatform — skip it if your Android project still uses the legacy View system. The viewer assumes Compose hosting (a ComponentActivity with setContent { … } or an embedded ComposeView).
KMP projects with a jvm() target need no extra setup — Gradle resolves the Desktop variant of pdfkmp for the JVM target automatically, exactly like it does for Android. For a plain JVM project (a standalone Compose for Desktop app or any non-KMP Gradle JVM module), depend on pdfkmp-jvm directly:
// build.gradle.kts
dependencies {
implementation("io.github.conamobiledev:pdfkmp-jvm:1.2.0")
implementation("io.github.conamobiledev:pdfkmp-viewer-jvm:1.2.0") // optional — Compose for Desktop viewer
}The Desktop backend is built on Apache PDFBox (pulled in transitively) and is pure-Java, so the same artifact runs on macOS, Windows, and Linux with no native libraries to bundle. The generation API (pdf { … }, document.save(...), toByteArray()) is identical across all platforms. pdfkmp-viewer on Desktop hosts the viewer in a Compose for Desktop window and rasterises pages with PDFBox's PDFRenderer.
compileSdk 34+ (Android targets)R8 is fully supported — no additional keep rules required.
import com.conamobile.pdfkmp.pdf
import com.conamobile.pdfkmp.storage.StorageLocation
import com.conamobile.pdfkmp.storage.save
import com.conamobile.pdfkmp.style.PdfColor
import com.conamobile.pdfkmp.unit.sp
// 1. Build the document
// Use `pdf { }` for a synchronous build, or `pdfAsync { }` (suspend) when
// the tree contains typed `Res.drawable.*` references — see Compose
// Multiplatform Resources below.
val document = pdf {
metadata { title = "Hello, PdfKmp" }
page {
text("Hello, world!") {
fontSize = 24.sp
bold = true
color = PdfColor.Blue
}
}
}
// 2. Pick what to do with it:
val bytes: ByteArray = document.toByteArray() // raw bytes
// or
val saved = document.save(StorageLocation.Cache, "hello.pdf") // returns SavedPdf (synchronous)
println(saved.path) // absolute filesystem path you can hand to a viewer / share intentAfter save(...) you get a SavedPdf with a real filesystem path you can hand to a PDF viewer or a share sheet — the Getting started guide has ready-made view / share / open snippets for every platform.
This README covers installation and your first document only — everything else lives on the documentation site:
📖 conamobiledev.github.io/PdfKmp
For repo conventions and AI-agent guidance see CLAUDE.md (working on this codebase) and AGENTS.md / .claude/skills/pdfkmp/SKILL.md (using the library).
Apache License 2.0 — see LICENSE.
The bundled Inter font is licensed separately under the SIL Open Font License 1.1 — see pdfkmp/fonts/Inter-LICENSE.txt.
If PdfKmp saved you some time, don't forget to click ⭐ — it helps others find the project :)