diff --git a/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs b/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
index 97a9320c95..088a063690 100644
--- a/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
+++ b/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);
}
}
}
diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml
index 43d93a9315..6929f192c7 100644
--- a/samples/Sandbox/MainWindow.axaml
+++ b/samples/Sandbox/MainWindow.axaml
@@ -1,5 +1,4 @@
-
diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs
index 5480336f84..27d99bdc10 100644
--- a/src/Avalonia.Base/Media/FormattedText.cs
+++ b/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;
+ }
///
/// Gets the current text line in the collection
@@ -1292,6 +1304,92 @@ namespace Avalonia.Media
return accumulatedGeometry;
}
+ ///
+ /// Builds a highlight geometry object.
+ ///
+ /// The origin of the highlight region
+ /// Geometry that surrounds the text.
+ public Geometry? BuildHighlightGeometry(Point origin)
+ {
+ return BuildHighlightGeometry(origin, 0, _text.Length);
+ }
+
+ ///
+ /// Builds a highlight geometry object for a given character range.
+ ///
+ /// The origin of the highlight region.
+ /// The start index of initial character the bounds should be obtained for.
+ /// The number of characters the bounds should be obtained for.
+ /// Geometry that surrounds the specified character range.
+ 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;
+ }
+
///
/// Draws the text object
///
diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs
index 76c67a5cf4..c31a6699c2 100644
--- a/src/Avalonia.Base/Media/Geometry.cs
+++ b/src/Avalonia.Base/Media/Geometry.cs
@@ -185,5 +185,18 @@ namespace Avalonia.Media
var control = e.Sender as Geometry;
control?.InvalidateGeometry();
}
+
+ ///
+ /// Combines the two geometries using the specified and applies the specified transform to the resulting geometry.
+ ///
+ /// The first geometry to combine.
+ /// The second geometry to combine.
+ /// One of the enumeration values that specifies how the geometries are combined.
+ /// A transformation to apply to the combined geometry, or null.
+ ///
+ public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
+ {
+ return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
+ }
}
}
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 58edea95ea..2289f98228 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/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;
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index fa1ab6fd29..aba8008fb9 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/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 { 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 { currentRunBounds }));
+ result.Add(new TextBounds(currentRect, currentDirection, new List { 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 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;
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs b/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
index 91150160ed..bdc7a1ca89 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
@@ -3,7 +3,7 @@
///
/// The bounding rectangle of text run
///
- public sealed class TextRunBounds
+ public readonly struct TextRunBounds
{
///
/// Constructing TextRunBounds
diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs
index 190373169b..1ba65b3e8f 100644
--- a/src/Avalonia.Controls/Documents/InlineCollection.cs
+++ b/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);
+ }
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index d744ede87d..251c850fc8 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/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().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));
}
}