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.
using System;
using System.Collections.Generic;
using Avalonia.Media.Fonts;
namespace Avalonia.Media
@ -15,7 +16,14 @@ namespace Avalonia.Media
/// <exception cref="ArgumentNullException">name</exception>
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 />
@ -35,7 +43,7 @@ namespace Avalonia.Media
/// <value>
/// The name.
/// </value>
public string Name { get; }
public string Name => FamilyNames.FirstFamilyName;
/// <summary>
/// Gets the key.
@ -43,7 +51,9 @@ namespace Avalonia.Media
/// <value>
/// The key.
/// </value>
public FontFamilyKey Key { get; }
internal FontFamilyKey Key { get; }
internal FamilyNameList FamilyNames { get; }
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
@ -61,6 +71,22 @@ namespace Avalonia.Media
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>
/// Parses a <see cref="FontFamily"/> string.
/// </summary>
@ -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);
}
}
}

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

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

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

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary>
/// Caches all <see cref="CachedFontFamily"/> instances to reduce memory usage and speed up loading times of custom font families
/// </summary>
public static class FontFamilyCache
internal static class FontFamilyCache
{
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>
/// Unique idetifier for a quantity of <see cref="FontResource"/> that is stored at a given location.
/// </summary>
public class FontFamilyKey
internal class FontFamilyKey
{
/// <summary>
/// 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>
/// Represents a font resource
/// </summary>
public class FontResource
internal class FontResource
{
public FontResource(Uri source)
{

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

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

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

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

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

@ -8,7 +8,7 @@ namespace Avalonia.Media.Fonts
/// <summary>
/// Loads <see cref="FontResource"/> that can be identified by a given <see cref="FontFamilyKey"/>
/// </summary>
public interface IFontResourceLoader
internal interface IFontResourceLoader
{
/// <summary>
/// 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")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia")]
[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)
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();

140
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<IAssetLoader>();
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<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;
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<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.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<FontFamilyKey, DWrite.FontCollection> s_cachedFonts =
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)
@ -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<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>();

Loading…
Cancel
Save