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