Skip to content

Commit

Permalink
FontFamily.FamilyTypefaces support (#18113)
Browse files Browse the repository at this point in the history
* Introduce FontFamily.FamilyTypefaces

* Detect FontWeight, FontStyle and FontStretch with the OS2Table
  • Loading branch information
Gillibald authored Feb 6, 2025
1 parent 8bc116f commit dd2c92c
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 20 deletions.
5 changes: 5 additions & 0 deletions src/Avalonia.Base/Media/FontFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public FontFamily(Uri? baseUri, string name)
/// <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>
Expand Down
31 changes: 30 additions & 1 deletion src/Avalonia.Base/Media/FontManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Avalonia.Logging;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
Expand Down Expand Up @@ -339,6 +338,36 @@ internal static bool TryCreateSyntheticGlyphTypeface(IFontManagerImpl fontManage
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);
Expand Down
23 changes: 22 additions & 1 deletion src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Avalonia.Media.Fonts
{
public class EmbeddedFontCollection : FontCollectionBase
public class EmbeddedFontCollection : FontCollectionBase, IFontCollection2
{
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);

Expand Down Expand Up @@ -142,5 +142,26 @@ void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypefac
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 changes: 13 additions & 0 deletions src/Avalonia.Base/Media/Fonts/IFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
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 changes: 13 additions & 1 deletion src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Avalonia.Media.Fonts
{
internal class SystemFontCollection : FontCollectionBase
internal class SystemFontCollection : FontCollectionBase, IFontCollection2
{
private readonly FontManager _fontManager;
private readonly List<string> _familyNames;
Expand Down Expand Up @@ -158,5 +158,17 @@ void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypefac
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 changes: 13 additions & 10 deletions src/Avalonia.Base/Media/Fonts/Tables/OS2Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ internal sealed class OS2Table
{
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;
Expand All @@ -31,8 +30,6 @@ internal sealed class OS2Table
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(
Expand Down Expand Up @@ -67,9 +64,9 @@ public OS2Table(
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;
Expand Down Expand Up @@ -108,9 +105,9 @@ public OS2Table(
ushort maxContext)
: this(
version0Table.averageCharWidth,
version0Table.weightClass,
version0Table.widthClass,
version0Table.styleType,
version0Table.WeightClass,
version0Table.WidthClass,
version0Table.StyleType,
version0Table.SubscriptXSize,
version0Table.SubscriptYSize,
version0Table.SubscriptXOffset,
Expand Down Expand Up @@ -249,6 +246,12 @@ internal enum FontStyleSelection : ushort

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))
Expand Down
17 changes: 16 additions & 1 deletion src/Avalonia.Base/Platform/IFontManagerImpl.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,4 +62,17 @@ bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weigh
/// </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);
}
}
Binary file not shown.
26 changes: 25 additions & 1 deletion src/Skia/Avalonia.Skia/FontManagerImpl.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
Expand All @@ -8,7 +9,7 @@

namespace Avalonia.Skia
{
internal class FontManagerImpl : IFontManagerImpl
internal class FontManagerImpl : IFontManagerImpl, IFontManagerImpl2
{
private SKFontManager _skFontManager = SKFontManager.Default;

Expand Down Expand Up @@ -119,5 +120,28 @@ public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulation

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 changes: 24 additions & 5 deletions src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,17 @@ public GlyphTypefaceImpl(SKTypeface typeface, 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 ?
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);

Expand Down Expand Up @@ -265,6 +269,21 @@ public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
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);
Expand Down
16 changes: 16 additions & 0 deletions tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,5 +364,21 @@ public void Should_Map_FontFamily()
}
}
}

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

0 comments on commit dd2c92c

Please sign in to comment.