Styling via extension methods
C# extension methods let you add fluent, discoverable shortcuts to types you don’t
own, including Rivoli PDF’s Section, Style, Paragraph, and the rest. This is how
you give your codebase a vocabulary that reads like part of the library: section.H2(...),
para.Branded(), style.AsHeading().
Rivoli PDF ships several of these itself, which is the clearest illustration of the pattern.
The built-in extensions are the model
Section titled “The built-in extensions are the model”The fluent helpers you’ve already used are extension methods on the model types, grouped by the type they extend:
SectionExtensions:H1(...)…H6(...),AddParagraph(...),AddUnorderedList(...),AddOrderedList(...),AddTable(...),AddImage(...),AddSpace(...),AddPageBreak().ListExtensions:Item(...),Items(...).TableExtensions:Header(...),Row(...),Rows(...).DocumentExtensions:WithSection(...),WithTitle(...), and friends.
They follow one shape: take the receiver as this, do the work, return the
receiver so calls chain.
// from the library, paraphrasedpublic static Section H2(this Section section, string text) => AddHeading(section, text, 2);
public static Section AddParagraph(this Section section, string text, Action<Style> configure){ var style = new Style(); configure(style); section.Add(new Paragraph(text) { Style = style }); return section;}Because they return Section, they chain with everything else:
section .H1("Quarterly Report") .AddParagraph("Prepared by the finance team.") .AddParagraph("Confidential", s => s.Italic().WithForegroundColor(Color.Gray)) .AddPageBreak();Write your own style shortcuts
Section titled “Write your own style shortcuts”The highest-value extensions in your own code are usually on Style: they capture
your brand as named, reusable formatting:
using Rivoli.Pdf.Styling;
public static class BrandStyles{ private static readonly Color Accent = Color.FromRgb(0, 90, 160);
public static Style Branded(this Style style) => style.WithFontFamily("Helvetica").WithForegroundColor(Accent);
public static Style AsHeading(this Style style, double size) => style.Branded().WithFontSize(size).Bold();
public static Style AsCaption(this Style style) => style.WithFontSize(9).Italic().WithForegroundColor(Color.Gray);}Now styling reads as intent, and the brand lives in one file:
var heading = new Style().AsHeading(20);var caption = new Style().AsCaption();Content shortcuts on the model types
Section titled “Content shortcuts on the model types”You can also extend the content types to encode house conventions: a branded paragraph, a standard signature line:
public static class DocExtensions{ public static Section Branded(this Section section, string text) => section.AddParagraph(text, s => s.Branded());
public static Section SignatureLine(this Section section, string name) { section.AddParagraph("_____________________________"); section.AddParagraph(name, s => s.AsCaption()); return section; }}
// usagesection .Branded("Acme Corporation") .SignatureLine("Authorized signatory");Extension methods vs. components
Section titled “Extension methods vs. components”Both reduce repetition, but they answer different questions:
- An extension method adds a verb: a fluent shortcut
that mutates or configures something in place (
section.Branded(...),style.AsHeading(...)). - A component produces a noun: a self-contained
Blockyou place wherever you want (section.Add(Callout(...))).
Use extensions for fluent styling and small content shortcuts; use components for reusable structural pieces. They compose well together: a component’s internals are often built with your own style extensions.
Guidelines
Section titled “Guidelines”- Group by receiver type in a
static class(SectionExtensions,StyleExtensions): mirrors how the library organizes its own. - Always return the receiver so your extensions chain with the built-in fluent API.
- Keep one namespace for your extensions so a single
usingbrings the whole vocabulary into scope. - Don’t shadow the built-in helpers; pick distinct, intent-revealing names.