diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 0b7e74cf1d..694961cb94 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -713,31 +713,37 @@ namespace Avalonia.Media.TextFormatting var measuredLength = MeasureLength(textRuns, paragraphWidth); - if (measuredLength == 0 && paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) + if(measuredLength == 0) { - for (int i = 0; i < textRuns.Count; i++) + if(paragraphProperties.TextWrapping == TextWrapping.NoWrap) { - measuredLength += textRuns[i].Length; - } - - TextLineBreak? textLineBreak; - - if (currentLineBreak?.TextEndOfLine is { } textEndOfLine) - { - textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); + for (int i = 0; i < textRuns.Count; i++) + { + measuredLength += textRuns[i].Length; + } } else { - textLineBreak = null; - } - - var textLine = new TextLineImpl(textRuns.ToArray(), firstTextSourceIndex, measuredLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, - textLineBreak); + var firstRun = textRuns[0]; - textLine.FinalizeLine(); + if(firstRun is ShapedTextRun) + { + var graphemeEnumerator = new GraphemeEnumerator(firstRun.Text.Span); - return textLine; + if(graphemeEnumerator.MoveNext(out var grapheme)) + { + measuredLength = grapheme.Length; + } + else + { + measuredLength = 1; + } + } + else + { + measuredLength = firstRun.Length; + } + } } var currentLength = 0; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 4e90425b5f..92901987be 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -766,12 +766,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } - [Fact] - public void Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Produce_Empty_Lines() + + [Theory] + [InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)] + public void Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Produce_Empty_Lines(TextWrapping wrapping) { var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black); var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties, - textWrap: TextWrapping.WrapWithOverflow); + textWrap: wrapping); using (Start()) { @@ -780,6 +782,48 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(200d, textLine.WidthIncludingTrailingWhitespace); } } + + [Theory] + [InlineData(TextWrapping.NoWrap),InlineData(TextWrapping.Wrap),InlineData(TextWrapping.WrapWithOverflow)] + public void Line_Formatting_For_Oversized_Embedded_Runs_Inside_Normal_Text_Does_Not_Produce_Empty_Lines( + TextWrapping wrapping) + { + var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black); + var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties, + textWrap: wrapping); + + using (Start()) + { + var typeface = new Typeface(FontFamily.Parse("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#DejaVu Sans")); + + var text1 = new TextCharacters("Hello", + new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black)); + var text2 = new TextCharacters("world", + new GenericTextRunProperties(typeface, foregroundBrush: Brushes.Black)); + + var source = new ListTextSource( + text1, + new RectangleRun(new Rect(0, 0, 200, 10), Brushes.Aqua), + new InvisibleRun(1), + new TextEndOfLine(), + text2, + new TextEndOfParagraph(1)); + + var lines = new List(); + var dcp = 0; + for (var c = 0;; c++) + { + Assert.True(c < 1000, "Infinite loop"); + var textLine = TextFormatter.Current.FormatLine(source, dcp, 30, paragraphProperties); + Assert.NotNull(textLine); + lines.Add(textLine); + dcp += textLine.Length; + + if (textLine.TextLineBreak is {} eol && eol.TextEndOfLine is TextEndOfParagraph) + break; + } + } + } class IncrementalTabProperties : TextParagraphProperties { @@ -910,7 +954,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting private class ListTextSource : ITextSource { - private Dictionary _runs = new(); + private List _runs = new(); public ListTextSource(params TextRun[] runs) : this((IEnumerable)runs) { @@ -919,18 +963,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting public ListTextSource(IEnumerable runs) { - var off = 0; - foreach (var r in runs) - { - _runs[off] = r; - off += r.Length; - } + _runs = runs.ToList(); } public TextRun GetTextRun(int textSourceIndex) { - _runs.TryGetValue(textSourceIndex, out var rv); - return rv; + var off = 0; + for (var c = 0; c < _runs.Count; c++) + { + var run = _runs[c]; + if (textSourceIndex >= off && textSourceIndex - off < run.Length) + { + if (run.Length == 1) + return run; + var chars = ((TextCharacters)run); + return new TextCharacters(chars.Text.Slice(textSourceIndex - off), chars.Properties); + } + + off += run.Length; + } + + return null; } }