From d3aa0c21e39a759e358fa7c3a90d3422fb19ecbf Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 10 Oct 2024 10:22:22 +0200 Subject: [PATCH] Introduce font family mapping (#17234) --- src/Avalonia.Base/Media/FontManager.cs | 30 +++++++++++++++++-- src/Avalonia.Base/Media/FontManagerOptions.cs | 17 +++++++++++ .../Media/FontManagerTests.cs | 29 ++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 88e332f334..9c55ee9700 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -26,6 +26,7 @@ namespace Avalonia.Media private readonly ConcurrentDictionary _fontCollections = new ConcurrentDictionary(); private readonly IReadOnlyList? _fontFallbacks; + private readonly IReadOnlyDictionary? _fontFamilyMappings; public FontManager(IFontManagerImpl platformImpl) { @@ -35,6 +36,7 @@ namespace Avalonia.Media var options = AvaloniaLocator.Current.GetService(); _fontFallbacks = options?.FontFallbacks; + _fontFamilyMappings = options?.FontFamilyMappings; var defaultFontFamilyName = GetDefaultFontFamilyName(options); DefaultFontFamily = new FontFamily(defaultFontFamilyName); @@ -91,8 +93,8 @@ namespace Avalonia.Media { glyphTypeface = null; - var fontFamily = typeface.FontFamily; - + var fontFamily = GetMappedFontFamily(typeface.FontFamily); + if (typeface.FontFamily.Name == FontFamily.DefaultFontFamilyName) { return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface); @@ -108,6 +110,20 @@ namespace Avalonia.Media var familyName = fontFamily.FamilyNames[i]; + if(_fontFamilyMappings != null && _fontFamilyMappings.TryGetValue(familyName, out var mappedFontFamily)) + { + if(mappedFontFamily.Key != null) + { + key = mappedFontFamily.Key; + } + else + { + key = new FontFamilyKey(SystemFontsKey); + } + + familyName = mappedFontFamily.FamilyNames.PrimaryFamilyName; + } + if (TryGetGlyphTypefaceByKeyAndName(typeface, key, familyName, out glyphTypeface) && glyphTypeface.FamilyName.Contains(familyName)) { @@ -144,6 +160,16 @@ namespace Avalonia.Media //Nothing was found so use the default return TryGetGlyphTypeface(new Typeface(FontFamily.DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface); + + FontFamily GetMappedFontFamily(FontFamily fontFamily) + { + if (_fontFamilyMappings == null ||!_fontFamilyMappings.TryGetValue(fontFamily.FamilyNames.PrimaryFamilyName, out var mappedFontFamily)) + { + return fontFamily; + } + + return mappedFontFamily; + } } private bool TryGetGlyphTypefaceByKeyAndName(Typeface typeface, FontFamilyKey key, string familyName, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) diff --git a/src/Avalonia.Base/Media/FontManagerOptions.cs b/src/Avalonia.Base/Media/FontManagerOptions.cs index 54227dce0f..c10310a6ca 100644 --- a/src/Avalonia.Base/Media/FontManagerOptions.cs +++ b/src/Avalonia.Base/Media/FontManagerOptions.cs @@ -4,8 +4,25 @@ namespace Avalonia.Media { public class FontManagerOptions { + /// + /// Gets or sets the default font family's name + /// public string? DefaultFamilyName { get; set; } + /// + /// Gets or sets the font fallbacks. + /// + /// + /// A fallback is fullfilled before anything else when the font manager tries to match a specific codepoint. + /// public IReadOnlyList? FontFallbacks { get; set; } + + /// + /// Gets or sets the font family mappings. + /// + /// + /// A font family mapping is used if a requested family name can't be resolved. + /// + public IReadOnlyDictionary? FontFamilyMappings { get; set; } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index e5ce99f079..6114b662d9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Avalonia.Fonts.Inter; using Avalonia.Headless; @@ -335,5 +337,32 @@ namespace Avalonia.Skia.UnitTests.Media } } } + + [Fact] + public void Should_Map_FontFamily() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions + { + DefaultFamilyName = s_fontUri, + FontFamilyMappings = new Dictionary + { + { "Segoe UI", new FontFamily("fonts:Inter#Inter") } + } + }); + + FontManager.Current.AddFontCollection(new InterFontCollection()); + + var result = FontManager.Current.TryGetGlyphTypeface(new Typeface("Abc, Segoe UI"), out var glyphTypeface); + + Assert.True(result); + + Assert.Equal("Inter", glyphTypeface.FamilyName); + } + } + } } }