Control a PDF’s pages from CSS with OneRender: page size and margins with @page, a running header and “Page 1 of 5” footer in the margin boxes, forced page breaks, and a landscape page for one section.
using System.Text; using OneRender.Pdf; var rows = new StringBuilder(); for (int i = 1; i <= 60; i++) rows.Append($"<tr><td>{i:000}</td><td>Order {1000 + i}</td><td>${i * 12.5m:0.00}</td></tr>"); string html = $$""" <!DOCTYPE html> <html lang="en"> <head> <title>Quarterly report</title> <style> @page { size: A4; margin: 25mm 20mm; @top-left { content: string(chapter); font: 9pt sans-serif; color: #666 } @bottom-right { content: "Page " counter(page) " of " counter(pages); font: 9pt sans-serif } } @page :first { @top-left { content: none } } @page wide { size: A4 landscape } body { font: 11pt/1.5 serif } h1 { string-set: chapter content() } .chapter { break-before: page } .wide { page: wide } table { width: 100%; border-collapse: collapse } th { background: #eee; text-align: left } td { padding: 4px 8px; border-bottom: 1px solid #ccc } </style> </head> <body> <h1>Summary</h1> <p>The orders table starts on the next page.</p> <section class="chapter"> <h1>Orders</h1> <table> <thead><tr><th>#</th><th>Order</th><th>Total</th></tr></thead> <tbody>{{rows}}</tbody> </table> </section> <section class="chapter wide"> <h1>Appendix</h1> <p>This section is on a landscape page.</p> </section> </body> </html> """; HtmlPdfResult result = HtmlPdf.ToPdf(html, File.Create("report.pdf")); Console.WriteLine($"{result.PageCount} pages");
5 pagesPage 1 is the summary, with no header. The table starts on page 2 and continues on pages 3 and 4, with “Orders” in the top-left corner of each. Page 5 is the appendix, on a landscape A4 page. Every page has “Page n of 5” at the bottom right.
What each rule does
@page { size; margin }sets the page box. Sizes take keywords (A3,A4,A5,B4,B5,letter,legal,ledger),landscapeorportrait, or one or two lengths. The page setup inHtmlPdfOptionsis only the default for a document without@page.- Margin boxes (
@top-left,@top-center,@bottom-rightand the other 13) hold generated content: text,counter(page),counter(pages)andstring(). They are drawn as artifacts, so the running header and page numbers stay out of the PDF’s structure tree and screen readers don’t read them on every page. string-setcopies an element’s text into a named string, andstring(chapter)shows the current value on each page.:first,:left,:rightand:blankselect pages.break-before: page(alsobreak-after, and the olderpage-break-before) forces a new page.break-inside: avoidkeeps a block together.orphansandwidowsapply to paragraphs.- Named pages.
page: wideputs an element on pages of the@page widerule, starting a new page when the name changes.
Tables across pages
A table breaks between rows, and a row is never split. In 1.0.0 a table that continues onto another page keeps room for its thead and tfoot at the top and bottom of that page, but the copies don’t show, so here page 3 starts with an empty band and then row 028. If each page of a long table needs its headings, split it into one table per page yourself.
Links and tables of contents
Links to #id targets become links inside the PDF. A contents list can show page numbers with target-counter(attr(href), page) and dot leaders with leader(dotted):
.toc a::after { content: leader(dotted) target-counter(attr(href), page) }Good to know
- Footnotes,
position: running()and vertical writing modes are not implemented. - Units in
@pageare CSS units;PdfPageSetupin the options takes CSS pixels (96 per inch). - A document that would need more than
MaxPagespages throwsPdfRenderException.
The paged-media rules are in Architecture and PDF output.