diff --git a/src/Avalonia.Base/Media/TextAlignment.cs b/src/Avalonia.Base/Media/TextAlignment.cs index fdcaea2d46..94416ccde2 100644 --- a/src/Avalonia.Base/Media/TextAlignment.cs +++ b/src/Avalonia.Base/Media/TextAlignment.cs @@ -21,7 +21,25 @@ namespace Avalonia.Media Right, /// - /// The text is layed out so each line is stretched to an equal width. + /// The beginning of the text is aligned to the edge of the available space. + /// + Start, + + /// + /// The end of the text is aligned to the edge of the available space. + /// + End, + + /// + /// Text alignment is inferred from the text content. + /// + /// + /// When the TextAlignment property is set to DetectFromContent, alignment is inferred from the text content of the control. For example, English text is left aligned, and Arabic text is right aligned. + /// + DetectFromContent, + + /// + /// Text is justified within the available space. /// Justify } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index f6c9c85889..5f9c230027 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Media.TextFormatting TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null) { var textWrapping = paragraphProperties.TextWrapping; - FlowDirection flowDirection; + FlowDirection resolvedFlowDirection; TextLineBreak? nextLineBreak = null; List drawableTextRuns; @@ -24,17 +24,17 @@ namespace Avalonia.Media.TextFormatting if (previousLineBreak?.RemainingRuns != null) { - flowDirection = previousLineBreak.FlowDirection; + resolvedFlowDirection = previousLineBreak.FlowDirection; drawableTextRuns = previousLineBreak.RemainingRuns.ToList(); nextLineBreak = previousLineBreak; } else { - drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out flowDirection); + drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection); if (nextLineBreak == null && textEndOfLine != null) { - nextLineBreak = new TextLineBreak(textEndOfLine, flowDirection); + nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); } } @@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.NoWrap: { textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength, - paragraphWidth, paragraphProperties, flowDirection, nextLineBreak); + paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); @@ -55,7 +55,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.Wrap: { textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, - flowDirection, nextLineBreak); + resolvedFlowDirection, nextLineBreak); break; } default: @@ -404,10 +404,6 @@ namespace Avalonia.Media.TextFormatting { endOfLine = textEndOfLine; - textRuns.Add(textRun); - - textSourceLength += textRun.TextSourceLength; - break; } @@ -552,11 +548,11 @@ namespace Avalonia.Media.TextFormatting /// The first text source index. /// The paragraph width. /// The text paragraph properties. - /// + /// /// The current line break if the line was explicitly broken. /// The wrapped text line. private static TextLineImpl PerformTextWrapping(List textRuns, int firstTextSourceIndex, - double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection, + double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak) { if(textRuns.Count == 0) @@ -684,16 +680,16 @@ namespace Avalonia.Media.TextFormatting var remainingCharacters = splitResult.Second; var lineBreak = remainingCharacters?.Count > 0 ? - new TextLineBreak(currentLineBreak?.TextEndOfLine, flowDirection, remainingCharacters) : + new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) : null; if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) { - lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, flowDirection); + lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); } var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength, - paragraphWidth, paragraphProperties, flowDirection, + paragraphWidth, paragraphProperties, resolvedFlowDirection, lineBreak); return textLine.FinalizeLine(); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index afd0516f56..88a8d9d985 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -15,7 +15,7 @@ namespace Avalonia.Media.TextFormatting /// The contained text runs. /// public abstract IReadOnlyList TextRuns { get; } - + public abstract int FirstTextSourceIndex { get; } public abstract int Length { get; } @@ -75,7 +75,7 @@ namespace Avalonia.Media.TextFormatting /// The number of newline characters. /// public abstract int NewLineLength { get; } - + /// /// Gets the distance that black pixels extend beyond the bottom alignment edge of a line. /// @@ -192,40 +192,5 @@ namespace Avalonia.Media.TextFormatting /// number of characters of the specified range /// an array of bounding rectangles. public abstract IReadOnlyList GetTextBounds(int firstTextSourceCharacterIndex, int textLength); - - /// - /// Gets the text line offset x. - /// - /// The line width. - /// The paragraph width including whitespace. - /// The paragraph width. - /// The text alignment. - /// The flow direction of the line. - /// The paragraph offset. - internal static double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace, - double paragraphWidth, TextAlignment textAlignment, FlowDirection flowDirection) - { - if (double.IsPositiveInfinity(paragraphWidth)) - { - return 0; - } - - if (flowDirection == FlowDirection.LeftToRight) - { - switch (textAlignment) - { - case TextAlignment.Center: - return Math.Max(0, (paragraphWidth - width) / 2); - - case TextAlignment.Right: - return flowDirection == FlowDirection.LeftToRight ? Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace) : 0; - - case TextAlignment.Left: - return flowDirection == FlowDirection.LeftToRight ? 0 : Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace); - } - } - - return 0; - } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index ff9d2cc868..ce7edaa5da 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -11,10 +11,10 @@ namespace Avalonia.Media.TextFormatting private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; - private readonly FlowDirection _flowDirection; + private readonly FlowDirection _resolvedFlowDirection; public TextLineImpl(List textRuns, int firstTextSourceIndex, int length, double paragraphWidth, - TextParagraphProperties paragraphProperties, FlowDirection flowDirection = FlowDirection.LeftToRight, + TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { FirstTextSourceIndex = firstTextSourceIndex; @@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting _paragraphWidth = paragraphWidth; _paragraphProperties = paragraphProperties; - _flowDirection = flowDirection; + _resolvedFlowDirection = resolvedFlowDirection; } /// @@ -137,7 +137,7 @@ namespace Avalonia.Media.TextFormatting } var collapsedLine = new TextLineImpl(collapsedRuns, FirstTextSourceIndex, Length, _paragraphWidth, _paragraphProperties, - _flowDirection, TextLineBreak, true); + _resolvedFlowDirection, TextLineBreak, true); if (collapsedRuns.Count > 0) { @@ -168,7 +168,7 @@ namespace Avalonia.Media.TextFormatting return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _); } - return _flowDirection == FlowDirection.LeftToRight ? + return _resolvedFlowDirection == FlowDirection.LeftToRight ? new CharacterHit(FirstTextSourceIndex) : new CharacterHit(FirstTextSourceIndex + Length); } @@ -261,7 +261,7 @@ namespace Avalonia.Media.TextFormatting //Look at the left and right edge of the current run if (currentRun.IsLeftToRight) { - if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) + if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) { if (characterIndex <= currentPosition) { @@ -846,7 +846,7 @@ namespace Avalonia.Media.TextFormatting // Build up the collection of ordered runs. var run = _textRuns[0]; - OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _flowDirection)); + OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _resolvedFlowDirection)); var current = orderedRun; @@ -854,7 +854,7 @@ namespace Avalonia.Media.TextFormatting { run = _textRuns[i]; - current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _flowDirection)); + current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _resolvedFlowDirection)); current = current.Next; } @@ -873,7 +873,7 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[i]; - var level = GetRunBidiLevel(currentRun, _flowDirection); + var level = GetRunBidiLevel(currentRun, _resolvedFlowDirection); if (level > max) { @@ -1353,8 +1353,7 @@ namespace Avalonia.Media.TextFormatting } } - var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth, - _paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection); + var start = GetParagraphOffsetX(width, widthIncludingWhitespace); if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { @@ -1368,6 +1367,55 @@ namespace Avalonia.Media.TextFormatting -ascent, trailingWhitespaceLength, width, widthIncludingWhitespace); } + /// + /// Gets the text line offset x. + /// + /// The line width. + /// The paragraph width including whitespace. + + /// The paragraph offset. + private double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace) + { + if (double.IsPositiveInfinity(_paragraphWidth)) + { + return 0; + } + + var textAlignment = _paragraphProperties.TextAlignment; + var paragraphFlowDirection = _paragraphProperties.FlowDirection; + + switch (textAlignment) + { + case TextAlignment.Start: + { + textAlignment = paragraphFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right; + break; + } + case TextAlignment.End: + { + textAlignment = paragraphFlowDirection == FlowDirection.RightToLeft ? TextAlignment.Left : TextAlignment.Right; + break; + } + case TextAlignment.DetectFromContent: + { + textAlignment = _resolvedFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right; + break; + } + } + + switch (textAlignment) + { + case TextAlignment.Center: + return Math.Max(0, (_paragraphWidth - width) / 2); + + case TextAlignment.Right: + return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); + + default: + return 0; + } + } + private sealed class OrderedBidiRun { public OrderedBidiRun(DrawableTextRun run, sbyte level)