From 6bcf9c2973cb28ded417a07534988960f914b351 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 14 Mar 2022 01:05:53 +0100 Subject: [PATCH 1/7] Refactor text trimming and implement prefix trimming. --- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Visuals/Media/FormattedText.cs | 19 +-- .../Media/TextCollapsingCreateInfo.cs | 16 +++ .../TextFormatting/ShapedTextCharacters.cs | 23 ++++ .../TextCollapsingProperties.cs | 9 +- .../TextFormatting/TextCollapsingStyle.cs | 18 --- .../TextFormatting/TextEllipsisHelper.cs | 99 ++++++++++++++ .../Media/TextFormatting/TextLayout.cs | 13 +- .../TextLeadingPrefixCharacterEllipsis.cs | 126 ++++++++++++++++++ .../Media/TextFormatting/TextLineImpl.cs | 79 +---------- .../TextTrailingCharacterEllipsis.cs | 17 +-- .../TextTrailingWordEllipsis.cs | 15 ++- .../Media/TextLeadingPrefixTrimming.cs | 31 +++++ .../Media/TextNoneTrimming.cs | 18 +++ .../Media/TextTrailingTrimming.cs | 36 +++++ src/Avalonia.Visuals/Media/TextTrimming.cs | 58 +++++++- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 26 ++++ .../AvaloniaXamlIlWellKnownTypes.cs | 6 + .../TextFormatting/TextFormatterTests.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 38 +++--- 20 files changed, 493 insertions(+), 158 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs delete mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs create mode 100644 src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs create mode 100644 src/Avalonia.Visuals/Media/TextNoneTrimming.cs create mode 100644 src/Avalonia.Visuals/Media/TextTrailingTrimming.cs diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 09f22612de8..69b55b72228 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -134,7 +134,7 @@ public class TextBlock : Control /// Defines the property. /// public static readonly StyledProperty TextTrimmingProperty = - AvaloniaProperty.Register(nameof(TextTrimming)); + AvaloniaProperty.Register(nameof(TextTrimming), defaultValue: TextTrimming.None); /// /// Defines the property. diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index 12c40e4d590..1cac3243e3a 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -854,19 +854,9 @@ private TextLine FormatLine(ITextSource textSource, int textSourcePosition, doub var lastRunProps = (GenericTextRunProperties)thatFormatRider.CurrentElement!; - TextCollapsingProperties trailingEllipsis; + TextCollapsingProperties collapsingProperties = _that._trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(maxLineLength, lastRunProps)); - if (_that._trimming == TextTrimming.CharacterEllipsis) - { - trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps); - } - else - { - Debug.Assert(_that._trimming == TextTrimming.WordEllipsis); - trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); - } - - var collapsedLine = line.Collapse(trailingEllipsis); + var collapsedLine = line.Collapse(collapsingProperties); line = collapsedLine; } @@ -1121,11 +1111,6 @@ public TextTrimming Trimming { set { - if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis) - { - throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(TextTrimming)); - } - _trimming = value; _defaultParaProps.SetTextWrapping(_trimming == TextTrimming.None ? diff --git a/src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs b/src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs new file mode 100644 index 00000000000..78f15b724a4 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs @@ -0,0 +1,16 @@ +using Avalonia.Media.TextFormatting; + +namespace Avalonia.Media +{ + public readonly struct TextCollapsingCreateInfo + { + public readonly double Width; + public readonly TextRunProperties TextRunProperties; + + public TextCollapsingCreateInfo(double width, TextRunProperties textRunProperties) + { + Width = width; + TextRunProperties = textRunProperties; + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 88ca596d2d0..fb857660035 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -132,6 +132,29 @@ internal bool TryMeasureCharacters(double availableWidth, out int length) return length > 0; } + internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width) + { + length = 0; + width = 0; + + for (var i = ShapedBuffer.Length - 1; i >= 0; i--) + { + var advance = ShapedBuffer.GlyphAdvances[i]; + + if (width + advance > availableWidth) + { + break; + } + + Codepoint.ReadAt(GlyphRun.Characters, length, out var count); + + length += count; + width += advance; + } + + return length > 0; + } + internal SplitResult Split(int length) { if (IsReversed) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs index ffd65423a30..0d6ae844934 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Media.TextFormatting +using System.Collections.Generic; + +namespace Avalonia.Media.TextFormatting { /// /// Properties of text collapsing @@ -16,8 +18,9 @@ public abstract class TextCollapsingProperties public abstract TextRun Symbol { get; } /// - /// Gets the style of collapsing + /// Collapses given text line. /// - public abstract TextCollapsingStyle Style { get; } + /// Text line to collapse. + public abstract IReadOnlyList? Collapse(TextLine textLine); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs deleted file mode 100644 index 1523cc4d9aa..00000000000 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Avalonia.Media.TextFormatting -{ - /// - /// Text collapsing style - /// - public enum TextCollapsingStyle - { - /// - /// Collapse trailing characters - /// - TrailingCharacter, - - /// - /// Collapse trailing words - /// - TrailingWord, - } -} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs new file mode 100644 index 00000000000..6ca93a61160 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using Avalonia.Media.TextFormatting.Unicode; + +namespace Avalonia.Media.TextFormatting +{ + internal class TextEllipsisHelper + { + public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) + { + var shapedTextRuns = textLine.TextRuns as List; + + if (shapedTextRuns is null) + { + return null; + } + + var runIndex = 0; + var currentWidth = 0.0; + var collapsedLength = 0; + var textRange = textLine.TextRange; + + // TODO: From where this is supposed to come now? + var flowDirection = FlowDirection.LeftToRight; + + var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, flowDirection); + + if (properties.Width < shapedSymbol.GlyphRun.Size.Width) + { + return new List(0); + } + + var availableWidth = properties.Width - shapedSymbol.Size.Width; + + while (runIndex < shapedTextRuns.Count) + { + var currentRun = shapedTextRuns[runIndex]; + + currentWidth += currentRun.Size.Width; + + if (currentWidth > availableWidth) + { + if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength)) + { + if (isWordEllipsis && measuredLength < textRange.End) + { + var currentBreakPosition = 0; + + var lineBreaker = new LineBreakEnumerator(currentRun.Text); + + while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) + { + var nextBreakPosition = lineBreaker.Current.PositionMeasure; + + if (nextBreakPosition == 0) + { + break; + } + + if (nextBreakPosition >= measuredLength) + { + break; + } + + currentBreakPosition = nextBreakPosition; + } + + measuredLength = currentBreakPosition; + } + } + + collapsedLength += measuredLength; + + var shapedTextCharacters = new List(shapedTextRuns.Count); + + if (collapsedLength > 0) + { + var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, collapsedLength); + + shapedTextCharacters.AddRange(splitResult.First); + + TextLineImpl.SortRuns(shapedTextCharacters); + } + + shapedTextCharacters.Add(shapedSymbol); + + return shapedTextCharacters; + } + + availableWidth -= currentRun.Size.Width; + + collapsedLength += currentRun.GlyphRun.Characters.Length; + + runIndex++; + } + + return null; + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 4512890f086..0ff127694b1 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -41,7 +41,7 @@ public TextLayout( IBrush? foreground, TextAlignment textAlignment = TextAlignment.Left, TextWrapping textWrapping = TextWrapping.NoWrap, - TextTrimming textTrimming = TextTrimming.None, + TextTrimming? textTrimming = null, TextDecorationCollection? textDecorations = null, FlowDirection flowDirection = FlowDirection.LeftToRight, double maxWidth = double.PositiveInfinity, @@ -58,7 +58,7 @@ public TextLayout( CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textDecorations, flowDirection, lineHeight); - _textTrimming = textTrimming; + _textTrimming = textTrimming ?? TextTrimming.None; _textStyleOverrides = textStyleOverrides; @@ -641,14 +641,7 @@ private IReadOnlyList CreateTextLines() /// The . private TextCollapsingProperties GetCollapsingProperties(double width) { - return _textTrimming switch - { - TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width, - _paragraphProperties.DefaultTextRunProperties), - TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width, - _paragraphProperties.DefaultTextRunProperties), - _ => throw new ArgumentOutOfRangeException(), - }; + return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs new file mode 100644 index 00000000000..f0f212b0fe8 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using Avalonia.Utilities; + +namespace Avalonia.Media.TextFormatting +{ + /// + /// Ellipsis based on a fixed length leading prefix and suffix growing from the end. + /// + public class TextLeadingPrefixEllipsis : TextCollapsingProperties + { + private readonly int _prefixLength; + + /// + /// Construct a text trailing word ellipsis collapsing properties + /// + /// width in which collapsing is constrained to + /// text run properties of ellispis symbol + public TextLeadingPrefixEllipsis( + ReadOnlySlice ellipsis, + int prefixLength, + double width, + TextRunProperties textRunProperties + ) + { + _prefixLength = prefixLength; + Width = width; + Symbol = new TextCharacters(ellipsis, textRunProperties); + } + + /// + public sealed override double Width { get; } + + /// + public sealed override TextRun Symbol { get; } + + public override IReadOnlyList? Collapse(TextLine textLine) + { + var shapedTextRuns = textLine.TextRuns as List; + + if (shapedTextRuns is null) + { + return null; + } + + var runIndex = 0; + var currentWidth = 0.0; + + // TODO: From where this is supposed to come now? + var flowDirection = FlowDirection.LeftToRight; + + var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, flowDirection); + + if (Width < shapedSymbol.GlyphRun.Size.Width) + { + return new List(0); + } + + var availableWidth = Width - shapedSymbol.Size.Width; + + while (runIndex < shapedTextRuns.Count) + { + var currentRun = shapedTextRuns[runIndex]; + + currentWidth += currentRun.Size.Width; + + if (currentWidth > availableWidth) + { + currentRun.TryMeasureCharacters(availableWidth, out var measuredLength); + + var shapedTextCharacters = new List(shapedTextRuns.Count); + + if (measuredLength > 0) + { + var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength)); + + shapedTextCharacters.AddRange(splitResult.First); + + TextLineImpl.SortRuns(shapedTextCharacters); + + shapedTextCharacters.Add(shapedSymbol); + + if (measuredLength > _prefixLength && splitResult.Second is not null) + { + var availableSuffixWidth = availableWidth; + + foreach (var run in splitResult.First) + { + availableSuffixWidth -= run.Size.Width; + } + + for (int i = splitResult.Second.Count - 1; i >= 0; i--) + { + var run = splitResult.Second[i]; + + if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth)) + { + availableSuffixWidth -= suffixWidth; + + if (suffixCount > 0) + { + var splitSuffix = run.Split(run.TextSourceLength - suffixCount); + + shapedTextCharacters.Add(splitSuffix.Second!); + } + } + } + } + } + else + { + shapedTextCharacters.Add(shapedSymbol); + } + + return shapedTextCharacters; + } + + availableWidth -= currentRun.Size.Width; + + runIndex++; + } + + return null; + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index e7766552844..49bee6e7769 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -106,85 +106,18 @@ public override TextLine Collapse(params TextCollapsingProperties[] collapsingPr var collapsingProperties = collapsingPropertiesList[0]; - var runIndex = 0; - var currentWidth = 0.0; - var textRange = TextRange; - var collapsedLength = 0; + var collapsedRuns = collapsingProperties.Collapse(this); - var shapedSymbol = TextFormatterImpl.CreateSymbol(collapsingProperties.Symbol, _paragraphProperties.FlowDirection); - - if (collapsingProperties.Width < shapedSymbol.GlyphRun.Size.Width) + if (collapsedRuns is List shapedRuns) { - return new TextLineImpl(new List(0), textRange, _paragraphWidth, _paragraphProperties, - _flowDirection, TextLineBreak, true); - } - - var availableWidth = collapsingProperties.Width - shapedSymbol.GlyphRun.Size.Width; - - while (runIndex < _textRuns.Count) - { - var currentRun = _textRuns[runIndex]; - - currentWidth += currentRun.Size.Width; + var collapsedLine = new TextLineImpl(shapedRuns, TextRange, _paragraphWidth, _paragraphProperties, _flowDirection, TextLineBreak, true); - if (currentWidth > availableWidth) + if (shapedRuns.Count > 0) { - if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength)) - { - if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && - measuredLength < textRange.End) - { - var currentBreakPosition = 0; - - var lineBreaker = new LineBreakEnumerator(currentRun.Text); - - while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) - { - var nextBreakPosition = lineBreaker.Current.PositionMeasure; - - if (nextBreakPosition == 0) - { - break; - } - - if (nextBreakPosition >= measuredLength) - { - break; - } - - currentBreakPosition = nextBreakPosition; - } - - measuredLength = currentBreakPosition; - } - } - - collapsedLength += measuredLength; - - var shapedTextCharacters = new List(_textRuns.Count); - - if (collapsedLength > 0) - { - var splitResult = TextFormatterImpl.SplitShapedRuns(_textRuns, collapsedLength); - - shapedTextCharacters.AddRange(splitResult.First); - - SortRuns(shapedTextCharacters); - } - - shapedTextCharacters.Add(shapedSymbol); - - var textLine = new TextLineImpl(shapedTextCharacters, textRange, _paragraphWidth, _paragraphProperties, - _flowDirection, TextLineBreak, true); - - return textLine.FinalizeLine(); + collapsedLine.FinalizeLine(); } - availableWidth -= currentRun.Size.Width; - - collapsedLength += currentRun.GlyphRun.Characters.Length; - - runIndex++; + return collapsedLine; } return this; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 4bd46e8c751..90424d84241 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -1,24 +1,23 @@ -using Avalonia.Utilities; +using System.Collections.Generic; +using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { /// - /// a collapsing properties to collapse whole line toward the end + /// A collapsing properties to collapse whole line toward the end /// at character granularity and with ellipsis being the collapsing symbol /// public class TextTrailingCharacterEllipsis : TextCollapsingProperties { - private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' }); - /// /// Construct a text trailing character ellipsis collapsing properties /// /// width in which collapsing is constrained to /// text run properties of ellispis symbol - public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties) + public TextTrailingCharacterEllipsis(ReadOnlySlice ellipsis, double width, TextRunProperties textRunProperties) { Width = width; - Symbol = new TextCharacters(s_ellipsis, textRunProperties); + Symbol = new TextCharacters(ellipsis, textRunProperties); } /// @@ -27,7 +26,9 @@ public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProp /// public sealed override TextRun Symbol { get; } - /// - public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter; + public override IReadOnlyList? Collapse(TextLine textLine) + { + return TextEllipsisHelper.Collapse(textLine, this, false); + } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs index 9dffddd207d..591df149cbc 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -1,4 +1,5 @@ -using Avalonia.Utilities; +using System.Collections.Generic; +using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { @@ -8,30 +9,30 @@ namespace Avalonia.Media.TextFormatting /// public class TextTrailingWordEllipsis : TextCollapsingProperties { - private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' }); - /// /// Construct a text trailing word ellipsis collapsing properties /// /// width in which collapsing is constrained to /// text run properties of ellispis symbol public TextTrailingWordEllipsis( + ReadOnlySlice ellipsis, double width, TextRunProperties textRunProperties ) { Width = width; - Symbol = new TextCharacters(s_ellipsis, textRunProperties); + Symbol = new TextCharacters(ellipsis, textRunProperties); } - /// public sealed override double Width { get; } /// public sealed override TextRun Symbol { get; } - /// - public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord; + public override IReadOnlyList? Collapse(TextLine textLine) + { + return TextEllipsisHelper.Collapse(textLine, this, true); + } } } diff --git a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs new file mode 100644 index 00000000000..66efe96ab9e --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs @@ -0,0 +1,31 @@ +using Avalonia.Media.TextFormatting; +using Avalonia.Utilities; + +namespace Avalonia.Media +{ + public class TextLeadingPrefixTrimming : TextTrimming + { + private readonly ReadOnlySlice _ellipsis; + private readonly int _prefixLength; + + public TextLeadingPrefixTrimming(char ellipsis, int prefixLength) : this(new[] { ellipsis }, prefixLength) + { + } + + public TextLeadingPrefixTrimming(char[] ellipsis, int prefixLength) + { + _prefixLength = prefixLength; + _ellipsis = new ReadOnlySlice(ellipsis); + } + + public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo) + { + return new TextLeadingPrefixEllipsis(_ellipsis, _prefixLength, createInfo.Width, createInfo.TextRunProperties); + } + + public override string ToString() + { + return nameof(PrefixEllipsis); + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextNoneTrimming.cs b/src/Avalonia.Visuals/Media/TextNoneTrimming.cs new file mode 100644 index 00000000000..6fd0bd2154f --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextNoneTrimming.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Media.TextFormatting; + +namespace Avalonia.Media +{ + internal class TextNoneTrimming : TextTrimming + { + public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo) + { + throw new NotSupportedException(); + } + + public override string ToString() + { + return nameof(None); + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs b/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs new file mode 100644 index 00000000000..fae001c0c87 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs @@ -0,0 +1,36 @@ +using Avalonia.Media.TextFormatting; +using Avalonia.Utilities; + +namespace Avalonia.Media +{ + public class TextTrailingTrimming : TextTrimming + { + private readonly ReadOnlySlice _ellipsis; + private readonly bool _isWordBased; + + public TextTrailingTrimming(char ellipsis, bool isWordBased) : this(new[] {ellipsis}, isWordBased) + { + } + + public TextTrailingTrimming(char[] ellipsis, bool isWordBased) + { + _isWordBased = isWordBased; + _ellipsis = new ReadOnlySlice(ellipsis); + } + + public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo) + { + if (_isWordBased) + { + return new TextTrailingWordEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties); + } + + return new TextTrailingCharacterEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties); + } + + public override string ToString() + { + return _isWordBased ? nameof(WordEllipsis) : nameof(CharacterEllipsis); + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextTrimming.cs b/src/Avalonia.Visuals/Media/TextTrimming.cs index f8a63dfede8..c79ff4cb1a4 100644 --- a/src/Avalonia.Visuals/Media/TextTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextTrimming.cs @@ -1,23 +1,71 @@ -namespace Avalonia.Media +using System; +using Avalonia.Media.TextFormatting; + +namespace Avalonia.Media { /// /// Describes how text is trimmed when it overflows. /// - public enum TextTrimming + public abstract class TextTrimming { + public static char s_defaultEllipsisChar = '\u2026'; + /// /// Text is not trimmed. /// - None, + public static TextTrimming None { get; } = new TextNoneTrimming(); /// /// Text is trimmed at a character boundary. An ellipsis (...) is drawn in place of remaining text. /// - CharacterEllipsis, + public static TextTrimming CharacterEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, false); /// /// Text is trimmed at a word boundary. An ellipsis (...) is drawn in place of remaining text. /// - WordEllipsis + public static TextTrimming WordEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, true); + + /// + /// Text is trimmed after a given prefix length. An ellipsis (...) is drawn in between prefix and suffix and represents remaining text. + /// + public static TextTrimming PrefixEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8); + + /// + /// Creates properties that will be used for collapsing lines of text. + /// + /// Contextual info about text that will be collapsed. + public abstract TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo); + + /// + /// Parses a text trimming string. Names must match static properties defined in this class. + /// + /// The text trimming string. + /// The . + public static TextTrimming Parse(string s) + { + bool Matches(string name) + { + return name.Equals(s, StringComparison.OrdinalIgnoreCase); + } + + if (Matches(nameof(None))) + { + return None; + } + if (Matches(nameof(CharacterEllipsis))) + { + return CharacterEllipsis; + } + else if (Matches(nameof(WordEllipsis))) + { + return WordEllipsis; + } + else if (Matches(nameof(PrefixEllipsis))) + { + return PrefixEllipsis; + } + + throw new FormatException($"Invalid text trimming string: '{s}'."); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index b622971d383..88529ae3a04 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -221,6 +221,32 @@ public static bool TryConvert(AstTransformationContext context, IXamlAstValueNod } } + if (type.Equals(types.TextTrimming)) + { + foreach (var property in types.TextTrimming.Properties) + { + if (property.PropertyType == types.TextTrimming && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase)) + { + result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty()); + + return true; + } + } + } + + if (type.Equals(types.TextDecorationCollection)) + { + foreach (var property in types.TextDecorations.Properties) + { + if (property.PropertyType == types.TextDecorationCollection && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase)) + { + result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty()); + + return true; + } + } + } + result = null; return false; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 90c3989238e..34a146cf373 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -88,6 +88,9 @@ class AvaloniaXamlIlWellKnownTypes public IXamlType ImmutableSolidColorBrush { get; } public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } public IXamlType TypeUtilities { get; } + public IXamlType TextDecorationCollection { get; } + public IXamlType TextDecorations { get; } + public IXamlType TextTrimming { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -193,6 +196,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List { UInt }); TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities"); + TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection"); + TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); + TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 326997328b0..7c7fb4783e3 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -387,7 +387,7 @@ public void Should_Produce_Wrapped_And_Trimmed_Lines(string text, string[] expec if (textLine.Width > 300 || currentHeight + textLine.Height > 240) { - textLine = textLine.Collapse(new TextTrailingWordEllipsis(300, defaultProperties)); + textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice(new[] {TextTrimming.s_defaultEllipsisChar}), 300, defaultProperties)); } currentHeight += textLine.Height; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index d1a8f175e77..9c0f2d0a9fd 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -360,12 +360,29 @@ public void Should_Get_CharacterHit_From_Distance(string text) } } - [InlineData("01234 01234", 58, TextCollapsingStyle.TrailingCharacter, "01234 0\u2026")] - [InlineData("01234 01234", 58, TextCollapsingStyle.TrailingWord, "01234\u2026")] - [InlineData("01234", 9, TextCollapsingStyle.TrailingCharacter, "\u2026")] - [InlineData("01234", 2, TextCollapsingStyle.TrailingCharacter, "")] + public static IEnumerable CollapsingData + { + get + { + yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixEllipsis, "01234 01\u20264 01234"); + yield return CreateData("01234 01234", 58, TextTrimming.CharacterEllipsis, "01234 0\u2026"); + yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026"); + yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026"); + yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, ""); + + object[] CreateData(string text, double width, TextTrimming mode, string expected) + { + return new object[] + { + text, width, mode, expected + }; + } + } + } + + [MemberData(nameof(CollapsingData))] [Theory] - public void Should_Collapse_Line(string text, double width, TextCollapsingStyle style, string expected) + public void Should_Collapse_Line(string text, double width, TextTrimming trimming, string expected) { using (Start()) { @@ -381,16 +398,7 @@ public void Should_Collapse_Line(string text, double width, TextCollapsingStyle Assert.False(textLine.HasCollapsed); - TextCollapsingProperties collapsingProperties; - - if (style == TextCollapsingStyle.TrailingCharacter) - { - collapsingProperties = new TextTrailingCharacterEllipsis(width, defaultProperties); - } - else - { - collapsingProperties = new TextTrailingWordEllipsis(width, defaultProperties); - } + TextCollapsingProperties collapsingProperties = trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, defaultProperties)); var collapsedLine = textLine.Collapse(collapsingProperties); From df7fffc7c935e43115583a62f62ef153bb8120e5 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 00:00:41 +0100 Subject: [PATCH 2/7] Allow for passing flow direction when collapsing lines. --- .../TextCollapsingProperties.cs | 3 +- .../TextFormatting/TextEllipsisHelper.cs | 6 +-- .../TextLeadingPrefixCharacterEllipsis.cs | 48 +++++++++++++------ .../Media/TextFormatting/TextLineImpl.cs | 2 +- .../TextTrailingCharacterEllipsis.cs | 4 +- .../TextTrailingWordEllipsis.cs | 4 +- .../Media/TextLeadingPrefixTrimming.cs | 2 +- src/Avalonia.Visuals/Media/TextTrimming.cs | 6 +-- 8 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs index 0d6ae844934..f766d951d40 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs @@ -21,6 +21,7 @@ public abstract class TextCollapsingProperties /// Collapses given text line. /// /// Text line to collapse. - public abstract IReadOnlyList? Collapse(TextLine textLine); + /// Text flow direction. + public abstract IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs index 6ca93a61160..eccaeba0387 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting { internal class TextEllipsisHelper { - public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) + public static List? Collapse(TextLine textLine, FlowDirection flowDirection, TextCollapsingProperties properties, bool isWordEllipsis) { var shapedTextRuns = textLine.TextRuns as List; @@ -18,10 +18,6 @@ internal class TextEllipsisHelper var currentWidth = 0.0; var collapsedLength = 0; var textRange = textLine.TextRange; - - // TODO: From where this is supposed to come now? - var flowDirection = FlowDirection.LeftToRight; - var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, flowDirection); if (properties.Width < shapedSymbol.GlyphRun.Size.Width) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index f0f212b0fe8..a2810a7d361 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -20,9 +20,13 @@ public TextLeadingPrefixEllipsis( ReadOnlySlice ellipsis, int prefixLength, double width, - TextRunProperties textRunProperties - ) + TextRunProperties textRunProperties) { + if (_prefixLength < 0) + { + throw new ArgumentOutOfRangeException(nameof(prefixLength)); + } + _prefixLength = prefixLength; Width = width; Symbol = new TextCharacters(ellipsis, textRunProperties); @@ -34,7 +38,7 @@ TextRunProperties textRunProperties /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine) + public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) { var shapedTextRuns = textLine.TextRuns as List; @@ -45,10 +49,6 @@ TextRunProperties textRunProperties var runIndex = 0; var currentWidth = 0.0; - - // TODO: From where this is supposed to come now? - var flowDirection = FlowDirection.LeftToRight; - var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, flowDirection); if (Width < shapedSymbol.GlyphRun.Size.Width) @@ -56,6 +56,8 @@ TextRunProperties textRunProperties return new List(0); } + // Overview of ellipsis structure + // Prefix length run | Ellipsis symbol | Post split run growing from the end | var availableWidth = Width - shapedSymbol.Size.Width; while (runIndex < shapedTextRuns.Count) @@ -72,26 +74,42 @@ TextRunProperties textRunProperties if (measuredLength > 0) { - var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength)); + List? preSplitRuns = null; + List? postSplitRuns = null; + + if (_prefixLength > 0) + { + var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength)); - shapedTextCharacters.AddRange(splitResult.First); + shapedTextCharacters.AddRange(splitResult.First); - TextLineImpl.SortRuns(shapedTextCharacters); + TextLineImpl.SortRuns(shapedTextCharacters); + + preSplitRuns = splitResult.First; + postSplitRuns = splitResult.Second; + } + else + { + postSplitRuns = shapedTextRuns; + } shapedTextCharacters.Add(shapedSymbol); - if (measuredLength > _prefixLength && splitResult.Second is not null) + if (measuredLength > _prefixLength && postSplitRuns is not null) { var availableSuffixWidth = availableWidth; - foreach (var run in splitResult.First) + if (preSplitRuns is not null) { - availableSuffixWidth -= run.Size.Width; + foreach (var run in preSplitRuns) + { + availableSuffixWidth -= run.Size.Width; + } } - for (int i = splitResult.Second.Count - 1; i >= 0; i--) + for (int i = postSplitRuns.Count - 1; i >= 0; i--) { - var run = splitResult.Second[i]; + var run = postSplitRuns[i]; if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth)) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 49bee6e7769..09da6db8d43 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -106,7 +106,7 @@ public override TextLine Collapse(params TextCollapsingProperties[] collapsingPr var collapsingProperties = collapsingPropertiesList[0]; - var collapsedRuns = collapsingProperties.Collapse(this); + var collapsedRuns = collapsingProperties.Collapse(this, _paragraphProperties.FlowDirection); if (collapsedRuns is List shapedRuns) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 90424d84241..221ba82ff94 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -26,9 +26,9 @@ public TextTrailingCharacterEllipsis(ReadOnlySlice ellipsis, double width, /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine) + public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) { - return TextEllipsisHelper.Collapse(textLine, this, false); + return TextEllipsisHelper.Collapse(textLine, flowDirection, this, false); } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs index 591df149cbc..24ff871022b 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -30,9 +30,9 @@ TextRunProperties textRunProperties /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine) + public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) { - return TextEllipsisHelper.Collapse(textLine, this, true); + return TextEllipsisHelper.Collapse(textLine, flowDirection, this, true); } } } diff --git a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs index 66efe96ab9e..6cf11edd42b 100644 --- a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs @@ -25,7 +25,7 @@ public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsi public override string ToString() { - return nameof(PrefixEllipsis); + return nameof(PrefixCharacterEllipsis); } } } diff --git a/src/Avalonia.Visuals/Media/TextTrimming.cs b/src/Avalonia.Visuals/Media/TextTrimming.cs index c79ff4cb1a4..3ba52b28f93 100644 --- a/src/Avalonia.Visuals/Media/TextTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextTrimming.cs @@ -28,7 +28,7 @@ public abstract class TextTrimming /// /// Text is trimmed after a given prefix length. An ellipsis (...) is drawn in between prefix and suffix and represents remaining text. /// - public static TextTrimming PrefixEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8); + public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8); /// /// Creates properties that will be used for collapsing lines of text. @@ -60,9 +60,9 @@ bool Matches(string name) { return WordEllipsis; } - else if (Matches(nameof(PrefixEllipsis))) + else if (Matches(nameof(PrefixCharacterEllipsis))) { - return PrefixEllipsis; + return PrefixCharacterEllipsis; } throw new FormatException($"Invalid text trimming string: '{s}'."); From 0e7c9192879b2b22826b0f1b91bcb0e2d193e0ef Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 00:08:23 +0100 Subject: [PATCH 3/7] Documentation fixes. --- .../Media/TextFormatting/TextCollapsingProperties.cs | 6 +++--- .../TextLeadingPrefixCharacterEllipsis.cs | 10 ++++++---- .../TextFormatting/TextTrailingCharacterEllipsis.cs | 7 ++++--- .../Media/TextFormatting/TextTrailingWordEllipsis.cs | 9 +++++---- .../Media/TextLeadingPrefixTrimming.cs | 2 +- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs index f766d951d40..288f9386c21 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs @@ -3,17 +3,17 @@ namespace Avalonia.Media.TextFormatting { /// - /// Properties of text collapsing + /// Properties of text collapsing. /// public abstract class TextCollapsingProperties { /// - /// Gets the width in which the collapsible range is constrained to + /// Gets the width in which the collapsible range is constrained to. /// public abstract double Width { get; } /// - /// Gets the text run that is used as collapsing symbol + /// Gets the text run that is used as collapsing symbol. /// public abstract TextRun Symbol { get; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index a2810a7d361..7f6ab2ef909 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -5,18 +5,20 @@ namespace Avalonia.Media.TextFormatting { /// - /// Ellipsis based on a fixed length leading prefix and suffix growing from the end. + /// Ellipsis based on a fixed length leading prefix and suffix growing from the end at character granularity. /// - public class TextLeadingPrefixEllipsis : TextCollapsingProperties + public class TextLeadingPrefixCharacterEllipsis : TextCollapsingProperties { private readonly int _prefixLength; /// - /// Construct a text trailing word ellipsis collapsing properties + /// Construct a text trailing word ellipsis collapsing properties. /// + /// Text used as collapsing symbol. + /// Length of leading prefix. /// width in which collapsing is constrained to /// text run properties of ellispis symbol - public TextLeadingPrefixEllipsis( + public TextLeadingPrefixCharacterEllipsis( ReadOnlySlice ellipsis, int prefixLength, double width, diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 221ba82ff94..06548e65903 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -5,15 +5,16 @@ namespace Avalonia.Media.TextFormatting { /// /// A collapsing properties to collapse whole line toward the end - /// at character granularity and with ellipsis being the collapsing symbol + /// at character granularity. /// public class TextTrailingCharacterEllipsis : TextCollapsingProperties { /// /// Construct a text trailing character ellipsis collapsing properties /// - /// width in which collapsing is constrained to - /// text run properties of ellispis symbol + /// Text used as collapsing symbol. + /// Width in which collapsing is constrained to. + /// Text run properties of ellispis symbol. public TextTrailingCharacterEllipsis(ReadOnlySlice ellipsis, double width, TextRunProperties textRunProperties) { Width = width; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs index 24ff871022b..bea8bd6be00 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -5,15 +5,16 @@ namespace Avalonia.Media.TextFormatting { /// /// a collapsing properties to collapse whole line toward the end - /// at word granularity and with ellipsis being the collapsing symbol + /// at word granularity. /// public class TextTrailingWordEllipsis : TextCollapsingProperties { /// - /// Construct a text trailing word ellipsis collapsing properties + /// Construct a text trailing word ellipsis collapsing properties. /// - /// width in which collapsing is constrained to - /// text run properties of ellispis symbol + /// Text used as collapsing symbol. + /// width in which collapsing is constrained to. + /// text run properties of ellispis symbol. public TextTrailingWordEllipsis( ReadOnlySlice ellipsis, double width, diff --git a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs index 6cf11edd42b..a8c7c3dda0e 100644 --- a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs @@ -20,7 +20,7 @@ public TextLeadingPrefixTrimming(char[] ellipsis, int prefixLength) public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo) { - return new TextLeadingPrefixEllipsis(_ellipsis, _prefixLength, createInfo.Width, createInfo.TextRunProperties); + return new TextLeadingPrefixCharacterEllipsis(_ellipsis, _prefixLength, createInfo.Width, createInfo.TextRunProperties); } public override string ToString() From 86262340c8b74ee57d62d4efe15f26fe34960298 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 00:11:24 +0100 Subject: [PATCH 4/7] Seal APIs. --- .../Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs | 2 +- .../Media/TextFormatting/TextTrailingWordEllipsis.cs | 2 +- src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs | 2 +- src/Avalonia.Visuals/Media/TextNoneTrimming.cs | 2 +- src/Avalonia.Visuals/Media/TextTrailingTrimming.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 7f6ab2ef909..a771b2a639d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media.TextFormatting /// /// Ellipsis based on a fixed length leading prefix and suffix growing from the end at character granularity. /// - public class TextLeadingPrefixCharacterEllipsis : TextCollapsingProperties + public sealed class TextLeadingPrefixCharacterEllipsis : TextCollapsingProperties { private readonly int _prefixLength; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs index bea8bd6be00..7a98d36510f 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media.TextFormatting /// a collapsing properties to collapse whole line toward the end /// at word granularity. /// - public class TextTrailingWordEllipsis : TextCollapsingProperties + public sealed class TextTrailingWordEllipsis : TextCollapsingProperties { /// /// Construct a text trailing word ellipsis collapsing properties. diff --git a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs index a8c7c3dda0e..19ca1a01988 100644 --- a/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs @@ -3,7 +3,7 @@ namespace Avalonia.Media { - public class TextLeadingPrefixTrimming : TextTrimming + public sealed class TextLeadingPrefixTrimming : TextTrimming { private readonly ReadOnlySlice _ellipsis; private readonly int _prefixLength; diff --git a/src/Avalonia.Visuals/Media/TextNoneTrimming.cs b/src/Avalonia.Visuals/Media/TextNoneTrimming.cs index 6fd0bd2154f..ec238cf17e1 100644 --- a/src/Avalonia.Visuals/Media/TextNoneTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextNoneTrimming.cs @@ -3,7 +3,7 @@ namespace Avalonia.Media { - internal class TextNoneTrimming : TextTrimming + internal sealed class TextNoneTrimming : TextTrimming { public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo) { diff --git a/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs b/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs index fae001c0c87..5bb35f0ba76 100644 --- a/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextTrailingTrimming.cs @@ -3,7 +3,7 @@ namespace Avalonia.Media { - public class TextTrailingTrimming : TextTrimming + public sealed class TextTrailingTrimming : TextTrimming { private readonly ReadOnlySlice _ellipsis; private readonly bool _isWordBased; From b6d2bbf9fb76474a9a9c66b1f41ceabba947debc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 00:13:43 +0100 Subject: [PATCH 5/7] Leftover unsealed API. --- .../Media/TextFormatting/TextTrailingCharacterEllipsis.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 06548e65903..5577fc31f54 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media.TextFormatting /// A collapsing properties to collapse whole line toward the end /// at character granularity. /// - public class TextTrailingCharacterEllipsis : TextCollapsingProperties + public sealed class TextTrailingCharacterEllipsis : TextCollapsingProperties { /// /// Construct a text trailing character ellipsis collapsing properties From 47ad3d7f667eed36d922db5ad9d8227a1b60cc92 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 00:24:29 +0100 Subject: [PATCH 6/7] API compat. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index c02b9d8639c..b9989ad6709 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -52,6 +52,12 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult..ctor()' MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsInside.set(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTrailing.set(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract. +CannotMakeTypeAbstract : Type 'Avalonia.Media.TextTrimming' is abstract in the implementation but is not abstract in the contract. +TypeCannotChangeClassification : Type 'Avalonia.Media.TextTrimming' is a 'class' in the implementation but is a 'struct' in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.CharacterEllipsis' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.None' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.WordEllipsis' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Int32 System.Int32 Avalonia.Media.TextTrimming.value__' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract. @@ -79,6 +85,9 @@ MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextC MembersMustExist : Member 'public Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult Avalonia.Media.TextFormatting.ShapedTextCharacters.Split(System.Int32)' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected System.Boolean Avalonia.Media.TextFormatting.TextCharacters.TryGetRunProperties(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, Avalonia.Media.Typeface, System.Int32)' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine, Avalonia.Media.FlowDirection)' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextCollapsingProperties.Style.get()' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'Avalonia.Media.TextFormatting.TextCollapsingStyle' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextEndOfLine..ctor()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout..ctor(System.String, Avalonia.Media.Typeface, System.Double, Avalonia.Media.IBrush, Avalonia.Media.TextAlignment, Avalonia.Media.TextWrapping, Avalonia.Media.TextTrimming, Avalonia.Media.TextDecorationCollection, System.Double, System.Double, System.Double, System.Int32, System.Collections.Generic.IReadOnlyList>)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. @@ -124,6 +133,12 @@ CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextForma CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment.get()' is abstract in the implementation but is missing in the contract. MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Media.TextFormatting.TextShaper.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. +CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract. +CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingWordEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingWordEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingWordEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract. @@ -161,4 +176,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. -Total Issues: 162 +Total Issues: 177 From 127344898106f3d0e9c201784f9975f85bd0cdf4 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Mar 2022 11:05:09 +0100 Subject: [PATCH 7/7] API changes and test fixes. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 2 +- .../Media/TextFormatting/TextCollapsingProperties.cs | 3 +-- .../Media/TextFormatting/TextEllipsisHelper.cs | 4 ++-- .../TextFormatting/TextLeadingPrefixCharacterEllipsis.cs | 4 ++-- src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs | 2 +- .../Media/TextFormatting/TextTrailingCharacterEllipsis.cs | 4 ++-- .../Media/TextFormatting/TextTrailingWordEllipsis.cs | 4 ++-- src/Avalonia.Visuals/Media/TextTrimming.cs | 5 +++++ .../Media/TextFormatting/TextLineTests.cs | 2 +- 9 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index b9989ad6709..b725993b446 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -85,7 +85,7 @@ MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextC MembersMustExist : Member 'public Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult Avalonia.Media.TextFormatting.ShapedTextCharacters.Split(System.Int32)' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected System.Boolean Avalonia.Media.TextFormatting.TextCharacters.TryGetRunProperties(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, Avalonia.Media.Typeface, System.Int32)' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine, Avalonia.Media.FlowDirection)' is abstract in the implementation but is missing in the contract. +CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine)' is abstract in the implementation but is missing in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextCollapsingProperties.Style.get()' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.TextCollapsingStyle' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextEndOfLine..ctor()' does not exist in the implementation but it does exist in the contract. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs index 288f9386c21..a46f9537d0a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs @@ -21,7 +21,6 @@ public abstract class TextCollapsingProperties /// Collapses given text line. /// /// Text line to collapse. - /// Text flow direction. - public abstract IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection); + public abstract IReadOnlyList? Collapse(TextLine textLine); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs index eccaeba0387..2031c2ec994 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting { internal class TextEllipsisHelper { - public static List? Collapse(TextLine textLine, FlowDirection flowDirection, TextCollapsingProperties properties, bool isWordEllipsis) + public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) { var shapedTextRuns = textLine.TextRuns as List; @@ -18,7 +18,7 @@ internal class TextEllipsisHelper var currentWidth = 0.0; var collapsedLength = 0; var textRange = textLine.TextRange; - var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, flowDirection); + var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight); if (properties.Width < shapedSymbol.GlyphRun.Size.Width) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index a771b2a639d..74c45736307 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -40,7 +40,7 @@ public TextLeadingPrefixCharacterEllipsis( /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) + public override IReadOnlyList? Collapse(TextLine textLine) { var shapedTextRuns = textLine.TextRuns as List; @@ -51,7 +51,7 @@ public TextLeadingPrefixCharacterEllipsis( var runIndex = 0; var currentWidth = 0.0; - var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, flowDirection); + var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight); if (Width < shapedSymbol.GlyphRun.Size.Width) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 09da6db8d43..49bee6e7769 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -106,7 +106,7 @@ public override TextLine Collapse(params TextCollapsingProperties[] collapsingPr var collapsingProperties = collapsingPropertiesList[0]; - var collapsedRuns = collapsingProperties.Collapse(this, _paragraphProperties.FlowDirection); + var collapsedRuns = collapsingProperties.Collapse(this); if (collapsedRuns is List shapedRuns) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 5577fc31f54..83acaa021ea 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -27,9 +27,9 @@ public TextTrailingCharacterEllipsis(ReadOnlySlice ellipsis, double width, /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) + public override IReadOnlyList? Collapse(TextLine textLine) { - return TextEllipsisHelper.Collapse(textLine, flowDirection, this, false); + return TextEllipsisHelper.Collapse(textLine, this, false); } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs index 7a98d36510f..ff2e4cf3250 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -31,9 +31,9 @@ TextRunProperties textRunProperties /// public sealed override TextRun Symbol { get; } - public override IReadOnlyList? Collapse(TextLine textLine, FlowDirection flowDirection) + public override IReadOnlyList? Collapse(TextLine textLine) { - return TextEllipsisHelper.Collapse(textLine, flowDirection, this, true); + return TextEllipsisHelper.Collapse(textLine, this, true); } } } diff --git a/src/Avalonia.Visuals/Media/TextTrimming.cs b/src/Avalonia.Visuals/Media/TextTrimming.cs index 3ba52b28f93..b6f5be496fe 100644 --- a/src/Avalonia.Visuals/Media/TextTrimming.cs +++ b/src/Avalonia.Visuals/Media/TextTrimming.cs @@ -30,6 +30,11 @@ public abstract class TextTrimming /// public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8); + /// + /// Text is trimmed at a character boundary starting from the beginning. An ellipsis (...) is drawn in place of remaining text. + /// + public static TextTrimming LeadingCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 0); + /// /// Creates properties that will be used for collapsing lines of text. /// diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 9c0f2d0a9fd..367e6f4beae 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -364,7 +364,7 @@ public static IEnumerable CollapsingData { get { - yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixEllipsis, "01234 01\u20264 01234"); + yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixCharacterEllipsis, "01234 01\u20264 01234"); yield return CreateData("01234 01234", 58, TextTrimming.CharacterEllipsis, "01234 0\u2026"); yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026"); yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026");