浏览代码

Merge pull request #4627 from Gillibald/fixes/TypefaceLeak

Simplify Typeface usage
danwalmsley 5 年之前
父节点
当前提交
19ff17f409

+ 26 - 2
Avalonia.sln

@@ -222,9 +222,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "sr
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
 EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -2040,6 +2040,30 @@ Global
 		{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU
 		{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.Build.0 = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 2 - 2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -282,7 +282,7 @@ namespace Avalonia.Controls.Presenters
             return new FormattedText
             {
                 Constraint = constraint,
-                Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
+                Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
                 FontSize = FontSize,
                 Text = text ?? string.Empty,
                 TextAlignment = TextAlignment,
@@ -499,7 +499,7 @@ namespace Avalonia.Controls.Presenters
                 return new FormattedText
                 {
                     Text = "X",
-                    Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
+                    Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
                     FontSize = FontSize,
                     TextAlignment = TextAlignment,
                     Constraint = availableSize,

+ 1 - 1
src/Avalonia.Controls/TextBlock.cs

@@ -452,7 +452,7 @@ namespace Avalonia.Controls
 
             return new TextLayout(
                 text ?? string.Empty,
-                FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
+                new Typeface(FontFamily, FontStyle, FontWeight),
                 FontSize,
                 Foreground,
                 TextAlignment,

+ 2 - 2
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@@ -155,9 +155,9 @@ namespace Avalonia.Headless
             return new List<string> { "Arial" };
         }
 
-        public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
+        public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
         {
-            fontKey = new FontKey("Arial", fontStyle, fontWeight);
+            typeface = new Typeface("Arial", fontStyle, fontWeight);
             return true;
         }
     }

+ 11 - 1
src/Avalonia.Visuals/ApiCompatBaseline.txt

@@ -1,5 +1,15 @@
 Compat issues with assembly Avalonia.Visuals:
+MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
+CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
+TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract.
+CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract.
+CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract.
+TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract.
 CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
 MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
 CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
-Total Issues: 3
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation.
+MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
+Total Issues: 13

+ 20 - 47
src/Avalonia.Visuals/Media/FontManager.cs

@@ -13,8 +13,8 @@ namespace Avalonia.Media
     /// </summary>
     public sealed class FontManager
     {
-        private readonly ConcurrentDictionary<FontKey, Typeface> _typefaceCache =
-            new ConcurrentDictionary<FontKey, Typeface>();
+        private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
+            new ConcurrentDictionary<Typeface, GlyphTypeface>();
         private readonly FontFamily _defaultFontFamily;
 
         public FontManager(IFontManagerImpl platformImpl)
@@ -76,79 +76,52 @@ namespace Avalonia.Media
             PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
 
         /// <summary>
-        ///     Returns a new typeface, or an existing one if a matching typeface exists.
+        ///     Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists.
         /// </summary>
-        /// <param name="fontFamily">The font family.</param>
-        /// <param name="fontStyle">The font style.</param>
-        /// <param name="fontWeight">The font weight.</param>
+        /// <param name="typeface">The typeface.</param>
         /// <returns>
-        ///     The typeface.
+        ///     The <see cref="GlyphTypeface"/>.
         /// </returns>
-        public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
-            FontWeight fontWeight = FontWeight.Normal)
+        public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
         {
             while (true)
             {
-                if (fontFamily.IsDefault)
+                if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface))
                 {
-                    fontFamily = _defaultFontFamily;
+                    return glyphTypeface;
                 }
 
-                var key = new FontKey(fontFamily.Name, fontStyle, fontWeight);
+                glyphTypeface = new GlyphTypeface(typeface);
 
-                if (_typefaceCache.TryGetValue(key, out var typeface))
+                if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
                 {
-                    return typeface;
+                    return glyphTypeface;
                 }
 
-                typeface = new Typeface(fontFamily, fontStyle, fontWeight);
-
-                if (_typefaceCache.TryAdd(key, typeface))
-                {
-                    return typeface;
-                }
-
-                if (fontFamily == _defaultFontFamily)
+                if (typeface.FontFamily == _defaultFontFamily)
                 {
                     return null;
                 }
 
-                fontFamily = _defaultFontFamily;
+                typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
             }
         }
 
         /// <summary>
-        ///     Tries to match a specified character to a typeface that supports specified font properties.
-        ///     Returns <c>null</c> if no fallback was found.
+        ///     Tries to match a specified character to a <see cref="Typeface"/> that supports specified font properties.
         /// </summary>
         /// <param name="codepoint">The codepoint to match against.</param>
         /// <param name="fontStyle">The font style.</param>
         /// <param name="fontWeight">The font weight.</param>
         /// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
         /// <param name="culture">The culture.</param>
+        /// <param name="typeface">The matching <see cref="Typeface"/>.</param>
         /// <returns>
-        ///     The matched typeface.
+        ///     <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
         /// </returns>
-        public Typeface MatchCharacter(int codepoint,
-            FontStyle fontStyle = FontStyle.Normal,
-            FontWeight fontWeight = FontWeight.Normal,
-            FontFamily fontFamily = null, CultureInfo culture = null)
-        {
-            foreach (var cachedTypeface in _typefaceCache.Values)
-            {
-                // First try to find a cached typeface by style and weight to avoid redundant glyph index lookup.
-                if (cachedTypeface.Style == fontStyle && cachedTypeface.Weight == fontWeight
-                                                      && cachedTypeface.GlyphTypeface.GetGlyph((uint)codepoint) != 0)
-                {
-                    return cachedTypeface;
-                }
-            }
-
-            var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
-                _typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
-                null;
-
-            return matchedTypeface;
-        }
+        public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
+            FontWeight fontWeight,
+            FontFamily fontFamily, CultureInfo culture, out Typeface typeface) =>
+            PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
     }
 }

