Browse Source

[Text] Properly handle DrawableRun.Baseline (#19896)

* Add failing test

* Also handle decent
fixes/gpu-interop-check-dedicated-allocation-needed
Benedikt Stebner 3 months ago
committed by GitHub
parent
commit
8c8545bc52
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  2. 146
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

11
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1306,9 +1306,16 @@ namespace Avalonia.Media.TextFormatting
case DrawableTextRun drawableTextRun:
{
if (drawableTextRun.Size.Height > -ascent)
if (drawableTextRun.Baseline > -ascent)
{
ascent = -drawableTextRun.Size.Height;
ascent = -drawableTextRun.Baseline;
}
var bottom = drawableTextRun.Size.Height - drawableTextRun.Baseline;
if (bottom > descent)
{
descent = bottom;
}
break;

146
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -905,7 +905,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.NotNull(textLine.TextLineBreak.TextEndOfLine);
}
}
[Fact]
public void Should_HitTestStringWithInvisibleRuns()
{
@ -913,7 +913,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
//var textSource = new ListTextSource(
using (Start())
{
@ -923,7 +923,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Red));
var source = new ListTextSource(new InvisibleRun(1), hello, new InvisibleRun(1), world);
var textLine =
TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
@ -939,7 +939,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
VerifyHit(8);
}
}
[Fact]
public void GetTextBounds_For_TextLine_With_ZeroWidthSpaces_Does_Not_Freeze()
{
@ -952,7 +952,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black));
var source = new ListTextSource(text, new InvisibleRun(1), new TextEndOfParagraph());
var textLine =
TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
@ -968,9 +968,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Theory]
[InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)]
[InlineData(TextWrapping.NoWrap), InlineData(TextWrapping.Wrap), InlineData(TextWrapping.WrapWithOverflow)]
public void Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Produce_Empty_Lines(TextWrapping wrapping)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black);
@ -985,25 +985,25 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(200d, textLine.WidthIncludingTrailingWhitespace);
}
}
[Theory]
[InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)]
[InlineData(TextWrapping.NoWrap), InlineData(TextWrapping.Wrap), InlineData(TextWrapping.WrapWithOverflow)]
public void Line_Formatting_For_Oversized_Embedded_Runs_Inside_Normal_Text_Does_Not_Produce_Empty_Lines(
TextWrapping wrapping)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties,
textWrapping: wrapping);
using (Start())
{
var typeface = new Typeface(FontFamily.Parse("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#DejaVu Sans"));
var text1 = new TextCharacters("Hello",
new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black));
var text2 = new TextCharacters("world",
new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black));
var source = new ListTextSource(
text1,
new RectangleRun(new Rect(0, 0, 200, 10), Brushes.Aqua),
@ -1014,15 +1014,15 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var lines = new List<TextLine>();
var dcp = 0;
for (var c = 0;; c++)
for (var c = 0; ; c++)
{
Assert.True(c < 1000, "Infinite loop");
var textLine = TextFormatter.Current.FormatLine(source, dcp, 30, paragraphProperties);
Assert.NotNull(textLine);
lines.Add(textLine);
dcp += textLine.Length;
if (textLine.TextLineBreak is {} eol && eol.TextEndOfLine is TextEndOfParagraph)
if (textLine.TextLineBreak is { } eol && eol.TextEndOfLine is TextEndOfParagraph)
break;
}
@ -1046,25 +1046,25 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public override double Indent => default;
public override double DefaultIncrementalTab => 64;
}
[Fact]
public void Line_With_IncrementalTab_Should_Return_Correct_Backspace_Position()
{
using (Start())
{
var typeface = new Typeface(FontFamily.Parse("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#DejaVu Sans"));
var defaultRunProperties = new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black);
var paragraphProperties = new IncrementalTabProperties(defaultRunProperties);
var text = new TextCharacters("ff",
new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black));
var source = new ListTextSource(text);
var textLine = TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paragraphProperties);
Assert.NotNull(textLine);
var backspaceHit = textLine.GetBackspaceCaretCharacterHit(new CharacterHit(2));
Assert.Equal(1, backspaceHit.FirstCharacterIndex);
Assert.Equal(0, backspaceHit.TrailingLength);
@ -1114,6 +1114,102 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void DrawableRun_With_Same_Baseline_And_Size_Should_Not_Alter_LineHeight()
{
using (Start())
{
var text = "ABC";
var typeface = new Typeface(new FontFamily(new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests"), "Noto Mono"));
var defaultRunProperties = new GenericTextRunProperties(typeface);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties, textWrapping: TextWrapping.Wrap);
var embeddedTextLine = TextFormatter.Current.FormatLine(new SimpleTextSource(text, defaultRunProperties), 0, 120, paragraphProperties);
Assert.NotNull(embeddedTextLine);
var expectedHeight = embeddedTextLine.Height;
var expectedBaseline = embeddedTextLine.Baseline;
var textSource = new ListTextSource(new TextCharacters("ABC", defaultRunProperties), new EmbeddedTextLineRun(embeddedTextLine));
var textLine = TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
Assert.NotNull(textLine);
Assert.Equal(expectedHeight, textLine.Height);
Assert.Equal(expectedBaseline, textLine.Baseline);
}
}
[Fact]
public void DrawableRun_With_Same_Baseline_And_BiggerHeight_Should_Not_Alter_Baseline()
{
using (Start())
{
var text = "ABC";
var typeface = new Typeface(new FontFamily(new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests"), "Noto Mono"));
var defaultRunProperties = new GenericTextRunProperties(typeface);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties, textWrapping: TextWrapping.Wrap);
var embeddedTextLine = TextFormatter.Current.FormatLine(new SimpleTextSource(text, defaultRunProperties), 0, 120, paragraphProperties);
Assert.NotNull(embeddedTextLine);
var expectedHeight = embeddedTextLine.Height + 10;
var embeddedSize = new Size(embeddedTextLine.Width, expectedHeight);
var expectedBaseline = embeddedTextLine.Baseline;
var textSource = new ListTextSource(new TextCharacters("ABC", defaultRunProperties), new CustomDrawableRun(embeddedSize, expectedBaseline));
var textLine = TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
Assert.NotNull(textLine);
Assert.Equal(expectedHeight, textLine.Height);
Assert.Equal(expectedBaseline, textLine.Baseline);
}
}
private class CustomDrawableRun : DrawableTextRun
{
public CustomDrawableRun(Size size, double baseLine)
{
Size = size;
Baseline = baseLine;
}
public override Size Size { get; }
public override double Baseline { get; }
public override void Draw(DrawingContext drawingContext, Point origin)
{
// no op
}
}
private class EmbeddedTextLineRun : DrawableTextRun
{
private readonly TextLine _textLine;
public EmbeddedTextLineRun(TextLine textLine)
{
_textLine = textLine;
}
public override Size Size => new Size(_textLine.Width, _textLine.Height);
public override double Baseline => _textLine.Baseline;
public override void Draw(DrawingContext drawingContext, Point origin)
{
_textLine.Draw(drawingContext, origin);
}
}
protected readonly record struct SimpleTextSource : ITextSource
{
private readonly string _text;
@ -1183,21 +1279,21 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
return new TextCharacters(_text, new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black));
}
}
internal class ListTextSource : ITextSource
{
private readonly List<TextRun> _runs;
public ListTextSource(params TextRun[] runs) : this((IEnumerable<TextRun>)runs)
{
}
public ListTextSource(IEnumerable<TextRun> runs)
{
_runs = runs.ToList();
}
public TextRun? GetTextRun(int textSourceIndex)
{
var off = 0;
@ -1240,7 +1336,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
}
private class InvisibleRun : TextRun
{
public InvisibleRun(int length)

Loading…
Cancel
Save