|
|
|
@ -208,57 +208,40 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
distance -= Start; |
|
|
|
|
|
|
|
var lastIndex = _textRuns.Length - 1; |
|
|
|
var lineLength = Length; |
|
|
|
|
|
|
|
if (_textRuns[lastIndex] is TextEndOfLine textEndOfLine) |
|
|
|
if (_textRuns[lastIndex] is TextEndOfLine) |
|
|
|
{ |
|
|
|
lastIndex--; |
|
|
|
lineLength -= textEndOfLine.Length; |
|
|
|
} |
|
|
|
|
|
|
|
var currentPosition = FirstTextSourceIndex; |
|
|
|
|
|
|
|
if (lastIndex < 0) |
|
|
|
{ |
|
|
|
return new CharacterHit(currentPosition); |
|
|
|
return new CharacterHit(FirstTextSourceIndex); |
|
|
|
} |
|
|
|
|
|
|
|
if (distance <= 0) |
|
|
|
{ |
|
|
|
var firstRun = _textRuns[0]; |
|
|
|
|
|
|
|
if (_paragraphProperties.FlowDirection == FlowDirection.RightToLeft) |
|
|
|
{ |
|
|
|
currentPosition += lineLength - firstRun.Length; |
|
|
|
} |
|
|
|
|
|
|
|
return GetRunCharacterHit(firstRun, currentPosition, 0); |
|
|
|
return GetRunCharacterHit(_textRuns[0], GetRunTextSourcePosition(0), 0); |
|
|
|
} |
|
|
|
|
|
|
|
if (distance >= WidthIncludingTrailingWhitespace) |
|
|
|
{ |
|
|
|
var lastRun = _textRuns[lastIndex]; |
|
|
|
|
|
|
|
if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) |
|
|
|
{ |
|
|
|
currentPosition += lineLength - lastRun.Length; |
|
|
|
} |
|
|
|
|
|
|
|
return GetRunCharacterHit(lastRun, currentPosition, distance); |
|
|
|
return GetRunCharacterHit(_textRuns[lastIndex], GetRunTextSourcePosition(lastIndex), distance); |
|
|
|
} |
|
|
|
|
|
|
|
// process hit that happens within the line
|
|
|
|
var characterHit = new CharacterHit(); |
|
|
|
var currentDistance = 0.0; |
|
|
|
var currentVisualIndex = 0; |
|
|
|
|
|
|
|
for (var i = 0; i <= lastIndex; i++) |
|
|
|
{ |
|
|
|
var currentRun = _textRuns[i]; |
|
|
|
currentVisualIndex = i; |
|
|
|
|
|
|
|
if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) |
|
|
|
{ |
|
|
|
var rightToLeftIndex = i; |
|
|
|
currentPosition += currentRun.Length; |
|
|
|
|
|
|
|
while (rightToLeftIndex + 1 <= _textRuns.Length - 1) |
|
|
|
{ |
|
|
|
@ -269,8 +252,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentPosition += nextShaped.Length; |
|
|
|
|
|
|
|
rightToLeftIndex++; |
|
|
|
} |
|
|
|
|
|
|
|
@ -282,6 +263,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
|
|
|
|
currentRun = _textRuns[j]; |
|
|
|
currentVisualIndex = j; |
|
|
|
|
|
|
|
if (currentRun is not ShapedTextRun) |
|
|
|
{ |
|
|
|
@ -293,16 +275,15 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
if (currentDistance + shapedRun.Size.Width <= distance) |
|
|
|
{ |
|
|
|
currentDistance += shapedRun.Size.Width; |
|
|
|
currentPosition -= currentRun.Length; |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
return GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); |
|
|
|
return GetRunCharacterHit(currentRun, GetRunTextSourcePosition(j), distance - currentDistance); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); |
|
|
|
characterHit = GetRunCharacterHit(currentRun, GetRunTextSourcePosition(currentVisualIndex), distance - currentDistance); |
|
|
|
|
|
|
|
if (currentRun is DrawableTextRun drawableTextRun) |
|
|
|
{ |
|
|
|
@ -310,15 +291,11 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
currentDistance += drawableTextRun.Size.Width; |
|
|
|
|
|
|
|
currentPosition += currentRun.Length; |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
currentPosition += currentRun.Length; |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
@ -338,12 +315,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); |
|
|
|
|
|
|
|
var offset = 0; |
|
|
|
|
|
|
|
if (shapedRun.GlyphRun.IsLeftToRight) |
|
|
|
{ |
|
|
|
offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster); |
|
|
|
} |
|
|
|
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster); |
|
|
|
|
|
|
|
characterHit = new CharacterHit(offset + characterHit.FirstCharacterIndex, characterHit.TrailingLength); |
|
|
|
|
|
|
|
@ -515,18 +487,33 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
case ShapedTextRun shapedRun: |
|
|
|
{ |
|
|
|
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster - characterHit.TrailingLength); |
|
|
|
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster); |
|
|
|
|
|
|
|
if (offset > 0) |
|
|
|
if (characterHit.FirstCharacterIndex < currentPosition && offset > 0) |
|
|
|
{ |
|
|
|
currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength); |
|
|
|
// Crossing from a previous run: find the nearest character hit at the first cluster
|
|
|
|
var nearest = shapedRun.GlyphRun.FindNearestCharacterHit( |
|
|
|
shapedRun.GlyphRun.Metrics.FirstCluster, out _); |
|
|
|
nextCharacterHit = new CharacterHit( |
|
|
|
nearest.FirstCharacterIndex + offset, nearest.TrailingLength); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (offset > 0) |
|
|
|
{ |
|
|
|
currentCharacterrHit = new CharacterHit( |
|
|
|
Math.Max(0, characterHit.FirstCharacterIndex - offset), |
|
|
|
characterHit.TrailingLength); |
|
|
|
} |
|
|
|
|
|
|
|
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit); |
|
|
|
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit); |
|
|
|
|
|
|
|
if (offset > 0) |
|
|
|
{ |
|
|
|
nextCharacterHit = new CharacterHit(nextCharacterHit.FirstCharacterIndex + offset, nextCharacterHit.TrailingLength); |
|
|
|
if (offset > 0) |
|
|
|
{ |
|
|
|
nextCharacterHit = new CharacterHit( |
|
|
|
nextCharacterHit.FirstCharacterIndex + offset, |
|
|
|
nextCharacterHit.TrailingLength); |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
@ -569,6 +556,34 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
return currentDirection; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the text source character index for the run at the given visual position in <see cref="_textRuns"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="visualRunIndex">The index of the run in the visual-order <see cref="_textRuns"/> array.</param>
|
|
|
|
/// <returns>The text source character index where the run starts.</returns>
|
|
|
|
private int GetRunTextSourcePosition(int visualRunIndex) |
|
|
|
{ |
|
|
|
if (_indexedTextRuns != null) |
|
|
|
{ |
|
|
|
for (var i = 0; i < _indexedTextRuns.Count; i++) |
|
|
|
{ |
|
|
|
if (_indexedTextRuns[i].RunIndex == visualRunIndex) |
|
|
|
{ |
|
|
|
return _indexedTextRuns[i].TextSourceCharacterIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var position = FirstTextSourceIndex; |
|
|
|
|
|
|
|
for (var i = 0; i < visualRunIndex && i < _textRuns.Length; i++) |
|
|
|
{ |
|
|
|
position += _textRuns[i].Length; |
|
|
|
} |
|
|
|
|
|
|
|
return position; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Get the last consecutive visual run index that shares the same direction as the current direction.
|
|
|
|
/// </summary>
|
|
|
|
|