+ 0 - 40
src/Avalonia.Visuals/Media/Fonts/FontKey.cs

@@ -1,40 +0,0 @@
-using System;
-
-namespace Avalonia.Media.Fonts
-{
-    public readonly struct FontKey : IEquatable<FontKey>
-    {
-        public FontKey(string familyName, FontStyle style, FontWeight weight)
-        {
-            FamilyName = familyName;
-            Style = style;
-            Weight = weight;
-        }
-
-        public string FamilyName { get; }
-        public FontStyle Style { get; }
-        public FontWeight Weight { get; }
-
-        public override int GetHashCode()
-        {
-            var hash = FamilyName.GetHashCode();
-
-            hash = hash * 31 + (int)Style;
-            hash = hash * 31 + (int)Weight;
-
-            return hash;
-        }
-
-        public override bool Equals(object other)
-        {
-            return other is FontKey key && Equals(key);
-        }
-
-        public bool Equals(FontKey other)
-        {
-            return FamilyName == other.FamilyName &&
-                Style == other.Style &&
-                   Weight == other.Weight;
-        }
-    }
-}

+ 4 - 3
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@@ -70,10 +70,11 @@ namespace Avalonia.Media.TextFormatting
             var codepoint = Codepoint.ReadAt(text, count, out _);
 
             //ToDo: Fix FontFamily fallback
-            currentTypeface =
-                FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
+            var matchFound =
+                FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
+                    defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface);
 
