Browse Source

Handle GlyphTypeface creation exceptions (#20739)

* Add failing test for fonts without supported cmap

* Handle GlyphTypeface creation exceptions
pull/20752/head
Julien Lebosquain 4 weeks ago
committed by GitHub
parent
commit
d5ef9b98ee
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/Avalonia.Base/Logging/LogArea.cs
  2. 2
      src/Avalonia.Base/Media/FontManager.cs
  3. 39
      src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
  4. 11
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  5. 21
      src/Avalonia.Base/Media/GlyphTypeface.cs
  6. BIN
      tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf
  7. 51
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

5
src/Avalonia.Base/Logging/LogArea.cs

@ -20,6 +20,11 @@ namespace Avalonia.Logging
/// </summary>
public const string Animations = nameof(Animations);
/// <summary>
/// The log event comes from the fonts system.
/// </summary>
public const string Fonts = nameof(Fonts);
/// <summary>
/// The log event comes from the visual system.
/// </summary>

2
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)}]");

39
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"/>.</returns>
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);
}
/// <summary>
@ -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;
}
}
}

11
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);

21
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;
}
}
/// <summary>
/// Gets the family name of the font.
/// </summary>

BIN
tests/Avalonia.Skia.UnitTests/Fonts/TestFontNoCmap412.ttf

Binary file not shown.

51
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<IFontManagerImpl>();
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
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<string>(loggedValues[0]));
Assert.Equal("No suitable cmap subtable found.", Assert.IsType<InvalidOperationException>(loggedValues[2]).Message);
}
}
}
}

Loading…
Cancel
Save