diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 417dfc77fa..1d4793e85d 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -1328,7 +1328,7 @@ namespace Avalonia.Media.TextFormatting
}
}
- var inkBounds = new Rect();
+ Rect? inkBounds = null;
for (var index = 0; index < _textRuns.Length; index++)
{
@@ -1342,7 +1342,14 @@ namespace Avalonia.Media.TextFormatting
var runBounds = glyphRun.InkBounds.Translate(new Vector(widthIncludingWhitespace, offsetY));
- inkBounds = inkBounds.Union(runBounds);
+ if (inkBounds == null)
+ {
+ inkBounds = runBounds;
+ }
+ else
+ {
+ inkBounds = inkBounds.Value.Union(runBounds);
+ }
widthIncludingWhitespace += textRun.Size.Width;
@@ -1354,7 +1361,16 @@ namespace Avalonia.Media.TextFormatting
//Align the bounds at the common baseline
var offsetY = -ascent - drawableTextRun.Baseline;
- inkBounds = inkBounds.Union(new Rect(new Point(widthIncludingWhitespace, offsetY), drawableTextRun.Size));
+ var drawableBounds = new Rect(new Point(widthIncludingWhitespace, offsetY), drawableTextRun.Size);
+
+ if (inkBounds == null)
+ {
+ inkBounds = drawableBounds;
+ }
+ else
+ {
+ inkBounds = inkBounds.Value.Union(drawableBounds);
+ }
widthIncludingWhitespace += drawableTextRun.Size.Width;
@@ -1362,6 +1378,8 @@ namespace Avalonia.Media.TextFormatting
}
}
}
+
+ var finalInkBounds = inkBounds ?? new Rect();
var halfLineGap = lineGap * 0.5;
var naturalHeight = descent - ascent + lineGap;
@@ -1416,18 +1434,15 @@ namespace Avalonia.Media.TextFormatting
}
}
- var extent = inkBounds.Height;
- //The height of overhanging pixels at the bottom
- var overhangAfter = inkBounds.Bottom - height + halfLineGap;
- //The width of overhanging pixels at the natural alignment point. Positive value means we are inside.
- var overhangLeading = inkBounds.Left;
- //The width of overhanging pixels at the end of the natural bounds. Positive value means we are inside.
- var overhangTrailing = widthIncludingWhitespace - inkBounds.Right;
+ var extent = finalInkBounds.Height;
+ var overhangAfter = finalInkBounds.Bottom - height + halfLineGap;
+ var overhangLeading = finalInkBounds.Left;
+ var overhangTrailing = widthIncludingWhitespace - finalInkBounds.Right;
var hasOverflowed = width > _paragraphWidth;
- var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
+ var start = GetParagraphOffsetX(width, widthIncludingWhitespace, overhangTrailing);
- _inkBounds = inkBounds.Translate(new Vector(start, 0));
+ _inkBounds = finalInkBounds.Translate(new Vector(start, 0));
_bounds = new Rect(start, 0, widthIncludingWhitespace, height);
@@ -1453,9 +1468,10 @@ namespace Avalonia.Media.TextFormatting
///
/// The line width.
/// The paragraph width including whitespace.
+ /// The trailing overhang.
/// The paragraph offset.
- private double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace)
+ private double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace, double trailingOverhang)
{
if (double.IsPositiveInfinity(_paragraphWidth))
{
@@ -1501,7 +1517,8 @@ namespace Avalonia.Media.TextFormatting
return Math.Max(0, start);
case TextAlignment.Right:
- return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);
+ var overhangAdjustment = Math.Min(0, trailingOverhang);
+ return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace + overhangAdjustment);
default:
return 0;
}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 7078650da4..8eccb0b219 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -372,6 +372,11 @@ namespace Avalonia.Controls.Presenters
var top = 0d;
var left = 0.0;
+
+ foreach (var line in TextLayout.TextLines)
+ {
+ left = Math.Max(left, -Math.Min(0, line.OverhangLeading));
+ }
var textHeight = TextLayout.Height;
@@ -627,8 +632,16 @@ namespace Avalonia.Controls.Presenters
InvalidateArrange();
- // The textWidth used here is matching that TextBlock uses to measure the text.
- var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
+ var textWidth = TextLayout.WidthIncludingTrailingWhitespace;
+
+ foreach (var line in TextLayout.TextLines)
+ {
+ var lineInkWidth = line.WidthIncludingTrailingWhitespace
+ - Math.Min(0, line.OverhangLeading)
+ - Math.Min(0, line.OverhangTrailing);
+ textWidth = Math.Max(textWidth, lineInkWidth);
+ }
+
return new Size(textWidth, TextLayout.Height);
}
@@ -636,7 +649,15 @@ namespace Avalonia.Controls.Presenters
{
var finalWidth = finalSize.Width;
- var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
+ var textWidth = TextLayout.WidthIncludingTrailingWhitespace;
+
+ foreach (var line in TextLayout.TextLines)
+ {
+ var lineInkWidth = line.WidthIncludingTrailingWhitespace
+ - Math.Min(0, line.OverhangLeading)
+ - Math.Min(0, line.OverhangTrailing);
+ textWidth = Math.Max(textWidth, lineInkWidth);
+ }
textWidth = Math.Ceiling(textWidth);
if (finalSize.Width < textWidth)
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 55cbae773c..22e60efcdc 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -630,7 +630,13 @@ namespace Avalonia.Controls
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
- TextLayout.Draw(context, origin);
+ var maxLeadingOverhang = 0.0;
+ foreach (var line in TextLayout.TextLines)
+ {
+ maxLeadingOverhang = Math.Max(maxLeadingOverhang, -Math.Min(0, line.OverhangLeading));
+ }
+
+ TextLayout.Draw(context, new Point(origin.X + maxLeadingOverhang, origin.Y));
}
private bool _clearTextInternal;
@@ -748,8 +754,17 @@ namespace Avalonia.Controls
//This implicitly recreated the TextLayout with a new constraint if we previously reset it.
var textLayout = TextLayout;
- // The textWidth used here is matching that TextPresenter uses to measure the text.
- return new Size(textLayout.WidthIncludingTrailingWhitespace, textLayout.Height).Inflate(padding);
+ var totalWidth = textLayout.WidthIncludingTrailingWhitespace;
+
+ foreach (var line in textLayout.TextLines)
+ {
+ var lineInkWidth = line.WidthIncludingTrailingWhitespace
+ - Math.Min(0, line.OverhangLeading)
+ - Math.Min(0, line.OverhangTrailing);
+ totalWidth = Math.Max(totalWidth, lineInkWidth);
+ }
+
+ return new Size(totalWidth, textLayout.Height).Inflate(padding);
}
protected override Size ArrangeOverride(Size finalSize)
diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
index 30f6da1dc2..7eee5d9fdc 100644
--- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
+++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
@@ -64,26 +64,36 @@ namespace Avalonia.Skia
// "F" text with Inter size 14 has a 0px left bound with SubpixelAntialias but 1px with Antialias.
using var font = CreateFont(SKFontEdging.SubpixelAntialias);
- var runBounds = new Rect();
var glyphBounds = ArrayPool.Shared.Rent(count);
font.GetGlyphWidths(_glyphIndices, null, glyphBounds.AsSpan(0, count));
currentX = 0;
+
+ Rect? runBounds = null;
for (var i = 0; i < count; i++)
{
var gBounds = glyphBounds[i];
var advance = glyphInfos[i].GlyphAdvance;
- runBounds = runBounds.Union(new Rect(currentX + gBounds.Left, gBounds.Top, gBounds.Width, gBounds.Height));
+ var glyphRect = new Rect(currentX + gBounds.Left, gBounds.Top, gBounds.Width, gBounds.Height);
+
+ if (runBounds == null)
+ {
+ runBounds = glyphRect;
+ }
+ else
+ {
+ runBounds = runBounds.Value.Union(glyphRect);
+ }
currentX += advance;
}
ArrayPool.Shared.Return(glyphBounds);
BaselineOrigin = baselineOrigin;
- Bounds = runBounds.Translate(new Vector(baselineOrigin.X, baselineOrigin.Y));
+ Bounds = (runBounds ?? new Rect()).Translate(new Vector(baselineOrigin.X, baselineOrigin.Y));
}
public double FontRenderingEmSize { get; }