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

MethodBehavior
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();
MethodWeight 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");
});
MethodOutput 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.");
});
MethodDescription
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 tagNameDescription
ligaStandard ligaturesfi, fl, ffi ligatures (on by default)
onumOldstyle figuresFigures with varying heights
lnumLining figuresUniform-height figures (default)
tnumTabular figuresFixed-width figures for tables
smcpSmall capitalsLowercase letters as small caps
ss01ss20Stylistic setsFont-specific alternate glyph sets
kernKerningPair-wise glyph spacing (on by default)
fracFractionsDiagonal 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

FamilyWeights includedLicense
LatoThin, Light, Regular, Bold, Black (+ italic variants)OFL 1.1
InterThin 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();
    });