|
|
|
@ -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<TextRunBounds> { 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<TextRunBounds> { currentRunBounds })); |
|
|
|
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { 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<TextBounds> 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; |
|
|
|
} |
|
|
|
|