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();