Skip to content
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

Fix TextAlignment="DetectFromContent" #17721

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
74 changes: 28 additions & 46 deletions src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ 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 @@ -544,21 +549,13 @@ private TextLine[] CreateTextLines()
{
var objectPool = FormattingObjectPool.Instance;

var lineStartOfLongestLine = double.MaxValue;
var origin = new Point();
var first = true;

double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom;

accBlackBoxLeft = accBlackBoxTop = double.MaxValue;
accBlackBoxRight = accBlackBoxBottom = double.MinValue;

if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);

UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
UpdateMetrics(textLine, ref first);

return new TextLine[] { textLine };
}
Expand All @@ -576,7 +573,7 @@ private TextLine[] CreateTextLines()
while (true)
{
var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth,
_paragraphProperties, previousLine?.TextLineBreak);
_paragraphProperties, previousLine?.TextLineBreak) as TextLineImpl;

if (textLine is null)
{
Expand All @@ -587,8 +584,7 @@ private TextLine[] CreateTextLines()

textLines.Add(emptyTextLine);

UpdateMetrics(emptyTextLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
UpdateMetrics(emptyTextLine, ref first);
}

break;
Expand All @@ -615,13 +611,12 @@ private TextLine[] CreateTextLines()

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

textLines.Add(textLine);

UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
UpdateMetrics(textLine, ref first);

previousLine = textLine;

Expand All @@ -648,8 +643,7 @@ private TextLine[] CreateTextLines()

textLines.Add(textLine);

UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
UpdateMetrics(textLine, ref first);
}

if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
Expand Down Expand Up @@ -683,44 +677,27 @@ private TextLine[] CreateTextLines()
}
}

private void UpdateMetrics(
TextLine currentLine,
ref double lineStartOfLongestLine,
ref Point origin,
ref bool first,
ref double accBlackBoxLeft,
ref double accBlackBoxTop,
ref double accBlackBoxRight,
ref double accBlackBoxBottom)
private void UpdateMetrics(TextLineImpl currentLine, ref bool first)
{
var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;
var blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing;
var blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter;
var blackBoxTop = blackBoxBottom - currentLine.Extent;
_metrics.InkBounds = _metrics.InkBounds.Union(new Rect(new Point(0, _metrics.Bounds.Bottom) + currentLine.InkBounds.Position, currentLine.InkBounds.Size));
_metrics.Bounds = _metrics.Bounds.Union(new Rect(new Point(0, _metrics.Bounds.Bottom) + currentLine.Bounds.Position, currentLine.Bounds.Size));

accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft);
accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight);
accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom);
accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop);
_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.Bounds.Width);
_metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.InkBounds.Width);

_metrics.OverhangAfter = currentLine.OverhangAfter;

_metrics.Height += currentLine.Height;
_metrics.Width = Math.Max(_metrics.Width, currentLine.Width);
_metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);
lineStartOfLongestLine = Math.Min(lineStartOfLongestLine, currentLine.Start);

_metrics.Extent = accBlackBoxBottom - accBlackBoxTop;
_metrics.OverhangLeading = accBlackBoxLeft - lineStartOfLongestLine;
_metrics.OverhangTrailing = _metrics.Width - (accBlackBoxRight - lineStartOfLongestLine);
_metrics.Height = _metrics.Bounds.Height;
_metrics.Width = _metrics.InkBounds.Width;
_metrics.WidthIncludingTrailingWhitespace = _metrics.Bounds.Width;
_metrics.Extent = _metrics.InkBounds.Height;
_metrics.OverhangLeading = Math.Max(0, _metrics.Bounds.Left - _metrics.InkBounds.Left);
_metrics.OverhangTrailing = Math.Max(0, _metrics.InkBounds.Right - _metrics.Bounds.Right);
_metrics.OverhangAfter = Math.Max(0, _metrics.InkBounds.Bottom - _metrics.Bounds.Bottom);

if (first)
{
_metrics.Baseline = currentLine.Baseline;
first = false;
}

origin = origin.WithY(origin.Y + currentLine.Height);
}

/// <summary>
Expand Down Expand Up @@ -764,6 +741,11 @@ private class CachedMetrics
// horizontal bounding box metrics
public double OverhangLeading;
public double OverhangTrailing;

public Rect Bounds;
public Rect InkBounds;

public double MinTextWidth;
}
}
}
19 changes: 18 additions & 1 deletion src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ internal class TextLineImpl : TextLine
private TextLineBreak? _textLineBreak;
private readonly FlowDirection _resolvedFlowDirection;

private Rect _inkBounds;
private Rect _bounds;

public TextLineImpl(TextRun[] textRuns, int firstTextSourceIndex, int length, double paragraphWidth,
TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight,
TextLineBreak? lineBreak = null, bool hasCollapsed = false)
Expand Down Expand Up @@ -85,10 +88,20 @@ public TextLineImpl(TextRun[] textRuns, int firstTextSourceIndex, int length, do
/// <inheritdoc/>
public override double WidthIncludingTrailingWhitespace => _textLineMetrics.WidthIncludingTrailingWhitespace;

/// <summary>
/// Get the logical text bounds.
/// </summary>
internal Rect Bounds => _bounds;

/// <summary>
/// Get the bounding box that is covered with black pixels.
/// </summary>
internal Rect InkBounds => _inkBounds;

/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point lineOrigin)
{
var (currentX, currentY) = lineOrigin + new Point(Start, 0);
var (currentX, currentY) = lineOrigin + new Point(Start, 0);

foreach (var textRun in _textRuns)
{
Expand Down Expand Up @@ -1377,6 +1390,10 @@ private TextLineMetrics CreateLineMetrics()

var start = GetParagraphOffsetX(width, widthIncludingWhitespace);

_inkBounds = new Rect(bounds.Position + new Point(start, 0), bounds.Size);

_bounds = new Rect(start, 0, widthIncludingWhitespace, height);

return new TextLineMetrics
{
HasOverflowed = hasOverflowed,
Expand Down
4 changes: 1 addition & 3 deletions src/Avalonia.Controls/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,9 +740,7 @@ protected override Size MeasureOverride(Size availableSize)
//This implicitly recreated the TextLayout with a new constraint if we previously reset it.
var textLayout = TextLayout;

var width = textLayout.OverhangLeading + textLayout.WidthIncludingTrailingWhitespace + textLayout.OverhangTrailing;

var size = LayoutHelper.RoundLayoutSizeUp(new Size(width, textLayout.Height).Inflate(padding), 1, 1);
var size = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.MinTextWidth, textLayout.Height).Inflate(padding), 1, 1);

return size;
}
Expand Down
26 changes: 25 additions & 1 deletion tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
using static System.Net.Mime.MediaTypeNames;

namespace Avalonia.Controls.UnitTests
{
Expand Down Expand Up @@ -48,6 +49,29 @@ public void Calling_Measure_Should_Update_TextLayout()
}
}

[Fact]
public void Should_Measure_MinTextWith()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var textBlock = new TextBlock
{
Text = "Hello&#10;שלום&#10;Really really really really long line",
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.DetectFromContent,
TextWrapping = TextWrapping.Wrap
};

textBlock.Measure(new Size(1920, 1080));

var textLayout = textBlock.TextLayout;

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

Assert.Equal(textBlock.DesiredSize, constraint);
}
}

[Fact]
public void Calling_Arrange_With_Different_Size_Should_Update_Constraint_And_TextLayout()
{
Expand Down
Loading