Browse Source

More fixes

pull/8471/head
Benedikt Stebner 4 years ago
parent
commit
eb627f393c
  1. 2
      samples/Sandbox/App.axaml
  2. 14
      samples/Sandbox/MainWindow.axaml
  3. 46
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  4. 58
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  5. 57
      src/Avalonia.Controls/RichTextBlock.cs
  6. 7
      src/Avalonia.Controls/TextBlock.cs
  7. 45
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  8. 17
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

2
samples/Sandbox/App.axaml

@ -3,6 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App"> x:Class="Sandbox.App">
<Application.Styles> <Application.Styles>
<FluentTheme Mode="Dark"/> <FluentTheme Mode="Light"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

14
samples/Sandbox/MainWindow.axaml

@ -1,5 +1,17 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow"> x:Class="Sandbox.MainWindow">
<RichTextBlock Width="200" IsTextSelectionEnabled="True" Text="Multiline TextBlock with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." TextWrapping="Wrap" /> <RichTextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="AliceBlue"
Padding="5"
IsTextSelectionEnabled="True"
FontSize="18"
FontWeight="Bold"
SelectionStart="24"
SelectionEnd="23">
<LineBreak/>
<Run>أَبْجَدِيَّة عَرَبِيَّة</Run>
</RichTextBlock>
</Window> </Window>

46
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -63,7 +63,7 @@ namespace Avalonia.Media.TextFormatting
MaxHeight = maxHeight; MaxHeight = maxHeight;
MaxLines = maxLines; MaxLines = maxLines;
TextLines = CreateTextLines(); TextLines = CreateTextLines();
} }
@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxLines">The maximum number of text lines.</param> /// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout( public TextLayout(
ITextSource textSource, ITextSource textSource,
TextParagraphProperties paragraphProperties, TextParagraphProperties paragraphProperties,
TextTrimming? textTrimming = null, TextTrimming? textTrimming = null,
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting
return new Rect(); return new Rect();
} }
if (textPosition < 0 || textPosition >= _textSourceLength) if (textPosition < 0)
{ {
var lastLine = TextLines[TextLines.Count - 1]; textPosition = _textSourceLength;
var lineX = lastLine.Width;
var lineY = Bounds.Bottom - lastLine.Height;
return new Rect(lineX, lineY, 0, lastLine.Height);
} }
var currentY = 0.0; var currentY = 0.0;
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
{ {
var end = textLine.FirstTextSourceIndex + textLine.Length - 1; var end = textLine.FirstTextSourceIndex + textLine.Length;
if (end < textPosition) if (end <= textPosition && end < _textSourceLength)
{ {
currentY += textLine.Height; currentY += textLine.Height;
@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting
} }
var result = new List<Rect>(TextLines.Count); var result = new List<Rect>(TextLines.Count);
var currentY = 0d; var currentY = 0d;
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length); var textBounds = textLine.GetTextBounds(start, length);
if(textBounds.Count > 0) if (textBounds.Count > 0)
{ {
foreach (var bounds in textBounds) foreach (var bounds in textBounds)
{ {
@ -262,7 +256,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length) if (textLine.FirstTextSourceIndex + textLine.Length >= start + length)
{ {
break; break;
} }
@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting
return GetHitTestResult(currentLine, characterHit, point); return GetHitTestResult(currentLine, characterHit, point);
} }
public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge) public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge)
{ {
if (charIndex < 0) if (charIndex < 0)
@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
if (charIndex >= textLine.FirstTextSourceIndex && if (charIndex >= textLine.FirstTextSourceIndex &&
charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1)) charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
{ {
return index; return index;
@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="left">The current left.</param> /// <param name="left">The current left.</param>
/// <param name="width">The current width.</param> /// <param name="width">The current width.</param>
/// <param name="height">The current height.</param> /// <param name="height">The current height.</param>
private static void UpdateBounds(TextLine textLine,ref double left, ref double width, ref double height) private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height)
{ {
var lineWidth = textLine.WidthIncludingTrailingWhitespace; var lineWidth = textLine.WidthIncludingTrailingWhitespace;
@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
Bounds = new Rect(0,0,0, textLine.Height); Bounds = new Rect(0, 0, 0, textLine.Height);
return new List<TextLine> { textLine }; return new List<TextLine> { textLine };
} }
@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
_paragraphProperties, previousLine?.TextLineBreak); _paragraphProperties, previousLine?.TextLineBreak);
if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
{ {
if(previousLine != null && previousLine.NewLineLength > 0) if (previousLine != null && previousLine.NewLineLength > 0)
{ {
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties); var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
} }
_textSourceLength += textLine.Length; _textSourceLength += textLine.Length;
//Fulfill max height constraint //Fulfill max height constraint
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight) if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
{ {
@ -490,7 +484,7 @@ namespace Avalonia.Media.TextFormatting
} }
//Make sure the TextLayout always contains at least on empty line //Make sure the TextLayout always contains at least on empty line
if(textLines.Count == 0) if (textLines.Count == 0)
{ {
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties); var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
@ -501,7 +495,7 @@ namespace Avalonia.Media.TextFormatting
Bounds = new Rect(left, 0, width, height); Bounds = new Rect(left, 0, width, height);
if(_paragraphProperties.TextAlignment == TextAlignment.Justify) if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
{ {
var whitespaceWidth = 0d; var whitespaceWidth = 0d;
@ -509,7 +503,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
if(lineWhitespaceWidth > whitespaceWidth) if (lineWhitespaceWidth > whitespaceWidth)
{ {
whitespaceWidth = lineWhitespaceWidth; whitespaceWidth = lineWhitespaceWidth;
} }
@ -517,7 +511,7 @@ namespace Avalonia.Media.TextFormatting
var justificationWidth = width - whitespaceWidth; var justificationWidth = width - whitespaceWidth;
if(justificationWidth > 0) if (justificationWidth > 0)
{ {
var justificationProperties = new InterWordJustification(justificationWidth); var justificationProperties = new InterWordJustification(justificationWidth);

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

@ -317,7 +317,7 @@ namespace Avalonia.Media.TextFormatting
{ {
currentGlyphRun = shapedTextCharacters.GlyphRun; currentGlyphRun = shapedTextCharacters.GlyphRun;
if (currentPosition + remainingLength < currentPosition + currentRun.Text.Length) if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length)
{ {
characterHit = new CharacterHit(currentRun.Text.Start + remainingLength); characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
@ -524,27 +524,30 @@ namespace Avalonia.Media.TextFormatting
characterLength = NewLineLength; characterLength = NewLineLength;
} }
var runwidth = endX - startX; var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun); var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runwidth); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
} }
currentWidth += runwidth; currentWidth += runWidth;
currentPosition += characterLength; currentPosition += characterLength;
if (currentPosition > characterIndex) if (currentPosition > characterIndex)
@ -671,22 +674,25 @@ namespace Avalonia.Media.TextFormatting
var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runWidth); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
} }
}
currentWidth += runWidth; currentWidth += runWidth;
currentPosition += characterLength; currentPosition += characterLength;
@ -705,6 +711,8 @@ namespace Avalonia.Media.TextFormatting
} }
} }
result.Reverse();
return result; return result;
} }

