Browse Source

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

pull/14226/head
Benedikt Stebner 3 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 try
{ {
sbyte? previousLevel = null;
_runs.Add(textRuns.Length); _runs.Add(textRuns.Length);
// Build up the collection of ordered runs. // Build up the collection of ordered runs.
for (var i = 0; i < textRuns.Length; i++) for (var i = 0; i < textRuns.Length; i++)
{ {
var textRun = textRuns[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) if (i > 0)
{ {
_runs[i - 1].NextRunIndex = i; _runs[i - 1].NextRunIndex = i;
} }
previousLevel = orderedRun.Level;
} }
// Reorder them into visual order. // Reorder them into visual order.
@ -72,7 +79,8 @@ namespace Avalonia.Media.TextFormatting
for (var i = 0; i < textRuns.Length; i++) for (var i = 0; i < textRuns.Length; i++)
{ {
var level = GetRunBidiLevel(textRuns[i], flowDirection); var level = _runs[i].Level;
if (level > max) if (level > max)
{ {
max = level; 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) if (run is ShapedTextRun shapedTextRun)
{ {
return shapedTextRun.BidiLevel; return shapedTextRun.BidiLevel;
} }
var defaultLevel = flowDirection == FlowDirection.LeftToRight ? 0 : 1; var defaultLevel = (sbyte)(flowDirection == FlowDirection.LeftToRight ? 0 : 1);
return (sbyte)defaultLevel;
if (run is TextEndOfLine)
{
return defaultLevel;
}
if(previousLevel is not null)
{
return previousLevel.Value;
}
return defaultLevel;
} }
/// <summary> /// <summary>

8
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

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

Loading…
Cancel
Save