Skip to content

Working with the builder

Rivoli PDF gives you three ways to construct the same document tree, and they interoperate freely. This page is a decision guide: which style to reach for, and how to mix them without friction.

The three styles are:

  1. DocumentBuilder: a dedicated builder with WithSection/Build.
  2. Fluent helpers: extension methods like H2, AddParagraph directly on the model types.
  3. Direct POCO: set properties and call Add on plain objects.

All three produce identical trees. There is no “blessed” path. Pick whichever reads best for the code in front of you.

DocumentBuilder (namespace Rivoli.Pdf.Builders) wraps a Document and offers a declarative, top-down API that ends in Build():

using Rivoli.Pdf.Builders;
var doc = DocumentBuilder.Create("Report", "Finance")
.WithPageSize(PageSize.A4)
.WithMargin(36)
.WithDefaultStyle(s => s.WithFontFamily("Helvetica").WithFontSize(11))
.WithSection(s => s.H1("Overview").AddParagraph("..."))
.Build();

Create() has overloads for title and author; From(existingDoc) wraps a document you already have. Reach for DocumentBuilder when you want document-wide setup (page size, margins, default style) and structure in one fluent expression, and the structure is mostly fixed.

The extension methods (H1H6, AddParagraph, AddTable, Header, Row, Item, …) read naturally and chain. They’re the everyday way to add content:

var doc = Document.Create("Report");
var section = doc.AddSection();
section
.H1("Quarterly Report")
.AddParagraph("Prepared by the finance team.")
.AddTable(t => t
.Header("Quarter", "Revenue")
.Row("Q1", "$1.2M")
.Row("Q2", "$1.5M"));

Reach for the fluent helpers for concise, readable content: the bulk of normal document building.

Every type is a plain object, so you can assign properties and manipulate collections directly. This shines in loops, conditionals, and when deserializing from data, where a fluent chain would fight the control flow:

var section = doc.AddSection();
foreach (var product in products)
{
var para = new Paragraph();
para.Inlines.Add(new Run(product.Name) { Style = boldStyle });
para.Inlines.Add(new Run($", ${product.Price}"));
section.Blocks.Add(para);
}
// Fine-grained tweaks after the fact:
section.Blocks[0].StartOnNewPage = true;
section.Blocks[0].Id = "first-para";

Reach for direct POCO when you need fine-grained control, are building from loops or conditionals, or are integrating with an existing object model.

The three styles share the same types, so you switch mid-stream whenever it reads better:

// Start with the builder for setup...
var doc = DocumentBuilder.Create("My Document")
.WithPageSize(PageSize.A4)
.Build();
var section = doc.AddSection();
// ...fluent helpers for content...
section.H2("Items");
// ...direct POCO for a data-driven loop...
foreach (var item in items)
section.Blocks.Add(new Paragraph(item.Name));
// ...and a direct tweak at the end.
section.Blocks[^1].KeepWithNext = true;
Use…When
DocumentBuilderDocument-wide setup + fixed structure in one expression
Fluent helpersEveryday content; concise, readable chains
Direct POCOLoops, conditionals, deserialization, fine-grained control
  • Don’t force a single style. The library is designed for mixing; optimize each passage for readability.
  • Use DocumentBuilder for the frame, helpers for the content, POCO for the data-driven bits: a common and comfortable division.
  • For larger documents, combine any of these with document structure and components so the top level stays a clean outline.