private static TextLineImpl PerformTextWrapping()

in src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs [731:966]


        private static TextLineImpl PerformTextWrapping(List<TextRun> textRuns, bool canReuseTextRunList,
            int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties,
            FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak, FormattingObjectPool objectPool)
        {
            if (textRuns.Count == 0)
            {
                return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
            }

            var measuredLength = MeasureLength(textRuns, paragraphWidth);

            if(measuredLength == 0)
            {
                if(paragraphProperties.TextWrapping == TextWrapping.NoWrap)
                {
                    for (int i = 0; i < textRuns.Count; i++)
                    {
                        measuredLength += textRuns[i].Length;
                    }
                }
                else
                {
                    var firstRun = textRuns[0];

                    if(firstRun is ShapedTextRun)
                    {
                        var graphemeEnumerator = new GraphemeEnumerator(firstRun.Text.Span);

                        if(graphemeEnumerator.MoveNext(out var grapheme))
                        {
                            measuredLength = grapheme.Length;
                        }
                        else
                        {
                            measuredLength = 1;
                        }
                    }
                    else
                    {
                        measuredLength = firstRun.Length;
                    }
                }
            }

            var currentLength = 0;

            var lastWrapPosition = 0;

            var currentPosition = 0;

            for (var index = 0; index < textRuns.Count; index++)
            {
                var breakFound = false;

                var currentRun = textRuns[index];

                switch (currentRun)
                {
                    case ShapedTextRun:
                        {
                            var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);

                            while (lineBreaker.MoveNext(out var lineBreak))
                            {
                                if (lineBreak.Required &&
                                    currentLength + lineBreak.PositionMeasure <= measuredLength)
                                {
                                    //Explicit break found
                                    breakFound = true;

                                    currentPosition = currentLength + lineBreak.PositionWrap;

                                    break;
                                }

                                if (currentLength + lineBreak.PositionMeasure > measuredLength)
                                {
                                    if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
                                    {
                                        if (lastWrapPosition > 0)
                                        {
                                            currentPosition = lastWrapPosition;

                                            breakFound = true;

                                            break;
                                        }

                                        //Find next possible wrap position (overflow)
                                        if (index < textRuns.Count - 1)
                                        {
                                            if (lineBreak.PositionWrap != currentRun.Length)
                                            {
                                                //We already found the next possible wrap position.
                                                breakFound = true;

                                                currentPosition = currentLength + lineBreak.PositionWrap;

                                                break;
                                            }

                                            while (lineBreaker.MoveNext(out lineBreak))
                                            {
                                                currentPosition += lineBreak.PositionWrap;

                                                if (lineBreak.PositionWrap != currentRun.Length)
                                                {
                                                    break;
                                                }

                                                index++;

                                                if (index >= textRuns.Count)
                                                {
                                                    break;
                                                }

                                                currentRun = textRuns[index];

                                                lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
                                            }
                                        }
                                        else
                                        {
                                            currentPosition = currentLength + lineBreak.PositionWrap;
                                        }

                                        if (currentPosition == 0 && measuredLength > 0)
                                        {
                                            currentPosition = measuredLength;
                                        }

                                        breakFound = true;

                                        break;
                                    }

                                    //We overflowed so we use the last available wrap position.
                                    currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition;

                                    breakFound = true;

                                    break;
                                }

                                if (lineBreak.PositionMeasure != lineBreak.PositionWrap || lineBreak.PositionWrap != currentRun.Length)
                                {
                                    lastWrapPosition = currentLength + lineBreak.PositionWrap;
                                }
                            }

                            break;
                        }
                }

                if (!breakFound)
                {
                    currentLength += currentRun.Length;

                    continue;
                }

                measuredLength = currentPosition;

                break;
            }

            var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength, objectPool);

            try
            {
                TextLineBreak? textLineBreak;
                if (postSplitRuns?.Count > 0)
                {
                    List<TextRun> remainingRuns;

                    // reuse the list as much as possible:
                    // if canReuseTextRunList == true it's coming from previous remaining runs
                    if (canReuseTextRunList)
                    {
                        remainingRuns = textRuns;
                        remainingRuns.Clear();
                    }
                    else
                    {
                        remainingRuns = new List<TextRun>();
                    }

                    for (var i = 0; i < postSplitRuns.Count; ++i)
                    {
                        remainingRuns.Add(postSplitRuns[i]);
                    }

                    textLineBreak = new WrappingTextLineBreak(null, resolvedFlowDirection, remainingRuns);
                }
                else if (currentLineBreak?.TextEndOfLine is { } textEndOfLine)
                {
                    textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
                }
                else
                {
                    textLineBreak = null;
                }

                if (postSplitRuns?.Count > 0)
                {
                    ResetTrailingWhitespaceBidiLevels(preSplitRuns, paragraphProperties.FlowDirection, objectPool);
                }

                var remainingTextRuns = new TextRun[preSplitRuns.Count];
                //Measured lenght might have changed after a possible line break was found so we need to calculate the real length
                var splitLength = 0;

                for(var i = 0; i < preSplitRuns.Count; i++)
                {
                    var currentRun = preSplitRuns[i];

                    remainingTextRuns[i] = currentRun;

                    splitLength += currentRun.Length;
                }

                var textLine = new TextLineImpl(remainingTextRuns, firstTextSourceIndex, splitLength,
                    paragraphWidth, paragraphProperties, resolvedFlowDirection,
                    textLineBreak);

                textLine.FinalizeLine();

                return textLine;
            }
            finally
            {
                objectPool.TextRunLists.Return(ref preSplitRuns);
                objectPool.TextRunLists.Return(ref postSplitRuns);
            }
        }