Przeglądaj źródła

Fix text line hit testing
Fix some xml comments

Benedikt Schroeder 5 lat temu
rodzic
commit
38bd8b8010

+ 2 - 8
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs

@@ -22,9 +22,7 @@ namespace Avalonia.Media.TextFormatting
             GlyphRun = glyphRun;
         }
 
-        /// <summary>
-        /// Gets the bounds.
-        /// </summary>
+        /// <inheritdoc/>
         public override Rect Bounds => GlyphRun.Bounds;
 
         /// <summary>
@@ -35,11 +33,7 @@ namespace Avalonia.Media.TextFormatting
         /// </value>
         public GlyphRun GlyphRun { get; }
 
-        /// <summary>
-        /// Draws the <see cref="TextRun"/> at the given origin.
-        /// </summary>
-        /// <param name="drawingContext">The drawing context.</param>
-        /// <param name="origin">The origin.</param>
+        /// <inheritdoc/>
         public override void Draw(IDrawingContextImpl drawingContext, Point origin)
         {
             if (GlyphRun.GlyphIndices.Length == 0)

+ 1 - 1
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs

@@ -166,7 +166,7 @@ namespace Avalonia.Media.TextFormatting
 
                     var splitResult = SplitTextRuns(textRuns, measuredLength);
 
-                    var trimmedRuns = new List<TextRun>(splitResult.First.Count + 1);
+                    var trimmedRuns = new List<ShapedTextRun>(splitResult.First.Count + 1);
 
                     trimmedRuns.AddRange(splitResult.First);
 

+ 75 - 99
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs

@@ -6,42 +6,43 @@ namespace Avalonia.Media.TextFormatting
 {
     internal class SimpleTextLine : TextLine
     {
-        public SimpleTextLine(TextPointer textPointer, IReadOnlyList<TextRun> textRuns, TextLineMetrics lineMetrics) :
-            base(textPointer, textRuns, lineMetrics)
-        {
+        private readonly IReadOnlyList<ShapedTextRun> _textRuns;
 
+        public SimpleTextLine(TextPointer textPointer, IReadOnlyList<ShapedTextRun> textRuns, TextLineMetrics lineMetrics)
+        {
+            Text = textPointer;
+            _textRuns = textRuns;
+            LineMetrics = lineMetrics;
         }
 
+        /// <inheritdoc/>
+        public override TextPointer Text { get; }
+
+        /// <inheritdoc/>
+        public override IReadOnlyList<TextRun> TextRuns => _textRuns;
+
+        /// <inheritdoc/>
+        public override TextLineMetrics LineMetrics { get; }
+
+        /// <inheritdoc/>
         public override void Draw(IDrawingContextImpl drawingContext, Point origin)
         {
             var currentX = origin.X;
 
-            foreach (var textRun in TextRuns)
+            foreach (var textRun in _textRuns)
             {
-                if (!(textRun is DrawableTextRun drawableRun))
-                {
-                    continue;
-                }
-
                 var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
                     origin.Y + LineMetrics.BaselineOrigin.Y);
 
-                drawableRun.Draw(drawingContext, baselineOrigin);
+                textRun.Draw(drawingContext, baselineOrigin);
 
-                currentX += drawableRun.Bounds.Width;
+                currentX += textRun.Bounds.Width;
             }
         }
 
-        /// <summary>
-        /// Client to get the character hit corresponding to the specified
-        /// distance from the beginning of the line.
-        /// </summary>
-        /// <param name="distance">distance in text flow direction from the beginning of the line</param>
-        /// <returns>character hit</returns>
+        /// <inheritdoc/>
         public override CharacterHit GetCharacterHitFromDistance(double distance)
         {
-            var first = Text.Start;
-
             if (distance < 0)
             {
                 // hit happens before the line, return the first position
@@ -49,45 +50,30 @@ namespace Avalonia.Media.TextFormatting
             }
 
             // process hit that happens within the line
-            var runIndex = new CharacterHit();
+            var characterHit = new CharacterHit();
 
-            foreach (var run in TextRuns) 
+            foreach (var run in _textRuns)
             {
-                var shapedTextRun = (ShapedTextRun)run;
-
-                first += runIndex.TrailingLength;
+                characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
 
-                runIndex = shapedTextRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
-
-                first += runIndex.FirstCharacterIndex;
-
-                if (distance <= shapedTextRun.Bounds.Width)
+                if (distance <= run.Bounds.Width)
                 {
                     break;
                 }
 
-                distance -= shapedTextRun.Bounds.Width;
+                distance -= run.Bounds.Width;
             }
 
-            return new CharacterHit(first, runIndex.TrailingLength);
+            return characterHit;
         }
 
-        /// <summary>
-        /// Client to get the distance from the beginning of the line from the specified
-        /// character hit.
-        /// </summary>
-        /// <param name="characterHit">character hit of the character to query the distance.</param>
-        /// <returns>distance in text flow direction from the beginning of the line.</returns>
+        /// <inheritdoc/>
         public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
         {
-            return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
+            return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
         }
 
-        /// <summary>
-        /// Client to get the next character hit for caret navigation
-        /// </summary>
-        /// <param name="characterHit">the current character hit</param>
-        /// <returns>the next character hit</returns>
+        /// <inheritdoc/>
         public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
         {
             int nextVisibleCp;
@@ -95,7 +81,7 @@ namespace Avalonia.Media.TextFormatting
 
             if (characterHit.TrailingLength == 0)
             {
-                navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex, out nextVisibleCp);
+                navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex, out nextVisibleCp);
 
                 if (navigableCpFound)
                 {
@@ -104,7 +90,7 @@ namespace Avalonia.Media.TextFormatting
                 }
             }
 
-            navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex + 1, out nextVisibleCp);
+            navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex + 1, out nextVisibleCp);
 
             if (navigableCpFound)
             {
@@ -116,18 +102,14 @@ namespace Avalonia.Media.TextFormatting
             return characterHit;
         }
 
