Skip to main content

CSS Paged Media

Chromium has limited support for CSS Paged Media, implementing only some features like page-break. Advanced features such as position: running and content: element are not supported, which makes setting up headers and footers more complicated.

Paged.js

To work around Chromium's limitations, you can use the Paged.js library. It parses web pages and automatically handles pagination, headers/footers, and page numbers. Currently, the library is not actively maintained, but it works for most scenarios.

Example

The following example uses Paged.js to implement dynamic headers and footers. Note: header and footer must be placed before main, otherwise the footer will not work.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hi, bkhtmltopdf!</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

@page {
margin: 1em;
background: darkgreen;
}

.page-break-after {
page-break-after: always;
text-align: center;
color: white;
}

@media print {

@page {
margin: 3rem 0;
@top-center {
content: element(header);
}
@bottom-center {
content: element(footer);
}
}


header {
height: 3rem;
position: running(header);
background: darkblue;
color: gray;
}

footer {
height: 3rem;
position: running(footer);
background: darkcyan;
color: darkgray;
}

.page-number::after {
content: counter(page) ' of ' counter(pages);
}
}
</style>
</head>
<body>
<header>
<h1>
Header, <span class="page-number"></span>
</h1>
</header>
<footer>
<h1>
Footer, <span class="page-number"></span>
</h1>
</footer>
<main>
<div class="page-break-after">
<h1>Page 1</h1>
</div>
<div class="page-break-after">
<h1>Page 2</h1>
</div>
<div class="page-break-after">
<h1>Page 3</h1>
</div>
</main>

<script>
window.PagedConfig = {auto: false};
</script>
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
<script>
class MyHandler extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}

afterPreview(pages) {
console.debug('print')
}
}

Paged.registerHandlers(MyHandler);

window.addEventListener('DOMContentLoaded', () => {
window.PagedPolyfill.preview();
})
</script>
</body>
</html>

In the code example, console.debug('print') is required, and the HTML to PDF waitUntil must be set to manual; otherwise, bkhtmltopdf will keep waiting for the print action.

CSS Paged Media Example - 1 CSS Paged Media Example - 2 CSS Paged Media Example - 3

Rendering Timing Explanation

In the HTML to PDF, you must set waitUntil: "manual"; otherwise, Paged.js may not finish processing before printing starts.
Since waitUntil can be load, domcontentloaded, or manual, if you do not use manual mode, printing will start immediately after the page loads, at which point Paged.js might not have completed its work.

warning

The console.debug('print') in the example is required; it signals that Paged.js has finished previewing.