Browse Source

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

pull/14226/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
2a7b5568b7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs
  2. 8
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  3. 29
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

29
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>

8
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
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()
{

Loading…
Cancel
Save