Browse Source

Try to find a fallback glyph typeface for unknown font families

pull/10455/head
Benedikt Stebner 4 years ago
parent
commit
2364c5d140
  1. 24
      src/Avalonia.Base/Media/FontManager.cs
  2. 1
      src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
  3. 35
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  4. 11
      tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
  5. 11
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

24
src/Avalonia.Base/Media/FontManager.cs

@ -15,9 +15,10 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public sealed class FontManager public sealed class FontManager
{ {
internal static Uri SystemFontsKey = new Uri("fonts:SystemFonts");
public const string FontCollectionScheme = "fonts"; public const string FontCollectionScheme = "fonts";
private readonly SystemFontCollection _systemFonts;
private readonly ConcurrentDictionary<Uri, IFontCollection> _fontCollections = new ConcurrentDictionary<Uri, IFontCollection>(); private readonly ConcurrentDictionary<Uri, IFontCollection> _fontCollections = new ConcurrentDictionary<Uri, IFontCollection>();
private readonly IReadOnlyList<FontFallback>? _fontFallbacks; private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
@ -36,7 +37,7 @@ namespace Avalonia.Media
throw new InvalidOperationException("Default font family name can't be null or empty."); 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 public static FontManager Current
@ -71,7 +72,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Get all system fonts. /// Get all system fonts.
/// </summary> /// </summary>
public IFontCollection SystemFonts => _systemFonts; public IFontCollection SystemFonts => _fontCollections[SystemFontsKey];
internal IFontManagerImpl PlatformImpl { get; } internal IFontManagerImpl PlatformImpl { get; }
@ -120,6 +121,11 @@ namespace Avalonia.Media
{ {
return true; return true;
} }
if (!fontFamily.FamilyNames.HasFallbacks)
{
return false;
}
} }
foreach (var familyName in fontFamily.FamilyNames) 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) public void AddFontCollection(IFontCollection fontCollection)
@ -139,13 +145,15 @@ namespace Avalonia.Media
if (!fontCollection.Key.IsFontCollection()) 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); fontCollection.Initialize(PlatformImpl);
} }

1
src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs

@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
{ {

35
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Platform; using Avalonia.Platform;
@ -8,8 +9,7 @@ namespace Avalonia.Media.Fonts
{ {
internal class SystemFontCollection : IFontCollection internal class SystemFontCollection : IFontCollection
{ {
private readonly Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = new();
new Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>>();
private readonly FontManager _fontManager; private readonly FontManager _fontManager;
private readonly string[] _familyNames; private readonly string[] _familyNames;
@ -20,7 +20,7 @@ namespace Avalonia.Media.Fonts
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames();
} }
public Uri Key => new Uri("fontCollection:SystemFonts"); public Uri Key => FontManager.SystemFontsKey;
public FontFamily this[int index] public FontFamily this[int index]
{ {
@ -44,23 +44,30 @@ namespace Avalonia.Media.Fonts
var key = new FontCollectionKey(style, weight, stretch); var key = new FontCollectionKey(style, weight, stretch);
if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
{ {
glyphTypefaces = new Dictionary<FontCollectionKey, IGlyphTypeface>(); if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
{
_glyphTypefaceCache.Add(familyName, glyphTypefaces); return true;
} }
else
if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) {
{ if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) &&
return true; glyphTypefaces.TryAdd(key, glyphTypeface))
{
return true;
}
}
} }
if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface)) if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
{ {
glyphTypefaces.Add(key, glyphTypeface); glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>();
return true; if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces))
{
return true;
}
} }
return false; return false;

11
tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs

@ -42,18 +42,17 @@ namespace Avalonia.Direct2D1.UnitTests.Media
} }
[Fact] [Fact]
public void Should_Throw_InvalidOperationException_For_Unknown_Font() public void Should_Create_Typeface_For_Unknown_Font()
{ {
using (AvaloniaLocator.EnterScope()) using (AvaloniaLocator.EnterScope())
{ {
Direct2D1Platform.Initialize(); Direct2D1Platform.Initialize();
var fontManager = FontManager.Current; var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface;
Assert.Throws<InvalidOperationException>(() => var defaultName = FontManager.Current.DefaultFontFamilyName;
{
var glyphTypeface =new Typeface(new FontFamily("Unknown")).GlyphTypeface; Assert.Equal(defaultName, glyphTypeface.FamilyName);
});
} }
} }

11
tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

@ -36,14 +36,13 @@ namespace Avalonia.Skia.UnitTests.Media
} }
[Fact] [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()))) using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
{ {
Assert.Throws<InvalidOperationException>(() => var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface;
{
var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; Assert.Equal(FontManager.Current.DefaultFontFamilyName, glyphTypeface.FamilyName);
});
} }
} }

Loading…
Cancel
Save