Browse Source

Merge pull request #8671 from Gillibald/fixes/TextProcessingFixes

Text processing fixes
disbar
Max Katz 3 years ago
committed by GitHub
parent
commit
6520eb4275
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  2. 1
      samples/Sandbox/MainWindow.axaml
  3. 106
      src/Avalonia.Base/Media/FormattedText.cs
  4. 13
      src/Avalonia.Base/Media/Geometry.cs
  5. 11
      src/Avalonia.Base/Media/GlyphRun.cs
  6. 253
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  7. 2
      src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
  8. 14
      src/Avalonia.Controls/Documents/InlineCollection.cs
  9. 83
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

5
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace RenderDemo.Pages
{
@ -59,6 +60,10 @@ namespace RenderDemo.Pages
var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(gradient, null, geometry);
var highlightGeometry = formattedText.BuildHighlightGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(null, new ImmutablePen(gradient.ToImmutable(), 2), highlightGeometry);
}
}
}

1
samples/Sandbox/MainWindow.axaml

@ -1,5 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow">
<TextBox />
</Window>

106
src/Avalonia.Base/Media/FormattedText.cs

@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
@ -654,14 +656,16 @@ namespace Avalonia.Media
// line break before _currentLine, needed in case we have to reformat it with collapsing symbol
private TextLineBreak? _previousLineBreak;
private int _position;
private int _length;
internal LineEnumerator(FormattedText text)
{
_previousHeight = 0;
Length = 0;
_length = 0;
_previousLineBreak = null;
Position = 0;
_position = 0;
_lineCount = 0;
_totalHeight = 0;
Current = null;
@ -678,9 +682,17 @@ namespace Avalonia.Media
_nextLine = null;
}
private int Position { get; set; }
public int Position
{
get => _position;
private set => _position = value;
}
private int Length { get; set; }
public int Length
{
get => _length;
private set => _length = value;
}
/// <summary>
/// Gets the current text line in the collection
@ -1292,6 +1304,92 @@ namespace Avalonia.Media
return accumulatedGeometry;
}
/// <summary>
/// Builds a highlight geometry object.
/// </summary>
/// <param name="origin">The origin of the highlight region</param>
/// <returns>Geometry that surrounds the text.</returns>
public Geometry? BuildHighlightGeometry(Point origin)
{
return BuildHighlightGeometry(origin, 0, _text.Length);
}
/// <summary>
/// Builds a highlight geometry object for a given character range.
/// </summary>
/// <param name="origin">The origin of the highlight region.</param>
/// <param name="startIndex">The start index of initial character the bounds should be obtained for.</param>
/// <param name="count">The number of characters the bounds should be obtained for.</param>
/// <returns>Geometry that surrounds the specified character range.</returns>
public Geometry? BuildHighlightGeometry(Point origin, int startIndex, int count)
{
ValidateRange(startIndex, count);
Geometry? accumulatedBounds = null;
using (var enumerator = GetEnumerator())
{
var lineOrigin = origin;
while (enumerator.MoveNext())
{
var currentLine = enumerator.Current!;
int x0 = Math.Max(enumerator.Position, startIndex);
int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count);
// check if this line is intersects with the specified character range
if (x0 < x1)
{
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null)
{
foreach (var bound in highlightBounds)
{
var rect = bound.Rectangle;
if (FlowDirection == FlowDirection.RightToLeft)
{
// Convert logical units (which extend leftward from the right edge
// of the paragraph) to physical units.
//
// Note that since rect is in logical units, rect.Right corresponds to
// the visual *left* edge of the rectangle in the RTL case. Specifically,
// is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine.
//
rect = rect.WithX(enumerator.CurrentParagraphWidth - rect.Right);
}
rect = new Rect(new Point(rect.X + lineOrigin.X, rect.Y + lineOrigin.Y), rect.Size);
RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
if (accumulatedBounds == null)
{
accumulatedBounds = rectangleGeometry;
}
else
{
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
}
}
}
}
AdvanceLineOrigin(ref lineOrigin, currentLine);
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty)
{
return null;
}
return accumulatedBounds;
}
/// <summary>
/// Draws the text object
/// </summary>

13
src/Avalonia.Base/Media/Geometry.cs

@ -185,5 +185,18 @@ namespace Avalonia.Media
var control = e.Sender as Geometry;
control?.InvalidateGeometry();
}
/// <summary>
/// Combines the two geometries using the specified <see cref="GeometryCombineMode"/> and applies the specified transform to the resulting geometry.
/// </summary>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
/// <param name="combineMode">One of the enumeration values that specifies how the geometries are combined.</param>
/// <param name="transform">A transformation to apply to the combined geometry, or <c>null</c>.</param>
/// <returns></returns>
public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
{
return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
}
}
}

11
src/Avalonia.Base/Media/GlyphRun.cs

