public override IReadOnlyList GetTextBounds()

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;
            }
        }