diff --git a/src/Avalonia.Visuals/Media/CharacterHit.cs b/src/Avalonia.Visuals/Media/CharacterHit.cs index ba691dad6e..f018b2d8a9 100644 --- a/src/Avalonia.Visuals/Media/CharacterHit.cs +++ b/src/Avalonia.Visuals/Media/CharacterHit.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace Avalonia.Media { @@ -9,6 +10,7 @@ namespace Avalonia.Media /// The CharacterHit structure provides information about the index of the first /// character that got hit as well as information about leading or trailing edge. /// + [DebuggerDisplay("CharacterHit({FirstCharacterIndex}, {TrailingLength})")] public readonly struct CharacterHit : IEquatable { /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index f73a7be759..435752160e 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -236,7 +236,7 @@ namespace Avalonia.Media.TextFormatting var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - if (codepointIndex >= TextRange.Start + TextRange.Length) + if (codepointIndex > TextRange.End) { return false; // Cannot go forward anymore } @@ -249,11 +249,14 @@ namespace Avalonia.Media.TextFormatting var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); - nextCharacterHit = characterHit.TrailingLength != 0 ? + var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == + TextRange.Length; + + nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? foundCharacterHit : new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); - if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) + if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) { return true; } @@ -272,6 +275,13 @@ namespace Avalonia.Media.TextFormatting /// private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit) { + if (characterHit.FirstCharacterIndex == TextRange.Start) + { + previousCharacterHit = new CharacterHit(TextRange.Start); + + return true; + } + previousCharacterHit = characterHit; var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; @@ -354,7 +364,7 @@ namespace Avalonia.Media.TextFormatting return new ShapedTextCharacters(glyphRun, textRun.Properties); } - + /// /// Gets the shaped width of specified shaped text characters. /// diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 09cbf3bf08..575a58f337 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -101,7 +102,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); - Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + Assert.Equal(0, previousCharacterHit.TrailingLength); previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]); @@ -119,7 +120,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); - Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + Assert.Equal(0, previousCharacterHit.TrailingLength); } } @@ -272,6 +273,76 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void TestNext() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var textSource = new SingleBufferTextSource("Text from memory", defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHits = new List(); + + var currentCharacterHit = new CharacterHit(0); + + characterHits.Add(currentCharacterHit); + + var nextCharacterHit = textLine.GetNextCaretCharacterHit(currentCharacterHit); + + while (nextCharacterHit != currentCharacterHit) + { + currentCharacterHit = nextCharacterHit; + + characterHits.Add(currentCharacterHit); + + nextCharacterHit = textLine.GetNextCaretCharacterHit(currentCharacterHit); + } + } + } + + [Fact] + public void TestPrevious() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var text = "Text from memory"; + + var textSource = new SingleBufferTextSource(text, defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHits = new List(); + + var currentCharacterHit = new CharacterHit(text.Length); + + characterHits.Add(currentCharacterHit); + + var nextCharacterHit = textLine.GetPreviousCaretCharacterHit(currentCharacterHit); + + while (nextCharacterHit != currentCharacterHit) + { + currentCharacterHit = nextCharacterHit; + + characterHits.Add(currentCharacterHit); + + nextCharacterHit = textLine.GetPreviousCaretCharacterHit(currentCharacterHit); + } + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface