diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index c51dbbfee3..77118b06a9 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -109,9 +109,9 @@ namespace Avalonia.Media
var familyName = fontFamily.FamilyNames[i];
- if(_fontFamilyMappings != null && _fontFamilyMappings.TryGetValue(familyName, out var mappedFontFamily))
+ if (_fontFamilyMappings != null && _fontFamilyMappings.TryGetValue(familyName, out var mappedFontFamily))
{
- if(mappedFontFamily.Key != null)
+ if (mappedFontFamily.Key != null)
{
key = mappedFontFamily.Key;
}
@@ -123,6 +123,11 @@ namespace Avalonia.Media
familyName = mappedFontFamily.FamilyNames.PrimaryFamilyName;
}
+ if (familyName == FontFamily.DefaultFontFamilyName)
+ {
+ return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
+ }
+
if (TryGetGlyphTypefaceByKeyAndName(typeface, key, familyName, out glyphTypeface) &&
glyphTypeface.FamilyName.Contains(familyName))
{
@@ -274,6 +279,11 @@ namespace Avalonia.Media
var familyName = fontFamily.FamilyNames[i];
var source = key.Source.EnsureAbsolute(key.BaseUri);
+ if(familyName == FontFamily.DefaultFontFamilyName)
+ {
+ familyName = DefaultFontFamily.Name;
+ }
+
if (TryGetFontCollection(source, out var fontCollection) &&
fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
{
@@ -286,58 +296,6 @@ namespace Avalonia.Media
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out typeface);
}
- ///
- /// Tries to create a synthetic glyph typefacefor specified source glyph typeface and font properties.
- ///
- /// The font manager implementation.
- /// The source glyph typeface.
- /// The requested font style.
- /// The requested font weight.
- /// The created synthetic glyph typeface.
- ///
- /// True, if the could create a synthetic glyph typeface, False otherwise.
- ///
- internal static bool TryCreateSyntheticGlyphTypeface(IFontManagerImpl fontManager, IGlyphTypeface glyphTypeface, FontStyle style, FontWeight weight,
- [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
- {
- if (fontManager == null)
- {
- syntheticGlyphTypeface = null;
-
- return false;
- }
-
- if (glyphTypeface is IGlyphTypeface2 glyphTypeface2)
- {
- var fontSimulations = FontSimulations.None;
-
- if (style != FontStyle.Normal && glyphTypeface2.Style != style)
- {
- fontSimulations |= FontSimulations.Oblique;
- }
-
- if ((int)weight >= 600 && glyphTypeface2.Weight < weight)
- {
- fontSimulations |= FontSimulations.Bold;
- }
-
- if (fontSimulations != FontSimulations.None && glyphTypeface2.TryGetStream(out var stream))
- {
- using (stream)
- {
- fontManager.TryCreateGlyphTypeface(stream, fontSimulations,
- out syntheticGlyphTypeface);
-
- return syntheticGlyphTypeface != null;
- }
- }
- }
-
- syntheticGlyphTypeface = null;
-
- return false;
- }
-
internal IReadOnlyList GetFamilyTypefaces(FontFamily fontFamily)
{
var key = fontFamily.Key;
diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
index f06e5d1562..06f9e82858 100644
--- a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
@@ -71,14 +71,16 @@ namespace Avalonia.Media.Fonts
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
{
- if(_fontManager != null && FontManager.TryCreateSyntheticGlyphTypeface(_fontManager, glyphTypeface, style, weight, out var syntheticGlyphTypeface))
+ var matchedKey = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+
+ if(matchedKey != key)
{
- glyphTypeface = syntheticGlyphTypeface;
+ if (TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out var syntheticGlyphTypeface))
+ {
+ glyphTypeface = syntheticGlyphTypeface;
+ }
}
- //Make sure we cache the found match
- glyphTypefaces.TryAdd(key, glyphTypeface);
-
return true;
}
}
@@ -143,7 +145,7 @@ namespace Avalonia.Media.Fonts
}
}
- bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces)
+ public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces)
{
familyTypefaces = null;
diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
index 4f8376d267..a022fcfe4d 100644
--- a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
+++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Media.Fonts
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,
+ public virtual bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch,
string? familyName, CultureInfo? culture, out Typeface match)
{
match = default;
@@ -59,6 +59,88 @@ namespace Avalonia.Media.Fonts
return false;
}
+ public virtual bool TryCreateSyntheticGlyphTypeface(
+ IGlyphTypeface glyphTypeface,
+ FontStyle style,
+ FontWeight weight,
+ FontStretch stretch,
+ [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
+ {
+ syntheticGlyphTypeface = null;
+
+ //Source family should be present in the cache.
+ if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
+ {
+ return false;
+ }
+
+ var fontManager = FontManager.Current.PlatformImpl;
+
+ var key = new FontCollectionKey(style, weight, stretch);
+
+ var currentKey =
+ new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
+
+ if (currentKey == key)
+ {
+ return false;
+ }
+
+ if (glyphTypeface is not IGlyphTypeface2 glyphTypeface2)
+ {
+ return false;
+ }
+
+ var fontSimulations = FontSimulations.None;
+
+ if (style != FontStyle.Normal && glyphTypeface2.Style != style)
+ {
+ fontSimulations |= FontSimulations.Oblique;
+ }
+
+ if ((int)weight >= 600 && glyphTypeface2.Weight < weight)
+ {
+ fontSimulations |= FontSimulations.Bold;
+ }
+
+ if (fontSimulations != FontSimulations.None && glyphTypeface2.TryGetStream(out var stream))
+ {
+ using (stream)
+ {
+ if (fontManager.TryCreateGlyphTypeface(stream, fontSimulations, out syntheticGlyphTypeface))
+ {
+ //Add the TypographicFamilyName to the cache
+ if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
+ {
+ AddGlyphTypefaceByFamilyName(glyphTypeface2.TypographicFamilyName, syntheticGlyphTypeface);
+ }
+
+ foreach (var kvp in glyphTypeface2.FamilyNames)
+ {
+ AddGlyphTypefaceByFamilyName(kvp.Value, syntheticGlyphTypeface);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+
+ void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
+ {
+ var typefaces = _glyphTypefaceCache.GetOrAdd(familyName,
+ x =>
+ {
+ return new ConcurrentDictionary();
+ });
+
+ typefaces.TryAdd(key, glyphTypeface);
+ }
+ }
+
public abstract void Initialize(IFontManagerImpl fontManager);
public abstract IEnumerator GetEnumerator();
diff --git a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
index a2fbdb69b0..2a30f0abd8 100644
--- a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs
@@ -59,5 +59,16 @@ namespace Avalonia.Media.Fonts
/// True, if the could get the list of typefaces, False otherwise.
///
bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces);
+
+ ///
+ /// Try to get a synthetic glyph typeface for given parameters.
+ ///
+ /// The glyph typeface we try to synthesize.
+ /// The font style.
+ /// The font weight.
+ /// The font stretch.
+ ///
+ /// Returns true if a synthetic glyph typface can be created; otherwise, false
+ bool TryCreateSyntheticGlyphTypeface(IGlyphTypeface glyphTypeface, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface);
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
index d59b1d1954..3a98a30b90 100644
--- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
+++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
@@ -91,9 +91,11 @@ namespace Avalonia.Media.Fonts
}
//Try to create a synthetic glyph typeface
- if (FontManager.TryCreateSyntheticGlyphTypeface(_fontManager.PlatformImpl, glyphTypeface, style, weight, out var syntheticGlyphTypeface))
+ if (TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out var syntheticGlyphTypeface))
{
glyphTypeface = syntheticGlyphTypeface;
+
+ return true;
}
}
@@ -159,7 +161,7 @@ namespace Avalonia.Media.Fonts
}
}
- bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces)
+ public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces)
{
familyTypefaces = null;
diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
index 893bb7ec95..4595df43a9 100644
--- a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
@@ -245,7 +245,11 @@ namespace Avalonia.Headless
public virtual bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
- glyphTypeface = new HeadlessGlyphTypefaceImpl(FontFamily.DefaultFontFamilyName, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal);
+ glyphTypeface = new HeadlessGlyphTypefaceImpl(
+ FontFamily.DefaultFontFamilyName,
+ fontSimulations.HasFlag(FontSimulations.Oblique) ? FontStyle.Italic : FontStyle.Normal,
+ fontSimulations.HasFlag(FontSimulations.Bold) ? FontWeight.Bold : FontWeight.Normal,
+ FontStretch.Normal);
TryCreateGlyphTypefaceCount++;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs
index fbf71795ba..a1ba9d92f8 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.UnitTests;
@@ -18,6 +19,7 @@ namespace Avalonia.Skia.UnitTests.Media
private const string s_manrope = "resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Manrope";
+
[InlineData(FontWeight.SemiLight, FontStyle.Normal)]
[InlineData(FontWeight.Bold, FontStyle.Italic)]
[InlineData(FontWeight.Heavy, FontStyle.Oblique)]
@@ -28,7 +30,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var source = new Uri(s_notoMono, UriKind.Absolute);
- var fontCollection = new EmbeddedFontCollection(source, source);
+ var fontCollection = new TestEmbeddedFontCollection(source, source);
fontCollection.Initialize(new CustomFontManagerImpl());
@@ -47,7 +49,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var source = new Uri(s_notoMono, UriKind.Absolute);
- var fontCollection = new EmbeddedFontCollection(source, source);
+ var fontCollection = new TestEmbeddedFontCollection(source, source);
fontCollection.Initialize(new CustomFontManagerImpl());
@@ -62,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#T", UriKind.Absolute);
- var fontCollection = new EmbeddedFontCollection(source, source);
+ var fontCollection = new TestEmbeddedFontCollection(source, source);
fontCollection.Initialize(new CustomFontManagerImpl());
@@ -79,7 +81,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var source = new Uri(s_manrope, UriKind.Absolute);
- var fontCollection = new EmbeddedFontCollection(source, source);
+ var fontCollection = new TestEmbeddedFontCollection(source, source);
fontCollection.Initialize(new CustomFontManagerImpl());
@@ -102,7 +104,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var source = new Uri(s_manrope, UriKind.Absolute);
- var fontCollection = new TestEmbeddedFontCollection(source, source);
+ var fontCollection = new TestEmbeddedFontCollection(source, source, true);
fontCollection.Initialize(new CustomFontManagerImpl());
@@ -120,11 +122,31 @@ namespace Avalonia.Skia.UnitTests.Media
private class TestEmbeddedFontCollection : EmbeddedFontCollection
{
- public TestEmbeddedFontCollection(Uri key, Uri source) : base(key, source)
+ private bool _createSyntheticTypefaces;
+
+ public TestEmbeddedFontCollection(Uri key, Uri source, bool createSyntheticTypefaces = false) : base(key, source)
{
+ _createSyntheticTypefaces = createSyntheticTypefaces;
}
public IDictionary> GlyphTypefaceCache => _glyphTypefaceCache;
+
+ public override bool TryCreateSyntheticGlyphTypeface(
+ IGlyphTypeface glyphTypeface,
+ FontStyle style,
+ FontWeight weight,
+ FontStretch stretch,
+ [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
+ {
+ if (!_createSyntheticTypefaces)
+ {
+ syntheticGlyphTypeface = null;
+
+ return false;
+ }
+
+ return base.TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out syntheticGlyphTypeface);
+ }
}
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs
index 7d63b9a79e..2dc8d7e772 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs
@@ -1,7 +1,10 @@
#nullable enable
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.UnitTests;
@@ -11,6 +14,9 @@ namespace Avalonia.Skia.UnitTests.Media
{
public class FontCollectionTests
{
+ private const string NotoMono =
+ "resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests";
+
[InlineData("Hello World 6", "Hello World 6", FontStyle.Normal, FontWeight.Normal)]
[InlineData("Hello World Italic", "Hello World", FontStyle.Italic, FontWeight.Normal)]
[InlineData("Hello World Italic Bold", "Hello World", FontStyle.Italic, FontWeight.Bold)]
@@ -41,8 +47,6 @@ namespace Avalonia.Skia.UnitTests.Media
Assert.True(fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface));
- Assert.True(glyphTypeface.FontSimulations == FontSimulations.Bold);
-
Assert.True(fontCollection.GlyphTypefaceCache.TryGetValue("Arial", out var glyphTypefaces));
Assert.Equal(2, glyphTypefaces.Count);
@@ -64,5 +68,108 @@ namespace Avalonia.Skia.UnitTests.Media
public IDictionary> GlyphTypefaceCache => _glyphTypefaceCache;
}
+
+ [Fact]
+ public void Should_Use_Fallback()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var source = new Uri(NotoMono, UriKind.Absolute);
+
+ var fallback = new FontFallback { FontFamily = new FontFamily("Arial"), UnicodeRange = new UnicodeRange('A', 'A') };
+
+ var fontCollection = new CustomizableFontCollection(source, source, new[] { fallback });
+
+ fontCollection.Initialize(new CustomFontManagerImpl());
+
+ Assert.True(fontCollection.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match));
+
+ Assert.Equal("Arial", match.FontFamily.Name);
+ }
+ }
+
+ [Fact]
+ public void Should_Ignore_FontFamily()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var source = new Uri(NotoMono + "#Noto Mono", UriKind.Absolute);
+
+ var ignorable = new FontFamily(new Uri(NotoMono, UriKind.Absolute), "Noto Mono");
+
+ var typeface = new Typeface(ignorable);
+
+ var fontCollection = new CustomizableFontCollection(source, source, null, new[] { ignorable });
+
+ fontCollection.Initialize(new CustomFontManagerImpl());
+
+ Assert.False(fontCollection.TryCreateSyntheticGlyphTypeface(
+ typeface.GlyphTypeface,
+ FontStyle.Italic,
+ FontWeight.DemiBold,
+ FontStretch.Normal,
+ out var syntheticGlyphTypeface));
+ }
+ }
+
+ private class CustomizableFontCollection : EmbeddedFontCollection
+ {
+ private readonly IReadOnlyList? _fallbacks;
+ private readonly IReadOnlyList? _ignorables;
+
+ public CustomizableFontCollection(Uri key, Uri source, IReadOnlyList? fallbacks = null, IReadOnlyList? ignorables = null) : base(key, source)
+ {
+ _fallbacks = fallbacks;
+ _ignorables = ignorables;
+ }
+
+ public override bool TryMatchCharacter(
+ int codepoint,
+ FontStyle style,
+ FontWeight weight,
+ FontStretch stretch,
+ string? familyName,
+ CultureInfo? culture,
+ out Typeface match)
+ {
+ if(_fallbacks is not null)
+ {
+ foreach (var fallback in _fallbacks)
+ {
+ if (fallback.UnicodeRange.IsInRange(codepoint))
+ {
+ match = new Typeface(fallback.FontFamily, style, weight, stretch);
+
+ return true;
+ }
+ }
+ }
+
+ return base.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match);
+ }
+
+ public override bool TryCreateSyntheticGlyphTypeface(
+ IGlyphTypeface glyphTypeface,
+ FontStyle style,
+ FontWeight weight,
+ FontStretch stretch,
+ [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
+ {
+ syntheticGlyphTypeface = null;
+
+ if(_ignorables is not null)
+ {
+ foreach (var ignorable in _ignorables)
+ {
+ if (glyphTypeface.FamilyName == ignorable.Name || glyphTypeface is IGlyphTypeface2 glyphTypeface2 && glyphTypeface2.TypographicFamilyName == ignorable.Name)
+ {
+ return false;
+ }
+ }
+ }
+
+ return base.TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out syntheticGlyphTypeface);
+ }
+ }
}
}