Browse Source

Cache platform font manager TryMatchCharacter result (#19987)

pull/20084/head
Benedikt Stebner 11 months ago
committed by Julien Lebosquain
parent
commit
d7d6d46e09
  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) && 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)) fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
{ {
return true; return true;
@ -306,8 +308,9 @@ namespace Avalonia.Media
} }
} }
//Try to find a match with the system font manager //Try to find a match with the system font collection
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out typeface); return SystemFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, fontFamily?.Name,
culture, out typeface);
} }
internal IReadOnlyList<Typeface> GetFamilyTypefaces(FontFamily fontFamily) 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.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
@ -15,7 +16,7 @@ namespace Avalonia.Media.Fonts
public SystemFontCollection(FontManager fontManager) public SystemFontCollection(FontManager fontManager)
{ {
_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; public override Uri Key => FontManager.SystemFontsKey;
@ -144,21 +145,6 @@ namespace Avalonia.Media.Fonts
} }
return; 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) public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
@ -172,5 +158,49 @@ namespace Avalonia.Media.Fonts
return false; 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 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> /// <summary>
/// Tries to get a list of typefaces for the specified family name. /// Tries to get a list of typefaces for the specified family name.
/// </summary> /// </summary>

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

@ -1,8 +1,11 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
@ -15,6 +18,7 @@ namespace Avalonia.Skia
public string GetDefaultFontFamilyName() public string GetDefaultFontFamilyName()
{ {
return SKTypeface.Default.FamilyName; return SKTypeface.Default.FamilyName;
} }
@ -32,6 +36,53 @@ namespace Avalonia.Skia
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface fontKey) 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; SKFontStyle skFontStyle;
@ -59,23 +110,9 @@ namespace Avalonia.Skia
t_languageTagBuffer ??= new string[1]; t_languageTagBuffer ??= new string[1];
t_languageTagBuffer[0] = culture.Name; 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) return 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;
} }
public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, 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] [Fact]
public void Should_Load_Embedded_DefaultFontFamily() public void Should_Load_Embedded_DefaultFontFamily()
{ {

Loading…
Cancel
Save