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