-        /// <summary>
-        /// Client to get the previous character hit for caret navigation
-        /// </summary>
-        /// <param name="characterHit">the current character hit</param>
-        /// <returns>the previous character hit</returns>
+        /// <inheritdoc/>
         public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
         {
-            int previousVisibleCp;
-            bool navigableCpFound;
+            int previousCodepointIndex;
+            bool codepointIndexFound;
 
-            int cpHit = characterHit.FirstCharacterIndex;
-            bool trailingHit = (characterHit.TrailingLength != 0);
+            var cpHit = characterHit.FirstCharacterIndex;
+            var trailingHit = characterHit.TrailingLength != 0;
 
             // Input can be right after the end of the current line. Snap it to be at the end of the line.
             if (cpHit >= Text.Start + Text.Length)
@@ -139,32 +121,28 @@ namespace Avalonia.Media.TextFormatting
 
             if (trailingHit)
             {
-                navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisibleCp);
+                codepointIndexFound = FindPreviousCodepointIndex(cpHit, out previousCodepointIndex);
 
-                if (navigableCpFound)
+                if (codepointIndexFound)
                 {
                     // Move from trailing to leading edge
-                    return new CharacterHit(previousVisibleCp, 0);
+                    return new CharacterHit(previousCodepointIndex, 0);
                 }
             }
 
-            navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisibleCp);
+            codepointIndexFound = FindPreviousCodepointIndex(cpHit - 1, out previousCodepointIndex);
 
-            if (navigableCpFound)
+            if (codepointIndexFound)
             {
                 // Move from leading edge of current character to leading edge of previous
-                return new CharacterHit(previousVisibleCp, 0);
+                return new CharacterHit(previousCodepointIndex, 0);
             }
 
             // Can't move, we're before the first character
             return characterHit;
         }
 
-        /// <summary>
-        /// Client to get the previous character hit after backspacing
-        /// </summary>
-        /// <param name="characterHit">the current character hit</param>
-        /// <returns>the character hit after backspacing</returns>
+        /// <inheritdoc/>
         public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
         {
             // same operation as move-to-previous
@@ -172,44 +150,41 @@ namespace Avalonia.Media.TextFormatting
         }
 
         /// <summary>
-        /// Get distance from line start to the specified cp
+        /// Get distance from line start to the specified codepoint index
         /// </summary>
-        private double DistanceFromCp(int currentIndex)
+        private double DistanceFromCodepointIndex(int codepointIndex)
         {
-            var distance = 0.0;
-            var dcp = currentIndex - Text.Start;
+            var currentDistance = 0.0;
 
-            foreach (var textRun in TextRuns)
+            foreach (var textRun in _textRuns)
             {
-                var run = (ShapedTextRun)textRun;
-
-                distance += run.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(dcp));
-
-                if (dcp <= run.Text.Length)
+                if (codepointIndex > textRun.Text.End)
                 {
-                    break;
+                    currentDistance += textRun.Bounds.Width;
+
+                    continue;
                 }
 
-                dcp -= run.Text.Length;
+                return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
             }
 
