diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index e82d5b7ba5..2dabb29e76 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -132,7 +132,7 @@ namespace Avalonia.Media
{
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
- var glyphTypeface = typeface.GlyphTypeface;
+ var glyphTypeface = GetOrAddGlyphTypeface(typeface);
if(glyphTypeface.TryGetGlyph((uint)codepoint, out _)){
return true;
diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
index d444a58297..ac196bf7e0 100644
--- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Media.TextFormatting
{
ShapedBuffer = shapedBuffer;
Properties = properties;
- TextMetrics = new TextMetrics(properties.Typeface.GlyphTypeface, properties.FontRenderingEmSize);
+ TextMetrics = new TextMetrics(properties.CachedGlyphTypeface, properties.FontRenderingEmSize);
}
public bool IsReversed { get; private set; }
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
index 3ccfb40c4a..94db739d4d 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
@@ -69,6 +69,7 @@ namespace Avalonia.Media.TextFormatting
/// The characters to create text runs from.
/// The default text run properties.
/// The bidi level of the run.
+ /// The font manager to use.
///
/// A list of shapeable text runs.
private static UnshapedTextRun CreateShapeableRun(ReadOnlyMemory text,
@@ -76,31 +77,32 @@ namespace Avalonia.Media.TextFormatting
ref TextRunProperties? previousProperties)
{
var defaultTypeface = defaultProperties.Typeface;
- var currentTypeface = defaultTypeface;
+ var defaultGlyphTypeface = defaultProperties.CachedGlyphTypeface;
var previousTypeface = previousProperties?.Typeface;
+ var previousGlyphTypeface = previousProperties?.CachedGlyphTypeface;
var textSpan = text.Span;
- if (TryGetShapeableLength(textSpan, currentTypeface, null, out var count, out var script))
+ if (TryGetShapeableLength(textSpan, defaultGlyphTypeface, null, out var count, out var script))
{
- if (script == Script.Common && previousTypeface is not null)
+ if (script == Script.Common && previousGlyphTypeface is not null)
{
- if (TryGetShapeableLength(textSpan, previousTypeface.Value, null, out var fallbackCount, out _))
+ if (TryGetShapeableLength(textSpan, previousGlyphTypeface, null, out var fallbackCount, out _))
{
return new UnshapedTextRun(text.Slice(0, fallbackCount),
- defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
+ defaultProperties.WithTypeface(previousTypeface!.Value), biDiLevel);
}
}
- return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(currentTypeface),
+ return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(defaultTypeface),
biDiLevel);
}
- if (previousTypeface is not null)
+ if (previousGlyphTypeface is not null)
{
- if (TryGetShapeableLength(textSpan, previousTypeface.Value, defaultTypeface, out count, out _))
+ if (TryGetShapeableLength(textSpan, previousGlyphTypeface, defaultGlyphTypeface, out count, out _))
{
return new UnshapedTextRun(text.Slice(0, count),
- defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
+ defaultProperties.WithTypeface(previousTypeface!.Value), biDiLevel);
}
}
@@ -124,25 +126,23 @@ namespace Avalonia.Media.TextFormatting
var matchFound =
fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
- out currentTypeface);
+ out var fallbackTypeface);
- if (matchFound && TryGetShapeableLength(textSpan, currentTypeface, defaultTypeface, out count, out _))
+ var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
+
+ if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count, out _))
{
//Fallback found
- return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(currentTypeface),
+ return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
biDiLevel);
}
// no fallback found
- currentTypeface = defaultTypeface;
-
- var glyphTypeface = currentTypeface.GlyphTypeface;
-
var enumerator = new GraphemeEnumerator(textSpan);
while (enumerator.MoveNext(out var grapheme))
{
- if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
+ if (!grapheme.FirstCodepoint.IsWhiteSpace && defaultGlyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
break;
}
@@ -157,15 +157,15 @@ namespace Avalonia.Media.TextFormatting
/// Tries to get a shapeable length that is supported by the specified typeface.
///
/// The characters to shape.
- /// The typeface that is used to find matching characters.
- ///
+ /// The typeface that is used to find matching characters.
+ /// The default typeface.
/// The shapeable length.
///
///
internal static bool TryGetShapeableLength(
ReadOnlySpan text,
- Typeface typeface,
- Typeface? defaultTypeface,
+ IGlyphTypeface glyphTypeface,
+ IGlyphTypeface? defaultGlyphTypeface,
out int length,
out Script script)
{
@@ -177,22 +177,22 @@ namespace Avalonia.Media.TextFormatting
return false;
}
- var font = typeface.GlyphTypeface;
- var defaultFont = defaultTypeface?.GlyphTypeface;
-
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext(out var currentGrapheme))
{
- var currentScript = currentGrapheme.FirstCodepoint.Script;
+ var currentCodepoint = currentGrapheme.FirstCodepoint;
+ var currentScript = currentCodepoint.Script;
- if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
+ if (!currentCodepoint.IsWhiteSpace
+ && defaultGlyphTypeface != null
+ && defaultGlyphTypeface.TryGetGlyph(currentCodepoint, out _))
{
break;
}
//Stop at the first missing glyph
- if (!currentGrapheme.FirstCodepoint.IsBreakChar && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
+ if (!currentCodepoint.IsBreakChar && !glyphTypeface.TryGetGlyph(currentCodepoint, out _))
{
break;
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
index b0242be87e..bc19690196 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
@@ -27,6 +27,7 @@ namespace Avalonia.Media.TextFormatting
TextLineBreak? nextLineBreak = null;
IReadOnlyList? textRuns;
var objectPool = FormattingObjectPool.Instance;
+ var fontManager = FontManager.Current;
var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool,
out var textEndOfLine, out var textSourceLength);
@@ -42,7 +43,7 @@ namespace Avalonia.Media.TextFormatting
}
else
{
- shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, out resolvedFlowDirection);
+ shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, out resolvedFlowDirection);
textRuns = shapedTextRuns;
if (nextLineBreak == null && textEndOfLine != null)
@@ -72,7 +73,7 @@ namespace Avalonia.Media.TextFormatting
case TextWrapping.Wrap:
{
textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth,
- paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
+ paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager);
break;
}
default:
@@ -178,12 +179,13 @@ namespace Avalonia.Media.TextFormatting
/// The default paragraph properties.
/// The resolved flow direction.
/// A pool used to get reusable formatting objects.
+ /// The font manager to use.
///
/// A list of shaped text characters.
///
private static RentedList ShapeTextRuns(IReadOnlyList textRuns,
TextParagraphProperties paragraphProperties, FormattingObjectPool objectPool,
- out FlowDirection resolvedFlowDirection)
+ FontManager fontManager, out FlowDirection resolvedFlowDirection)
{
var flowDirection = paragraphProperties.FlowDirection;
var shapedRuns = objectPool.TextRunLists.Rent();
@@ -223,7 +225,7 @@ namespace Avalonia.Media.TextFormatting
var processedRuns = objectPool.TextRunLists.Rent();
- CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, processedRuns);
+ CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns);
bidiData.Reset();
bidiAlgorithm.Reset();
@@ -240,7 +242,9 @@ namespace Avalonia.Media.TextFormatting
{
groupedRuns.Clear();
groupedRuns.Add(shapeableRun);
+
var text = shapeableRun.Text;
+ var properties = shapeableRun.Properties;
while (index + 1 < processedRuns.Count)
{
@@ -251,7 +255,7 @@ namespace Avalonia.Media.TextFormatting
if (shapeableRun.BidiLevel == nextRun.BidiLevel
&& TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
- && CanShapeTogether(shapeableRun.Properties, nextRun.Properties))
+ && CanShapeTogether(properties, nextRun.Properties))
{
groupedRuns.Add(nextRun);
index++;
@@ -263,10 +267,10 @@ namespace Avalonia.Media.TextFormatting
break;
}
- var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface,
- currentRun.Properties.FontRenderingEmSize,
- shapeableRun.BidiLevel, currentRun.Properties.CultureInfo,
- paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
+ var shaperOptions = new TextShaperOptions(
+ properties.CachedGlyphTypeface,
+ properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
+ paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
ShapeTogether(groupedRuns, text, shaperOptions, shapedRuns);
@@ -377,10 +381,11 @@ namespace Avalonia.Media.TextFormatting
///
/// The text characters to form from.
/// The bidi levels.
+ /// The font manager to use.
/// A list that will be filled with the processed runs.
///
private static void CoalesceLevels(IReadOnlyList textCharacters, ReadOnlySpan levels,
- RentedList processedRuns)
+ FontManager fontManager, RentedList processedRuns)
{
if (levels.Length == 0)
{
@@ -393,7 +398,6 @@ namespace Avalonia.Media.TextFormatting
TextRunProperties? previousProperties = null;
TextCharacters? currentRun = null;
ReadOnlyMemory runText = default;
- var fontManager = FontManager.Current;
for (var i = 0; i < textCharacters.Count; i++)
{
@@ -638,11 +642,11 @@ namespace Avalonia.Media.TextFormatting
///
/// The empty text line.
public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth,
- TextParagraphProperties paragraphProperties, FormattingObjectPool objectPool)
+ TextParagraphProperties paragraphProperties, FontManager fontManager)
{
var flowDirection = paragraphProperties.FlowDirection;
var properties = paragraphProperties.DefaultTextRunProperties;
- var glyphTypeface = properties.Typeface.GlyphTypeface;
+ var glyphTypeface = properties.CachedGlyphTypeface;
var glyph = glyphTypeface.GetGlyph(s_empty[0]);
var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex, 0.0) };
@@ -666,14 +670,15 @@ namespace Avalonia.Media.TextFormatting
///
/// The current line break if the line was explicitly broken.
/// A pool used to get reusable formatting objects.
+ /// The font manager to use.
/// The wrapped text line.
private static TextLineImpl PerformTextWrapping(IReadOnlyList textRuns, int firstTextSourceIndex,
double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
- TextLineBreak? currentLineBreak, FormattingObjectPool objectPool)
+ TextLineBreak? currentLineBreak, FormattingObjectPool objectPool, FontManager fontManager)
{
if (textRuns.Count == 0)
{
- return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties, objectPool);
+ return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties, fontManager);
}
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
@@ -869,7 +874,7 @@ namespace Avalonia.Media.TextFormatting
{
var textShaper = TextShaper.Current;
- var glyphTypeface = textRun.Properties!.Typeface.GlyphTypeface;
+ var glyphTypeface = textRun.Properties!.CachedGlyphTypeface;
var fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
index 7a74dc89ae..bb58e0d692 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
@@ -427,11 +427,12 @@ namespace Avalonia.Media.TextFormatting
private TextLine[] CreateTextLines()
{
var objectPool = FormattingObjectPool.Instance;
+ var fontManager = FontManager.Current;
if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties,
- FormattingObjectPool.Instance);
+ fontManager);
Bounds = new Rect(0, 0, 0, textLine.Height);
@@ -458,7 +459,7 @@ namespace Avalonia.Media.TextFormatting
if (previousLine != null && previousLine.NewLineLength > 0)
{
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth,
- _paragraphProperties, objectPool);
+ _paragraphProperties, fontManager);
textLines.Add(emptyTextLine);
@@ -517,7 +518,7 @@ namespace Avalonia.Media.TextFormatting
//Make sure the TextLayout always contains at least on empty line
if (textLines.Count == 0)
{
- var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, objectPool);
+ var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager);
textLines.Add(textLine);
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 260fcaccbe..ad3244a3a5 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -1256,7 +1256,7 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics()
{
- var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
+ var fontMetrics = _paragraphProperties.DefaultTextRunProperties.CachedGlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
@@ -1285,12 +1285,13 @@ namespace Avalonia.Media.TextFormatting
{
case ShapedTextRun textRun:
{
+ var properties = textRun.Properties;
var textMetrics =
- new TextMetrics(textRun.Properties.Typeface.GlyphTypeface, textRun.Properties.FontRenderingEmSize);
+ new TextMetrics(properties.CachedGlyphTypeface, properties.FontRenderingEmSize);
- if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
+ if (fontRenderingEmSize < properties.FontRenderingEmSize)
{
- fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
+ fontRenderingEmSize = properties.FontRenderingEmSize;
if (ascent > textMetrics.Ascent)
{
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs
index 7bad99f33f..1622bc3b6d 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs
@@ -12,6 +12,8 @@ namespace Avalonia.Media.TextFormatting
///
public abstract class TextRunProperties : IEquatable
{
+ private IGlyphTypeface? _cachedGlyphTypeFace;
+
///
/// Run typeface
///
@@ -47,6 +49,9 @@ namespace Avalonia.Media.TextFormatting
///
public virtual BaselineAlignment BaselineAlignment => BaselineAlignment.Baseline;
+ internal IGlyphTypeface CachedGlyphTypeface
+ => _cachedGlyphTypeFace ??= Typeface.GlyphTypeface;
+
public bool Equals(TextRunProperties? other)
{
if (ReferenceEquals(null, other))