Getting Started¶
Learn how to open a PDF, display it in the UI, and extract its content with KitePDF: a pure-Kotlin multiplatform PDF engine.
Step 1: Add the dependency¶
KitePDF is published to Maven Central as four artifacts. Start with the core headless engine; add the Compose viewer and renderers only when you need them.
The core kitepdf artifact has one dependency: kotlin-stdlib. It works on every Kotlin target (JVM, Android, Native, JS, wasmJs).
Step 2: Open your first PDF¶
Get PDF bytes from a file, network, or resources; the method depends on your platform.
JVM / Desktop:
import java.io.File
import io.github.yuroyami.kitepdf.KitePDF
val bytes = File("sample.pdf").readBytes()
val doc = KitePDF.open(bytes)
println("${doc.pageCount} pages")
println("PDF version ${doc.version}")
Android:
import io.github.yuroyami.kitepdf.KitePDF
// From resources or assets
val bytes = context.resources.openRawResource(R.raw.sample).readBytes()
val doc = KitePDF.open(bytes)
// Or from a file
val bytes = File(context.cacheDir, "sample.pdf").readBytes()
val doc = KitePDF.open(bytes)
Password-protected PDFs:
import io.github.yuroyami.kitepdf.PdfDocument
val doc = PdfDocument.open(bytes, "secret".encodeToByteArray())
Step 3: Show it on screen in Compose¶
The kitepdf-compose artifact provides PdfView: a Compose-Multiplatform viewer supporting pinch zoom, paging, continuous scroll, and more.
Add the dependency:
dependencies {
commonMain.dependencies {
implementation("io.github.yuroyami:kitepdf-compose:0.1.0")
}
}
Display the whole document (vertical continuous scroll):
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import io.github.yuroyami.kitepdf.PdfDocument
import io.github.yuroyami.kitepdf.compose.PdfView
@Composable
fun PdfViewer(doc: PdfDocument) {
PdfView(document = doc, modifier = Modifier.fillMaxSize())
}
Show a single page with fit-to-viewport letterboxing:
@Composable
fun SinglePageView(doc: PdfDocument) {
PdfView(
document = doc,
page = 0, // zero-based index
modifier = Modifier.fillMaxSize()
)
}
Full control over layout, zoom, and rendering:
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import io.github.yuroyami.kitepdf.PdfDocument
import io.github.yuroyami.kitepdf.compose.*
@Composable
fun AdvancedViewer(doc: PdfDocument) {
val state = rememberPdfViewState(doc)
PdfView(
state = state,
modifier = Modifier.fillMaxSize(),
layout = PdfLayout.Paged(Orientation.Horizontal),
zoomSpec = PdfZoomSpec(minZoom = 0.5f, maxZoom = 6f),
renderSpec = PdfRenderSpec.Rasterized(
quality = 1f,
maxBitmapLongSide = 4096,
rerasterizeOnZoom = true,
preserveHairlines = true
),
colors = PdfViewColors(
pageBackground = Color.White,
viewportBackground = Color(0xFF1E1E1E)
),
overlay = { state ->
PdfNavigationControls(state, Modifier.align(Alignment.BottomCenter))
}
)
}
Tip
Use PdfRenderSpec.Rasterized() (the default) for scrolling performance and memory efficiency. Switch to PdfRenderSpec.Vectorized() for resolution-independent, bitmap-free rendering: best for simple pages or when memory is tight.
Step 4: Read text out of it¶
Extract text from any page without displaying it:
For geometry-aware extraction (to build selection rectangles or search highlights), use structured text:
val structured = page.structuredText
// structured.blocks → lines → spans, each with bounds
for (block in structured.blocks) {
println("Block: ${block.bounds}")
for (line in block.lines) {
println(" Line: ${line.text}")
for (span in line.spans) {
println(" Span: ${span.text} at ${span.bounds}")
}
}
}
Step 5: Render a page to PNG¶
Export a page as a PNG (or JPEG) without showing the UI. The kitepdf-native-renderer artifact provides headless rasterization using the JDK's built-in AWT + ImageIO.
Add the dependency:
Render and encode:
import io.github.yuroyami.kitepdf.nativerenderer.AwtPdfRasterizer
import java.io.File
val page = doc.pages[0]
// Render at 2× scale (e.g. for print or high-DPI screenshots)
val pngBytes = AwtPdfRasterizer.encodeToPng(page, scale = 2.0)
File("page-0.png").writeBytes(pngBytes)
// Or JPEG
val jpegBytes = AwtPdfRasterizer.encodeToJpeg(page, scale = 1.0)
File("page-0.jpg").writeBytes(jpegBytes)
You can also grab the BufferedImage directly:
Note
The native-renderer artifact works on JVM, Android, and macOS. For other platforms (JS, wasm, iOS), use the Skia renderer (kitepdf-skia) or render through Compose (kitepdf-compose).
Where to next?¶
- Compose Viewer: zoom gestures, paging modes, navigation overlays, and thumbnails.
- Editing & Writing: modify PDFs programmatically (form fields, metadata, incremental updates).
- Recipes: common patterns: search text, extract images, parse annotations, batch processing.