diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 7f0f204886..4205268bc6 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -528,7 +528,7 @@ namespace Avalonia.Media.TextFormatting /// Creates an empty text line. /// /// The empty text line. - public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, TextParagraphProperties paragraphProperties) + public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties) { var flowDirection = paragraphProperties.FlowDirection; var properties = paragraphProperties.DefaultTextRunProperties; @@ -542,7 +542,7 @@ namespace Avalonia.Media.TextFormatting var textRuns = new List { new ShapedTextCharacters(shapedBuffer, properties) }; - return new TextLineImpl(textRuns, firstTextSourceIndex, 0, double.PositiveInfinity, paragraphProperties, flowDirection).FinalizeLine(); + return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine(); } /// @@ -561,7 +561,7 @@ namespace Avalonia.Media.TextFormatting { if(textRuns.Count == 0) { - return CreateEmptyTextLine(firstTextSourceIndex, paragraphProperties); + return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties); } if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength)) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 0df608cb34..5d5d45db2d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -410,7 +410,7 @@ namespace Avalonia.Media.TextFormatting { if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) { - var textLine = TextFormatterImpl.CreateEmptyTextLine(0, _paragraphProperties); + var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); Bounds = new Rect(0,0,0, textLine.Height); @@ -434,7 +434,7 @@ namespace Avalonia.Media.TextFormatting { if(previousLine != null && previousLine.NewLineLength > 0) { - var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, _paragraphProperties); + var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties); textLines.Add(emptyTextLine); @@ -483,7 +483,7 @@ namespace Avalonia.Media.TextFormatting //Make sure the TextLayout always contains at least on empty line if(textLines.Count == 0) { - var textLine = TextFormatterImpl.CreateEmptyTextLine(0, _paragraphProperties); + var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties); textLines.Add(textLine); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 6a704f6f3e..73ec055bbe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -252,7 +252,7 @@ namespace Avalonia.Media.TextFormatting //Look at the left and right edge of the current run if (currentRun.IsLeftToRight) { - if (lastRun == null || lastRun.IsLeftToRight) + if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) { if (characterIndex <= textRun.Text.Start) { @@ -418,11 +418,6 @@ namespace Avalonia.Media.TextFormatting continue; } - if(currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex) - { - continue; - } - TextRun? nextRun = null; if (index + 1 < TextRuns.Count) @@ -460,7 +455,7 @@ namespace Avalonia.Media.TextFormatting } default: { - goto noop; + goto noop; } } @@ -541,6 +536,11 @@ namespace Avalonia.Media.TextFormatting endX += currentRun.Size.Width; } + if (currentPosition < firstTextSourceCharacterIndex) + { + startX += currentRun.Size.Width; + } + currentPosition += currentRun.TextSourceLength; break; @@ -554,24 +554,29 @@ namespace Avalonia.Media.TextFormatting var width = endX - startX; - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + if (!MathUtilities.IsZero(width)) { - currentRect = currentRect.WithWidth(currentRect.Width + width); + if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + { + currentRect = currentRect.WithWidth(currentRect.Width + width); - var textBounds = new TextBounds(currentRect, currentDirection); + var textBounds = new TextBounds(currentRect, currentDirection); - result[result.Count - 1] = textBounds; - } - else - { - currentRect = new Rect(startX, 0, width, Height); + result[result.Count - 1] = textBounds; + } + else + { - result.Add(new TextBounds(currentRect, currentDirection)); + currentRect = new Rect(startX, 0, width, Height); + + result.Add(new TextBounds(currentRect, currentDirection)); + + } } if (currentDirection == FlowDirection.LeftToRight) { - if (currentPosition >= firstTextSourceCharacterIndex + textLength) + if (currentPosition > firstTextSourceCharacterIndex + textLength) { break; } @@ -595,10 +600,10 @@ namespace Avalonia.Media.TextFormatting public TextLineImpl FinalizeLine() { - BidiReorder(); - _textLineMetrics = CreateLineMetrics(); + BidiReorder(); + return this; } @@ -1026,7 +1031,7 @@ namespace Avalonia.Media.TextFormatting var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; - + var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; @@ -1036,8 +1041,8 @@ namespace Avalonia.Media.TextFormatting var lineGap = glyphTypeface.LineGap * scale; var height = descent - ascent + lineGap; - - var lineHeight = _paragraphProperties.LineHeight; + + var lineHeight = _paragraphProperties.LineHeight; for (var index = 0; index < _textRuns.Count; index++) { @@ -1073,41 +1078,11 @@ namespace Avalonia.Media.TextFormatting } } - switch (_paragraphProperties.FlowDirection) + if (index == _textRuns.Count - 1) { - case FlowDirection.LeftToRight: - { - if (index == _textRuns.Count - 1) - { - width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width; - trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength; - newLineLength = textRun.GlyphRun.Metrics.NewlineLength; - } - - break; - } - - case FlowDirection.RightToLeft: - { - if (index == _textRuns.Count - 1) - { - var firstRun = _textRuns[0]; - - if (firstRun is ShapedTextCharacters shapedTextCharacters) - { - var offset = shapedTextCharacters.GlyphRun.Metrics.WidthIncludingTrailingWhitespace - - shapedTextCharacters.GlyphRun.Metrics.Width; - - width = widthIncludingWhitespace + - textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace - offset; - - trailingWhitespaceLength = shapedTextCharacters.GlyphRun.Metrics.TrailingWhitespaceLength; - newLineLength = shapedTextCharacters.GlyphRun.Metrics.NewlineLength; - } - } - - break; - } + width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width; + trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength; + newLineLength = textRun.GlyphRun.Metrics.NewlineLength; } widthIncludingWhitespace += textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace; @@ -1166,10 +1141,10 @@ namespace Avalonia.Media.TextFormatting if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { - if(lineHeight > height) + if (lineHeight > height) { height = lineHeight; - } + } } return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start, diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index db1bbdbc6c..0785149a73 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -511,28 +511,39 @@ namespace Avalonia.Controls.Presenters InvalidateMeasure(); } - + + protected override Size MeasureOverride(Size availableSize) { + if (string.IsNullOrEmpty(Text)) + { + return new Size(); + } + _constraint = availableSize; - + _textLayout = null; - + InvalidateArrange(); - var measuredSize = PixelSize.FromSize(TextLayout.Bounds.Size, 1); - - return new Size(measuredSize.Width, measuredSize.Height); + var measuredSize = TextLayout.Bounds.Size; + + return measuredSize; } protected override Size ArrangeOverride(Size finalSize) { + if (finalSize.Width < TextLayout.Bounds.Width) + { + finalSize = finalSize.WithWidth(TextLayout.Bounds.Width); + } + if (MathUtilities.AreClose(_constraint.Width, finalSize.Width)) { return finalSize; } - _constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height)); + _constraint = new Size(finalSize.Width, double.PositiveInfinity); _textLayout = null; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index c04a62008b..bbe6aeb7ee 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -604,7 +604,9 @@ namespace Avalonia.Controls return new Size(); } - var padding = Padding; + var scale = LayoutHelper.GetLayoutScale(this); + + var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); _constraint = availableSize.Deflate(padding); @@ -612,23 +614,24 @@ namespace Avalonia.Controls InvalidateArrange(); - var measuredSize = PixelSize.FromSize(TextLayout.Bounds.Size, 1); + var measuredSize = TextLayout.Bounds.Size.Inflate(padding); - return new Size(measuredSize.Width, measuredSize.Height).Inflate(padding); + return measuredSize; } protected override Size ArrangeOverride(Size finalSize) { + if(finalSize.Width < TextLayout.Bounds.Width) + { + finalSize = finalSize.WithWidth(TextLayout.Bounds.Width); + } + if (MathUtilities.AreClose(_constraint.Width, finalSize.Width)) { return finalSize; } - var padding = Padding; - - var textSize = finalSize.Deflate(padding); - - _constraint = new Size(textSize.Width, Math.Ceiling(textSize.Height)); + _constraint = new Size(finalSize.Width, double.PositiveInfinity); _textLayout = null; diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index ebaa247da8..908b0ffa47 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -27,7 +27,7 @@ namespace Avalonia.Skia buffer.GuessSegmentProperties(); - buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; + buffer.Direction = Direction.LeftToRight; //Always shape LeftToRight buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); @@ -35,11 +35,6 @@ namespace Avalonia.Skia font.Shape(buffer); - if (buffer.Direction == Direction.RightToLeft) - { - buffer.Reverse(); - } - font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index 59027a663f..f4e4b00147 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; @@ -11,8 +10,7 @@ using GlyphInfo = HarfBuzzSharp.GlyphInfo; namespace Avalonia.Direct2D1.Media { - -internal class TextShaperImpl : ITextShaperImpl + internal class TextShaperImpl : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlySlice text, TextShaperOptions options) { @@ -23,25 +21,20 @@ internal class TextShaperImpl : ITextShaperImpl using (var buffer = new Buffer()) { - buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length); + buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length); MergeBreakPair(buffer); - + buffer.GuessSegmentProperties(); - buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; + buffer.Direction = Direction.LeftToRight; //Always shape LeftToRight - buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); + buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; font.Shape(buffer); - if (buffer.Direction == Direction.RightToLeft) - { - buffer.Reverse(); - } - font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; @@ -60,13 +53,13 @@ internal class TextShaperImpl : ITextShaperImpl var glyphIndex = (ushort)sourceInfo.Codepoint; - var glyphCluster = (int)sourceInfo.Cluster; + var glyphCluster = (int)(sourceInfo.Cluster); var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - if (glyphIndex == 0 && text[glyphCluster] == '\t') + if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); @@ -75,9 +68,7 @@ internal class TextShaperImpl : ITextShaperImpl 4 * typeface.GetGlyphAdvance(glyphIndex) * textScale; } - var targetInfo = - new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, - glyphOffset); + var targetInfo = new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset); shapedBuffer[i] = targetInfo; } @@ -91,7 +82,7 @@ internal class TextShaperImpl : ITextShaperImpl var length = buffer.Length; var glyphInfos = buffer.GetGlyphInfoSpan(); - + var second = glyphInfos[length - 1]; if (!new Codepoint((int)second.Codepoint).IsBreakChar) @@ -102,7 +93,7 @@ internal class TextShaperImpl : ITextShaperImpl if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n') { var first = glyphInfos[length - 2]; - + first.Codepoint = '\u200C'; second.Codepoint = '\u200C'; second.Cluster = first.Cluster; @@ -113,7 +104,7 @@ internal class TextShaperImpl : ITextShaperImpl { *p = first; } - + fixed (GlyphInfo* p = &glyphInfos[length - 1]) { *p = second; @@ -148,7 +139,7 @@ internal class TextShaperImpl : ITextShaperImpl private static double GetGlyphAdvance(ReadOnlySpan glyphPositions, int index, double textScale) { // Depends on direction of layout - // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale; + // glyphPositions[index].YAdvance * textScale; return glyphPositions[index].XAdvance * textScale; } } diff --git a/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png b/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png index edd4dfd263..e8624fa457 100644 Binary files a/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png and b/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png index a76c6a5b2a..7bd622050e 100644 Binary files a/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png and b/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png differ