diff --git a/src/Avalonia.Base/Media/CharacterHit.cs b/src/Avalonia.Base/Media/CharacterHit.cs
index 6bbbff4f5b..27cf3a42dc 100644
--- a/src/Avalonia.Base/Media/CharacterHit.cs
+++ b/src/Avalonia.Base/Media/CharacterHit.cs
@@ -19,6 +19,7 @@ namespace Avalonia.Media
/// Index of the first character that got hit.
/// In the case of a leading edge, this value is 0. In the case of a trailing edge,
/// this value is the number of code points until the next valid caret position.
+ [DebuggerStepThrough]
public CharacterHit(int firstCharacterIndex, int trailingLength = 0)
{
FirstCharacterIndex = firstCharacterIndex;
diff --git a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs b/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs
index 4db55fae6d..39ef8cce48 100644
--- a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs
@@ -18,14 +18,14 @@ namespace Avalonia.Media.TextFormatting
public static BidiReorderer Instance
=> t_instance ??= new();
- public void BidiReorder(Span textRuns, FlowDirection flowDirection)
+ public IndexedTextRun[] BidiReorder(Span textRuns, FlowDirection flowDirection, int firstTextSourceIndex)
{
Debug.Assert(_runs.Length == 0);
Debug.Assert(_ranges.Length == 0);
if (textRuns.IsEmpty)
{
- return;
+ return Array.Empty();
}
try
@@ -46,6 +46,22 @@ namespace Avalonia.Media.TextFormatting
// Reorder them into visual order.
var firstIndex = LinearReorder();
+ var indexedTextRuns = new IndexedTextRun[textRuns.Length];
+
+ for (var i = 0; i < textRuns.Length; i++)
+ {
+ var currentRun = textRuns[i];
+
+ indexedTextRuns[i] = new IndexedTextRun
+ {
+ TextRun = currentRun,
+ TextSourceCharacterIndex = firstTextSourceIndex,
+ RunIndex = i,
+ NextRunIndex = i + 1
+ };
+
+ firstTextSourceIndex += currentRun.Length;
+ }
// Now perform a recursive reversal of each run.
// From the highest level found in the text to the lowest odd level on each line, including intermediate levels
@@ -76,7 +92,7 @@ namespace Avalonia.Media.TextFormatting
if (max == 0 || (min == max && (max & 1) == 0))
{
// Nothing to reverse.
- return;
+ return indexedTextRuns;
}
// Now apply the reversal and replace the original contents.
@@ -107,13 +123,25 @@ namespace Avalonia.Media.TextFormatting
var index = 0;
currentIndex = firstIndex;
+
while (currentIndex >= 0)
{
ref var current = ref _runs[currentIndex];
- textRuns[index++] = current.Run;
+
+ textRuns[index] = current.Run;
+
+ var indexedRun = indexedTextRuns[index];
+
+ indexedRun.RunIndex = current.RunIndex;
+
+ indexedRun.NextRunIndex = current.NextRunIndex;
+
+ index++;
currentIndex = current.NextRunIndex;
}
+
+ return indexedTextRuns;
}
finally
{
@@ -227,25 +255,6 @@ namespace Avalonia.Media.TextFormatting
return previousIndex;
}
- private struct OrderedBidiRun
- {
- public OrderedBidiRun(int runIndex, TextRun run, sbyte level)
- {
- RunIndex = runIndex;
- Run = run;
- Level = level;
- NextRunIndex = -1;
- }
-
- public int RunIndex { get; }
-
- public sbyte Level { get; }
-
- public TextRun Run { get; }
-
- public int NextRunIndex { get; set; } // -1 if none
- }
-
private struct BidiRange
{
public BidiRange(sbyte level, int leftRunIndex, int rightRunIndex, int previousRangeIndex)
@@ -265,4 +274,23 @@ namespace Avalonia.Media.TextFormatting
public int PreviousRangeIndex { get; } // -1 if none
}
}
+
+ internal struct OrderedBidiRun
+ {
+ public OrderedBidiRun(int runIndex, TextRun run, sbyte level)
+ {
+ RunIndex = runIndex;
+ Run = run;
+ Level = level;
+ NextRunIndex = -1;
+ }
+
+ public int RunIndex { get; }
+
+ public sbyte Level { get; }
+
+ public TextRun Run { get; }
+
+ public int NextRunIndex { get; set; } // -1 if none
+ }
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs
new file mode 100644
index 0000000000..0eb98533d2
--- /dev/null
+++ b/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs
@@ -0,0 +1,10 @@
+namespace Avalonia.Media.TextFormatting
+{
+ internal class IndexedTextRun
+ {
+ public int TextSourceCharacterIndex { get; init; }
+ public int RunIndex { get; set; }
+ public int NextRunIndex { get; set; }
+ public TextRun? TextRun { get; init; }
+ }
+}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
index 93edf68348..946c2e6931 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics;
namespace Avalonia.Media.TextFormatting
{
@@ -10,6 +11,7 @@ namespace Avalonia.Media.TextFormatting
///
/// Constructing TextBounds object
///
+ [DebuggerStepThrough]
internal TextBounds(Rect bounds, FlowDirection flowDirection, IList runBounds)
{
Rectangle = bounds;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index a0d7cabefd..ae2dbe0c8c 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -4,8 +4,12 @@ using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
- internal sealed class TextLineImpl : TextLine
+ internal class TextLineImpl : TextLine
{
+ internal static Comparer TextBoundsComparer { get; } =
+ Comparer.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left));
+
+ private IReadOnlyList? _indexedTextRuns;
private readonly TextRun[] _textRuns;
private readonly double _paragraphWidth;
private readonly TextParagraphProperties _paragraphProperties;
@@ -338,184 +342,171 @@ namespace Avalonia.Media.TextFormatting
///
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
- var flowDirection = _paragraphProperties.FlowDirection;
- var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
- var currentPosition = FirstTextSourceIndex;
- var remainingLength = characterIndex - FirstTextSourceIndex;
-
- var currentDistance = Start;
-
- if (flowDirection == FlowDirection.LeftToRight)
+ if (_indexedTextRuns is null || _indexedTextRuns.Count == 0)
{
- for (var index = 0; index < _textRuns.Length; index++)
- {
- var currentRun = _textRuns[index];
-
- if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
- {
- var i = index;
-
- var rightToLeftWidth = shapedRun.Size.Width;
-
- while (i + 1 <= _textRuns.Length - 1)
- {
- var nextRun = _textRuns[i + 1];
+ return Start;
+ }
- if (nextRun is ShapedTextRun nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight)
- {
- i++;
+ var characterIndex = Math.Min(
+ characterHit.FirstCharacterIndex + characterHit.TrailingLength,
+ FirstTextSourceIndex + Length);
- rightToLeftWidth += nextShapedRun.Size.Width;
+ var currentPosition = FirstTextSourceIndex;
- continue;
- }
+ static FlowDirection GetDirection(TextRun textRun, FlowDirection currentDirection)
+ {
+ if (textRun is ShapedTextRun shapedTextRun)
+ {
+ return shapedTextRun.ShapedBuffer.IsLeftToRight ?
+ FlowDirection.LeftToRight :
+ FlowDirection.RightToLeft;
+ }
- break;
- }
+ return currentDirection;
+ }
- if (i > index)
- {
- while (i >= index)
- {
- currentRun = _textRuns[i];
+ IndexedTextRun FindIndexedRun()
+ {
+ var i = 0;
- if (currentRun is DrawableTextRun drawable)
- {
- rightToLeftWidth -= drawable.Size.Width;
- }
+ IndexedTextRun currentIndexedRun = _indexedTextRuns[i];
- if (currentPosition + currentRun.Length >= characterIndex)
- {
- break;
- }
+ while(currentIndexedRun.TextSourceCharacterIndex != currentPosition)
+ {
+ if(i + 1 < _indexedTextRuns.Count)
+ {
+ i++;
- currentPosition += currentRun.Length;
+ currentIndexedRun = _indexedTextRuns[i];
+ }
- remainingLength -= currentRun.Length;
+ break;
+ }
- i--;
- }
+ return currentIndexedRun;
+ }
- currentDistance += rightToLeftWidth;
- }
- }
+ double GetPreceedingDistance(int firstIndex)
+ {
+ var distance = 0.0;
- if (currentPosition + currentRun.Length >= characterIndex &&
- TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
- {
- return Math.Max(0, currentDistance + distance);
- }
+ for (var i = 0; i < firstIndex; i++)
+ {
+ var currentRun = _textRuns[i];
if (currentRun is DrawableTextRun drawableTextRun)
{
- currentDistance += drawableTextRun.Size.Width;
+ distance += drawableTextRun.Size.Width;
}
-
- //No hit hit found so we add the full width
-
- currentPosition += currentRun.Length;
- remainingLength -= currentRun.Length;
}
+
+ return distance;
}
- else
+
+ TextRun? currentTextRun = null;
+ var currentIndexedRun = FindIndexedRun();
+
+ while (currentPosition < FirstTextSourceIndex + Length)
{
- currentDistance += WidthIncludingTrailingWhitespace;
+ currentTextRun = currentIndexedRun.TextRun;
- for (var index = _textRuns.Length - 1; index >= 0; index--)
+ if (currentTextRun == null)
{
- var currentRun = _textRuns[index];
+ break;
+ }
- if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
- flowDirection, out var distance, out var currentGlyphRun))
+ if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= characterHit.FirstCharacterIndex)
+ {
+ if (currentPosition + currentTextRun.Length < FirstTextSourceIndex + Length)
{
- if (currentGlyphRun != null)
- {
- currentDistance -= currentGlyphRun.Bounds.Width;
- }
+ currentPosition += currentTextRun.Length;
- return currentDistance + distance;
- }
+ currentIndexedRun = FindIndexedRun();
- if (currentRun is DrawableTextRun drawableTextRun)
- {
- currentDistance -= drawableTextRun.Size.Width;
+ continue;
}
-
- //No hit hit found so we add the full width
- currentPosition += currentRun.Length;
- remainingLength -= currentRun.Length;
}
+
+ break;
}
- return Math.Max(0, currentDistance);
- }
+ if (currentTextRun == null)
+ {
+ return 0;
+ }
- private static bool TryGetDistanceFromCharacterHit(
- TextRun currentRun,
- CharacterHit characterHit,
- int currentPosition,
- int remainingLength,
- FlowDirection flowDirection,
- out double distance,
- out GlyphRun? currentGlyphRun)
- {
- var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
- var isTrailingHit = characterHit.TrailingLength > 0;
+ var directionalWidth = 0.0;
+ var firstRunIndex = currentIndexedRun.RunIndex;
+ var lastRunIndex = firstRunIndex;
- distance = 0;
- currentGlyphRun = null;
+ var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection);
- switch (currentRun)
+ var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex);
+
+ if (currentTextRun is DrawableTextRun currentDrawable)
{
- case ShapedTextRun shapedTextCharacters:
- {
- currentGlyphRun = shapedTextCharacters.GlyphRun;
+ directionalWidth = currentDrawable.Size.Width;
+ }
- if (currentPosition + remainingLength <= currentPosition + currentRun.Length)
- {
- characterHit = new CharacterHit(currentPosition + remainingLength);
+ if (currentTextRun is not TextEndOfLine)
+ {
+ if (currentDirection == FlowDirection.LeftToRight)
+ {
+ // Find consecutive runs of same direction
+ for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++)
+ {
+ var nextRun = _textRuns[lastRunIndex + 1];
- distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit);
+ var nextDirection = GetDirection(nextRun, currentDirection);
- return true;
+ if (currentDirection != nextDirection)
+ {
+ break;
}
- if (currentPosition + remainingLength == currentPosition + currentRun.Length && isTrailingHit)
+ if (nextRun is DrawableTextRun nextDrawable)
{
- if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
- {
- distance = currentGlyphRun.Bounds.Width;
- }
-
- return true;
+ directionalWidth += nextDrawable.Size.Width;
}
-
- break;
}
- case DrawableTextRun drawableTextRun:
+ }
+ else
+ {
+ // Find consecutive runs of same direction
+ for (; firstRunIndex - 1 > 0; firstRunIndex--)
{
- if (characterIndex == currentPosition)
+ var previousRun = _textRuns[firstRunIndex - 1];
+
+ var previousDirection = GetDirection(previousRun, currentDirection);
+
+ if (currentDirection != previousDirection)
{
- return true;
+ break;
}
- if (characterIndex == currentPosition + currentRun.Length)
+ if (previousRun is DrawableTextRun previousDrawable)
{
- distance = drawableTextRun.Size.Width;
-
- return true;
+ directionalWidth += previousDrawable.Size.Width;
+ currentX -= previousDrawable.Size.Width;
}
+ }
+ }
+ }
- break;
+ switch (currentDirection)
+ {
+ case FlowDirection.RightToLeft:
+ {
+ return GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, characterIndex,
+ currentPosition, 1, out _, out _).Rectangle.Right;
}
default:
{
- return false;
+ return GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, characterIndex,
+ currentPosition, 1, out _, out _).Rectangle.Left;
}
}
-
- return false;
}
///
@@ -585,7 +576,7 @@ namespace Avalonia.Media.TextFormatting
public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength)
{
- if (_textRuns.Length == 0)
+ if (_indexedTextRuns is null || _indexedTextRuns.Count == 0)
{
return Array.Empty();
}
@@ -607,303 +598,156 @@ namespace Avalonia.Media.TextFormatting
return currentDirection;
}
- if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
+ IndexedTextRun FindIndexedRun()
{
- var currentX = Start;
+ var i = 0;
- for (int i = 0; i < _textRuns.Length; i++)
- {
- var currentRun = _textRuns[i];
+ IndexedTextRun currentIndexedRun = _indexedTextRuns[i];
- var firstRunIndex = i;
- var lastRunIndex = firstRunIndex;
- var currentDirection = GetDirection(currentRun, FlowDirection.LeftToRight);
- var directionalWidth = 0.0;
-
- if (currentRun is DrawableTextRun currentDrawable)
+ while (currentIndexedRun.TextSourceCharacterIndex != currentPosition)
+ {
+ if (i + 1 < _indexedTextRuns.Count)
{
- directionalWidth = currentDrawable.Size.Width;
+ i++;
+
+ currentIndexedRun = _indexedTextRuns[i];
}
- // Find consecutive runs of same direction
- for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++)
- {
- var nextRun = _textRuns[lastRunIndex + 1];
+ break;
+ }
- var nextDirection = GetDirection(nextRun, currentDirection);
+ return currentIndexedRun;
+ }
- if (currentDirection != nextDirection)
- {
- break;
- }
+ double GetPreceedingDistance(int firstIndex)
+ {
+ var distance = 0.0;
- if (nextRun is DrawableTextRun nextDrawable)
- {
- directionalWidth += nextDrawable.Size.Width;
- }
- }
+ for (var i = 0; i < firstIndex; i++)
+ {
+ var currentRun = _textRuns[i];
- //Skip runs that are not part of the hit test range
- switch (currentDirection)
+ if (currentRun is DrawableTextRun drawableTextRun)
{
- case FlowDirection.RightToLeft:
- {
- for (; lastRunIndex >= firstRunIndex; lastRunIndex--)
- {
- currentRun = _textRuns[lastRunIndex];
+ distance += drawableTextRun.Size.Width;
+ }
+ }
- if (currentPosition + currentRun.Length > firstTextSourceIndex)
- {
- break;
- }
+ return distance;
+ }
- currentPosition += currentRun.Length;
+ while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length)
+ {
+ var currentIndexedRun = FindIndexedRun();
- if (currentRun is DrawableTextRun drawableTextRun)
- {
- directionalWidth -= drawableTextRun.Size.Width;
- currentX += drawableTextRun.Size.Width;
- }
+ if (currentIndexedRun == null)
+ {
+ break;
+ }
- if (lastRunIndex - 1 < 0)
- {
- break;
- }
- }
+ var directionalWidth = 0.0;
+ var firstRunIndex = currentIndexedRun.RunIndex;
+ var lastRunIndex = firstRunIndex;
+ var currentTextRun = currentIndexedRun.TextRun;
- break;
- }
- default:
- {
- for (; firstRunIndex <= lastRunIndex; firstRunIndex++)
- {
- currentRun = _textRuns[firstRunIndex];
-
- if (currentPosition + currentRun.Length > firstTextSourceIndex)
- {
- break;
- }
+ if (currentTextRun == null)
+ {
+ break;
+ }
- currentPosition += currentRun.Length;
+ var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection);
- if (currentRun is DrawableTextRun drawableTextRun)
- {
- currentX += drawableTextRun.Size.Width;
- directionalWidth -= drawableTextRun.Size.Width;
- }
+ if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= firstTextSourceIndex)
+ {
+ currentPosition += currentTextRun.Length;
- if (firstRunIndex + 1 == _textRuns.Length)
- {
- break;
- }
- }
+ continue;
+ }
- break;
- }
- }
+ var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex);
- i = lastRunIndex;
+ if (currentTextRun is DrawableTextRun currentDrawable)
+ {
+ directionalWidth = currentDrawable.Size.Width;
+ }
- //Possible overlap at runs of different direction
- if (directionalWidth == 0 && i < _textRuns.Length - 1)
+ if (currentTextRun is not TextEndOfLine)
+ {
+ if (currentDirection == FlowDirection.LeftToRight)
{
- //In case a run only contains a linebreak we don't want to skip it.
- if (currentRun is ShapedTextRun shaped)
- {
- if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
- {
- continue;
- }
- }
- else
+ // Find consecutive runs of same direction
+ for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++)
{
- continue;
- }
- }
+ var nextRun = _textRuns[lastRunIndex + 1];
- int coveredLength;
- TextBounds? textBounds;
+ var nextDirection = GetDirection(nextRun, currentDirection);
- switch (currentDirection)
- {
-
- case FlowDirection.RightToLeft:
+ if (currentDirection != nextDirection)
{
- textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex,
- currentPosition, remainingLength, out coveredLength, out currentPosition);
-
- currentX += directionalWidth;
-
break;
}
- default:
- {
- textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
- currentPosition, remainingLength, out coveredLength, out currentPosition);
- currentX = textBounds.Rectangle.Right;
-
- break;
+ if (nextRun is DrawableTextRun nextDrawable)
+ {
+ directionalWidth += nextDrawable.Size.Width;
}
+ }
}
-
- if (coveredLength > 0)
- {
- result.Add(textBounds);
-
- remainingLength -= coveredLength;
- }
-
- if (remainingLength <= 0)
- {
- break;
- }
- }
- }
- else
- {
- var currentX = Start + WidthIncludingTrailingWhitespace;
-
- for (int i = _textRuns.Length - 1; i >= 0; i--)
- {
- var currentRun = _textRuns[i];
- var firstRunIndex = i;
- var lastRunIndex = firstRunIndex;
- var currentDirection = GetDirection(currentRun, FlowDirection.RightToLeft);
- var directionalWidth = 0.0;
-
- if (currentRun is DrawableTextRun currentDrawable)
- {
- directionalWidth = currentDrawable.Size.Width;
- }
-
- // Find consecutive runs of same direction
- for (; firstRunIndex - 1 > 0; firstRunIndex--)
+ else
{
- var previousRun = _textRuns[firstRunIndex - 1];
-
- var previousDirection = GetDirection(previousRun, currentDirection);
-
- if (currentDirection != previousDirection)
+ // Find consecutive runs of same direction
+ for (; firstRunIndex - 1 > 0; firstRunIndex--)
{
- break;
- }
+ var previousRun = _textRuns[firstRunIndex - 1];
- if (currentRun is DrawableTextRun previousDrawable)
- {
- directionalWidth += previousDrawable.Size.Width;
- }
- }
+ var previousDirection = GetDirection(previousRun, currentDirection);
- //Skip runs that are not part of the hit test range
- switch (currentDirection)
- {
- case FlowDirection.RightToLeft:
+ if (currentDirection != previousDirection)
{
- for (; lastRunIndex >= firstRunIndex; lastRunIndex--)
- {
- currentRun = _textRuns[lastRunIndex];
-
- if (currentPosition + currentRun.Length <= firstTextSourceIndex)
- {
- currentPosition += currentRun.Length;
-
- if (currentRun is DrawableTextRun drawableTextRun)
- {
- currentX -= drawableTextRun.Size.Width;
- directionalWidth -= drawableTextRun.Size.Width;
- }
-
- continue;
- }
-
- break;
- }
-
break;
}
- default:
- {
- for (; firstRunIndex <= lastRunIndex; firstRunIndex++)
- {
- currentRun = _textRuns[firstRunIndex];
-
- if (currentPosition + currentRun.Length <= firstTextSourceIndex)
- {
- currentPosition += currentRun.Length;
-
- if (currentRun is DrawableTextRun drawableTextRun)
- {
- currentX += drawableTextRun.Size.Width;
- directionalWidth -= drawableTextRun.Size.Width;
- }
- continue;
- }
-
- break;
- }
+ if (previousRun is DrawableTextRun previousDrawable)
+ {
+ directionalWidth += previousDrawable.Size.Width;
- break;
+ currentX -= previousDrawable.Size.Width;
}
+ }
}
+ }
- i = firstRunIndex;
+ int coveredLength;
+ TextBounds? textBounds;
- //Possible overlap at runs of different direction
- if (directionalWidth == 0 && i > 0)
- {
- //In case a run only contains a linebreak we don't want to skip it.
- if (currentRun is ShapedTextRun shaped)
+ switch (currentDirection)
+ {
+ case FlowDirection.RightToLeft:
{
- if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
- {
- continue;
- }
+ textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex,
+ currentPosition, remainingLength, out coveredLength, out currentPosition);
+
+ break;
}
- else
+ default:
{
- continue;
- }
- }
-
- int coveredLength;
- TextBounds? textBounds;
-
- switch (currentDirection)
- {
- case FlowDirection.LeftToRight:
- {
- textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX - directionalWidth, firstTextSourceIndex,
- currentPosition, remainingLength, out coveredLength, out currentPosition);
-
- currentX -= directionalWidth;
-
- break;
- }
- default:
- {
- textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
- currentPosition, remainingLength, out coveredLength, out currentPosition);
-
- currentX = textBounds.Rectangle.Left;
+ textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
+ currentPosition, remainingLength, out coveredLength, out currentPosition);
- break;
- }
- }
+ break;
+ }
+ }
- //Visual order is always left to right so we need to insert
- result.Insert(0, textBounds);
+ if (coveredLength > 0)
+ {
+ result.Add(textBounds);
remainingLength -= coveredLength;
-
- if (remainingLength <= 0)
- {
- break;
- }
}
}
+ result.Sort(TextBoundsComparer);
+
return result;
}
@@ -1164,7 +1008,7 @@ namespace Avalonia.Media.TextFormatting
_textLineBreak = new TextLineBreak(textEndOfLine);
}
- BidiReorderer.Instance.BidiReorder(_textRuns, _resolvedFlowDirection);
+ _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
}
///
@@ -1211,13 +1055,6 @@ namespace Avalonia.Media.TextFormatting
return true;
}
- //var characterIndex = codepointIndex - shapedRun.Text.Start;
-
- //if (characterIndex < 0 && shapedRun.ShapedBuffer.IsLeftToRight)
- //{
- // foundCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex);
- //}
-
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
@@ -1556,8 +1393,8 @@ namespace Avalonia.Media.TextFormatting
TrailingWhitespaceLength = trailingWhitespaceLength,
Width = width,
WidthIncludingTrailingWhitespace = widthIncludingWhitespace,
- OverhangLeading= overhangLeading,
- OverhangTrailing= overhangTrailing,
+ OverhangLeading = overhangLeading,
+ OverhangTrailing = overhangTrailing,
OverhangAfter = overhangAfter
};
}
@@ -1615,8 +1452,7 @@ namespace Avalonia.Media.TextFormatting
return Math.Max(0, start);
case TextAlignment.Right:
- return Math.Max(0, _paragraphWidth - width);
-
+ return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);
default:
return 0;
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index aa5d707d0f..1d07e780e6 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -1071,6 +1071,55 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [Fact]
+ public void Should_GetTextBounds_BiDi()
+ {
+ var text = "אבגדה 12345 ABCDEF אבגדה";
+
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var textSource = new SingleBufferTextSource(text, defaultProperties, true);
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+ new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
+ true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
+
+ var bounds = textLine.GetTextBounds(6, 1);
+
+ Assert.Equal(1, bounds.Count);
+
+ Assert.Equal(0, bounds[0].Rectangle.Left);
+
+ bounds = textLine.GetTextBounds(5, 1);
+
+ Assert.Equal(1, bounds.Count);
+
+ Assert.Equal(36.005859374999993, bounds[0].Rectangle.Left);
+
+ bounds = textLine.GetTextBounds(0, 1);
+
+ Assert.Equal(1, bounds.Count);
+
+ Assert.Equal(71.165859375, bounds[0].Rectangle.Right);
+
+ bounds = textLine.GetTextBounds(11, 1);
+
+ Assert.Equal(1, bounds.Count);
+
+ Assert.Equal(71.165859375, bounds[0].Rectangle.Left);
+
+ bounds = textLine.GetTextBounds(0, 25);
+
+ Assert.Equal(5, bounds.Count);
+
+ Assert.Equal(textLine.WidthIncludingTrailingWhitespace, bounds.Last().Rectangle.Right);
+ }
+ }
+
private class FixedRunsTextSource : ITextSource
{
private readonly IReadOnlyList _textRuns;