// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Numerics; using SixLabors.Fonts.Rendering; namespace SixLabors.Fonts { /// /// Represents one laid-out line from a . /// public sealed class LineLayout { private readonly TextBox textBox; private readonly TextOptions options; private readonly float wrappingLength; private readonly int lineIndex; private readonly LayoutMode layoutMode; private readonly ReadOnlyMemory graphemeMetrics; private readonly ReadOnlyMemory wordMetrics; private GlyphMetrics[]? glyphMetrics; internal LineLayout( TextBox textBox, TextOptions options, float wrappingLength, int lineIndex, in LineMetrics metrics, ReadOnlyMemory graphemeMetrics, ReadOnlyMemory wordMetrics) { this.textBox = textBox; this.options = options; this.wrappingLength = wrappingLength; this.lineIndex = lineIndex; this.layoutMode = options.LayoutMode; this.LineMetrics = metrics; this.graphemeMetrics = graphemeMetrics; this.wordMetrics = wordMetrics; } /// /// Gets the measured line metrics. /// public LineMetrics LineMetrics { get; } /// /// Gets the grapheme metrics entries for this line in final layout order. /// public ReadOnlySpan GraphemeMetrics => this.graphemeMetrics.Span; /// /// Hit tests the supplied point against this line's grapheme advance bounds. /// /// The point in pixel units. /// The hit-tested grapheme position. public TextHit HitTest(Vector2 point) => TextInteraction.HitTestLine(this.lineIndex, this.GraphemeMetrics, point, this.layoutMode); /// /// Gets the caret position for the supplied hit. /// /// The hit-tested grapheme position. /// The caret position in pixel units. public CaretPosition GetCaretPosition(TextHit hit) => TextInteraction.GetCaretPositionLine( this.lineIndex, this.LineMetrics, this.GraphemeMetrics, hit.GraphemeInsertionIndex, this.layoutMode); /// /// Gets an absolute caret position in the laid-out line. /// /// The absolute caret placement. /// The caret position in pixel units. public CaretPosition GetCaret(CaretPlacement placement) => TextInteraction.GetCaretLine( this.lineIndex, this.LineMetrics, this.GraphemeMetrics, placement, this.layoutMode, this.textBox.TextDirection()); /// /// Moves the supplied caret by the requested operation within this line. /// /// The current caret position. /// The movement operation. /// The moved caret position in pixel units. public CaretPosition MoveCaret(CaretPosition caret, CaretMovement movement) => TextInteraction.MoveCaretLine( this.lineIndex, this.LineMetrics, this.GraphemeMetrics, this.wordMetrics.Span, caret, movement, this.layoutMode, this.textBox.TextDirection()); /// /// Gets the word metrics for the word-boundary segment containing the supplied hit-tested grapheme position. /// /// The hit-tested grapheme position. /// The word metrics containing the hit grapheme. public WordMetrics GetWordMetrics(TextHit hit) => TextInteraction.GetWordMetrics(this.wordMetrics.Span, hit.GraphemeIndex); /// /// Gets the word metrics for the word-boundary segment containing the supplied caret position. /// /// The caret position. /// The word metrics containing the caret's grapheme insertion index. public WordMetrics GetWordMetrics(CaretPosition caret) => TextInteraction.GetWordMetrics(this.wordMetrics.Span, caret.GraphemeIndex); /// /// Gets selection bounds between two hit-tested grapheme positions. /// /// The fixed selection endpoint. /// The active selection endpoint. /// A read-only memory region containing the selection bounds in visual order and pixel units. public ReadOnlyMemory GetSelectionBounds(TextHit anchor, TextHit focus) => TextInteraction.GetSelectionBoundsLine( this.LineMetrics, this.GraphemeMetrics, anchor.GraphemeInsertionIndex, focus.GraphemeInsertionIndex, this.layoutMode); /// /// Gets selection bounds between two caret positions. /// /// The fixed selection endpoint. /// The active selection endpoint. /// A read-only memory region containing the selection bounds in visual order and pixel units. public ReadOnlyMemory GetSelectionBounds(CaretPosition anchor, CaretPosition focus) => TextInteraction.GetSelectionBoundsLine( this.LineMetrics, this.GraphemeMetrics, anchor.GraphemeIndex, focus.GraphemeIndex, this.layoutMode); /// /// Gets line-local selection bounds for the supplied grapheme metrics. /// /// The grapheme metrics to select. /// A read-only memory region containing the selection bounds in visual order and pixel units. public ReadOnlyMemory GetSelectionBounds(GraphemeMetrics metrics) => TextInteraction.GetSelectionBoundsLine( this.LineMetrics, metrics, this.layoutMode); /// /// Gets line-local selection bounds for the supplied word metrics. /// /// The word metrics to select. /// A read-only memory region containing the selection bounds in visual order and pixel units. public ReadOnlyMemory GetSelectionBounds(WordMetrics metrics) => TextInteraction.GetSelectionBoundsLine( this.LineMetrics, this.GraphemeMetrics, metrics.GraphemeStart, metrics.GraphemeEnd, this.layoutMode); /// public ReadOnlyMemory GetGlyphMetrics() => this.glyphMetrics ??= TextBlock.GetGlyphMetricsArray( this.textBox, this.options, this.wrappingLength, this.lineIndex); /// /// Renders this line to the supplied glyph renderer. /// /// The target renderer. public void RenderTo(IGlyphRenderer renderer) { FontRectangle bounds = FontRectangle.Empty; ReadOnlySpan glyphMetrics = this.GetGlyphMetrics().Span; for (int i = 0; i < glyphMetrics.Length; i++) { bounds = i == 0 ? glyphMetrics[i].Bounds : FontRectangle.Union(bounds, glyphMetrics[i].Bounds); } TextBlock.RenderTo( renderer, this.textBox, this.options, this.wrappingLength, bounds, this.lineIndex); } } }