diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 850d0011f5..af7a58dbe1 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -28,20 +28,13 @@ namespace Avalonia.Media { PlatformImpl = platformImpl; - var options = AvaloniaLocator.Current.GetService(); + AddFontCollection(new SystemFontCollection(this)); + var options = AvaloniaLocator.Current.GetService(); _fontFallbacks = options?.FontFallbacks; - var defaultFontFamilyName = options?.DefaultFamilyName ?? PlatformImpl.GetDefaultFontFamilyName(); - - if (string.IsNullOrEmpty(defaultFontFamilyName)) - { - throw new InvalidOperationException("Default font family name can't be null or empty."); - } - + var defaultFontFamilyName = GetDefaultFontFamilyName(options); DefaultFontFamily = new FontFamily(defaultFontFamilyName); - - AddFontCollection(new SystemFontCollection(this)); } /// @@ -111,8 +104,8 @@ namespace Avalonia.Media var key = compositeKey.Keys[i]; var familyName = fontFamily.FamilyNames[i]; - - if (TryGetGlyphTypefaceByKeyAndName(typeface, key, familyName, out glyphTypeface) && + + if (TryGetGlyphTypefaceByKeyAndName(typeface, key, familyName, out glyphTypeface) && glyphTypeface.FamilyName.Contains(familyName)) { return true; @@ -165,7 +158,7 @@ namespace Avalonia.Media source = new Uri(key.BaseUri, source); } - if (TryGetFontCollection(source, out var fontCollection) && + if (TryGetFontCollection(source, out var fontCollection) && fontCollection.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface)) { if (glyphTypeface.FamilyName.Contains(familyName)) @@ -270,7 +263,7 @@ namespace Avalonia.Media private bool TryGetFontCollection(Uri source, [NotNullWhen(true)] out IFontCollection? fontCollection) { - if(source.Scheme == SystemFontScheme) + if (source.Scheme == SystemFontScheme) { source = SystemFontsKey; } @@ -289,5 +282,24 @@ namespace Avalonia.Media return fontCollection != null; } + + private string GetDefaultFontFamilyName(FontManagerOptions? options) + { + var defaultFontFamilyName = options?.DefaultFamilyName + ?? PlatformImpl.GetDefaultFontFamilyName(); + + if (string.IsNullOrEmpty(defaultFontFamilyName) && SystemFonts.Count > 0) + { + defaultFontFamilyName = SystemFonts[0].Name; + } + + if (string.IsNullOrEmpty(defaultFontFamilyName)) + { + throw new InvalidOperationException( + "Default font family name can't be null or empty."); + } + + return defaultFontFamilyName; + } } } diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs index 471ea9a6d0..0311136d1c 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -250,6 +250,65 @@ namespace Avalonia.Headless } } + internal class HeadlessFontManagerWithMultipleSystemFontsStub : IFontManagerImpl + { + private readonly string[] _installedFontFamilyNames; + private readonly string _defaultFamilyName; + + public HeadlessFontManagerWithMultipleSystemFontsStub( + string[] installedFontFamilyNames, + string defaultFamilyName = "Default") + { + _installedFontFamilyNames = installedFontFamilyNames; + _defaultFamilyName = defaultFamilyName; + } + + public int TryCreateGlyphTypefaceCount { get; private set; } + + public string GetDefaultFontFamilyName() + { + return _defaultFamilyName; + } + + string[] IFontManagerImpl.GetInstalledFontFamilyNames(bool checkForUpdates) + { + return _installedFontFamilyNames; + } + + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, + FontStretch fontStretch, + CultureInfo? culture, out Typeface fontKey) + { + fontKey = new Typeface(_defaultFamilyName); + + return false; + } + + public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) + { + glyphTypeface = null; + + TryCreateGlyphTypefaceCount++; + + if (familyName == "Unknown") + { + return false; + } + + glyphTypeface = new HeadlessGlyphTypefaceImpl(); + + return true; + } + + public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface) + { + glyphTypeface = new HeadlessGlyphTypefaceImpl(); + + return true; + } + } + internal class HeadlessIconLoaderStub : IPlatformIconLoader { private class IconStub : IWindowIconImpl diff --git a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs index adb5431ce6..fe72a9dfd1 100644 --- a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs @@ -26,9 +26,12 @@ namespace Avalonia.Base.UnitTests.Media } [Fact] - public void Should_Throw_When_Default_FamilyName_Is_Null() + public void Should_Throw_When_Default_FamilyName_Is_Null_And_Installed_Font_Family_Names_Is_Empty() { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new HeadlessFontManagerStub(null!)))) + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface + .With(fontManagerImpl: new HeadlessFontManagerWithMultipleSystemFontsStub( + installedFontFamilyNames: new string[] { }, + defaultFamilyName: null)))) { Assert.Throws(() => FontManager.Current); } @@ -73,5 +76,17 @@ namespace Avalonia.Base.UnitTests.Media Assert.Equal("MyFont", typeface.FontFamily.Name); } } + + [Fact] + public void Should_Return_First_Installed_Font_Family_Name_When_Default_Family_Name_Is_Null() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface + .With(fontManagerImpl: new HeadlessFontManagerWithMultipleSystemFontsStub( + installedFontFamilyNames: new[] { "DejaVu", "Verdana" }, + defaultFamilyName: null)))) + { + Assert.Equal("DejaVu", FontManager.Current.DefaultFontFamily.Name); + } + } } }