Browse Source

FontFamily.FamilyTypefaces support (#18113)

* Introduce FontFamily.FamilyTypefaces

* Detect FontWeight, FontStyle and FontStretch with the OS2Table
Benedikt Stebner 8 months ago
parent
commit
dd2c92ce46

+ 5 - 0
src/Avalonia.Base/Media/FontFamily.cs

@@ -104,6 +104,11 @@ namespace Avalonia.Media
         /// <remarks>Key is only used for custom fonts.</remarks>
         public FontFamilyKey? Key { get; }
 
+        /// <summary>
+        /// Gets the typefaces for this font family.
+        /// </summary>
+        public IReadOnlyList<Typeface> FamilyTypefaces => FontManager.Current.GetFamilyTypefaces(this);
+
         /// <summary>
         /// Implicit conversion of string to FontFamily
         /// </summary>

+ 30 - 1
src/Avalonia.Base/Media/FontManager.cs

@@ -4,7 +4,6 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
-using System.Linq;
 using Avalonia.Logging;
 using Avalonia.Media.Fonts;
 using Avalonia.Platform;
@@ -339,6 +338,36 @@ namespace Avalonia.Media
             return false;
         }
 
+        internal IReadOnlyList<Typeface> GetFamilyTypefaces(FontFamily fontFamily)
+        {
+            var key = fontFamily.Key;
+
+            if (key == null)
+            {
+                if(SystemFonts is IFontCollection2 fontCollection2)
+                {
+                    if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
+                    {
+                        return familyTypefaces;
+                    }
+                }
+            }
+            else
+            {
+                var source = key.Source.EnsureAbsolute(key.BaseUri);
+
+                if (TryGetFontCollection(source, out var fontCollection) && fontCollection is IFontCollection2 fontCollection2)
+                {
+                    if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
+                    {
+                        return familyTypefaces;
+                    }
+                }
+            }
+
+            return [];
+        }
+
         private bool TryGetFontCollection(Uri source, [NotNullWhen(true)] out IFontCollection? fontCollection)
         {
             Debug.Assert(source.IsAbsoluteUri);

+ 22 - 1
src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs

@@ -7,7 +7,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Media.Fonts
 {
-    public class EmbeddedFontCollection : FontCollectionBase
+    public class EmbeddedFontCollection : FontCollectionBase, IFontCollection2
     {
         private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);
 
@@ -142,5 +142,26 @@ namespace Avalonia.Media.Fonts
                     glyphTypeface);
             }
         }
+
+        bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
+        {
+            familyTypefaces = null;
+
+            if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
+            {
+                var typefaces = new List<Typeface>(glyphTypefaces.Count);
+
+                foreach (var key in glyphTypefaces.Keys)
+                {
+                    typefaces.Add(new Typeface(new FontFamily(_key, familyName), key.Style, key.Weight, key.Stretch));
+                }
+
+                familyTypefaces = typefaces;
+
+                return true;
+            }
+
+            return false;
+        }
     }
 }

+ 13 - 0
src/Avalonia.Base/Media/Fonts/IFontCollection.cs

@@ -47,4 +47,17 @@ namespace Avalonia.Media.Fonts
         bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
             FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface);
     }
+
+    internal interface IFontCollection2 : IFontCollection
+    {
+        /// <summary>
+        /// Tries to get a list of typefaces for the specified family name.
+        /// </summary>
+        /// <param name="familyName">The family name.</param>
+        /// <param name="familyTypefaces">The list of typefaces.</param>
+        /// <returns>
+        ///     <c>True</c>, if the <see cref="IFontCollection2"/> could get the list of typefaces, <c>False</c> otherwise.
+        /// </returns>
+        bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces);
+    }
 }

+ 13 - 1
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@@ -7,7 +7,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Media.Fonts
 {
-    internal class SystemFontCollection : FontCollectionBase
+    internal class SystemFontCollection : FontCollectionBase, IFontCollection2
     {
         private readonly FontManager _fontManager;
         private readonly List<string> _familyNames;
@@ -158,5 +158,17 @@ namespace Avalonia.Media.Fonts
                     glyphTypeface);
             }
         }
+
+        bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
+        {
+            familyTypefaces = null;
+
+            if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2)
+            {
+                return fontManagerImpl2.TryGetFamilyTypefaces(familyName, out familyTypefaces);
+            }
+
+            return false;
+        }
     }
 }

+ 13 - 10
src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs

