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 TextLayoutRemarks
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
| Name | Description |
|---|---|
| Baseline | 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. |
| HasMissingGlyphs | Gets a value indicating whether a character of the text has no glyph in the fonts of its style. |
| Height | 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. |
| InkBounds | 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. |
| IsTruncated | 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. |
| LineCount | Gets the number of lines. |
| Lines | 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. |
| Placeholders | 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. |
| Scale | 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. |
| Width | Gets the width of the text, in pixels: the width of its widest line, with the indent of a first line. |
Baseline
TextLayout.csHasMissingGlyphs
TextLayout.csHeight
TextLayout.csInkBounds
TextLayout.csGets 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
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.csGets 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
LineCount
TextLayout.csLines
TextLayout.csGets 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; }Property value
Placeholders
TextLayout.csGets 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
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.
Scale
TextLayout.csGets 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
Width
TextLayout.csGets 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
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
| Name | Description |
|---|---|
| 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.csLays out a text whose parts have their own style.
public static TextLayout Create(RichText text, TextLayoutOptions? options = null)Parameters
textRichText- The text and its spans.
optionsTextLayoutOptions?- How the text is wrapped and aligned, or
nullforTextLayoutOptions.Default.
Returns
TextLayout The layout.
Exceptions
ArgumentNullExceptiontextisnull.ArgumentExceptiontexthas 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.csLays out a text with one font, on one line per paragraph.
public static TextLayout Create(string text, Font font, double size)Parameters
textstring- The text.
fontFont- The font.
sizedouble- The size of the font, in pixels per em: a positive number up to 65536.
Returns
TextLayout The layout.
Exceptions
ArgumentNullExceptiontextorfontisnull.ArgumentOutOfRangeExceptionsizeis 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.MaxTextLinesorImageResourceLimits.MaxGlyphComplexity. The limits are those of the configuration the font was loaded with.
Create(string, TextStyle, TextLayoutOptions?)
TextLayout.csLays out a text with one style.
public static TextLayout Create(string text, TextStyle style, TextLayoutOptions? options = null)Parameters
textstring- The text.
styleTextStyle- The fonts, the size and the other settings of the text.
optionsTextLayoutOptions?- How the text is wrapped and aligned, or
nullforTextLayoutOptions.Default.
Returns
TextLayout The layout.
Exceptions
ArgumentNullExceptiontextorstyleisnull.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.csLays 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
textRichText- The text and its spans.
widthdouble- The width of the box, in pixels: lines are wrapped and aligned at it, unless the text is vertical. Positive, or
Double.PositiveInfinity. heightdouble- The height of the box, in pixels: the columns of a vertical text are wrapped and aligned at it. Positive, or
Double.PositiveInfinity. optionsTextLayoutOptions?- How the text is wrapped and aligned, or
nullforTextLayoutOptions.Default. ItsTextLayoutOptions.MaxWidthandTextLayoutOptions.MaxHeightare replaced by the box. minimumScaledouble- The smallest fraction of their sizes the styles are given, more than 0 and up to 1.
Returns
TextLayout The layout.
Exceptions
ArgumentNullExceptiontextisnull.ArgumentExceptiontexthas no span.ArgumentOutOfRangeExceptionwidthorheightis not positive, orminimumScaleis 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.csLays 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
textstring- The text.
styleTextStyle- The fonts, the size and the other settings of the text. Its size is the largest one the text is given.
widthdouble- The width of the box, in pixels: lines are wrapped and aligned at it, unless the text is vertical. Positive, or
Double.PositiveInfinityfor a text that is only fitted to a height. heightdouble- The height of the box, in pixels: the columns of a vertical text are wrapped and aligned at it. Positive, or
Double.PositiveInfinityfor a text that is only fitted to a width. optionsTextLayoutOptions?- How the text is wrapped and aligned, or
nullforTextLayoutOptions.Default. ItsTextLayoutOptions.MaxWidthandTextLayoutOptions.MaxHeightare replaced by the box. minimumScaledouble- 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
ArgumentNullExceptiontextorstyleisnull.ArgumentOutOfRangeExceptionwidthorheightis not positive, orminimumScaleis 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.csGets 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
textIndexint- 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
ArgumentOutOfRangeExceptiontextIndexis 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.csGets 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
penPen- The stroke. Its brush is not used.
Returns
RectangleD The rectangle.
Exceptions
ArgumentNullExceptionpenisnull.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.csGets 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
anchorPointD- Where the point of the box goes, on the canvas.
horizontalTextHorizontalAnchor- The point of the box along x.
verticalTextVerticalAnchor- The point of the box along y.
boundsTextBounds- 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
anchoris not a finite number, orverticalisTextVerticalAnchor.Baselineand the layout is vertical. ArgumentOutOfRangeException- The magnitude of a coordinate of
anchorexceeds 16777216, orhorizontal,verticalorboundsis 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.csGets 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
boxRectangleD- The rectangle, on the canvas.
horizontalTextHorizontalAnchor- Where the text goes along x.
verticalTextVerticalAnchor- Where the text goes along y: not
TextVerticalAnchor.Baseline, which a rectangle does not have. boundsTextBounds- 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
boxis not a finite number, orverticalisTextVerticalAnchor.Baseline. ArgumentOutOfRangeException- The magnitude of an edge of
boxexceeds 16777216, orhorizontal,verticalorboundsis 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.csGets 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
startint- The index in the text of the first character of the range, in UTF-16 code units.
lengthint- 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
ArgumentOutOfRangeExceptionstartorlengthis 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.csFinds the character at a point of the layout, or the nearest one: what a click selects.
Parameters
pointPointD- 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
pointis 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.csCreates 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
originPointD- 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
ArgumentOutOfRangeExceptionoriginis not finite or is too large.