Browse Source

Cache platform font manager TryMatchCharacter result (#19987)

pull/20023/head
Benedikt Stebner 3 months ago
committed by GitHub
parent
commit
1936725f2d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      src/Avalonia.Base/Media/FontManager.cs
  2. 62
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  3. 15
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  4. 71
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  5. 21
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

7
src/Avalonia.Base/Media/FontManager.cs

@ -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)

62
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@ -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);
}
}
}

15
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@ -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>

71
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -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,

21
tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

@ -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()
{

Loading…
Cancel
Save