Skip to content

Recipes

Short solutions to common tasks. Each one links to the page that explains it in full.

Reading

Open a PDF with a password

val doc = PdfDocument.open(bytes, password = "secret")
println("${doc.pageCount} pages")

A wrong password throws KiteWrongPasswordException. See Encrypted PDFs.

Get the text of a page

val text = doc.pages[0].extractText()

See Text.

Read the metadata and the outline

println("Title: ${doc.info.title}")
println("Author: ${doc.info.author}")

for (item in doc.outline) {
    println("- ${item.title}")
    for (child in item.children) {
        println("  - ${child.title}")
    }
}

See Metadata and Bookmarks.

Read the form fields

for (field in doc.formFields) {
    println("${field.fullyQualifiedName}: ${field.value}")
}

// One field by its name
val email = doc.formField("email")?.value

See Form fields.

Check the permissions

if (!doc.permissions.canPrint) {
    println("The document asks not to be printed")
}

KitePDF does not enforce the permissions. See Permissions.

Viewing in Compose

Show a whole document

@Composable
fun PdfViewer(doc: PdfDocument) {
    KiteDocView(
        state = rememberKiteDocViewState(doc),
        modifier = Modifier.fillMaxSize(),
    )
}

The pages scroll down, and pinch zoom works with no setup. See Compose viewer.

Show one page

KiteDocView(doc, page = 0, modifier = Modifier.fillMaxWidth())

Turn pages with your own buttons

@Composable
fun PdfPager(doc: PdfDocument) {
    val state = rememberKiteDocViewState(doc)
    val scope = rememberCoroutineScope()

    Column(Modifier.fillMaxSize()) {
        KiteDocView(
            state = state,
            modifier = Modifier.weight(1f),
            layout = KiteDocLayout.Paged(Orientation.Horizontal),
            pageSpacing = 12.dp,
        )
        Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = { scope.launch { state.previousPage() } }) { Text("Previous") }
            Text("${state.currentPage + 1} / ${state.pageCount}")
            Button(onClick = { scope.launch { state.nextPage() } }) { Text("Next") }
        }
    }
}

See Navigation and Layouts.

Control the zoom from your own UI

@Composable
fun ZoomControls(state: KiteDocViewState) {
    val scope = rememberCoroutineScope()
    Row {
        Button(onClick = { scope.launch { state.animateZoomTo(2f) } }) { Text("2x") }
        Button(onClick = { state.resetZoom() }) { Text("Reset") }
    }
}

To turn off every zoom gesture, pass zoomSpec = KiteZoomSpec.Disabled. See Zoom and pan.

Creating and editing

Make a new PDF

val bytes = PdfBuilder()
    .setInfo(title = "Invoice", author = "MyApp")
    .page {
        text(StandardFont.HelveticaBold, 24.0, 36.0, 750.0, "Invoice 2024-001")
        text(StandardFont.Helvetica, 12.0, 36.0, 720.0, "Customer: Jane Doe")
    }
    .build()

A page is US Letter (612 × 792 points) unless you pass a size. See Creating PDFs.

Add a watermark to every page

val editor = doc.edit()
for (page in doc.pages) {
    editor.stampPage(page) {
        setFillRgb(0.9, 0.1, 0.1)
        text(StandardFont.Helvetica, 48.0, 200.0, 400.0, "DRAFT")
    }
}
val bytes = editor.saveIncremental()

See Stamps and watermarks.

Fill in a text field

val field = requireNotNull(doc.formField("full_name"))
val editor = doc.edit()
editor.setTextFieldValue(field, "Jane Doe")
val bytes = editor.saveIncremental()

See Forms.

Change the metadata

val editor = doc.edit()
editor.setInfo(title = "New Title", author = "New Author")
val bytes = editor.saveIncremental()

setInfo changes only the values that you pass.

Redact a region

Warning

Save with saveRewritten() after a redaction. An incremental save keeps the redacted content in the file.

val editor = doc.edit()
editor.redactRegion(doc.pages[0], KiteRectangle(left = 100.0, bottom = 500.0, right = 300.0, top = 550.0))
val bytes = editor.saveRewritten()

See Redaction.

Rendering

Render every page to a PNG file

val doc = PdfDocument.open(bytes)
for ((index, page) in doc.pages.withIndex()) {
    val png = AwtPdfRasterizer.encodeToPng(page, scale = 1.0)
    File("page_${index + 1}.png").writeBytes(png)
}

AwtPdfRasterizer runs on the JVM with the JDK alone. See Headless rendering.

Make a thumbnail

// A quarter of the size in each direction
val png = AwtPdfRasterizer.encodeToPng(doc.pages[0], scale = 0.25)