-            return distance;
+            return currentDistance;
         }
 
         /// <summary>
-        /// Search forward from the given cp index (inclusive) to find the next navigable cp index.
-        /// Return true if one such cp is found, false otherwise.
+        /// Search forward from the given codepoint index (inclusive) to find the next navigable codepoint index.
+        /// Return true if one such codepoint index is found, false otherwise.
         /// </summary>
-        private bool FindNextVisibleCp(int cp, out int cpVisible)
+        private bool FindNextCodepointIndex(int codepointIndex, out int nextCodepointIndex)
         {
-            cpVisible = cp;
+            nextCodepointIndex = codepointIndex;
 
-            if (cp >= Text.Start + Text.Length)
+            if (codepointIndex >= Text.Start + Text.Length)
             {
                 return false; // Cannot go forward anymore
             }
 
-            GetRunIndexAtCp(cp, out var runIndex, out var cpRunStart);
+            GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var cpRunStart);
 
             while (runIndex < TextRuns.Count)
             {
@@ -217,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
                 // navigable.
                 if (runIndex < TextRuns.Count)
                 {
-                    cpVisible = Math.Max(cpRunStart, cp);
+                    nextCodepointIndex = Math.Max(cpRunStart, codepointIndex);
                     return true;
                 }
 
@@ -228,29 +203,29 @@ namespace Avalonia.Media.TextFormatting
         }
 
         /// <summary>
-        /// Search backward from the given cp index (inclusive) to find the previous navigable cp index.
-        /// Return true if one such cp is found, false otherwise.
+        /// Search backward from the given codepoint index (inclusive) to find the previous navigable codepoint index.
+        /// Return true if one such codepoint is found, false otherwise.
         /// </summary>
-        private bool FindPreviousVisibleCp(int cp, out int cpVisible)
+        private bool FindPreviousCodepointIndex(int codepointIndex, out int previousCodepointIndex)
         {
-            cpVisible = cp;
+            previousCodepointIndex = codepointIndex;
 
-            if (cp < Text.Start)
+            if (codepointIndex < Text.Start)
             {
                 return false; // Cannot go backward anymore.
             }
 
             // Position the cpRunEnd at the end of the span that contains the given cp
-            GetRunIndexAtCp(cp, out var runIndex, out var cpRunEnd);
+            GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var codepointIndexAtRunEnd);
 
-            cpRunEnd += TextRuns[runIndex].Text.End;
+            codepointIndexAtRunEnd += TextRuns[runIndex].Text.End;
 
             while (runIndex >= 0)
             {
                 // Visible content has caret stops at its leading edge.
                 if (runIndex + 1 < TextRuns.Count)
                 {
-                    cpVisible = Math.Min(cpRunEnd, cp);
+                    previousCodepointIndex = Math.Min(codepointIndexAtRunEnd, codepointIndex);
                     return true;
                 }
 
@@ -258,25 +233,26 @@ namespace Avalonia.Media.TextFormatting
                 if (runIndex == TextRuns.Count)
                 {
                     // Get the cp index at the beginning of the newline sequence.
-                    cpVisible = cpRunEnd - TextRuns[runIndex].Text.Length + 1;
+                    previousCodepointIndex = codepointIndexAtRunEnd - TextRuns[runIndex].Text.Length + 1;
                     return true;
                 }
 
-                cpRunEnd -= TextRuns[runIndex--].Text.Length;
+                codepointIndexAtRunEnd -= TextRuns[runIndex--].Text.Length;
             }
 
             return false;
         }
 
-        private void GetRunIndexAtCp(int cp, out int runIndex, out int cpRunStart)
+        private void GetRunIndexAtCodepointIndex(int codepointIndex, out int runIndex, out int codepointIndexAtRunStart)
         {
-            cpRunStart = Text.Start;
+            codepointIndexAtRunStart = Text.Start;
             runIndex = 0;
 
             // Find the span that contains the given cp
-            while (runIndex < TextRuns.Count && cpRunStart + TextRuns[runIndex].Text.Length <= cp)
+            while (runIndex < TextRuns.Count &&
+                   codepointIndexAtRunStart + TextRuns[runIndex].Text.Length <= codepointIndex)
             {
-                cpRunStart += TextRuns[runIndex++].Text.Length;
+                codepointIndexAtRunStart += TextRuns[runIndex++].Text.Length;
             }
         }
     }

+ 14 - 9
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Media.TextFormatting
 
         private readonly ReadOnlySlice<char> _text;
         private readonly TextParagraphProperties _paragraphProperties;
