Browse Source

FontFamily.FamilyTypefaces support (#18113)

* Introduce FontFamily.FamilyTypefaces

* Detect FontWeight, FontStyle and FontStretch with the OS2Table
pull/18154/head
Benedikt Stebner 1 year 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>
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>

31
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);

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

14
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;
}
}
}

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 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))

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.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

Binary file not shown.

26
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;
}
}
}

29
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
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