in src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs [625:815]
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
{
if(textLength == 0)
{
throw new ArgumentOutOfRangeException(nameof(textLength), textLength, $"{nameof(textLength)} ('0') must be a non-zero value. ");
}
if (_indexedTextRuns is null || _indexedTextRuns.Count == 0)
{
return [];
}
var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength;
//We can return early if the requested text range is before the line's text range.
if (firstTextSourceIndex + textLength < FirstTextSourceIndex)
{
var indexedTextRun = _indexedTextRuns[0];
var currentDirection = GetRunDirection(indexedTextRun.TextRun, _resolvedFlowDirection);
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.
if (firstTextSourceIndex >= FirstTextSourceIndex + Length)
{
var indexedTextRun = _indexedTextRuns[_indexedTextRuns.Count - 1];
var currentDirection = GetRunDirection(indexedTextRun.TextRun, _resolvedFlowDirection);
return [new TextBounds(new Rect(WidthIncludingTrailingWhitespace, 0, 0, Height), currentDirection, [])];
}
var result = new List<TextBounds>();
TextBounds? lastBounds = null;
while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length)
{
var currentIndexedRun = FindIndexedRun(out var indexedRunIndex);
if (currentIndexedRun == null)
{
break;
}
var currentTextRun = currentIndexedRun.TextRun;
if (currentTextRun == null)
{
break;
}
var currentDirection = GetRunDirection(currentTextRun, _resolvedFlowDirection);
if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= firstTextSourceIndex)
{
currentPosition += currentTextRun.Length;
continue;
}
var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex);
var directionalWidth = 0.0;
if (currentTextRun is DrawableTextRun currentDrawable)
{
directionalWidth = currentDrawable.Size.Width;
}
var firstRunIndex = currentIndexedRun.RunIndex;
var lastRunIndex = GetLastDirectionalRunIndex(indexedRunIndex, currentDirection, ref directionalWidth);
TextBounds currentBounds;
int coveredLength;
switch (currentDirection)
{
case FlowDirection.RightToLeft:
{
currentBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex,
currentPosition, remainingLength, out coveredLength, out currentPosition);
break;
}
default:
{
currentBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex,
currentPosition, remainingLength, out coveredLength, out currentPosition);
break;
}
}
if (lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
{
currentBounds = lastBounds;
result[result.Count - 1] = currentBounds;
}
else
{
result.Add(currentBounds);
}
lastBounds = currentBounds;
if(coveredLength <= 0)
{
throw new InvalidOperationException("Covered length must be greater than zero.");
}
remainingLength -= coveredLength;
}
result.Sort(TextBoundsComparer);
return result;
IndexedTextRun FindIndexedRun(out int index)
{
index = 0;
var currentIndexedRun = _indexedTextRuns[index];
while (currentIndexedRun.TextSourceCharacterIndex != currentPosition)
{
if (index + 1 == _indexedTextRuns.Count)
{
break;
}
index++;
currentIndexedRun = _indexedTextRuns[index];
}
return currentIndexedRun;
}
double GetPreceedingDistance(int firstIndex)
{
var distance = 0.0;
for (var i = 0; i < firstIndex; i++)
{
var currentRun = _textRuns[i];
if (currentRun is DrawableTextRun drawableTextRun)
{
distance += drawableTextRun.Size.Width;
}
}
return distance;
}
bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds)
{
if (currentBounds.FlowDirection != lastBounds.FlowDirection)
{
return false;
}
if (currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
{
foreach (var runBounds in currentBounds.TextRunBounds)
{
lastBounds.TextRunBounds.Add(runBounds);
}
lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle);
return true;
}
if (currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
{
for (int i = 0; i < currentBounds.TextRunBounds.Count; i++)
{
lastBounds.TextRunBounds.Insert(i, currentBounds.TextRunBounds[i]);
}
lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle);
return true;
}
return false;
}
}