-            if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
+            if (matchFound && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
             {
                 //Fallback found
                 return new ShapeableTextCharacters(text.Take(count),

+ 7 - 24
src/Avalonia.Visuals/Media/Typeface.cs

@@ -8,17 +8,15 @@ namespace Avalonia.Media
     /// Represents a typeface.
     /// </summary>
     [DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")]
-    public class Typeface : IEquatable<Typeface>
+    public readonly struct Typeface : IEquatable<Typeface>
     {
-        private GlyphTypeface _glyphTypeface;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="Typeface"/> class.
         /// </summary>
         /// <param name="fontFamily">The font family.</param>
         /// <param name="style">The font style.</param>
         /// <param name="weight">The font weight.</param>
-        public Typeface([NotNull]FontFamily fontFamily,
+        public Typeface([NotNull] FontFamily fontFamily,
             FontStyle style = FontStyle.Normal,
             FontWeight weight = FontWeight.Normal)
         {
@@ -45,7 +43,7 @@ namespace Avalonia.Media
         {
         }
 
-        public static Typeface Default => FontManager.Current?.GetOrAddTypeface(FontFamily.Default);
+        public static Typeface Default { get; } = new Typeface(FontFamily.Default);
 
         /// <summary>
         /// Gets the font family.
@@ -68,7 +66,7 @@ namespace Avalonia.Media
         /// <value>
         /// The glyph typeface.
         /// </value>
-        public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this));
+        public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
 
         public static bool operator !=(Typeface a, Typeface b)
         {
@@ -77,32 +75,17 @@ namespace Avalonia.Media
 
         public static bool operator ==(Typeface a, Typeface b)
         {
-            if (ReferenceEquals(a, b))
-            {
-                return true;
-            }
-
-            return !(a is null) && a.Equals(b);
+            return  a.Equals(b);
         }
 
         public override bool Equals(object obj)
         {
-            if (obj is Typeface typeface)
-            {
-                return Equals(typeface);
-            }
-
-            return false;
+            return obj is Typeface typeface && Equals(typeface);
         }
 
         public bool Equals(Typeface other)
         {
-            if (other is null)
-            {
-                return false;
-            }
-
-            return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight;
+            return FontFamily == other.FontFamily && Style == other.Style && Weight == other.Weight;
         }
 
         public override int GetHashCode()

+ 2 - 3
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using System.Globalization;
 using Avalonia.Media;
-using Avalonia.Media.Fonts;
 
 namespace Avalonia.Platform
 {
@@ -26,13 +25,13 @@ namespace Avalonia.Platform
         /// <param name="fontWeight">The font weight.</param>
         /// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
         /// <param name="culture">The culture.</param>
-        /// <param name="fontKey">The matching font key.</param>
+        /// <param name="typeface">The matching typeface.</param>
         /// <returns>
         ///     <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
         /// </returns>
         bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
             FontWeight fontWeight,
-            FontFamily fontFamily, CultureInfo culture, out FontKey fontKey);
+            FontFamily fontFamily, CultureInfo culture, out Typeface typeface);
 
         /// <summary>
         ///     Creates a glyph typeface.

+ 1 - 1
src/Avalonia.Visuals/Rendering/RendererBase.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Rendering
             _useManualFpsCounting = useManualFpsCounting;
             _fpsText = new FormattedText
             {
-                Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default),
+                Typeface = new Typeface(FontFamily.Default),
                 FontSize = s_fontSize
             };
         }

+ 3 - 4
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.Globalization;
 using Avalonia.Media;
-using Avalonia.Media.Fonts;
 using Avalonia.Platform;
 using SkiaSharp;
 
@@ -31,7 +30,7 @@ namespace Avalonia.Skia
 
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
             FontWeight fontWeight,
-            FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
+            FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
         {
             SKFontStyle skFontStyle;
 
@@ -81,7 +80,7 @@ namespace Avalonia.Skia
                         continue;
                     }
 
-                    fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
+                    fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
 
                     return true;
                 }
@@ -92,7 +91,7 @@ namespace Avalonia.Skia
 
                 if (skTypeface != null)
                 {
-                    fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
+                    fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
 
                     return true;
                 }

+ 8 - 11
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@@ -2,31 +2,28 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Media;
-using Avalonia.Media.Fonts;
 using SkiaSharp;
 
 namespace Avalonia.Skia
 {
     internal class SKTypefaceCollection
     {
-        private readonly ConcurrentDictionary<FontKey, SKTypeface> _typefaces =
-            new ConcurrentDictionary<FontKey, SKTypeface>();
+        private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces =
+            new ConcurrentDictionary<Typeface, SKTypeface>();
 
-        public void AddTypeface(FontKey key, SKTypeface typeface)
+        public void AddTypeface(Typeface key, SKTypeface typeface)
         {
             _typefaces.TryAdd(key, typeface);
         }
 
         public SKTypeface Get(Typeface typeface)
         {
-            var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight);
-
-            return GetNearestMatch(_typefaces, key);
+            return GetNearestMatch(_typefaces, typeface);
         }
 
-        private static SKTypeface GetNearestMatch(IDictionary<FontKey, SKTypeface> typefaces, FontKey key)
+        private static SKTypeface GetNearestMatch(IDictionary<Typeface, SKTypeface> typefaces, Typeface key)
         {
-            if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface))
+            if (typefaces.TryGetValue(key, out var typeface))
             {
                 return typeface;
             }
@@ -42,7 +39,7 @@ namespace Avalonia.Skia
                 {
                     if (weight - j >= 100)
                     {
-                        if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
+                        if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
                         {
                             return typeface;
                         }
@@ -53,7 +50,7 @@ namespace Avalonia.Skia
                         continue;
                     }
 
-                    if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
+                    if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
                     {
                         return typeface;
                     }

+ 1 - 1
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@@ -56,7 +56,7 @@ namespace Avalonia.Skia
                     continue;
                 }
 
-                var key = new FontKey(fontFamily.Name, typeface.FontSlant.ToAvalonia(),
+                var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(),
                     (FontWeight)typeface.FontWeight);
 
                 typeFaceCollection.AddTypeface(key, typeface);

+ 3 - 4
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using System.Globalization;
 using Avalonia.Media;
-using Avalonia.Media.Fonts;
 using Avalonia.Platform;
 using SharpDX.DirectWrite;
 using FontFamily = Avalonia.Media.FontFamily;
@@ -34,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
 
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
             FontWeight fontWeight,
-            FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
+            FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
         {
             var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
 
@@ -51,12 +50,12 @@ namespace Avalonia.Direct2D1.Media
 
                 var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
 
-                fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight);
+                typeface = new Typeface(fontFamilyName, fontStyle, fontWeight);
 
                 return true;
             }
 
-            fontKey = default;
+            typeface = default;
 
             return false;
         }

