Auto-pagination
By default, renderToPdf automatically splits content across multiple pages when it overflows. This is the simplest way to create multi-page documents.
How it works
val pdf = renderToPdf(config = PdfPageConfig.A4WithMargins) {
// Each direct child is a "keep-together" unit
ReportHeader()
DataTable(items) // won't be split across pages
SummarySection() // pushed to next page if needed
}
- Your content is laid out in a tall virtual scene
- Direct children are measured as “keep-together” units
- If a child would straddle a page boundary, padding is inserted to push it to the next page
- A single child taller than a page flows continuously across pages
Keep-together units
The library treats direct children of the content block as keep-together units. This means:
renderToPdf {
// GOOD: Each section is a direct child -- can be kept together
Section1()
Section2()
Section3()
}
renderToPdf {
// BAD: Single Column wraps everything -- no page-break decisions possible
Column {
Section1()
Section2()
Section3()
}
}
Place content items as direct children rather than wrapping in a single Column. The library can only keep direct children together.
Using PaginatedColumn inside wrappers
If you need to wrap content in a provider, theme, or layout container, use the public PaginatedColumn composable inside the wrapper to restore per-child page breaking:
renderToPdf(config = PdfPageConfig.A4WithMargins) {
MyThemeProvider { // auto-pagination sees 1 child
PaginatedColumn { // restores per-child page breaking
Section1() // keep-together unit
Section2() // keep-together unit
Section3() // keep-together unit
}
}
}
PaginatedColumn reads the page configuration automatically from the composition — no parameters needed beyond modifier and content.
Oversized children
If a single child is taller than the page content area, it flows continuously across pages rather than being truncated:
renderToPdf(config = PdfPageConfig.A4WithMargins) {
// This 2000dp element spans ~3 pages (A4 content height is 698dp)
Spacer(Modifier.fillMaxWidth().height(2000.dp))
}
Disabling auto-pagination
To clip content to a single page (pre-0.2.0 behavior), pass PdfPagination.SINGLE_PAGE:
val pdf = renderToPdf(pagination = PdfPagination.SINGLE_PAGE) {
// Content beyond the page boundary is clipped
Text("Only what fits on one page is visible")
}
Fallback behavior
Auto-pagination falls back to single-page rendering when:
- Content fits on one page – no pagination needed
- Content uses
fillMaxHeight()– the layout fills the entire measurement scene, so the library can’t determine the natural content height
In both cases, you get a single-page PDF identical to PdfPagination.SINGLE_PAGE.
Page limit
Auto-pagination supports up to 100 pages. If your content requires more pages, the output is truncated at 100 pages and a warning is logged. For very large documents, consider splitting content into batches.
Streaming output
For large auto-paginated documents, use the streaming variant to avoid holding the final PDF bytes in memory:
FileOutputStream("report.pdf").use { out ->
renderToPdf(out, config = PdfPageConfig.A4WithMargins) {
repeat(50) { index ->
DataRow(items[index])
}
}
}
When to use auto vs manual pagination
| Auto-pagination | Manual pagination | |
|---|---|---|
| Best for | Flowing content (reports, lists, articles) | Fixed-layout pages (cover + data + summary) |
| Page breaks | Automatic – elements kept together | You decide what goes on each page |
| Headers/footers | Supported via header/footer slots |
Use Spacer(Modifier.weight(1f)) pattern |
| Page count | Determined automatically | Must be known upfront |
fillMaxHeight() |
Falls back to single page | Works as expected |
Headers and footers
Add a repeated header and/or footer band to every page with the header and footer
slots. Both receive a PdfPageInfo with pageIndex (zero-based), pageCount, and a
convenience pageNumber (one-based):
val pdf = renderToPdf(
config = PdfPageConfig.A4WithMargins,
header = {
Row(Modifier.fillMaxWidth().background(Color(0xFF1565C0)).padding(10.dp)) {
Text("Acme Corp", color = Color.White, fontWeight = FontWeight.Bold)
}
},
footer = { info ->
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Text("Page ${info.pageNumber} of ${info.pageCount}", fontSize = 9.sp)
}
},
) {
// body content — auto-paginated between the bands
}
How the space works
- Bands render inside the page margins, anchored about 0.25in (18pt) from the physical page edge — the same convention as a browser’s print header/footer or a word processor’s “header from edge” setting.
- Adding a header/footer does not move or shrink your body content: the body keeps
using your configured
marginsas long as the band (edge inset + band height + a small ~10pt gap to the body) fits within that margin. Only a band too tall for its margin pushes the body inward to make room.LocalPdfPageConfig(and thereforePaginatedColumn) always reflects this effective content area, which equals your configured margins in the common case. - Each slot’s height is measured once and is stable on every page — content
taller than the measured band is clipped. Measurement uses a
pageCount = 2sentinel, so a footer wrapped inif (info.pageCount > 1) { ... }still reserves its space. - A header + footer that leave no room for content throw
IllegalArgumentException. - Slots work in both
VECTORandRASTERmodes, withPdfPagination.SINGLE_PAGE, and on single-page documents (pageCount == 1). PdfLinkworks inside slots.- Want a large masthead only on page 1? Keep it in the body content — the
headerslot is for the repeated band. If pagination truncates at the 100-page cap,pageCountreflects the emitted pages.
See also
- API Reference: PaginatedColumn – Public composable for fine-grained page breaks
- Multi-page Documents – Manual pagination
- API Reference: renderToPdf – Full API signatures
- Best Practices – Tips for auto-pagination