Cache platform font manager TryMatchCharacter result (#19987)

This commit is contained in:
Benedikt Stebner
2025-11-08 14:35:20 +01:00
committed by GitHub
parent a12b8d7061
commit 1936725f2d
5 changed files with 141 additions and 35 deletions
+5 -2
View File
@@ -287,6 +287,8 @@ namespace Avalonia.Media
}
if (TryGetFontCollection(source, out var fontCollection) &&
// With composite fonts we need to first check if the font collection contains the family if not we skip it
fontCollection.TryGetGlyphTypeface(familyName, fontStyle, fontWeight, fontStretch, out _) &&
fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
{
return true;
@@ -306,8 +308,9 @@ namespace Avalonia.Media
}
}
//Try to find a match with the system font manager
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out typeface);
//Try to find a match with the system font collection
return SystemFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, fontFamily?.Name,
culture, out typeface);
}
internal IReadOnlyList<Typeface> GetFamilyTypefaces(FontFamily fontFamily)
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Avalonia.Platform;
@@ -15,7 +16,7 @@ namespace Avalonia.Media.Fonts
public SystemFontCollection(FontManager fontManager)
{
_fontManager = fontManager;
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().Where(x=> !string.IsNullOrEmpty(x)).ToList();
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().Where(x => !string.IsNullOrEmpty(x)).ToList();
}
public override Uri Key => FontManager.SystemFontsKey;
@@ -144,21 +145,6 @@ namespace Avalonia.Media.Fonts
}
return;
void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
{
var typefaces = _glyphTypefaceCache.GetOrAdd(familyName,
x =>
{
_familyNames.Insert(0, familyName);
return new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
});
typefaces.TryAdd(
new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch),
glyphTypeface);
}
}
public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
@@ -172,5 +158,49 @@ namespace Avalonia.Media.Fonts
return false;
}
public override bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch, string? familyName,
CultureInfo? culture, out Typeface match)
{
//TODO12: Think about removing familyName parameter
match = default;
if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2)
{
if (fontManagerImpl2.TryMatchCharacter(codepoint, style, weight, stretch, culture, out var glyphTypeface))
{
AddGlyphTypefaceByFamilyName(glyphTypeface.FamilyName, glyphTypeface);
match = new Typeface(glyphTypeface.FamilyName, glyphTypeface.Style, glyphTypeface.Weight,
glyphTypeface.Stretch);
return true;
}
return false;
}
else
{
return _fontManager.PlatformImpl.TryMatchCharacter(codepoint, style, weight, stretch, culture, out match);
}
}
private void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
{
// Add family name to the collection if not exists
if (!_familyNames.Contains(familyName))
{
_familyNames.Add(familyName);
}
// Get or create the typefaces dictionary for the family name
if (!_glyphTypefaceCache.TryGetValue(familyName, out var typefaces))
{
_glyphTypefaceCache[familyName] = typefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
}
// Add the glyph typeface to the cache
typefaces.TryAdd(new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch), glyphTypeface);
}
}
}
@@ -65,6 +65,21 @@ namespace Avalonia.Platform
internal interface IFontManagerImpl2 : IFontManagerImpl
{
/// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStretch">The font stretch.</param>
/// <param name="culture">The culture.</param>
/// <param name="typeface">The matching typeface.</param>
/// <returns>
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, [NotNullWhen(true)] out IGlyphTypeface? typeface);
/// <summary>
/// Tries to get a list of typefaces for the specified family name.
/// </summary>
+54 -17
View File
@@ -1,8 +1,11 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
@@ -15,6 +18,7 @@ namespace Avalonia.Skia
public string GetDefaultFontFamilyName()
{
return SKTypeface.Default.FamilyName;
}
@@ -32,6 +36,53 @@ namespace Avalonia.Skia
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface fontKey)
{
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out SKTypeface? skTypeface))
{
fontKey = default;
return false;
}
fontKey = new Typeface(
skTypeface.FamilyName,
skTypeface.FontStyle.Slant.ToAvalonia(),
(FontWeight)skTypeface.FontStyle.Weight,
(FontStretch)skTypeface.FontStyle.Width);
skTypeface.Dispose();
return true;
}
public bool TryMatchCharacter(
int codepoint,
FontStyle fontStyle,
FontWeight fontWeight,
FontStretch fontStretch,
CultureInfo? culture,
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out SKTypeface? skTypeface))
{
glyphTypeface = null;
return false;
}
glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
return true;
}
private bool TryMatchCharacter(
int codepoint,
FontStyle fontStyle,
FontWeight fontWeight,
FontStretch fontStretch,
CultureInfo? culture,
[NotNullWhen(true)] out SKTypeface? skTypeface)
{
SKFontStyle skFontStyle;
@@ -59,23 +110,9 @@ namespace Avalonia.Skia
t_languageTagBuffer ??= new string[1];
t_languageTagBuffer[0] = culture.Name;
using var skTypeface = _skFontManager.MatchCharacter(null, skFontStyle, t_languageTagBuffer, codepoint);
skTypeface = _skFontManager.MatchCharacter(null, skFontStyle, t_languageTagBuffer, codepoint);
if (skTypeface != null)
{
// ToDo: create glyph typeface here to get the correct style/weight/stretch
fontKey = new Typeface(
skTypeface.FamilyName,
skTypeface.FontStyle.Slant.ToAvalonia(),
(FontWeight)skTypeface.FontStyle.Weight,
(FontStretch)skTypeface.FontStyle.Width);
return true;
}
fontKey = default;
return false;
return skTypeface != null;
}
public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
@@ -110,6 +110,27 @@ namespace Avalonia.Skia.UnitTests.Media
}
}
[Fact]
public void Should_Cache_MatchCharacter()
{
var fontManagerImpl = new CustomFontManagerImpl();
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl)))
{
var emoji = Codepoint.ReadAt("😀", 0, out _);
Assert.True(FontManager.Current.TryMatchCharacter((int)emoji, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var firstMatch));
var firstGlyphTypeface = firstMatch.GlyphTypeface;
Assert.True(FontManager.Current.TryMatchCharacter((int)emoji, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var secondMatch));
var secondGlyphTypeface = secondMatch.GlyphTypeface;
Assert.Equal(firstGlyphTypeface, secondGlyphTypeface);
}
}
[Fact]
public void Should_Load_Embedded_DefaultFontFamily()
{