Skip to content

Commit

Permalink
Improve TextTrimming customization experience (#16521)
Browse files Browse the repository at this point in the history
* Add some hack

* Make some helper APIs public for easier TextTrimming customization
  • Loading branch information
Gillibald authored Jul 31, 2024
1 parent 9c5aab9 commit a562c4e
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 78 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ internal void Reverse()
/// <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;
Expand Down
51 changes: 51 additions & 0 deletions src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,56 @@ public abstract class TextCollapsingProperties
/// </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 changes: 7 additions & 51 deletions src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Media.TextFormatting.Unicode;

namespace Avalonia.Media.TextFormatting
{
Expand All @@ -17,12 +16,12 @@ internal static class TextEllipsisHelper
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;
Expand Down Expand Up @@ -72,7 +71,7 @@ internal static class TextEllipsisHelper

collapsedLength += measuredLength;

return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol);
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.LeftToRight, shapedSymbol);
}

availableWidth -= shapedRun.Size.Width;
Expand All @@ -85,7 +84,7 @@ internal static class TextEllipsisHelper
//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;
Expand Down Expand Up @@ -146,7 +145,7 @@ internal static class TextEllipsisHelper

collapsedLength += measuredLength;

return CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol);
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, FlowDirection.RightToLeft, shapedSymbol);
}

availableWidth -= shapedRun.Size.Width;
Expand All @@ -159,7 +158,7 @@ internal static class TextEllipsisHelper
//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;
Expand All @@ -176,48 +175,5 @@ internal static class TextEllipsisHelper

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 changes: 26 additions & 0 deletions src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,31 @@ public static TextFormatter Current
/// <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 changes: 0 additions & 26 deletions src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,31 +957,5 @@ public bool MoveNext()
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);
}
}
}

0 comments on commit a562c4e

Please sign in to comment.