فهرست منبع

Handle extra lines during hit testing

Benedikt Stebner 2 سال پیش
والد
کامیت
cc507ba359

+ 7 - 6
src/Avalonia.Base/Media/GlyphRun.cs

@@ -643,12 +643,13 @@ namespace Avalonia.Media
                 lastCluster = _glyphInfos[_glyphInfos.Count - 1].GlyphCluster;
             }
 
+            var isReversed = firstCluster > lastCluster;
+
             if (!IsLeftToRight)
             {
                 (lastCluster, firstCluster) = (firstCluster, lastCluster);
             }
 
-            var isReversed = firstCluster > lastCluster;
             var height = GlyphTypeface.Metrics.LineSpacing * Scale;
             var widthIncludingTrailingWhitespace = 0d;
 
@@ -766,15 +767,13 @@ namespace Avalonia.Media
 
             if (!charactersSpan.IsEmpty)
             {
-                var characterIndex = 0;
+                var characterIndex = charactersSpan.Length - 1;
 
                 for (var i = 0; i < _glyphInfos.Count; i++)
                 {
                     var currentCluster = _glyphInfos[i].GlyphCluster;
                     var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
 
-                    characterIndex += characterLength;
-
                     if (!codepoint.IsWhiteSpace)
                     {
                         break;
@@ -784,9 +783,9 @@ namespace Avalonia.Media
 
                     var j = i;
 
-                    while (j - 1 >= 0)
+                    while (j + 1 < _glyphInfos.Count)
                     {
-                        var nextCluster = _glyphInfos[--j].GlyphCluster;
+                        var nextCluster = _glyphInfos[++j].GlyphCluster;
 
                         if (currentCluster == nextCluster)
                         {
@@ -798,6 +797,8 @@ namespace Avalonia.Media
                         break;
                     }
 
+                    characterIndex -= clusterLength;
+
                     if (codepoint.IsBreakChar)
                     {
                         newLineLength += clusterLength;

+ 2 - 0
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -684,7 +684,9 @@ namespace Avalonia.Media.TextFormatting
             var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) };
 
             var line = new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection);
+
             line.FinalizeLine();
+
             return line;
         }
 

+ 12 - 10
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
         /// <summary>
         /// Gets the text spacing.
         /// </summary>
-        public double LetterSpacing  => _paragraphProperties.LetterSpacing;
+        public double LetterSpacing => _paragraphProperties.LetterSpacing;
 
         /// <summary>
         /// Gets the text lines.
@@ -271,11 +271,13 @@ namespace Avalonia.Media.TextFormatting
 
             var currentY = 0.0;
 
-            foreach (var textLine in _textLines)
+            for (var i = 0; i < _textLines.Length; i++)
             {
+                var textLine = _textLines[i];
+
                 var end = textLine.FirstTextSourceIndex + textLine.Length;
 
-                if (end <= textPosition && end < _textSourceLength)
+                if (end <= textPosition && i + 1 < _textLines.Length)
                 {
                     currentY += textLine.Height;
 
@@ -511,7 +513,7 @@ namespace Avalonia.Media.TextFormatting
             {
                 var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
 
-                UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, 
+                UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
                     ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
 
                 return new TextLine[] { textLine };
@@ -638,13 +640,13 @@ namespace Avalonia.Media.TextFormatting
         }
 
         private void UpdateMetrics(
-            TextLine currentLine, 
-            ref double lineStartOfLongestLine, 
-            ref Point origin, 
-            ref bool first, 
+            TextLine currentLine,
+            ref double lineStartOfLongestLine,
+            ref Point origin,
+            ref bool first,
             ref double accBlackBoxLeft,
-            ref double accBlackBoxTop, 
-            ref double accBlackBoxRight, 
+            ref double accBlackBoxTop,
+            ref double accBlackBoxRight,
             ref double accBlackBoxBottom)
         {
             var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;

+ 9 - 9
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -640,14 +640,14 @@ namespace Avalonia.Media.TextFormatting
 
             bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds)
             {
-                if(currentBounds.FlowDirection != lastBounds.FlowDirection)
+                if (currentBounds.FlowDirection != lastBounds.FlowDirection)
                 {
                     return false;
                 }
 
-                if(currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
+                if (currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
                 {
-                    foreach(var runBounds in currentBounds.TextRunBounds)
+                    foreach (var runBounds in currentBounds.TextRunBounds)
                     {
                         lastBounds.TextRunBounds.Add(runBounds);
                     }
@@ -657,7 +657,7 @@ namespace Avalonia.Media.TextFormatting
                     return true;
                 }
 
-                if(currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
+                if (currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
                 {
                     for (int i = 0; i < currentBounds.TextRunBounds.Count; i++)
                     {
@@ -730,7 +730,7 @@ namespace Avalonia.Media.TextFormatting
 
                 if (coveredLength > 0)
                 {
-                    if(lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
+                    if (lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
                     {
                         currentBounds = lastBounds;
 
@@ -739,7 +739,7 @@ namespace Avalonia.Media.TextFormatting
                     else
                     {
                         result.Add(currentBounds);
-                    } 
+                    }
 
                     lastBounds = currentBounds;
 
@@ -1002,14 +1002,14 @@ namespace Avalonia.Media.TextFormatting
 
         public void FinalizeLine()
         {
+            _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
+
             _textLineMetrics = CreateLineMetrics();
 
             if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine)
             {
                 _textLineBreak = new TextLineBreak(textEndOfLine);
-            }
-
-            _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
+            }      
         }
 
         /// <summary>

+ 15 - 0
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@@ -1112,6 +1112,21 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
+        [Fact]
+        public void Should_HitTestTextPosition_EndOfLine_RTL()
+        {
+            var text = "גש\r\n";
+
+            using (Start())
+            {
+                var textLayout = new TextLayout(text, Typeface.Default, 12, Brushes.Black, flowDirection: FlowDirection.RightToLeft);
+
+                var rect = textLayout.HitTestTextPosition(text.Length);
+
+                Assert.Equal(14.0625, rect.Top);
+            }
+        }
+
 
         private static IDisposable Start()
         {