Skip to content

1. Building blocks

The DOM, sections, and paragraphs: the primitives every document is built from.

Every Rivoli PDF document is a tree. You assemble it in memory, then hand it to the writer to produce a PDF. The tree has four levels, and almost everything you do in this tutorial is adding nodes at one of them:

Document
└── Section (a run of pages with shared page setup)
└── Block (paragraphs, headings, tables, lists, images, page breaks)
└── Inline (runs of text, links, inline images, inside paragraphs)

If you want the full reference for these types, read The document model. This chapter is the hands-on version: we’ll build a small document from the root down.

Document (namespace Rivoli.Pdf) is the root. Create one empty, or with metadata:

using Rivoli.Pdf;
using Rivoli.Pdf.Model;
var doc = new Document(); // empty
var titled = Document.Create("My Report"); // title only
var full = Document.Create("My Report", "Jane Doe"); // title + author

The document also carries the defaults that its sections inherit: page size and margins:

var doc = Document.Create("My Report", "Jane Doe");
doc.DefaultPageSize = PageSize.A4; // also Letter, Legal, A3
doc.DefaultMargin = Margin.Standard; // 1 inch on all sides

PageSize.A4.Rotate() gives you landscape. A section that doesn’t set its own page size or margin falls back to these document defaults, so set them once at the top and forget about them.

A Section is a run of pages that share page setup: page size, margins, columns, header/footer, and background. Most documents need only one. You reach for a second section when part of the document needs different page setup: a landscape appendix after a portrait body, say.

var section = doc.AddSection(); // creates one, appends it, returns it
var appendix = doc.AddSection();
appendix.PageSize = PageSize.Letter.Rotate(); // landscape, this section only

AddSection() both creates and appends. If you’ve built a Section separately, doc.Add(section) appends the one you already have.

A Paragraph is the workhorse block: a run of inline content that flows and wraps. The fluent helper is AddParagraph:

using Rivoli.Pdf.Builders;
section.AddParagraph("This is a paragraph. It wraps to fit the page width.");

AddParagraph returns the Section, so calls chain:

section
.AddParagraph("First paragraph.")
.AddParagraph("Second paragraph.")
.AddParagraph("Third paragraph.");

For richer paragraphs (mixed formatting, links, inline images), build the Paragraph yourself and Add it. Paragraphs compose inline content fluently:

section.Add(new Paragraph()
.Add("Visit our ")
.AddLink("https://example.com", "website")
.Add(" for the full report."));

AddLink(target, text) accepts an external URL or a #anchor that points at another node in the same document.

Headings are just paragraphs with larger, bolder styling. Rivoli PDF gives you H1 through H6, plus the size-by-number form AddHeading:

section
.H1("Annual Report")
.H2("Executive Summary")
.AddParagraph("...")
.H2("Financial Results")
.H3("Revenue");

The level controls the font size for you:

HelperLevelFont size (pt)
H1124
H2220
H3316
H4414
H5512
H6610

AddHeading(text) is the same as H1(text); AddHeading(text, level) takes the level as a number, which is handy when it’s computed in a loop. Levels outside 1–6 throw, so clamp computed values before passing them.

You override the built-in heading styling per heading the same way you style any paragraph. See Styling and typography.

To open up space between blocks, add a spacer with AddSpace:

section
.AddParagraph("Above the gap.")
.AddSpace() // 12 points by default
.AddParagraph("Below the gap.")
.AddSpace(36) // a larger, explicit gap
.AddParagraph("Further down.");

AddSpace inserts an empty paragraph whose trailing space equals the height you ask for. For finer control over the space around a single block, set paragraph spacing in its style instead.

When the tree is complete, write it with SavePdf from Rivoli.Pdf:

using Rivoli.Pdf;
doc.SavePdf("document.pdf"); // PDF 1.4 by default
doc.SavePdf("document.pdf", PdfVersion.Version17);
await doc.SavePdfAsync("document.pdf");

Putting the whole chapter together:

using Rivoli.Pdf;
using Rivoli.Pdf.Builders;
using Rivoli.Pdf.Model;
var doc = Document.Create("Annual Report", "Jane Doe");
doc.DefaultPageSize = PageSize.A4;
doc.DefaultMargin = Margin.Standard;
var section = doc.AddSection();
section
.H1("Annual Report")
.AddParagraph("Prepared for the board of directors.")
.AddSpace()
.H2("Overview")
.AddParagraph("Revenue and headcount both grew this year.");
doc.SavePdf("annual-report.pdf");

Everything above used the fluent helpers, but every type is a plain C# object. The same document can be built by setting properties and manipulating collections directly:

var para = new Paragraph("Built without helpers");
section.Blocks.Add(para);
section.Blocks[0].StartOnNewPage = true;

Both styles produce the same tree and mix freely. Use whichever reads better for the code in front of you. Working with the builder covers the trade-offs, plus the higher-level DocumentBuilder entry point.