Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to normalize family names for embedded fonts #15703

Merged
merged 7 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/Avalonia.Base/Media/FontManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Avalonia.Logging;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using Avalonia.Utilities;
Expand Down Expand Up @@ -91,6 +93,8 @@ public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyp

var fontFamily = typeface.FontFamily;

typeface = FontCollectionBase.GetImplicitTypeface(typeface);

if (typeface.FontFamily.Name == FontFamily.DefaultFontFamilyName)
{
return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
Expand Down Expand Up @@ -144,13 +148,20 @@ private bool TryGetGlyphTypefaceByKeyAndName(Typeface typeface, FontFamilyKey ke
{
var source = key.Source.EnsureAbsolute(key.BaseUri);

if (TryGetFontCollection(source, out var fontCollection) &&
fontCollection.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
if (TryGetFontCollection(source, out var fontCollection))
{
if (glyphTypeface.FamilyName.Contains(familyName))
if (fontCollection.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch,
out glyphTypeface))
{
return true;
}

var logger = Logger.TryGet(LogEventLevel.Debug, "FontManager");

logger?.Log(this,
$"Font family '{familyName}' could not be found. Present font families: [{string.Join(",", fontCollection)}]");

return false;
}

glyphTypeface = null;
Expand Down
16 changes: 11 additions & 5 deletions src/Avalonia.Base/Media/FontWeight.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma warning disable CA1069
namespace Avalonia.Media
{
/// <summary>
Expand All @@ -22,7 +23,7 @@ public enum FontWeight
/// <summary>
/// Specifies an "ultra light" font weight.
/// </summary>
UltraLight = 200,
UltraLight = ExtraLight,

/// <summary>
/// Specifies a "light" font weight.
Expand All @@ -42,7 +43,7 @@ public enum FontWeight
/// <summary>
/// Specifies a "regular" font weight.
/// </summary>
Regular = 400,
Regular = Normal,

/// <summary>
/// Specifies a "medium" font weight.
Expand All @@ -52,7 +53,7 @@ public enum FontWeight
/// <summary>
/// Specifies a "demi-bold" font weight.
/// </summary>
DemiBold = 600,
DemiBold = SemiBold,

/// <summary>
/// Specifies a "semi-bold" font weight.
Expand All @@ -72,7 +73,7 @@ public enum FontWeight
/// <summary>
/// Specifies an "ultra bold" font weight.
/// </summary>
UltraBold = 800,
UltraBold = ExtraBold,

/// <summary>
/// Specifies a "black" font weight.
Expand All @@ -82,7 +83,12 @@ public enum FontWeight
/// <summary>
/// Specifies a "heavy" font weight.
/// </summary>
Heavy = 900,
Heavy = Black,

/// <summary>
/// Specifies a "solid" font weight.
/// </summary>
Solid = Black,

/// <summary>
/// Specifies an "extra black" font weight.
Expand Down
3 changes: 3 additions & 0 deletions src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, Fon
}
}

//Replace known typographic names
familyName = NormalizeFamilyName(familyName);

//Try to find a partially matching font
for (var i = 0; i < Count; i++)
{
Expand Down
110 changes: 110 additions & 0 deletions src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
using Avalonia.Platform;
using Avalonia.Utilities;

namespace Avalonia.Media.Fonts
{
Expand Down Expand Up @@ -255,5 +257,113 @@ internal static bool TryFindWeightFallback(

return false;
}

private static readonly List<string> s_knownNames = ["Solid", "Regular", "Bold", "Black", "Normal", "Thin"];

internal static string NormalizeFamilyName(string familyName)
{
//Return early if no separator is present.
if (!familyName.Contains(' '))
{
return familyName;
}

foreach (var name in s_knownNames)
{
familyName = Regex.Replace(familyName, name, "", RegexOptions.IgnoreCase);
}

return familyName.Trim();
}

internal static Typeface GetImplicitTypeface(Typeface typeface)
{
var familyName = typeface.FontFamily.FamilyNames.PrimaryFamilyName;

//Return early if no separator is present.
if (!familyName.Contains(" "))
{
return typeface;
}

var style = typeface.Style;
var weight = typeface.Weight;
var stretch = typeface.Stretch;

if(TryGetStyle(familyName, out var foundStyle))
{
style = foundStyle;
}

if(TryGetWeight(familyName, out var foundWeight))
{
weight = foundWeight;
}

if(TryGetStretch(familyName, out var foundStretch))
{
stretch = foundStretch;
}

return new Typeface(typeface.FontFamily, style, weight, stretch);

}

internal static bool TryGetWeight(string familyName, out FontWeight weight)
{
weight = FontWeight.Normal;

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();

while (tokenizer.TryReadString(out var weightString))
{
if (Enum.TryParse(weightString, true, out weight))
{
return true;
}
}

return false;
}

internal static bool TryGetStyle(string familyName, out FontStyle style)
{
style = FontStyle.Normal;

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();

while (tokenizer.TryReadString(out var styleString))
{
if (Enum.TryParse(styleString, true, out style))
{
return true;
}
}

return false;
}

internal static bool TryGetStretch(string familyName, out FontStretch stretch)
{
stretch = FontStretch.Normal;

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();

while (tokenizer.TryReadString(out var stretchString))
{
if (Enum.TryParse(stretchString, true, out stretch))
{
return true;
}
}

return false;
}
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class SystemFontCollection : FontCollectionBase
public SystemFontCollection(FontManager fontManager)
{
_fontManager = fontManager;
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().ToList();
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().Where(x=> !string.IsNullOrEmpty(x)).ToList();
}

public override Uri Key => FontManager.SystemFontsKey;
Expand Down
17 changes: 17 additions & 0 deletions tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,22 @@ public void Should_Get_Typeface_For_Partial_FamilyName()
Assert.Equal("Twitter Color Emoji", glyphTypeface.FamilyName);
}
}

[Fact]
public void Should_Get_Typeface_For_Known_Typographic_Name()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests", UriKind.Absolute);

var fontCollection = new EmbeddedFontCollection(source, source);

fontCollection.Initialize(new CustomFontManagerImpl());

Assert.True(fontCollection.TryGetGlyphTypeface("Twitter Regular", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var glyphTypeface));

Assert.Equal("Twitter Color Emoji", glyphTypeface.FamilyName);
}
}
}
}
23 changes: 23 additions & 0 deletions tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,28 @@ public void Should_Get_Nearest_Match_For_Custom_SystemFont()
}
}
}

[Fact]
public void Should_Get_Implicit_Typeface()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
{
using (AvaloniaLocator.EnterScope())
{
var systemFontCollection = FontManager.Current.SystemFonts as SystemFontCollection;

Assert.NotNull(systemFontCollection);

systemFontCollection.AddCustomFontSource(new Uri(s_fontUri, UriKind.Absolute));

Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono Italic"),
out var glyphTypeface));

Assert.Equal("Noto Mono", glyphTypeface.FamilyName);

Assert.Equal(FontStyle.Italic, glyphTypeface.Style);
}
}
}
}
}
Loading