// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.Fonts {
///
/// Encapsulates the full set of measurement results for laid-out text.
///
public sealed class TextMetrics
{
private readonly TextBlock textBlock;
private readonly TextBox textBox;
private readonly float wrappingLength;
private readonly LayoutMode layoutMode;
private readonly TextDirection textDirection;
private readonly GraphemeMetrics[] graphemeMetrics;
private readonly LineMetrics[] lineMetrics;
private readonly WordMetrics[] wordMetrics;
private GlyphMetrics[]? glyphMetrics;
internal TextMetrics(
TextBlock textBlock,
TextBox textBox,
float wrappingLength,
FontRectangle advance,
FontRectangle bounds,
FontRectangle renderableBounds,
int lineCount,
GraphemeMetrics[] graphemes,
LineMetrics[] lines,
WordMetrics[] words)
{
this.textBlock = textBlock;
this.textBox = textBox;
this.wrappingLength = wrappingLength;
this.layoutMode = textBlock.Options.LayoutMode;
this.textDirection = textBox.TextLines.Count == 0
? (textBlock.Options.TextDirection == TextDirection.RightToLeft ? TextDirection.RightToLeft : TextDirection.LeftToRight)
: textBox.TextDirection();
this.Advance = advance;
this.Bounds = bounds;
this.RenderableBounds = renderableBounds;
this.LineCount = lineCount;
this.graphemeMetrics = graphemes;
this.lineMetrics = lines;
this.wordMetrics = words;
}
///
/// Gets the logical advance rectangle of the text in pixel units.
///
///
/// Reflects line-box height and horizontal or vertical text advance from the layout model.
/// Does not guarantee that all rendered glyph pixels fit within the returned rectangle.
///
public FontRectangle Advance { get; }
///
/// Gets the rendered glyph bounds of the text in pixel units.
///
///
/// This is the tight ink bounds enclosing all rendered glyphs and may be smaller or larger
/// than the logical advance. May have a non-zero origin.
///
public FontRectangle Bounds { get; }
///
/// Gets the union of the logical advance rectangle (positioned at the text options origin)
/// and the rendered glyph bounds in pixel units.
///
///
/// Use this rectangle when both typographic advance and rendered glyph overshoot
/// must fit within the same bounding box.
///
public FontRectangle RenderableBounds { get; }
///
/// Gets the number of laid-out lines in the text.
///
public int LineCount { get; }
///
/// Gets the grapheme metrics entries in final layout order.
///
public ReadOnlySpan GraphemeMetrics => this.graphemeMetrics;
///
/// Gets the per-line layout metrics for the text.
///
public ReadOnlySpan LineMetrics => this.lineMetrics;
///
/// Gets the word-boundary segment metrics in source order.
///
public ReadOnlySpan WordMetrics => this.wordMetrics;
///
/// Hit tests the supplied point against the laid-out grapheme advance bounds.
///
/// The point in pixel units.
/// The hit-tested grapheme position.
public TextHit HitTest(Vector2 point)
=> TextInteraction.HitTest(
this.LineMetrics,
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.GetCaretPosition(
this.LineMetrics,
this.GraphemeMetrics,
hit.GraphemeInsertionIndex,
this.layoutMode);
///
/// Gets an absolute caret position in the laid-out text.
///
/// The absolute caret placement.
/// The caret position in pixel units.
public CaretPosition GetCaret(CaretPlacement placement)
=> TextInteraction.GetCaret(
this.LineMetrics,
this.GraphemeMetrics,
placement,
this.layoutMode,
this.textDirection);
///
/// Moves the supplied caret by the requested operation.
///
/// The current caret position.
/// The movement operation.
/// The moved caret position in pixel units.
public CaretPosition MoveCaret(CaretPosition caret, CaretMovement movement)
=> TextInteraction.MoveCaret(
this.LineMetrics,
this.GraphemeMetrics,
this.WordMetrics,
caret,
movement,
this.layoutMode,
this.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, 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, 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.GetSelectionBounds(
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.GetSelectionBounds(
this.LineMetrics,
this.GraphemeMetrics,
anchor.GraphemeIndex,
focus.GraphemeIndex,
this.layoutMode);
///
/// Gets 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.GetSelectionBounds(
this.LineMetrics,
this.GraphemeMetrics,
metrics,
this.layoutMode);
///
/// Gets 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.GetSelectionBounds(
this.LineMetrics,
this.GraphemeMetrics,
metrics.GraphemeStart,
metrics.GraphemeEnd,
this.layoutMode);
///
public ReadOnlyMemory GetGlyphMetrics()
=> this.glyphMetrics ??= TextBlock.GetGlyphMetricsArray(
this.textBox,
this.textBlock.Options,
this.wrappingLength);
}
}