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.
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.
The console.debug('print')
in the example is required; it signals that Paged.js has finished previewing.