|
|
|
@ -13,6 +13,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
private readonly TextParagraphProperties _paragraphProperties; |
|
|
|
private readonly TextTrimming _textTrimming; |
|
|
|
private readonly TextLine[] _textLines; |
|
|
|
private readonly CachedMetrics _metrics = new(); |
|
|
|
|
|
|
|
private int _textSourceLength; |
|
|
|
|
|
|
|
@ -151,12 +152,95 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
=> _textLines; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the bounds of the layout.
|
|
|
|
/// The distance from the top of the first line to the bottom of the last line.
|
|
|
|
/// </summary>
|
|
|
|
/// <value>
|
|
|
|
/// The bounds.
|
|
|
|
/// </value>
|
|
|
|
public Rect Bounds { get; private set; } |
|
|
|
public double Height |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.Height; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The distance from the topmost black pixel of the first line
|
|
|
|
/// to the bottommost black pixel of the last line.
|
|
|
|
/// </summary>
|
|
|
|
public double Extent |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.Extent; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The distance from the top of the first line to the baseline of the first line.
|
|
|
|
/// </summary>
|
|
|
|
public double Baseline |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.Baseline; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The distance from the bottom of the last line to the extent bottom.
|
|
|
|
/// </summary>
|
|
|
|
public double OverhangAfter |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.OverhangAfter; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum distance from the leading black pixel to the leading alignment point of a line.
|
|
|
|
/// </summary>
|
|
|
|
public double OverhangLeading |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.OverhangLeading; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum distance from the trailing black pixel to the trailing alignment point of a line.
|
|
|
|
/// </summary>
|
|
|
|
public double OverhangTrailing |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.OverhangTrailing; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum advance width between the leading and trailing alignment points of a line,
|
|
|
|
/// excluding the width of whitespace characters at the end of the line.
|
|
|
|
/// </summary>
|
|
|
|
public double Width |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.Width; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum advance width between the leading and trailing alignment points of a line,
|
|
|
|
/// including the width of whitespace characters at the end of the line.
|
|
|
|
/// </summary>
|
|
|
|
public double WidthIncludingTrailingWhitespace |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
return _metrics.WidthIncludingTrailingWhitespace; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws the text layout.
|
|
|
|
@ -382,7 +466,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; |
|
|
|
|
|
|
|
var isTrailing = lastTrailingIndex == textPosition && characterHit.TrailingLength > 0 || |
|
|
|
y > Bounds.Bottom; |
|
|
|
y > Height; |
|
|
|
|
|
|
|
if (textPosition == textLine.FirstTextSourceIndex + textLine.Length) |
|
|
|
{ |
|
|
|
@ -422,41 +506,25 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
textRunStyle, textWrapping, lineHeight, 0, letterSpacing); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Updates the current bounds.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="textLine">The text line.</param>
|
|
|
|
/// <param name="left">The current left.</param>
|
|
|
|
/// <param name="width">The current width.</param>
|
|
|
|
/// <param name="height">The current height.</param>
|
|
|
|
private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height) |
|
|
|
private TextLine[] CreateTextLines() |
|
|
|
{ |
|
|
|
var lineWidth = textLine.WidthIncludingTrailingWhitespace; |
|
|
|
|
|
|
|
if (width < lineWidth) |
|
|
|
{ |
|
|
|
width = lineWidth; |
|
|
|
} |
|
|
|
var objectPool = FormattingObjectPool.Instance; |
|
|
|
|
|
|
|
var start = textLine.Start; |
|
|
|
var lineStartOfLongestLine = double.MaxValue; |
|
|
|
var origin = new Point(); |
|
|
|
var first = true; |
|
|
|
|
|
|
|
if (left > start) |
|
|
|
{ |
|
|
|
left = start; |
|
|
|
} |
|
|
|
|
|
|
|
height += textLine.Height; |
|
|
|
} |
|
|
|
double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; |
|
|
|
|
|
|
|
private TextLine[] CreateTextLines() |
|
|
|
{ |
|
|
|
var objectPool = FormattingObjectPool.Instance; |
|
|
|
accBlackBoxLeft = accBlackBoxTop = double.MaxValue; |
|
|
|
accBlackBoxRight = accBlackBoxBottom = double.MinValue; |
|
|
|
|
|
|
|
if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) |
|
|
|
{ |
|
|
|
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); |
|
|
|
|
|
|
|
Bounds = new Rect(0, 0, 0, textLine.Height); |
|
|
|
UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, |
|
|
|
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); |
|
|
|
|
|
|
|
return new TextLine[] { textLine }; |
|
|
|
} |
|
|
|
@ -465,8 +533,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
double left = double.PositiveInfinity, width = 0.0, height = 0.0; |
|
|
|
|
|
|
|
_textSourceLength = 0; |
|
|
|
|
|
|
|
TextLine? previousLine = null; |
|
|
|
@ -487,7 +553,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
textLines.Add(emptyTextLine); |
|
|
|
|
|
|
|
UpdateBounds(emptyTextLine, ref left, ref width, ref height); |
|
|
|
UpdateMetrics(emptyTextLine, ref lineStartOfLongestLine, ref origin, ref first, |
|
|
|
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
@ -497,7 +564,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
//Fulfill max height constraint
|
|
|
|
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) |
|
|
|
&& height + textLine.Height > MaxHeight) |
|
|
|
&& Height + textLine.Height > MaxHeight) |
|
|
|
{ |
|
|
|
if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) |
|
|
|
{ |
|
|
|
@ -519,7 +586,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
textLines.Add(textLine); |
|
|
|
|
|
|
|
UpdateBounds(textLine, ref left, ref width, ref height); |
|
|
|
UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, |
|
|
|
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); |
|
|
|
|
|
|
|
previousLine = textLine; |
|
|
|
|
|
|
|
@ -528,7 +596,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
if (textLine.TextLineBreak is { IsSplit: true }) |
|
|
|
{ |
|
|
|
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); |
|
|
|
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(WidthIncludingTrailingWhitespace)); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
@ -546,18 +614,17 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
textLines.Add(textLine); |
|
|
|
|
|
|
|
UpdateBounds(textLine, ref left, ref width, ref height); |
|
|
|
UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, |
|
|
|
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); |
|
|
|
} |
|
|
|
|
|
|
|
Bounds = new Rect(left, 0, width, height); |
|
|
|
|
|
|
|
if (_paragraphProperties.TextAlignment == TextAlignment.Justify) |
|
|
|
{ |
|
|
|
var justificationWidth = MaxWidth; |
|
|
|
|
|
|
|
if (_paragraphProperties.TextWrapping != TextWrapping.NoWrap) |
|
|
|
{ |
|
|
|
justificationWidth = width; |
|
|
|
justificationWidth = WidthIncludingTrailingWhitespace; |
|
|
|
} |
|
|
|
|
|
|
|
if (justificationWidth > 0) |
|
|
|
@ -582,6 +649,46 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
{ |
|
|
|
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; |
|
|
|
|
|
|
|
accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); |
|
|
|
accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight); |
|
|
|
accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); |
|
|
|
accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); |
|
|
|
|
|
|
|
_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); |
|
|
|
|
|
|
|
if (first) |
|
|
|
{ |
|
|
|
_metrics.Baseline = currentLine.Baseline; |
|
|
|
first = false; |
|
|
|
} |
|
|
|
|
|
|
|
origin = origin.WithY(origin.Y + currentLine.Height); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the <see cref="TextCollapsingProperties"/> for current text trimming mode.
|
|
|
|
/// </summary>
|
|
|
|
@ -605,5 +712,24 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
line.Dispose(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private class CachedMetrics |
|
|
|
{ |
|
|
|
// vertical
|
|
|
|
public double Height; |
|
|
|
public double Baseline; |
|
|
|
|
|
|
|
// horizontal
|
|
|
|
public double Width; |
|
|
|
public double WidthIncludingTrailingWhitespace; |
|
|
|
|
|
|
|
// vertical bounding box metrics
|
|
|
|
public double Extent; |
|
|
|
public double OverhangAfter; |
|
|
|
|
|
|
|
// horizontal bounding box metrics
|
|
|
|
public double OverhangLeading; |
|
|
|
public double OverhangTrailing; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|