diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index c7ec28f16d..812c4e9eb8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -57,7 +57,7 @@ namespace Avalonia.Media.TextFormatting textSourceLength, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); - textLine.FinalizeLine(); + textLine.FinalizeLine(); return textLine; } @@ -236,49 +236,49 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { case UnshapedTextRun shapeableRun: - { - groupedRuns.Clear(); - groupedRuns.Add(shapeableRun); + { + groupedRuns.Clear(); + groupedRuns.Add(shapeableRun); - var text = shapeableRun.Text; - var properties = shapeableRun.Properties; + var text = shapeableRun.Text; + var properties = shapeableRun.Properties; - while (index + 1 < processedRuns.Count) - { - if (processedRuns[index + 1] is not UnshapedTextRun nextRun) + while (index + 1 < processedRuns.Count) { + if (processedRuns[index + 1] is not UnshapedTextRun nextRun) + { + break; + } + + if (shapeableRun.BidiLevel == nextRun.BidiLevel + && TryJoinContiguousMemories(text, nextRun.Text, out var joinedText) + && CanShapeTogether(properties, nextRun.Properties)) + { + groupedRuns.Add(nextRun); + index++; + shapeableRun = nextRun; + text = joinedText; + continue; + } + break; } - if (shapeableRun.BidiLevel == nextRun.BidiLevel - && TryJoinContiguousMemories(text, nextRun.Text, out var joinedText) - && CanShapeTogether(properties, nextRun.Properties)) - { - groupedRuns.Add(nextRun); - index++; - shapeableRun = nextRun; - text = joinedText; - continue; - } + var shaperOptions = new TextShaperOptions( + properties.CachedGlyphTypeface, + properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo, + paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); + + ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns); break; } - - var shaperOptions = new TextShaperOptions( - properties.CachedGlyphTypeface, - properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo, - paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); - - ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns); - - break; - } default: - { - shapedRuns.Add(currentRun); + { + shapedRuns.Add(currentRun); - break; - } + break; + } } } } @@ -699,7 +699,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { case ShapedTextRun: - { + { var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span); while (lineBreaker.MoveNext(out var lineBreak)) @@ -741,7 +741,7 @@ namespace Avalonia.Media.TextFormatting break; } - while (lineBreaker.MoveNext(out lineBreak) && index < textRuns.Count) + while (lineBreaker.MoveNext(out lineBreak)) { currentPosition += lineBreak.PositionWrap; @@ -767,6 +767,11 @@ namespace Avalonia.Media.TextFormatting currentPosition = currentLength + lineBreak.PositionWrap; } + if (currentPosition == 0 && measuredLength > 0) + { + currentPosition = measuredLength; + } + breakFound = true; break; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 4a7916275d..d29063e07d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -172,9 +172,21 @@ namespace Avalonia.Media.TextFormatting distance -= Start; + var firstRunIndex = 0; + + if (_textRuns[firstRunIndex] is TextEndOfLine) + { + firstRunIndex++; + } + + if(firstRunIndex >= _textRuns.Length) + { + return new CharacterHit(FirstTextSourceIndex); + } + if (distance <= 0) { - var firstRun = _textRuns[0]; + var firstRun = _textRuns[firstRunIndex]; return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs index f963277397..7837749adf 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs @@ -7,25 +7,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { private readonly string _text; private readonly GenericTextRunProperties _defaultGenericPropertiesRunProperties; + private readonly bool _addEndOfParagraph; - public SingleBufferTextSource(string text, GenericTextRunProperties defaultProperties) + public SingleBufferTextSource(string text, GenericTextRunProperties defaultProperties, bool addEndOfParagraph = false) { _text = text; _defaultGenericPropertiesRunProperties = defaultProperties; + _addEndOfParagraph = addEndOfParagraph; } public TextRun GetTextRun(int textSourceIndex) { if (textSourceIndex >= _text.Length) { - return null; + return _addEndOfParagraph ? new TextEndOfParagraph() : null; } var runText = _text.AsMemory(textSourceIndex); if (runText.IsEmpty) { - return null; + return _addEndOfParagraph ? new TextEndOfParagraph() : null; } return new TextCharacters(runText, _defaultGenericPropertiesRunProperties); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index e8b87ebe00..954169f975 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -242,10 +242,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var defaultProperties = new GenericTextRunProperties(Typeface.Default); - var textSource = new SingleBufferTextSource(text, defaultProperties); + var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow); + + var textSource = new SingleBufferTextSource("ABCDEFHFFHFJHKHFK", defaultProperties, true); var formatter = new TextFormatterImpl(); + var line = formatter.FormatLine(textSource, 0, 33, paragraphProperties); + + textSource = new SingleBufferTextSource(text, defaultProperties); + var numberOfLines = 0; var currentPosition = 0; @@ -253,8 +259,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting while (currentPosition < text.Length) { var textLine = - formatter.FormatLine(textSource, currentPosition, 1, - new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow)); + formatter.FormatLine(textSource, currentPosition, 1, paragraphProperties); if (text.Length - currentPosition > expectedCharactersPerLine) {