Skip to content

Commit dd2c92c

Browse files
authored
FontFamily.FamilyTypefaces support (#18113)
* Introduce FontFamily.FamilyTypefaces * Detect FontWeight, FontStyle and FontStretch with the OS2Table
1 parent 8bc116f commit dd2c92c

File tree

11 files changed

+177
-20
lines changed

11 files changed

+177
-20
lines changed

src/Avalonia.Base/Media/FontFamily.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public FontFamily(Uri? baseUri, string name)
104104
/// <remarks>Key is only used for custom fonts.</remarks>
105105
public FontFamilyKey? Key { get; }
106106

107+
/// <summary>
108+
/// Gets the typefaces for this font family.
109+
/// </summary>
110+
public IReadOnlyList<Typeface> FamilyTypefaces => FontManager.Current.GetFamilyTypefaces(this);
111+
107112
/// <summary>
108113
/// Implicit conversion of string to FontFamily
109114
/// </summary>

src/Avalonia.Base/Media/FontManager.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Diagnostics;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Globalization;
7-
using System.Linq;
87
using Avalonia.Logging;
98
using Avalonia.Media.Fonts;
109
using Avalonia.Platform;
@@ -339,6 +338,36 @@ internal static bool TryCreateSyntheticGlyphTypeface(IFontManagerImpl fontManage
339338
return false;
340339
}
341340

341+
internal IReadOnlyList<Typeface> GetFamilyTypefaces(FontFamily fontFamily)
342+
{
343+
var key = fontFamily.Key;
344+
345+
if (key == null)
346+
{
347+
if(SystemFonts is IFontCollection2 fontCollection2)
348+
{
349+
if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
350+
{
351+
return familyTypefaces;
352+
}
353+
}
354+
}
355+
else
356+
{
357+
var source = key.Source.EnsureAbsolute(key.BaseUri);
358+
359+
if (TryGetFontCollection(source, out var fontCollection) && fontCollection is IFontCollection2 fontCollection2)
360+
{
361+
if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
362+
{
363+
return familyTypefaces;
364+
}
365+
}
366+
}
367+
368+
return [];
369+
}
370+
342371
private bool TryGetFontCollection(Uri source, [NotNullWhen(true)] out IFontCollection? fontCollection)
343372
{
344373
Debug.Assert(source.IsAbsoluteUri);

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Avalonia.Media.Fonts
99
{
10-
public class EmbeddedFontCollection : FontCollectionBase
10+
public class EmbeddedFontCollection : FontCollectionBase, IFontCollection2
1111
{
1212
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);
1313

@@ -142,5 +142,26 @@ void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypefac
142142
glyphTypeface);
143143
}
144144
}
145+
146+
bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
147+
{
148+
familyTypefaces = null;
149+
150+
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
151+
{
152+
var typefaces = new List<Typeface>(glyphTypefaces.Count);
153+
154+
foreach (var key in glyphTypefaces.Keys)
155+
{
156+
typefaces.Add(new Typeface(new FontFamily(_key, familyName), key.Style, key.Weight, key.Stretch));
157+
}
158+
159+
familyTypefaces = typefaces;
160+
161+
return true;
162+
}
163+
164+
return false;
165+
}
145166
}
146167
}

