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 0000000000..03a0fbe537 Binary files /dev/null and b/tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf differ 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); + } + } } }