diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs index 80365aaf4d..e1365bb87d 100644 --- a/src/Avalonia.Base/Media/FontFamily.cs +++ b/src/Avalonia.Base/Media/FontFamily.cs @@ -104,6 +104,11 @@ namespace Avalonia.Media /// Key is only used for custom fonts. public FontFamilyKey? Key { get; } + /// + /// Gets the typefaces for this font family. + /// + public IReadOnlyList FamilyTypefaces => FontManager.Current.GetFamilyTypefaces(this); + /// /// Implicit conversion of string to FontFamily /// diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 28f2e6b234..c51dbbfee3 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/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 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); diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs index 789122988f..f06e5d1562 100644 --- a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs +++ b/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 _fontFamilies = new List(1); @@ -142,5 +142,26 @@ namespace Avalonia.Media.Fonts glyphTypeface); } } + + bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces) + { + familyTypefaces = null; + + if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) + { + var typefaces = new List(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; + } } } diff --git a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs index 1a30f168f1..a2fbdb69b0 100644 --- a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs +++ b/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 + { + /// + /// Tries to get a list of typefaces for the specified family name. + /// + /// The family name. + /// The list of typefaces. + /// + /// True, if the could get the list of typefaces, False otherwise. + /// + bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces); + } } diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index be5c2a2588..d59b1d1954 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/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 _familyNames; @@ -158,5 +158,17 @@ namespace Avalonia.Media.Fonts glyphTypeface); } } + + bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces) + { + familyTypefaces = null; + + if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2) + { + return fontManagerImpl2.TryGetFamilyTypefaces(familyName, out familyTypefaces); + } + + return false; + } } } diff --git a/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs b/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs index 73d80edd7d..9dc41ef083 100644 --- a/src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs +++ b/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)) diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index f868bfaaed..ce9f85a5e2 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/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 /// bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface); } + + internal interface IFontManagerImpl2 : IFontManagerImpl + { + /// + /// Tries to get a list of typefaces for the specified family name. + /// + /// The family name. + /// The list of typefaces. + /// + /// True, if the could get the list of typefaces, False otherwise. + /// + bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces); + } } diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf deleted file mode 100644 index 64aee30a4e..0000000000 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf and /dev/null differ diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 1865594aa5..477348b59a 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/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? familyTypefaces) + { + familyTypefaces = null; + + var set = _skFontManager.GetFontStyles(familyName); + + if(set.Count == 0) + { + return false; + } + + var typefaces = new List(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; + } } } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index 0b9a48deab..9170393034 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/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); diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 6114b662d9..ac6da9c661 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/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); + } + } + } } }