+ 1 - 1
tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
         {
             var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
             return r.CreateFormattedText(text,
-                FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight),
+                new Typeface(fontFamily, fontStyle, fontWeight),
                 fontSize,
                 textAlignment,
                 wrapping,

+ 4 - 4
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@@ -39,7 +39,7 @@ namespace Avalonia.Skia.UnitTests.Media
         private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
 
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
-            CultureInfo culture, out FontKey fontKey)
+            CultureInfo culture, out Typeface typeface)
         {
             foreach (var customTypeface in _customTypefaces)
             {
@@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
                     continue;
                 }
 
-                fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight);
+                typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
 
                 return true;
             }
@@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
             var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
                 SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
 
-            fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
+            typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
 
             return true;
         }
@@ -73,13 +73,13 @@ namespace Avalonia.Skia.UnitTests.Media
                         skTypeface = typefaceCollection.Get(typeface);
                         break;
                     }
-
                 case "Noto Sans":
                     {
                         var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily);
                         skTypeface = typefaceCollection.Get(typeface);
                         break;
                     }
+                case FontFamily.DefaultFontFamilyName:
                 case "Noto Mono":
                     {
                         var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily);

+ 1 - 1
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@@ -130,7 +130,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
         {
             using (Start())
             {
-                const string text = "1234الدولي";
+                const string text = "ABCDالدولي";
 
                 var defaultProperties = new GenericTextRunProperties(Typeface.Default);
 

+ 2 - 3
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using System.Globalization;
 using Avalonia.Media;
-using Avalonia.Media.Fonts;
 using Avalonia.Platform;
 
 namespace Avalonia.UnitTests
@@ -26,9 +25,9 @@ namespace Avalonia.UnitTests
         }
 
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
-            CultureInfo culture, out FontKey fontKey)
+            CultureInfo culture, out Typeface fontKey)
         {
-            fontKey = default;
+            fontKey = new Typeface(_defaultFamilyName);
 
             return false;
         }

+ 4 - 3
tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

@@ -1,6 +1,5 @@
 using System;
 using Avalonia.Media;
-using Avalonia.Platform;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -15,9 +14,11 @@ namespace Avalonia.Visuals.UnitTests.Media
             {
                 var fontFamily = new FontFamily("MyFont");
 
-                var typeface = FontManager.Current.GetOrAddTypeface(fontFamily);
+                var typeface = new Typeface(fontFamily);
 
-                Assert.Same(typeface, FontManager.Current.GetOrAddTypeface(fontFamily));
+                var glyphTypeface = FontManager.Current.GetOrAddGlyphTypeface(typeface);
+
+                Assert.Same(glyphTypeface, FontManager.Current.GetOrAddGlyphTypeface(typeface));
             }
         }