@ -789,14 +789,15 @@ namespace Avalonia.Media
var clusterLength = 1;
while (i - 1 >= 0)
var j = i;
while (j - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
var nextCluster = GlyphClusters[--j];
if (currentCluster == nextCluster)
{
clusterLength++;
i--;
clusterLength++;
continue;
}
@ -811,7 +812,7 @@ namespace Avalonia.Media
trailingWhitespaceLength += clusterLength;
glyphCount++;
glyphCount += clusterLength;
}
}

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

@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
if (collapsingProperties is null)
{
return this;
}
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{
var currentRun = _textRuns[i];
if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = i;
currentPosition += currentRun.TextSourceLength;
@ -213,14 +213,14 @@ namespace Avalonia.Media.TextFormatting
for (var j = i; i <= rightToLeftIndex; j++)
{
if(j > _textRuns.Count - 1)
if (j > _textRuns.Count - 1)
{
break;
}
currentRun = _textRuns[j];
if(currentDistance + currentRun.Size.Width <= distance)
if (currentDistance + currentRun.Size.Width <= distance)
{
currentDistance += currentRun.Size.Width;
currentPosition -= currentRun.TextSourceLength;
@ -266,10 +266,6 @@ namespace Avalonia.Media.TextFormatting
{
offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
}
//else
//{
// offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
//}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
@ -326,11 +322,11 @@ namespace Avalonia.Media.TextFormatting
continue;
}
break;
}
if(i > index)
if (i > index)
{
while (i >= index)
{
@ -354,7 +350,7 @@ namespace Avalonia.Media.TextFormatting
}
}
if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
{
return Math.Max(0, currentDistance + distance);
@ -534,6 +530,8 @@ namespace Avalonia.Media.TextFormatting
double currentWidth = 0;
var currentRect = Rect.Empty;
TextRunBounds lastRunBounds = default;
for (var index = 0; index < TextRuns.Count; index++)
{
if (TextRuns[index] is not DrawableTextRun currentRun)
@ -543,53 +541,93 @@ namespace Avalonia.Media.TextFormatting
var characterLength = 0;
var endX = startX;
var runWidth = 0.0;
TextRunBounds? currentRunBounds = null;
var currentShapedRun = currentRun as ShapedTextCharacters;
TextRunBounds currentRunBounds;
double combinedWidth;
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = index;
startX += currentShapedRun.Size.Width;
var rightToLeftWidth = currentShapedRun.Size.Width;
while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextCharacters nextShapedRun)
{
var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
{
break;
}
startX += nextShapedRun.Size.Width;
rightToLeftIndex++;
rightToLeftWidth += nextShapedRun.Size.Width;
if (currentPosition + nextShapedRun.TextSourceLength > firstTextSourceIndex + textLength)
{
break;
}
currentShapedRun = nextShapedRun;
}
if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds))
startX = startX + rightToLeftWidth;
currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
remainingLength -= currentRunBounds.Length;
currentPosition = currentRunBounds.TextSourceCharacterIndex + currentRunBounds.Length;
endX = currentRunBounds.Rectangle.Right;
startX = currentRunBounds.Rectangle.Left;
var rightToLeftRunBounds = new List<TextRunBounds> { currentRunBounds };
for (int i = rightToLeftIndex - 1; i >= index; i--)
{
startX = currentRunBounds!.Rectangle.Left;
endX = currentRunBounds.Rectangle.Right;
currentShapedRun = TextRuns[i] as ShapedTextCharacters;
if(currentShapedRun == null)
{
continue;
}
runWidth = currentRunBounds.Rectangle.Width;
currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
rightToLeftRunBounds.Insert(0, currentRunBounds);
remainingLength -= currentRunBounds.Length;
startX = currentRunBounds.Rectangle.Left;
currentPosition += currentRunBounds.Length;
}
combinedWidth = endX - startX;
currentRect = new Rect(startX, 0, combinedWidth, Height);
currentDirection = FlowDirection.RightToLeft;
if (!MathUtilities.IsZero(combinedWidth))
{
result.Add(new TextBounds(currentRect, currentDirection, rightToLeftRunBounds));
}
startX = endX;
}
else
{
if (currentShapedRun != null)
{
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
@ -665,43 +703,46 @@ namespace Avalonia.Media.TextFormatting
characterLength = NewLineLength;
}
runWidth = endX - startX;
currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
combinedWidth = endX - startX;
currentRunBounds = new TextRunBounds(new Rect(startX, 0, combinedWidth, Height), currentPosition, characterLength, currentRun);
currentPosition += characterLength;
remainingLength -= characterLength;
}
if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
startX = endX;
if (currentRunBounds.TextRun != null && !MathUtilities.IsZero(combinedWidth) || NewLineLength > 0)
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
if (result.Count > 0 && lastDirection == currentDirection && MathUtilities.AreClose(currentRect.Left, lastRunBounds.Rectangle.Right))
{
currentRect = currentRect.WithWidth(currentWidth + combinedWidth);
var textBounds = result[result.Count - 1];
var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect;
textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds!);
}
else
{
currentRect = currentRunBounds!.Rectangle;
textBounds.TextRunBounds.Add(currentRunBounds);
}
else
{
currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
}
lastRunBounds = currentRunBounds;
}
currentWidth += runWidth;
currentWidth += combinedWidth;
if (remainingLength <= 0 || currentPosition >= characterIndex)
{
break;
}
startX = endX;
lastDirection = currentDirection;
}
@ -856,105 +897,45 @@ namespace Avalonia.Media.TextFormatting
return result;
}
private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds)
private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextCharacters currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength)
{
textRunBounds = null;
var startX = endX;
for (var index = runIndex; index >= 0; index--)
{
if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
currentPosition += offset;
currentPosition += currentRun.TextSourceLength;
var startIndex = currentRun.Text.Start + offset;
continue;
}
double startOffset;
double endOffset;
var characterLength = 0;
var endX = startX;
if (currentRun is ShapedTextCharacters currentShapedRun)
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
if (currentPosition < startIndex)
{
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
}
else
{
if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
endX -= currentRun.Size.Width;
}
startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
if (currentPosition < firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
characterLength = currentRun.TextSourceLength;
}
}
var endHit = currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
var characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
//Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
var runWidth = endX - startX;
remainingLength -= characterLength;
currentPosition += characterLength;
textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
return true;
//Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
return false;
var runWidth = endX - startX;
return new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
}
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
@ -1536,7 +1517,7 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection;
if(textAlignment == TextAlignment.Justify)
if (textAlignment == TextAlignment.Justify)
{
textAlignment = TextAlignment.Start;
}

