diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index 2e8d8e415d..4425147098 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -30,13 +30,15 @@ namespace Avalonia.Media
_fontFallbacks = options?.FontFallbacks;
- DefaultFontFamilyName = options?.DefaultFamilyName ?? PlatformImpl.GetDefaultFontFamilyName();
+ var defaultFontFamilyName = options?.DefaultFamilyName ?? PlatformImpl.GetDefaultFontFamilyName();
- if (string.IsNullOrEmpty(DefaultFontFamilyName))
+ if (string.IsNullOrEmpty(defaultFontFamilyName))
{
throw new InvalidOperationException("Default font family name can't be null or empty.");
}
+ DefaultFontFamily = new FontFamily(defaultFontFamilyName);
+
AddFontCollection(new SystemFontCollection(this));
}
@@ -65,9 +67,9 @@ namespace Avalonia.Media
}
///
- /// Gets the system's default font family's name.
+ /// Gets the system's default font family.
///
- public string DefaultFontFamilyName
+ public FontFamily DefaultFontFamily
{
get;
}
@@ -93,6 +95,11 @@ namespace Avalonia.Media
var fontFamily = typeface.FontFamily;
+ if(typeface.FontFamily.Name == FontFamily.DefaultFontFamilyName)
+ {
+ return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
+ }
+
if (fontFamily.Key is FontFamilyKey key)
{
var source = key.Source;
@@ -131,15 +138,21 @@ namespace Avalonia.Media
}
}
- foreach (var familyName in fontFamily.FamilyNames)
+ for (var i = 0; i < fontFamily.FamilyNames.Count; i++)
{
+ var familyName = fontFamily.FamilyNames[i];
+
if (SystemFonts.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
{
- return true;
+ if (!fontFamily.FamilyNames.HasFallbacks || glyphTypeface.FamilyName != DefaultFontFamily.Name)
+ {
+ return true;
+ }
}
}
- return TryGetGlyphTypeface(new Typeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
+ //Nothing was found so use the default
+ return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
}
///
@@ -199,16 +212,37 @@ namespace Avalonia.Media
{
foreach (var fallback in _fontFallbacks)
{
- typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
+ if (fallback.UnicodeRange.IsInRange(codepoint))
+ {
+ typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
+
+ if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ {
+ return true;
+ }
+ }
+ }
+ }
- if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ //Try to match against fallbacks first
+ if (fontFamily != null && fontFamily.FamilyNames.HasFallbacks)
+ {
+ for (int i = 1; i < fontFamily.FamilyNames.Count; i++)
+ {
+ var familyName = fontFamily.FamilyNames[i];
+
+ foreach (var fontCollection in _fontCollections.Values)
{
- return true;
+ if (fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
+ {
+ return true;
+ };
}
}
}
- return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, fontFamily, culture, out typeface);
+ //Try to find a match with the system font manager
+ return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out typeface);
}
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
index f2fb490592..4d4751db02 100644
--- a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
@@ -8,10 +8,8 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts
{
- public class EmbeddedFontCollection : IFontCollection
+ public class EmbeddedFontCollection : FontCollectionBase
{
- private readonly ConcurrentDictionary> _glyphTypefaceCache = new();
-
private readonly List _fontFamilies = new List(1);
private readonly Uri _key;
@@ -25,13 +23,13 @@ namespace Avalonia.Media.Fonts
_source = source;
}
- public Uri Key => _key;
+ public override Uri Key => _key;
- public FontFamily this[int index] => _fontFamilies[index];
+ public override FontFamily this[int index] => _fontFamilies[index];
- public int Count => _fontFamilies.Count;
+ public override int Count => _fontFamilies.Count;
- public void Initialize(IFontManagerImpl fontManager)
+ public override void Initialize(IFontManagerImpl fontManager)
{
var assetLoader = AvaloniaLocator.Current.GetRequiredService();
@@ -45,7 +43,7 @@ namespace Avalonia.Media.Fonts
{
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
{
- glyphTypefaces = new ConcurrentDictionary();
+ glyphTypefaces = new ConcurrentDictionary();
if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces))
{
@@ -63,27 +61,8 @@ namespace Avalonia.Media.Fonts
}
}
- public void Dispose()
- {
- foreach (var fontFamily in _fontFamilies)
- {
- if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out var glyphTypefaces))
- {
- foreach (var glyphTypeface in glyphTypefaces.Values)
- {
- glyphTypeface.Dispose();
- }
- }
- }
- GC.SuppressFinalize(this);
- }
-
- public IEnumerator GetEnumerator() => _fontFamilies.GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
var key = new FontCollectionKey(style, weight, stretch);
@@ -116,175 +95,6 @@ namespace Avalonia.Media.Fonts
return false;
}
- private static bool TryGetNearestMatch(
- ConcurrentDictionary glyphTypefaces,
- FontCollectionKey key,
- [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
- {
- if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
- {
- return true;
- }
-
- if (key.Style != FontStyle.Normal)
- {
- key = key with { Style = FontStyle.Normal };
- }
-
- if (key.Stretch != FontStretch.Normal)
- {
- if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
- {
- return true;
- }
-
- if (key.Weight != FontWeight.Normal)
- {
- if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface))
- {
- return true;
- }
- }
-
- key = key with { Stretch = FontStretch.Normal };
- }
-
- if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface))
- {
- return true;
- }
-
- if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
- {
- return true;
- }
-
- //Take the first glyph typeface we can find.
- foreach (var typeface in glyphTypefaces.Values)
- {
- glyphTypeface = typeface;
-
- return true;
- }
-
- return false;
- }
-
- private static bool TryFindStretchFallback(
- ConcurrentDictionary glyphTypefaces,
- FontCollectionKey key,
- [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
- {
- glyphTypeface = null;
-
- var stretch = (int)key.Stretch;
-
- if (stretch < 5)
- {
- for (var i = 0; stretch + i < 9; i++)
- {
- if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface))
- {
- return true;
- }
- }
- }
- else
- {
- for (var i = 0; stretch - i > 1; i++)
- {
- if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static bool TryFindWeightFallback(
- ConcurrentDictionary glyphTypefaces,
- FontCollectionKey key,
- [NotNullWhen(true)] out IGlyphTypeface? typeface)
- {
- typeface = null;
- var weight = (int)key.Weight;
-
- //If the target weight given is between 400 and 500 inclusive
- if (weight >= 400 && weight <= 500)
- {
- //Look for available weights between the target and 500, in ascending order.
- for (var i = 0; weight + i <= 500; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
- {
- return true;
- }
- }
-
- //If no match is found, look for available weights less than the target, in descending order.
- for (var i = 0; weight - i >= 100; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
- {
- return true;
- }
- }
-
- //If no match is found, look for available weights greater than 500, in ascending order.
- for (var i = 0; weight + i <= 900; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
- {
- return true;
- }
- }
- }
-
- //If a weight less than 400 is given, look for available weights less than the target, in descending order.
- if (weight < 400)
- {
- for (var i = 0; weight - i >= 100; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
- {
- return true;
- }
- }
-
- //If no match is found, look for available weights less than the target, in descending order.
- for (var i = 0; weight + i <= 900; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
- {
- return true;
- }
- }
- }
-
- //If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
- if (weight > 500)
- {
- for (var i = 0; weight + i <= 900; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
- {
- return true;
- }
- }
-
- //If no match is found, look for available weights less than the target, in descending order.
- for (var i = 0; weight - i >= 100; i += 50)
- {
- if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
- {
- return true;
- }
- }
- }
-
- return false;
- }
+ public override IEnumerator GetEnumerator() => _fontFamilies.GetEnumerator();
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
new file mode 100644
index 0000000000..713b3dafcd
--- /dev/null
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Fonts
+{
+ public abstract class FontCollectionBase : IFontCollection
+ {
+ protected readonly ConcurrentDictionary> _glyphTypefaceCache = new();
+
+ public abstract Uri Key { get; }
+
+ public abstract int Count { get; }
+
+ public abstract FontFamily this[int index] { get; }
+
+ public abstract bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch,
+ [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
+
+ public bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch,
+ string? familyName, CultureInfo? culture, out Typeface match)
+ {
+ match = default;
+
+ if (string.IsNullOrEmpty(familyName))
+ {
+ foreach (var typefaces in _glyphTypefaceCache.Values)
+ {
+ if (TryGetNearestMatch(typefaces, new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch }, out var glyphTypeface))
+ {
+ if (glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ {
+ match = new Typeface(glyphTypeface.FamilyName, style, weight, stretch);
+
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (TryGetGlyphTypeface(familyName, style, weight, stretch, out var glyphTypeface))
+ {
+ if (glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+ {
+ match = new Typeface(familyName, style, weight, stretch);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public abstract void Initialize(IFontManagerImpl fontManager);
+
+ public abstract IEnumerator GetEnumerator();
+
+ void IDisposable.Dispose()
+ {
+ foreach (var glyphTypefaces in _glyphTypefaceCache.Values)
+ {
+ foreach (var pair in glyphTypefaces)
+ {
+ pair.Value?.Dispose();
+ }
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ internal static bool TryGetNearestMatch(
+ ConcurrentDictionary glyphTypefaces,
+ FontCollectionKey key,
+ [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ {
+ if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+
+ if (key.Style != FontStyle.Normal)
+ {
+ key = key with { Style = FontStyle.Normal };
+ }
+
+ if (key.Stretch != FontStretch.Normal)
+ {
+ if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+ {
+ return true;
+ }
+
+ if (key.Weight != FontWeight.Normal)
+ {
+ if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface))
+ {
+ return true;
+ }
+ }
+
+ key = key with { Stretch = FontStretch.Normal };
+ }
+
+ if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface))
+ {
+ return true;
+ }
+
+ if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+ {
+ return true;
+ }
+
+ //Take the first glyph typeface we can find.
+ foreach (var typeface in glyphTypefaces.Values)
+ {
+ if(typeface != null)
+ {
+ glyphTypeface = typeface;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal static bool TryFindStretchFallback(
+ ConcurrentDictionary glyphTypefaces,
+ FontCollectionKey key,
+ [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ {
+ glyphTypeface = null;
+
+ var stretch = (int)key.Stretch;
+
+ if (stretch < 5)
+ {
+ for (var i = 0; stretch + i < 9; i++)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ for (var i = 0; stretch - i > 1; i++)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ internal static bool TryFindWeightFallback(
+ ConcurrentDictionary glyphTypefaces,
+ FontCollectionKey key,
+ [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+ {
+ glyphTypeface = null;
+ var weight = (int)key.Weight;
+
+ //If the target weight given is between 400 and 500 inclusive
+ if (weight >= 400 && weight <= 500)
+ {
+ //Look for available weights between the target and 500, in ascending order.
+ for (var i = 0; weight + i <= 500; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+
+ //If no match is found, look for available weights less than the target, in descending order.
+ for (var i = 0; weight - i >= 100; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+
+ //If no match is found, look for available weights greater than 500, in ascending order.
+ for (var i = 0; weight + i <= 900; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+ }
+
+ //If a weight less than 400 is given, look for available weights less than the target, in descending order.
+ if (weight < 400)
+ {
+ for (var i = 0; weight - i >= 100; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+
+ //If no match is found, look for available weights less than the target, in descending order.
+ for (var i = 0; weight + i <= 900; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+ }
+
+ //If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
+ if (weight > 500)
+ {
+ for (var i = 0; weight + i <= 900; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+
+ //If no match is found, look for available weights less than the target, in descending order.
+ for (var i = 0; weight - i >= 100; i += 50)
+ {
+ if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out glyphTypeface) && glyphTypeface != null)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
index 814230bcf3..1a30f168f1 100644
--- a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using Avalonia.Platform;
namespace Avalonia.Media.Fonts
@@ -29,5 +30,21 @@ namespace Avalonia.Media.Fonts
/// Returns true if a glyph typface can be found; otherwise, false
bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
+
+ ///
+ /// Tries to match a specified character to a that supports specified font properties.
+ ///
+ /// The codepoint to match against.
+ /// The font style.
+ /// The font weight.
+ /// The font stretch.
+ /// The family name. This is optional and used for fallback lookup.
+ /// The culture.
+ /// The matching .
+ ///
+ /// True, if the could match the character to specified parameters, False otherwise.
+ ///
+ bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
+ FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface);
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
index fd332c6ebe..2f2948cb3e 100644
--- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -7,10 +6,8 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts
{
- internal class SystemFontCollection : IFontCollection
+ internal class SystemFontCollection : FontCollectionBase
{
- private readonly ConcurrentDictionary> _glyphTypefaceCache = new();
-
private readonly FontManager _fontManager;
private readonly string[] _familyNames;
@@ -20,9 +17,9 @@ namespace Avalonia.Media.Fonts
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames();
}
- public Uri Key => FontManager.SystemFontsKey;
+ public override Uri Key => FontManager.SystemFontsKey;
- public FontFamily this[int index]
+ public override FontFamily this[int index]
{
get
{
@@ -32,76 +29,41 @@ namespace Avalonia.Media.Fonts
}
}
- public int Count => _familyNames.Length;
+ public override int Count => _familyNames.Length;
- public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
- if (familyName == FontFamily.DefaultFontFamilyName)
- {
- familyName = _fontManager.DefaultFontFamilyName;
- }
+ glyphTypeface = null;
var key = new FontCollectionKey(style, weight, stretch);
- if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
- {
- 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;
- }
- }
- }
+ var glyphTypefaces = _glyphTypefaceCache.GetOrAdd(familyName, (key) => new ConcurrentDictionary());
- if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
+ if (!glyphTypefaces.TryGetValue(key, out glyphTypeface))
{
- glyphTypefaces = new ConcurrentDictionary();
+ _fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface);
- if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces))
+ if (!glyphTypefaces.TryAdd(key, glyphTypeface))
{
- return true;
+ return false;
}
}
- return false;
+ return glyphTypeface != null;
}
- public void Initialize(IFontManagerImpl fontManager)
+ public override void Initialize(IFontManagerImpl fontManager)
{
//We initialize the system font collection during construction.
}
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public IEnumerator GetEnumerator()
+ public override IEnumerator GetEnumerator()
{
foreach (var familyName in _familyNames)
{
yield return new FontFamily(familyName);
}
}
-
- void IDisposable.Dispose()
- {
- foreach (var glyphTypefaces in _glyphTypefaceCache.Values)
- {
- foreach (var pair in glyphTypefaces)
- {
- pair.Value.Dispose();
- }
- }
-
- GC.SuppressFinalize(this);
- }
}
}
diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs
index 116f7cd6e2..222e7196bb 100644
--- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs
+++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs
@@ -27,15 +27,13 @@ namespace Avalonia.Platform
/// The font style.
/// The font weight.
/// The font stretch.
- /// The font family. This is optional and used for fallback lookup.
/// The culture.
/// The matching typeface.
///
/// True, if the could match the character to specified parameters, False otherwise.
///
bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
- FontWeight fontWeight, FontStretch fontStretch,
- FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface);
+ FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface);
///
/// Tries to get a glyph typeface for specified parameters.
diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
index ee4cd5af98..aa400ab3e6 100644
--- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
@@ -179,8 +179,7 @@ namespace Avalonia.Headless
return true;
}
- public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
- FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, out Typeface typeface)
{
typeface = new Typeface("Arial", fontStyle, fontWeight, fontStretch);
return true;
diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
index 29e5687423..a97a198621 100644
--- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs
+++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
@@ -30,8 +30,7 @@ namespace Avalonia.Skia
[ThreadStatic] private static string[]? t_languageTagBuffer;
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
- FontWeight fontWeight, FontStretch fontStretch,
- FontFamily? fontFamily, CultureInfo? culture, out Typeface fontKey)
+ FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface fontKey)
{
SKFontStyle skFontStyle;
@@ -60,35 +59,13 @@ namespace Avalonia.Skia
t_languageTagBuffer[0] = culture.TwoLetterISOLanguageName;
t_languageTagBuffer[1] = culture.ThreeLetterISOLanguageName;
- if (fontFamily is not null && fontFamily.FamilyNames.HasFallbacks)
- {
- var familyNames = fontFamily.FamilyNames;
-
- for (var i = 1; i < familyNames.Count; i++)
- {
- var skTypeface =
- _skFontManager.MatchCharacter(familyNames[i], skFontStyle, t_languageTagBuffer, codepoint);
-
- if (skTypeface == null)
- {
- continue;
- }
+ var skTypeface = _skFontManager.MatchCharacter(null, skFontStyle, t_languageTagBuffer, codepoint);
- fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight, fontStretch);
-
- return true;
- }
- }
- else
+ if (skTypeface != null)
{
- var skTypeface = _skFontManager.MatchCharacter(null, skFontStyle, t_languageTagBuffer, codepoint);
+ fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight, fontStretch);
- if (skTypeface != null)
- {
- fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight, fontStretch);
-
- return true;
- }
+ return true;
}
fontKey = default;
@@ -96,7 +73,7 @@ namespace Avalonia.Skia
return false;
}
- public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+ public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@@ -111,12 +88,6 @@ namespace Avalonia.Skia
return false;
}
- //MatchFamily can return a font other than we requested so we have to verify we got the expected.
- if (!skTypeface.FamilyName.ToLower(CultureInfo.InvariantCulture).Equals(familyName.ToLower(CultureInfo.InvariantCulture), StringComparison.Ordinal))
- {
- return false;
- }
-
var fontSimulations = FontSimulations.None;
if ((int)weight >= 600 && !skTypeface.IsBold)
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
index ec2f6385da..85bf2b6c4c 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
@@ -33,8 +33,7 @@ namespace Avalonia.Direct2D1.Media
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
- FontWeight fontWeight, FontStretch fontStretch,
- FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
+ FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, out Typeface typeface)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
diff --git a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs
index 89e609eb10..3ccec872d2 100644
--- a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Base.UnitTests.Media
{
AvaloniaLocator.CurrentMutable.Bind().ToConstant(options);
- Assert.Equal("MyFont", FontManager.Current.DefaultFontFamilyName);
+ Assert.Equal("MyFont", FontManager.Current.DefaultFontFamily.Name);
}
}
diff --git a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
index 14e48b3b6c..81ac9030bf 100644
--- a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
+++ b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
@@ -50,7 +50,7 @@ namespace Avalonia.Direct2D1.UnitTests.Media
var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface;
- var defaultName = FontManager.Current.DefaultFontFamilyName;
+ var defaultName = FontManager.Current.DefaultFontFamily.Name;
Assert.Equal(defaultName, glyphTypeface.FamilyName);
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
index e18344580b..617ab952fa 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
@@ -12,24 +12,17 @@ namespace Avalonia.Skia.UnitTests.Media
{
public class CustomFontManagerImpl : IFontManagerImpl
{
- private readonly Typeface[] _customTypefaces;
private readonly string _defaultFamilyName;
-
- private readonly Typeface _defaultTypeface =
- new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
- private readonly Typeface _arabicTypeface =
- new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Arabic");
- private readonly Typeface _hebrewTypeface =
- new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Hebrew");
- private readonly Typeface _italicTypeface =
- new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic);
- private readonly Typeface _emojiTypeface =
- new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Twitter Color Emoji");
+ private readonly IFontCollection _customFonts;
+ private bool _isInitialized;
public CustomFontManagerImpl()
{
- _customTypefaces = new[] { _emojiTypeface, _italicTypeface, _arabicTypeface, _hebrewTypeface, _defaultTypeface };
- _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
+ _defaultFamilyName = "Noto Mono";
+
+ var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests");
+
+ _customFonts = new EmbeddedFontCollection(source, source);
}
public string GetDefaultFontFamilyName()
@@ -39,28 +32,32 @@ namespace Avalonia.Skia.UnitTests.Media
public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
- return _customTypefaces.Select(x => x.FontFamily.Name).ToArray();
+ if (!_isInitialized)
+ {
+ _customFonts.Initialize(this);
+
+ _isInitialized = true;
+ }
+
+ return _customFonts.Select(x=> x.Name).ToArray();
}
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
- FontFamily fontFamily,
CultureInfo culture, out Typeface typeface)
{
- foreach (var customTypeface in _customTypefaces)
+ if (!_isInitialized)
{
- if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
- {
- continue;
- }
-
- typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight);
+ _customFonts.Initialize(this);
+ }
+ if(_customFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, null, culture, out typeface))
+ {
return true;
}
- var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
+ var fallback = SKFontManager.Default.MatchCharacter(null, (SKFontStyleWeight)fontWeight,
(SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
@@ -68,123 +65,21 @@ namespace Avalonia.Skia.UnitTests.Media
return true;
}
- public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
- {
- SKTypeface skTypeface;
-
- Uri source = null;
-
- switch (typeface.FontFamily.Name)
- {
- case "Twitter Color Emoji":
- {
- source = _emojiTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans":
- {
- source = _italicTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans Arabic":
- {
- source = _arabicTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans Hebrew":
- {
- source = _hebrewTypeface.FontFamily.Key.Source;
- break;
- }
- case FontFamily.DefaultFontFamilyName:
- case "Noto Mono":
- {
- source = _defaultTypeface.FontFamily.Key.Source;
- break;
- }
- default:
- {
-
- break;
- }
- }
-
- if (source is null)
- {
- skTypeface = SKTypeface.FromFamilyName(typeface.FontFamily.Name,
- (SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
- }
- else
- {
- var assetLoader = AvaloniaLocator.Current.GetRequiredService();
-
- var assetUri = FontFamilyLoader.LoadFontAssets(source).First();
-
- var stream = assetLoader.Open(assetUri);
-
- skTypeface = SKTypeface.FromStream(stream);
- }
-
- return new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
- }
-
public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
{
- SKTypeface skTypeface;
-
- Uri source = null;
-
- switch (familyName)
+ if (!_isInitialized)
{
- case "Twitter Color Emoji":
- {
- source = _emojiTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans":
- {
- source = _italicTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans Arabic":
- {
- source = _arabicTypeface.FontFamily.Key.Source;
- break;
- }
- case "Noto Sans Hebrew":
- {
- source = _hebrewTypeface.FontFamily.Key.Source;
- break;
- }
- case FontFamily.DefaultFontFamilyName:
- case "Noto Mono":
- {
- source = _defaultTypeface.FontFamily.Key.Source;
- break;
- }
- default:
- {
-
- break;
- }
+ _customFonts.Initialize(this);
}
- if (source is null)
+ if (_customFonts.TryGetGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
{
- skTypeface = SKTypeface.FromFamilyName(familyName,
- (SKFontStyleWeight)weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)style);
+ return true;
}
- else
- {
- var assetLoader = AvaloniaLocator.Current.GetRequiredService();
-
- var assetUri = FontFamilyLoader.LoadFontAssets(source).First();
- var stream = assetLoader.Open(assetUri);
-
- skTypeface = SKTypeface.FromStream(stream);
- }
+ var skTypeface = SKTypeface.FromFamilyName(familyName,
+ (SKFontStyleWeight)weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)style);
glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
index c15cbfb845..8ca16bd873 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var fontManager = FontManager.Current;
- var glyphTypeface = new Typeface(new FontFamily("A, B, " + fontManager.DefaultFontFamilyName)).GlyphTypeface;
+ var glyphTypeface = new Typeface(new FontFamily("A, B, " + FontFamily.DefaultFontFamilyName)).GlyphTypeface;
Assert.Equal(SKTypeface.Default.FamilyName, glyphTypeface.FamilyName);
}
@@ -41,7 +41,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface;
- Assert.Equal(FontManager.Current.DefaultFontFamilyName, glyphTypeface.FamilyName);
+ Assert.Equal(FontManager.Current.DefaultFontFamily.Name, glyphTypeface.FamilyName);
}
}
@@ -87,6 +87,24 @@ namespace Avalonia.Skia.UnitTests.Media
}
}
+ [Fact]
+ public void Should_Only_Try_To_Create_GlyphTypeface_Once()
+ {
+ var fontManagerImpl = new MockFontManagerImpl();
+
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl)))
+ {
+ Assert.True(FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out _));
+
+ for (int i = 0;i < 10; i++)
+ {
+ FontManager.Current.TryGetGlyphTypeface(new Typeface("Unknown"), out _);
+ }
+
+ Assert.Equal(fontManagerImpl.TryCreateGlyphTypefaceCount, 2);
+ }
+ }
+
[Fact]
public void Should_Load_Embedded_DefaultFontFamily()
{
@@ -96,7 +114,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions { DefaultFamilyName = s_fontUri });
- var result = FontManager.Current.TryGetGlyphTypeface(new Typeface(FontFamily.DefaultFontFamilyName), out var glyphTypeface);
+ var result = FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out var glyphTypeface);
Assert.True(result);
diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
index a819cbd5e3..38897d28c5 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
@@ -35,8 +35,8 @@ namespace Avalonia.UnitTests
return _customTypefaces.Select(x => x.FontFamily!.Name).ToArray();
}
- public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
- FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
+ FontStretch fontStretch, CultureInfo culture, out Typeface fontKey)
{
foreach (var customTypeface in _customTypefaces)
{
diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
index eda4544877..16423884b3 100644
--- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
+++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
@@ -15,6 +15,8 @@ namespace Avalonia.UnitTests
_defaultFamilyName = defaultFamilyName;
}
+ public int TryCreateGlyphTypefaceCount { get; private set; }
+
public string GetDefaultFontFamilyName()
{
return _defaultFamilyName;
@@ -26,7 +28,7 @@ namespace Avalonia.UnitTests
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
- FontStretch fontStretch, FontFamily fontFamily,
+ FontStretch fontStretch,
CultureInfo culture, out Typeface fontKey)
{
fontKey = new Typeface(_defaultFamilyName);
@@ -34,14 +36,24 @@ namespace Avalonia.UnitTests
return false;
}
- public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
+ 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 MockGlyphTypeface();
return true;
}
- public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
+ public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new MockGlyphTypeface();