Browse Source

Fixes TextLineImpl.GetTextBounds with trailing zero width (#19616)

* Fixes TextLineImpl.GetTextBounds with trailing zero width

* Add assert
Benedikt Stebner 1 month ago
parent
commit
ec9f50db3d

+ 12 - 6
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -1118,23 +1118,29 @@ namespace Avalonia.Media.TextFormatting
             }
 
             // Find the start of the hit
-            var startHit = currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
-            var startHitIndex = startHit.FirstCharacterIndex + startHit.TrailingLength;
+            var startHit = currentRun.GlyphRun.FindNearestCharacterHit(startIndex, out _);
+            var startHitIndex = startHit.FirstCharacterIndex;
+
+            //If the requested text range starts at the trailing edge we need to move at the end of the hit
+            if(startHitIndex < startIndex)
+            {
+                startHitIndex += startHit.TrailingLength;
+            }
 
             //Find the next possible position that contains the endIndex
-            var nearestCharacterHit = currentRun.GlyphRun.FindNearestCharacterHit(endIndex, out _);
+            var nearestEndHit = currentRun.GlyphRun.FindNearestCharacterHit(endIndex, out _);
 
             int endHitIndex;
 
-            if (nearestCharacterHit.FirstCharacterIndex < endIndex)
+            if (nearestEndHit.FirstCharacterIndex < endIndex)
             {
                 //The hit is inside or at the trailing edge
-                endHitIndex = nearestCharacterHit.FirstCharacterIndex + nearestCharacterHit.TrailingLength;
+                endHitIndex = nearestEndHit.FirstCharacterIndex + nearestEndHit.TrailingLength;
             }
             else
             {
                 //The hit is at the leading edge
-                endHitIndex = nearestCharacterHit.FirstCharacterIndex;
+                endHitIndex = nearestEndHit.FirstCharacterIndex;
             }
 
             var coveredLength = Math.Max(0, Math.Abs(startHitIndex - endHitIndex) - clusterOffset);

+ 38 - 0
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@@ -2135,6 +2135,44 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
+        [Fact]
+        public void Should_GetTextBounds_Trailing_ZeroWidth()
+        {
+            var text = "dasdsad\r\n";
+
+            using (Start())
+            {
+                var typeface = Typeface.Default;
+
+                var defaultProperties = new GenericTextRunProperties(typeface);
+                var shaperOption = new TextShaperOptions(typeface.GlyphTypeface);
+
+                var textSource = new SingleBufferTextSource(text, defaultProperties);
+
+                var formatter = new TextFormatterImpl();
+
+                var textLine =
+                    formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+                        new GenericTextParagraphProperties(defaultProperties));
+
+                Assert.NotNull(textLine);
+
+                var textBounds = textLine.GetTextBounds(7, 3);
+
+                Assert.NotEmpty(textBounds);
+
+                var firstBounds = textBounds[0];
+
+                Assert.NotEmpty(firstBounds.TextRunBounds);
+
+                var firstRunBounds = firstBounds.TextRunBounds[0];
+
+                Assert.Equal(7, firstRunBounds.TextSourceCharacterIndex);
+
+                Assert.Equal(2, firstRunBounds.Length);
+            }
+        }
+
         private class FixedRunsTextSource : ITextSource
         {
             private readonly IReadOnlyList<TextRun> _textRuns;