TextLayout Class

A text laid out: its lines, its glyphs and their positions. It is what DrawingCanvas.FillText draws, and what measures a text before it is drawn.

public sealed class TextLayout

Remarks

A layout is immutable and can be used by several threads at the same time, and drawn any number of times. Creating it does all the work that depends on the text: drawing it allocates nothing.

Paragraphs and lines. A paragraph ends at a line break of the text: a line feed, a carriage return (alone or followed by a line feed), a vertical tab, a form feed, a next line (U+0085), a line separator (U+2028) or a paragraph separator (U+2029); a line break at the end of the text starts an empty line. With a TextLayoutOptions.MaxWidth, the lines of a paragraph are wrapped where the Unicode line breaking algorithm (UAX #14) allows it, at soft hyphens and, on request, where hyphenation patterns allow it. The white space that ends a wrapped line hangs outside its width.

Direction. Each paragraph is ordered by the Unicode Bidirectional Algorithm (UAX #9): left-to-right and right-to-left text can be mixed, and each line is put in display order. A vertical tab, a form feed and a line separator (U+2028) end a line but not the direction of the paragraph: with TextDirection.Auto, the text after them takes the direction of the text before them, and the next direction is found after the other line breaks.

Geometry. Positions are in pixels from the top-left corner of the layout, with the y axis down. A line is as tall as its fonts ask (the largest ascent and descent, each with half of the line gap), or as TextLayoutOptions.LineHeight says. Glyphs are placed at fractions of a pixel and their outlines are scaled exactly, unless TextLayoutOptions.Hinting fits them to the pixel grid.

Shaping. The text is shaped with the OpenType Layout tables of its fonts, one run per script, direction and font: characters are put in the form the font has glyphs for (composed or decomposed), the GSUB table substitutes glyphs (ligatures, contextual forms) and the GPOS table positions them (kerning, marks), with the features a client applies by default to horizontal text and the ones of the style; a font without GPOS kerning is kerned by its kern table. A change of paint or decoration inside a word does not interrupt shaping.

A character is drawn with the first font of the stack of its style that has it; a character no font has is drawn with the missing glyph of the primary font and reported by TextLayout.HasMissingGlyphs. Control characters are not drawn, format characters are invisible, and a tabulation moves to the next tabulation stop.

A layout is validated when it is created: the outline of every glyph is decoded once, so drawing a layout only fails when a limit of the image is reached or when the operation is canceled.

Properties

NameDescription
BaselineGets the distance from the top of the layout to the baseline of its first line, in pixels. In a vertical layout, it is the distance from the left of the layout to the central baseline of its first line, which is its rightmost or its leftmost one.
HasMissingGlyphsGets a value indicating whether a character of the text has no glyph in the fonts of its style.
HeightGets the height of the layout, in pixels: the sum of the heights of its lines, or the height of the tallest line of a vertical layout.
InkBoundsGets a rectangle that contains everything the layout draws when it is filled, relative to its top-left corner; empty when the layout draws nothing.
IsTruncatedGets a value indicating whether a part of the text is not in the layout: it did not fit TextLayoutOptions.MaxLines or TextLayoutOptions.MaxHeight, or a line was trimmed to TextLayoutOptions.MaxWidth.
LineCountGets the number of lines.
LinesGets the lines, from the top: the part of the text each one holds and where it is. The lines of a vertical layout are its columns, from the first one: the rightmost one or the leftmost one.
PlaceholdersGets the rectangle of each placeholder of the text (RichTextBuilder.AppendPlaceholder), in the order of the text, from the top-left corner of the layout: where the caller draws what the placeholder makes room for, once the origin the layout is drawn at is added.
ScaleGets what the sizes of the styles were multiplied by to lay out the text: 1, unless the layout was created by TextLayout.CreateToFit, which then gives the factor that makes the text fit its box.
WidthGets the width of the text, in pixels: the width of its widest line, with the indent of a first line.

Gets the distance from the top of the layout to the baseline of its first line, in pixels. In a vertical layout, it is the distance from the left of the layout to the central baseline of its first line, which is its rightmost or its leftmost one.

public double Baseline { get; }

Property value

double

HasMissingGlyphs

TextLayout.cs

Gets a value indicating whether a character of the text has no glyph in the fonts of its style.

public bool HasMissingGlyphs { get; }

Property value

bool

Gets the height of the layout, in pixels: the sum of the heights of its lines, or the height of the tallest line of a vertical layout.

public double Height { get; }

Property value

double

InkBounds

TextLayout.cs

Gets a rectangle that contains everything the layout draws when it is filled, relative to its top-left corner; empty when the layout draws nothing.

public RectangleD InkBounds { get; }

Property value

RectangleD

Remarks

The rectangle contains the control points of the outlines, the decorations and the backgrounds of the styles (not the strokes of their outlines, which reach half the width of a pen further: see TextLayout.GetInkBounds). It is the exact bounding box of the glyphs of a font that has a point at each extremum of its curves, as fonts do. It can extend beyond the box of the layout: glyphs overhang their advance, and marks rise above the ascent.

IsTruncated

TextLayout.cs

Gets a value indicating whether a part of the text is not in the layout: it did not fit TextLayoutOptions.MaxLines or TextLayoutOptions.MaxHeight, or a line was trimmed to TextLayoutOptions.MaxWidth.

public bool IsTruncated { get; }

Property value

bool

LineCount

TextLayout.cs

Gets the number of lines.

public int LineCount { get; }

Property value

int

Gets the lines, from the top: the part of the text each one holds and where it is. The lines of a vertical layout are its columns, from the first one: the rightmost one or the leftmost one.

public IReadOnlyList<TextLine> Lines { get; }

Placeholders

TextLayout.cs

Gets the rectangle of each placeholder of the text (RichTextBuilder.AppendPlaceholder), in the order of the text, from the top-left corner of the layout: where the caller draws what the placeholder makes room for, once the origin the layout is drawn at is added.

public IReadOnlyList<RectangleD> Placeholders { get; }

Property value

IReadOnlyList<RectangleD>

Remarks

The rectangle has the width and the height of the placeholder (multiplied by TextLayout.Scale), on its line: its top is the baseline of the line less the baseline of the placeholder. A placeholder that is not in the layout, because the text was cut before it, has an empty rectangle. The rectangles are those of a layout drawn at a point, not along a path.

Gets what the sizes of the styles were multiplied by to lay out the text: 1, unless the layout was created by TextLayout.CreateToFit, which then gives the factor that makes the text fit its box.

public double Scale { get; }

Property value

double

Gets the width of the text, in pixels: the width of its widest line, with the indent of a first line.

public double Width { get; }

Property value

double

Remarks

The white space that ends a wrapped line hangs and does not count; the spaces that end a paragraph do. Lines are aligned in TextLayoutOptions.MaxWidth when it is set, which can be wider than the text. In a vertical layout (TextLayoutOptions.WritingMode), it is the sum of the widths of the lines.

Methods

NameDescription
Create(RichText, TextLayoutOptions?)Lays out a text whose parts have their own style.
Create(string, Font, double)Lays out a text with one font, on one line per paragraph.
Create(string, TextStyle, TextLayoutOptions?)Lays out a text with one style.
CreateToFit(RichText, double, double, TextLayoutOptions?, double)Lays out a text whose parts have their own style at the largest sizes, up to those of its styles, at which it fits a box: a title that fills a card, a label that never overflows its button.
CreateToFit(string, TextStyle, double, double, TextLayoutOptions?, double)Lays out a text with one style at the largest size, up to the size of the style, at which it fits a box.
GetCaretRectangle(int)Gets where a caret is drawn before a character of the text: a rectangle without width, as tall as its line.
GetInkBounds(Pen)Gets a rectangle that contains everything a pen paints when it strokes the layout, relative to its top-left corner.
GetOrigin(PointD, TextHorizontalAnchor, TextVerticalAnchor, TextBounds)Gets the origin to draw the layout at so that a point of its box lands on an anchor: its center, a corner, the middle of a side, or a point of its first baseline.
GetOrigin(RectangleD, TextHorizontalAnchor, TextVerticalAnchor, TextBounds)Gets the origin to draw the layout at so that its box is aligned in a rectangle: centered, or against a side or a corner.
GetSelectionRectangles(int, int)Gets the rectangles that cover a range of the text: what is highlighted when it is selected.
HitTest(PointD)Finds the character at a point of the layout, or the nearest one: what a click selects.
ToPath(PointD)Creates a path with the outlines of the glyphs and of the decorations: what filling the layout at a point draws.

Create overloads

Create(RichText, TextLayoutOptions?)

TextLayout.cs

Lays out a text whose parts have their own style.

public static TextLayout Create(RichText text, TextLayoutOptions? options = null)
Parameters
text RichText
The text and its spans.
options TextLayoutOptions?
How the text is wrapped and aligned, or null for TextLayoutOptions.Default.
Returns

TextLayout The layout.

Exceptions
ArgumentNullException
text is null.
ArgumentException
text has no span: there is no font to lay out even an empty line.
InvalidImageContentException
The data of a glyph of the text, or a layout table of a font, is malformed.
UnsupportedImageFeatureException
The outlines of a font use a feature that is not supported.
ImageResourceLimitException
The text, its glyphs, its lines or one of its glyphs exceed a limit. The limits are those of the configuration the primary font of the first span was loaded with.

Create(string, Font, double)

TextLayout.cs

Lays out a text with one font, on one line per paragraph.

public static TextLayout Create(string text, Font font, double size)
Parameters
text string
The text.
font Font
The font.
size double
The size of the font, in pixels per em: a positive number up to 65536.
Returns

TextLayout The layout.

Exceptions
ArgumentNullException
text or font is null.
ArgumentOutOfRangeException
size is not a positive number up to 65536.
InvalidImageContentException
The data of a glyph of the text, or a layout table of the font, is malformed.
UnsupportedImageFeatureException
The outlines of the font use a feature that is not supported.
ImageResourceLimitException
The text, its glyphs, its lines or one of its glyphs exceed ImageResourceLimits.MaxTextLength, ImageResourceLimits.MaxTextGlyphs, ImageResourceLimits.MaxTextLines or ImageResourceLimits.MaxGlyphComplexity. The limits are those of the configuration the font was loaded with.

Create(string, TextStyle, TextLayoutOptions?)

TextLayout.cs

Lays out a text with one style.

public static TextLayout Create(string text, TextStyle style, TextLayoutOptions? options = null)
Parameters
text string
The text.
style TextStyle
The fonts, the size and the other settings of the text.
options TextLayoutOptions?
How the text is wrapped and aligned, or null for TextLayoutOptions.Default.
Returns

TextLayout The layout.

Exceptions
ArgumentNullException
text or style is null.
InvalidImageContentException
The data of a glyph of the text, or a layout table of a font, is malformed.
UnsupportedImageFeatureException
The outlines of a font use a feature that is not supported.
ImageResourceLimitException
The text, its glyphs, its lines or one of its glyphs exceed a limit. The limits are those of the configuration the primary font of the style was loaded with.

CreateToFit overloads

CreateToFit(RichText, double, double, TextLayoutOptions?, double)

TextLayout.cs

Lays out a text whose parts have their own style at the largest sizes, up to those of its styles, at which it fits a box: a title that fills a card, a label that never overflows its button.

public static TextLayout CreateToFit(RichText text, double width, double height, TextLayoutOptions? options = null, double minimumScale = 0.1d)
Parameters
text RichText
The text and its spans.
width double
The width of the box, in pixels: lines are wrapped and aligned at it, unless the text is vertical. Positive, or Double.PositiveInfinity.
height double
The height of the box, in pixels: the columns of a vertical text are wrapped and aligned at it. Positive, or Double.PositiveInfinity.
options TextLayoutOptions?
How the text is wrapped and aligned, or null for TextLayoutOptions.Default. Its TextLayoutOptions.MaxWidth and TextLayoutOptions.MaxHeight are replaced by the box.
minimumScale double
The smallest fraction of their sizes the styles are given, more than 0 and up to 1.
Returns

TextLayout The layout.

Exceptions
ArgumentNullException
text is null.
ArgumentException
text has no span.
ArgumentOutOfRangeException
width or height is not positive, or minimumScale is not more than 0 and up to 1.
InvalidImageContentException
The data of a glyph of the text, or a layout table of a font, is malformed.
UnsupportedImageFeatureException
The outlines of a font use a feature that is not supported.
ImageResourceLimitException
The text, its glyphs, its lines or one of its glyphs exceed a limit.
Remarks

Every length of the styles is multiplied by one factor, TextLayout.Scale: the sizes of the fonts, the letter and word spacings, and with them the indent of the first lines, a fixed line height and the placeholders. The widths of the pens of TextStyle.Outline are not. The factor is 1 when the text fits its box at the sizes of its styles: a text is made smaller, never larger.

A text fits when none of it is cut (TextLayout.IsTruncated), no line is wider than width (a word that cannot be broken can be) and its lines are not taller than height. Lines are wrapped at width as options say: with TextWrapping.NoWrap or TextLayoutOptions.MaxLines, the text is fitted on the lines it is allowed. In a vertical TextLayoutOptions.WritingMode, the box is turned: the lines are columns, wrapped at height, and they fit when they are not wider than width together. The factor is found by trying the one the box is to the text as it is laid out at its sizes (the exact factor of a text whose lines stay the same), then by halving twelve times the range between a factor that fits and one that does not, each time with a whole layout: it is the largest one of those that were tried and fit, within 1/4096 of the range from minimumScale to 1. A text that does not fit at minimumScale is laid out at that factor in the box, and cut as the options say.

CreateToFit(string, TextStyle, double, double, TextLayoutOptions?, double)

TextLayout.cs

Lays out a text with one style at the largest size, up to the size of the style, at which it fits a box.

public static TextLayout CreateToFit(string text, TextStyle style, double width, double height, TextLayoutOptions? options = null, double minimumScale = 0.1d)
Parameters
text string
The text.
style TextStyle
The fonts, the size and the other settings of the text. Its size is the largest one the text is given.
width double
The width of the box, in pixels: lines are wrapped and aligned at it, unless the text is vertical. Positive, or Double.PositiveInfinity for a text that is only fitted to a height.
height double
The height of the box, in pixels: the columns of a vertical text are wrapped and aligned at it. Positive, or Double.PositiveInfinity for a text that is only fitted to a width.
options TextLayoutOptions?
How the text is wrapped and aligned, or null for TextLayoutOptions.Default. Its TextLayoutOptions.MaxWidth and TextLayoutOptions.MaxHeight are replaced by the box.
minimumScale double
The smallest fraction of its size the text is given, more than 0 and up to 1: a text that does not fit at that size is laid out at it, and cut as the options say.
Returns

TextLayout The layout; TextLayout.Scale is the fraction of its size the text has.

Exceptions
ArgumentNullException
text or style is null.
ArgumentOutOfRangeException
width or height is not positive, or minimumScale is not more than 0 and up to 1.
InvalidImageContentException
The data of a glyph of the text, or a layout table of a font, is malformed.
UnsupportedImageFeatureException
The outlines of a font use a feature that is not supported.
ImageResourceLimitException
The text, its glyphs, its lines or one of its glyphs exceed a limit.
Remarks

GetCaretRectangle(int)

TextLayout.cs

Gets where a caret is drawn before a character of the text: a rectangle without width, as tall as its line.

public RectangleD GetCaretRectangle(int textIndex)

Parameters

textIndex int
The index in the text of the character the caret is before, in UTF-16 code units: from 0 to the length of the text, which is after its last character.

Returns

RectangleD The rectangle, from the top-left corner of the layout: its left is the position of the caret, and its width is 0.

Exceptions

ArgumentOutOfRangeException
textIndex is negative or greater than the length of the text.

Remarks

The caret is on the leading edge of the character: its left in a left-to-right text, its right in a right-to-left one. At the end of a line (a line break, the end of the text), it is after the last character of the line; on a line without character, it is where the line starts. The position between two lines of a wrapped paragraph is the start of the second one.

An index inside a character (between a letter and its mark, inside a surrogate pair) gives the caret of that character.

In a vertical layout, the caret lies across its line: the rectangle is as wide as the line and has no height, and its top is the position of the caret.

GetInkBounds(Pen)

TextLayout.cs

Gets a rectangle that contains everything a pen paints when it strokes the layout, relative to its top-left corner.

public RectangleD GetInkBounds(Pen pen)

Parameters

pen Pen
The stroke. Its brush is not used.

Returns

RectangleD The rectangle.

Exceptions

ArgumentNullException
pen is null.
ArgumentOutOfRangeException
The stroke goes beyond the largest coordinate (16777216).

Remarks

The rectangle is TextLayout.InkBounds spread by how far the stroke can reach from the outlines, as VectorPath.GetBounds spreads the bounds of a path: what DrawingCanvas.StrokeText paints, or a filled layout outlined with that pen. A pen with markers strokes TextLayout.ToPath, which allocates, and gives its exact bounds. Empty when the layout draws nothing.

GetOrigin overloads

GetOrigin(PointD, TextHorizontalAnchor, TextVerticalAnchor, TextBounds)

TextLayout.cs

Gets the origin to draw the layout at so that a point of its box lands on an anchor: its center, a corner, the middle of a side, or a point of its first baseline.

public PointD GetOrigin(PointD anchor, TextHorizontalAnchor horizontal, TextVerticalAnchor vertical, TextBounds bounds = TextBounds.Layout)
Parameters
anchor PointD
Where the point of the box goes, on the canvas.
horizontal TextHorizontalAnchor
The point of the box along x.
vertical TextVerticalAnchor
The point of the box along y.
bounds TextBounds
The box: the box of the layout, or what is drawn.
Returns

PointD The position of the top-left corner of the layout.

Exceptions
ArgumentException
A coordinate of anchor is not a finite number, or vertical is TextVerticalAnchor.Baseline and the layout is vertical.
ArgumentOutOfRangeException
The magnitude of a coordinate of anchor exceeds 16777216, or horizontal, vertical or bounds is not a defined value.
Remarks

The origin is the top-left corner of the layout, which DrawingCanvas.FillText and DrawingCanvas.StrokeText take. It is not rounded: glyphs are placed at fractions of a pixel, and rounding it is the choice of the caller.

Example
public static void DrawPayButton(string fontPath, string outputPath)
{
    var font = Font.Load(fontPath);
    var label = TextLayout.Create("Pay now", font, size: 20);
    var caption = TextLayout.Create("Total", font, size: 16);
    var amount = TextLayout.Create("42.00", font, size: 24);

    using var image = new Image<Rgba32>(240, 120, new Rgba32(255, 255, 255));
    image.Draw(canvas =>
    {
        // GetOrigin gives the top-left corner to draw a layout at so that a point of its box lands on an anchor.
        // The ink box centers what is drawn, the tail of the y included; the layout box would center the line
        var button = new RectangleD(20, 64, 200, 40);
        canvas.FillRoundedRectangle(new Rgba32(30, 60, 160), button, radius: 8);
        canvas.FillText(new Rgba32(255, 255, 255), label, label.GetOrigin(button, TextHorizontalAnchor.Center, TextVerticalAnchor.Center, TextBounds.Ink));

        // Two sizes on one baseline: the caption from the left, the amount aligned to the right
        var baseline = 44;
        canvas.FillText(new Rgba32(90, 90, 90), caption, caption.GetOrigin(new PointD(20, baseline), TextHorizontalAnchor.Left, TextVerticalAnchor.Baseline));
        canvas.FillText(new Rgba32(0, 0, 0), amount, amount.GetOrigin(new PointD(220, baseline), TextHorizontalAnchor.Right, TextVerticalAnchor.Baseline));
    });

    image.Save(outputPath);
}

GetOrigin(RectangleD, TextHorizontalAnchor, TextVerticalAnchor, TextBounds)

TextLayout.cs

Gets the origin to draw the layout at so that its box is aligned in a rectangle: centered, or against a side or a corner.

public PointD GetOrigin(RectangleD box, TextHorizontalAnchor horizontal, TextVerticalAnchor vertical, TextBounds bounds = TextBounds.Layout)
Parameters
box RectangleD
The rectangle, on the canvas.
horizontal TextHorizontalAnchor
Where the text goes along x.
vertical TextVerticalAnchor
Where the text goes along y: not TextVerticalAnchor.Baseline, which a rectangle does not have.
bounds TextBounds
The box of the text: the box of the layout, or what is drawn.
Returns

PointD The position of the top-left corner of the layout.

Exceptions
ArgumentException
An edge of box is not a finite number, or vertical is TextVerticalAnchor.Baseline.
ArgumentOutOfRangeException
The magnitude of an edge of box exceeds 16777216, or horizontal, vertical or bounds is not a defined value.
Remarks

The point of the box goes on the same point of the rectangle: its center on the center, its right side on the right side. A text larger than the rectangle overflows it, on both sides when it is centered. The origin is not rounded.

GetSelectionRectangles(int, int)

TextLayout.cs

Gets the rectangles that cover a range of the text: what is highlighted when it is selected.

public IReadOnlyList<RectangleD> GetSelectionRectangles(int start, int length)

Parameters

start int
The index in the text of the first character of the range, in UTF-16 code units.
length int
The number of UTF-16 code units of the range.

Returns

IReadOnlyList<RectangleD> The rectangles, from the top-left corner of the layout, line by line and from left to right on each line (from top to bottom on the lines of a vertical layout); none for an empty range.

Exceptions

ArgumentOutOfRangeException
start or length is negative, or the range goes beyond the end of the text.

Remarks

Each rectangle is as tall as its line and covers the advances of characters that follow each other on it. A range that spans several lines has at least one rectangle per line; in a text that mixes directions, a range can be several rectangles on one line. Line breaks and the characters that have no glyph take no space, and a part of a ligature takes its share of the width of the ligature. In a vertical layout, each rectangle is as wide as its line and covers the characters that follow each other down it.

HitTest(PointD)

TextLayout.cs

Finds the character at a point of the layout, or the nearest one: what a click selects.

public TextHit HitTest(PointD point)

Parameters

point PointD
The point, from the top-left corner of the layout: the point of the canvas, less the origin the layout is drawn at.

Returns

TextHit The character and the side of it the point is on.

Exceptions

ArgumentException
A coordinate of point is not a number.

Remarks

The line is the one whose rows contain the point; above the first line it is the first one, and below the last line the last one. In a vertical layout, the line is the one whose columns contain the point, and a character is found down its line. On that line, the character is the one whose advance contains the point (the space from where the pen is before it to where it is after it, not its ink); beside the text, it is the first or the last one of the line as it is displayed. TextHit.IsInside tells the two apart.

A character is a grapheme cluster. The characters a font draws as one glyph (a ligature) share its width equally. In a text that mixes directions, the characters of a line are not in the order of the text: the hit gives the index in the text, and TextHit.IsTrailing is relative to the direction of the character.

ToPath(PointD)

TextLayout.cs

Creates a path with the outlines of the glyphs and of the decorations: what filling the layout at a point draws.

public VectorPath ToPath(PointD origin)

Parameters

origin PointD
The position of the top-left corner of the layout.

Returns

VectorPath A path with one closed contour per contour of the glyphs and one per decoration, to fill with FillRule.NonZero. Filling or stroking it changes the same pixels, to the same values, as filling or stroking a layout of one paint at the same point.

Exceptions

ArgumentOutOfRangeException
origin is not finite or is too large.