57
src/Avalonia.Controls/RichTextBlock.cs

@ -41,9 +41,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue); AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionForegroundBrush));
/// <summary> /// <summary>
/// Defines the <see cref="Inlines"/> property. /// Defines the <see cref="Inlines"/> property.
/// </summary> /// </summary>
@ -68,7 +65,7 @@ namespace Avalonia.Controls
{ {
FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true);
AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty); AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty);
} }
public RichTextBlock() public RichTextBlock()
@ -89,15 +86,6 @@ namespace Avalonia.Controls
set => SetValue(SelectionBrushProperty, value); set => SetValue(SelectionBrushProperty, value);
} }
/// <summary>
/// Gets or sets a value that defines the brush used for selected text.
/// </summary>
public IBrush? SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets a character index for the beginning of the current selection. /// Gets or sets a character index for the beginning of the current selection.
/// </summary> /// </summary>
@ -198,7 +186,7 @@ namespace Avalonia.Controls
} }
} }
public override void Render(DrawingContext context) protected override void RenderTextLayout(DrawingContext context, Point origin)
{ {
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
@ -213,13 +201,16 @@ namespace Avalonia.Controls
var rects = TextLayout.HitTestTextRange(start, length); var rects = TextLayout.HitTestTextRange(start, length);
foreach (var rect in rects) using (context.PushPostTransform(Matrix.CreateTranslation(origin)))
{ {
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); foreach (var rect in rects)
{
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
}
} }
} }
base.Render(context); base.RenderTextLayout(context, origin);
} }
/// <summary> /// <summary>
@ -280,8 +271,9 @@ namespace Avalonia.Controls
/// <returns>A <see cref="TextLayout"/> object.</returns> /// <returns>A <see cref="TextLayout"/> object.</returns>
protected override TextLayout CreateTextLayout(string? text) protected override TextLayout CreateTextLayout(string? text)
{ {
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var defaultProperties = new GenericTextRunProperties( var defaultProperties = new GenericTextRunProperties(
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), typeface,
FontSize, FontSize,
TextDecorations, TextDecorations,
Foreground); Foreground);
@ -328,6 +320,8 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e);
var handled = false; var handled = false;
var modifiers = e.KeyModifiers; var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>(); var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
@ -346,6 +340,8 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
base.OnPointerPressed(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -356,7 +352,9 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed) if (text != null && clickInfo.Properties.IsLeftButtonPressed)
{ {
var point = e.GetPosition(this); var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift); var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
@ -403,6 +401,8 @@ namespace Avalonia.Controls
protected override void OnPointerMoved(PointerEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
base.OnPointerMoved(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -411,11 +411,13 @@ namespace Avalonia.Controls
// selection should not change during pointer move if the user right clicks // selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
var point = e.GetPosition(this); var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
point = new Point( point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(Bounds.Width - 1, 0)), MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(Bounds.Height - 1, 0))); MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0)));
var hit = TextLayout.HitTestPoint(point); var hit = TextLayout.HitTestPoint(point);
@ -425,6 +427,8 @@ namespace Avalonia.Controls
protected override void OnPointerReleased(PointerReleasedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
base.OnPointerReleased(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -437,7 +441,9 @@ namespace Avalonia.Controls
if (e.InitialPressMouseButton == MouseButton.Right) if (e.InitialPressMouseButton == MouseButton.Right)
{ {
var point = e.GetPosition(this); var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var hit = TextLayout.HitTestPoint(point); var hit = TextLayout.HitTestPoint(point);
@ -470,11 +476,6 @@ namespace Avalonia.Controls
InvalidateTextLayout(); InvalidateTextLayout();
break; break;
} }
case nameof(TextProperty):
{
InvalidateTextLayout();
break;
}
} }
} }

