Text & Typography
Render simple text, rich multi-span paragraphs, page numbers, hyperlinks, and styled typography with full HarfBuzz shaping, OpenType features, and bidirectional support.
Simple Text
The simplest way to display text is the .Text(string) extension on any IContainer. It creates a single-span text block.
col.Item().Text("Hello, World!");
// Chained styling
col.Item().Text("Bold heading").FontSize(24).Bold();
col.Item().Text("Subtle note").FontSize(9).FontColor(Color.Grey).Italic();
The .Text(object?) overload calls ToString() on the argument, so you can pass numbers, dates, and other types directly:
col.Item().Text(DateTime.Now).FontSize(10);
col.Item().Text(42.5m).Bold();
Rich Text (Multi-Span)
For paragraphs with mixed styles, use the .Text(Action<TextDescriptor>) overload. Each Span() or Line() call adds a separately styled segment.
col.Item().Text(t =>
{
t.DefaultTextStyle(TextStyle.Default.WithFontSize(12));
t.Span("FolioPDF ").Bold();
t.Span("is a ");
t.Span("high-performance").Italic().FontColor(Color.Blue);
t.Span(" PDF library for .NET.");
});
Line vs. Span
| Method | Behavior |
|---|---|
t.Span("text") | Inline segment — next span flows on the same line |
t.Line("text") | Inline segment followed by a line break |
t.EmptyLine() | Inserts a blank line |
col.Item().Text(t =>
{
t.Line("First line of the paragraph.");
t.Line("Second line with a break after it.");
t.EmptyLine();
t.Span("Third line after a blank gap.");
});
Text Span Styling
Every Span(), Line(), and page-number method returns a TextSpanDescriptor with a complete set of fluent style methods.
Font Size, Weight, and Style
t.Span("Title").FontSize(24).Bold();
t.Span("Subtitle").FontSize(18).SemiBold();
t.Span("Body text").FontSize(12).NormalWeight();
t.Span("Caption").FontSize(9).Light();
t.Span("Emphasis").Italic();
t.Span("Strong emphasis").Bold().Italic();
| Method | Weight value |
|---|---|
Thin() | 100 |
ExtraLight() | 200 |
Light() | 300 |
NormalWeight() | 400 |
Medium() | 500 |
SemiBold() | 600 |
Bold() | 700 |
ExtraBold() | 800 |
Black() | 900 |
ExtraBlack() | 1000 |
Color
t.Span("Red text").FontColor(Color.Red);
t.Span("Hex color").FontColor(Color.FromHex("#1A73E8"));
t.Span("RGBA").FontColor(Color.FromRGBA(0, 128, 0, 200));
// Background highlight behind text
t.Span("Highlighted").BackgroundColor(Color.Yellow);
Decorations
t.Span("Underlined").Underline();
t.Span("Struck through").Strikethrough();
t.Span("Overlined").Overline();
// Decoration styling
t.Span("Wavy red underline")
.Underline()
.DecorationColor(Color.Red)
.DecorationWavy()
.DecorationThickness(1.5f);
// Decoration styles: Solid, Double, Wavy, Dotted, Dashed
t.Span("Dashed").Underline().DecorationDashed();
t.Span("Dotted").Underline().DecorationDotted();
Font Family
// Single font
t.Span("Inter text").FontFamily("Inter");
// Fallback chain (first family with the glyph wins)
t.Span("With fallback").FontFamily("CustomFont", "Inter", "Lato");
Subscript and Superscript
Position text above or below the baseline for scientific notation, footnotes, and mathematical expressions.
col.Item().Text(t =>
{
t.Span("H");
t.Span("2").Subscript();
t.Span("O is the chemical formula for water.");
});
col.Item().Text(t =>
{
t.Span("E = mc");
t.Span("2").Superscript();
});
Letter Spacing and Word Spacing
Fine-tune horizontal spacing between glyphs and between words.
// Wider letter spacing (tracking)
col.Item().Text("S P A C E D").FontSize(14).LetterSpacing(2);
// Tighter letter spacing
col.Item().Text("Tight").FontSize(14).LetterSpacing(-0.5f);
// Extra word spacing
col.Item().Text("Words with extra space between them").WordSpacing(5);
Line Height
Control the vertical distance between lines of text. A value of 1.0 uses the font's default line height; 1.5 adds 50% extra space.
col.Item().Text(t =>
{
t.DefaultTextStyle(TextStyle.Default.WithLineHeight(1.6f));
t.Span("This paragraph has 1.6x line height for improved readability. " +
"Long blocks of body text are easier to read with generous leading.");
});
Page Numbers
Insert dynamic page numbers into text blocks. These are resolved at render time across all pages.
// Basic page counter
page.Footer().AlignCenter().Text(t =>
{
t.Span("Page ");
t.CurrentPageNumber();
t.Span(" of ");
t.TotalPages();
});
// Section-relative numbering
col.Item().Section("appendix");
col.Item().Text(t =>
{
t.Span("Appendix page ");
t.PageNumberWithinSection("appendix");
t.Span(" of ");
t.TotalPagesWithinSection("appendix");
});
| Method | Output example |
|---|---|
CurrentPageNumber() | "5" |
TotalPages() | "24" |
BeginPageNumberOfSection("ch2") | "8" (first page of section "ch2") |
EndPageNumberOfSection("ch2") | "15" (last page of section "ch2") |
PageNumberWithinSection("ch2") | "3" (third page within the section) |
TotalPagesWithinSection("ch2") | "8" (section spans 8 pages) |
Section References
Combine sections and section links to create a table of contents with clickable entries.
// Define sections in the content
col.Item().Section("intro").Text("Introduction").FontSize(20).Bold();
col.Item().Text("Introduction content...");
col.Item().Section("methods").Text("Methods").FontSize(20).Bold();
col.Item().Text("Methods content...");
// Table of contents (on a separate page)
doc.Page(page =>
{
page.Content().Column(toc =>
{
toc.Item().Text("Table of Contents").FontSize(24).Bold();
toc.Spacing(8);
toc.Item().SectionLink("intro").Row(r =>
{
r.AutoItem().Text("Introduction");
r.RelativeItem().AlignRight().Text(t =>
{
t.BeginPageNumberOfSection("intro");
});
});
toc.Item().SectionLink("methods").Row(r =>
{
r.AutoItem().Text("Methods");
r.RelativeItem().AlignRight().Text(t =>
{
t.BeginPageNumberOfSection("methods");
});
});
});
});
Hyperlinks in Text
Add clickable links inside rich text blocks.
col.Item().Text(t =>
{
t.Span("Visit our website at ");
t.Hyperlink("documentation", "https://truespar.com/folio/docs")
.FontColor(Color.Blue).Underline();
t.Span(" for more information.");
t.EmptyLine();
t.Span("Jump to ");
t.SectionLink("appendix-a", "appendix-a")
.FontColor(Color.Blue).Underline();
t.Span(" in this document.");
});
Text Alignment
Control horizontal text alignment within a TextDescriptor.
col.Item().Text(t =>
{
t.AlignCenter();
t.Span("This paragraph is centered.");
});
col.Item().Text(t =>
{
t.AlignRight();
t.Span("Right-aligned text.");
});
col.Item().Text(t =>
{
t.Justify();
t.Span("This justified paragraph stretches each line to fill the full " +
"available width, creating clean left and right edges.");
});
| Method | Description |
|---|---|
AlignLeft() | Left edge (default for LTR) |
AlignCenter() | Centered |
AlignRight() | Right edge |
AlignStart() | Start edge in current writing direction |
AlignEnd() | End edge in current writing direction |
Justify() | Lines stretch to fill width |
Paragraph Controls
Control spacing between paragraphs and first-line indentation within a TextDescriptor.
col.Item().Text(t =>
{
t.ParagraphSpacing(8);
t.ParagraphFirstLineIndentation(20);
t.Line("First paragraph. Lorem ipsum dolor sit amet, consectetur " +
"adipiscing elit. Sed do eiusmod tempor incididunt.");
t.Line("Second paragraph. Ut enim ad minim veniam, quis nostrud " +
"exercitation ullamco laboris nisi ut aliquip.");
});
// Line clamping (truncate after N lines)
col.Item().Text(t =>
{
t.ClampLines(3);
t.Span("This very long text will be truncated after three lines...");
});
HarfBuzz Text Shaping
FolioPDF uses HarfBuzz for OpenType text shaping, which is active by default. This ensures correct glyph selection, ligature substitution, and positioning for complex scripts including Arabic, Devanagari, Thai, and other scripts that require contextual glyph shaping.
// Arabic text shaped correctly with HarfBuzz
col.Item().ContentFromRightToLeft()
.Text("مرحبا بالعالم").FontSize(18).FontFamily("Noto Sans Arabic");
// Devanagari (Hindi)
col.Item().Text("नमस्ते दुनिया").FontSize(18).FontFamily("Noto Sans Devanagari");
OpenType Font Features
Enable or disable specific OpenType feature tags on individual text spans.
col.Item().Text(t =>
{
// Enable old-style figures
t.Span("Price: 1,234.56").EnableFontFeature("onum");
t.EmptyLine();
// Enable small caps
t.Span("Small Caps Text").EnableFontFeature("smcp");
t.EmptyLine();
// Enable stylistic alternates
t.Span("Stylistic set 1").EnableFontFeature("ss01");
t.EmptyLine();
// Disable standard ligatures (normally on by default)
t.Span("fi fl ffi ffl").DisableFontFeature("liga");
});
| Feature tag | Name | Description |
|---|---|---|
liga | Standard ligatures | fi, fl, ffi ligatures (on by default) |
onum | Oldstyle figures | Figures with varying heights |
lnum | Lining figures | Uniform-height figures (default) |
tnum | Tabular figures | Fixed-width figures for tables |
smcp | Small capitals | Lowercase letters as small caps |
ss01–ss20 | Stylistic sets | Font-specific alternate glyph sets |
kern | Kerning | Pair-wise glyph spacing (on by default) |
frac | Fractions | Diagonal fractions (e.g. 1/2 → ½) |
RTL and Bidirectional Text
FolioPDF resolves text direction automatically from the Unicode script of the content. For mixed-direction text, use explicit direction overrides on individual spans.
// Automatic direction detection
col.Item().Text(t =>
{
t.Span("Hello ").DirectionFromLeftToRight();
t.Span("مرحبا").DirectionFromRightToLeft();
t.Span(" World").DirectionFromLeftToRight();
});
// Page-level RTL
page.ContentFromRightToLeft();
// Auto-detect (default behavior)
col.Item().Text(t =>
{
t.Span("Auto-detected direction").DirectionAuto();
});
Font Management
FolioPDF ships two font families out of the box: Lato and Inter. These are bundled in the NuGet package and auto-discovered at startup. Register additional fonts using FontManager.
using FolioPDF.Drawing;
// Register from file
FontManager.RegisterFont("/path/to/CustomFont-Regular.ttf");
FontManager.RegisterFont("/path/to/CustomFont-Bold.ttf");
// Register from stream
using var stream = File.OpenRead("MyFont.otf");
FontManager.RegisterFont(stream);
// Register from embedded resource
FontManager.RegisterFontFromEmbeddedResource("MyApp.Fonts.Roboto-Regular.ttf");
// Register with a custom name (overrides the embedded family name)
using var s = File.OpenRead("SpecialFont.ttf");
FontManager.RegisterFontWithCustomName("BrandFont", s);
// Use the registered font
col.Item().Text("Custom font text").FontFamily("CustomFont");
col.Item().Text("Brand identity").FontFamily("BrandFont");
Note: The FontManager auto-discovers .ttf, .otf, and .ttc files from all directories listed in Settings.FontDiscoveryPaths. Add custom directories to this list before the first render call. After auto-discovery runs, use RegisterFont() for runtime additions.
Default Fonts
| Family | Weights included | License |
|---|---|---|
Lato | Thin, Light, Regular, Bold, Black (+ italic variants) | OFL 1.1 |
Inter | Thin through Black (100–900) | OFL 1.1 |
Inline Elements
Embed non-text content (images, icons, boxes) within a text flow using the Element() method inside a TextDescriptor.
col.Item().Text(t =>
{
t.Span("Status: ");
t.Element(el =>
{
el.Width(12).Height(12).Background(Color.Green).CornerRadius(6);
}, TextInjectedElementAlignment.AboveBaseline);
t.Span(" Active");
});
Default Text Style
Apply a default style to all text spans within a block or within an entire container subtree.
// Block-level default
col.Item().Text(t =>
{
t.DefaultTextStyle(TextStyle.Default
.WithFontSize(11)
.WithFontFamily("Inter")
.WithColor(Color.FromHex("#333")));
t.Span("This inherits the default. ");
t.Span("This overrides font size.").FontSize(16);
});
// Container-level default (applies to all nested Text elements)
col.Item().DefaultTextStyle(TextStyle.Default.WithFontSize(10).WithColor(Color.Grey))
.Column(inner =>
{
inner.Item().Text("All text here is 10pt grey.");
inner.Item().Text("This too, unless overridden.");
inner.Item().Text("Override!").FontSize(14).Bold();
});