From d5ef9b98ee3b8a2bab506a1a11dd46ac90960300 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 25 Feb 2026 07:19:42 +0000 Subject: [PATCH] Handle GlyphTypeface creation exceptions (#20739) * Add failing test for fonts without supported cmap * Handle GlyphTypeface creation exceptions --- src/Avalonia.Base/Logging/LogArea.cs | 5 ++ src/Avalonia.Base/Media/FontManager.cs | 2 +- .../Media/Fonts/FontCollectionBase.cs | 39 ++++++-------- .../Media/Fonts/SystemFontCollection.cs | 11 +++- src/Avalonia.Base/Media/GlyphTypeface.cs | 21 +++++++- .../Fonts/TestFontNoCmap412.ttf | Bin 0 -> 2788 bytes .../Media/FontManagerTests.cs | 51 +++++++++++++++++- 7 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 139129e623..07553a647e 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -20,6 +20,11 @@ namespace Avalonia.Logging /// public const string Animations = nameof(Animations); + /// + /// The log event comes from the fonts system. + /// + public const string Fonts = nameof(Fonts); + /// /// The log event comes from the visual system. /// diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 9d1d0145d0..1f15820b9a 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -199,7 +199,7 @@ namespace Avalonia.Media return true; } - var logger = Logger.TryGet(LogEventLevel.Debug, "FontManager"); + var logger = Logger.TryGet(LogEventLevel.Debug, LogArea.Fonts); logger?.Log(this, $"Font family '{familyName}' could not be found. Present font families: [{string.Join(",", fontCollection)}]"); diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs index 4c535bdc0e..a3375652b8 100644 --- a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs +++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs @@ -128,7 +128,9 @@ namespace Avalonia.Media.Fonts { if (_fontManagerImpl.TryCreateGlyphTypeface(stream, fontSimulations, out var platformTypeface)) { - syntheticGlyphTypeface = new GlyphTypeface(platformTypeface, fontSimulations); + syntheticGlyphTypeface = GlyphTypeface.TryCreate(platformTypeface, fontSimulations); + if (syntheticGlyphTypeface is null) + return false; //Add the TypographicFamilyName to the cache if (!string.IsNullOrEmpty(glyphTypeface.TypographicFamilyName)) @@ -272,16 +274,14 @@ namespace Avalonia.Media.Fonts /// langword="false"/>. public bool TryAddGlyphTypeface(Stream stream, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface) { - glyphTypeface = null; - if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface)) { + glyphTypeface = null; return false; } - glyphTypeface = new GlyphTypeface(platformTypeface); - - return TryAddGlyphTypeface(glyphTypeface); + glyphTypeface = GlyphTypeface.TryCreate(platformTypeface); + return glyphTypeface is not null && TryAddGlyphTypeface(glyphTypeface); } /// @@ -315,13 +315,12 @@ namespace Avalonia.Media.Fonts { var stream = _assetLoader.Open(fontAsset); - if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface)) + if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) || + GlyphTypeface.TryCreate(platformTypeface) is not { } glyphTypeface) { continue; } - var glyphTypeface = new GlyphTypeface(platformTypeface); - var key = glyphTypeface.ToFontCollectionKey(); //Add TypographicFamilyName to the cache @@ -353,14 +352,11 @@ namespace Avalonia.Media.Fonts using var stream = File.OpenRead(source.LocalPath); - if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface)) + if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) && + GlyphTypeface.TryCreate(platformTypeface) is { } glyphTypeface && + TryAddGlyphTypeface(glyphTypeface)) { - var glyphTypeface = new GlyphTypeface(platformTypeface); - - if (TryAddGlyphTypeface(glyphTypeface)) - { - result = true; - } + result = true; } } // If the path is a directory, load all font files from that directory @@ -377,14 +373,11 @@ namespace Avalonia.Media.Fonts { using var stream = File.OpenRead(file); - if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface)) + if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface) && + GlyphTypeface.TryCreate(platformTypeface) is { } glyphTypeface && + TryAddGlyphTypeface(glyphTypeface)) { - var glyphTypeface = new GlyphTypeface(platformTypeface); - - if (TryAddGlyphTypeface(glyphTypeface)) - { - result = true; - } + result = true; } } } diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index cf055e5d99..3c81e9890f 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -52,7 +52,11 @@ namespace Avalonia.Media.Fonts return false; } - glyphTypeface = new GlyphTypeface(platformTypeface); + glyphTypeface = GlyphTypeface.TryCreate(platformTypeface); + if (glyphTypeface is null) + { + return false; + } //Add to cache with platform typeface family name first TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface); @@ -112,7 +116,10 @@ namespace Avalonia.Media.Fonts } // Not in cache yet: create glyph typeface and try to add it. - var glyphTypeface = new GlyphTypeface(platformTypeface); + if (GlyphTypeface.TryCreate(platformTypeface) is not { } glyphTypeface) + { + return false; + } // Try adding with the platform typeface family name first. TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface); diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs index ca8e3fec16..3b3875b5de 100644 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ b/src/Avalonia.Base/Media/GlyphTypeface.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Runtime.CompilerServices; +using Avalonia.Logging; using Avalonia.Media.Fonts; using Avalonia.Media.Fonts.Tables; using Avalonia.Media.Fonts.Tables.Cmap; @@ -222,6 +222,25 @@ namespace Avalonia.Media } } + internal static GlyphTypeface? TryCreate(IPlatformTypeface typeface, FontSimulations fontSimulations = FontSimulations.None) + { + try + { + return new GlyphTypeface(typeface, fontSimulations); + } + catch (Exception ex) + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Fonts)?.Log( + null, + "Could not create glyph typeface from platform typeface named {FamilyName} with simulations {Simulations}: {Exception}", + typeface.FamilyName, + fontSimulations, + ex); + + return null; + } + } + /// /// Gets the family name of the font. /// diff --git a/tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf b/tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf new file mode 100644 index 0000000000000000000000000000000000000000..03a0fbe53792ab0f1b77c50e966b4f3071900959 GIT binary patch literal 2788 zcmbW3U2IfE6vxlpd)sc8QrbWei657y5BR}SYDNKA37r_#OYZ*E+^hF#_MQT5*%+(!NlIh&o%luXyf%OYOL92u)*W`6kO z>suo69_$0-Q>Vu&KmQtt_&bqD$FU!L`oezfXOI&U`Alxd?H}J~E;`vefui9J|2lFR zxqV`~Jnt_F+`Mhbv8iG>HNhvM_$3cAW}C_D$bU# zKK|t%rhbooPlfn(zqj6HeVb3k#`jCenoo=rG>z{@KmF^{TbuU(Ek3K$ZtT8wSjRuU zJ%3hmYiImLog?+)sg}k(zqCe9mTk^ow2>lP)O%LrNsH83g|2je3;ZSXv0A;4yw@-t ziAHlmIz`#7x3!wq0PgMYOG=N(+M44~lKGo28!YQnLdPsp81W?zY?6&uaVJvC=4iaG z^Wb-rVTH4!c;!#xuUM?ijl-J1=dcg<9cITRa9H!LbGRN{?{EXS!QmJ<=5Qmp(P3p7 zcX$JMgTqbWCWkeLjSe@1n;qT+-sEr#xW!?KP7sx@?xfF7Hc|~{Jl49S)tDW|v_@Ud z4_8a(z{;XIL7(&9TfMUSg*m{fT;AqAY0j-4SRMB}m(QDX=F0L_bB=XHw#Z#7Yy!D2FnWoVi$u`q1u9>)CGhExeb_bXN#?897~?!Y%_t$= z{Pxm!RwbS~AkHjoz8J4NcBzryw zmx!NMJtPb5+o)lRafrT+vCqmXDUajm(=rI_V~i^Ivdc&5N05i)ke!=E6LJ=kX#4Gq zA&WI`l05a?jmYbaN3g1o#?Heks*cu4_qYU8J!4w;B3LUtjaDnH%r*ZMNbC9Nbq(3Q zC~9@?tlf(&(Z~2@iK-{JV9#)dK5u#IiI{ z1+*G9 zn(Qs#1$S=49-jMKq^arkt3c=JcY~ssXx35OcJWT-$`fiHJUp1@J?nf$zlHP*q1sN^ z82@i=!rO10==($$(_afbJa`ysRu0vLqOT!i`8=RAMwz?D{Sd{8blYuK!u_NzzQC;_ i^$XIY(n62F^udRUOpBO1tK96-Dz;ZH_nPM{rGEikrm#)` literal 0 HcmV?d00001 diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 994edc6095..76d4857e64 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using Avalonia.Fonts.Inter; -using Avalonia.Headless; +using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Media.TextFormatting.Unicode; +using Avalonia.Platform; using Avalonia.UnitTests; using SkiaSharp; using Xunit; @@ -494,5 +495,53 @@ namespace Avalonia.Skia.UnitTests.Media } } } + + [Fact] + public void TryGetGlyphTypeface_Should_Return_False_For_Font_Without_Supported_Cmap() + { + const string fontUri = "resm:Avalonia.Skia.UnitTests.Fonts.TestFontNoCmap412.ttf?assembly=Avalonia.Skia.UnitTests#TestFontNoCmap412"; + + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())); + using var scope = AvaloniaLocator.EnterScope(); + + // Skia can load the font + AssertCanCreatePlatformTypeface(); + + // But Avalonia can't, because it has no supported cmap subtable + AssertCannotCreateGlyphTypeface(); + + void AssertCanCreatePlatformTypeface() + { + var fontManagerImpl = AvaloniaLocator.Current.GetRequiredService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + + using var stream = assetLoader.Open(new Uri(fontUri)); + + Assert.True(fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var platformTypeface)); + Assert.NotNull(platformTypeface); + Assert.Equal("TestFontNoCmap412", platformTypeface.FamilyName); + } + + void AssertCannotCreateGlyphTypeface() + { + string? loggedTemplate = null; + object?[] loggedValues = []; + + using var logSinkScope = TestLogSink.Start((level, area, _, template, values) => + { + if (level == LogEventLevel.Warning && area == LogArea.Fonts) + { + loggedTemplate = template; + loggedValues = values; + } + }); + + Assert.False(FontManager.Current.TryGetGlyphTypeface(new Typeface(fontUri), out _)); + Assert.Equal(loggedTemplate, "Could not create glyph typeface from platform typeface named {FamilyName} with simulations {Simulations}: {Exception}"); + Assert.Equal(3, loggedValues.Length); + Assert.Equal("TestFontNoCmap412", Assert.IsType(loggedValues[0])); + Assert.Equal("No suitable cmap subtable found.", Assert.IsType(loggedValues[2]).Message); + } + } } }