csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
647 lines
28 KiB
647 lines
28 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Avalonia.Fonts.Inter;
|
|
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;
|
|
|
|
namespace Avalonia.Skia.UnitTests.Media
|
|
{
|
|
public class FontManagerTests
|
|
{
|
|
private static string s_fontUri = "resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono";
|
|
|
|
[Fact]
|
|
public void Should_Create_Typeface_From_Fallback()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var fontManager = FontManager.Current;
|
|
|
|
var glyphTypeface = new Typeface(new FontFamily("A, B, " + FontFamily.DefaultFontFamilyName)).GlyphTypeface;
|
|
|
|
Assert.Equal(SKTypeface.Default.FamilyName, glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Create_Typeface_From_Fallback_Bold()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var glyphTypeface = new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold).GlyphTypeface;
|
|
|
|
Assert.True((int)glyphTypeface.Weight >= 600);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Yield_Default_GlyphTypeface_For_Invalid_FamilyName()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface;
|
|
|
|
Assert.Equal(FontManager.Current.DefaultFontFamily.Name, glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Load_Typeface_From_Resource()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var glyphTypeface = new Typeface(s_fontUri).GlyphTypeface;
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Load_Nearest_Matching_Font()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var glyphTypeface = new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black).GlyphTypeface;
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Throw_For_Invalid_Custom_Font()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
Assert.Throws<InvalidOperationException>(() => new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown").GlyphTypeface);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Return_False_For_Unregistered_FontCollection_Uri()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
var result = FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:invalid#Something"), out _);
|
|
|
|
Assert.False(result);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Only_Try_To_Create_GlyphTypeface_Once()
|
|
{
|
|
var fontManagerImpl = new TestFontManager();
|
|
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl)))
|
|
{
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out _));
|
|
|
|
var countBefore = fontManagerImpl.TryCreateGlyphTypefaceCount;
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
FontManager.Current.TryGetGlyphTypeface(new Typeface("Unknown"), out _);
|
|
}
|
|
|
|
Assert.Equal(countBefore + 1, fontManagerImpl.TryCreateGlyphTypefaceCount);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Cache_MatchCharacter()
|
|
{
|
|
var fontManagerImpl = new CustomFontManagerImpl();
|
|
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl)))
|
|
{
|
|
var emoji = Codepoint.ReadAt("😀", 0, out _);
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter((int)emoji, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var firstMatch));
|
|
|
|
var firstGlyphTypeface = firstMatch.GlyphTypeface;
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter((int)emoji, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var secondMatch));
|
|
|
|
var secondGlyphTypeface = secondMatch.GlyphTypeface;
|
|
|
|
Assert.Equal(firstGlyphTypeface, secondGlyphTypeface);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Load_Embedded_DefaultFontFamily()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions { DefaultFamilyName = s_fontUri });
|
|
|
|
var result = FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out var glyphTypeface);
|
|
|
|
Assert.True(result);
|
|
Assert.NotNull(glyphTypeface);
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Return_False_For_Invalid_DefaultFontFamily()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions { DefaultFamilyName = "avares://resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown" });
|
|
|
|
var result = FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out _);
|
|
|
|
Assert.False(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Load_Embedded_Fallbacks()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
var fontFamily = FontFamily.Parse("NotFound, " + s_fontUri);
|
|
|
|
var typeface = new Typeface(fontFamily);
|
|
|
|
var glyphTypeface = typeface.GlyphTypeface;
|
|
|
|
Assert.NotNull(glyphTypeface);
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Match_Chararcter_Width_Embedded_Fallbacks()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
var fontFamily = FontFamily.Parse("NotFound, " + s_fontUri);
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, fontFamily, null, out var typeface));
|
|
|
|
var glyphTypeface = typeface.GlyphTypeface;
|
|
|
|
Assert.NotNull(glyphTypeface);
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Match_Chararcter_From_SystemFonts()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
Assert.True(FontManager.Current.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var typeface));
|
|
|
|
var glyphTypeface = typeface.GlyphTypeface;
|
|
|
|
Assert.NotNull(glyphTypeface);
|
|
|
|
Assert.Equal(FontManager.Current.DefaultFontFamily.Name, glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("NotFound, Unknown", null)] // system fonts
|
|
[InlineData("/#NotFound, /#Unknown", "avares://some/path")] // embedded fonts
|
|
public void Should_Match_Character_With_Fallbacks(string familyName, string? baseUri)
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
var fontFamily = FontFamily.Parse(familyName, baseUri is null ? null : new Uri(baseUri));
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, fontFamily, null, out var typeface));
|
|
|
|
var glyphTypeface = typeface.GlyphTypeface;
|
|
|
|
Assert.NotNull(glyphTypeface);
|
|
|
|
Assert.Equal(FontManager.Current.DefaultFontFamily.Name, glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Use_Custom_SystemFont()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new EmbeddedFontCollection(FontManager.SystemFontsKey,
|
|
new Uri(s_fontUri, UriKind.Absolute)));
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono"), out var glyphTypeface));
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public void Should_Get_Nearest_Match_For_Custom_SystemFont()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new EmbeddedFontCollection(FontManager.SystemFontsKey,
|
|
new Uri(s_fontUri, UriKind.Absolute)));
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono", FontStyle.Italic), out var glyphTypeface));
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Get_Implicit_Typeface()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new EmbeddedFontCollection(FontManager.SystemFontsKey,
|
|
new Uri(s_fontUri, UriKind.Absolute)));
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono Italic"),
|
|
out var glyphTypeface));
|
|
|
|
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
|
|
|
Assert.Equal(FontStyle.Italic, glyphTypeface.Style);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Create_Synthetic_Typeface()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new EmbeddedFontCollection(FontManager.SystemFontsKey,
|
|
new Uri(s_fontUri, UriKind.Absolute)));
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono", FontStyle.Italic, FontWeight.Bold),
|
|
out var italicBoldTypeface));
|
|
|
|
Assert.Equal("Noto Mono", italicBoldTypeface.FamilyName);
|
|
|
|
Assert.True(italicBoldTypeface.PlatformTypeface.FontSimulations.HasFlag(FontSimulations.Bold));
|
|
|
|
Assert.True(italicBoldTypeface.PlatformTypeface.FontSimulations.HasFlag(FontSimulations.Oblique));
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Noto Mono", FontStyle.Normal, FontWeight.Normal),
|
|
out var regularTypeface));
|
|
|
|
Assert.NotEqual(((SkiaTypeface)regularTypeface.PlatformTypeface).SKTypeface, ((SkiaTypeface)italicBoldTypeface.PlatformTypeface).SKTypeface);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Win32Fact("Requires Windows Fonts")]
|
|
public void Should_Get_GlyphTypeface_By_Localized_FamilyName()
|
|
{
|
|
using (UnitTestApplication.Start(
|
|
TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("微軟正黑體"), out var glyphTypeface));
|
|
|
|
Assert.Equal("Microsoft JhengHei", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Get_FontFeatures()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new InterFontCollection());
|
|
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:Inter#Inter"),
|
|
out var glyphTypeface));
|
|
|
|
Assert.Equal("Inter", glyphTypeface.FamilyName);
|
|
|
|
var features = glyphTypeface.SupportedFeatures;
|
|
|
|
Assert.NotEmpty(features);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Map_FontFamily()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions
|
|
{
|
|
DefaultFamilyName = s_fontUri,
|
|
FontFamilyMappings = new Dictionary<string, FontFamily>
|
|
{
|
|
{ "Segoe UI", new FontFamily("fonts:Inter#Inter") }
|
|
}
|
|
});
|
|
|
|
FontManager.Current.AddFontCollection(new InterFontCollection());
|
|
|
|
var result = FontManager.Current.TryGetGlyphTypeface(new Typeface("Abc, Segoe UI"), out var glyphTypeface);
|
|
|
|
Assert.True(result);
|
|
Assert.NotNull(glyphTypeface);
|
|
Assert.Equal("Inter", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Get_FamilyTypefaces()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(new InterFontCollection());
|
|
|
|
var familyTypefaces = FontManager.Current.GetFamilyTypefaces(new FontFamily("fonts:Inter#Inter"));
|
|
|
|
Assert.Equal(6, familyTypefaces.Count);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Use_FontCollection_MatchCharacter()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(
|
|
new EmbeddedFontCollection(
|
|
new Uri("fonts:MyCollection"), //key
|
|
new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests"))); //source
|
|
|
|
var fontFamily = new FontFamily("fonts:MyCollection#Noto Mono");
|
|
|
|
var character = "א";
|
|
|
|
var codepoint = Codepoint.ReadAt(character, 0, out _);
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter(codepoint, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, fontFamily, null, out var typeface));
|
|
|
|
//Typeface should come from the font collection
|
|
Assert.NotNull(typeface.FontFamily.Key);
|
|
|
|
Assert.Equal("Noto Sans Hebrew", typeface.GlyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Use_Last_Resort_Font_Last_MatchCharacter()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
FontManager.Current.AddFontCollection(
|
|
new EmbeddedFontCollection(
|
|
new Uri("fonts:MyCollection"), //key
|
|
new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests"))); //source
|
|
|
|
var fontFamily = new FontFamily("fonts:MyCollection#Noto Sans");
|
|
|
|
const string characters = "א𪜶";
|
|
|
|
var codepoint1 = Codepoint.ReadAt(characters, 0, out _);
|
|
Assert.Equal(0x5D0, codepoint1); // א
|
|
|
|
// Typeface should come from the font collection - falling back to Noto Sans Hebrew
|
|
Assert.True(FontManager.Current.TryMatchCharacter(codepoint1, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, fontFamily, null, out var typeface1));
|
|
Assert.NotNull(typeface1.FontFamily.Key);
|
|
Assert.Equal("Noto Sans Hebrew", typeface1.GlyphTypeface.FamilyName);
|
|
|
|
var codepoint2 = Codepoint.ReadAt(characters, 1, out _);
|
|
Assert.Equal(0x2A736, codepoint2); // 𪜶
|
|
|
|
// Typeface should come from the font collection - falling back to Adobe Blank 2 VF R as a last resort
|
|
Assert.True(FontManager.Current.TryMatchCharacter(codepoint2, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, fontFamily, null, out var typeface2));
|
|
Assert.NotNull(typeface2.FontFamily.Key);
|
|
Assert.Equal("Adobe Blank 2 VF R", typeface2.GlyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
[InlineData("Arial")]
|
|
[InlineData("#Arial")]
|
|
[Win32Theory("Windows specific font")]
|
|
public void Should_Get_SystemFont_With_BaseUri(string name)
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
var fontFamily = new FontFamily(new Uri("avares://Avalonia.Skia.UnitTests/NotFound"), name);
|
|
|
|
var glyphTypeface = new Typeface(fontFamily).GlyphTypeface;
|
|
|
|
Assert.Equal("Arial", glyphTypeface.FamilyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[Win32Fact("Windows specific font")]
|
|
public void Should_Get_Regular_Font_After_Matching_Italic_Font()
|
|
{
|
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
Assert.True(FontManager.Current.TryMatchCharacter('こ', FontStyle.Italic, FontWeight.Normal, FontStretch.Normal, null, null, out var italicTypeface));
|
|
|
|
Assert.Equal(FontSimulations.None, italicTypeface.GlyphTypeface.FontSimulations);
|
|
|
|
Assert.Equal("Yu Gothic UI", italicTypeface.GlyphTypeface.FamilyName);
|
|
|
|
Assert.NotEqual(FontStyle.Normal, italicTypeface.Style);
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter('こ', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var regularTypeface));
|
|
|
|
Assert.Equal("Yu Gothic UI", regularTypeface.GlyphTypeface.FamilyName);
|
|
|
|
Assert.Equal(FontStyle.Normal, regularTypeface.Style);
|
|
|
|
Assert.NotEqual(((SkiaTypeface)italicTypeface.GlyphTypeface.PlatformTypeface).SKTypeface, ((SkiaTypeface)regularTypeface.GlyphTypeface.PlatformTypeface).SKTypeface);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Should_Fallback_When_Font_Family_Is_Empty()
|
|
{
|
|
using (UnitTestApplication.Start(
|
|
TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
|
{
|
|
using (AvaloniaLocator.EnterScope())
|
|
{
|
|
var typeface = new Typeface(string.Empty);
|
|
Assert.NotNull(typeface.FontFamily);
|
|
}
|
|
}
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(FontWeight.Normal, "Inter")]
|
|
[InlineData(FontWeight.Bold, "Inter")]
|
|
[InlineData(FontWeight.SemiBold, "Inter SemiBold")]
|
|
[InlineData(FontWeight.SemiLight, "Inter Light")]
|
|
public void TryMatchCharacter_Should_Return_Correct_Weight(FontWeight requestedWeight, string expectedFamilyName)
|
|
{
|
|
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()));
|
|
using var scope = AvaloniaLocator.EnterScope();
|
|
|
|
FontManager.Current.AddFontCollection(new InterFontCollection());
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter(
|
|
'A', FontStyle.Normal, requestedWeight, FontStretch.Normal, new FontFamily("fonts:Inter#Inter"), null, out var typeface));
|
|
|
|
Assert.NotNull(typeface);
|
|
Assert.Equal(expectedFamilyName, typeface.GlyphTypeface.FamilyName);
|
|
Assert.Equal(requestedWeight, typeface.Weight);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(FontStretch.Normal)]
|
|
[InlineData(FontStretch.Condensed)]
|
|
[InlineData(FontStretch.Expanded)]
|
|
[InlineData(FontStretch.SemiCondensed)]
|
|
[InlineData(FontStretch.SemiExpanded)]
|
|
public void TryMatchCharacter_Should_Return_Correct_Stretch(FontStretch requestedStretch)
|
|
{
|
|
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()));
|
|
using var scope = AvaloniaLocator.EnterScope();
|
|
|
|
FontManager.Current.AddFontCollection(new InterFontCollection());
|
|
|
|
Assert.True(FontManager.Current.TryMatchCharacter(
|
|
'A', FontStyle.Normal, FontWeight.Normal, requestedStretch, new FontFamily("fonts:Inter#Inter"), null, out var typeface));
|
|
|
|
Assert.NotNull(typeface);
|
|
Assert.Equal("Inter", typeface.GlyphTypeface.FamilyName);
|
|
Assert.Equal(requestedStretch, typeface.Stretch);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetGlyphTypeface_Should_Use_Perfect_Match_In_Collection_Before_Nearest_Match()
|
|
{
|
|
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()));
|
|
using var scope = AvaloniaLocator.EnterScope();
|
|
|
|
// Load bold font (Inter-Bold.ttf) first
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Inter", FontStyle.Normal, FontWeight.Bold), out var boldGlyphTypeface));
|
|
Assert.NotNull(boldGlyphTypeface);
|
|
Assert.Equal("Inter", boldGlyphTypeface.FamilyName);
|
|
Assert.Equal(FontWeight.Bold, boldGlyphTypeface.Weight);
|
|
|
|
// Normal font (Inter-Regular.ttf) should be loaded since it's a perfect match, instead of falling back
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Inter", FontStyle.Normal, FontWeight.Normal), out var regularGlyphTypeface));
|
|
Assert.NotNull(regularGlyphTypeface);
|
|
Assert.NotSame(regularGlyphTypeface, boldGlyphTypeface);
|
|
Assert.Equal("Inter", regularGlyphTypeface.FamilyName);
|
|
Assert.Equal(FontWeight.Normal, regularGlyphTypeface.Weight);
|
|
|
|
// Nearest match should still work (650 falls back to 700 Bold)
|
|
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Inter", FontStyle.Normal, (FontWeight)650), out var nearestMatchTypeface));
|
|
Assert.Same(boldGlyphTypeface, nearestMatchTypeface);
|
|
}
|
|
}
|
|
}
|
|
|