// 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);
}
}
}