浏览代码

Make sure non text runs are properly ordered by the bidi reorder logic (#14183)

Benedikt Stebner 1 年之前
父节点
当前提交
2a7b5568b7

+ 24 - 5
src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs

@@ -30,18 +30,25 @@ namespace Avalonia.Media.TextFormatting
 
             try
             {
+                sbyte? previousLevel = null;
+
                 _runs.Add(textRuns.Length);
 
                 // Build up the collection of ordered runs.
                 for (var i = 0; i < textRuns.Length; i++)
                 {
                     var textRun = textRuns[i];
-                    _runs[i] = new OrderedBidiRun(i, textRun, GetRunBidiLevel(textRun, flowDirection));
+
+                    var orderedRun = new OrderedBidiRun(i, textRun, GetRunBidiLevel(textRun, flowDirection, previousLevel));
+
+                    _runs[i] = orderedRun;
 
                     if (i > 0)
                     {
                         _runs[i - 1].NextRunIndex = i;
                     }
+
+                    previousLevel = orderedRun.Level;
                 }
 
                 // Reorder them into visual order.
@@ -72,7 +79,8 @@ namespace Avalonia.Media.TextFormatting
 
                 for (var i = 0; i < textRuns.Length; i++)
                 {
-                    var level = GetRunBidiLevel(textRuns[i], flowDirection);
+                    var level = _runs[i].Level;
+
                     if (level > max)
                     {
                         max = level;
@@ -150,15 +158,26 @@ namespace Avalonia.Media.TextFormatting
             }
         }
 
-        private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection)
+        private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection, sbyte? previousLevel)
         {
             if (run is ShapedTextRun shapedTextRun)
             {
                 return shapedTextRun.BidiLevel;
             }
 
-            var defaultLevel = flowDirection == FlowDirection.LeftToRight ? 0 : 1;
-            return (sbyte)defaultLevel;
+            var defaultLevel = (sbyte)(flowDirection == FlowDirection.LeftToRight ? 0 : 1);
+
+            if (run is TextEndOfLine)
+            {
+                return defaultLevel;
+            }
+
+            if(previousLevel is not null)
+            {
+                return previousLevel.Value;
+            }
+
+            return defaultLevel;
         }
 
         /// <summary>

+ 5 - 3
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Media.TextFormatting
     internal sealed class TextFormatterImpl : TextFormatter
     {
         private static readonly char[] s_empty = { ' ' };
-        private static readonly char[] s_defaultText = new char[TextRun.DefaultTextSourceLength];
+        private static readonly string s_defaultText = new string('a', TextRun.DefaultTextSourceLength);
 
         [ThreadStatic] private static BidiData? t_bidiData;
         [ThreadStatic] private static BidiAlgorithm? t_bidiAlgorithm;
@@ -206,9 +206,11 @@ namespace Avalonia.Media.TextFormatting
                 if (!textRun.Text.IsEmpty)
                     text = textRun.Text.Span;
                 else if (textRun.Length == TextRun.DefaultTextSourceLength)
-                    text = s_defaultText;
+                    text = s_defaultText.AsSpan();
                 else
-                    text = new char[textRun.Length];
+                {
+                    text = new string('a', textRun.Length).AsSpan();
+                }
 
                 bidiData.Append(text);
             }

+ 29 - 0
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@@ -135,6 +135,35 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
+        [Fact]
+        public void Should_Format_TextLine_With_Non_Text_TextRuns_RightToLeft()
+        {
+            using (Start())
+            {
+                var defaultProperties =
+                    new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black);
+
+                var textSource = new TextSourceWithDummyRuns(defaultProperties);
+
+                var formatter = new TextFormatterImpl();
+
+                var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+                    new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
+
+                Assert.NotNull(textLine);
+
+                Assert.Equal(5, textLine.TextRuns.Count);
+
+                Assert.Equal(14, textLine.Length);
+
+                var second = textLine.TextRuns[1] as ShapedTextRun;
+
+                Assert.NotNull(second);
+
+                Assert.Equal("Hello".AsMemory(), second.Text);
+            }
+        }
+
         [Fact]
         public void Should_Format_TextRuns_With_TextRunStyles()
         {