|
|
|
@ -580,7 +580,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
/// </returns>
|
|
|
|
private int GetLastDirectionalRunIndex(int indexedRunIndex, FlowDirection flowDirection, ref double directionalWidth) |
|
|
|
{ |
|
|
|
if(_indexedTextRuns is null) |
|
|
|
if (_indexedTextRuns is null) |
|
|
|
{ |
|
|
|
return -1; |
|
|
|
} |
|
|
|
@ -624,7 +624,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength) |
|
|
|
{ |
|
|
|
if(textLength == 0) |
|
|
|
if (textLength == 0) |
|
|
|
{ |
|
|
|
throw new ArgumentOutOfRangeException(nameof(textLength), textLength, $"{nameof(textLength)} ('0') must be a non-zero value. "); |
|
|
|
} |
|
|
|
@ -643,7 +643,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
var indexedTextRun = _indexedTextRuns[0]; |
|
|
|
var currentDirection = GetRunDirection(indexedTextRun.TextRun, _resolvedFlowDirection); |
|
|
|
|
|
|
|
return [new TextBounds(new Rect(0,0,0, Height), currentDirection, [])]; |
|
|
|
return [new TextBounds(new Rect(0, 0, 0, Height), currentDirection, [])]; |
|
|
|
} |
|
|
|
|
|
|
|
//We can return early if the requested text range is after the line's text range.
|
|
|
|
@ -667,7 +667,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var currentTextRun = currentIndexedRun.TextRun; |
|
|
|
|
|
|
|
if (currentTextRun == null) |
|
|
|
@ -691,7 +691,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
directionalWidth = currentDrawable.Size.Width; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var firstRunIndex = currentIndexedRun.RunIndex; |
|
|
|
var lastRunIndex = GetLastDirectionalRunIndex(indexedRunIndex, currentDirection, ref directionalWidth); |
|
|
|
|
|
|
|
@ -709,8 +709,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
default: |
|
|
|
{ |
|
|
|
currentBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, |
|
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
|
currentBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, |
|
|
|
currentPosition, remainingLength, out coveredLength, out currentPosition); |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
@ -729,7 +729,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
lastBounds = currentBounds; |
|
|
|
|
|
|
|
if(coveredLength <= 0) |
|
|
|
if (coveredLength <= 0) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("Covered length must be greater than zero."); |
|
|
|
} |
|
|
|
@ -988,14 +988,14 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
var runBounds = GetRunBounds(shapedTextRun, endX, firstTextSourceIndex, remainingLength, currentPosition); |
|
|
|
|
|
|
|
if(runBounds.TextSourceCharacterIndex < FirstTextSourceIndex + Length) |
|
|
|
if (runBounds.TextSourceCharacterIndex < FirstTextSourceIndex + Length) |
|
|
|
{ |
|
|
|
textRunBounds.Add(runBounds); |
|
|
|
} |
|
|
|
|
|
|
|
currentPosition = runBounds.TextSourceCharacterIndex + runBounds.Length; |
|
|
|
|
|
|
|
if(i == firstRunIndex) |
|
|
|
if (i == firstRunIndex) |
|
|
|
{ |
|
|
|
startX = runBounds.Rectangle.Left; |
|
|
|
} |
|
|
|
@ -1112,7 +1112,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
var startHitIndex = startHit.FirstCharacterIndex; |
|
|
|
|
|
|
|
//If the requested text range starts at the trailing edge we need to move at the end of the hit
|
|
|
|
if(startHitIndex < startIndex) |
|
|
|
if (startHitIndex < startIndex) |
|
|
|
{ |
|
|
|
startHitIndex += startHit.TrailingLength; |
|
|
|
} |
|
|
|
@ -1230,7 +1230,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
case not null: |
|
|
|
{ |
|
|
|
if(direction == LogicalDirection.Forward) |
|
|
|
if (direction == LogicalDirection.Forward) |
|
|
|
{ |
|
|
|
if (textPosition == codepointIndex) |
|
|
|
{ |
|
|
|
@ -1316,8 +1316,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var height = descent - ascent + lineGap; |
|
|
|
|
|
|
|
var inkBounds = new Rect(); |
|
|
|
|
|
|
|
for (var index = 0; index < _textRuns.Length; index++) |
|
|
|
@ -1325,31 +1323,53 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
switch (_textRuns[index]) |
|
|
|
{ |
|
|
|
case ShapedTextRun textRun: |
|
|
|
{ |
|
|
|
var glyphRun = textRun.GlyphRun; |
|
|
|
//Align the ink bounds at the common baseline
|
|
|
|
var offsetY = -ascent - textRun.Baseline; |
|
|
|
{ |
|
|
|
var glyphRun = textRun.GlyphRun; |
|
|
|
//Align the ink bounds at the common baseline
|
|
|
|
var offsetY = -ascent - textRun.Baseline; |
|
|
|
|
|
|
|
var runBounds = glyphRun.InkBounds.Translate(new Vector(widthIncludingWhitespace, offsetY)); |
|
|
|
var runBounds = glyphRun.InkBounds.Translate(new Vector(widthIncludingWhitespace, offsetY)); |
|
|
|
|
|
|
|
inkBounds = inkBounds.Union(runBounds); |
|
|
|
inkBounds = inkBounds.Union(runBounds); |
|
|
|
|
|
|
|
widthIncludingWhitespace += textRun.Size.Width; |
|
|
|
widthIncludingWhitespace += textRun.Size.Width; |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
case DrawableTextRun drawableTextRun: |
|
|
|
{ |
|
|
|
//Align the bounds at the common baseline
|
|
|
|
var offsetY = -ascent - drawableTextRun.Baseline; |
|
|
|
{ |
|
|
|
//Align the bounds at the common baseline
|
|
|
|
var offsetY = -ascent - drawableTextRun.Baseline; |
|
|
|
|
|
|
|
inkBounds = inkBounds.Union(new Rect(new Point(widthIncludingWhitespace, offsetY), drawableTextRun.Size)); |
|
|
|
inkBounds = inkBounds.Union(new Rect(new Point(widthIncludingWhitespace, offsetY), drawableTextRun.Size)); |
|
|
|
|
|
|
|
widthIncludingWhitespace += drawableTextRun.Size.Width; |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
widthIncludingWhitespace += drawableTextRun.Size.Width; |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var halfLineGap = lineGap * 0.5; |
|
|
|
var naturalHeight = descent - ascent + lineGap; |
|
|
|
var baseline = -ascent + halfLineGap; |
|
|
|
var height = naturalHeight; |
|
|
|
|
|
|
|
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) |
|
|
|
{ |
|
|
|
if (lineHeight <= naturalHeight) |
|
|
|
{ |
|
|
|
//Clamp to the specified line height
|
|
|
|
height = lineHeight; |
|
|
|
baseline = -ascent; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Center the text vertically within the specified line height
|
|
|
|
height = lineHeight; |
|
|
|
var extra = lineHeight - (descent - ascent); |
|
|
|
baseline = -ascent + extra / 2; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1385,24 +1405,14 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
|
|
|
|
var extent = inkBounds.Height; |
|
|
|
//The width of overhanging pixels at the bottom
|
|
|
|
var overhangAfter = inkBounds.Bottom - height; |
|
|
|
//The height of overhanging pixels at the bottom
|
|
|
|
var overhangAfter = inkBounds.Bottom - height + halfLineGap; |
|
|
|
//The width of overhanging pixels at the natural alignment point. Positive value means we are inside.
|
|
|
|
var overhangLeading = inkBounds.Left; |
|
|
|
//The width of overhanging pixels at the end of the natural bounds. Positive value means we are inside.
|
|
|
|
var overhangTrailing = widthIncludingWhitespace - inkBounds.Right; |
|
|
|
var hasOverflowed = width > _paragraphWidth; |
|
|
|
|
|
|
|
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) |
|
|
|
{ |
|
|
|
//Center the line
|
|
|
|
var offset = (height - lineHeight) / 2; |
|
|
|
|
|
|
|
ascent += offset; |
|
|
|
|
|
|
|
height = lineHeight; |
|
|
|
} |
|
|
|
|
|
|
|
var start = GetParagraphOffsetX(width, widthIncludingWhitespace); |
|
|
|
|
|
|
|
_inkBounds = inkBounds.Translate(new Vector(start, 0)); |
|
|
|
@ -1416,7 +1426,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
Extent = extent, |
|
|
|
NewlineLength = newLineLength, |
|
|
|
Start = start, |
|
|
|
TextBaseline = -ascent, |
|
|
|
TextBaseline = baseline, |
|
|
|
TrailingWhitespaceLength = trailingWhitespaceLength, |
|
|
|
Width = width, |
|
|
|
WidthIncludingTrailingWhitespace = widthIncludingWhitespace, |
|
|
|
|