Skip to content

Links, bookmarks & TOC

link(url) { … } wraps any content in a clickable region:

link(url = "https://github.com/conamobiledev/PdfKmp") {
    text("github.com/conamobiledev/PdfKmp") {
        color = PdfColor.Blue; underline = true
    }
}

link(url = "mailto:hello@example.com") {
    text("hello@example.com") { color = PdfColor.Blue; underline = true }
}

// Whole card clickable:
link(url = "https://example.com") {
    card(border = BorderStroke(1.dp, PdfColor.Blue)) {
        text("Visit site →") { color = PdfColor.Blue; bold = true }
    }
}

All three native platforms produce real, clickable PDF link annotations that work in any reader (Preview, Adobe Reader, Chrome): iOS via UIGraphicsSetPDFContextURLForRect, Desktop via PDFBox PDAnnotationLink, and Android via a post-processing incremental update applied in finish() (since android.graphics.pdf.PdfDocument exposes no annotation API). The post-processor is defensive: any parse surprise returns the original bytes unchanged.

anchor("id") marks a jump target; linkToAnchor(anchor = "id") { … } makes content clickable to it (forward references resolve at finish()). Both are clickable on all three native platforms (Android via the same post-processor):

anchor("intro")
text("1. Introduction") { fontSize = 22.sp; bold = true }
// … later, on another page …
linkToAnchor(anchor = "intro") {
    text("← Back to the introduction") { color = PdfColor.Blue; underline = true }
}

Bookmarks & outline

bookmark("title", level) adds an entry to the reader's outline sidebar — place it right before the heading it labels:

bookmark("Introduction")             // level 0
text("1. Introduction") { fontSize = 22.sp; bold = true }
bookmark("Motivation", level = 1)
text("1.1 Motivation") { fontSize = 16.sp; bold = true }

The outline works on all three native platforms (Android via the post-processor; iOS and Desktop natively).

Table of contents

tableOfContents() expands every bookmark into a clickable row — title, dotted leader, resolved page number — using a dry-run pagination pass so forward references (the TOC usually sits before the chapters) and the page shift the TOC itself introduces both come out correct.

page {
    text("Contents") { fontSize = 26.sp; bold = true }
    tableOfContents(maxLevel = 1)        // 0 = chapters only, 1 = + sections, …
}
page {
    bookmark("Introduction")
    text("1. Introduction") { fontSize = 22.sp; bold = true }
}

Page body only

tableOfContents() is only valid in a page body — headers, footers, and watermarks are rebuilt per physical page and cannot host one.

Web backend

The Wasm backend writes named destinations, the outline, and the info dictionary, so links / bookmarks / TOC carry over to the browser too. See Web (Kotlin/Wasm).

See also

  • Platform parity — the per-platform support matrix.
  • Samples.navigation(), Samples.pageChrome().