src/Avalonia.Base/Media/Fonts/IFontCollection.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,17 @@ bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
4747
bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
4848
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface);
4949
}
50+
51+
internal interface IFontCollection2 : IFontCollection
52+
{
53+
/// <summary>
54+
/// Tries to get a list of typefaces for the specified family name.
55+
/// </summary>
56+
/// <param name="familyName">The family name.</param>
57+
/// <param name="familyTypefaces">The list of typefaces.</param>
58+
/// <returns>
59+
/// <c>True</c>, if the <see cref="IFontCollection2"/> could get the list of typefaces, <c>False</c> otherwise.
60+
/// </returns>
61+
bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces);
62+
}
5063
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Avalonia.Media.Fonts
99
{
10-
internal class SystemFontCollection : FontCollectionBase
10+
internal class SystemFontCollection : FontCollectionBase, IFontCollection2
1111
{
1212
private readonly FontManager _fontManager;
1313
private readonly List<string> _familyNames;
@@ -158,5 +158,17 @@ void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypefac
158158
glyphTypeface);
159159
}
160160
}
161+
162+
bool IFontCollection2.TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
163+
{
164+
familyTypefaces = null;
165+
166+
if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2)
167+
{
168+
return fontManagerImpl2.TryGetFamilyTypefaces(familyName, out familyTypefaces);
169+
}
170+
171+
return false;
172+
}
161173
}
162174
}

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ internal sealed class OS2Table
1111
{
1212
internal const string TableName = "OS/2";
1313
internal static OpenTypeTag Tag = OpenTypeTag.Parse(TableName);
14-
15-
private readonly ushort styleType;
14+
1615
private readonly byte[] panose;
1716
private readonly short capHeight;
1817
private readonly short familyClass;
@@ -31,8 +30,6 @@ internal sealed class OS2Table
3130
private readonly ushort lowerOpticalPointSize;
3231
private readonly ushort maxContext;
3332
private readonly ushort upperOpticalPointSize;
34-
private readonly ushort weightClass;
35-
private readonly ushort widthClass;
3633
private readonly short averageCharWidth;
3734

3835
public OS2Table(
@@ -67,9 +64,9 @@ public OS2Table(
6764
ushort winDescent)
6865
{
6966
this.averageCharWidth = averageCharWidth;
70-
this.weightClass = weightClass;
71-
this.widthClass = widthClass;
72-
this.styleType = styleType;
67+
WeightClass = weightClass;
68+
WidthClass = widthClass;
69+
StyleType = styleType;
7370
SubscriptXSize = subscriptXSize;
7471
SubscriptYSize = subscriptYSize;
7572
SubscriptXOffset = subscriptXOffset;
@@ -108,9 +105,9 @@ public OS2Table(
108105
ushort maxContext)
109106
: this(
110107
version0Table.averageCharWidth,
111-
version0Table.weightClass,
112-
version0Table.widthClass,
113-
version0Table.styleType,
108+
version0Table.WeightClass,
109+
version0Table.WidthClass,
110+
version0Table.StyleType,
114111
version0Table.SubscriptXSize,
115112
version0Table.SubscriptYSize,
116113
version0Table.SubscriptXOffset,
@@ -249,6 +246,12 @@ internal enum FontStyleSelection : ushort
249246

250247
public short SuperscriptYSize { get; }
251248

249+
public ushort StyleType { get; }
250+
251+
public ushort WeightClass { get; }
252+
253+
public ushort WidthClass { get; }
254+
252255
public static OS2Table? Load(IGlyphTypeface glyphTypeface)
253256
{
254257
if (!glyphTypeface.TryGetTable(Tag, out var table))

src/Avalonia.Base/Platform/IFontManagerImpl.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Globalization;
34
using System.IO;
45
using Avalonia.Media;
6+
using Avalonia.Media.Fonts;
57
using Avalonia.Metadata;
68

79
namespace Avalonia.Platform
@@ -60,4 +62,17 @@ bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weigh
6062
/// </returns>
6163
bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
6264
}
65+
66+
internal interface IFontManagerImpl2 : IFontManagerImpl
67+
{
68+
/// <summary>
69+
/// Tries to get a list of typefaces for the specified family name.
70+
/// </summary>
71+
/// <param name="familyName">The family name.</param>
72+
/// <param name="familyTypefaces">The list of typefaces.</param>
73+
/// <returns>
74+
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could get the list of typefaces, <c>False</c> otherwise.
75+
/// </returns>
76+
bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces);
77+
}
6378
}
Binary file not shown.

src/Skia/Avalonia.Skia/FontManagerImpl.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Globalization;
45
using System.IO;
@@ -8,7 +9,7 @@
89

910
namespace Avalonia.Skia
1011
{
11-
internal class FontManagerImpl : IFontManagerImpl
12+
internal class FontManagerImpl : IFontManagerImpl, IFontManagerImpl2
1213
{
1314
private SKFontManager _skFontManager = SKFontManager.Default;
1415

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

120121
return false;
121122
}
123+
124+
public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
125+
{
126+
familyTypefaces = null;
127+
128+
var set = _skFontManager.GetFontStyles(familyName);
129+
130+
if(set.Count == 0)
131+
{
132+
return false;
133+
}
134+
135+
var typefaces = new List<Typeface>(set.Count);
136+
137+
foreach (var fontStyle in set)
138+
{
139+
typefaces.Add(new Typeface(familyName, fontStyle.Slant.ToAvalonia(), (FontWeight)fontStyle.Weight, (FontStretch)fontStyle.Width));
140+
}
141+
142+
familyTypefaces = typefaces;
143+
144+
return true;
145+
}
122146
}
123147
}

src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,17 @@ public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations)
9090

9191
FontSimulations = fontSimulations;
9292

93-
Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : (FontWeight)typeface.FontWeight;
93+
var fontWeight = _os2Table != null ? (FontWeight)_os2Table.WeightClass : FontWeight.Normal;
9494

95-
Style = (fontSimulations & FontSimulations.Oblique) != 0 ?
96-
FontStyle.Italic :
97-
typeface.FontSlant.ToAvalonia();
95+
Weight = (fontSimulations & FontSimulations.Bold) != 0 ? FontWeight.Bold : fontWeight;
9896

99-
Stretch = (FontStretch)typeface.FontStyle.Width;
97+
var style = _os2Table != null ? GetFontStyle(_os2Table.FontStyle) : FontStyle.Normal;
98+
99+
Style = (fontSimulations & FontSimulations.Oblique) != 0 ? FontStyle.Italic : style;
100+
101+
var stretch = _os2Table != null ? (FontStretch)_os2Table.WidthClass : FontStretch.Normal;
102+
103+
Stretch = stretch;
100104

101105
_nameTable = NameTable.Load(this);
102106

@@ -265,6 +269,21 @@ public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
265269
return Font.GetHorizontalGlyphAdvances(glyphIndices);
266270
}
267271

272+
private static FontStyle GetFontStyle(OS2Table.FontStyleSelection styleSelection)
273+
{
274+
if((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0)
275+
{
276+
return FontStyle.Italic;
277+
}
278+
279+
if((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0)
280+
{
281+
return FontStyle.Oblique;
282+
}
283+
284+
return FontStyle.Normal;
285+
}
286+
268287
private Blob? GetTable(Face face, Tag tag)
269288
{
270289
var size = _typeface.GetTableSize(tag);

tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,5 +364,21 @@ public void Should_Map_FontFamily()
364364
}
365365
}
366366
}
367+
368+
[Fact]
369+
public void Should_Get_FamilyTypefaces()
370+
{
371+
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
372+
{
373+
using (AvaloniaLocator.EnterScope())
374+
{
375+
FontManager.Current.AddFontCollection(new InterFontCollection());
376+
377+
var familyTypefaces = FontManager.Current.GetFamilyTypefaces(new FontFamily("fonts:Inter#Inter"));
378+
379+
Assert.Equal(6, familyTypefaces.Count);
380+
}
381+
}
382+
}
367383
}
368384
}

0 commit comments

Comments
 (0)