diff --git a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
index a0b51671f0..93edf68348 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
@@ -10,20 +10,27 @@ namespace Avalonia.Media.TextFormatting
///
/// Constructing TextBounds object
///
- internal TextBounds(Rect bounds, FlowDirection flowDirection)
+ internal TextBounds(Rect bounds, FlowDirection flowDirection, IList runBounds)
{
Rectangle = bounds;
FlowDirection = flowDirection;
+ TextRunBounds = runBounds;
}
///
/// Bounds rectangle
///
- public Rect Rectangle { get; }
+ public Rect Rectangle { get; internal set; }
///
/// Text flow direction inside the boundary rectangle
///
public FlowDirection FlowDirection { get; }
+
+ ///
+ /// Get a list of run bounding rectangles
+ ///
+ /// Array of text run bounds
+ public IList TextRunBounds { get; }
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
index 5d5d45db2d..4f7c43a6d1 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
@@ -230,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textLine in TextLines)
{
//Current line isn't covered.
- if (textLine.FirstTextSourceIndex + textLine.Length <= start)
+ if (textLine.FirstTextSourceIndex + textLine.Length < start)
{
currentY += textLine.Height;
@@ -239,18 +239,27 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length);
- foreach (var bounds in textBounds)
+ if(textBounds.Count > 0)
{
- Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
-
- if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
+ foreach (var bounds in textBounds)
{
- result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
+ Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
+
+ if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
+ {
+ result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
+ }
+ else
+ {
+ result.Add(bounds.Rectangle.WithY(currentY));
+ }
+
+ foreach (var runBounds in bounds.TextRunBounds)
+ {
+ start += runBounds.Length;
+ length -= runBounds.Length;
+ }
}
- else
- {
- result.Add(bounds.Rectangle.WithY(currentY));
- }
}
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 26e73cdf3b..8b5e2cc2ce 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -184,6 +184,10 @@ namespace Avalonia.Media.TextFormatting
{
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
+ var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
+
+ characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
+
break;
}
default:
@@ -215,9 +219,11 @@ namespace Avalonia.Media.TextFormatting
///
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
- var characterIndex = characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0);
+ var isTrailingHit = characterHit.TrailingLength > 0;
+ var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start;
var currentPosition = FirstTextSourceIndex;
+ var remainingLength = characterIndex - FirstTextSourceIndex;
GlyphRun? lastRun = null;
@@ -242,8 +248,10 @@ namespace Avalonia.Media.TextFormatting
}
//Look for a hit in within the current run
- if (characterIndex >= textRun.Text.Start && characterIndex <= textRun.Text.Start + textRun.Text.Length)
+ if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
{
+ characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
+
var distance = currentRun.GetDistanceFromCharacterHit(characterHit);
return currentDistance + distance;
@@ -254,28 +262,27 @@ namespace Avalonia.Media.TextFormatting
{
if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
{
- if (characterIndex <= textRun.Text.Start)
+ if (characterIndex <= currentPosition)
{
return currentDistance;
}
}
else
{
- if (characterIndex == textRun.Text.Start)
+ if (characterIndex == currentPosition)
{
return currentDistance;
}
}
- if (characterIndex == textRun.Text.Start + textRun.Text.Length &&
- characterHit.TrailingLength > 0)
+ if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
{
return currentDistance + currentRun.Size.Width;
}
}
else
{
- if (characterIndex == textRun.Text.Start)
+ if (characterIndex == currentPosition)
{
return currentDistance + currentRun.Size.Width;
}
@@ -286,20 +293,24 @@ namespace Avalonia.Media.TextFormatting
if (nextRun != null)
{
- if (characterHit.FirstCharacterIndex == textRun.Text.End &&
- nextRun.ShapedBuffer.IsLeftToRight)
+ if (nextRun.ShapedBuffer.IsLeftToRight)
{
- return currentDistance;
+ if (characterIndex == currentPosition + textRun.Text.Length)
+ {
+ return currentDistance;
+ }
}
-
- if (characterIndex > textRun.Text.End && nextRun.Text.End < textRun.Text.End)
+ else
{
- return currentDistance;
+ if (currentPosition + nextRun.Text.Length == characterIndex)
+ {
+ return currentDistance;
+ }
}
}
else
{
- if (characterIndex > textRun.Text.End)
+ if (characterIndex > currentPosition + textRun.Text.Length)
{
return currentDistance;
}
@@ -329,6 +340,12 @@ namespace Avalonia.Media.TextFormatting
//No hit hit found so we add the full width
currentDistance += textRun.Size.Width;
currentPosition += textRun.TextSourceLength;
+ remainingLength -= textRun.TextSourceLength;
+
+ if (remainingLength <= 0)
+ {
+ break;
+ }
}
return currentDistance;
@@ -394,217 +411,299 @@ namespace Avalonia.Media.TextFormatting
return GetPreviousCaretCharacterHit(characterHit);
}
- public override IReadOnlyList GetTextBounds(int firstTextSourceCharacterIndex, int textLength)
+ private IReadOnlyList GetTextBoundsLeftToRight(int firstTextSourceIndex, int textLength)
{
- if (firstTextSourceCharacterIndex + textLength <= FirstTextSourceIndex)
- {
- return Array.Empty();
- }
+ var characterIndex = firstTextSourceIndex + textLength;
var result = new List(TextRuns.Count);
- var lastDirection = _flowDirection;
+ var lastDirection = FlowDirection.LeftToRight;
var currentDirection = lastDirection;
+
var currentPosition = FirstTextSourceIndex;
- var currentRect = Rect.Empty;
+ var remainingLength = textLength;
+
var startX = Start;
- var runStart = startX;
+ double currentWidth = 0;
+ var currentRect = Rect.Empty;
- //A portion of the line is covered.
for (var index = 0; index < TextRuns.Count; index++)
{
- var currentRun = TextRuns[index] as DrawableTextRun;
-
- if (currentRun is null)
+ if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
}
- TextRun? nextRun = null;
-
- if (index + 1 < TextRuns.Count)
+ if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
- nextRun = TextRuns[index + 1];
+ startX += currentRun.Size.Width;
+
+ currentPosition += currentRun.TextSourceLength;
+
+ continue;
}
- if (nextRun != null)
+ var characterLength = 0;
+ var endX = startX;
+
+ if (currentRun is ShapedTextCharacters currentShapedRun)
{
- switch (nextRun)
- {
- case ShapedTextCharacters when currentRun is ShapedTextCharacters:
- {
- if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End && _flowDirection == FlowDirection.LeftToRight)
- {
- goto skip;
- }
+ var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
- if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
- {
- goto skip;
- }
+ currentPosition += offset;
- if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex)
- {
- goto skip;
- }
+ var startIndex = currentRun.Text.Start + offset;
- if (currentRun.Text.End < firstTextSourceCharacterIndex)
- {
- goto skip;
- }
+ var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+ currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ new CharacterHit(startIndex + remainingLength) :
+ new CharacterHit(startIndex));
- goto noop;
- }
- default:
- {
- goto noop;
- }
- }
+ endX += endOffset;
- skip:
+ var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+ currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ new CharacterHit(startIndex) :
+ new CharacterHit(startIndex + remainingLength));
+
+ startX += startOffset;
+
+ var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+ var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+
+ characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
+
+ currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ FlowDirection.LeftToRight :
+ FlowDirection.RightToLeft;
+ }
+ else
+ {
+ if (currentPosition < firstTextSourceIndex)
{
startX += currentRun.Size.Width;
- currentPosition += currentRun.TextSourceLength;
}
- continue;
-
- noop:
+ if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
+ endX += currentRun.Size.Width;
+
+ characterLength = currentRun.TextSourceLength;
}
}
- var endX = startX;
- var endOffset = 0d;
+ if (endX < startX)
+ {
+ (endX, startX) = (startX, endX);
+ }
- switch (currentRun)
+ //Lines that only contain a linebreak need to be covered here
+ if(characterLength == 0)
{
- case ShapedTextCharacters shapedRun:
- {
- endOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
- shapedRun.ShapedBuffer.IsLeftToRight ?
- new CharacterHit(firstTextSourceCharacterIndex + textLength) :
- new CharacterHit(firstTextSourceCharacterIndex));
+ characterLength = NewLineLength;
+ }
- endX += endOffset;
+ var runwidth = endX - startX;
+ var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
- var startOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
- shapedRun.ShapedBuffer.IsLeftToRight ?
- new CharacterHit(firstTextSourceCharacterIndex) :
- new CharacterHit(firstTextSourceCharacterIndex + textLength));
+ if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+ {
+ currentRect = currentRect.WithWidth(currentWidth + runwidth);
- startX += startOffset;
+ var textBounds = result[result.Count - 1];
- var characterHit = _flowDirection == FlowDirection.LeftToRight ?
- shapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) :
- shapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+ textBounds.Rectangle = currentRect;
- currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+ textBounds.TextRunBounds.Add(currentRunBounds);
+ }
+ else
+ {
+ currentRect = currentRunBounds.Rectangle;
- currentDirection = shapedRun.ShapedBuffer.IsLeftToRight ?
- FlowDirection.LeftToRight :
- FlowDirection.RightToLeft;
+ result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds }));
+ }
- if (nextRun is ShapedTextCharacters nextShaped)
- {
- if (shapedRun.ShapedBuffer.IsLeftToRight == nextShaped.ShapedBuffer.IsLeftToRight)
- {
- endOffset = nextShaped.GlyphRun.GetDistanceFromCharacterHit(
- nextShaped.ShapedBuffer.IsLeftToRight ?
- new CharacterHit(firstTextSourceCharacterIndex + textLength) :
- new CharacterHit(firstTextSourceCharacterIndex));
+ currentWidth += runwidth;
+ currentPosition += characterLength;
- index++;
+ if (currentDirection == FlowDirection.LeftToRight)
+ {
+ if (currentPosition > characterIndex)
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (currentPosition <= firstTextSourceIndex)
+ {
+ break;
+ }
+ }
- endX += endOffset;
+ startX = endX;
+ lastDirection = currentDirection;
+ remainingLength -= characterLength;
- currentRun = nextShaped;
+ if (remainingLength <= 0)
+ {
+ break;
+ }
+ }
- if (nextShaped.ShapedBuffer.IsLeftToRight)
- {
- characterHit = nextShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+ return result;
+ }
- currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
- }
- }
- }
+ private IReadOnlyList GetTextBoundsRightToLeft(int firstTextSourceIndex, int textLength)
+ {
+ var characterIndex = firstTextSourceIndex + textLength;
- break;
- }
- default:
- {
- if (currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex + textLength)
- {
- endX += currentRun.Size.Width;
- }
+ var result = new List(TextRuns.Count);
+ var lastDirection = FlowDirection.LeftToRight;
+ var currentDirection = lastDirection;
- if (currentPosition < firstTextSourceCharacterIndex)
- {
- startX += currentRun.Size.Width;
- }
+ var currentPosition = FirstTextSourceIndex;
+ var remainingLength = textLength;
- currentPosition += currentRun.TextSourceLength;
+ var startX = Start + WidthIncludingTrailingWhitespace;
+ double currentWidth = 0;
+ var currentRect = Rect.Empty;
- break;
- }
+ for (var index = TextRuns.Count - 1; index >= 0; index--)
+ {
+ if (TextRuns[index] is not DrawableTextRun currentRun)
+ {
+ continue;
}
- if (endX < startX)
+ if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
- (endX, startX) = (startX, endX);
+ startX -= currentRun.Size.Width;
+
+ currentPosition += currentRun.TextSourceLength;
+
+ continue;
}
- var width = endX - startX;
+ var characterLength = 0;
+ var endX = startX;
- if (!MathUtilities.IsZero(width))
+ if (currentRun is ShapedTextCharacters currentShapedRun)
{
- if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
- {
- currentRect = currentRect.WithWidth(currentRect.Width + width);
+ var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
+
+ currentPosition += offset;
+
+ var startIndex = currentRun.Text.Start + offset;
+
+ var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+ currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ new CharacterHit(startIndex + remainingLength) :
+ new CharacterHit(startIndex));
+
+ endX += endOffset - currentShapedRun.Size.Width;
+
+ var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+ currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ new CharacterHit(startIndex) :
+ new CharacterHit(startIndex + remainingLength));
+
+ startX += startOffset - currentShapedRun.Size.Width;
+
+ var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+ var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
- var textBounds = new TextBounds(currentRect, currentDirection);
+ characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
- result[result.Count - 1] = textBounds;
+ currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
+ FlowDirection.LeftToRight :
+ FlowDirection.RightToLeft;
+ }
+ else
+ {
+ if (currentPosition + currentRun.TextSourceLength <= characterIndex)
+ {
+ endX -= currentRun.Size.Width;
}
- else
+
+ if (currentPosition < firstTextSourceIndex)
{
+ startX -= currentRun.Size.Width;
- currentRect = new Rect(startX, 0, width, Height);
+ characterLength = currentRun.TextSourceLength;
+ }
+ }
- result.Add(new TextBounds(currentRect, currentDirection));
+ if (endX < startX)
+ {
+ (endX, startX) = (startX, endX);
+ }
- }
+ //Lines that only contain a linebreak need to be covered here
+ if (characterLength == 0)
+ {
+ characterLength = NewLineLength;
}
+ var runWidth = endX - startX;
+ var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
+
+ if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+ {
+ currentRect = currentRect.WithWidth(currentWidth + runWidth);
+
+ var textBounds = result[result.Count - 1];
+
+ textBounds.Rectangle = currentRect;
+
+ textBounds.TextRunBounds.Add(currentRunBounds);
+ }
+ else
+ {
+ currentRect = currentRunBounds.Rectangle;
+
+ result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds }));
+ }
+
+ currentWidth += runWidth;
+ currentPosition += characterLength;
+
if (currentDirection == FlowDirection.LeftToRight)
{
- if (currentPosition > firstTextSourceCharacterIndex + textLength)
+ if (currentPosition > characterIndex)
{
break;
}
-
- if (_flowDirection == FlowDirection.RightToLeft)
- {
- endX += currentRun.Size.Width - endOffset;
- }
}
else
{
- if (currentPosition <= firstTextSourceCharacterIndex)
+ if (currentPosition <= firstTextSourceIndex)
{
break;
}
-
- endX += currentRun.Size.Width - endOffset;
}
- startX = endX;
lastDirection = currentDirection;
- runStart += currentRun.Size.Width;
+ remainingLength -= characterLength;
+
+ if (remainingLength <= 0)
+ {
+ break;
+ }
}
return result;
}
+ public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength)
+ {
+ if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
+ {
+ return GetTextBoundsLeftToRight(firstTextSourceIndex, textLength);
+ }
+
+ return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength);
+ }
+
public TextLineImpl FinalizeLine()
{
_textLineMetrics = CreateLineMetrics();
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index f29dddf86b..a974e06385 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -543,6 +543,98 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [Fact]
+ public void Should_Get_Distance_From_CharacterHit_Mixed_TextBuffer()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var textSource = new MixedTextBufferTextSource();
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(10));
+
+ Assert.Equal(72.01171875, distance);
+
+ distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(20));
+
+ Assert.Equal(144.0234375, distance);
+
+ distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(30));
+
+ Assert.Equal(216.03515625, distance);
+
+ distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(40));
+
+ Assert.Equal(textLine.WidthIncludingTrailingWhitespace, distance);
+ }
+ }
+
+ [Fact]
+ public void Should_Get_TextBounds_From_Mixed_TextBuffer()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var textSource = new MixedTextBufferTextSource();
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ var textBounds = textLine.GetTextBounds(0, 10);
+
+ Assert.Equal(1, textBounds.Count);
+
+ Assert.Equal(72.01171875, textBounds[0].Rectangle.Width);
+
+ textBounds = textLine.GetTextBounds(0, 20);
+
+ Assert.Equal(1, textBounds.Count);
+
+ Assert.Equal(144.0234375, textBounds[0].Rectangle.Width);
+
+ textBounds = textLine.GetTextBounds(0, 30);
+
+ Assert.Equal(1, textBounds.Count);
+
+ Assert.Equal(216.03515625, textBounds[0].Rectangle.Width);
+
+ textBounds = textLine.GetTextBounds(0, 40);
+
+ Assert.Equal(1, textBounds.Count);
+
+ Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds[0].Rectangle.Width);
+ }
+ }
+
+ private class MixedTextBufferTextSource : ITextSource
+ {
+ public TextRun? GetTextRun(int textSourceIndex)
+ {
+ switch (textSourceIndex)
+ {
+ case 0:
+ return new TextCharacters(new ReadOnlySlice("aaaaaaaaaa".AsMemory()), new GenericTextRunProperties(Typeface.Default));
+ case 10:
+ return new TextCharacters(new ReadOnlySlice("bbbbbbbbbb".AsMemory()), new GenericTextRunProperties(Typeface.Default));
+ case 20:
+ return new TextCharacters(new ReadOnlySlice("cccccccccc".AsMemory()), new GenericTextRunProperties(Typeface.Default));
+ case 30:
+ return new TextCharacters(new ReadOnlySlice("dddddddddd".AsMemory()), new GenericTextRunProperties(Typeface.Default));
+ default:
+ return null;
+ }
+ }
+ }
+
private class DrawableRunTextSource : ITextSource
{
const string Text = "_A_A";
@@ -713,7 +805,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
[Fact]
- public void Should_Get_TextBounds_BiDi()
+ public void Should_Get_TextBounds_BiDi_LeftToRight()
{
using (Start())
{
@@ -725,38 +817,84 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 200,
- new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
+ new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
- var textBounds = textLine.GetTextBounds(0, text.Length);
+ var textBounds = textLine.GetTextBounds(0, 3);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
+ var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
- textBounds = textLine.GetTextBounds(0, 4);
+ Assert.Equal(1, textBounds.Count);
+ Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
+
+ textBounds = textLine.GetTextBounds(3, 4);
var secondRun = textLine.TextRuns[1] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(4, 3);
+ textBounds = textLine.GetTextBounds(0, 4);
- var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
+ Assert.Equal(2, textBounds.Count);
+
+ Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
+
+ Assert.Equal(7.201171875, textBounds[1].Rectangle.Width);
+
+ Assert.Equal(firstRun.Size.Width, textBounds[1].Rectangle.Left);
+
+ textBounds = textLine.GetTextBounds(0, text.Length);
+
+ Assert.Equal(2, textBounds.Count);
+ Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
+ }
+ }
+
+ [Fact]
+ public void Should_Get_TextBounds_BiDi_RightToLeft()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var text = "אאא AAA";
+ var textSource = new SingleBufferTextSource(text, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, 200,
+ new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
+
+ var textBounds = textLine.GetTextBounds(0, 4);
+
+ var firstRun = textLine.TextRuns[1] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
+ textBounds = textLine.GetTextBounds(4, 3);
+
+ var secondRun = textLine.TextRuns[0] as ShapedTextCharacters;
+
+ Assert.Equal(1, textBounds.Count);
+
+ Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length));
+ Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
+
textBounds = textLine.GetTextBounds(0, 5);
Assert.Equal(2, textBounds.Count);
+ Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length)));
- Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
-
- Assert.Equal(textLine.Start, textBounds[0].Rectangle.Left);
+ Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
+ Assert.Equal(7.201171875, textBounds[1].Rectangle.Width);
+ Assert.Equal(textLine.Start + 7.201171875, textBounds[1].Rectangle.Right);
- Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
+ textBounds = textLine.GetTextBounds(0, text.Length);
- Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
+ Assert.Equal(2, textBounds.Count);
+ Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
+ Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}