diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index 2f2948cb3e..a4a1fcf41d 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,46 @@ namespace Avalonia.Media.Fonts //We initialize the system font collection during construction. } - public override IEnumerator GetEnumerator() + public void AddCustomFontSource(Uri source) { - foreach (var familyName in _familyNames) + if (source is null) { - yield return new FontFamily(familyName); + return; + } + + LoadGlyphTypefaces(_fontManager.PlatformImpl, 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..2e9cb2bcbf --- /dev/null +++ b/src/Avalonia.Controls/SystemFontAppBuilderExtension.cs @@ -0,0 +1,19 @@ +using System; +using Avalonia.Media.Fonts; + +namespace Avalonia +{ + public static class SystemFontAppBuilderExtension + { + public static AppBuilder WithSystemFontSource(this AppBuilder appBuilder, Uri fontSource) + { + return appBuilder.ConfigureFonts(fontManager => + { + if(fontManager.SystemFonts is SystemFontCollection systemFontCollection) + { + systemFontCollection.AddCustomFontSource(fontSource); + } + }); + } + } +} diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 98971426f1..ca7772f0c9 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,25 @@ 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); + + systemFontCollection.AddCustomFontSource(new Uri(s_fontUri, UriKind.Absolute)); + + Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono"), out var glyphTypeface)); + + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); + } + } + } } }