Skip to content

Commit 5a8ce64

Browse files
xoofxGillibald
authored andcommitted
Fix issue with OverhangLeading (#18438)
* Fix issue with MinTextWidth (Fixes #18372) * Make sure that MeasureOverride for TextPresenter and TextBlock are using same textWidth * Revert #16601 that is introducing an invalid calculation for the OverhangLeading Add tests for OverhangLeading and OverhangTrailing * Revert MinTextWidth * Fix tests to not rely on fixed values * Fix remaining issues * Fix comment in Direct2D1 GlyphRunImpl.cs * Fix Direct2D1 rendering * Fix gold images * Restore TextLineImpl * Update gold image * Restore Math.Max on OverhangLeading and Trailing * Adopt similar behavior to WPF: don't use OverhangLeading/Trailing for measuring and remove clip by default. But it requires further support with NeedsClipBounds * Remove MinTextWidth Keep ClipToBounds=true default for TextBlock * Revert change --------- Co-authored-by: Benedikt Stebner <[email protected]> #Conflicts: # src/Avalonia.Controls/TextBlock.cs # tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
1 parent 3b64ef4 commit 5a8ce64

23 files changed

+233
-98
lines changed

src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,6 @@ public double WidthIncludingTrailingWhitespace
273273
}
274274
}
275275

276-
/// <summary>
277-
/// Get minimum width of all text lines that can be layouted horizontally without trimming or wrapping.
278-
/// </summary>
279-
internal double MinTextWidth => _metrics.MinTextWidth;
280-
281276
/// <summary>
282277
/// Draws the text layout.
283278
/// </summary>
@@ -679,45 +674,30 @@ private TextLine[] CreateTextLines()
679674

680675
private void UpdateMetrics(TextLineImpl currentLine, ref bool first)
681676
{
682-
// 1) Offset each line’s bounding rectangles by the total height so far,
683-
// so we keep an overall bounding box for the entire text block.
684-
var lineTop = _metrics.Height;
685-
686-
// Offset the line's Bounds
687-
var lineBoundsRect = new Rect(
688-
currentLine.Bounds.X,
689-
lineTop + currentLine.Bounds.Y,
690-
currentLine.Bounds.Width,
691-
currentLine.Bounds.Height);
692-
693-
_metrics.Bounds = _metrics.Bounds.Union(lineBoundsRect);
694-
695-
// Offset the line's InkBounds
696-
var lineInkRect = new Rect(
697-
currentLine.InkBounds.X,
698-
lineTop + currentLine.InkBounds.Y,
699-
currentLine.InkBounds.Width,
700-
currentLine.InkBounds.Height);
701-
702-
_metrics.InkBounds = _metrics.InkBounds.Union(lineInkRect);
703-
704-
// 2) Accumulate total layout height by adding the line’s Height.
677+
// 1) Accumulate total layout height by adding the line’s Height.
705678
_metrics.Height += currentLine.Height;
706679

707-
// 3) For the layout’s Width and WidthIncludingTrailingWhitespace,
680+
// 2) For the layout’s Width and WidthIncludingTrailingWhitespace,
708681
// use the maximum of the line widths rather than the bounding box.
709682
_metrics.Width = Math.Max(_metrics.Width, currentLine.Width);
710-
_metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);
711683

712-
// 4) Extent is the max black-pixel extent among lines.
684+
// 3) Extent is the max black-pixel extent among lines.
713685
_metrics.Extent = Math.Max(_metrics.Extent, currentLine.Extent);
714686

715-
// 5) We can track min-text-width or overhangs similarly if needed.
716-
_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.Width);
687+
// 4) TextWidth is the max of the text width among lines.
688+
// We choose to update all related metrics at once (OverhangLeading, WidthIncludingTrailingWhitespace, OverhangTrailing)
689+
// if the current line has a larger text width.
690+
var previousTextWidth = _metrics.OverhangLeading + _metrics.WidthIncludingTrailingWhitespace + _metrics.OverhangTrailing;
691+
var textWidth = currentLine.OverhangLeading + currentLine.WidthIncludingTrailingWhitespace + currentLine.OverhangTrailing;
692+
if (previousTextWidth < textWidth)
693+
{
694+
_metrics.WidthIncludingTrailingWhitespace = currentLine.WidthIncludingTrailingWhitespace;
695+
_metrics.OverhangLeading = currentLine.OverhangLeading;
696+
_metrics.OverhangTrailing = currentLine.OverhangTrailing;
697+
}
717698

718-
_metrics.OverhangLeading = Math.Max(_metrics.OverhangLeading, currentLine.OverhangLeading);
719-
_metrics.OverhangTrailing = Math.Max(_metrics.OverhangTrailing, currentLine.OverhangTrailing);
720-
_metrics.OverhangAfter = Math.Max(_metrics.OverhangAfter, currentLine.OverhangAfter);
699+
// 5) OverhangAfter is the last line’s OverhangAfter.
700+
_metrics.OverhangAfter = currentLine.OverhangAfter;
721701

