|
@@ -1,7 +1,10 @@
|
|
|
#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.UnitTests;
|
|
@@ -11,6 +14,9 @@ namespace Avalonia.Skia.UnitTests.Media
|
|
|
{
|
|
|
public class FontCollectionTests
|
|
|
{
|
|
|
+ private const string NotoMono =
|
|
|
+ "resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests";
|
|
|
+
|
|
|
[InlineData("Hello World 6", "Hello World 6", FontStyle.Normal, FontWeight.Normal)]
|
|
|
[InlineData("Hello World Italic", "Hello World", FontStyle.Italic, FontWeight.Normal)]
|
|
|
[InlineData("Hello World Italic Bold", "Hello World", FontStyle.Italic, FontWeight.Bold)]
|
|
@@ -41,8 +47,6 @@ namespace Avalonia.Skia.UnitTests.Media
|
|
|
|
|
|
Assert.True(fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface));
|
|
|
|
|
|
- Assert.True(glyphTypeface.FontSimulations == FontSimulations.Bold);
|
|
|
-
|
|
|
Assert.True(fontCollection.GlyphTypefaceCache.TryGetValue("Arial", out var glyphTypefaces));
|
|
|
|
|
|
Assert.Equal(2, glyphTypefaces.Count);
|
|
@@ -64,5 +68,108 @@ namespace Avalonia.Skia.UnitTests.Media
|
|
|
|
|
|
public IDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>> GlyphTypefaceCache => _glyphTypefaceCache;
|
|
|
}
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Should_Use_Fallback()
|
|
|
+ {
|
|
|
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
|
|
+ {
|
|
|
+ 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 });
|
|
|
+
|
|
|
+ fontCollection.Initialize(new CustomFontManagerImpl());
|
|
|
+
|
|
|
+ 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))
|
|
|
+ {
|
|
|
+ var source = new Uri(NotoMono + "#Noto Mono", UriKind.Absolute);
|
|
|
+
|
|
|
+ var ignorable = new FontFamily(new Uri(NotoMono, UriKind.Absolute), "Noto Mono");
|
|
|
+
|
|
|
+ var typeface = new Typeface(ignorable);
|
|
|
+
|
|
|
+ var fontCollection = new CustomizableFontCollection(source, source, null, new[] { ignorable });
|
|
|
+
|
|
|
+ fontCollection.Initialize(new CustomFontManagerImpl());
|
|
|
+
|
|
|
+ Assert.False(fontCollection.TryCreateSyntheticGlyphTypeface(
|
|
|
+ typeface.GlyphTypeface,
|
|
|
+ FontStyle.Italic,
|
|
|
+ FontWeight.DemiBold,
|
|
|
+ FontStretch.Normal,
|
|
|
+ out var syntheticGlyphTypeface));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CustomizableFontCollection : EmbeddedFontCollection
|
|
|
+ {
|
|
|
+ private readonly IReadOnlyList<FontFallback>? _fallbacks;
|
|
|
+ private readonly IReadOnlyList<FontFamily>? _ignorables;
|
|
|
+
|
|
|
+ public CustomizableFontCollection(Uri key, Uri source, IReadOnlyList<FontFallback>? fallbacks = null, IReadOnlyList<FontFamily>? 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|