using System.Collections.Generic; namespace Avalonia.Media.TextFormatting { internal class TextLineImpl : TextLine { private readonly IReadOnlyList _textRuns; public TextLineImpl(IReadOnlyList textRuns, TextLineMetrics lineMetrics, TextLineBreak lineBreak = null) { _textRuns = textRuns; LineMetrics = lineMetrics; LineBreak = lineBreak; } /// public override TextRange TextRange => LineMetrics.TextRange; /// public override IReadOnlyList TextRuns => _textRuns; /// public override TextLineMetrics LineMetrics { get; } /// public override TextLineBreak LineBreak { get; } /// public override void Draw(DrawingContext drawingContext, Point origin) { var currentX = origin.X; foreach (var textRun in _textRuns) { var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X, origin.Y + LineMetrics.BaselineOrigin.Y); textRun.Draw(drawingContext, baselineOrigin); currentX += textRun.Bounds.Width; } } /// public override CharacterHit GetCharacterHitFromDistance(double distance) { if (distance < 0) { // hit happens before the line, return the first position return new CharacterHit(TextRange.Start); } // process hit that happens within the line var characterHit = new CharacterHit(); foreach (var run in _textRuns) { characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _); if (distance <= run.Bounds.Width) { break; } distance -= run.Bounds.Width; } return characterHit; } /// public override double GetDistanceFromCharacterHit(CharacterHit characterHit) { return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0)); } /// public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) { if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit)) { return nextCharacterHit; } return new CharacterHit(TextRange.End); // Can't move, we're after the last character } /// public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) { if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit)) { return previousCharacterHit; } return new CharacterHit(TextRange.Start); // Can't move, we're before the first character } /// public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit) { // same operation as move-to-previous return GetPreviousCaretCharacterHit(characterHit); } /// /// Get distance from line start to the specified codepoint index. /// private double DistanceFromCodepointIndex(int codepointIndex) { var currentDistance = 0.0; foreach (var textRun in _textRuns) { if (codepointIndex > textRun.Text.End) { currentDistance += textRun.Bounds.Width; continue; } return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex)); } return currentDistance; } /// /// Tries to find the next character hit. /// /// The current character hit. /// The next character hit. /// private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit) { nextCharacterHit = characterHit; var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; if (codepointIndex >= TextRange.Start + TextRange.Length) { return false; // Cannot go forward anymore } var runIndex = GetRunIndexAtCodepointIndex(codepointIndex); while (runIndex < TextRuns.Count) { var run = _textRuns[runIndex]; nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) { return true; } runIndex++; } return false; } /// /// Tries to find the previous character hit. /// /// The current character hit. /// The previous character hit. /// private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit) { previousCharacterHit = characterHit; var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; if (codepointIndex < TextRange.Start) { return false; // Cannot go backward anymore. } var runIndex = GetRunIndexAtCodepointIndex(codepointIndex); while (runIndex >= 0) { var run = _textRuns[runIndex]; previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); if (previousCharacterHit.FirstCharacterIndex < codepointIndex) { return true; } runIndex--; } return false; } /// /// Gets the run index of the specified codepoint index. /// /// The codepoint index. /// The text run index. private int GetRunIndexAtCodepointIndex(int codepointIndex) { if (codepointIndex >= TextRange.End) { return _textRuns.Count - 1; } if (codepointIndex <= 0) { return 0; } var runIndex = 0; while (runIndex < _textRuns.Count) { var run = _textRuns[runIndex]; if (run.Text.End > codepointIndex) { return runIndex; } runIndex++; } return runIndex; } } }