Skip to content

Fix issue with OverhangLeading #18438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 18 additions & 43 deletions src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,6 @@ public double WidthIncludingTrailingWhitespace
}
}

/// <summary>
/// Get minimum width of all text lines that can be layouted horizontally without trimming or wrapping.
/// </summary>
internal double MinTextWidth => _metrics.MinTextWidth;

/// <summary>
/// Draws the text layout.
/// </summary>
Expand Down Expand Up @@ -611,8 +606,8 @@ private TextLine[] CreateTextLines()

if (hasOverflowed && _textTrimming != TextTrimming.None)
{
textLine = (TextLineImpl)textLine.Collapse(GetCollapsingProperties(MaxWidth));
}
textLine = (TextLineImpl)textLine.Collapse(GetCollapsingProperties(MaxWidth));
}

textLines.Add(textLine);

Expand Down Expand Up @@ -679,45 +674,30 @@ private TextLine[] CreateTextLines()

private void UpdateMetrics(TextLineImpl currentLine, ref bool first)
{
// 1) Offset each line’s bounding rectangles by the total height so far,
// so we keep an overall bounding box for the entire text block.
var lineTop = _metrics.Height;

// Offset the line's Bounds
var lineBoundsRect = new Rect(
currentLine.Bounds.X,
lineTop + currentLine.Bounds.Y,
currentLine.Bounds.Width,
currentLine.Bounds.Height);

_metrics.Bounds = _metrics.Bounds.Union(lineBoundsRect);

// Offset the line's InkBounds
var lineInkRect = new Rect(
currentLine.InkBounds.X,
lineTop + currentLine.InkBounds.Y,
currentLine.InkBounds.Width,
currentLine.InkBounds.Height);

_metrics.InkBounds = _metrics.InkBounds.Union(lineInkRect);

// 2) Accumulate total layout height by adding the line’s Height.
// 1) Accumulate total layout height by adding the line’s Height.
_metrics.Height += currentLine.Height;

// 3) For the layout’s Width and WidthIncludingTrailingWhitespace,
// 2) For the layout’s Width and WidthIncludingTrailingWhitespace,
// use the maximum of the line widths rather than the bounding box.
_metrics.Width = Math.Max(_metrics.Width, currentLine.Width);
_metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);

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

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

_metrics.OverhangLeading = Math.Max(_metrics.OverhangLeading, currentLine.OverhangLeading);
_metrics.OverhangTrailing = Math.Max(_metrics.OverhangTrailing, currentLine.OverhangTrailing);
_metrics.OverhangAfter = Math.Max(_metrics.OverhangAfter, currentLine.OverhangAfter);
// 5) OverhangAfter is the last line’s OverhangAfter.
_metrics.OverhangAfter = currentLine.OverhangAfter;

// 6) Capture the baseline from the first line.
if (first)
Expand Down Expand Up @@ -768,11 +748,6 @@ private class CachedMetrics
// horizontal bounding box metrics
public double OverhangLeading;
public double OverhangTrailing;

public Rect Bounds;
public Rect InkBounds;

public double MinTextWidth;
}
}
}
5 changes: 3 additions & 2 deletions src/Avalonia.Controls/Presenters/TextPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -627,16 +627,17 @@ protected override Size MeasureOverride(Size availableSize)

InvalidateArrange();

// The textWidth used here is matching that TextBlock uses to measure the text.
var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;

return new Size(textWidth, TextLayout.Height);
}

protected override Size ArrangeOverride(Size finalSize)
{
var finalWidth = finalSize.Width;

var textWidth = Math.Ceiling(TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing);
var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
textWidth = Math.Ceiling(textWidth);

if (finalSize.Width < textWidth)
{
Expand Down
5 changes: 3 additions & 2 deletions src/Avalonia.Controls/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ private protected virtual void RenderCore(DrawingContext context)

protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
TextLayout.Draw(context, origin + new Point(TextLayout.OverhangLeading, 0));
TextLayout.Draw(context, origin);
}

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

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

return size;
}
Expand Down
6 changes: 0 additions & 6 deletions src/Skia/Avalonia.Skia/GlyphRunImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,

currentX += advance;
}

if (runBounds.Left < 0)
{
runBounds = runBounds.Translate(new Vector(-runBounds.Left, 0));
}

ArrayPool<SKRect>.Shared.Return(glyphBounds);

BaselineOrigin = baselineOrigin;
Expand Down
29 changes: 24 additions & 5 deletions src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
}

_glyphOffsets = new GlyphOffset[glyphCount];


var runBounds = new Rect();
var currentX = 0.0;
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
for (var i = 0; i < glyphCount; i++)
{
var (x, y) = glyphInfos[i].GlyphOffset;
Expand All @@ -60,13 +63,29 @@ public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
AdvanceOffset = (float)x,
AscenderOffset = (float)y
};
}

var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
if (_glyphTypefaceImpl.TryGetGlyphMetrics(glyphInfos[i].GlyphIndex, out var metrics))
{
// Found metrics with negative height, prefer to adjust it to positive.
var ybearing = metrics.YBearing;
var height = metrics.Height;
if (height < 0)
{
ybearing += height;
height = -height;
}

// Not entirely sure about why we need to do this, but it seems to work
var xOffset = metrics.XBearing * scale;
var xWidth = xOffset > 0 ? xOffset : 0;
var xBearing = xOffset < 0 ? xOffset : 0;
runBounds = runBounds.Union(new Rect(currentX + xBearing, baselineOrigin.Y + ybearing, xWidth + metrics.Width * scale, height * scale));
}

var height = glyphTypeface.Metrics.LineSpacing * scale;
currentX += glyphInfos[i].GlyphAdvance;
}

Bounds = new Rect(baselineOrigin.X, 0, width, height);
Bounds = runBounds.Translate(new Vector(baselineOrigin.X, 0));
}

public SharpDX.DirectWrite.GlyphRun GlyphRun
Expand Down
2 changes: 1 addition & 1 deletion tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Should_Measure_MinTextWith()

var textLayout = textBlock.TextLayout;

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

Assert.Equal(textBlock.DesiredSize, constraint);
}
Expand Down
Binary file not shown.
Loading
Loading