Browse Source

FontFamily.FamilyTypefaces support (#18113)

* Introduce FontFamily.FamilyTypefaces

* Detect FontWeight, FontStyle and FontStretch with the OS2Table
pull/18154/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
dd2c92ce46
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/Avalonia.Base/Media/FontFamily.cs
  2. 31
      src/Avalonia.Base/Media/FontManager.cs
  3. 23
      src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
  4. 13
      src/Avalonia.Base/Media/Fonts/IFontCollection.cs
  5. 14
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  6. 23
      src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs
  7. 17
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  8. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf
  9. 26
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  10. 29
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  11. 16
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

5
src/Avalonia.Base/Media/FontFamily.cs

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

31
src/Avalonia.Base/Media/FontManager.cs

@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media.Fonts; using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
@ -339,6 +338,36 @@ namespace Avalonia.Media
return false; 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) private bool TryGetFontCollection(Uri source, [NotNullWhen(true)] out IFontCollection? fontCollection)
{ {
Debug.Assert(source.IsAbsoluteUri); Debug.Assert(source.IsAbsoluteUri);

23
src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs

@ -7,7 +7,7 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
{ {
public class EmbeddedFontCollection : FontCollectionBase public class EmbeddedFontCollection : FontCollectionBase, IFontCollection2
{ {
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1); private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);
@ -142,5 +142,26 @@ namespace Avalonia.Media.Fonts
glyphTypeface); 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
src/Avalonia.Base/Media/Fonts/IFontCollection.cs

@ -47,4 +47,17 @@ namespace Avalonia.Media.Fonts
bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface); 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);
}
} }

14
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@ -7,7 +7,7 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
{ {
internal class SystemFontCollection : FontCollectionBase internal class SystemFontCollection : FontCollectionBase, IFontCollection2
{ {
private readonly FontManager _fontManager; private readonly FontManager _fontManager;
private readonly List<string> _familyNames; private readonly List<string> _familyNames;
@ -158,5 +158,17 @@ namespace Avalonia.Media.Fonts
glyphTypeface); 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;
}
} }
} }

23
src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs

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

17
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.Globalization;
using System.IO; using System.IO;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Platform
@ -60,4 +62,17 @@ namespace Avalonia.Platform
/// </returns> /// </returns>
bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface); 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

Binary file not shown.

26
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -8,7 +9,7 @@ using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
internal class FontManagerImpl : IFontManagerImpl internal class FontManagerImpl : IFontManagerImpl, IFontManagerImpl2
{ {
private SKFontManager _skFontManager = SKFontManager.Default; private SKFontManager _skFontManager = SKFontManager.Default;
@ -119,5 +120,28 @@ namespace Avalonia.Skia
return false; 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;
}
} }
} }

29
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -90,13 +90,17 @@ namespace Avalonia.Skia
FontSimulations = fontSimulations; 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 ? Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : fontWeight;
FontStyle.Italic :
typeface.FontSlant.ToAvalonia();
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); _nameTable = NameTable.Load(this);
@ -265,6 +269,21 @@ namespace Avalonia.Skia
return Font.GetHorizontalGlyphAdvances(glyphIndices); 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) private Blob? GetTable(Face face, Tag tag)
{ {
var size = _typeface.GetTableSize(tag); var size = _typeface.GetTableSize(tag);

16
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);
}
}
}
} }
} }

Loading…
Cancel
Save