diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 27e25cf359..e472e56d97 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -15,9 +15,10 @@ namespace Avalonia.Media /// public sealed class FontManager { + internal static Uri SystemFontsKey = new Uri("fonts:SystemFonts"); + public const string FontCollectionScheme = "fonts"; - private readonly SystemFontCollection _systemFonts; private readonly ConcurrentDictionary _fontCollections = new ConcurrentDictionary(); private readonly IReadOnlyList? _fontFallbacks; @@ -36,7 +37,7 @@ namespace Avalonia.Media throw new InvalidOperationException("Default font family name can't be null or empty."); } - _systemFonts = new SystemFontCollection(this); + AddFontCollection(new SystemFontCollection(this)); } public static FontManager Current @@ -71,7 +72,7 @@ namespace Avalonia.Media /// /// Get all system fonts. /// - public IFontCollection SystemFonts => _systemFonts; + public IFontCollection SystemFonts => _fontCollections[SystemFontsKey]; internal IFontManagerImpl PlatformImpl { get; } @@ -120,6 +121,11 @@ namespace Avalonia.Media { return true; } + + if (!fontFamily.FamilyNames.HasFallbacks) + { + return false; + } } foreach (var familyName in fontFamily.FamilyNames) @@ -130,7 +136,7 @@ namespace Avalonia.Media } } - return false; + return SystemFonts.TryGetGlyphTypeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface); } public void AddFontCollection(IFontCollection fontCollection) @@ -139,13 +145,15 @@ namespace Avalonia.Media if (!fontCollection.Key.IsFontCollection()) { - throw new ArgumentException(nameof(fontCollection), "Font collection Key should follow the fontCollection: scheme."); + throw new ArgumentException(nameof(fontCollection), "Font collection Key should follow the fonts: scheme."); } - if (!_fontCollections.TryAdd(key, fontCollection)) + _fontCollections.AddOrUpdate(key, fontCollection, (_, oldCollection) => { - throw new ArgumentException(nameof(fontCollection), "Font collection is already registered."); - } + oldCollection.Dispose(); + + return fontCollection; + }); fontCollection.Initialize(PlatformImpl); } diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs index 80f47cee3b..7f12825a66 100644 --- a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Platform; -using Avalonia.Utilities; namespace Avalonia.Media.Fonts { diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index ce0deb21e4..fd332c6ebe 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Platform; @@ -8,8 +9,7 @@ namespace Avalonia.Media.Fonts { internal class SystemFontCollection : IFontCollection { - private readonly Dictionary> _glyphTypefaceCache = - new Dictionary>(); + private readonly ConcurrentDictionary> _glyphTypefaceCache = new(); private readonly FontManager _fontManager; private readonly string[] _familyNames; @@ -20,7 +20,7 @@ namespace Avalonia.Media.Fonts _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); } - public Uri Key => new Uri("fontCollection:SystemFonts"); + public Uri Key => FontManager.SystemFontsKey; public FontFamily this[int index] { @@ -44,23 +44,30 @@ namespace Avalonia.Media.Fonts var key = new FontCollectionKey(style, weight, stretch); - if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) + if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) { - glyphTypefaces = new Dictionary(); - - _glyphTypefaceCache.Add(familyName, glyphTypefaces); - } - - if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) - { - return true; + if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) + { + return true; + } + else + { + if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) && + glyphTypefaces.TryAdd(key, glyphTypeface)) + { + return true; + } + } } if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface)) { - glyphTypefaces.Add(key, glyphTypeface); + glyphTypefaces = new ConcurrentDictionary(); - return true; + if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces)) + { + return true; + } } return false; diff --git a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs index c50f31a834..14e48b3b6c 100644 --- a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs +++ b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs @@ -42,18 +42,17 @@ namespace Avalonia.Direct2D1.UnitTests.Media } [Fact] - public void Should_Throw_InvalidOperationException_For_Unknown_Font() + public void Should_Create_Typeface_For_Unknown_Font() { using (AvaloniaLocator.EnterScope()) { Direct2D1Platform.Initialize(); - var fontManager = FontManager.Current; + var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; - Assert.Throws(() => - { - var glyphTypeface =new Typeface(new FontFamily("Unknown")).GlyphTypeface; - }); + var defaultName = FontManager.Current.DefaultFontFamilyName; + + Assert.Equal(defaultName, glyphTypeface.FamilyName); } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs index a9a0bd8faf..21c46b836d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs @@ -36,14 +36,13 @@ namespace Avalonia.Skia.UnitTests.Media } [Fact] - public void Should_Throw_InvalidOperationException_For_Invalid_FamilyName() + public void Should_Yield_Default_GlyphTypeface_For_Invalid_FamilyName() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) - { - Assert.Throws(() => - { - var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; - }); + { + var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; + + Assert.Equal(FontManager.Current.DefaultFontFamilyName, glyphTypeface.FamilyName); } }