// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Runtime.CompilerServices; using SixLabors.Fonts.Unicode; namespace SixLabors.Fonts { /// /// Represents a run of text spanning a series of graphemes within a string. /// public class TextRun { /// /// Gets or sets the inclusive start index of the first grapheme in this . /// public int Start { get; set; } /// /// Gets or sets the exclusive end index of the last grapheme in this . /// public int End { get; set; } /// /// Gets or sets the font for this run. /// public Font? Font { get; set; } /// /// Gets or sets the text attributes applied to this run. /// public TextAttributes TextAttributes { get; set; } /// /// Gets or sets the text decorations applied to this run. /// public TextDecorations TextDecorations { get; set; } /// /// Gets or sets the inline placeholder represented by this run. /// /// /// Placeholder runs are inserted at and must have equal to . /// public TextPlaceholder? Placeholder { get; set; } /// /// Returns the slice of the given text representing this . /// /// The text to slice. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Slice(ReadOnlySpan text) { ValidateRange(this.Start, this.End); // Convert grapheme indices into char indices so we can slice int chars = 0; int count = 0; int start = 0; int length = 0; SpanGraphemeEnumerator graphemeEnumerator = new(text); while (graphemeEnumerator.MoveNext()) { if (count == this.Start) { start = chars; } SpanCodePointEnumerator codePointEnumerator = new(graphemeEnumerator.Current.Span); while (codePointEnumerator.MoveNext()) { chars += codePointEnumerator.Current.Utf16SequenceLength; length = chars - start; } if (++count == this.End) { break; } } return text.Slice(start, length); } /// public override string ToString() => $"[TextRun: Start={this.Start}, End={this.End}, TextAttributes={this.TextAttributes}]"; [MethodImpl(MethodImplOptions.NoInlining)] private static void ValidateRange(int start, int end) { if (start < 0 || end < 0) { throw new ArgumentOutOfRangeException($"Start '{start}' and End '{end}' must be greater or equal to zero."); } if (end <= start) { throw new ArgumentOutOfRangeException($"End '{end}' must be greater than Start '{start}'."); } } } }