722702
// 6) Capture the baseline from the first line.
723703
if (first)
@@ -768,11 +748,6 @@ private class CachedMetrics
768748
// horizontal bounding box metrics
769749
public double OverhangLeading;
770750
public double OverhangTrailing;
771-
772-
public Rect Bounds;
773-
public Rect InkBounds;
774-
775-
public double MinTextWidth;
776751
}
777752
}
778753
}

src/Avalonia.Controls/Presenters/TextPresenter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,16 +627,17 @@ protected override Size MeasureOverride(Size availableSize)
627627

628628
InvalidateArrange();
629629

630+
// The textWidth used here is matching that TextBlock uses to measure the text.
630631
var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
631-
632632
return new Size(textWidth, TextLayout.Height);
633633
}
634634

635635
protected override Size ArrangeOverride(Size finalSize)
636636
{
637637
var finalWidth = finalSize.Width;
638638

639-
var textWidth = Math.Ceiling(TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing);
639+
var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
640+
textWidth = Math.Ceiling(textWidth);
640641

641642
if (finalSize.Width < textWidth)
642643
{

src/Avalonia.Controls/TextBlock.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ private protected virtual void RenderCore(DrawingContext context)
628628

629629
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
630630
{
631-
TextLayout.Draw(context, origin + new Point(TextLayout.OverhangLeading, 0));
631+
TextLayout.Draw(context, origin);
632632
}
633633

634634
private bool _clearTextInternal;
@@ -740,7 +740,8 @@ protected override Size MeasureOverride(Size availableSize)
740740
//This implicitly recreated the TextLayout with a new constraint if we previously reset it.
741741
var textLayout = TextLayout;
742742

743-
var size = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.MinTextWidth, textLayout.Height).Inflate(padding), 1, 1);
743+
// The textWidth used here is matching that TextPresenter uses to measure the text.
744+
var size = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.WidthIncludingTrailingWhitespace, textLayout.Height).Inflate(padding), 1, 1);
744745

745746
return size;
746747
}

src/Skia/Avalonia.Skia/GlyphRunImpl.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
7474

7575
currentX += advance;
7676
}
77-
78-
if (runBounds.Left < 0)
79-
{
80-
runBounds = runBounds.Translate(new Vector(-runBounds.Left, 0));
81-
}
82-
8377
ArrayPool<SKRect>.Shared.Return(glyphBounds);
8478

8579
BaselineOrigin = baselineOrigin;

src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
5050
}
5151

5252
_glyphOffsets = new GlyphOffset[glyphCount];
53-
53+
54+
var runBounds = new Rect();
55+
var currentX = 0.0;
56+
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
5457
for (var i = 0; i < glyphCount; i++)
5558
{
5659
var (x, y) = glyphInfos[i].GlyphOffset;
@@ -60,13 +63,29 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
6063
AdvanceOffset = (float)x,
6164
AscenderOffset = (float)y
6265
};
63-
}
6466

65-
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
67+
if (_glyphTypefaceImpl.TryGetGlyphMetrics(glyphInfos[i].GlyphIndex, out var metrics))
68+
{
69+
// Found metrics with negative height, prefer to adjust it to positive.
70+
var ybearing = metrics.YBearing;
71+
var height = metrics.Height;
72+
if (height < 0)
73+
{
74+
ybearing += height;
75+
height = -height;
76+
}
77+
78+
// Not entirely sure about why we need to do this, but it seems to work
79+
var xOffset = metrics.XBearing * scale;
80+
var xWidth = xOffset > 0 ? xOffset : 0;
81+
var xBearing = xOffset < 0 ? xOffset : 0;
82+
runBounds = runBounds.Union(new Rect(currentX + xBearing, baselineOrigin.Y + ybearing, xWidth + metrics.Width * scale, height * scale));
83+
}
6684

67-
var height = glyphTypeface.Metrics.LineSpacing * scale;
85+
currentX += glyphInfos[i].GlyphAdvance;
86+
}
6887

69-
Bounds = new Rect(baselineOrigin.X, 0, width, height);
88+
Bounds = runBounds.Translate(new Vector(baselineOrigin.X, 0));
7089
}
7190

7291
public SharpDX.DirectWrite.GlyphRun GlyphRun

tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void Should_Measure_MinTextWith()
6666

6767
var textLayout = textBlock.TextLayout;
6868

69-
var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.MinTextWidth, textLayout.Height), 1, 1);
69+
var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.Width, textLayout.Height), 1, 1);
7070

7171
Assert.Equal(textBlock.DesiredSize, constraint);
7272
}
Binary file not shown.

0 commit comments

Comments
 (0)