7
src/Avalonia.Controls/TextBlock.cs

@ -509,7 +509,12 @@ namespace Avalonia.Controls
} }
} }
TextLayout.Draw(context, new Point(padding.Left, top)); RenderTextLayout(context, new Point(padding.Left, top));
}
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
TextLayout.Draw(context, origin);
} }
protected virtual string? GetText() protected virtual string? GetText()

45
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -910,6 +910,51 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
[Fact]
public void Should_Get_CharacterHit_From_Distance_RTL()
{
using (Start())
{
var text = "أَبْجَدِيَّة عَرَبِيَّة";
var layout = new TextLayout(
text,
Typeface.Default,
12,
Brushes.Black);
var textLine = layout.TextLines[0];
var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0];
var characterHit = textLine.GetCharacterHitFromDistance(0);
Assert.Equal(firstCluster, characterHit.FirstCharacterIndex);
Assert.Equal(text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
var distance = textLine.GetDistanceFromCharacterHit(characterHit);
Assert.Equal(0, distance);
distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0];
Assert.Equal(firstAdvance, distance);
var rect = layout.HitTestTextPosition(22);
Assert.Equal(firstAdvance, rect.Left);
rect = layout.HitTestTextPosition(23);
Assert.Equal(0, rect.Left);
}
}
private static IDisposable Start() private static IDisposable Start()
{ {
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

17
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -867,28 +867,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textBounds = textLine.GetTextBounds(0, 4); var textBounds = textLine.GetTextBounds(0, 4);
var firstRun = textLine.TextRuns[1] as ShapedTextCharacters; var secondRun = textLine.TextRuns[1] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count); Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(4, 3); textBounds = textLine.GetTextBounds(4, 3);
var secondRun = textLine.TextRuns[0] as ShapedTextCharacters; var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count); Assert.Equal(1, textBounds.Count);
Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length)); Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length));
Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 5); textBounds = textLine.GetTextBounds(0, 5);
Assert.Equal(2, textBounds.Count); Assert.Equal(2, textBounds.Count);
Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length))); Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length)));
Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width); Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
Assert.Equal(7.201171875, textBounds[1].Rectangle.Width); Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
Assert.Equal(textLine.Start + 7.201171875, textBounds[1].Rectangle.Right); Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right);
Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
textBounds = textLine.GetTextBounds(0, text.Length); textBounds = textLine.GetTextBounds(0, text.Length);
@ -896,7 +897,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length))); Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
} }
} }
private class FixedRunsTextSource : ITextSource private class FixedRunsTextSource : ITextSource
{ {

Loading…
Cancel
Save