36 changed files with 1059 additions and 656 deletions
@ -0,0 +1,298 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Media.Fonts |
||||
|
{ |
||||
|
public class EmbeddedFontCollection : IFontCollection |
||||
|
{ |
||||
|
private readonly Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = |
||||
|
new Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>>(); |
||||
|
|
||||
|
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1); |
||||
|
|
||||
|
private readonly Uri _key; |
||||
|
|
||||
|
private readonly Uri _source; |
||||
|
|
||||
|
public EmbeddedFontCollection(Uri key, Uri source) |
||||
|
{ |
||||
|
_key = key; |
||||
|
|
||||
|
if(!source.IsAvares() && !source.IsAbsoluteResm()) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(source), "Specified source uri does not follow the resm: or avares: scheme."); |
||||
|
} |
||||
|
|
||||
|
_source = source; |
||||
|
} |
||||
|
|
||||
|
public Uri Key => _key; |
||||
|
|
||||
|
public FontFamily this[int index] => _fontFamilies[index]; |
||||
|
|
||||
|
public int Count => _fontFamilies.Count; |
||||
|
|
||||
|
public void Initialize(IFontManagerImpl fontManager) |
||||
|
{ |
||||
|
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); |
||||
|
|
||||
|
var fontAssets = FontFamilyLoader.LoadFontAssets(_source); |
||||
|
|
||||
|
foreach (var fontAsset in fontAssets) |
||||
|
{ |
||||
|
var stream = assetLoader.Open(fontAsset); |
||||
|
|
||||
|
if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface)) |
||||
|
{ |
||||
|
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces)) |
||||
|
{ |
||||
|
glyphTypefaces = new Dictionary<FontCollectionKey, IGlyphTypeface>(); |
||||
|
|
||||
|
_glyphTypefaceCache.Add(glyphTypeface.FamilyName, glyphTypefaces); |
||||
|
|
||||
|
_fontFamilies.Add(new FontFamily(_key, glyphTypeface.FamilyName)); |
||||
|
} |
||||
|
|
||||
|
var key = new FontCollectionKey( |
||||
|
glyphTypeface.Style, |
||||
|
glyphTypeface.Weight, |
||||
|
glyphTypeface.Stretch); |
||||
|
|
||||
|
if (!glyphTypefaces.ContainsKey(key)) |
||||
|
{ |
||||
|
glyphTypefaces.Add(key, glyphTypeface); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
foreach (var fontFamily in _fontFamilies) |
||||
|
{ |
||||
|
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out var glyphTypefaces)) |
||||
|
{ |
||||
|
foreach (var glyphTypeface in glyphTypefaces.Values) |
||||
|
{ |
||||
|
glyphTypeface.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
|
||||
|
public IEnumerator<FontFamily> GetEnumerator() => _fontFamilies.GetEnumerator(); |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||
|
|
||||
|
public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
||||
|
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
||||
|
{ |
||||
|
var key = new FontCollectionKey(style, weight, stretch); |
||||
|
|
||||
|
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) |
||||
|
{ |
||||
|
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Try to find a partially matching font
|
||||
|
for (var i = 0; i < Count; i++) |
||||
|
{ |
||||
|
var fontFamily = _fontFamilies[i]; |
||||
|
|
||||
|
if (fontFamily.Name.ToLower(CultureInfo.InvariantCulture).StartsWith(familyName.ToLower(CultureInfo.InvariantCulture))) |
||||
|
{ |
||||
|
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) && |
||||
|
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
glyphTypeface = null; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static bool TryGetNearestMatch( |
||||
|
Dictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
||||
|
FontCollectionKey key, |
||||
|
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (key.Style != FontStyle.Normal) |
||||
|
{ |
||||
|
key = key with { Style = FontStyle.Normal }; |
||||
|
} |
||||
|
|
||||
|
if (key.Stretch != FontStretch.Normal) |
||||
|
{ |
||||
|
if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (key.Weight != FontWeight.Normal) |
||||
|
{ |
||||
|
if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
key = key with { Stretch = FontStretch.Normal }; |
||||
|
} |
||||
|
|
||||
|
if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
//Take the first glyph typeface we can find.
|
||||
|
foreach (var typeface in glyphTypefaces.Values) |
||||
|
{ |
||||
|
glyphTypeface = typeface; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static bool TryFindStretchFallback( |
||||
|
Dictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
||||
|
FontCollectionKey key, |
||||
|
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
||||
|
{ |
||||
|
glyphTypeface = null; |
||||
|
|
||||
|
var stretch = (int)key.Stretch; |
||||
|
|
||||
|
if (stretch < 5) |
||||
|
{ |
||||
|
for (var i = 0; stretch + i < 9; i++) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (var i = 0; stretch - i > 1; i++) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static bool TryFindWeightFallback( |
||||
|
Dictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
||||
|
FontCollectionKey key, |
||||
|
[NotNullWhen(true)] out IGlyphTypeface? typeface) |
||||
|
{ |
||||
|
typeface = null; |
||||
|
var weight = (int)key.Weight; |
||||
|
|
||||
|
//If the target weight given is between 400 and 500 inclusive
|
||||
|
if (weight >= 400 && weight <= 500) |
||||
|
{ |
||||
|
//Look for available weights between the target and 500, in ascending order.
|
||||
|
for (var i = 0; weight + i <= 500; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If no match is found, look for available weights less than the target, in descending order.
|
||||
|
for (var i = 0; weight - i >= 100; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If no match is found, look for available weights greater than 500, in ascending order.
|
||||
|
for (var i = 0; weight + i <= 900; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If a weight less than 400 is given, look for available weights less than the target, in descending order.
|
||||
|
if (weight < 400) |
||||
|
{ |
||||
|
for (var i = 0; weight - i >= 100; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If no match is found, look for available weights less than the target, in descending order.
|
||||
|
for (var i = 0; weight + i <= 900; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
|
||||
|
if (weight > 500) |
||||
|
{ |
||||
|
for (var i = 0; weight + i <= 900; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//If no match is found, look for available weights less than the target, in descending order.
|
||||
|
for (var i = 0; weight - i >= 100; i += 50) |
||||
|
{ |
||||
|
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
namespace Avalonia.Media.Fonts |
||||
|
{ |
||||
|
public readonly record struct FontCollectionKey(FontStyle Style, FontWeight Weight, FontStretch Stretch); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media.Fonts |
||||
|
{ |
||||
|
public interface IFontCollection : IReadOnlyList<FontFamily>, IDisposable |
||||
|
{ |
||||
|
Uri Key { get; } |
||||
|
|
||||
|
void Initialize(IFontManagerImpl fontManager); |
||||
|
|
||||
|
bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
||||
|
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media.Fonts |
||||
|
{ |
||||
|
internal class SystemFontCollection : IFontCollection |
||||
|
{ |
||||
|
private readonly Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = |
||||
|
new Dictionary<string, Dictionary<FontCollectionKey, IGlyphTypeface>>(); |
||||
|
|
||||
|
private readonly FontManager _fontManager; |
||||
|
private readonly string[] _familyNames; |
||||
|
|
||||
|
public SystemFontCollection(FontManager fontManager) |
||||
|
{ |
||||
|
_fontManager = fontManager; |
||||
|
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); |
||||
|
} |
||||
|
|
||||
|
public Uri Key => new Uri("fontCollection:SystemFonts"); |
||||
|
|
||||
|
public FontFamily this[int index] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var familyName = _familyNames[index]; |
||||
|
|
||||
|
return new FontFamily(familyName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int Count => _familyNames.Length; |
||||
|
|
||||
|
public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
||||
|
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
||||
|
{ |
||||
|
if (familyName == FontFamily.DefaultFontFamilyName) |
||||
|
{ |
||||
|
familyName = _fontManager.DefaultFontFamilyName; |
||||
|
} |
||||
|
|
||||
|
var key = new FontCollectionKey(style, weight, stretch); |
||||
|
|
||||
|
if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) |
||||
|
{ |
||||
|
glyphTypefaces = new Dictionary<FontCollectionKey, IGlyphTypeface>(); |
||||
|
|
||||
|
_glyphTypefaceCache.Add(familyName, glyphTypefaces); |
||||
|
} |
||||
|
|
||||
|
if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface)) |
||||
|
{ |
||||
|
glyphTypefaces.Add(key, glyphTypeface); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public void Initialize(IFontManagerImpl fontManager) |
||||
|
{ |
||||
|
//We initialize the system font collection during construction.
|
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
public IEnumerator<FontFamily> GetEnumerator() |
||||
|
{ |
||||
|
foreach (var familyName in _familyNames) |
||||
|
{ |
||||
|
yield return new FontFamily(familyName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void IDisposable.Dispose() |
||||
|
{ |
||||
|
foreach (var glyphTypefaces in _glyphTypefaceCache.Values) |
||||
|
{ |
||||
|
foreach (var pair in glyphTypefaces) |
||||
|
{ |
||||
|
pair.Value.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Media.Fonts; |
||||
|
|
||||
|
namespace Avalonia.Fonts.Inter |
||||
|
{ |
||||
|
public sealed class InterFontCollection : EmbeddedFontCollection |
||||
|
{ |
||||
|
public InterFontCollection() : base( |
||||
|
new Uri("fonts:Inter", UriKind.Absolute), |
||||
|
new Uri("avares://Avalonia.Fonts.Inter/Assets", UriKind.Absolute)) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,198 +0,0 @@ |
|||||
using System.Collections.Concurrent; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Avalonia.Media; |
|
||||
using SkiaSharp; |
|
||||
|
|
||||
namespace Avalonia.Skia |
|
||||
{ |
|
||||
internal class SKTypefaceCollection |
|
||||
{ |
|
||||
private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces = new(); |
|
||||
|
|
||||
public void AddTypeface(Typeface key, SKTypeface typeface) |
|
||||
{ |
|
||||
_typefaces.TryAdd(key, typeface); |
|
||||
} |
|
||||
|
|
||||
public SKTypeface? Get(Typeface typeface) |
|
||||
{ |
|
||||
return GetNearestMatch(typeface); |
|
||||
} |
|
||||
|
|
||||
private SKTypeface? GetNearestMatch(Typeface key) |
|
||||
{ |
|
||||
if (_typefaces.Count == 0) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
if (_typefaces.TryGetValue(key, out var typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
|
|
||||
if(key.Style != FontStyle.Normal) |
|
||||
{ |
|
||||
key = new Typeface(key.FontFamily, FontStyle.Normal, key.Weight, key.Stretch); |
|
||||
} |
|
||||
|
|
||||
if(key.Stretch != FontStretch.Normal) |
|
||||
{ |
|
||||
if(TryFindStretchFallback(key, out typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
|
|
||||
if(key.Weight != FontWeight.Normal) |
|
||||
{ |
|
||||
if (TryFindStretchFallback(new Typeface(key.FontFamily, key.Style, FontWeight.Normal, key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
key = new Typeface(key.FontFamily, key.Style, key.Weight, FontStretch.Normal); |
|
||||
} |
|
||||
|
|
||||
if(TryFindWeightFallback(key, out typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
|
|
||||
if (TryFindStretchFallback(key, out typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
|
|
||||
//Nothing was found so we try some regular typeface.
|
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface)) |
|
||||
{ |
|
||||
return typeface; |
|
||||
} |
|
||||
|
|
||||
SKTypeface? skTypeface = null; |
|
||||
|
|
||||
foreach(var pair in _typefaces) |
|
||||
{ |
|
||||
skTypeface = pair.Value; |
|
||||
|
|
||||
if (skTypeface.FamilyName.Contains(key.FontFamily.Name)) |
|
||||
{ |
|
||||
return skTypeface; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return skTypeface; |
|
||||
} |
|
||||
|
|
||||
private bool TryFindStretchFallback(Typeface key, [NotNullWhen(true)] out SKTypeface? typeface) |
|
||||
{ |
|
||||
typeface = null; |
|
||||
var stretch = (int)key.Stretch; |
|
||||
|
|
||||
if (stretch < 5) |
|
||||
{ |
|
||||
for (var i = 0; stretch + i < 9; i++) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, key.Weight, (FontStretch)(stretch + i)), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
for (var i = 0; stretch - i > 1; i++) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, key.Weight, (FontStretch)(stretch - i)), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private bool TryFindWeightFallback(Typeface key, [NotNullWhen(true)] out SKTypeface? typeface) |
|
||||
{ |
|
||||
typeface = null; |
|
||||
var weight = (int)key.Weight; |
|
||||
|
|
||||
//If the target weight given is between 400 and 500 inclusive
|
|
||||
if (weight >= 400 && weight <= 500) |
|
||||
{ |
|
||||
//Look for available weights between the target and 500, in ascending order.
|
|
||||
for (var i = 0; weight + i <= 500; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If no match is found, look for available weights less than the target, in descending order.
|
|
||||
for (var i = 0; weight - i >= 100; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If no match is found, look for available weights greater than 500, in ascending order.
|
|
||||
for (var i = 0; weight + i <= 900; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If a weight less than 400 is given, look for available weights less than the target, in descending order.
|
|
||||
if (weight < 400) |
|
||||
{ |
|
||||
for (var i = 0; weight - i >= 100; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If no match is found, look for available weights less than the target, in descending order.
|
|
||||
for (var i = 0; weight + i <= 900; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
|
|
||||
if (weight > 500) |
|
||||
{ |
|
||||
for (var i = 0; weight + i <= 900; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//If no match is found, look for available weights less than the target, in descending order.
|
|
||||
for (var i = 0; weight - i >= 100; i += 50) |
|
||||
{ |
|
||||
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,73 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Concurrent; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Media.Fonts; |
|
||||
using Avalonia.Platform; |
|
||||
using SkiaSharp; |
|
||||
|
|
||||
namespace Avalonia.Skia |
|
||||
{ |
|
||||
internal static class SKTypefaceCollectionCache |
|
||||
{ |
|
||||
private static readonly ConcurrentDictionary<FontFamily, SKTypefaceCollection> s_cachedCollections; |
|
||||
|
|
||||
static SKTypefaceCollectionCache() |
|
||||
{ |
|
||||
s_cachedCollections = new ConcurrentDictionary<FontFamily, SKTypefaceCollection>(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the or add typeface collection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="fontFamily">The font family.</param>
|
|
||||
/// <returns></returns>
|
|
||||
public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily) |
|
||||
{ |
|
||||
return s_cachedCollections.GetOrAdd(fontFamily, CreateCustomFontCollection); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Creates the custom font collection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="fontFamily">The font family.</param>
|
|
||||
/// <returns></returns>
|
|
||||
private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily) |
|
||||
{ |
|
||||
var typeFaceCollection = new SKTypefaceCollection(); |
|
||||
|
|
||||
if (fontFamily.Key is not { } fontFamilyKey) |
|
||||
{ |
|
||||
return typeFaceCollection; |
|
||||
} |
|
||||
|
|
||||
var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamilyKey); |
|
||||
|
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); |
|
||||
|
|
||||
foreach (var asset in fontAssets) |
|
||||
{ |
|
||||
var assetStream = assetLoader.Open(asset); |
|
||||
|
|
||||
if (assetStream == null) |
|
||||
throw new InvalidOperationException("Asset could not be loaded."); |
|
||||
|
|
||||
var typeface = SKTypeface.FromStream(assetStream); |
|
||||
|
|
||||
if (typeface == null) |
|
||||
throw new InvalidOperationException("Typeface could not be loaded."); |
|
||||
|
|
||||
if (!typeface.FamilyName.Contains(fontFamily.Name)) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(), |
|
||||
(FontWeight)typeface.FontWeight, (FontStretch)typeface.FontWidth); |
|
||||
|
|
||||
typeFaceCollection.AddTypeface(key, typeface); |
|
||||
} |
|
||||
|
|
||||
return typeFaceCollection; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,58 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Fonts; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Skia.UnitTests.Media |
||||
|
{ |
||||
|
public class EmbeddedFontCollectionTests |
||||
|
{ |
||||
|
private const string s_notoMono = |
||||
|
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"; |
||||
|
|
||||
|
[InlineData(FontWeight.SemiLight, FontStyle.Normal)] |
||||
|
[InlineData(FontWeight.Bold, FontStyle.Italic)] |
||||
|
[InlineData(FontWeight.Heavy, FontStyle.Oblique)] |
||||
|
[Theory] |
||||
|
public void Should_Get_Near_Matching_Typeface(FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()))) |
||||
|
{ |
||||
|
var fontCollection = new EmbeddedFontCollection(FontManager.Current, new Uri(s_notoMono)); |
||||
|
|
||||
|
Assert.True(fontCollection.TryGetGlyphTypeface("Noto Mono", fontStyle, fontWeight, FontStretch.Normal, out var glyphTypeface)); |
||||
|
|
||||
|
var actual = glyphTypeface?.FamilyName; |
||||
|
|
||||
|
Assert.Equal("Noto Mono", actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Not_Get_Typeface_For_Invalid_FamilyName() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()))) |
||||
|
{ |
||||
|
var fontCollection = new EmbeddedFontCollection(FontManager.Current, new Uri(s_notoMono)); |
||||
|
|
||||
|
Assert.False(fontCollection.TryGetGlyphTypeface("ABC", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var glyphTypeface)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Get_Typeface_For_Partial_FamilyName() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl()))) |
||||
|
{ |
||||
|
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#T", UriKind.Absolute); |
||||
|
|
||||
|
var fontCollection = new EmbeddedFontCollection(FontManager.Current, source); |
||||
|
|
||||
|
Assert.True(fontCollection.TryGetGlyphTypeface("T", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var glyphTypeface)); |
||||
|
|
||||
|
Assert.Equal("Twitter Color Emoji", glyphTypeface.FamilyName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,63 +0,0 @@ |
|||||
using Avalonia.Media; |
|
||||
using Avalonia.UnitTests; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Skia.UnitTests.Media |
|
||||
{ |
|
||||
public class SKTypefaceCollectionCacheTests |
|
||||
{ |
|
||||
private const string s_notoMono = |
|
||||
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"; |
|
||||
|
|
||||
[InlineData(s_notoMono, FontWeight.SemiLight, FontStyle.Normal)] |
|
||||
[InlineData(s_notoMono, FontWeight.Bold, FontStyle.Italic)] |
|
||||
[InlineData(s_notoMono, FontWeight.Heavy, FontStyle.Oblique)] |
|
||||
[Theory] |
|
||||
public void Should_Get_Near_Matching_Typeface(string familyName, FontWeight fontWeight, FontStyle fontStyle) |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) |
|
||||
{ |
|
||||
var fontFamily = new FontFamily(familyName); |
|
||||
|
|
||||
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(fontFamily); |
|
||||
|
|
||||
var actual = typefaceCollection.Get(new Typeface(fontFamily, fontStyle, fontWeight))?.FamilyName; |
|
||||
|
|
||||
Assert.Equal("Noto Mono", actual); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_Get_Typeface_For_Invalid_FamilyName() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) |
|
||||
{ |
|
||||
var notoMono = |
|
||||
new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); |
|
||||
|
|
||||
var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono); |
|
||||
|
|
||||
var typeface = notoMonoCollection.Get(new Typeface("ABC")); |
|
||||
|
|
||||
Assert.NotNull(typeface); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_Get_Typeface_For_Partial_FamilyName() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) |
|
||||
{ |
|
||||
var fontFamily = new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#T"); |
|
||||
|
|
||||
var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(fontFamily); |
|
||||
|
|
||||
var typeface = fontCollection.Get(new Typeface(fontFamily)); |
|
||||
|
|
||||
Assert.NotNull(typeface); |
|
||||
|
|
||||
Assert.Equal("Twitter Color Emoji", typeface.FamilyName); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue