Browse Source

Fix issue 4427 - System.InvalidOperationException: Default font family name can't be null or empty (#12817)

* Fix issue 4427 - System.InvalidOperationException: Default font family name can't be null or empty

* Updated FontManager

* Removed Linq usage from FontManager

---------

Co-authored-by: Mihnea Rădulescu <>
Co-authored-by: Benedikt Stebner <[email protected]>
Mihnea Rădulescu 2 years ago
parent
commit
7b67fda0eb

+ 26 - 14
src/Avalonia.Base/Media/FontManager.cs

@@ -28,20 +28,13 @@ namespace Avalonia.Media
         {
             PlatformImpl = platformImpl;
 
-            var options = AvaloniaLocator.Current.GetService<FontManagerOptions>();
+            AddFontCollection(new SystemFontCollection(this));
 
+            var options = AvaloniaLocator.Current.GetService<FontManagerOptions>();
             _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));
         }
 
         /// <summary>
@@ -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;
+        }
     }
 }

+ 59 - 0
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

+ 17 - 2
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<InvalidOperationException>(() => 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);
+            }
+        }
     }
 }