Browse Source

Made font API internal

Add FamilyNameList
Add SKTypefaceCollection
repros/custom-fonts-pr
Benedikt Schroeder 9 years ago
parent
commit
fe1d7058e7
  1. 66
      src/Avalonia.Visuals/Media/FontFamily.cs
  2. 2
      src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs
  3. 2
      src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs
  4. 2
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  5. 2
      src/Avalonia.Visuals/Media/Fonts/FontResource.cs
  6. 2
      src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs
  7. 2
      src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs
  8. 2
      src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs
  9. 2
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  10. 18
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  11. 140
      src/Skia/Avalonia.Skia/TypefaceCache.cs
  12. 10
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

66
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using Avalonia.Media.Fonts; using Avalonia.Media.Fonts;
namespace Avalonia.Media namespace Avalonia.Media
@ -15,7 +16,14 @@ namespace Avalonia.Media
/// <exception cref="ArgumentNullException">name</exception> /// <exception cref="ArgumentNullException">name</exception>
public FontFamily(string name = "Courier New") 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<string> names)
{
if (names == null) throw new ArgumentNullException(nameof(names));
FamilyNames = new FamilyNameList(names);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -35,7 +43,7 @@ namespace Avalonia.Media
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; } public string Name => FamilyNames.FirstFamilyName;
/// <summary> /// <summary>
/// Gets the key. /// Gets the key.
@ -43,7 +51,9 @@ namespace Avalonia.Media
/// <value> /// <value>
/// The key. /// The key.
/// </value> /// </value>
public FontFamilyKey Key { get; } internal FontFamilyKey Key { get; }
internal FamilyNameList FamilyNames { get; }
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Returns a <see cref="string" /> that represents this instance.
@ -61,6 +71,22 @@ namespace Avalonia.Media
return Name; return Name;
} }
internal class FamilyNameList : List<string>
{
public FamilyNameList(string familyName)
{
Add(familyName);
FirstFamilyName = familyName;
}
public FamilyNameList(IEnumerable<string> familyNames) : base(familyNames)
{
FirstFamilyName = this[0];
}
public string FirstFamilyName { get; }
}
/// <summary> /// <summary>
/// Parses a <see cref="FontFamily"/> string. /// Parses a <see cref="FontFamily"/> string.
/// </summary> /// </summary>
@ -75,35 +101,23 @@ namespace Avalonia.Media
var fontFamilyExpression = s.Split('#'); var fontFamilyExpression = s.Split('#');
if (fontFamilyExpression.Length == 1)
{
return new FontFamily(s);
}
string familyName;
Uri source = null;
switch (fontFamilyExpression.Length) switch (fontFamilyExpression.Length)
{ {
case 1: case 1:
{ {
familyName = fontFamilyExpression[0]; var familyNames = fontFamilyExpression[0].Split(';');
break; return new FontFamily(familyNames);
} }
case 2: case 2:
{ {
source = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute); var source = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute);
familyName = fontFamilyExpression[1]; return new FontFamily(fontFamilyExpression[1], source);
break; }
}
default: default:
{ {
throw new ArgumentException("Specified family is not supported."); throw new ArgumentException("Specified family is not supported.");
} }
} }
return new FontFamily(familyName, source);
} }
} }
} }

