diff --git a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs b/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs index 39ef8cce48..85fea48edb 100644 --- a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs +++ b/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; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 694961cb94..1b477db1a8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/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); } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index f80c89e23d..83c5fc377c 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/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() {