Browse Source

Improve TextTrimming customization experience (#16521)

* Add some hack

* Make some helper APIs public for easier TextTrimming customization
pull/16568/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
a562c4e4e5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  2. 51
      src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
  3. 58
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  4. 26
      src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs
  5. 26
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

2
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@ -98,7 +98,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>
/// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
/// </returns>
internal bool TryMeasureCharacters(double availableWidth, out int length)
public bool TryMeasureCharacters(double availableWidth, out int length)
{
length = 0;
var currentWidth = 0.0;

51
src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs

@ -27,5 +27,56 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="textLine">Text line to collapse.</param>
public abstract TextRun[]? Collapse(TextLine textLine);
/// <summary>
/// Creates a list of runs for given collapsed length which includes specified symbol at the end.
/// </summary>
/// <param name="textLine">The text line.</param>
/// <param name="collapsedLength">The collapsed length.</param>
/// <param name="flowDirection">The flow direction.</param>
/// <param name="shapedSymbol">The symbol.</param>
/// <returns>List of remaining runs.</returns>
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);
}
}
}
}

58
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<TextRun>();
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);
}
}
}
}

26
src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs

@ -40,5 +40,31 @@
/// <returns>The formatted line.</returns>
public abstract TextLine? FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null);
/// <summary>
/// Creates a shaped symbol.
/// </summary>
/// <param name="textRun">The symbol run to shape.</param>
/// <param name="flowDirection">The flow direction.</param>
/// <returns>
/// The shaped symbol.
/// </returns>
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);
}
}
}

26
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -957,31 +957,5 @@ namespace Avalonia.Media.TextFormatting
return true;
}
}
/// <summary>
/// Creates a shaped symbol.
/// </summary>
/// <param name="textRun">The symbol run to shape.</param>
/// <param name="flowDirection">The flow direction.</param>
/// <returns>
/// The shaped symbol.
/// </returns>
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);
}
}
}

Loading…
Cancel
Save