Skip to content

4. Headers, footers, page layout

Page geometry, running headers and footers, and multi-page layout.

Up to now you have been adding content and letting the page take care of itself. This chapter is about the page itself: how big it is, which way up it sits, how many columns the text flows into, what runs along the top and bottom of every page, and how you control where one page ends and the next begins.

The unit that owns all of this is the section. A Section (namespace Rivoli.Pdf.Model) is a run of pages that share one page setup. Change the geometry and you start a new section; everything inside a section gets the same size, margins, columns, and header/footer.

A section inherits the document defaults unless it overrides them. Leave PageSize or Margin null and the section uses Document.DefaultPageSize / Document.DefaultMargin; set them and the override applies to that section only.

using Rivoli.Pdf;
using Rivoli.Pdf.Model;
var doc = Document.Create("Field Report", "Survey Team");
doc.DefaultPageSize = PageSize.A4;
doc.DefaultMargin = Margin.Standard; // 1 inch (72 pt) all round
var body = doc.AddSection(); // inherits A4 + Standard
body.AddHeading("Summary");
var appendix = doc.AddSection();
appendix.PageSize = PageSize.Legal; // this section only
appendix.Margin = Margin.Narrow; // 0.5 inch
appendix.AddHeading("Raw measurements");

PageSize is a readonly record struct of width and height in points. The named sizes are Letter, Legal, A3, A4, A5, and Tabloid; build a custom size with PageSize.FromInches(w, h) or PageSize.FromMillimeters(w, h). Margin offers None, Narrow (36 pt), Standard (72 pt), Wide (108 pt), plus Margin.All(x) and Margin.Symmetric(horizontal, vertical).

PageSize.Rotate() returns the same size with width and height swapped. That is all “landscape” means: a wider-than-tall page:

var charts = doc.AddSection();
charts.PageSize = PageSize.A4.Rotate(); // 842 x 595 instead of 595 x 842
charts.AddHeading("Trends");

A section flows its body into one or more columns. ColumnLayout is a readonly record struct of (ColumnCount, ColumnGap) in points, with named presets and factory methods:

var news = doc.AddSection();
news.WithColumns(ColumnLayout.TwoColumns); // 2 columns, 36 pt gap
news.WithColumns(ColumnLayout.Create(3)); // 3 columns, default gap
news.WithColumns(ColumnLayout.Create(2, 24)); // 2 columns, 24 pt gap
MemberLayout
ColumnLayout.OneColumn1 column (the default)
ColumnLayout.TwoColumns2 columns, 36 pt gap
ColumnLayout.ThreeColumns3 columns, 24 pt gap
ColumnLayout.FourColumns4 columns, 18 pt gap
ColumnLayout.Create(n)n columns, gap of 36 / n pt
ColumnLayout.Create(n, gap)n columns, explicit gap

WithColumns returns the section, so it chains. You can also set the property directly: news.ColumnLayout = ColumnLayout.TwoColumns;.

Running headers and footers are configured per section through a HeaderFooterConfig, which carries Header and Footer objects. Each Header or Footer is a small container with its own Blocks collection and an optional Height: you fill it with the same blocks you use in the body.

using Rivoli.Pdf.Builders;
var report = doc.AddSection();
report.WithHeaderFooter(new HeaderFooterConfig
{
Header = (Header)new Header().Add(new Paragraph("Quarterly Report")),
Footer = (Footer)new Footer().Add(
new Paragraph().Add("Confidential")),
});

HeaderFooterConfig also lets you vary the running content by page position: useful for two-sided print layouts and a distinct cover page:

MemberWhen it applies
Header / FooterDefault for every page in the section
FirstPageHeader / FirstPageFooterOverrides on the section’s first page
EvenPageHeader / EvenPageFooterOverrides on even pages
OddPageHeader / OddPageFooterOverrides on odd pages

The read-only HasFirstPageDifference and HasEvenOddDifference flags report whether any of those overrides are set.

The model carries intent about breaking; the layout engine makes the actual decision. Four controls cover the common cases.

Force a break. Add a PageBreak block: the following content starts on a fresh page:

section.AddParagraph("End of part one.");
section.AddPageBreak(); // or section.Add(new PageBreak());
section.AddHeading("Part two");

Start a block on a new page. Every block carries StartOnNewPage; set it and the block begins a page rather than continuing the current one:

var chapter = new Paragraph("Chapter 2") { StartOnNewPage = true };
section.Add(chapter);

Keep a block intact. KeepTogether asks the layout engine not to split a block across a page boundary: push the whole thing to the next page rather than break it:

section.Add(new Table { KeepTogether = true }
.Header("Metric", "Value")
.Row("Latency", "12 ms"));

Keep two blocks adjacent. KeepWithNext binds a block to the one after it, so a heading never strands at the bottom of a page away from its paragraph:

section.Blocks[^1].KeepWithNext = true; // e.g. a heading + its first paragraph

Paragraphs additionally honour WidowControl and OrphanControl (both default to 2 lines) so the engine avoids leaving a single line alone at the top or bottom of a page.