@@ -11,8 +11,7 @@ namespace Avalonia.Media.Fonts.Tables
     {
         internal const string TableName = "OS/2";
         internal static OpenTypeTag Tag = OpenTypeTag.Parse(TableName);
-
-        private readonly ushort styleType;
+  
         private readonly byte[] panose;
         private readonly short capHeight;
         private readonly short familyClass;
@@ -31,8 +30,6 @@ namespace Avalonia.Media.Fonts.Tables
         private readonly ushort lowerOpticalPointSize;
         private readonly ushort maxContext;
         private readonly ushort upperOpticalPointSize;
-        private readonly ushort weightClass;
-        private readonly ushort widthClass;
         private readonly short averageCharWidth;
 
         public OS2Table(
@@ -67,9 +64,9 @@ namespace Avalonia.Media.Fonts.Tables
             ushort winDescent)
         {
             this.averageCharWidth = averageCharWidth;
-            this.weightClass = weightClass;
-            this.widthClass = widthClass;
-            this.styleType = styleType;
+            WeightClass = weightClass;
+            WidthClass = widthClass;
+            StyleType = styleType;
             SubscriptXSize = subscriptXSize;
             SubscriptYSize = subscriptYSize;
             SubscriptXOffset = subscriptXOffset;
@@ -108,9 +105,9 @@ namespace Avalonia.Media.Fonts.Tables
             ushort maxContext)
             : this(
                 version0Table.averageCharWidth,
-                version0Table.weightClass,
-                version0Table.widthClass,
-                version0Table.styleType,
+                version0Table.WeightClass,
+                version0Table.WidthClass,
+                version0Table.StyleType,
                 version0Table.SubscriptXSize,
                 version0Table.SubscriptYSize,
                 version0Table.SubscriptXOffset,
@@ -249,6 +246,12 @@ namespace Avalonia.Media.Fonts.Tables
 
         public short SuperscriptYSize { get; }
 
+        public ushort StyleType { get; }
+
+        public ushort WeightClass { get; }
+
+        public ushort WidthClass { get; }
+
         public static OS2Table? Load(IGlyphTypeface glyphTypeface)
         {
             if (!glyphTypeface.TryGetTable(Tag, out var table))

+ 16 - 1
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@@ -1,7 +1,9 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.IO;
 using Avalonia.Media;
+using Avalonia.Media.Fonts;
 using Avalonia.Metadata;
 
 namespace Avalonia.Platform
@@ -60,4 +62,17 @@ namespace Avalonia.Platform
         /// </returns>
         bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
     }
+
+    internal interface IFontManagerImpl2 : IFontManagerImpl
+    {
+        /// <summary>
+        /// Tries to get a list of typefaces for the specified family name.
+        /// </summary>
+        /// <param name="familyName">The family name.</param>
+        /// <param name="familyTypefaces">The list of typefaces.</param>
+        /// <returns>
+        ///     <c>True</c>, if the <see cref="IFontManagerImpl"/> could get the list of typefaces, <c>False</c> otherwise.
+        /// </returns>
+        bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces);
+    }
 }

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf


+ 25 - 1
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.IO;
@@ -8,7 +9,7 @@ using SkiaSharp;
 
 namespace Avalonia.Skia
 {
-    internal class FontManagerImpl : IFontManagerImpl
+    internal class FontManagerImpl : IFontManagerImpl, IFontManagerImpl2
     {
         private SKFontManager _skFontManager = SKFontManager.Default;
 
@@ -119,5 +120,28 @@ namespace Avalonia.Skia
 
             return false;
         }
+
+        public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
+        {
+            familyTypefaces = null;
+
+            var set = _skFontManager.GetFontStyles(familyName);
+
+            if(set.Count == 0)
+            {
+                return false;
+            }
+
+            var typefaces = new List<Typeface>(set.Count);
+
+            foreach (var fontStyle in set)
+            {
+                typefaces.Add(new Typeface(familyName, fontStyle.Slant.ToAvalonia(), (FontWeight)fontStyle.Weight, (FontStretch)fontStyle.Width));
+            }
+
+            familyTypefaces = typefaces;
+
+            return true;
+        }
     }
 }

+ 24 - 5
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@@ -90,13 +90,17 @@ namespace Avalonia.Skia
 
             FontSimulations = fontSimulations;
 
-            Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : (FontWeight)typeface.FontWeight;
+            var fontWeight = _os2Table != null ? (FontWeight)_os2Table.WeightClass : FontWeight.Normal;
 
-            Style = (fontSimulations & FontSimulations.Oblique) != 0 ?
-                FontStyle.Italic :
-                typeface.FontSlant.ToAvalonia();
+            Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : fontWeight;
 
-            Stretch = (FontStretch)typeface.FontStyle.Width;
+            var style = _os2Table != null ? GetFontStyle(_os2Table.FontStyle) : FontStyle.Normal;
+
+            Style = (fontSimulations & FontSimulations.Oblique) != 0 ? FontStyle.Italic : style;
+
+            var stretch = _os2Table != null ? (FontStretch)_os2Table.WidthClass : FontStretch.Normal;
+
+            Stretch = stretch;
 
             _nameTable = NameTable.Load(this);
 
@@ -265,6 +269,21 @@ namespace Avalonia.Skia
             return Font.GetHorizontalGlyphAdvances(glyphIndices);
         }
 
+        private static FontStyle GetFontStyle(OS2Table.FontStyleSelection styleSelection)
+        {
+            if((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0)
+            {
+                return FontStyle.Italic;
+            }
+
+            if((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0)
+            {
+                return FontStyle.Oblique;
+            }
+
+            return FontStyle.Normal;
+        }
+
         private Blob? GetTable(Face face, Tag tag)
         {
             var size = _typeface.GetTableSize(tag);

+ 16 - 0
tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

@@ -364,5 +364,21 @@ namespace Avalonia.Skia.UnitTests.Media
                 }
             }
         }
+
+        [Fact]
+        public void Should_Get_FamilyTypefaces()
+        {
+            using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
+            {
+                using (AvaloniaLocator.EnterScope())
+                {
+                    FontManager.Current.AddFontCollection(new InterFontCollection());
+
+                    var familyTypefaces = FontManager.Current.GetFamilyTypefaces(new FontFamily("fonts:Inter#Inter"));
+
+                    Assert.Equal(6, familyTypefaces.Count);
+                }
+            }
+        }
     }
 }