diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 1cb20411ef..37faa1a068 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using Avalonia.Media.Fonts; namespace Avalonia.Media @@ -15,7 +16,14 @@ namespace Avalonia.Media /// name public FontFamily(string name = "Courier New") { - Name = name ?? throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + FamilyNames = new FamilyNameList(name); + } + + public FontFamily(IEnumerable names) + { + if (names == null) throw new ArgumentNullException(nameof(names)); + FamilyNames = new FamilyNameList(names); } /// @@ -35,7 +43,7 @@ namespace Avalonia.Media /// /// The name. /// - public string Name { get; } + public string Name => FamilyNames.FirstFamilyName; /// /// Gets the key. @@ -43,7 +51,9 @@ namespace Avalonia.Media /// /// The key. /// - public FontFamilyKey Key { get; } + internal FontFamilyKey Key { get; } + + internal FamilyNameList FamilyNames { get; } /// /// Returns a that represents this instance. @@ -61,6 +71,22 @@ namespace Avalonia.Media return Name; } + internal class FamilyNameList : List + { + public FamilyNameList(string familyName) + { + Add(familyName); + FirstFamilyName = familyName; + } + + public FamilyNameList(IEnumerable familyNames) : base(familyNames) + { + FirstFamilyName = this[0]; + } + + public string FirstFamilyName { get; } + } + /// /// Parses a string. /// @@ -75,35 +101,23 @@ namespace Avalonia.Media var fontFamilyExpression = s.Split('#'); - if (fontFamilyExpression.Length == 1) - { - return new FontFamily(s); - } - - string familyName; - - Uri source = null; - switch (fontFamilyExpression.Length) { case 1: - { - familyName = fontFamilyExpression[0]; - break; - } + { + var familyNames = fontFamilyExpression[0].Split(';'); + return new FontFamily(familyNames); + } case 2: - { - source = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute); - familyName = fontFamilyExpression[1]; - break; - } + { + var source = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute); + return new FontFamily(fontFamilyExpression[1], source); + } default: - { - throw new ArgumentException("Specified family is not supported."); - } + { + throw new ArgumentException("Specified family is not supported."); + } } - - return new FontFamily(familyName, source); } } } diff --git a/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs b/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs index 30bc4203c5..dc0cb8b305 100644 --- a/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs +++ b/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts /// /// Holds a quantity of that belongs to a specific /// - public class CachedFontFamily + internal class CachedFontFamily { private readonly FontResourceCollection _fontResourceCollection; diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs index 6c8172ca2a..4ce140b7e9 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts /// /// Caches all instances to reduce memory usage and speed up loading times of custom font families /// - public static class FontFamilyCache + internal static class FontFamilyCache { private static readonly ConcurrentDictionary s_cachedFontFamilies = new ConcurrentDictionary(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 0926df0744..a6115b30e1 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media.Fonts /// /// Unique idetifier for a quantity of that is stored at a given location. /// - public class FontFamilyKey + internal class FontFamilyKey { /// /// Creates a new instance of and extracts and from given diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResource.cs b/src/Avalonia.Visuals/Media/Fonts/FontResource.cs index 67761bbc80..50df3efefd 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontResource.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontResource.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts /// /// Represents a font resource /// - public class FontResource + internal class FontResource { public FontResource(Uri source) { diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs index a2a8c48de3..427ba25277 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media.Fonts /// /// Represents a collection of that is identified by a unique /// - public class FontResourceCollection + internal class FontResourceCollection { private Dictionary _fontResources; private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs index 7286f5ad16..df7571ed3b 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs @@ -13,7 +13,7 @@ namespace Avalonia.Media.Fonts /// /// Implementation of /// - public class FontResourceLoader : IFontResourceLoader + internal class FontResourceLoader : IFontResourceLoader { private static readonly Dictionary s_assemblyNameCache = new Dictionary(); diff --git a/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs b/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs index c639d62623..16d512f743 100644 --- a/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts /// /// Loads that can be identified by a given /// - public interface IFontResourceLoader + internal interface IFontResourceLoader { /// /// Returns a quanity of that belongs to a given diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs index 900746d05a..6b93dedb29 100644 --- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs @@ -11,5 +11,7 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] +[assembly: InternalsVisibleTo("Avalonia.Direct2D1")] [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")] +[assembly: InternalsVisibleTo("Avalonia.Skia")] [assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 23a9ec615e..ec00eb4148 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -25,10 +25,20 @@ namespace Avalonia.Skia // Replace 0 characters with zero-width spaces (200B) Text = Text.Replace((char)0, (char)0x200B); - var skiaTypeface = TypefaceCache.GetTypeface( - typeface.FontFamily, - typeface.Style, - typeface.Weight); + SKTypeface skiaTypeface; + + if (typeface.FontFamily.Key != null) + { + var typefaces = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily); + skiaTypeface = typefaces.GetTypeFace(typeface); + } + else + { + skiaTypeface = TypefaceCache.GetTypeface( + typeface.FontFamily, + typeface.Style, + typeface.Weight); + } _paint = new SKPaint(); diff --git a/src/Skia/Avalonia.Skia/TypefaceCache.cs b/src/Skia/Avalonia.Skia/TypefaceCache.cs index 96696488c3..c65336320c 100644 --- a/src/Skia/Avalonia.Skia/TypefaceCache.cs +++ b/src/Skia/Avalonia.Skia/TypefaceCache.cs @@ -1,6 +1,6 @@ -using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Text; +using System.Linq; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Platform; @@ -40,13 +40,13 @@ namespace Avalonia.Skia public bool Equals(FontKey other) { return Slant == other.Slant && - Weight == other.Weight; + Weight == other.Weight; } // Equals and GetHashCode ommitted } - unsafe static SKTypeface GetTypeface(FontFamily fontFamily, FontKey key) + static SKTypeface GetTypeface(FontFamily fontFamily, FontKey key) { var familyKey = fontFamily.Name; @@ -57,53 +57,95 @@ namespace Avalonia.Skia if (!entry.TryGetValue(key, out var typeface)) { - if (fontFamily.Key != null) + typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant); + + if (typeface == null) { - var cachedFontFamily = FontFamilyCache.GetOrAddFontFamily(fontFamily.Key); + typeface = SKTypeface.FromFamilyName(null); + } - var assetLoader = AvaloniaLocator.Current.GetService(); + entry[key] = typeface; + } - foreach (var fontResource in cachedFontFamily.FontResources) - { - var stream = assetLoader.Open(fontResource.Source); + return typeface; + } - typeface = SKTypeface.FromStream(stream); + public static SKTypeface GetTypeface(FontFamily fontFamily, FontStyle style, FontWeight weight) + { + SKFontStyleSlant skStyle = SKFontStyleSlant.Upright; - if (typeface.FamilyName != familyKey) continue; + switch (style) + { + case FontStyle.Italic: + skStyle = SKFontStyleSlant.Italic; + break; - var fontKey = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); + case FontStyle.Oblique: + skStyle = SKFontStyleSlant.Oblique; + break; + } - entry[fontKey] = typeface; - } + return GetTypeface(fontFamily, new FontKey((SKFontStyleWeight)weight, skStyle)); + } - entry.TryGetValue(key, out typeface); + } - if (typeface == null) - { - typeface = SKTypeface.FromFamilyName(null); - } + internal class SKTypefaceCollection + { + private static readonly SKTypeface s_defaultTypeface = SKTypeface.FromFamilyName(null); - return typeface; - } + struct FontKey + { + public readonly string Name; + public readonly SKFontStyleSlant Slant; + public readonly SKFontStyleWeight Weight; - typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant); + public FontKey(string name, SKFontStyleWeight weight, SKFontStyleSlant slant) + { + Name = name; + Slant = slant; + Weight = weight; + } - if (typeface == null) - { - typeface = SKTypeface.FromFamilyName(null); - } + public override int GetHashCode() + { + int hash = 17; + hash = hash * 31 + Name.GetHashCode(); + hash = hash * 31 + (int)Slant; + hash = hash * 31 + (int)Weight; - entry[key] = typeface; + return hash; } - return typeface; + public override bool Equals(object other) + { + return other is FontKey ? Equals((FontKey)other) : false; + } + + public bool Equals(FontKey other) + { + return Name == other.Name && Slant == other.Slant && + Weight == other.Weight; + } + + // Equals and GetHashCode ommitted } - public static SKTypeface GetTypeface(FontFamily fontFamily, FontStyle style, FontWeight weight) + private readonly ConcurrentDictionary _cachedTypefaces = + new ConcurrentDictionary(); + + public void AddTypeFace(FontFamily fontFamily, SKTypeface typeface) + { + var key = new FontKey(fontFamily.Name, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); + + _cachedTypefaces.TryAdd(key, typeface); + } + + public SKTypeface GetTypeFace(Typeface typeface) { SKFontStyleSlant skStyle = SKFontStyleSlant.Upright; - switch (style) + switch (typeface.Style) { case FontStyle.Italic: skStyle = SKFontStyleSlant.Italic; @@ -114,8 +156,42 @@ namespace Avalonia.Skia break; } - return GetTypeface(fontFamily, new FontKey((SKFontStyleWeight)weight, skStyle)); + var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle); + + return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : s_defaultTypeface; + } + } + + internal static class SKTypefaceCollectionCache + { + private static readonly ConcurrentDictionary s_cachedCollections = + new ConcurrentDictionary(); + + public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily) + { + return s_cachedCollections.GetOrAdd(fontFamily.Key, x => CreateCustomFontCollection(fontFamily)); } + private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily) + { + var cachedFontFamily = FontFamilyCache.GetOrAddFontFamily(fontFamily.Key); + + var typeFaceCollection = new SKTypefaceCollection(); + + if (!cachedFontFamily.FontResources.Any()) return typeFaceCollection; + + var assetLoader = AvaloniaLocator.Current.GetService(); + + foreach (var fontResource in cachedFontFamily.FontResources) + { + var stream = assetLoader.Open(fontResource.Source); + + var typeface = SKTypeface.FromStream(stream); + + typeFaceCollection.AddTypeFace(fontFamily, typeface); + } + + return typeFaceCollection; + } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index b6457adc41..3d2c6e64b9 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Platform; @@ -33,7 +31,7 @@ namespace Avalonia.Direct2D1.Media if (typeface.FontFamily.Key != null) { - var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(typeface.FontFamily.Key, factory); + var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(typeface.FontFamily, factory); textFormat = new DWrite.TextFormat( factory, @@ -152,9 +150,9 @@ namespace Avalonia.Direct2D1.Media private static readonly ConcurrentDictionary s_cachedFonts = new ConcurrentDictionary(); - public static DWrite.FontCollection GetOrAddCustomFontCollection(FontFamilyKey key, DWrite.Factory factory) + public static DWrite.FontCollection GetOrAddCustomFontCollection(FontFamily fontFamily, DWrite.Factory factory) { - return s_cachedFonts.GetOrAdd(key, x => CreateCustomFontCollection(key, factory)); + return s_cachedFonts.GetOrAdd(fontFamily.Key, x => CreateCustomFontCollection(x, factory)); } private static DWrite.FontCollection CreateCustomFontCollection(FontFamilyKey key, DWrite.Factory factory) @@ -167,7 +165,7 @@ namespace Avalonia.Direct2D1.Media } } - public class ResourceFontLoader : CallbackBase, DWrite.FontCollectionLoader, DWrite.FontFileLoader + internal class ResourceFontLoader : CallbackBase, DWrite.FontCollectionLoader, DWrite.FontFileLoader { private readonly List _fontStreams = new List(); private readonly List _enumerators = new List();