Blocks
One-column pages to Skia PDF
IBlock instances live in PagedDocument.Body, nested chapter builders, column slots, and LastPage.Blocks. Layout walks them top-to-bottom in a single content column (except ColumnsBlock, which splits one row).
Catalog
| Type | Purpose |
|---|---|
| `HeadingBlock` | Level 1–3 titles |
| `ParagraphBlock` | Body prose |
| `TableBlock` | Grid of string cells |
| `ColumnsBlock` | Side-by-side block streams |
| `ImageBlock` | Raster/SVG bytes or path |
| `TextBoxBlock` | Bordered text panel (notes / panels); splits by line across pages |
| `CodeBlock` | Monospace filled panel; optional line numbers + colored spans; splits by line across pages |
| `SceneBreakBlock` | Ornamental break between scenes |
| `LineBreakBlock` | Forced blank body line |
| `PageBreakBlock` | Force a new page in the current region (Body / First / Last) |
| `BlankPageBlock` | Emit an intentionally empty page |
Fluent verbs live on DocumentContentBuilder / nested builders — see authoring.md.
HeadingBlock
new HeadingBlock { Level = 1, Text = "Arrival" }| Level | Fluent | Layout |
|---|---|---|
| 1 | `Chapter` / `H1` | Page break when prior content exists; TOC entry; chapter-title header source |
| 2 | `H2` | Continues flow |
| 3 | `H3` | Continues flow |
Prefer Chapter for level 1 so authoring and TOC semantics stay obvious.
ParagraphBlock
new ParagraphBlock { Text = "The river ran cold." }Plain text only. No inline markup in v1.
TableBlock
new TableBlock
{
Headers = ["#", "Item", "Amount"],
Rows =
[
["1", "Widget", "10,00"],
["2", "Gadget", "20,00"],
],
ColumnWidths = [0.1f, 0.6f, 0.3f],
ColumnAlignments = [CellAlign.Left, CellAlign.Left, CellAlign.Right],
Rules = TableRuleStyle.Horizontal,
HeaderBackground = true,
RepeatHeaderOnPageBreak = true,
}| Property | Meaning |
|---|---|
| `Headers` | Optional header row strings |
| `ShowHeader` | When true (default) and headers exist, paint the header row |
| `Rows` | `IReadOnlyList<IReadOnlyList<string>>` |
| `ColumnWidths` | Fractions summing ~1.0; omit for equal columns |
| `ColumnAlignments` | Per-column `CellAlign` (`Left` / `Center` / `Right`) |
| `Rules` | `None`, `Horizontal`, `Grid` |
| `HeaderBackground` | Light fill behind header row |
| `RepeatHeaderOnPageBreak` | Re-draw header when the table spans pages |
Page breaks: Layout splits tables between rows when they no longer fit the content box. Continuation slices keep column widths/alignments/rules. With RepeatHeaderOnPageBreak (default true), each continuation redraws the header row. Rows are never split mid-cell.
Non-goal: nested blocks inside cells. Cells are strings.
ColumnsBlock
new ColumnsBlock
{
Gap = Length.FromPoints(16),
Fractions = [0.5f, 0.5f],
Columns =
[
[new HeadingBlock { Level = 3, Text = "From" }, new ParagraphBlock { Text = "…" }],
[new HeadingBlock { Level = 3, Text = "To" }, new ParagraphBlock { Text = "…" }],
],
}Used for invoice party rows, side-by-side payment/summary, etc. Columns share one vertical band; tallest column wins height.
ImageBlock
new ImageBlock
{
Path = @"C:\path\logo.svg", // or Bytes = […]
Width = Length.FromPoints(120),
Height = Length.FromPoints(40),
}Skia paints rasters and SVG (via Svg.Skia). Prefer explicit size so layout can reserve space before decode.
TextBoxBlock
Bordered panel of plain lines. Domain-agnostic (callouts, notes, sidebars — consumers supply the lines).
new TextBoxBlock
{
Lines = ["2497.110 17:40", "System Y982283", "Earth Fleet battlecruiser, Quartermaster's office"],
PaddingPt = 6f,
BorderStrokePt = 0.8f,
BorderColor = DocumentColor.Gray,
Background = DocumentColor.LightGray,
FontSizePt = 8.5f,
LineHeight = 1.22f,
LineGapPt = 1.5f,
TextColor = DocumentColor.Gray,
}Fluent: .TextBox(t => t.Lines("…").Padding(6f).Border(0.8f).Background(DocumentColor.LightGray).Font(8.5f)).
Layout splits by line when the box no longer fits the page (each slice keeps the same border/fill options).
SceneBreakBlock / PageBreakBlock / BlankPageBlock
| Block | Behavior |
|---|---|
| `SceneBreakBlock` | Small ornamental gap (centered dots/line depending on paint) |
| `PageBreakBlock` | Force next content onto a new body page |
| `BlankPageBlock` | Intentionally empty body page (still receives header/footer per includes) |
Choosing blocks for invoices vs books
Same block types, different composition:
- Trade sample: First → Toc → Chapters → Last
- Invoice: often First omitted; Content with logo image, columns (seller/buyer), table (lines), columns (payment/totals) — layout language stays domain-agnostic