Prepared Text with TextBlock
TextMeasurer is the shortest path from a string to a measurement, but every call shapes the text from scratch. TextBlock does the wrapping-independent work once, then lets you measure, render, and inspect the same text repeatedly at different wrapping lengths.
Use TextBlock whenever the same string will be measured, wrapped, drawn, or inspected more than once: rich-text editors, layout panels that resize, anything that needs both a measurement pass and a render pass.
Construct once, vary the wrapping length
using SixLabors.Fonts;
Font font = SystemFonts.CreateFont("Segoe UI", 18);
TextOptions options = new(font)
{
Origin = new System.Numerics.Vector2(20, 30)
};
TextBlock block = new("Hello, world!", options);
TextMetrics narrow = block.Measure(240);
TextMetrics wide = block.Measure(480);
TextOptions.WrappingLength is ignored by the constructor. Pass the wrapping length to each operation instead, and use -1 to disable wrapping for that call.
TextMetrics unwrapped = block.Measure(-1);
Detail APIs
TextBlock exposes the same per-entry collections that TextMetrics does, for callers that do not need the full measurement object:
using System;
using SixLabors.Fonts;
ReadOnlyMemory<LineMetrics> lines = block.GetLineMetrics(320);
ReadOnlyMemory<GraphemeMetrics> graphemes = block.GetGraphemeMetrics(320);
ReadOnlyMemory<WordMetrics> words = block.GetWordMetrics(320);
ReadOnlyMemory<GlyphMetrics> glyphs = block.GetGlyphMetrics(320);
Method-returned collections use ReadOnlyMemory<T> because they are snapshots a caller may store with their own layout state. Owner-backed properties such as TextMetrics.LineMetrics and LineLayout.GraphemeMetrics use ReadOnlySpan<T> because the owner already controls the lifetime.
Per-line layout
When the UI needs line-local data, use GetLineLayouts(...) or EnumerateLineLayouts(). Both produce LineLayout instances that mirror the interaction surface of TextMetrics for a single line — hit testing, caret positioning, caret movement, word lookup, and selection bounds — but they position those lines in different coordinate spaces.
Block coordinates with GetLineLayouts
GetLineLayouts(...) lays out the whole block as one unit. Lines stack in their natural flow direction starting from TextOptions.Origin, so each successive line's LineMetrics.Start includes the cumulative advance of the lines that came before it.
using SixLabors.Fonts;
ReadOnlyMemory<LineLayout> layouts = block.GetLineLayouts(320);
foreach (LineLayout line in layouts.Span)
{
LineMetrics lineMetrics = line.LineMetrics;
ReadOnlySpan<GraphemeMetrics> lineGraphemes = line.GraphemeMetrics;
ReadOnlyMemory<GlyphMetrics> lineGlyphs = line.GetGlyphMetrics();
}
Use this when the whole block paints into one rectangle and you want the returned geometry to be ready to draw without any further offsetting.
Line-local coordinates with EnumerateLineLayouts
EnumerateLineLayouts() lays out one line at a time and accepts the wrapping length per call. Each produced line is positioned independently, as if it were the first and only line in the block — its geometry sits at TextOptions.Origin regardless of which line index the enumerator is on. The caller is responsible for placing the line into the final layout.
using SixLabors.Fonts;
LineLayoutEnumerator enumerator = block.EnumerateLineLayouts();
while (enumerator.MoveNext(wrappingLength: 320))
{
LineLayout line = enumerator.Current;
}
Use this when each line goes into a different column, frame, or shape — flowed text, variable-width columns, virtualized lists, or curved baselines — and the block's natural top-to-bottom stacking does not match the surface you are painting on. The wrapping length can also vary per line.
Picking between them
- Use
GetLineLayouts(...)when the whole block paints as one stacked unit and you want the returned line positions to be ready to draw against the block origin. - Use
EnumerateLineLayouts()when the caller controls where each line lands and the block's stacking is not the layout you want.
Render the prepared block
RenderTo(...) draws the block to any IGlyphRenderer using the same wrapping-length argument as the measurement methods.
using SixLabors.Fonts.Rendering;
block.RenderTo(renderer, wrappingLength: 480);
Always render with the same TextOptions and wrapping length you measured with. Reusing the prepared block avoids re-shaping the text between the two passes.
When to choose TextBlock over TextMeasurer
Use TextMeasurer for one-off measurements where you do not need to keep a measurement object around.
Use TextBlock when:
- The same text is laid out repeatedly with different wrapping lengths.
- You want to measure once and render later with the same prepared shaping.
- You need per-line interaction (hit testing, carets, selection) — see Hit Testing and Caret Movement.
- You want to walk the laid-out text line by line without materializing every line up front.
Practical guidance
Use TextMeasurer for one-off answers. Use TextBlock when the shaped text becomes state: it will be measured more than once, rendered later, inspected line by line, hit-tested, or used for caret and selection behavior. Preparing the block once keeps measurement and rendering tied to the same shaping result.
The line-layout APIs differ by coordinate model. GetLineLayouts(...) returns lines positioned as one stacked block, which is what you want when the text paints as a normal paragraph or label. EnumerateLineLayouts() returns line-local layouts, which is what you want when another system places each line into columns, frames, paths, or virtualized rows. Choosing the wrong one usually shows up as doubled offsets or lines that are positioned correctly by themselves but not as a block.
Keep the prepared block tied to the TextOptions that created it. If font, culture, fallback, feature tags, or direction changes, prepare a new block rather than trying to reuse old layout state.