diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index a40cbf95ad..a609800fb8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -57,21 +57,21 @@ namespace Avalonia.Media.TextFormatting switch (paragraphProperties.TextWrapping) { case TextWrapping.NoWrap: - { - var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex, - textSourceLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); + { + var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex, + textSourceLength, + paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); - return textLine; - } + return textLine; + } case TextWrapping.WrapWithOverflow: case TextWrapping.Wrap: - { - return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth, - paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool); - } + { + return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth, + paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool); + } default: throw new ArgumentOutOfRangeException(nameof(paragraphProperties.TextWrapping)); } @@ -568,9 +568,9 @@ namespace Avalonia.Media.TextFormatting return false; } - private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength) + private static int MeasureLength(IReadOnlyList textRuns, double paragraphWidth) { - measuredLength = 0; + var measuredLength = 0; var currentWidth = 0.0; for (var i = 0; i < textRuns.Count; ++i) @@ -583,25 +583,59 @@ namespace Avalonia.Media.TextFormatting { if (shapedTextCharacters.ShapedBuffer.Length > 0) { - var firstCluster = shapedTextCharacters.ShapedBuffer[0].GlyphCluster; - var lastCluster = firstCluster; + var runLength = 0; for (var j = 0; j < shapedTextCharacters.ShapedBuffer.Length; j++) { - var glyphInfo = shapedTextCharacters.ShapedBuffer[j]; + var currentInfo = shapedTextCharacters.ShapedBuffer[j]; + + var clusterWidth = currentInfo.GlyphAdvance; - if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) + GlyphInfo nextInfo = default; + + while (j + 1 < shapedTextCharacters.ShapedBuffer.Length) { - measuredLength += Math.Max(0, lastCluster - firstCluster); + nextInfo = shapedTextCharacters.ShapedBuffer[j + 1]; + + if (currentInfo.GlyphCluster == nextInfo.GlyphCluster) + { + clusterWidth += nextInfo.GlyphAdvance; + + j++; + + continue; + } + + break; + } - return measuredLength != 0; + var clusterLength = Math.Max(0, nextInfo.GlyphCluster - currentInfo.GlyphCluster); + + if(clusterLength == 0) + { + clusterLength = currentRun.Length - runLength; + } + + if(clusterLength == 0) + { + clusterLength = shapedTextCharacters.GlyphRun.Metrics.FirstCluster + currentRun.Length - currentInfo.GlyphCluster; + } + + if (currentWidth + clusterWidth > paragraphWidth) + { + if (runLength == 0 && measuredLength == 0) + { + runLength = clusterLength; + } + + return measuredLength + runLength; } - lastCluster = glyphInfo.GlyphCluster; - currentWidth += glyphInfo.GlyphAdvance; + currentWidth += clusterWidth; + runLength += clusterLength; } - measuredLength += currentRun.Length; + measuredLength += runLength; } break; @@ -611,7 +645,7 @@ namespace Avalonia.Media.TextFormatting { if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth) { - return measuredLength != 0; + return measuredLength; } measuredLength += currentRun.Length; @@ -628,7 +662,7 @@ namespace Avalonia.Media.TextFormatting } } - return measuredLength != 0; + return measuredLength; } /// @@ -675,9 +709,11 @@ namespace Avalonia.Media.TextFormatting return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties); } - if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength)) + var measuredLength = MeasureLength(textRuns, paragraphWidth); + + if(measuredLength == 0) { - measuredLength = 1; + } var currentLength = 0; @@ -798,6 +834,12 @@ namespace Avalonia.Media.TextFormatting continue; } + //We don't want to surpass the measuredLength with trailing whitespace when we are in a right to left setting. + if(currentPosition > measuredLength && resolvedFlowDirection == FlowDirection.RightToLeft) + { + break; + } + measuredLength = currentPosition; break; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index b55ae74965..19eaaaa0d9 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -663,7 +663,6 @@ namespace Avalonia.Controls var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); _constraint = availableSize.Deflate(padding); - _textLayout?.Dispose(); _textLayout = null; @@ -690,18 +689,18 @@ namespace Avalonia.Controls inline.BuildTextRun(textRuns); } - foreach (var textRun in textRuns) + _textRuns = textRuns; + + foreach (var textRun in _textRuns) { if (textRun is EmbeddedControlRun controlRun && - controlRun.Control is Control control) + controlRun.Control is Control control) { VisualChildren.Add(control); control.Measure(Size.Infinity); } } - - _textRuns = textRuns; } var measuredSize = TextLayout.Bounds.Size.Inflate(padding); @@ -711,64 +710,39 @@ namespace Avalonia.Controls protected override Size ArrangeOverride(Size finalSize) { - var textWidth = Math.Ceiling(TextLayout.Bounds.Width); - - if (finalSize.Width < textWidth) - { - finalSize = finalSize.WithWidth(textWidth); - } - - var scale = LayoutHelper.GetLayoutScale(this); - - var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); - if (HasComplexContent) { - ArrangeComplexContent(TextLayout, padding); - } + var scale = LayoutHelper.GetLayoutScale(this); - if (MathUtilities.AreClose(_constraint.Inflate(padding).Width, finalSize.Width)) - { - return finalSize; - } + var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); - _constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity); + var currentY = padding.Top; - _textLayout?.Dispose(); - _textLayout = null; - - if (HasComplexContent) - { - ArrangeComplexContent(TextLayout, padding); - } - - return finalSize; - } - - private static void ArrangeComplexContent(TextLayout textLayout, Thickness padding) - { - var currentY = padding.Top; - - foreach (var textLine in textLayout.TextLines) - { - var currentX = padding.Left + textLine.Start; - - foreach (var run in textLine.TextRuns) + foreach (var textLine in TextLayout.TextLines) { - if (run is DrawableTextRun drawable) + var currentX = padding.Left + textLine.Start; + + foreach (var run in textLine.TextRuns) { - if (drawable is EmbeddedControlRun controlRun - && controlRun.Control is Control control) + if (run is DrawableTextRun drawable) { - control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange( + new Rect(new Point(currentX, currentY), + new Size(control.DesiredSize.Width, textLine.Height))); + } + + currentX += drawable.Size.Width; } - - currentX += drawable.Size.Width; } - } - currentY += textLine.Height; + currentY += textLine.Height; + } } + + return finalSize; } protected override AutomationPeer OnCreateAutomationPeer() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 779ffc0341..0cb8b09579 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -731,36 +731,25 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_COMPOSITION: { - var previousComposition = Imm32InputMethod.Current.Composition; - var flags = (GCS)ToInt32(lParam); - var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); + if ((flags & GCS.GCS_COMPSTR) != 0) + { + var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); - Imm32InputMethod.Current.CompositionChanged(currentComposition); + Imm32InputMethod.Current.CompositionChanged(currentComposition); + } - switch (flags) + if ((flags & GCS.GCS_RESULTSTR) != 0) { - case GCS.GCS_RESULTSTR: - { - if(!string.IsNullOrEmpty(previousComposition) && ToInt32(wParam) >= 32) - { - Imm32InputMethod.Current.Composition = previousComposition; + var result = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_RESULTSTR); - _ignoreWmChar = true; - } - break; - } - case GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE: - { - // Chinese IME sends WM_CHAR after composition has finished. - break; - } - case GCS.GCS_RESULTREADSTR | GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE: - { - // Japanese IME sends WM_CHAR after composition has finished. - break; - } + if (!string.IsNullOrEmpty(result)) + { + Imm32InputMethod.Current.Composition = result; + + _ignoreWmChar = true; + } } break; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index cdcaadeff8..8c16e1046b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -416,7 +416,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor", - new[] { "Lorem ipsum ", "dolor sit ", "amet, ", "consectetur ", "adipisicing ", "elit, sed do ", "eiusmod tempor" })] + new[] { "Lorem ipsum ", "dolor sit amet, ", "consectetur ", "adipisicing ", "elit, sed do ", "eiusmod tempor" })] [Theory] public void Should_Produce_Wrapped_And_Trimmed_Lines(string text, string[] expectedLines)