From 36ec244b2c374648448f0928278af84cd3480d81 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 10 Oct 2023 10:32:18 +0200 Subject: [PATCH 1/4] Introduce AppBuilder extension to customize system fonts --- .../Media/Fonts/SystemFontCollection.cs | 64 +++++++++++++++++-- .../SystemFontAppBuilderExtension.cs | 20 ++++++ .../Media/FontManagerTests.cs | 24 +++++++ 3 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Controls/SystemFontAppBuilderExtension.cs diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index 2f2948cb3e..ce2b107f62 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/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.Linq; using Avalonia.Platform; namespace Avalonia.Media.Fonts @@ -9,12 +10,12 @@ namespace Avalonia.Media.Fonts internal class SystemFontCollection : FontCollectionBase { private readonly FontManager _fontManager; - private readonly string[] _familyNames; + private readonly List _familyNames; public SystemFontCollection(FontManager fontManager) { _fontManager = fontManager; - _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); + _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().ToList(); } public override Uri Key => FontManager.SystemFontsKey; @@ -29,7 +30,15 @@ namespace Avalonia.Media.Fonts } } - public override int Count => _familyNames.Length; + public override int Count => _familyNames.Count; + + public override IEnumerator GetEnumerator() + { + foreach (var familyName in _familyNames) + { + yield return new FontFamily(familyName); + } + } public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) @@ -58,11 +67,54 @@ namespace Avalonia.Media.Fonts //We initialize the system font collection during construction. } - public override IEnumerator GetEnumerator() + public void AddCustomFontFamilies(IReadOnlyList customFontFamilies) { - foreach (var familyName in _familyNames) + if (customFontFamilies is null) { - yield return new FontFamily(familyName); + return; + } + + for (int i = 0; i < customFontFamilies.Count; i++) + { + var fontFamily = customFontFamilies[i]; + + if (fontFamily.Key is FontFamilyKey key) + { + LoadGlyphTypefaces(_fontManager.PlatformImpl, key.Source); + } + } + } + + private void LoadGlyphTypefaces(IFontManagerImpl fontManager, Uri source) + { + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + + var fontAssets = FontFamilyLoader.LoadFontAssets(source); + + foreach (var fontAsset in fontAssets) + { + var stream = assetLoader.Open(fontAsset); + + if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface)) + { + if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces)) + { + glyphTypefaces = new ConcurrentDictionary(); + + if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces)) + { + //Move the user defined system font to the start of the collection + _familyNames.Insert(0, glyphTypeface.FamilyName); + } + } + + var key = new FontCollectionKey( + glyphTypeface.Style, + glyphTypeface.Weight, + glyphTypeface.Stretch); + + glyphTypefaces.TryAdd(key, glyphTypeface); + } } } } diff --git a/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs new file mode 100644 index 0000000000..6c8ac2ffaa --- /dev/null +++ b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Media.Fonts; + +namespace Avalonia +{ + public static class SystemFontAppBuilderExtension + { + public static AppBuilder WithCustomSystemFonts(this AppBuilder appBuilder, IReadOnlyList customFontFamilies) + { + return appBuilder.ConfigureFonts(fontManager => + { + if(fontManager.SystemFonts is SystemFontCollection systemFontCollection) + { + systemFontCollection.AddCustomFontFamilies(customFontFamilies); + } + }); + } + } +} diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 98971426f1..4fe04b25ef 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs @@ -1,6 +1,8 @@ using System; +using System.Linq; using Avalonia.Headless; using Avalonia.Media; +using Avalonia.Media.Fonts; using Avalonia.UnitTests; using SkiaSharp; using Xunit; @@ -217,5 +219,27 @@ namespace Avalonia.Skia.UnitTests.Media } } } + + [Fact] + public void Should_Use_Custom_SystemFont() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + using (AvaloniaLocator.EnterScope()) + { + var systemFontCollection = FontManager.Current.SystemFonts as SystemFontCollection; + + Assert.NotNull(systemFontCollection); + + var customFontFamilies = new[] { new FontFamily(s_fontUri) }; + + systemFontCollection.AddCustomFontFamilies(customFontFamilies); + + Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono"), out var glyphTypeface)); + + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); + } + } + } } } From c857a47f2ac2697a9f6b773505856119721ae156 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 12 Oct 2023 08:26:41 +0200 Subject: [PATCH 2/4] Simplify custom system font source --- .../Media/Fonts/SystemFontCollection.cs | 14 +++----------- .../SystemFontAppBuilderExtension.cs | 7 +++---- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index ce2b107f62..a4a1fcf41d 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -67,22 +67,14 @@ namespace Avalonia.Media.Fonts //We initialize the system font collection during construction. } - public void AddCustomFontFamilies(IReadOnlyList customFontFamilies) + public void AddCustomFontSource(Uri source) { - if (customFontFamilies is null) + if (source is null) { return; } - for (int i = 0; i < customFontFamilies.Count; i++) - { - var fontFamily = customFontFamilies[i]; - - if (fontFamily.Key is FontFamilyKey key) - { - LoadGlyphTypefaces(_fontManager.PlatformImpl, key.Source); - } - } + LoadGlyphTypefaces(_fontManager.PlatformImpl, source); } private void LoadGlyphTypefaces(IFontManagerImpl fontManager, Uri source) diff --git a/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs index 6c8ac2ffaa..056ebf13e8 100644 --- a/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs +++ b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs @@ -1,18 +1,17 @@ -using System.Collections.Generic; -using Avalonia.Media; +using System; using Avalonia.Media.Fonts; namespace Avalonia { public static class SystemFontAppBuilderExtension { - public static AppBuilder WithCustomSystemFonts(this AppBuilder appBuilder, IReadOnlyList customFontFamilies) + public static AppBuilder WithCustomSystemFont(this AppBuilder appBuilder, Uri fontSource) { return appBuilder.ConfigureFonts(fontManager => { if(fontManager.SystemFonts is SystemFontCollection systemFontCollection) { - systemFontCollection.AddCustomFontFamilies(customFontFamilies); + systemFontCollection.AddCustomFontSource(fontSource); } }); } From cc558bbc8ae8fd4a5db97d716b562645222b82ed Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 12 Oct 2023 08:32:24 +0200 Subject: [PATCH 3/4] Improve naming --- src/Avalonia.Controls/SystemFontAppBuilderExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs index 056ebf13e8..2e9cb2bcbf 100644 --- a/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs +++ b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs @@ -5,7 +5,7 @@ namespace Avalonia { public static class SystemFontAppBuilderExtension { - public static AppBuilder WithCustomSystemFont(this AppBuilder appBuilder, Uri fontSource) + public static AppBuilder WithSystemFontSource(this AppBuilder appBuilder, Uri fontSource) { return appBuilder.ConfigureFonts(fontManager => { From 1d3feffed6fe8e00785fa23ed15f163e10e8a917 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 12 Oct 2023 08:39:56 +0200 Subject: [PATCH 4/4] Fix unit test --- tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 4fe04b25ef..ca7772f0c9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs @@ -231,9 +231,7 @@ namespace Avalonia.Skia.UnitTests.Media Assert.NotNull(systemFontCollection); - var customFontFamilies = new[] { new FontFamily(s_fontUri) }; - - systemFontCollection.AddCustomFontFamilies(customFontFamilies); + systemFontCollection.AddCustomFontSource(new Uri(s_fontUri, UriKind.Absolute)); Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono"), out var glyphTypeface));