2
src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Holds a quantity of <see cref="FontResource"/> that belongs to a specific <see cref="FontFamilyKey"/> /// Holds a quantity of <see cref="FontResource"/> that belongs to a specific <see cref="FontFamilyKey"/>
/// </summary> /// </summary>
public class CachedFontFamily internal class CachedFontFamily
{ {
private readonly FontResourceCollection _fontResourceCollection; private readonly FontResourceCollection _fontResourceCollection;

2
src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Caches all <see cref="CachedFontFamily"/> instances to reduce memory usage and speed up loading times of custom font families /// Caches all <see cref="CachedFontFamily"/> instances to reduce memory usage and speed up loading times of custom font families
/// </summary> /// </summary>
public static class FontFamilyCache internal static class FontFamilyCache
{ {
private static readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> s_cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>(); private static readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> s_cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();

2
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@ -9,7 +9,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Unique idetifier for a quantity of <see cref="FontResource"/> that is stored at a given location. /// Unique idetifier for a quantity of <see cref="FontResource"/> that is stored at a given location.
/// </summary> /// </summary>
public class FontFamilyKey internal class FontFamilyKey
{ {
/// <summary> /// <summary>
/// Creates a new instance of <see cref="FontFamilyKey"/> and extracts <see cref="Location"/> and <see cref="FileName"/> from given <see cref="Uri"/> /// Creates a new instance of <see cref="FontFamilyKey"/> and extracts <see cref="Location"/> and <see cref="FileName"/> from given <see cref="Uri"/>

2
src/Avalonia.Visuals/Media/Fonts/FontResource.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Represents a font resource /// Represents a font resource
/// </summary> /// </summary>
public class FontResource internal class FontResource
{ {
public FontResource(Uri source) public FontResource(Uri source)
{ {

2
src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Represents a collection of <see cref="FontResource"/> that is identified by a unique <see cref="FontFamilyKey"/> /// Represents a collection of <see cref="FontResource"/> that is identified by a unique <see cref="FontFamilyKey"/>
/// </summary> /// </summary>
public class FontResourceCollection internal class FontResourceCollection
{ {
private Dictionary<Uri, FontResource> _fontResources; private Dictionary<Uri, FontResource> _fontResources;
private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader(); private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader();

2
src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs

@ -13,7 +13,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Implementation of <see cref="T:Avalonia.Media.Fonts.IFontResourceLoader" /> /// Implementation of <see cref="T:Avalonia.Media.Fonts.IFontResourceLoader" />
/// </summary> /// </summary>
public class FontResourceLoader : IFontResourceLoader internal class FontResourceLoader : IFontResourceLoader
{ {
private static readonly Dictionary<string, AssemblyDescriptor> s_assemblyNameCache private static readonly Dictionary<string, AssemblyDescriptor> s_assemblyNameCache
= new Dictionary<string, AssemblyDescriptor>(); = new Dictionary<string, AssemblyDescriptor>();

2
src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary> /// <summary>
/// Loads <see cref="FontResource"/> that can be identified by a given <see cref="FontFamilyKey"/> /// Loads <see cref="FontResource"/> that can be identified by a given <see cref="FontFamilyKey"/>
/// </summary> /// </summary>
public interface IFontResourceLoader internal interface IFontResourceLoader
{ {
/// <summary> /// <summary>
/// Returns a quanity of <see cref="FontResource"/> that belongs to a given <see cref="FontFamilyKey"/> /// Returns a quanity of <see cref="FontResource"/> that belongs to a given <see cref="FontFamilyKey"/>

2
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.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")] [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] [assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]

18
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -25,10 +25,20 @@ namespace Avalonia.Skia
// Replace 0 characters with zero-width spaces (200B) // Replace 0 characters with zero-width spaces (200B)
Text = Text.Replace((char)0, (char)0x200B); Text = Text.Replace((char)0, (char)0x200B);
var skiaTypeface = TypefaceCache.GetTypeface( SKTypeface skiaTypeface;
typeface.FontFamily,
typeface.Style, if (typeface.FontFamily.Key != null)
typeface.Weight); {
var typefaces = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
skiaTypeface = typefaces.GetTypeFace(typeface);
}
else
{
skiaTypeface = TypefaceCache.GetTypeface(
typeface.FontFamily,
typeface.Style,
typeface.Weight);
}
_paint = new SKPaint(); _paint = new SKPaint();

140
src/Skia/Avalonia.Skia/TypefaceCache.cs

@ -1,6 +1,6 @@
using System; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Linq;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts; using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
@ -40,13 +40,13 @@ namespace Avalonia.Skia
public bool Equals(FontKey other) public bool Equals(FontKey other)
{ {
return Slant == other.Slant && return Slant == other.Slant &&
Weight == other.Weight; Weight == other.Weight;
} }
// Equals and GetHashCode ommitted // Equals and GetHashCode ommitted
} }
unsafe static SKTypeface GetTypeface(FontFamily fontFamily, FontKey key) static SKTypeface GetTypeface(FontFamily fontFamily, FontKey key)
{ {
var familyKey = fontFamily.Name; var familyKey = fontFamily.Name;
@ -57,53 +57,95 @@ namespace Avalonia.Skia
if (!entry.TryGetValue(key, out var typeface)) 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<IAssetLoader>(); entry[key] = typeface;
}
foreach (var fontResource in cachedFontFamily.FontResources) return typeface;
{ }
var stream = assetLoader.Open(fontResource.Source);
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) internal class SKTypefaceCollection
{ {
typeface = SKTypeface.FromFamilyName(null); 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) public override int GetHashCode()
{ {
typeface = SKTypeface.FromFamilyName(null); 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<FontKey, SKTypeface> _cachedTypefaces =
new ConcurrentDictionary<FontKey, SKTypeface>();
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; SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
switch (style) switch (typeface.Style)
{ {
case FontStyle.Italic: case FontStyle.Italic:
skStyle = SKFontStyleSlant.Italic; skStyle = SKFontStyleSlant.Italic;
@ -114,8 +156,42 @@ namespace Avalonia.Skia
break; 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<FontFamilyKey, SKTypefaceCollection> s_cachedCollections =
new ConcurrentDictionary<FontFamilyKey, SKTypefaceCollection>();
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<IAssetLoader>();
foreach (var fontResource in cachedFontFamily.FontResources)
{
var stream = assetLoader.Open(fontResource.Source);
var typeface = SKTypeface.FromStream(stream);
typeFaceCollection.AddTypeFace(fontFamily, typeface);
}
return typeFaceCollection;
}
} }
} }

10
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -4,9 +4,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts; using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
@ -33,7 +31,7 @@ namespace Avalonia.Direct2D1.Media
if (typeface.FontFamily.Key != null) if (typeface.FontFamily.Key != null)
{ {
var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(typeface.FontFamily.Key, factory); var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(typeface.FontFamily, factory);
textFormat = new DWrite.TextFormat( textFormat = new DWrite.TextFormat(
factory, factory,
@ -152,9 +150,9 @@ namespace Avalonia.Direct2D1.Media
private static readonly ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection> s_cachedFonts = private static readonly ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection> s_cachedFonts =
new ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection>(); new ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection>();
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) 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<ResourceFontFileStream> _fontStreams = new List<ResourceFontFileStream>(); private readonly List<ResourceFontFileStream> _fontStreams = new List<ResourceFontFileStream>();
private readonly List<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>(); private readonly List<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>();

Loading…
Cancel
Save