Document structure & composition
A real document (a report, a contract, a multi-page invoice) is too big to build
in one method without it becoming an unreadable wall of .Add(...) calls. This
pattern keeps large documents legible: compose the whole from small, named
pieces, each responsible for one part.
Build the skeleton, fill the parts
Section titled “Build the skeleton, fill the parts”Start with a top-level method that lays out the document’s shape and delegates the
content of each part to a helper that returns a Block (a
component) or configures a Section.
using Rivoli.Pdf;using Rivoli.Pdf.Model;
public Document BuildReport(ReportData data){ var doc = Document.Create(data.Title, data.Author); doc.DefaultPageSize = PageSize.A4; doc.DefaultMargin = Margin.Standard;
BuildCover(doc, data); BuildSummary(doc, data); BuildFinancials(doc, data); BuildAppendix(doc, data);
return doc;}The top-level method now reads like a table of contents. Each Build* helper owns
one concern:
private void BuildCover(Document doc, ReportData data){ var cover = doc.AddSection(); cover.H1(data.Title); cover.AddParagraph(data.Author); cover.AddPageBreak();}
private void BuildSummary(Document doc, ReportData data){ var section = doc.AddSection(); section.H2("Executive summary"); section.AddParagraph(data.Summary);}(H1, H2, AddParagraph, and AddPageBreak are built-in
section extensions.)
One section per logical part
Section titled “One section per logical part”A Section is the natural unit of
composition: it owns its own page size, margins, columns, header/footer, and
background. Mapping one section to one logical part of the document means each part
can have its own page setup without affecting the others.
private void BuildAppendix(Document doc, ReportData data){ var appendix = doc.AddSection(); appendix.PageSize = PageSize.Letter.Rotate(); // landscape, appendix only appendix.WithColumns(ColumnLayout.TwoColumns); appendix.H2("Appendix: detailed data"); // ...}Compose with the declarative builder
Section titled “Compose with the declarative builder”For documents whose structure is fixed, the WithSection/WithSections extensions
read top-down without intermediate variables:
using Rivoli.Pdf.Builders;
var doc = Document.Create("Report", "Team") .WithSection(cover => cover.H1("Report").AddPageBreak()) .WithSection(summary => summary.H2("Summary").AddParagraph(text)) .WithSection(body => BuildBody(body, data));Each lambda is a small, self-contained description of one section, and you can
still hand off to a named helper (BuildBody) when a section gets complex. See
Working with the builder for choosing between these styles.
Keep structure and content separate
Section titled “Keep structure and content separate”The most maintainable layout puts structure in one place and content rules in another:
- Structure: the
BuildReportmethod: which sections exist, in what order, with what page setup. - Content: the per-part helpers and components: what each part actually contains.
- Style: a shared
StyleSheetor a set of style extension methods, referenced by both.
When the brand changes, you touch the styles. When the report grows a section, you touch the structure method. When a section’s wording changes, you touch one helper. Each kind of change lives in one place.
Guidelines
Section titled “Guidelines”- One method per logical part, named for the part (
BuildFinancials, notBuildSection3). - Pass the document or section in, return nothing: or return a
Blockand let the caller place it (the component style). Pick one and be consistent within a document. - Hoist page setup to the top. Set
DefaultPageSize/DefaultMarginon the document once; override per section only where it genuinely differs.
Next: Local helper methods for keeping the individual Build*
methods themselves tidy.