From 28c21078f51ef3ad557801067d3fd72679a9b09a Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 19 Nov 2024 15:27:55 +0100 Subject: [PATCH] Fix FontCollection glyph typeface caching (#17519) * Make sure we always cache the created glyph typeface before we try to find the nearest match * Cleanup usings * Add failing test * Cache the matched glyph typeface for EmbeddedFontCollection --- .../Media/Fonts/EmbeddedFontCollection.cs | 3 ++ .../Media/Fonts/SystemFontCollection.cs | 3 ++ .../Media/EmbeddedFontCollectionTests.cs | 35 ++++++++++++++++ .../Media/FontCollectionTests.cs | 40 ++++++++++++++++++- 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs index 8e8cc5fd184..789122988fa 100644 --- a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs @@ -76,6 +76,9 @@ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, Fon glyphTypeface = syntheticGlyphTypeface; } + //Make sure we cache the found match + glyphTypefaces.TryAdd(key, glyphTypeface); + return true; } } diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index 2f1e65c23ff..be5c2a25884 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -81,6 +81,9 @@ public override bool TryGetGlyphTypeface(string familyName, FontStyle style, Fon //No exact match if (createdKey != key) { + //Add the created glyph typeface to the cache so we can match it. + glyphTypefaces.TryAdd(createdKey, glyphTypeface); + //Try to find nearest match if possible if (TryGetNearestMatch(glyphTypefaces, key, out var nearestMatch)) { diff --git a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs index 910e87131a4..ac97455110b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.UnitTests; @@ -89,5 +92,37 @@ public void Should_Get_Typeface_For_TypographicFamilyName() Assert.Equal("Manrope", glyphTypeface2.TypographicFamilyName); } } + + [Fact] + public void Should_Cache_Synthetic_GlyphTypeface() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var source = new Uri(s_manrope, UriKind.Absolute); + + var fontCollection = new TestEmbeddedFontCollection(source, source); + + fontCollection.Initialize(new CustomFontManagerImpl()); + + Assert.True(fontCollection.TryGetGlyphTypeface("Manrope", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface)); + + Assert.True(fontCollection.GlyphTypefaceCache.TryGetValue("Manrope", out var glyphTypefaces)); + + Assert.Equal(2, glyphTypefaces.Count); + + fontCollection.TryGetGlyphTypeface("Manrope", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var otherGlyphTypeface); + + Assert.Equal(glyphTypeface, otherGlyphTypeface); + } + } + + private class TestEmbeddedFontCollection : EmbeddedFontCollection + { + public TestEmbeddedFontCollection(Uri key, Uri source) : base(key, source) + { + } + + public IDictionary> GlyphTypefaceCache => _glyphTypefaceCache; + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs index 182024dc88d..36cb14010d3 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs @@ -1,5 +1,8 @@ -using Avalonia.Media; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Avalonia.Media; using Avalonia.Media.Fonts; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Skia.UnitTests.Media @@ -24,5 +27,40 @@ public void Should_Get_Implicit_Typeface(string input, string familyName, FontSt Assert.Equal(weight, result.Weight); Assert.Equal(FontStretch.Normal, result.Stretch); } + + [Win32Fact("Relies on some installed font family")] + public void Should_Cache_Nearest_Match() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + var fontManager = FontManager.Current; + + var fontCollection = new TestSystemFontCollection(FontManager.Current); + + Assert.True(fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface)); + + Assert.True(glyphTypeface.FontSimulations == FontSimulations.Bold); + + Assert.True(fontCollection.GlyphTypfaceCache.TryGetValue("Arial", out var glyphTypefaces)); + + Assert.Equal(2, glyphTypefaces.Count); + + Assert.True(glyphTypefaces.ContainsKey(new FontCollectionKey(FontStyle.Normal, FontWeight.Black, FontStretch.Normal))); + + fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var otherGlyphTypeface); + + Assert.Equal(glyphTypeface, otherGlyphTypeface); + } + } + + private class TestSystemFontCollection : SystemFontCollection + { + public TestSystemFontCollection(FontManager fontManager) : base(fontManager) + { + + } + + public IDictionary> GlyphTypfaceCache => _glyphTypefaceCache; + } } }