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

Fix SystemFontCollection nearest match lookup #13365

Merged
merged 10 commits into from
Oct 26, 2023
40 changes: 39 additions & 1 deletion src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class EmbeddedFontCollection : FontCollectionBase

private readonly Uri _source;

private IFontManagerImpl? _fontManager;

public EmbeddedFontCollection(Uri key, Uri source)
{
_key = key;
Expand All @@ -31,6 +33,8 @@ public EmbeddedFontCollection(Uri key, Uri source)

public override void Initialize(IFontManagerImpl fontManager)
{
_fontManager = fontManager;

var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();

var fontAssets = FontFamilyLoader.LoadFontAssets(_source);
Expand All @@ -39,7 +43,7 @@ public override void Initialize(IFontManagerImpl fontManager)
{
var stream = assetLoader.Open(fontAsset);

if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface))
if (fontManager.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
{
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
{
Expand Down Expand Up @@ -69,8 +73,42 @@ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, Fon

if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
{
if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
{
return true;
}

if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
{
if (glyphTypeface is IGlyphTypeface2 glyphTypeface2)
{
var fontSimulations = FontSimulations.None;

if (style != FontStyle.Normal && glyphTypeface2.Style != style)
{
fontSimulations |= FontSimulations.Oblique;
}

if ((int)weight >= 600 && glyphTypeface2.Weight != weight)
{
fontSimulations |= FontSimulations.Bold;
}

if (fontSimulations != FontSimulations.None && glyphTypeface2.TryGetStream(out var stream))
{
using (stream)
{
if(_fontManager is not null && _fontManager.TryCreateGlyphTypeface(stream, fontSimulations, out glyphTypeface) &&
glyphTypefaces.TryAdd(key, glyphTypeface))
{
return true;
}

return false;
}
}
}

return true;
}
}
Expand Down
46 changes: 37 additions & 9 deletions src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,46 @@ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, Fon

var key = new FontCollectionKey(style, weight, stretch);

var glyphTypefaces = _glyphTypefaceCache.GetOrAdd(familyName, (key) => new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>());
var glyphTypefaces = _glyphTypefaceCache.GetOrAdd(familyName,
(_) => new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>());

if (!glyphTypefaces.TryGetValue(key, out glyphTypeface))
if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
{
_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface);
return glyphTypeface != null;
}

if(!_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) ||
!glyphTypeface.FamilyName.Contains(familyName))
{
//Try to find nearest match if possible
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface);
}

if (!glyphTypefaces.TryAdd(key, glyphTypeface))
if(glyphTypeface is IGlyphTypeface2 glyphTypeface2)
{
var fontSimulations = FontSimulations.None;

if(style != FontStyle.Normal && glyphTypeface2.Style != style)
{
return false;
fontSimulations |= FontSimulations.Oblique;
}

if((int)weight >= 600 && glyphTypeface2.Weight != weight)
{
fontSimulations |= FontSimulations.Bold;
}

if(fontSimulations != FontSimulations.None && glyphTypeface2.TryGetStream(out var stream))
{
using (stream)
{
_fontManager.PlatformImpl.TryCreateGlyphTypeface(stream, fontSimulations, out glyphTypeface);
}
}
}

glyphTypefaces.TryAdd(key, glyphTypeface);

return glyphTypeface != null;
}

Expand Down Expand Up @@ -87,7 +115,7 @@ private void LoadGlyphTypefaces(IFontManagerImpl fontManager, Uri source)
{
var stream = assetLoader.Open(fontAsset);

if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface))
if (fontManager.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
{
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
{
Expand All @@ -101,9 +129,9 @@ private void LoadGlyphTypefaces(IFontManagerImpl fontManager, Uri source)
}

var key = new FontCollectionKey(
glyphTypeface.Style,
glyphTypeface.Weight,
glyphTypeface.Stretch);
glyphTypeface.Style,
glyphTypeface.Weight,
glyphTypeface.Stretch);

glyphTypefaces.TryAdd(key, glyphTypeface);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Avalonia.Base/Platform/IFontManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weigh
/// Tries to create a glyph typeface from specified stream.
/// </summary>
/// <param name="stream">A stream that holds the font's data.</param>
/// <param name="fontSimulations">Specifies algorithmic style simulations.</param>
/// <param name="glyphTypeface">The created glyphTypeface</param>
/// <returns>
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could create the glyph typeface, <c>False</c> otherwise.
/// </returns>
bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
}
}
6 changes: 4 additions & 2 deletions src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,12 @@ public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, F
return true;
}

public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
public virtual bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new HeadlessGlyphTypefaceImpl();

TryCreateGlyphTypefaceCount++;

return true;
}
}
Expand Down Expand Up @@ -301,7 +303,7 @@ public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, F
return true;
}

public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
public virtual bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new HeadlessGlyphTypefaceImpl();

Expand Down
4 changes: 2 additions & 2 deletions src/Skia/Avalonia.Skia/FontManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeigh
return true;
}

public bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
var skTypeface = SKTypeface.FromStream(stream);

if (skTypeface != null)
{
glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
glyphTypeface = new GlyphTypefaceImpl(skTypeface, fontSimulations);

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
}

public SKFont CreateSKFont(float size)
=> new(_typeface, size, skewX: (FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0.0f)
=> new(_typeface, size, skewX: (FontSimulations & FontSimulations.Oblique) != 0 ? -0.3f : 0.0f)
{
LinearMetrics = true,
Embolden = (FontSimulations & FontSimulations.Bold) != 0
Expand Down
2 changes: 1 addition & 1 deletion src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeigh
return false;
}

public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, new[] { stream });

Expand Down
4 changes: 2 additions & 2 deletions tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeigh
return true;
}

public bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
{
var skTypeface = SKTypeface.FromStream(stream);

glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
glyphTypeface = new GlyphTypefaceImpl(skTypeface, fontSimulations);

return true;
}
Expand Down
21 changes: 21 additions & 0 deletions tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,26 @@ public void Should_Use_Custom_SystemFont()
}
}
}


[Fact]
public void Should_Get_Nearest_Match_For_Custom_SystemFont()
{
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", FontStyle.Italic), out var glyphTypeface));

Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
}
}
}
}
}
2 changes: 1 addition & 1 deletion tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fon
return false;
}

public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new HarfBuzzGlyphTypefaceImpl(stream);

Expand Down
Loading