|
|
@ -4,8 +4,12 @@ using Avalonia.Utilities; |
|
|
|
|
|
|
|
|
namespace Avalonia.Media.TextFormatting |
|
|
namespace Avalonia.Media.TextFormatting |
|
|
{ |
|
|
{ |
|
|
internal sealed class TextLineImpl : TextLine |
|
|
internal class TextLineImpl : TextLine |
|
|
{ |
|
|
{ |
|
|
|
|
|
internal static Comparer<TextBounds> TextBoundsComparer { get; } = |
|
|
|
|
|
Comparer<TextBounds>.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left)); |
|
|
|
|
|
|
|
|
|
|
|
private IReadOnlyList<IndexedTextRun>? _indexedTextRuns; |
|
|
private readonly TextRun[] _textRuns; |
|
|
private readonly TextRun[] _textRuns; |
|
|
private readonly double _paragraphWidth; |
|
|
private readonly double _paragraphWidth; |
|
|
private readonly TextParagraphProperties _paragraphProperties; |
|
|
private readonly TextParagraphProperties _paragraphProperties; |
|
|
@ -338,184 +342,171 @@ namespace Avalonia.Media.TextFormatting |
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) |
|
|
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) |
|
|
{ |
|
|
{ |
|
|
var flowDirection = _paragraphProperties.FlowDirection; |
|
|
if (_indexedTextRuns is null || _indexedTextRuns.Count == 0) |
|
|
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; |
|
|
|
|
|
var currentPosition = FirstTextSourceIndex; |
|
|
|
|
|
var remainingLength = characterIndex - FirstTextSourceIndex; |
|
|
|
|
|
|
|
|
|
|
|
var currentDistance = Start; |
|
|
|
|
|
|
|
|
|
|
|
if (flowDirection == FlowDirection.LeftToRight) |
|
|
|
|
|
{ |
|
|
{ |
|
|
for (var index = 0; index < _textRuns.Length; index++) |
|
|
return Start; |
|
|
{ |
|
|
} |
|
|
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]; |
|
|
|
|
|
|
|
|
|
|
|
if (nextRun is ShapedTextRun nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) |
|
|
var characterIndex = Math.Min( |
|
|
{ |
|
|
characterHit.FirstCharacterIndex + characterHit.TrailingLength, |
|
|
i++; |
|
|
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) |
|
|
IndexedTextRun FindIndexedRun() |
|
|
{ |
|
|
{ |
|
|
while (i >= index) |
|
|
var i = 0; |
|
|
{ |
|
|
|
|
|
currentRun = _textRuns[i]; |
|
|
|
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawable) |
|
|
IndexedTextRun currentIndexedRun = _indexedTextRuns[i]; |
|
|
{ |
|
|
|
|
|
rightToLeftWidth -= drawable.Size.Width; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (currentPosition + currentRun.Length >= characterIndex) |
|
|
while(currentIndexedRun.TextSourceCharacterIndex != currentPosition) |
|
|
{ |
|
|
{ |
|
|
break; |
|
|
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 && |
|
|
for (var i = 0; i < firstIndex; i++) |
|
|
TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) |
|
|
{ |
|
|
{ |
|
|
var currentRun = _textRuns[i]; |
|
|
return Math.Max(0, currentDistance + distance); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
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, |
|
|
if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= characterHit.FirstCharacterIndex) |
|
|
flowDirection, out var distance, out var currentGlyphRun)) |
|
|
{ |
|
|
|
|
|
if (currentPosition + currentTextRun.Length < FirstTextSourceIndex + Length) |
|
|
{ |
|
|
{ |
|
|
if (currentGlyphRun != null) |
|
|
currentPosition += currentTextRun.Length; |
|
|
{ |
|
|
|
|
|
currentDistance -= currentGlyphRun.Bounds.Width; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return currentDistance + distance; |
|
|
currentIndexedRun = FindIndexedRun(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
continue; |
|
|
{ |
|
|
|
|
|
currentDistance -= drawableTextRun.Size.Width; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
//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( |
|
|
var directionalWidth = 0.0; |
|
|
TextRun currentRun, |
|
|
var firstRunIndex = currentIndexedRun.RunIndex; |
|
|
CharacterHit characterHit, |
|
|
var lastRunIndex = firstRunIndex; |
|
|
int currentPosition, |
|
|
|
|
|
int remainingLength, |
|
|
|
|
|
FlowDirection flowDirection, |
|
|
|
|
|
out double distance, |
|
|
|
|
|
out GlyphRun? currentGlyphRun) |
|
|
|
|
|
{ |
|
|
|
|
|
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; |
|
|
|
|
|
var isTrailingHit = characterHit.TrailingLength > 0; |
|
|
|
|
|
|
|
|
|
|
|
distance = 0; |
|
|
var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection); |
|
|
currentGlyphRun = null; |
|
|
|
|
|
|
|
|
|
|
|
switch (currentRun) |
|
|
var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex); |
|
|
|
|
|
|
|
|
|
|
|
if (currentTextRun is DrawableTextRun currentDrawable) |
|
|
{ |
|
|
{ |
|
|
case ShapedTextRun shapedTextCharacters: |
|
|
directionalWidth = currentDrawable.Size.Width; |
|
|
{ |
|
|
} |
|
|
currentGlyphRun = shapedTextCharacters.GlyphRun; |
|
|
|
|
|
|
|
|
|
|
|
if (currentPosition + remainingLength <= currentPosition + currentRun.Length) |
|
|
if (currentTextRun is not TextEndOfLine) |
|
|
{ |
|
|
{ |
|
|
characterHit = new CharacterHit(currentPosition + remainingLength); |
|
|
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) |
|
|
directionalWidth += nextDrawable.Size.Width; |
|
|
{ |
|
|
|
|
|
distance = currentGlyphRun.Bounds.Width; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
directionalWidth += previousDrawable.Size.Width; |
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
currentX -= previousDrawable.Size.Width; |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
break; |
|
|
switch (currentDirection) |
|
|
|
|
|
{ |
|
|
|
|
|
case FlowDirection.RightToLeft: |
|
|
|
|
|
{ |
|
|
|
|
|
return GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, characterIndex, |
|
|
|
|
|
currentPosition, 1, out _, out _).Rectangle.Right; |
|
|
} |
|
|
} |
|
|
default: |
|
|
default: |
|
|
{ |
|
|
{ |
|
|
return false; |
|
|
return GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, characterIndex, |
|
|
|
|
|
currentPosition, 1, out _, out _).Rectangle.Left; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
@ -585,7 +576,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
|
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength) |
|
|
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength) |
|
|
{ |
|
|
{ |
|
|
if (_textRuns.Length == 0) |
|
|
if (_indexedTextRuns is null || _indexedTextRuns.Count == 0) |
|
|
{ |
|
|
{ |
|
|
return Array.Empty<TextBounds>(); |
|
|
return Array.Empty<TextBounds>(); |
|
|
} |
|
|
} |
|
|
@ -607,303 +598,156 @@ namespace Avalonia.Media.TextFormatting |
|
|
return currentDirection; |
|
|
return currentDirection; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) |
|
|
IndexedTextRun FindIndexedRun() |
|
|
{ |
|
|
{ |
|
|
var currentX = Start; |
|
|
var i = 0; |
|
|
|
|
|
|
|
|
for (int i = 0; i < _textRuns.Length; i++) |
|
|
IndexedTextRun currentIndexedRun = _indexedTextRuns[i]; |
|
|
{ |
|
|
|
|
|
var currentRun = _textRuns[i]; |
|
|
|
|
|
|
|
|
|
|
|
var firstRunIndex = i; |
|
|
while (currentIndexedRun.TextSourceCharacterIndex != currentPosition) |
|
|
var lastRunIndex = firstRunIndex; |
|
|
{ |
|
|
var currentDirection = GetDirection(currentRun, FlowDirection.LeftToRight); |
|
|
if (i + 1 < _indexedTextRuns.Count) |
|
|
var directionalWidth = 0.0; |
|
|
|
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun currentDrawable) |
|
|
|
|
|
{ |
|
|
{ |
|
|
directionalWidth = currentDrawable.Size.Width; |
|
|
i++; |
|
|
|
|
|
|
|
|
|
|
|
currentIndexedRun = _indexedTextRuns[i]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Find consecutive runs of same direction
|
|
|
break; |
|
|
for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) |
|
|
} |
|
|
{ |
|
|
|
|
|
var nextRun = _textRuns[lastRunIndex + 1]; |
|
|
|
|
|
|
|
|
|
|
|
var nextDirection = GetDirection(nextRun, currentDirection); |
|
|
return currentIndexedRun; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (currentDirection != nextDirection) |
|
|
double GetPreceedingDistance(int firstIndex) |
|
|
{ |
|
|
{ |
|
|
break; |
|
|
var distance = 0.0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (nextRun is DrawableTextRun nextDrawable) |
|
|
for (var i = 0; i < firstIndex; i++) |
|
|
{ |
|
|
{ |
|
|
directionalWidth += nextDrawable.Size.Width; |
|
|
var currentRun = _textRuns[i]; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//Skip runs that are not part of the hit test range
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
switch (currentDirection) |
|
|
|
|
|
{ |
|
|
{ |
|
|
case FlowDirection.RightToLeft: |
|
|
distance += drawableTextRun.Size.Width; |
|
|
{ |
|
|
} |
|
|
for (; lastRunIndex >= firstRunIndex; lastRunIndex--) |
|
|
} |
|
|
{ |
|
|
|
|
|
currentRun = _textRuns[lastRunIndex]; |
|
|
|
|
|
|
|
|
|
|
|
if (currentPosition + currentRun.Length > firstTextSourceIndex) |
|
|
return distance; |
|
|
{ |
|
|
} |
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
currentPosition += currentRun.Length; |
|
|
while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length) |
|
|
|
|
|
{ |
|
|
|
|
|
var currentIndexedRun = FindIndexedRun(); |
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
if (currentIndexedRun == null) |
|
|
{ |
|
|
{ |
|
|
directionalWidth -= drawableTextRun.Size.Width; |
|
|
break; |
|
|
currentX += drawableTextRun.Size.Width; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (lastRunIndex - 1 < 0) |
|
|
var directionalWidth = 0.0; |
|
|
{ |
|
|
var firstRunIndex = currentIndexedRun.RunIndex; |
|
|
break; |
|
|
var lastRunIndex = firstRunIndex; |
|
|
} |
|
|
var currentTextRun = currentIndexedRun.TextRun; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
if (currentTextRun == null) |
|
|
} |
|
|
{ |
|
|
default: |
|
|
break; |
|
|
{ |
|
|
} |
|
|
for (; firstRunIndex <= lastRunIndex; firstRunIndex++) |
|
|
|
|
|
{ |
|
|
|
|
|
currentRun = _textRuns[firstRunIndex]; |
|
|
|
|
|
|
|
|
|
|
|
if (currentPosition + currentRun.Length > firstTextSourceIndex) |
|
|
|
|
|
{ |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
currentPosition += currentRun.Length; |
|
|
var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection); |
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= firstTextSourceIndex) |
|
|
{ |
|
|
{ |
|
|
currentX += drawableTextRun.Size.Width; |
|
|
currentPosition += currentTextRun.Length; |
|
|
directionalWidth -= drawableTextRun.Size.Width; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (firstRunIndex + 1 == _textRuns.Length) |
|
|
continue; |
|
|
{ |
|
|
} |
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 (currentTextRun is not TextEndOfLine) |
|
|
if (directionalWidth == 0 && i < _textRuns.Length - 1) |
|
|
{ |
|
|
|
|
|
if (currentDirection == FlowDirection.LeftToRight) |
|
|
{ |
|
|
{ |
|
|
//In case a run only contains a linebreak we don't want to skip it.
|
|
|
// Find consecutive runs of same direction
|
|
|
if (currentRun is ShapedTextRun shaped) |
|
|
for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) |
|
|
{ |
|
|
|
|
|
if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0) |
|
|
|
|
|
{ |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
{ |
|
|
continue; |
|
|
var nextRun = _textRuns[lastRunIndex + 1]; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int coveredLength; |
|
|
var nextDirection = GetDirection(nextRun, currentDirection); |
|
|
TextBounds? textBounds; |
|
|
|
|
|
|
|
|
|
|
|
switch (currentDirection) |
|
|
if (currentDirection != nextDirection) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
case FlowDirection.RightToLeft: |
|
|
|
|
|
{ |
|
|
{ |
|
|
textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, |
|
|
|
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
|
|
|
|
|
|
|
|
|
currentX += directionalWidth; |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
default: |
|
|
|
|
|
{ |
|
|
|
|
|
textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, |
|
|
|
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
|
|
|
|
|
|
|
|
|
currentX = textBounds.Rectangle.Right; |
|
|
if (nextRun is DrawableTextRun nextDrawable) |
|
|
|
|
|
{ |
|
|
break; |
|
|
directionalWidth += nextDrawable.Size.Width; |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
else |
|
|
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--) |
|
|
|
|
|
{ |
|
|
{ |
|
|
var previousRun = _textRuns[firstRunIndex - 1]; |
|
|
// Find consecutive runs of same direction
|
|
|
|
|
|
for (; firstRunIndex - 1 > 0; firstRunIndex--) |
|
|
var previousDirection = GetDirection(previousRun, currentDirection); |
|
|
|
|
|
|
|
|
|
|
|
if (currentDirection != previousDirection) |
|
|
|
|
|
{ |
|
|
{ |
|
|
break; |
|
|
var previousRun = _textRuns[firstRunIndex - 1]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun previousDrawable) |
|
|
var previousDirection = GetDirection(previousRun, currentDirection); |
|
|
{ |
|
|
|
|
|
directionalWidth += previousDrawable.Size.Width; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//Skip runs that are not part of the hit test range
|
|
|
if (currentDirection != previousDirection) |
|
|
switch (currentDirection) |
|
|
|
|
|
{ |
|
|
|
|
|
case FlowDirection.RightToLeft: |
|
|
|
|
|
{ |
|
|
{ |
|
|
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; |
|
|
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; |
|
|
if (previousRun is DrawableTextRun previousDrawable) |
|
|
} |
|
|
{ |
|
|
|
|
|
directionalWidth += previousDrawable.Size.Width; |
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
currentX -= previousDrawable.Size.Width; |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
i = firstRunIndex; |
|
|
int coveredLength; |
|
|
|
|
|
TextBounds? textBounds; |
|
|
|
|
|
|
|
|
//Possible overlap at runs of different direction
|
|
|
switch (currentDirection) |
|
|
if (directionalWidth == 0 && i > 0) |
|
|
{ |
|
|
{ |
|
|
case FlowDirection.RightToLeft: |
|
|
//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) |
|
|
textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, |
|
|
{ |
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
continue; |
|
|
|
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
else |
|
|
default: |
|
|
{ |
|
|
{ |
|
|
continue; |
|
|
textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, |
|
|
} |
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
//Visual order is always left to right so we need to insert
|
|
|
if (coveredLength > 0) |
|
|
result.Insert(0, textBounds); |
|
|
{ |
|
|
|
|
|
result.Add(textBounds); |
|
|
|
|
|
|
|
|
remainingLength -= coveredLength; |
|
|
remainingLength -= coveredLength; |
|
|
|
|
|
|
|
|
if (remainingLength <= 0) |
|
|
|
|
|
{ |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
result.Sort(TextBoundsComparer); |
|
|
|
|
|
|
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1164,7 +1008,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
_textLineBreak = new TextLineBreak(textEndOfLine); |
|
|
_textLineBreak = new TextLineBreak(textEndOfLine); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
BidiReorderer.Instance.BidiReorder(_textRuns, _resolvedFlowDirection); |
|
|
_indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -1211,13 +1055,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
//var characterIndex = codepointIndex - shapedRun.Text.Start;
|
|
|
|
|
|
|
|
|
|
|
|
//if (characterIndex < 0 && shapedRun.ShapedBuffer.IsLeftToRight)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// foundCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? |
|
|
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? |
|
|
foundCharacterHit : |
|
|
foundCharacterHit : |
|
|
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); |
|
|
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); |
|
|
@ -1556,8 +1393,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
TrailingWhitespaceLength = trailingWhitespaceLength, |
|
|
TrailingWhitespaceLength = trailingWhitespaceLength, |
|
|
Width = width, |
|
|
Width = width, |
|
|
WidthIncludingTrailingWhitespace = widthIncludingWhitespace, |
|
|
WidthIncludingTrailingWhitespace = widthIncludingWhitespace, |
|
|
OverhangLeading= overhangLeading, |
|
|
OverhangLeading = overhangLeading, |
|
|
OverhangTrailing= overhangTrailing, |
|
|
OverhangTrailing = overhangTrailing, |
|
|
OverhangAfter = overhangAfter |
|
|
OverhangAfter = overhangAfter |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
@ -1615,8 +1452,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
|
return Math.Max(0, start); |
|
|
return Math.Max(0, start); |
|
|
case TextAlignment.Right: |
|
|
case TextAlignment.Right: |
|
|
return Math.Max(0, _paragraphWidth - width); |
|
|
return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); |
|
|
|
|
|
|
|
|
default: |
|
|
default: |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|