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