#nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Platform; using Avalonia.UnitTests; using Xunit; namespace Avalonia.Skia.UnitTests.Media { public class FontCollectionTests { private const string NotoMono = "resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests"; [Win32Fact("Relies on some installed font family")] public void Should_Cache_Nearest_Match() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) { var fontCollection = new TestSystemFontCollection(FontManager.Current.PlatformImpl); Assert.True(fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface)); Assert.True(fontCollection.GlyphTypefaceCache.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(IFontManagerImpl platformImpl) : base(platformImpl) { } public IDictionary> GlyphTypefaceCache => _glyphTypefaceCache; } [Fact] public void Should_Use_Fallback() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()))) { var source = new Uri(NotoMono, UriKind.Absolute); var fallback = new FontFallback { FontFamily = new FontFamily("Arial"), UnicodeRange = new UnicodeRange('A', 'A') }; var fontCollection = new CustomizableFontCollection(source, source, new[] { fallback }); Assert.True(fontCollection.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match)); Assert.Equal("Arial", match.FontFamily.Name); } } [Fact] public void Should_Ignore_FontFamily() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()))) { var key = new Uri(NotoMono, UriKind.Absolute); var ignorable = new FontFamily(new Uri(NotoMono, UriKind.Absolute), "Noto Mono"); var fontCollection = new CustomizableFontCollection(key, key, null, new[] { ignorable }); var typeface = new Typeface(ignorable); var glyphTypeface = typeface.GlyphTypeface; Assert.False(fontCollection.TryCreateSyntheticGlyphTypeface( typeface.GlyphTypeface, FontStyle.Italic, FontWeight.DemiBold, FontStretch.Normal, out var syntheticGlyphTypeface)); } } private class CustomizableFontCollection : EmbeddedFontCollection { private readonly IReadOnlyList? _fallbacks; private readonly IReadOnlyList? _ignorables; public CustomizableFontCollection(Uri key, Uri source, IReadOnlyList? fallbacks = null, IReadOnlyList? ignorables = null) : base(key, source) { _fallbacks = fallbacks; _ignorables = ignorables; } public override bool TryMatchCharacter( int codepoint, FontStyle style, FontWeight weight, FontStretch stretch, string? familyName, CultureInfo? culture, out Typeface match) { if(_fallbacks is not null) { foreach (var fallback in _fallbacks) { if (fallback.UnicodeRange.IsInRange(codepoint)) { match = new Typeface(fallback.FontFamily, style, weight, stretch); return true; } } } return base.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match); } public override bool TryCreateSyntheticGlyphTypeface( IGlyphTypeface glyphTypeface, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface) { syntheticGlyphTypeface = null; if(_ignorables is not null) { foreach (var ignorable in _ignorables) { if (glyphTypeface.FamilyName == ignorable.Name || glyphTypeface is IGlyphTypeface2 glyphTypeface2 && glyphTypeface2.TypographicFamilyName == ignorable.Name) { return false; } } } return base.TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out syntheticGlyphTypeface); } } } }