Skip to content

Lines, page breaks, and pagination

A horizontal rule across the content width:

doc.Line(); // default thickness and color
doc.Line(thickness: 2f, color: Colors.Teal.Medium);

Lines are a quick way to separate a letterhead from the body, or totals from the line items.

width limits the line’s length (in points) and alignment positions it within the content width - the classic use is the short rule above an invoice’s grand total:

doc.Line(2f, width: 220f, alignment: TextAlignment.Right);
doc.Line(1f, width: 100f, alignment: TextAlignment.Center);

width must be positive (it is clamped to the content width), and JUSTIFY is rejected. Per-format behavior:

  • PDF draws the line at its exact position and length.
  • DOCX renders the line as a paragraph bottom border between matching left/right indents - native Word behavior, exact position and length.
  • XLSX approximates: the line becomes a bottom border on the grid columns its box overlaps, so its edges snap to the sheet’s column boundaries.

style switches the stroke pattern - LineStyle.Solid (default), DASHED, or DOTTED:

doc.Line(1f, style: LineStyle.Dashed);
doc.Line(2f, Colors.Blue.Medium, width: 200f, style: LineStyle.Dotted);

PDF strokes the dash pattern directly (3× thickness on / 2× off for dashed, thickness-sized square dots for dotted); DOCX uses the native dashed/dotted paragraph border values; HTML emits the CSS border-style keyword. XLSX maps the line’s bottom-border approximation onto Excel’s dashed/dotted border styles - the thickness-based weight is dropped for dotted, which Excel only offers in one weight.

Force the following content onto a new page:

doc.Paragraph(p => p.Text("End of summary."))
.PageBreak()
.Paragraph(p => p.Text("Appendix A"));

Page breaks apply to paginated output - PDF and DOCX. XLSX ignores them (a spreadsheet has no pages).

Automatic pagination - content flowing onto new pages when it runs out of space - happens regardless; PageBreak is for the places where you want the break, not where the space forces one.

Two chaining methods control where the automatic breaks may fall. Both apply to the most recently added top-level block (paragraph, table, row, list, image, or line):

doc.Paragraph(h => h.Text("Positions"))
.KeepWithNext() // the heading stays with the table's first row
.Table(t => ...);
doc.Table(t => ...)
.KeepTogether(); // the whole table moves to the next page instead of splitting
  • KeepWithNext() binds the block to the start of the following block. Chains are transitive - three consecutive KeepWithNext() blocks move as one unit. The flag is inert on the last element and before an explicit PageBreak() (the break wins).
  • KeepTogether() prevents the block from being split across pages. Paragraphs, images, rows, and lines are never split anyway; for tables and lists the flag suppresses the row/item splitting when the block fits on one page.

Content is never lost to these rules: a block or chain taller than one full page degrades gracefully - the chain places its members individually (still keeping the last pair together when that pair fits a page), and an oversized keep-together table or list falls back to normal row/item splitting.

Per-format behavior:

  • PDF applies the rules exactly during its pagination pass.
  • DOCX emits the native properties (w:keepNext, w:keepLines, w:cantSplit) and lets Word paginate. Word has no whole-table keep, so a table’s KeepTogether() is approximated as cantSplit per row plus a keepNext chain through its rows. KeepWithNext() on a multi-paragraph block sets keepNext on all of its paragraphs, which also binds the block internally - consistent with the PDF behavior, where such a block moves as one unit.
  • XLSX has no pages and ignores both flags.