Table of Contents

Class Paragraph

Namespace
Egami.Fonts.Layout
Assembly
Egami.Fonts.dll

A paragraph of styled text, prepared once to be laid out at any width: styles resolved, bidi levels and break opportunities found, text itemized into runs and each run shaped. Immutable and safe to share between threads.

public sealed class Paragraph
Inheritance
Paragraph
Inherited Members

Examples

// Font families come from a collection; later families cover what the first lacks.
FontCollection fonts = new();
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSans-Regular.ttf"));
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSansArabic-Regular.ttf"));
fonts.TryGetFamily("Noto Sans", out FontFamily latin);
fonts.TryGetFamily("Noto Sans Arabic", out FontFamily arabic);

// Styles nest: the inner span keeps the outer one's size and adds a color and an underline.
RichText text = new RichTextBuilder()
    .Append("Peace is ")
    .Push(new TextStyle { Size = 20 })
    .Append("\u0633\u0644\u0627\u0645 ")
    .Push(new TextStyle { Color = new Rgba8(200, 30, 30, 255), Decoration = new TextDecoration() })
    .Append("in Arabic")
    .Pop()
    .Pop()
    .AddPlaceholder(new Placeholder(16, 16, PlaceholderAlignment.Middle))
    .ToRichText();

ParagraphStyle style = new()
{
    DefaultStyle = new TextStyle { FontFamilies = [latin, arabic], Size = 16, LineHeight = LineHeight.Multiple(1.4f) },
    Align = TextAlign.Justify,
    LineBreaking = new LineBreakOptions(WordBreak: WordBreak.Normal),
};

// Shaped once: lay it out at any width later.
Paragraph paragraph = Paragraph.Create(text, style);

Remarks

Runs split where the bidi level, the script, the font (after fallback) or a style field that changes shaping changes; paint fields never split a run. Hard breaks, other control characters and placeholders are runs of their own without glyphs.

Properties

RichText

Gets the styled text the paragraph was made from.

public RichText RichText { get; }

Property Value

RichText

Style

Gets the paragraph style.

public ParagraphStyle Style { get; }

Property Value

ParagraphStyle

Text

Gets the text.

public string Text { get; }

Property Value

string

Methods

Create(RichText, ParagraphStyle)

Prepares a paragraph of styled text.

public static Paragraph Create(RichText text, ParagraphStyle style)

Parameters

text RichText

The styled text.

style ParagraphStyle

The paragraph style; text and spans take unset fields from its DefaultStyle.

Returns

Paragraph

The paragraph.

Examples

// Font families come from a collection; later families cover what the first lacks.
FontCollection fonts = new();
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSans-Regular.ttf"));
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSansArabic-Regular.ttf"));
fonts.TryGetFamily("Noto Sans", out FontFamily latin);
fonts.TryGetFamily("Noto Sans Arabic", out FontFamily arabic);

// Styles nest: the inner span keeps the outer one's size and adds a color and an underline.
RichText text = new RichTextBuilder()
    .Append("Peace is ")
    .Push(new TextStyle { Size = 20 })
    .Append("\u0633\u0644\u0627\u0645 ")
    .Push(new TextStyle { Color = new Rgba8(200, 30, 30, 255), Decoration = new TextDecoration() })
    .Append("in Arabic")
    .Pop()
    .Pop()
    .AddPlaceholder(new Placeholder(16, 16, PlaceholderAlignment.Middle))
    .ToRichText();

ParagraphStyle style = new()
{
    DefaultStyle = new TextStyle { FontFamilies = [latin, arabic], Size = 16, LineHeight = LineHeight.Multiple(1.4f) },
    Align = TextAlign.Justify,
    LineBreaking = new LineBreakOptions(WordBreak: WordBreak.Normal),
};

// Shaped once: lay it out at any width later.
Paragraph paragraph = Paragraph.Create(text, style);

Exceptions

ArgumentNullException

text or style is null.

ArgumentException

Some text has no font families in its style or the default style.

Create(string, ParagraphStyle)

