Text and paragraphs
Paragraphs and runs
Section titled “Paragraphs and runs”A paragraph is a sequence of text runs. Each run can carry its own style:
doc.Paragraph(p => p .Text("Total due: ") .Text("1,242.00 GBP", TextStyle.Default.Bold()));Chaining several Text calls inside one paragraph keeps the runs on the same logical line; separate Paragraph calls stack vertically.
Styling a run inline
Section titled “Styling a run inline”Text, Span, Link, and the page-field runs also accept fluent styling right off the call itself, as an alternative to building a TextStyle and passing it as an argument:
doc.Paragraph(p => p .Text("Total due: ") .Text("1,242.00 GBP").FontSize(14).Bold().Color(Colors.Red.Medium));FontSize, Bold, Italic, Underline, Strikethrough, Color, FontFamily, Superscript, Subscript, and LetterSpacing are only available right after a run-appending call - chaining them before any run exists, or after a non-text element such as Image(...), is a compile error rather than a runtime one.
To reuse a style across many runs, build it once and apply it with WithStyle:
TextStyle heading = TextStyle.Default.WithFontSize(20).Bold();
doc.Paragraph(p => p.Text("Chapter 1").WithStyle(heading));doc.Paragraph(p => p.Text("Chapter 2").WithStyle(heading));WithStyle’s explicit properties win over anything set earlier in the chain; properties it leaves unset fall back to whatever the run already had. The same eleven methods are available on doc.Header(...)/doc.Footer(...) runs.
Hyperlinks
Section titled “Hyperlinks”doc.Paragraph(p => p .Text("Questions? Visit ") .Link("our support page", "https://example.com/support") .Text("."));Links render as real hyperlinks in all four output formats - clickable annotations in PDF, native hyperlinks in DOCX and XLSX, and <a href> elements in HTML. An optional third parameter styles the link text.
Decorations, scripts, and tracking
Section titled “Decorations, scripts, and tracking”Beyond bold/italic/underline, runs take strikethrough, super/subscript, and letter spacing:
doc.Paragraph(p => p .Text("was 99.00 ", TextStyle.Default.WithStrikethrough()) .Span("now 79.00"));
doc.Paragraph(p => p .Text("E = mc").Span("2", TextStyle.Default.Superscript()) .Span(" H").Span("2", TextStyle.Default.Subscript()).Span("O"));
doc.Paragraph(p => p.Text("T R A C K E D", TextStyle.Default.WithLetterSpacing(1.5f)));Strikethrough and scripts are native in DOCX (w:strike, w:vertAlign) and XLSX; letter spacing maps to Word’s character spacing and has no XLSX equivalent. In PDF, script runs render at 65% size with a raised/lowered baseline and are measured at that size, so wrapping stays exact. Strikethrough and underline lines are always black and 1 unit thick, regardless of the run color.
Word wrapping
Section titled “Word wrapping”Text wraps automatically: the layout engine measures glyph widths and breaks lines greedily at whitespace, in paragraphs and in table cells (rows grow with wrapped content). Hard breaks are written as \n inside any run. Soft hyphens (\u00AD), no-break spaces, CJK line breaking, and opt-in automatic hyphenation are covered in Hyphenation and line breaking.
Alignment
Section titled “Alignment”Alignment is part of TextStyle:
doc.Paragraph(p => p.Text("Centered heading", TextStyle.Default.WithFontSize(18f).AlignCenter()));LEFT, CENTER, RIGHT, and JUSTIFY are available in all four output formats - in PDF, CENTER/RIGHT are positioned exactly per line and JUSTIFY distributes the slack over each line’s whitespace. With no alignment set, text sits against the side its writing direction starts on (see right-to-left text).
Paragraph layout
Section titled “Paragraph layout”Beyond per-run text styling, each paragraph takes a ParagraphStyle with block-level layout options - spacing before/after, line spacing, indentation, and a background fill:
doc.Paragraph(p => p.Text("Roomy, indented, 1.5-spaced text …"), ParagraphStyle.Default .WithSpacingBefore(12f) // points above the paragraph .WithSpacingAfter(6f) // points below .WithLineSpacing(1.5f) // multiple of the font size (built-in: 1.2) .WithIndentLeft(20f) .WithIndentRight(20f) .WithIndentFirstLine(15f) // extra indent on the first line .WithBackgroundColor(Colors.Yellow.Lighten3));The same options are also available chained directly after Paragraph(...), as an alternative to passing a ParagraphStyle argument:
doc.Paragraph(p => p.Text("Roomy, indented, 1.5-spaced text …")) .SpacingBefore(12f) .SpacingAfter(6f) .LineSpacing(1.5f) .IndentLeft(20f) .IndentRight(20f) .IndentFirstLine(15f) .BackgroundColor(Colors.Yellow.Lighten3);Unlike the per-run chain, this one applies to the most recently added block rather than being gated by a narrower return type - Paragraph(...) still returns the same builder every other block method does, so calling it before any paragraph exists, or right after a non-paragraph block, throws at compose time (the same convention as Heading()/KeepTogether()). A whole ParagraphStyle built elsewhere can be applied with WithStyle(preset), same fallback semantics as TextRunBuilder.WithStyle.
Inside table cells, lists, rows, and header/footer bands the same options apply via the builder: Cell(p => p.Style(new ParagraphStyle(…)).Text("…")). A document-wide default is set once with doc.DefaultParagraphStyle(…) - per-paragraph values override it property by property, exactly like DefaultTextStyle.
Per-format behavior:
- PDF - everything renders literally: extra leading between wrapped lines, exact indent geometry, a background rectangle (translucent colors supported).
- DOCX - native
w:spacing,w:ind, and paragraph shading, so Word shows and keeps the options when editing. Translucent backgrounds are blended to white (DOCX shading has no alpha). - XLSX - the background becomes the cell fill and left indentation maps to Excel’s character-based cell indent (≈9pt per step); spacing and line spacing have no worksheet equivalent and are dropped.
Two boundaries to know: a paragraph background inside a table cell is ignored - the cell’s CellStyle.BackgroundColor owns that fill. And spacing applies to paragraphs only; vertical space between other blocks (tables, rows) still needs a spacer paragraph.
Right-to-left text
Section titled “Right-to-left text”Arabic, Hebrew and mixed text need no configuration: the layout engine runs the Unicode bidirectional algorithm over each paragraph, so a Hebrew phrase inside an English sentence is laid out right-to-left in place, and digits and punctuation between the two land on the side the algorithm says they belong to.
What you do have to say is the paragraph’s base direction when its own text can’t tell you - an Arabic paragraph opening with a Latin product name or an invoice number would otherwise be read as left-to-right:
doc.Paragraph(p => p.Text("Papyra - فاتورة"), ParagraphStyle.Default.WithDirection(TextDirection.RightToLeft));TextDirection.Auto (the default) takes the direction from the paragraph’s first strong character; LeftToRight and RightToLeft state it outright. The base direction also decides which edge text sits against when no alignment is set - a right-to-left paragraph starts at the right margin. An explicit AlignLeft()/AlignRight() always wins.
Shaping comes with it: kerning pairs and ligatures are applied in PDF, and Arabic letters take their initial, medial, final or isolated forms from the font’s own substitution tables.
Three things to know:
- The built-in fonts cover Hebrew but not Arabic. Register an Arabic font with
doc.RegisterFont(...)and set it as the family - see fonts. - Copying right-to-left text out of a PDF gives you visual order. A PDF content stream stores glyphs in the order they are painted, so a naive extractor reads a right-to-left run backwards. That is true of PDFs in general; readers that implement bidi extraction recover the original order.
- DOCX, XLSX and HTML hand the direction to the reader -
w:bidi, Excel’s reading order, and thedirattribute - and Word, Excel and the browser run the algorithm themselves.AUTOwrites nothing at all, which is exactly their own default behavior.
The explicit Unicode formatting controls (LRE, RLE, LRO, RLO, PDF and the isolates LRI, RLI, FSI, PDI) are ignored. They exist to override the algorithm from inside the text; ParagraphStyle.Direction is how you do that here.
Where styles come from
Section titled “Where styles come from”Each run’s style merges with the document’s DefaultTextStyle - unset properties fall through; paragraph-level options merge with DefaultParagraphStyle the same way. Details in styling and colors.
© 2026 Stefan Nikolei. Payments are processed by Paddle.com as merchant of record.