2
src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

@ -3,7 +3,7 @@
/// <summary>
/// The bounding rectangle of text run
/// </summary>
public sealed class TextRunBounds
public readonly struct TextRunBounds
{
/// <summary>
/// Constructing TextRunBounds

14
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.Documents
private void AddText(string text)
{
if(Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
if (Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
}
@ -156,7 +156,17 @@ namespace Avalonia.Controls.Documents
{
foreach (var child in this)
{
((ISetLogicalParent)child).SetParent(parent);
var oldParent = child.Parent;
if (oldParent != parent)
{
if (oldParent != null)
{
((ISetLogicalParent)child).SetParent(null);
}
((ISetLogicalParent)child).SetParent(parent);
}
}
}

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

@ -597,21 +597,82 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, 20);
Assert.Equal(1, textBounds.Count);
Assert.Equal(2, textBounds.Count);
Assert.Equal(144.0234375, textBounds[0].Rectangle.Width);
Assert.Equal(144.0234375, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 30);
Assert.Equal(1, textBounds.Count);
Assert.Equal(3, textBounds.Count);
Assert.Equal(216.03515625, textBounds[0].Rectangle.Width);
Assert.Equal(216.03515625, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 40);
Assert.Equal(1, textBounds.Count);
Assert.Equal(4, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
[Fact]
public void Should_GetTextRange()
{
var text = "שדגככעיחדגכAישדגשדגחייטYDASYWIWחיחלדשSAטויליHUHIUHUIDWKLאא'ק'קחליק/'וקןגגגלךשף'/קפוכדגכשדגשיח'/קטאגשד";
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList();
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds[0].Rectangle.Width);
var lineWidth = textLine.WidthIncludingTrailingWhitespace;
var textBounds = textLine.GetTextBounds(0, text.Length);
TextBounds lastBounds = null;
var runBounds = textBounds.SelectMany(x => x.TextRunBounds).ToList();
Assert.Equal(textRuns.Count, runBounds.Count);
for (var i = 0; i < textRuns.Count; i++)
{
var run = textRuns[i];
var bounds = runBounds[i];
Assert.Equal(run.Text.Start, bounds.TextSourceCharacterIndex);
Assert.Equal(run, bounds.TextRun);
Assert.Equal(run.Size.Width, bounds.Rectangle.Width);
}
for (var i = 0; i < textBounds.Count; i++)
{
var currentBounds = textBounds[i];
if (lastBounds != null)
{
Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left);
}
var sumOfRunWidth = currentBounds.TextRunBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width);
lastBounds = currentBounds;
}
var sumOfBoundsWidth = textBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(lineWidth, sumOfBoundsWidth);
}
}
@ -779,7 +840,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textBounds = textLine.GetTextBounds(0, text.Length * 3 + 3);
Assert.Equal(1, textBounds.Count);
Assert.Equal(6, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 1);
@ -789,8 +850,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length);
@ -799,8 +860,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
}
}

Loading…
Cancel
Save