[Text] Properly handle DrawableRun.Baseline (#19896)
* Add failing test * Also handle decent
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user