diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index ee8dbcedc1..5440b76bfe 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -131,11 +131,8 @@ [self updateRenderTarget]; auto reason = [self inLiveResize] ? ResizeUser : _resizeReason; - - if(_parent->IsShown()) - { - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason); - } + + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason); } } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index c3add53368..edf6bcf508 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -4,6 +4,7 @@ // #import +#import #include "common.h" #include "AvnView.h" #include "menu.h" @@ -295,15 +296,24 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso } @try { - if(x != lastSize.width || y != lastSize.height) { - lastSize = NSSize{x, y}; - + if(x != lastSize.width || y != lastSize.height) + { if (!_shown) { - BaseEvents->Resized(AvnSize{x, y}, reason); - } else if (Window != nullptr) { - [Window setContentSize:lastSize]; - [Window invalidateShadow]; + auto screenSize = [Window screen].visibleFrame.size; + + if (x > screenSize.width) { + x = screenSize.width; + } + + if (y > screenSize.height) { + y = screenSize.height; + } } + + lastSize = NSSize{x, y}; + + [Window setContentSize:lastSize]; + [Window invalidateShadow]; } } @finally { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index cf1ee6943d..840f2c9e88 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -54,6 +54,11 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { WindowBaseImpl::Show(activate, isDialog); GetWindowState(&_actualWindowState); + + if(IsZoomed()) { + _lastWindowState = _actualWindowState; + } + return SetWindowState(_lastWindowState); } } diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index e55f003133..85c159467b 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Fonts.Inter; using Avalonia.Headless; using Avalonia.LogicalTree; using Avalonia.Threading; @@ -124,6 +125,7 @@ namespace ControlCatalog.NetCore EnableIme = true }) .UseSkia() + .WithInterFont() .AfterSetup(builder => { builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs index 6d624c9a07..6d759597b5 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs @@ -18,7 +18,7 @@ namespace ControlCatalog.Pages { AvaloniaXamlLoader.Load(this); var fontComboBox = this.Get("fontComboBox"); - fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x)); + fontComboBox.Items = FontManager.Current.SystemFonts; fontComboBox.SelectedIndex = 0; } } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index 5162eeee92..bd6910dd4d 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -1,41 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - Normal - Minimized - Maximized - FullScreen - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + Normal + Minimized + Maximized + FullScreen + + + + + + + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 1a267ea20b..f0be34fdaa 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -7,6 +7,25 @@ using Avalonia.Threading; namespace IntegrationTestApp { + public class MeasureBorder : Border + { + protected override Size MeasureOverride(Size availableSize) + { + MeasuredWith = availableSize; + + return base.MeasureOverride(availableSize); + } + + public static readonly StyledProperty MeasuredWithProperty = AvaloniaProperty.Register( + nameof(MeasuredWith)); + + public Size MeasuredWith + { + get => GetValue(MeasuredWithProperty); + set => SetValue(MeasuredWithProperty, value); + } + } + public class ShowWindowTest : Window { private readonly DispatcherTimer? _timer; diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 2dabb29e76..595a2f3474 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Media.Fonts; using Avalonia.Platform; +using Avalonia.Utilities; namespace Avalonia.Media { @@ -13,9 +15,11 @@ namespace Avalonia.Media /// public sealed class FontManager { - private readonly ConcurrentDictionary _glyphTypefaceCache = - new ConcurrentDictionary(); - private readonly FontFamily _defaultFontFamily; + internal static Uri SystemFontsKey = new Uri("fonts:SystemFonts"); + + public const string FontCollectionScheme = "fonts"; + + private readonly ConcurrentDictionary _fontCollections = new ConcurrentDictionary(); private readonly IReadOnlyList? _fontFallbacks; public FontManager(IFontManagerImpl platformImpl) @@ -33,9 +37,12 @@ namespace Avalonia.Media throw new InvalidOperationException("Default font family name can't be null or empty."); } - _defaultFontFamily = new FontFamily(DefaultFontFamilyName); + AddFontCollection(new SystemFontCollection(this)); } + /// + /// Get the current font manager instance. + /// public static FontManager Current { get @@ -57,11 +64,6 @@ namespace Avalonia.Media } } - /// - /// - /// - public IFontManagerImpl PlatformImpl { get; } - /// /// Gets the system's default font family's name. /// @@ -71,41 +73,109 @@ namespace Avalonia.Media } /// - /// Get all installed font family names. + /// Get all system fonts. /// - /// If true the font collection is updated. - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) => - PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); + public IFontCollection SystemFonts => _fontCollections[SystemFontsKey]; + + internal IFontManagerImpl PlatformImpl { get; } /// - /// Returns a new , or an existing one if a matching exists. + /// Tries to get a glyph typeface for specified typeface. /// /// The typeface. + /// The created glyphTypeface /// - /// The . + /// True, if the could create the glyph typeface, False otherwise. /// - public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) + public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) { - while (true) + glyphTypeface = null; + + var fontFamily = typeface.FontFamily; + + if (fontFamily.Key is FontFamilyKey key) { - if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface)) + var source = key.Source; + + if (!source.IsAbsoluteUri) { - return glyphTypeface; + if (key.BaseUri == null) + { + throw new NotSupportedException($"{nameof(key.BaseUri)} can't be null."); + } + + source = new Uri(key.BaseUri, source); } - glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface); + if (!_fontCollections.TryGetValue(source, out var fontCollection)) + { + var embeddedFonts = new EmbeddedFontCollection(source, source); + + embeddedFonts.Initialize(PlatformImpl); - if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) + if (embeddedFonts.Count > 0 && _fontCollections.TryAdd(source, embeddedFonts)) + { + fontCollection = embeddedFonts; + } + } + + if (fontCollection != null && fontCollection.TryGetGlyphTypeface(fontFamily.FamilyNames.PrimaryFamilyName, + typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface)) { - return glyphTypeface; + return true; } - if (typeface.FontFamily == _defaultFontFamily) + if (!fontFamily.FamilyNames.HasFallbacks) { - throw new InvalidOperationException($"Could not create glyph typeface for: {typeface.FontFamily.Name}."); + return false; } + } - typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight); + foreach (var familyName in fontFamily.FamilyNames) + { + if (SystemFonts.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface)) + { + return true; + } + } + + return SystemFonts.TryGetGlyphTypeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface); + } + + /// + /// Add a font collection to the manager. + /// + /// The font collection. + /// + /// If a font collection's key is already present the collection is replaced. + public void AddFontCollection(IFontCollection fontCollection) + { + var key = fontCollection.Key; + + if (!fontCollection.Key.IsFontCollection()) + { + throw new ArgumentException("Font collection Key should follow the fonts: scheme.", nameof(fontCollection)); + } + + _fontCollections.AddOrUpdate(key, fontCollection, (_, oldCollection) => + { + oldCollection.Dispose(); + + return fontCollection; + }); + + fontCollection.Initialize(PlatformImpl); + } + + /// + /// Removes the font collection that corresponds to specified key. + /// + /// The font collection's key. + public void RemoveFontCollection(Uri key) + { + if (_fontCollections.TryRemove(key, out var fontCollection)) + { + fontCollection.Dispose(); } } @@ -123,18 +193,16 @@ namespace Avalonia.Media /// True, if the could match the character to specified parameters, False otherwise. /// public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontStretch fontStretch, - FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface) + FontStretch fontStretch, FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface) { - if(_fontFallbacks != null) + if (_fontFallbacks != null) { foreach (var fallback in _fontFallbacks) { typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch); - var glyphTypeface = GetOrAddGlyphTypeface(typeface); - - if(glyphTypeface.TryGetGlyph((uint)codepoint, out _)){ + if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _)) + { return true; } } diff --git a/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs new file mode 100644 index 0000000000..f2fb490592 --- /dev/null +++ b/src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Avalonia.Platform; + +namespace Avalonia.Media.Fonts +{ + public class EmbeddedFontCollection : IFontCollection + { + private readonly ConcurrentDictionary> _glyphTypefaceCache = new(); + + private readonly List _fontFamilies = new List(1); + + private readonly Uri _key; + + private readonly Uri _source; + + public EmbeddedFontCollection(Uri key, Uri source) + { + _key = key; + + _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(); + + 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 ConcurrentDictionary(); + + if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces)) + { + _fontFamilies.Add(new FontFamily(_key, glyphTypeface.FamilyName)); + } + } + + var key = new FontCollectionKey( + glyphTypeface.Style, + glyphTypeface.Weight, + glyphTypeface.Stretch); + + glyphTypefaces.TryAdd(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 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( + ConcurrentDictionary 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( + ConcurrentDictionary 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( + ConcurrentDictionary 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; + } + } +} diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs new file mode 100644 index 0000000000..0d0dc3016e --- /dev/null +++ b/src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs @@ -0,0 +1,4 @@ +namespace Avalonia.Media.Fonts +{ + public readonly record struct FontCollectionKey(FontStyle Style, FontWeight Weight, FontStretch Stretch); +} diff --git a/src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs index 365fb6e412..37992c895e 100644 --- a/src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs @@ -11,22 +11,30 @@ namespace Avalonia.Media.Fonts /// /// Loads all font assets that belong to the specified /// - /// + /// /// - public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) => - IsFontTtfOrOtf(fontFamilyKey.Source) ? - GetFontAssetsByExpression(fontFamilyKey) : - GetFontAssetsBySource(fontFamilyKey); + public static IEnumerable LoadFontAssets(Uri source) + { + if (source.IsAvares() || source.IsAbsoluteResm()) + { + return IsFontTtfOrOtf(source) ? + GetFontAssetsByExpression(source) : + GetFontAssetsBySource(source); + } + + return Enumerable.Empty(); + } + /// /// Searches for font assets at a given location and returns a quantity of found assets /// - /// + /// /// - private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) + private static IEnumerable GetFontAssetsBySource(Uri source) { var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); + var availableAssets = assetLoader.GetAssets(source, null); return availableAssets.Where(x => IsFontTtfOrOtf(x)); } @@ -34,60 +42,50 @@ namespace Avalonia.Media.Fonts /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// File names can target multiple files with * wildcard. For example "FontFile*.ttf" /// - /// + /// /// - private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) + private static IEnumerable GetFontAssetsByExpression(Uri source) { - var (fileNameWithoutExtension, extension) = GetFileName(fontFamilyKey, out var location); - var filePattern = CreateFilePattern(fontFamilyKey, location, fileNameWithoutExtension); + var (fileNameWithoutExtension, extension) = GetFileName(source, out var location); + var filePattern = CreateFilePattern(source, location, fileNameWithoutExtension); var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - var availableResources = assetLoader.GetAssets(location, fontFamilyKey.BaseUri); + var availableResources = assetLoader.GetAssets(location, null); return availableResources.Where(x => IsContainsFile(x, filePattern, extension)); } private static (string fileNameWithoutExtension, string extension) GetFileName( - FontFamilyKey fontFamilyKey, out Uri location) + Uri source, out Uri location) { - if (fontFamilyKey.Source.IsAbsoluteResm()) + if (source.IsAbsoluteResm()) { - var fileName = GetFileNameAndExtension(fontFamilyKey.Source.GetUnescapeAbsolutePath(), '.'); + var fileName = GetFileNameAndExtension(source.GetUnescapeAbsolutePath(), '.'); - var uriLocation = fontFamilyKey.Source.GetUnescapeAbsoluteUri() + var uriLocation = source.GetUnescapeAbsoluteUri() .Replace("." + fileName.fileNameWithoutExtension + fileName.extension, string.Empty); location = new Uri(uriLocation, UriKind.RelativeOrAbsolute); return fileName; } - var filename = GetFileNameAndExtension(fontFamilyKey.Source.OriginalString); + var filename = GetFileNameAndExtension(source.OriginalString); var fullFilename = filename.fileNameWithoutExtension + filename.extension; - if (fontFamilyKey.BaseUri != null) - { - var relativePath = fontFamilyKey.Source.OriginalString - .Replace(fullFilename, string.Empty); - - location = new Uri(fontFamilyKey.BaseUri, relativePath); - } - else - { - var uriString = fontFamilyKey.Source - .GetUnescapeAbsoluteUri() - .Replace(fullFilename, string.Empty); - location = new Uri(uriString); - } + var uriString = source + .GetUnescapeAbsoluteUri() + .Replace(fullFilename, string.Empty); + location = new Uri(uriString); return filename; } private static string CreateFilePattern( - FontFamilyKey fontFamilyKey, Uri location, string fileNameWithoutExtension) + Uri source, Uri location, string fileNameWithoutExtension) { var path = location.GetUnescapeAbsolutePath(); var file = GetSubString(fileNameWithoutExtension, '*'); - return fontFamilyKey.Source.IsAbsoluteResm() + return source.IsAbsoluteResm() ? path + "." + file : path + file; } diff --git a/src/Avalonia.Base/Media/Fonts/IFontCollection.cs b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs new file mode 100644 index 0000000000..814230bcf3 --- /dev/null +++ b/src/Avalonia.Base/Media/Fonts/IFontCollection.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Platform; + +namespace Avalonia.Media.Fonts +{ + public interface IFontCollection : IReadOnlyList, IDisposable + { + /// + /// Get the font collection's key. + /// + Uri Key { get; } + + /// + /// Initializes the font collection. + /// + /// The font manager the collection is registered with. + void Initialize(IFontManagerImpl fontManager); + + /// + /// Try to get a glyph typeface for given parameters. + /// + /// The family name. + /// The font style. + /// The font weight. + /// The font stretch. + /// The glyph typeface. + /// Returns true if a glyph typface can be found; otherwise, false + bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface); + } +} diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs new file mode 100644 index 0000000000..fd332c6ebe --- /dev/null +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Platform; + +namespace Avalonia.Media.Fonts +{ + internal class SystemFontCollection : IFontCollection + { + private readonly ConcurrentDictionary> _glyphTypefaceCache = new(); + + private readonly FontManager _fontManager; + private readonly string[] _familyNames; + + public SystemFontCollection(FontManager fontManager) + { + _fontManager = fontManager; + _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); + } + + public Uri Key => FontManager.SystemFontsKey; + + 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)) + { + if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) + { + return true; + } + else + { + if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) && + glyphTypefaces.TryAdd(key, glyphTypeface)) + { + return true; + } + } + } + + if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface)) + { + glyphTypefaces = new ConcurrentDictionary(); + + if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces)) + { + return true; + } + } + + return false; + } + + public void Initialize(IFontManagerImpl fontManager) + { + //We initialize the system font collection during construction. + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator 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); + } + } +} diff --git a/src/Avalonia.Base/Media/IGlyphTypeface.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs index 9e1e52cb73..09740aac81 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -6,6 +6,26 @@ namespace Avalonia.Media [Unstable] public interface IGlyphTypeface : IDisposable { + /// + /// Gets the family name for the object. + /// + string FamilyName { get; } + + /// + /// Gets the designed weight of the font represented by the object. + /// + FontWeight Weight { get; } + + /// + /// Gets the style for the object. + /// + FontStyle Style { get; } + + /// + /// Gets the value for the object. + /// + FontStretch Stretch { get; } + /// /// Gets the number of glyphs held by this glyph typeface. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index b4734d702b..253c7075fa 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -122,13 +122,14 @@ namespace Avalonia.Media.TextFormatting if (matchFound) { // Fallback found - var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - - if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) - { - return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), - biDiLevel); - } + if(fontManager.TryGetGlyphTypeface(fallbackTypeface, out var fallbackGlyphTypeface)) + { + if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) + { + return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), + biDiLevel); + } + } } // no fallback found diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index 1e744c30c8..e2729c9158 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -80,7 +80,18 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public IGlyphTypeface GlyphTypeface + { + get + { + if(FontManager.Current.TryGetGlyphTypeface(this, out var glyphTypeface)) + { + return glyphTypeface; + } + + throw new InvalidOperationException("Could not create glyphTypeface."); + } + } public static bool operator !=(Typeface a, Typeface b) { diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index cd6e64abaf..116f7cd6e2 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using Avalonia.Media; using Avalonia.Metadata; @@ -17,7 +18,7 @@ namespace Avalonia.Platform /// Get all installed fonts in the system. /// If true the font collection is updated. /// - IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false); + string[] GetInstalledFontFamilyNames(bool checkForUpdates = false); /// /// Tries to match a specified character to a typeface that supports specified font properties. @@ -37,12 +38,27 @@ namespace Avalonia.Platform FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface); /// - /// Creates a glyph typeface. + /// Tries to get a glyph typeface for specified parameters. /// - /// The typeface. - /// 0 - /// The created glyph typeface. Can be Null if it was not possible to create a glyph typeface. + /// The family name. + /// The font style. + /// The font weiht. + /// The font stretch. + /// The created glyphTypeface + /// + /// True, if the could create the glyph typeface, False otherwise. + /// + bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface); + + /// + /// Tries to create a glyph typeface from specified stream. + /// + /// A stream that holds the font's data. + /// The created glyphTypeface + /// + /// True, if the could create the glyph typeface, False otherwise. /// - IGlyphTypeface CreateGlyphTypeface(Typeface typeface); + bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface); } } diff --git a/src/Avalonia.Base/Utilities/UriExtensions.cs b/src/Avalonia.Base/Utilities/UriExtensions.cs index c706f72a63..1f9c694eab 100644 --- a/src/Avalonia.Base/Utilities/UriExtensions.cs +++ b/src/Avalonia.Base/Utilities/UriExtensions.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media; namespace Avalonia.Utilities; @@ -10,7 +11,9 @@ internal static class UriExtensions public static bool IsResm(this Uri uri) => uri.Scheme == "resm"; public static bool IsAvares(this Uri uri) => uri.Scheme == "avares"; - + + public static bool IsFontCollection(this Uri uri) => uri.Scheme == FontManager.FontCollectionScheme; + public static Uri EnsureAbsolute(this Uri uri, Uri? baseUri) { if (uri.IsAbsoluteUri) diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index cf79fcd1a8..64bf92b7cd 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -4,6 +4,8 @@ using System.Reflection; using System.Linq; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; +using Avalonia.Media.Fonts; +using Avalonia.Media; namespace Avalonia { @@ -205,6 +207,19 @@ namespace Avalonia return Self; } + /// + /// Registers an action that is executed with the current font manager. + /// + /// The action. + /// An instance. + public AppBuilder ConfigureFonts(Action action) + { + return AfterSetup(appBuilder => + { + action?.Invoke(FontManager.Current); + }); + } + /// /// Sets up the platform-specific services for the . /// diff --git a/src/Avalonia.Fonts.Inter/AppBuilderExtension.cs b/src/Avalonia.Fonts.Inter/AppBuilderExtension.cs new file mode 100644 index 0000000000..842629c923 --- /dev/null +++ b/src/Avalonia.Fonts.Inter/AppBuilderExtension.cs @@ -0,0 +1,13 @@ +namespace Avalonia.Fonts.Inter +{ + public static class AppBuilderExtension + { + public static AppBuilder WithInterFont(this AppBuilder appBuilder) + { + return appBuilder.ConfigureFonts(fontManager => + { + fontManager.AddFontCollection(new InterFontCollection()); + }); + } + } +} diff --git a/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj b/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj index c81a13558c..c18c07d347 100644 --- a/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj +++ b/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Avalonia.Fonts.Inter/InterFontCollection.cs b/src/Avalonia.Fonts.Inter/InterFontCollection.cs new file mode 100644 index 0000000000..0ed1779a03 --- /dev/null +++ b/src/Avalonia.Fonts.Inter/InterFontCollection.cs @@ -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)) + { + } + } +} diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 46e3515d11..ee4cd5af98 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -84,6 +84,14 @@ namespace Avalonia.Headless public FontSimulations FontSimulations { get; } + public string FamilyName => "Arial"; + + public FontWeight Weight => FontWeight.Normal; + + public FontStyle Style => FontStyle.Normal; + + public FontStretch Stretch => FontStretch.Normal; + public void Dispose() { } @@ -147,19 +155,28 @@ namespace Avalonia.Headless class HeadlessFontManagerStub : IFontManagerImpl { - public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) + public string GetDefaultFontFamilyName() { - return new HeadlessGlyphTypefaceImpl(); + return "Arial"; } - public string GetDefaultFontFamilyName() + public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false) { - return "Arial"; + return new string[] { "Arial" }; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, out IGlyphTypeface glyphTypeface) { - return new List { "Arial" }; + glyphTypeface= new HeadlessGlyphTypefaceImpl(); + + return true; + } + + public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface) + { + glyphTypeface = new HeadlessGlyphTypefaceImpl(); + + return true; } public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml index 82e48851b5..c19a4f5c09 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml @@ -3,7 +3,7 @@ xmlns:sys="using:System" xmlns:converters="using:Avalonia.Controls.Converters"> - avares://Avalonia.Fonts.Inter/Assets#Inter, $Default + fonts:Inter#Inter, $Default 14 diff --git a/src/Avalonia.Themes.Simple/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml index 0a06927034..38b122d8b2 100644 --- a/src/Avalonia.Themes.Simple/Accents/Base.xaml +++ b/src/Avalonia.Themes.Simple/Accents/Base.xaml @@ -76,7 +76,7 @@ - avares://Avalonia.Fonts.Inter/Assets#Inter, $Default + fonts://Inter#Inter, $Default #CC119EDA #99119EDA #66119EDA diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 90a2f9169b..29e5687423 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -1,6 +1,7 @@ using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using Avalonia.Media; using Avalonia.Platform; using SkiaSharp; @@ -16,14 +17,14 @@ namespace Avalonia.Skia return SKTypeface.Default.FamilyName; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false) { if (checkForUpdates) { _skFontManager = SKFontManager.CreateDefault(); } - return _skFontManager.FontFamilies; + return _skFontManager.GetFontFamilies(); } [ThreadStatic] private static string[]? t_languageTagBuffer; @@ -95,72 +96,58 @@ namespace Avalonia.Skia return false; } - public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) { - SKTypeface? skTypeface = null; + glyphTypeface = null; - if(typeface.FontFamily.Key is not null) - { - var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily); - - skTypeface = fontCollection.Get(typeface); + var fontStyle = new SKFontStyle((SKFontStyleWeight)weight, (SKFontStyleWidth)stretch, + (SKFontStyleSlant)style); - if (skTypeface is null && !typeface.FontFamily.FamilyNames.HasFallbacks) - { - throw new InvalidOperationException( - $"Could not create glyph typeface for: {typeface.FontFamily.Name}."); - } - } + var skTypeface = _skFontManager.MatchFamily(familyName, fontStyle); if (skTypeface is null) { - var defaultName = SKTypeface.Default.FamilyName; - - var fontStyle = new SKFontStyle((SKFontStyleWeight)typeface.Weight, (SKFontStyleWidth)typeface.Stretch, - (SKFontStyleSlant)typeface.Style); - - foreach (var familyName in typeface.FontFamily.FamilyNames) - { - if(familyName == FontFamily.DefaultFontFamilyName) - { - continue; - } - - skTypeface = _skFontManager.MatchFamily(familyName, fontStyle); - - if (skTypeface is null || defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal)) - { - continue; - } - - break; - } - - // MatchTypeface can return "null" if matched typeface wasn't found for the style - // Fallback to the default typeface and styles instead. - skTypeface ??= _skFontManager.MatchTypeface(SKTypeface.Default, fontStyle) - ?? SKTypeface.Default; + return false; } - - if (skTypeface == null) + + //MatchFamily can return a font other than we requested so we have to verify we got the expected. + if (!skTypeface.FamilyName.ToLower(CultureInfo.InvariantCulture).Equals(familyName.ToLower(CultureInfo.InvariantCulture), StringComparison.Ordinal)) { - throw new InvalidOperationException( - $"Could not create glyph typeface for: {typeface.FontFamily.Name}."); + return false; } var fontSimulations = FontSimulations.None; - if((int)typeface.Weight >= 600 && !skTypeface.IsBold) + if ((int)weight >= 600 && !skTypeface.IsBold) { fontSimulations |= FontSimulations.Bold; } - if(typeface.Style == FontStyle.Italic && !skTypeface.IsItalic) + if (style == FontStyle.Italic && !skTypeface.IsItalic) { fontSimulations |= FontSimulations.Oblique; } - return new GlyphTypefaceImpl(skTypeface, fontSimulations); + glyphTypeface = new GlyphTypefaceImpl(skTypeface, fontSimulations); + + return true; + } + + public bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) + { + var skTypeface = SKTypeface.FromStream(stream); + + if (skTypeface != null) + { + glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None); + + return true; + } + + glyphTypeface = null; + + return false; } } } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index 3093455bec..43e10e3e96 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -51,6 +51,12 @@ namespace Avalonia.Skia GlyphCount = Typeface.GlyphCount; FontSimulations = fontSimulations; + + Weight = (FontWeight)Typeface.FontWeight; + + Style = Typeface.FontSlant.ToAvalonia(); + + Stretch = (FontStretch)Typeface.FontStyle.Width; } public Face Face { get; } @@ -67,6 +73,14 @@ namespace Avalonia.Skia public int GlyphCount { get; } + public string FamilyName => Typeface.FamilyName; + + public FontWeight Weight { get; } + + public FontStyle Style { get; } + + public FontStretch Stretch { get; } + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) { metrics = default; diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs deleted file mode 100644 index 9ee17a09d6..0000000000 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ /dev/null @@ -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 _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; - } - } -} diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs deleted file mode 100644 index d064f49ae4..0000000000 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ /dev/null @@ -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 s_cachedCollections; - - static SKTypefaceCollectionCache() - { - s_cachedCollections = new ConcurrentDictionary(); - } - - /// - /// Gets the or add typeface collection. - /// - /// The font family. - /// - public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily) - { - return s_cachedCollections.GetOrAdd(fontFamily, CreateCustomFontCollection); - } - - /// - /// Creates the custom font collection. - /// - /// The font family. - /// - 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(); - - 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; - } - } -} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs index 4663a6561f..b60962a091 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -using Avalonia.Platform; using SharpDX; using SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { - using System; + using System.IO; internal class DWriteResourceFontLoader : CallbackBase, FontCollectionLoader, FontFileLoader { @@ -18,19 +17,15 @@ namespace Avalonia.Direct2D1.Media /// /// The factory. /// - public DWriteResourceFontLoader(Factory factory, IEnumerable fontAssets) + public DWriteResourceFontLoader(Factory factory, Stream[] fontAssets) { var factory1 = factory; - var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - foreach (var asset in fontAssets) { - var assetStream = assetLoader.Open(asset); - - var dataStream = new DataStream((int)assetStream.Length, true, true); + var dataStream = new DataStream((int)asset.Length, true, true); - assetStream.CopyTo(dataStream); + asset.CopyTo(dataStream); dataStream.Position = 0; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 792bf2d0be..ad2ede3a91 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -6,6 +6,9 @@ using FontFamily = Avalonia.Media.FontFamily; using FontStyle = SharpDX.DirectWrite.FontStyle; using FontWeight = SharpDX.DirectWrite.FontWeight; using FontStretch = SharpDX.DirectWrite.FontStretch; +using Avalonia.Platform; +using System.Linq; +using System; namespace Avalonia.Direct2D1.Media { @@ -53,9 +56,15 @@ namespace Avalonia.Direct2D1.Media private static FontCollection CreateFontCollection(FontFamilyKey key) { - var assets = FontFamilyLoader.LoadFontAssets(key); + var source = key.BaseUri != null ? new Uri(key.BaseUri, key.Source) : key.Source; - var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); + var assets = FontFamilyLoader.LoadFontAssets(source); + + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + + var fontAssets = assets.Select(x => assetLoader.Open(x)).ToArray(); + + var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, fontAssets); return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs index b98ed3ffe6..ec2f6385da 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using Avalonia.Media; using Avalonia.Platform; -using SharpDX.DirectWrite; using FontFamily = Avalonia.Media.FontFamily; using FontStretch = Avalonia.Media.FontStretch; using FontStyle = Avalonia.Media.FontStyle; @@ -18,7 +18,7 @@ namespace Avalonia.Direct2D1.Media return "Segoe UI"; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false) { var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; @@ -62,9 +62,56 @@ namespace Avalonia.Direct2D1.Media return false; } - public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) { - return new GlyphTypefaceImpl(typeface); + var systemFonts = Direct2D1FontCollectionCache.InstalledFontCollection; + + if (familyName == FontFamily.DefaultFontFamilyName) + { + familyName = "Segoe UI"; + } + + if (systemFonts.FindFamilyName(familyName, out var index)) + { + var font = systemFonts.GetFontFamily(index).GetFirstMatchingFont( + (SharpDX.DirectWrite.FontWeight)weight, + (SharpDX.DirectWrite.FontStretch)stretch, + (SharpDX.DirectWrite.FontStyle)style); + + glyphTypeface = new GlyphTypefaceImpl(font); + + return true; + } + + glyphTypeface = null; + + return false; + } + + public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface) + { + var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, new[] { stream }); + + var fontCollection = new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); + + if (fontCollection.FontFamilyCount > 0) + { + var fontFamily = fontCollection.GetFontFamily(0); + + if (fontFamily.FontCount > 0) + { + var font = fontFamily.GetFont(0); + + glyphTypeface = new GlyphTypefaceImpl(font); + + return true; + } + } + + glyphTypeface = null; + + return false; } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index e4988322e7..01add0f0cb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -12,9 +12,9 @@ namespace Avalonia.Direct2D1.Media { private bool _isDisposed; - public GlyphTypefaceImpl(Typeface typeface) + public GlyphTypefaceImpl(SharpDX.DirectWrite.Font font) { - DWFont = Direct2D1FontCollectionCache.GetFont(typeface); + DWFont = font; FontFace = new FontFace(DWFont).QueryInterface(); @@ -48,6 +48,14 @@ namespace Avalonia.Direct2D1.Media StrikethroughThickness = strikethroughThickness, IsFixedPitch = FontFace.IsMonospacedFont }; + + FamilyName = DWFont.FontFamily.FamilyNames.GetString(0); + + Weight = (Avalonia.Media.FontWeight)DWFont.Weight; + + Style = (Avalonia.Media.FontStyle)DWFont.Style; + + Stretch = (Avalonia.Media.FontStretch)DWFont.Stretch; } private Blob GetTable(Face face, Tag tag) @@ -83,6 +91,14 @@ namespace Avalonia.Direct2D1.Media public FontSimulations FontSimulations => FontSimulations.None; + public string FamilyName { get; } + + public Avalonia.Media.FontWeight Weight { get; } + + public Avalonia.Media.FontStyle Style { get; } + + public Avalonia.Media.FontStretch Stretch { get; } + /// public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs index 11ecac0039..89e609eb10 100644 --- a/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs @@ -16,9 +16,11 @@ namespace Avalonia.Base.UnitTests.Media var typeface = new Typeface(fontFamily); - var glyphTypeface = FontManager.Current.GetOrAddGlyphTypeface(typeface); + Assert.True(FontManager.Current.TryGetGlyphTypeface(typeface, out var glyphTypeface)); - Assert.Same(glyphTypeface, FontManager.Current.GetOrAddGlyphTypeface(typeface)); + FontManager.Current.TryGetGlyphTypeface(typeface, out var other); + + Assert.Same(glyphTypeface, other); } } diff --git a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index afc25ab88e..82dcd8f4fc 100644 --- a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -46,9 +46,8 @@ namespace Avalonia.Base.UnitTests.Media.Fonts public void Should_Load_Single_FontAsset() { var source = new Uri(AssetMyFontRegular, UriKind.RelativeOrAbsolute); - var key = new FontFamilyKey(source); - var fontAssets = FontFamilyLoader.LoadFontAssets(key); + var fontAssets = FontFamilyLoader.LoadFontAssets(source); Assert.Single(fontAssets); } @@ -57,9 +56,8 @@ namespace Avalonia.Base.UnitTests.Media.Fonts public void Should_Load_Single_FontAsset_Avares_Without_BaseUri() { var source = new Uri(AssetYourFontAvares); - var key = new FontFamilyKey(source); - var fontAssets = FontFamilyLoader.LoadFontAssets(key); + var fontAssets = FontFamilyLoader.LoadFontAssets(source); Assert.Single(fontAssets); } @@ -69,9 +67,8 @@ namespace Avalonia.Base.UnitTests.Media.Fonts { var source = new Uri(AssetYourFileName, UriKind.RelativeOrAbsolute); var baseUri = new Uri(AssetLocationAvares); - var key = new FontFamilyKey(source, baseUri); - var fontAssets = FontFamilyLoader.LoadFontAssets(key); + var fontAssets = FontFamilyLoader.LoadFontAssets(new Uri(baseUri, source)); Assert.Single(fontAssets); } @@ -80,9 +77,8 @@ namespace Avalonia.Base.UnitTests.Media.Fonts public void Should_Load_Matching_Assets() { var source = new Uri(AssetLocation + ".MyFont*.ttf" + Assembly + FontName, UriKind.RelativeOrAbsolute); - var key = new FontFamilyKey(source); - var fontAssets = FontFamilyLoader.LoadFontAssets(key).ToArray(); + var fontAssets = FontFamilyLoader.LoadFontAssets(source).ToArray(); foreach (var fontAsset in fontAssets) { @@ -99,9 +95,9 @@ namespace Avalonia.Base.UnitTests.Media.Fonts { var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - var fontFamily = new FontFamily("resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests#Noto Mono"); + var source = new Uri("resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests#Noto Mono", UriKind.RelativeOrAbsolute); - var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamily.Key).ToArray(); + var fontAssets = FontFamilyLoader.LoadFontAssets(source).ToArray(); Assert.NotEmpty(fontAssets); diff --git a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs index c6ecc0a7e5..14e48b3b6c 100644 --- a/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs +++ b/tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs @@ -1,4 +1,5 @@ -using Avalonia.Direct2D1.Media; +using System; +using Avalonia.Direct2D1.Media; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -16,18 +17,10 @@ namespace Avalonia.Direct2D1.UnitTests.Media { Direct2D1Platform.Initialize(); - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily("A, B, Arial"))); - - var font = glyphTypeface.DWFont; - - Assert.Equal("Arial", font.FontFamily.FamilyNames.GetString(0)); - - Assert.Equal(SharpDX.DirectWrite.FontWeight.Normal, font.Weight); + var glyphTypeface = + new Typeface(new FontFamily("A, B, Arial")).GlyphTypeface; - Assert.Equal(SharpDX.DirectWrite.FontStyle.Normal, font.Style); + Assert.Equal("Arial", glyphTypeface.FamilyName); } } @@ -38,18 +31,13 @@ namespace Avalonia.Direct2D1.UnitTests.Media { Direct2D1Platform.Initialize(); - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily("A, B, Arial"), weight: FontWeight.Bold)); + var glyphTypeface = new Typeface(new FontFamily("A, B, Arial"), weight: FontWeight.Bold).GlyphTypeface; - var font = glyphTypeface.DWFont; + Assert.Equal("Arial", glyphTypeface.FamilyName); - Assert.Equal("Arial", font.FontFamily.FamilyNames.GetString(0)); + Assert.Equal(FontWeight.Bold, glyphTypeface.Weight); - Assert.Equal(SharpDX.DirectWrite.FontWeight.Bold, font.Weight); - - Assert.Equal(SharpDX.DirectWrite.FontStyle.Normal, font.Style); + Assert.Equal(FontStyle.Normal, glyphTypeface.Style); } } @@ -60,20 +48,11 @@ namespace Avalonia.Direct2D1.UnitTests.Media { Direct2D1Platform.Initialize(); - var fontManager = new FontManagerImpl(); + var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily("Unknown"))); + var defaultName = FontManager.Current.DefaultFontFamilyName; - var font = glyphTypeface.DWFont; - - var defaultName = fontManager.GetDefaultFontFamilyName(); - - Assert.Equal(defaultName, font.FontFamily.FamilyNames.GetString(0)); - - Assert.Equal(SharpDX.DirectWrite.FontWeight.Normal, font.Weight); - - Assert.Equal(SharpDX.DirectWrite.FontStyle.Normal, font.Style); + Assert.Equal(defaultName, glyphTypeface.FamilyName); } } @@ -86,12 +65,9 @@ namespace Avalonia.Direct2D1.UnitTests.Media var fontManager = new FontManagerImpl(); - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(s_fontUri)); + var glyphTypeface = new Typeface(s_fontUri).GlyphTypeface; - var font = glyphTypeface.DWFont; - - Assert.Equal("Noto Mono", font.FontFamily.FamilyNames.GetString(0)); + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); } } @@ -102,14 +78,9 @@ namespace Avalonia.Direct2D1.UnitTests.Media { Direct2D1Platform.Initialize(); - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black)); - - var font = glyphTypeface.DWFont; + var glyphTypeface = new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black).GlyphTypeface; - Assert.Equal("Noto Mono", font.FontFamily.FamilyNames.GetString(0)); + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 79eea09e01..6921a23161 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; +using Avalonia.Utilities; using Avalonia.Media.Imaging; using OpenQA.Selenium; using OpenQA.Selenium.Appium; @@ -143,6 +144,24 @@ namespace Avalonia.IntegrationTests.Appium } } + + [Fact] + public void Showing_Window_With_Size_Larger_Than_Screen_Measures_Content_With_Working_Area() + { + using (OpenWindow(new Size(4000, 2200), ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) + { + var screenRectTextBox = _session.FindElementByAccessibilityId("CurrentClientSize"); + var measuredWithTextBlock = _session.FindElementByAccessibilityId("CurrentMeasuredWithText"); + + var measuredWithString = measuredWithTextBlock.Text; + var workingAreaString = screenRectTextBox.Text; + + var workingArea = Size.Parse(workingAreaString); + var measuredWith = Size.Parse(measuredWithString); + + Assert.Equal(workingArea, measuredWith); + } + } [Theory] [InlineData(ShowWindowMode.NonOwned)] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 8a1ee72c54..933958b3a0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -14,6 +15,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml { public class StyleTests : XamlTestBase { + static StyleTests() + { + GC.KeepAlive(typeof(ItemsRepeater)); + } + [Fact] public void Color_Can_Be_Added_To_Style_Resources() { diff --git a/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs index 6f47aa58d8..7b128076cd 100644 --- a/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs @@ -17,11 +17,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media { public class TextLayoutTests : TestBase { - private const string FontName = "Courier New"; private const double FontSize = 12; private const double MediumFontSize = 18; private const double BigFontSize = 32; - private const double FontSizeHeight = 13.594;//real value 13.59375 + private const double FontSizeHeight = 14.0625;//real value 13.59375 private const string stringword = "word"; private const string stringmiddle = "The quick brown fox jumps over the lazy dog"; private const string stringmiddle2lines = "The quick brown fox\njumps over the lazy dog"; @@ -40,7 +39,6 @@ namespace Avalonia.Direct2D1.RenderTests.Media } private static TextLayout Create(string text, - string fontFamily, double fontSize, FontStyle fontStyle, TextAlignment textAlignment, @@ -48,7 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media TextWrapping wrapping, double widthConstraint) { - var typeface = new Typeface(fontFamily, fontStyle, fontWeight); + var typeface = new Typeface(TestFontFamily, fontStyle, fontWeight); var formattedText = new TextLayout(text, typeface, fontSize, null, textAlignment, wrapping, maxWidth: widthConstraint == -1 ? double.PositiveInfinity : widthConstraint); @@ -58,7 +56,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media private static TextLayout Create(string text, double fontSize) { - return Create(text, FontName, fontSize, + return Create(text, fontSize, FontStyle.Normal, TextAlignment.Left, FontWeight.Normal, TextWrapping.NoWrap, -1); @@ -66,7 +64,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media private static TextLayout Create(string text, double fontSize, TextAlignment alignment, double widthConstraint) { - return Create(text, FontName, fontSize, + return Create(text, fontSize, FontStyle.Normal, alignment, FontWeight.Normal, TextWrapping.NoWrap, widthConstraint); @@ -74,7 +72,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media private static TextLayout Create(string text, double fontSize, TextWrapping wrap, double widthConstraint) { - return Create(text, FontName, fontSize, + return Create(text, fontSize, FontStyle.Normal, TextAlignment.Left, FontWeight.Normal, wrap, widthConstraint); @@ -86,11 +84,11 @@ namespace Avalonia.Direct2D1.RenderTests.Media [InlineData("x", FontSize, 7.20, FontSizeHeight)] [InlineData(stringword, FontSize, 28.80, FontSizeHeight)] [InlineData(stringmiddle, FontSize, 309.65, FontSizeHeight)] - [InlineData(stringmiddle, MediumFontSize, 464.48, 20.391)] - [InlineData(stringmiddle, BigFontSize, 825.73, 36.25)] + [InlineData(stringmiddle, MediumFontSize, 464.48, 21.09375)] + [InlineData(stringmiddle, BigFontSize, 825.73, 37.5)] [InlineData(stringmiddle2lines, FontSize, 165.63, 2 * FontSizeHeight)] - [InlineData(stringmiddle2lines, MediumFontSize, 248.44, 2 * 20.391)] - [InlineData(stringmiddle2lines, BigFontSize, 441.67, 2 * 36.25)] + [InlineData(stringmiddle2lines, MediumFontSize, 248.44, 2 * 21.09375)] + [InlineData(stringmiddle2lines, BigFontSize, 441.67, 2 * 37.5)] [InlineData(stringlong, FontSize, 2160.35, FontSizeHeight)] [InlineData(stringmiddlenewlines, FontSize, 72.01, 4 * FontSizeHeight)] public void Should_Measure_String_Correctly(string input, double fontSize, double expWidth, double expHeight) @@ -221,12 +219,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media } [Theory] - [InlineData("x", 0, 1, "0,0,7.20,13.59")] - [InlineData(stringword, 0, 4, "0,0,28.80,13.59")] - [InlineData(stringmiddlenewlines, 10, 10, "0,13.59,57.61,13.59")] - [InlineData(stringmiddlenewlines, 10, 20, "0,13.59,57.61,13.59;0,27.19,64.81,13.59")] - [InlineData(stringmiddlenewlines, 10, 15, "0,13.59,57.61,13.59;0,27.19,36.01,13.59")] - [InlineData(stringmiddlenewlines, 15, 15, "36.01,13.59,21.60,13.59;0,27.19,64.81,13.59")] + [InlineData("x", 0, 1, "0,0,7.20,14.0625")] + [InlineData(stringword, 0, 4, "0,0,28.80,14.0625")] + [InlineData(stringmiddlenewlines, 10, 10, "0,14.0625,57.61,14.0625")] + [InlineData(stringmiddlenewlines, 10, 20, "0,14.0625,57.61,14.0625;0,28.125,64.81,14.0625")] + [InlineData(stringmiddlenewlines, 10, 15, "0,14.0625,57.61,14.0625;0,28.125,36.01,14.0625")] + [InlineData(stringmiddlenewlines, 15, 15, "36.01,14.0625,21.60,14.0625;0,28.125,64.81,14.0625")] public void Should_HitTestRange_Correctly(string input, int index, int length, string expectedRects) diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 5a6d7f2cdf..e18344580b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; using System.Globalization; using System.Linq; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Platform; using SkiaSharp; +using System.Diagnostics.CodeAnalysis; +using System.IO; namespace Avalonia.Skia.UnitTests.Media { @@ -35,9 +37,9 @@ namespace Avalonia.Skia.UnitTests.Media return _defaultFamilyName; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false) { - return _customTypefaces.Select(x => x.FontFamily.Name); + return _customTypefaces.Select(x => x.FontFamily.Name).ToArray(); } private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName }; @@ -70,48 +72,132 @@ namespace Avalonia.Skia.UnitTests.Media { SKTypeface skTypeface; + Uri source = null; + switch (typeface.FontFamily.Name) { case "Twitter Color Emoji": { - var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_emojiTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + source = _emojiTypeface.FontFamily.Key.Source; break; } case "Noto Sans": { - var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + source = _italicTypeface.FontFamily.Key.Source; break; } case "Noto Sans Arabic": { - var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_arabicTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + source = _arabicTypeface.FontFamily.Key.Source; break; } case "Noto Sans Hebrew": { - var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_hebrewTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + source = _hebrewTypeface.FontFamily.Key.Source; break; } case FontFamily.DefaultFontFamilyName: case "Noto Mono": { - var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); - skTypeface = typefaceCollection.Get(_defaultTypeface); + source = _defaultTypeface.FontFamily.Key.Source; break; } default: { - skTypeface = SKTypeface.FromFamilyName(typeface.FontFamily.Name, - (SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style); + break; } } + if (source is null) + { + skTypeface = SKTypeface.FromFamilyName(typeface.FontFamily.Name, + (SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style); + } + else + { + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + + var assetUri = FontFamilyLoader.LoadFontAssets(source).First(); + + var stream = assetLoader.Open(assetUri); + + skTypeface = SKTypeface.FromStream(stream); + } + return new GlyphTypefaceImpl(skTypeface, FontSimulations.None); } + + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) + { + SKTypeface skTypeface; + + Uri source = null; + + switch (familyName) + { + case "Twitter Color Emoji": + { + source = _emojiTypeface.FontFamily.Key.Source; + break; + } + case "Noto Sans": + { + source = _italicTypeface.FontFamily.Key.Source; + break; + } + case "Noto Sans Arabic": + { + source = _arabicTypeface.FontFamily.Key.Source; + break; + } + case "Noto Sans Hebrew": + { + source = _hebrewTypeface.FontFamily.Key.Source; + break; + } + case FontFamily.DefaultFontFamilyName: + case "Noto Mono": + { + source = _defaultTypeface.FontFamily.Key.Source; + break; + } + default: + { + + break; + } + } + + if (source is null) + { + skTypeface = SKTypeface.FromFamilyName(familyName, + (SKFontStyleWeight)weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)style); + } + else + { + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + + var assetUri = FontFamilyLoader.LoadFontAssets(source).First(); + + var stream = assetLoader.Open(assetUri); + + skTypeface = SKTypeface.FromStream(stream); + } + + glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None); + + return true; + } + + public bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) + { + var skTypeface = SKTypeface.FromStream(stream); + + glyphTypeface = new GlyphTypefaceImpl(skTypeface, FontSimulations.None); + + return true; + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs new file mode 100644 index 0000000000..006abe9278 --- /dev/null +++ b/tests/Avalonia.Skia.UnitTests/Media/EmbeddedFontCollectionTests.cs @@ -0,0 +1,68 @@ +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)) + { + var source = new Uri(s_notoMono, UriKind.Absolute); + + var fontCollection = new EmbeddedFontCollection(source, source); + + fontCollection.Initialize(new CustomFontManagerImpl()); + + 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)) + { + var source = new Uri(s_notoMono, UriKind.Absolute); + + var fontCollection = new EmbeddedFontCollection(source, source); + + fontCollection.Initialize(new CustomFontManagerImpl()); + + 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)) + { + var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#T", UriKind.Absolute); + + var fontCollection = new EmbeddedFontCollection(source, source); + + fontCollection.Initialize(new CustomFontManagerImpl()); + + Assert.True(fontCollection.TryGetGlyphTypeface("T", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var glyphTypeface)); + + Assert.Equal("Twitter Color Emoji", glyphTypeface.FamilyName); + } + } + } +} diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs index 649e1fbf3d..21c46b836d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs @@ -14,92 +14,66 @@ namespace Avalonia.Skia.UnitTests.Media [Fact] public void Should_Create_Typeface_From_Fallback() { - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily("A, B, " + fontManager.GetDefaultFontFamilyName()))); - - var skTypeface = glyphTypeface.Typeface; - - Assert.Equal(SKTypeface.Default.FamilyName, skTypeface.FamilyName); + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + var fontManager = FontManager.Current; - Assert.Equal(SKTypeface.Default.FontWeight, skTypeface.FontWeight); + var glyphTypeface = new Typeface(new FontFamily("A, B, " + fontManager.DefaultFontFamilyName)).GlyphTypeface; - Assert.Equal(SKTypeface.Default.FontSlant, skTypeface.FontSlant); + Assert.Equal(SKTypeface.Default.FamilyName, glyphTypeface.FamilyName); + } } [Fact] public void Should_Create_Typeface_From_Fallback_Bold() { - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold)); + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + var glyphTypeface = new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold).GlyphTypeface; - var skTypeface = glyphTypeface.Typeface; - - Assert.True(skTypeface.FontWeight >= 600); + Assert.True((int)glyphTypeface.Weight >= 600); + } } [Fact] - public void Should_Create_Typeface_For_Unknown_Font() + public void Should_Yield_Default_GlyphTypeface_For_Invalid_FamilyName() { - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily("Unknown"))); + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + var glyphTypeface = new Typeface(new FontFamily("Unknown")).GlyphTypeface; - var skTypeface = glyphTypeface.Typeface; - - Assert.Equal(SKTypeface.Default.FamilyName, skTypeface.FamilyName); - - Assert.Equal(SKTypeface.Default.FontWeight, skTypeface.FontWeight); - - Assert.Equal(SKTypeface.Default.FontSlant, skTypeface.FontSlant); + Assert.Equal(FontManager.Current.DefaultFontFamilyName, glyphTypeface.FamilyName); + } } [Fact] public void Should_Load_Typeface_From_Resource() { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) { - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(s_fontUri)); + var glyphTypeface = new Typeface(s_fontUri).GlyphTypeface; - var skTypeface = glyphTypeface.Typeface; - - Assert.Equal("Noto Mono", skTypeface.FamilyName); + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); } } [Fact] public void Should_Load_Nearest_Matching_Font() { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) { - var fontManager = new FontManagerImpl(); - - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black)); + var glyphTypeface = new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black).GlyphTypeface; - var skTypeface = glyphTypeface.Typeface; - - Assert.Equal("Noto Mono", skTypeface.FamilyName); + Assert.Equal("Noto Mono", glyphTypeface.FamilyName); } } [Fact] public void Should_Throw_For_Invalid_Custom_Font() { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) { - var fontManager = new FontManagerImpl(); - - Assert.Throws(() => - fontManager.CreateGlyphTypeface( - new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown"))); + Assert.Throws(() => new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown").GlyphTypeface); } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs b/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs deleted file mode 100644 index 64050bd85e..0000000000 --- a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs +++ /dev/null @@ -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); - } - } - } -} diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 55ac16054d..a819cbd5e3 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using System.Linq; using Avalonia.Media; -using Avalonia.Media.Fonts; using Avalonia.Platform; namespace Avalonia.UnitTests @@ -31,9 +30,9 @@ namespace Avalonia.UnitTests return _defaultFamilyName; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + string[] IFontManagerImpl.GetInstalledFontFamilyNames(bool checkForUpdates) { - return _customTypefaces.Select(x => x.FontFamily!.Name); + return _customTypefaces.Select(x => x.FontFamily!.Name).ToArray(); } public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, @@ -58,29 +57,19 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) + public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface) { - var fontFamily = typeface.FontFamily; + glyphTypeface = new HarfBuzzGlyphTypefaceImpl(stream); - if (fontFamily.IsDefault) - { - fontFamily = _defaultTypeface.FontFamily; - } - - if (fontFamily!.Key == null) - { - return null; - } - - var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamily.Key); + return true; + } - var asset = fontAssets.First(); - - var assetLoader = AvaloniaLocator.Current.GetRequiredService(); + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, + FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) + { + glyphTypeface = null; - var stream = assetLoader.Open(asset); - - return new HarfBuzzGlyphTypefaceImpl(stream); + return false; } } } diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 5b11345f16..db517ba176 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -57,6 +57,15 @@ namespace Avalonia.UnitTests public FontSimulations FontSimulations { get; } + public string FamilyName => "$Default"; + + public FontWeight Weight { get; } + + public FontStyle Style { get; } + + public FontStretch Stretch { get; } + + /// public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs index e9b923a367..eda4544877 100644 --- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using Avalonia.Media; using Avalonia.Platform; @@ -19,12 +20,12 @@ namespace Avalonia.UnitTests return _defaultFamilyName; } - public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) + string[] IFontManagerImpl.GetInstalledFontFamilyNames(bool checkForUpdates) { return new[] { _defaultFamilyName }; } - public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, FontFamily fontFamily, CultureInfo culture, out Typeface fontKey) { @@ -33,9 +34,18 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) { - return new MockGlyphTypeface(); + glyphTypeface = new MockGlyphTypeface(); + + return true; + } + + public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface) + { + glyphTypeface = new MockGlyphTypeface(); + + return true; } } } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index bd9d8e5adf..5fcee7f515 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -17,6 +17,14 @@ namespace Avalonia.UnitTests public FontSimulations FontSimulations => throw new NotImplementedException(); + public string FamilyName => "$Default"; + + public FontWeight Weight { get; } + + public FontStyle Style { get; } + + public FontStretch Stretch { get; } + public ushort GetGlyph(uint codepoint) { return (ushort)codepoint;