From a562c4e4e5839dfd761d178ebbe41e934c301092 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 31 Jul 2024 10:35:07 +0200 Subject: [PATCH] Improve TextTrimming customization experience (#16521) * Add some hack * Make some helper APIs public for easier TextTrimming customization --- .../Media/TextFormatting/ShapedTextRun.cs | 2 +- .../TextCollapsingProperties.cs | 51 ++++++++++++++++ .../TextFormatting/TextEllipsisHelper.cs | 58 +++---------------- .../Media/TextFormatting/TextFormatter.cs | 26 +++++++++ .../Media/TextFormatting/TextFormatterImpl.cs | 26 --------- 5 files changed, 85 insertions(+), 78 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 5bb8ad5b95..f01de9cc9e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -98,7 +98,7 @@ namespace Avalonia.Media.TextFormatting /// /// true if characters fit into the available width; otherwise, false. /// - internal bool TryMeasureCharacters(double availableWidth, out int length) + public bool TryMeasureCharacters(double availableWidth, out int length) { length = 0; var currentWidth = 0.0; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs index 7cdf81ecc9..e1d9253415 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs @@ -27,5 +27,56 @@ namespace Avalonia.Media.TextFormatting /// /// Text line to collapse. public abstract TextRun[]? Collapse(TextLine textLine); + + /// + /// Creates a list of runs for given collapsed length which includes specified symbol at the end. + /// + /// The text line. + /// The collapsed length. + /// The flow direction. + /// The symbol. + /// List of remaining runs. + public static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, + FlowDirection flowDirection, TextRun shapedSymbol) + { + var textRuns = textLine.TextRuns; + + if (collapsedLength <= 0) + { + return new[] { shapedSymbol }; + } + + if (flowDirection == FlowDirection.RightToLeft) + { + collapsedLength = textLine.Length - collapsedLength; + } + + var objectPool = FormattingObjectPool.Instance; + + var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool); + + try + { + if (flowDirection == FlowDirection.RightToLeft) + { + var collapsedRuns = new TextRun[postSplitRuns!.Count + 1]; + postSplitRuns.CopyTo(collapsedRuns, 1); + collapsedRuns[0] = shapedSymbol; + return collapsedRuns; + } + else + { + var collapsedRuns = new TextRun[preSplitRuns!.Count + 1]; + preSplitRuns.CopyTo(collapsedRuns); + collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; + return collapsedRuns; + } + } + finally + { + objectPool.TextRunLists.Return(ref preSplitRuns); + objectPool.TextRunLists.Return(ref postSplitRuns); + } + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index 8b6d576c6e..fb01afa33d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Media.TextFormatting.Unicode; +using Avalonia.Media.TextFormatting.Unicode; namespace Avalonia.Media.TextFormatting { @@ -17,12 +16,12 @@ namespace Avalonia.Media.TextFormatting var runIndex = 0; var currentWidth = 0.0; var collapsedLength = 0; - var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight); + var shapedSymbol = TextFormatter.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight); if (properties.Width < shapedSymbol.GlyphRun.Bounds.Width) { //Not enough space to fit in the symbol - return Array.Empty(); + return []; } var availableWidth = properties.Width - shapedSymbol.Size.Width; @@ -72,7 +71,7 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol); } availableWidth -= shapedRun.Size.Width; @@ -85,7 +84,7 @@ namespace Avalonia.Media.TextFormatting //The whole run needs to fit into available space if (currentWidth + drawableRun.Size.Width > availableWidth) { - return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol); } availableWidth -= drawableRun.Size.Width; @@ -146,7 +145,7 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol); } availableWidth -= shapedRun.Size.Width; @@ -159,7 +158,7 @@ namespace Avalonia.Media.TextFormatting //The whole run needs to fit into available space if (currentWidth + drawableRun.Size.Width > availableWidth) { - return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol); } availableWidth -= drawableRun.Size.Width; @@ -176,48 +175,5 @@ namespace Avalonia.Media.TextFormatting return null; } - - private static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, - FlowDirection flowDirection, TextRun shapedSymbol) - { - var textRuns = textLine.TextRuns; - - if (collapsedLength <= 0) - { - return new[] { shapedSymbol }; - } - - if(flowDirection == FlowDirection.RightToLeft) - { - collapsedLength = textLine.Length - collapsedLength; - } - - var objectPool = FormattingObjectPool.Instance; - - var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool); - - try - { - if (flowDirection == FlowDirection.RightToLeft) - { - var collapsedRuns = new TextRun[postSplitRuns!.Count + 1]; - postSplitRuns.CopyTo(collapsedRuns, 1); - collapsedRuns[0] = shapedSymbol; - return collapsedRuns; - } - else - { - var collapsedRuns = new TextRun[preSplitRuns!.Count + 1]; - preSplitRuns.CopyTo(collapsedRuns); - collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; - return collapsedRuns; - } - } - finally - { - objectPool.TextRunLists.Return(ref preSplitRuns); - objectPool.TextRunLists.Return(ref postSplitRuns); - } - } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs index ff8c1c4860..1dbf55fb97 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs @@ -40,5 +40,31 @@ /// The formatted line. public abstract TextLine? FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null); + + /// + /// Creates a shaped symbol. + /// + /// The symbol run to shape. + /// The flow direction. + /// + /// The shaped symbol. + /// + public static ShapedTextRun CreateSymbol(TextRun textRun, FlowDirection flowDirection) + { + var textShaper = TextShaper.Current; + + var glyphTypeface = textRun.Properties!.CachedGlyphTypeface; + + var fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; + + var cultureInfo = textRun.Properties.CultureInfo; + + var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures, + fontRenderingEmSize, (sbyte)flowDirection, cultureInfo); + + var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions); + + return new ShapedTextRun(shapedBuffer, textRun.Properties); + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 5aeff0fba2..8e2325fb14 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -957,31 +957,5 @@ namespace Avalonia.Media.TextFormatting return true; } } - - /// - /// Creates a shaped symbol. - /// - /// The symbol run to shape. - /// The flow direction. - /// - /// The shaped symbol. - /// - internal static ShapedTextRun CreateSymbol(TextRun textRun, FlowDirection flowDirection) - { - var textShaper = TextShaper.Current; - - var glyphTypeface = textRun.Properties!.CachedGlyphTypeface; - - var fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; - - var cultureInfo = textRun.Properties.CultureInfo; - - var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures, - fontRenderingEmSize, (sbyte)flowDirection, cultureInfo); - - var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions); - - return new ShapedTextRun(shapedBuffer, textRun.Properties); - } } }