-        private readonly TextStyleRun[] _textStyleOverrides;
+        private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TextLayout" /> class.
@@ -47,7 +47,7 @@ namespace Avalonia.Media.TextFormatting
             TextDecorationCollection textDecorations = null,
             double maxWidth = double.PositiveInfinity,
             double maxHeight = double.PositiveInfinity,
-            TextStyleRun[] textStyleOverrides = null)
+            IReadOnlyList<TextStyleRun> textStyleOverrides = null)
         {
             _text = string.IsNullOrEmpty(text) ?
                 new ReadOnlySlice<char>() :
@@ -276,10 +276,10 @@ namespace Avalonia.Media.TextFormatting
         {
             private readonly ReadOnlySlice<char> _text;
             private readonly TextStyle _defaultStyle;
-            private readonly TextStyleRun[] _textStyleOverrides;
+            private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
 
             public FormattedTextSource(ReadOnlySlice<char> text, TextStyle defaultStyle,
-                TextStyleRun[] textStyleOverrides)
+                IReadOnlyList<TextStyleRun> textStyleOverrides)
             {
                 _text = text;
                 _defaultStyle = defaultStyle;
@@ -311,8 +311,13 @@ namespace Avalonia.Media.TextFormatting
             /// The created text style run.
             /// </returns>
             private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice<char> text,
-                TextStyle defaultTextStyle, ReadOnlySpan<TextStyleRun> textStyleOverrides)
+                TextStyle defaultTextStyle, IReadOnlyList<TextStyleRun> textStyleOverrides)
             {
+                if(textStyleOverrides == null || textStyleOverrides.Count == 0)
+                {
+                    return new TextStyleRun(new TextPointer(text.Start, text.Length), defaultTextStyle);
+                }
+
                 var currentTextStyle = defaultTextStyle;
 
                 var hasOverride = false;
@@ -321,7 +326,7 @@ namespace Avalonia.Media.TextFormatting
 
                 var length = 0;
 
-                for (; i < textStyleOverrides.Length; i++)
+                for (; i < textStyleOverrides.Count; i++)
                 {
                     var styleOverride = textStyleOverrides[i];
 
@@ -341,7 +346,7 @@ namespace Avalonia.Media.TextFormatting
                     if (textPointer.Start > text.Start)
                     {
                         if (styleOverride.Style.TextFormat != currentTextStyle.TextFormat ||
-                            currentTextStyle.Foreground != styleOverride.Style.Foreground)
+                            !currentTextStyle.Foreground.Equals(styleOverride.Style.Foreground))
                         {
                             length = Math.Min(Math.Abs(textPointer.Start - text.Start), text.Length);
 
@@ -361,9 +366,9 @@ namespace Avalonia.Media.TextFormatting
                     currentTextStyle = styleOverride.Style;
                 }
 
-                if (length < text.Length && i == textStyleOverrides.Length)
+                if (length < text.Length && i == textStyleOverrides.Count)
                 {
-                    if (currentTextStyle.Foreground == defaultTextStyle.Foreground &&
+                    if (currentTextStyle.Foreground.Equals(defaultTextStyle.Foreground) &&
                         currentTextStyle.TextFormat == defaultTextStyle.TextFormat)
                     {
                         length = text.Length;

+ 3 - 15
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@@ -11,25 +11,13 @@ namespace Avalonia.Media.TextFormatting
     /// </summary>
     public abstract class TextLine
     {
-        protected TextLine()
-        {
-            
-        }
-
-        protected TextLine(TextPointer text, IReadOnlyList<TextRun> textRuns, TextLineMetrics lineMetrics)
-        {
-            Text = text;
-            TextRuns = textRuns;
-            LineMetrics = lineMetrics;
-        }
-
         /// <summary>
         /// Gets the text.
         /// </summary>
         /// <value>
         /// The text pointer.
         /// </value>
-        public TextPointer Text { get; protected set; }
+        public abstract TextPointer Text { get; }
 
         /// <summary>
         /// Gets the text runs.
@@ -37,7 +25,7 @@ namespace Avalonia.Media.TextFormatting
         /// <value>
         /// The text runs.
         /// </value>
-        public IReadOnlyList<TextRun> TextRuns { get; protected set; }
+        public abstract IReadOnlyList<TextRun> TextRuns { get; }
 
         /// <summary>
         /// Gets the line metrics.
@@ -45,7 +33,7 @@ namespace Avalonia.Media.TextFormatting
         /// <value>
         /// The line metrics.
         /// </value>
-        public TextLineMetrics LineMetrics { get; protected set; }
+        public abstract TextLineMetrics LineMetrics { get; }
 
         /// <summary>
         /// Draws the <see cref="TextLine"/> at the given origin.

+ 1 - 6
src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs

@@ -43,12 +43,7 @@ namespace Avalonia.Media.TextFormatting
             }
         }
 
-        /// <summary>
-        /// Shapes the specified text and returns a resulting glyph run.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <param name="textFormat">The text format.</param>
-        /// <returns>A shaped glyph run.</returns>
+        /// <inheritdoc cref="ITextShaperImpl.ShapeText"/>
         public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat)
         {
             return _platformImpl.ShapeText(text, textFormat);

+ 56 - 14
tests/Avalonia.Skia.UnitTests/SimpleTextFormatterTests.cs

@@ -43,7 +43,7 @@ namespace Avalonia.Skia.UnitTests
             {
                 var defaultTextRunStyle = new TextStyle(Typeface.Default, 12, Brushes.Black);
 
-                var textSource = new MultipleBufferTextSource(defaultTextRunStyle);
+                var textSource = new MultiBufferTextSource(defaultTextRunStyle);
 
                 var formatter = new SimpleTextFormatter();
 
@@ -56,18 +56,20 @@ namespace Avalonia.Skia.UnitTests
             }
         }
 
-        private class MultipleBufferTextSource : ITextSource
+        private class MultiBufferTextSource : ITextSource
         {
             private readonly string[] _runTexts;
             private readonly TextStyle _defaultStyle;
 
-            public MultipleBufferTextSource(TextStyle defaultStyle)
+            public MultiBufferTextSource(TextStyle defaultStyle)
             {
                 _defaultStyle = defaultStyle;
 
                 _runTexts = new[] { "A123456789", "B123456789", "C123456789", "D123456789", "E123456789" };
             }
 
+            public TextPointer TextPointer => new TextPointer(0, 50);
+
             public TextRun GetTextRun(int textSourceIndex)
             {
                 if (textSourceIndex == 50)
@@ -197,7 +199,7 @@ namespace Avalonia.Skia.UnitTests
         }
 
         [Fact]
-        public void Should_Split_Run_On_Direction()
+        public void Should_Split_Run_On_Script()
         {
             using (Start())
             {
@@ -219,18 +221,36 @@ namespace Avalonia.Skia.UnitTests
         {
             using (Start())
             {
-                const string text = "0123456789";
-
-                var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
+                var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
 
                 var formatter = new SimpleTextFormatter();
 
                 var textLine =
                     formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
 
-                var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(text.Length));
+                var currentDistance = 0.0;
+
+                foreach (var run in textLine.TextRuns)
+                {
+                    var textRun = (ShapedTextRun)run;
+
+                    var glyphRun = textRun.GlyphRun;
 
-                Assert.Equal(textLine.LineMetrics.Size.Width, distance);
+                    for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+                    {
+                        var cluster = glyphRun.GlyphClusters[i];
+
+                        var advance = glyphRun.GlyphAdvances[i];
+
+                        var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster));
+
+                        Assert.Equal(currentDistance, distance);
+
+                        currentDistance += advance;
+                    }
+                }
+
+                Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(textSource.TextPointer.Length)));
             }
         }
 
@@ -239,18 +259,40 @@ namespace Avalonia.Skia.UnitTests
         {
             using (Start())
             {
-                const string text = "0123456789";
-
-                var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
+                var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
 
                 var formatter = new SimpleTextFormatter();
 
                 var textLine =
                     formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
 
-                var characterHit = textLine.GetCharacterHitFromDistance(textLine.LineMetrics.Size.Width);
+                var currentDistance = 0.0;
+
+                CharacterHit characterHit;
+
+                foreach (var run in textLine.TextRuns)
+                {
+                    var textRun = (ShapedTextRun)run;
+
+                    var glyphRun = textRun.GlyphRun;
+
+                    for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+                    {
+                        var cluster = glyphRun.GlyphClusters[i];
+
+                        var advance = glyphRun.GlyphAdvances[i];
+
+                        characterHit = textLine.GetCharacterHitFromDistance(currentDistance);
+
+                        Assert.Equal(cluster, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
+
+                        currentDistance += advance;
+                    }
+                }
+
+                characterHit = textLine.GetCharacterHitFromDistance(textLine.LineMetrics.Size.Width);
 
-                Assert.Equal(textLine.Text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
+                Assert.Equal(textSource.TextPointer.End, characterHit.FirstCharacterIndex);
             }
         }