Prepares a plain-text paragraph in the paragraph's default style.

public static Paragraph Create(string text, ParagraphStyle style)

Parameters

text string

The text.

style ParagraphStyle

The paragraph style; its DefaultStyle must name font families.

Returns

Paragraph

The paragraph.

Examples

// Font families come from a collection; later families cover what the first lacks.
FontCollection fonts = new();
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSans-Regular.ttf"));
fonts.AddFile(System.IO.Path.Combine(folder, "NotoSansArabic-Regular.ttf"));
fonts.TryGetFamily("Noto Sans", out FontFamily latin);
fonts.TryGetFamily("Noto Sans Arabic", out FontFamily arabic);

// Styles nest: the inner span keeps the outer one's size and adds a color and an underline.
RichText text = new RichTextBuilder()
    .Append("Peace is ")
    .Push(new TextStyle { Size = 20 })
    .Append("\u0633\u0644\u0627\u0645 ")
    .Push(new TextStyle { Color = new Rgba8(200, 30, 30, 255), Decoration = new TextDecoration() })
    .Append("in Arabic")
    .Pop()
    .Pop()
    .AddPlaceholder(new Placeholder(16, 16, PlaceholderAlignment.Middle))
    .ToRichText();

ParagraphStyle style = new()
{
    DefaultStyle = new TextStyle { FontFamilies = [latin, arabic], Size = 16, LineHeight = LineHeight.Multiple(1.4f) },
    Align = TextAlign.Justify,
    LineBreaking = new LineBreakOptions(WordBreak: WordBreak.Normal),
};

// Shaped once: lay it out at any width later.
Paragraph paragraph = Paragraph.Create(text, style);

Exceptions

ArgumentNullException

text or style is null.

ArgumentException

No font families are given.

CreateFlow()

Starts laying the paragraph out one line at a time, each at its own width.

public LineFlow CreateFlow()

Returns

LineFlow

A flow positioned at the first line.

Examples

// One line at a time, each at its own width: two columns of four lines, the second wrapping around an image.
LineFlow flow = paragraph.CreateFlow();
List<TextLine> first = [], second = [];
while (first.Count < 4 && flow.NextLine(150) is TextLine line)
{
    first.Add(line);
}

while (flow.NextLine(second.Count < 2 ? 90 : 150) is TextLine line)
{
    second.Add(line);                                     // narrower beside the image
}

GetStyle(int)

Gets the text style a run with StyleIndex (or a decoration with StyleIndex) was laid out in: the default style for 0, else that span's style with the default's values filled in where it sets none.

public TextStyle GetStyle(int styleIndex)

Parameters

styleIndex int

0 for the default style, else 1 plus the span's index in Spans.

Returns

TextStyle

The style.

Exceptions

ArgumentOutOfRangeException

styleIndex is not a style of this paragraph.

Layout(float)

Lays the paragraph out at a width.

public TextLayout Layout(float width)

Parameters

width float

The layout box's width in canvas units; PositiveInfinity for no wrapping, in which case the box is as wide as the longest line.

Returns

TextLayout

The layout.

Examples

ParagraphStyle style = new() { DefaultStyle = new TextStyle { FontFamilies = [family], Size = 16 } };
Paragraph paragraph = Paragraph.Create("A paragraph is shaped once and laid out at any width.", style);

// Each width gives an immutable layout: lines of glyph runs, positioned from the box's top-left corner.
TextLayout narrow = paragraph.Layout(120);
TextLayout wide = paragraph.Layout(400);
foreach (TextLine line in narrow.Lines)
{
    string text = paragraph.Text[line.Start..line.End];   // the line's text, its trailing space included
    float baseline = line.Baseline;                        // y of the baseline
    foreach (LayoutRun run in line.Runs)
    {
        GlyphRun? glyphs = run.Glyphs;                     // glyphs at their layout positions
    }
}

// No wrapping: the box is as wide as the longest line.
TextLayout unbounded = paragraph.Layout(float.PositiveInfinity);

Exceptions

ArgumentOutOfRangeException

width is negative or not a number.