compose2pdf — Kotlin PDF Library for Compose Desktop
Generate production-quality PDFs from Compose Desktop content — vector text, embedded fonts, auto-pagination, and server-side streaming. A Kotlin JVM library that turns your @Composable functions into PDF documents.
Compose in, PDF out — pixel-perfect
Write standard @Composable functions. Get production-quality PDFs.
| Compose (reference render) | PDF (vector output) |
|---|---|
![]() | ![]() |
A performance report with KPI cards, bar charts, and data tables — the PDF output is virtually identical to the Compose reference.
Three lines of code
val pdfBytes = renderToPdf {
Text("Hello, PDF!")
}
File("hello.pdf").writeBytes(pdfBytes)
That’s it. renderToPdf takes a @Composable lambda and returns a ByteArray.
Real-world output
| Detailed Invoice | Professional Invoice |
|---|---|
![]() | ![]() |
| Fidelity test fixture (PDF) | Example walkthrough |
Why compose2pdf?
Use the same Compose layout primitives you already know – Column, Row, Box, Text, Canvas – and get a production-quality PDF.
Vector PDFs
Text is selectable, scales to any zoom level, and produces small files. Powered by Skia’s SVGCanvas and Apache PDFBox.
Raster Fallback
When you need pixel-perfect rendering – complex gradients, visual effects, or exact bitmap output – switch to raster mode with one parameter.
Font Embedding
Ships with bundled Inter fonts (Regular, Bold, Italic, BoldItalic). Fonts are automatically subsetted and embedded so PDFs look the same everywhere.
Compose-Native API
No new DSL to learn. Write @Composable functions, pass them to renderToPdf(), get a ByteArray. Works with all standard Compose layout, text, shape, and drawing APIs.
Features at a Glance
| Feature | Description |
|---|---|
| Vector output | Selectable text, crisp at any zoom, small file size |
| Raster fallback | Pixel-perfect rendering as an embedded image |
| Font embedding | Bundled Inter fonts or system font resolution with automatic subsetting |
| Link annotations | Clickable URLs in the PDF via PdfLink |
| Auto-pagination | Content automatically flows across pages; elements kept together at boundaries |
| Multi-page | Render multiple pages in a single PDF (automatic or manual) |
| Streaming output | Write PDFs directly to an OutputStream for Ktor, servlets, or files |
| Page presets | A4, Letter, A3 with configurable margins and landscape support |
| Shapes | Backgrounds, borders, clips, rounded corners, Canvas drawing |
| Images | Embed bitmap images with clipping and layout |
How it works
compose2pdf renders your Compose content through a Skia SVGCanvas → Apache PDFBox pipeline. Compose Desktop’s layout engine runs your composables, Skia captures the draw calls as SVG, and compose2pdf converts that to vector PDF commands with embedded fonts and link annotations.
Want native PDF output from Skia? Skiko PR #775 proposes adding a direct PDF backend to Skia/Skiko. This would eliminate the SVG intermediary — faster rendering, smaller files, and full gradient/effect support in vector mode. If this matters to you, upvote the PR!
Requirements
- JDK 17 or later
- Kotlin 2.x
- Compose Multiplatform (Desktop) 1.9+



