From ea68c0e306bef82626ad4fbace8fbfcd8d8a2d9c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 15 Nov 2024 08:31:00 +0100 Subject: [PATCH 1/4] Make sure we always cache the created glyph typeface before we try to find the nearest match --- .../Media/Fonts/SystemFontCollection.cs | 3 ++ .../Media/FontCollectionTests.cs | 41 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) 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/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs index 182024dc88d..53d8c0c47cf 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs @@ -1,5 +1,13 @@ -using Avalonia.Media; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Avalonia.Media; using Avalonia.Media.Fonts; +using Avalonia.Platform; +using Avalonia.UnitTests; +using SkiaSharp; using Xunit; namespace Avalonia.Skia.UnitTests.Media @@ -24,5 +32,36 @@ 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))); + } + } + + private class TestSystemFontCollection : SystemFontCollection + { + public TestSystemFontCollection(FontManager fontManager) : base(fontManager) + { + + } + + public IDictionary> GlyphTypfaceCache => _glyphTypefaceCache; + } } } From 6ddabf595e5a54ad8c5f255530d9ba08d935a6c1 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 15 Nov 2024 08:33:50 +0100 Subject: [PATCH 2/4] Cleanup usings --- tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs index 53d8c0c47cf..303928524b5 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using Avalonia.Media; using Avalonia.Media.Fonts; -using Avalonia.Platform; using Avalonia.UnitTests; -using SkiaSharp; using Xunit; namespace Avalonia.Skia.UnitTests.Media From e5992640b24ace55901b509d4c7cc7ff82d27e14 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 15 Nov 2024 10:40:21 +0100 Subject: [PATCH 3/4] Add failing test --- .../Media/EmbeddedFontCollectionTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs index 910e87131a4..f4a558c2546 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,33 @@ 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); + } + } + + private class TestEmbeddedFontCollection : EmbeddedFontCollection + { + public TestEmbeddedFontCollection(Uri key, Uri source) : base(key, source) + { + } + + public IDictionary> GlyphTypefaceCache => _glyphTypefaceCache; + } } } From b31ec20f89ea07d575ccc11c7315cf09f18542f8 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 15 Nov 2024 10:44:01 +0100 Subject: [PATCH 4/4] Cache the matched glyph typeface for EmbeddedFontCollection --- src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs | 3 +++ .../Media/EmbeddedFontCollectionTests.cs | 4 ++++ tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs | 4 ++++ 3 files changed, 11 insertions(+) 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/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs index f4a558c2546..ac97455110b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs @@ -109,6 +109,10 @@ public void Should_Cache_Synthetic_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); } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs index 303928524b5..36cb14010d3 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs @@ -46,6 +46,10 @@ public void Should_Cache_Nearest_Match() 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); } }