diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 09f22612de..69b55b7222 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// 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 12c40e4d59..1cac3243e3 100644
--- a/src/Avalonia.Visuals/Media/FormattedText.cs
+++ b/src/Avalonia.Visuals/Media/FormattedText.cs
@@ -854,19 +854,9 @@ namespace Avalonia.Media
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 @@ namespace Avalonia.Media
{
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 0000000000..78f15b724a
--- /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 88ca596d2d..fb85766003 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -132,6 +132,29 @@ namespace Avalonia.Media.TextFormatting
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 ffd65423a3..0d6ae84493 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 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 1523cc4d9a..0000000000
--- 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 0000000000..6ca93a6116
--- /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 4512890f08..0ff127694b 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
@@ -41,7 +41,7 @@ namespace Avalonia.Media.TextFormatting
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 @@ namespace Avalonia.Media.TextFormatting
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, flowDirection, lineHeight);
- _textTrimming = textTrimming;
+ _textTrimming = textTrimming ?? TextTrimming.None;
_textStyleOverrides = textStyleOverrides;
@@ -641,14 +641,7 @@ namespace Avalonia.Media.TextFormatting
/// 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 0000000000..f0f212b0fe
--- /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 e776655284..49bee6e776 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -106,85 +106,18 @@ namespace Avalonia.Media.TextFormatting
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 4bd46e8c75..90424d8424 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 @@ namespace Avalonia.Media.TextFormatting
///
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 9dffddd207..591df149cb 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 0000000000..66efe96ab9
--- /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 0000000000..6fd0bd2154
--- /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 0000000000..fae001c0c8
--- /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 f8a63dfede..c79ff4cb1a 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 b622971d38..88529ae3a0 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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
+ 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 90c3989238..34a146cf37 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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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 326997328b..7c7fb4783e 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -387,7 +387,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
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 d1a8f175e7..9c0f2d0a9f 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -360,12 +360,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
- [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