Browse Source

Finished skia support

repros/custom-fonts-pr
Benedikt Schroeder 8 years ago
parent
commit
5fe6276228
  1. 2
      samples/ControlCatalog.Desktop/Program.cs
  2. 290
      src/Avalonia.Visuals/Media/FontFamily.cs
  3. 22
      src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs
  4. 22
      src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs
  5. 84
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  6. 17
      src/Avalonia.Visuals/Media/Fonts/FontResource.cs
  7. 40
      src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs
  8. 142
      src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs
  9. 12
      src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs
  10. 43
      src/Skia/Avalonia.Skia/TypefaceCache.cs
  11. 18
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

2
samples/ControlCatalog.Desktop/Program.cs

@ -23,7 +23,7 @@ namespace ControlCatalog
/// This method is needed for IDE previewer infrastructure
/// </summary>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect();
=> AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect().UseSkia();
private static void ConfigureAssetAssembly(AppBuilder builder)
{

290
src/Avalonia.Visuals/Media/FontFamily.cs

@ -2,12 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Platform;
using Avalonia.Media.Fonts;
namespace Avalonia.Media
{
@ -37,287 +32,4 @@ namespace Avalonia.Media
return Name;
}
}
public class FontFamilyKey
{
public FontFamilyKey(Uri source)
{
if (source.AbsolutePath.Contains(".ttf"))
{
if (source.Scheme == "res")
{
FileName = source.AbsolutePath.Split('/').Last();
}
else
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", "");
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".ttf";
}
Location = new Uri(source.OriginalString.Replace("." + FileName, ""), UriKind.RelativeOrAbsolute);
}
else
{
Location = source;
}
}
public Uri Location { get; }
public string FileName { get; }
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
if (Location != null)
{
hash = (hash * 16777619) ^ Location.GetHashCode();
}
if (FileName != null)
{
hash = (hash * 16777619) ^ FileName.GetHashCode();
}
return hash;
}
}
public override bool Equals(object obj)
{
if (!(obj is FontFamilyKey other)) return false;
if (Location != other.Location) return false;
if (FileName != other.FileName) return false;
return true;
}
public override string ToString()
{
if (FileName != null)
{
if (Location.Scheme == "resm")
{
return Location + "." + FileName;
}
return Location + "/" + FileName;
}
return Location.ToString();
}
}
public class FontResource
{
public FontResource(Uri source)
{
Source = source;
}
public Uri Source { get; }
}
public class FontResourceCollection
{
private Dictionary<Uri, FontResource> _fontResources;
private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader();
public FontResourceCollection(FontFamilyKey key)
{
Key = key;
}
public FontFamilyKey Key { get; }
public IEnumerable<FontResource> FontResources
{
get
{
if (_fontResources == null)
{
_fontResources = CreateFontResources();
}
return _fontResources.Values;
}
}
private Dictionary<Uri, FontResource> CreateFontResources()
{
return _fontResourceLoader.GetFontResources(Key).ToDictionary(x => x.Source);
}
}
public interface IFontResourceLoader
{
IEnumerable<FontResource> GetFontResources(FontFamilyKey fontFamilyKey);
}
public class FontResourceLoader : IFontResourceLoader
{
private static readonly Dictionary<string, AssemblyDescriptor> s_assemblyNameCache
= new Dictionary<string, AssemblyDescriptor>();
private readonly AssemblyDescriptor _defaultAssembly;
public FontResourceLoader(Assembly assembly = null)
{
if (assembly == null)
{
assembly = Assembly.GetEntryAssembly();
}
if (assembly != null)
{
_defaultAssembly = new AssemblyDescriptor(assembly);
}
}
public IEnumerable<FontResource> GetFontResources(FontFamilyKey fontFamilyKey)
{
return fontFamilyKey.FileName != null
? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
: GetFontResourcesByLocation(fontFamilyKey.Location);
}
private IEnumerable<FontResource> GetFontResourcesByLocation(Uri location)
{
var assembly = GetAssembly(location);
var locationPath = GetLocationPath(location);
var matchingResources = assembly.Resources.Where(x => x.Contains(locationPath));
return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name)));
}
private IEnumerable<FontResource> GetFontResourcesByFileName(Uri location, string fileName)
{
var assembly = GetAssembly(location);
var compareTo = GetLocationPath(location) + fileName.Split('*').First();
var matchingResources = assembly.Resources.Where(x => x.Contains(compareTo));
return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name)));
}
private static Uri GetResourceUri(string path, string assemblyName)
{
return new Uri("resm:" + path + "?assembly=" + assemblyName);
}
private static string GetLocationPath(Uri uri)
{
if (uri.Scheme == "resm") return uri.AbsolutePath;
var path = uri.AbsolutePath.Replace("/", ".");
return path;
}
private AssemblyDescriptor GetAssembly(Uri uri)
{
if (uri == null) return null;
var parameters = ParseParameters(uri);
return parameters.TryGetValue("assembly", out var assemblyName) ? GetAssembly(assemblyName) : null;
}
private AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
{
return _defaultAssembly;
}
if (!s_assemblyNameCache.TryGetValue(name, out var rv))
{
var loadedAssemblies = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies();
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
if (match != null)
{
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(match);
}
else
{
// iOS does not support loading assemblies dynamically!
//
#if NETCOREAPP1_0
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(new AssemblyName(name)));
#elif __IOS__
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
#else
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
#endif
}
}
return rv;
}
private static Dictionary<string, string> ParseParameters(Uri uri)
{
return uri.Query.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
}
private class AssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (Assembly == null) return;
Resources = assembly.GetManifestResourceNames().ToList();
Name = Assembly.GetName().Name;
}
public string Name { get; }
public Assembly Assembly { get; }
public List<string> Resources { get; }
}
}
public class CachedFontFamily
{
private readonly FontResourceCollection _fontResourceCollection;
public CachedFontFamily(FontFamilyKey key, FontResourceCollection fontResourceCollection)
{
Key = key;
_fontResourceCollection = fontResourceCollection;
}
public FontFamilyKey Key { get; }
public IEnumerable<FontResource> FontResources => _fontResourceCollection.FontResources;
}
public class FontFamilyCache
{
private static readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> s_cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
public CachedFontFamily GetOrAddFontFamily(FontFamilyKey key)
{
return s_cachedFontFamilies.GetOrAdd(key, CreateCachedFontFamily);
}
private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey)
{
return new CachedFontFamily(fontFamilyKey, new FontResourceCollection(fontFamilyKey));
}
}
}

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

@ -0,0 +1,22 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace Avalonia.Media.Fonts
{
public class CachedFontFamily
{
private readonly FontResourceCollection _fontResourceCollection;
public CachedFontFamily(FontFamilyKey key, FontResourceCollection fontResourceCollection)
{
Key = key;
_fontResourceCollection = fontResourceCollection;
}
public FontFamilyKey Key { get; }
public IEnumerable<FontResource> FontResources => _fontResourceCollection.FontResources;
}
}

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

@ -0,0 +1,22 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Concurrent;
namespace Avalonia.Media.Fonts
{
public static class FontFamilyCache
{
private static readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> s_cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
public static CachedFontFamily GetOrAddFontFamily(FontFamilyKey key)
{
return s_cachedFontFamilies.GetOrAdd(key, CreateCachedFontFamily);
}
private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey)
{
return new CachedFontFamily(fontFamilyKey, new FontResourceCollection(fontFamilyKey));
}
}
}

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

@ -0,0 +1,84 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
namespace Avalonia.Media.Fonts
{
public class FontFamilyKey
{
public FontFamilyKey(Uri source)
{
if (source.AbsolutePath.Contains(".ttf"))
{
if (source.Scheme == "res")
{
FileName = source.AbsolutePath.Split('/').Last();
Location = new Uri(source.OriginalString.Replace("/" + FileName, ""), UriKind.RelativeOrAbsolute);
}
else
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", "");
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".ttf";
Location = new Uri(source.OriginalString.Replace("." + FileName, ""), UriKind.RelativeOrAbsolute);
}
}
else
{
Location = source;
}
}
public Uri Location { get; }
public string FileName { get; }
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
if (Location != null)
{
hash = (hash * 16777619) ^ Location.GetHashCode();
}
if (FileName != null)
{
hash = (hash * 16777619) ^ FileName.GetHashCode();
}
return hash;
}
}
public override bool Equals(object obj)
{
if (!(obj is FontFamilyKey other)) return false;
if (Location != other.Location) return false;
if (FileName != other.FileName) return false;
return true;
}
public override string ToString()
{
if (FileName != null)
{
if (Location.Scheme == "resm")
{
return Location.AbsolutePath + "." + FileName;
}
return Location.AbsolutePath + "/" + FileName;
}
return Location.AbsolutePath;
}
}
}

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

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Media.Fonts
{
public class FontResource
{
public FontResource(Uri source)
{
Source = source;
}
public Uri Source { get; }
}
}

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

@ -0,0 +1,40 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Media.Fonts
{
public class FontResourceCollection
{
private Dictionary<Uri, FontResource> _fontResources;
private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader();
public FontResourceCollection(FontFamilyKey key)
{
Key = key;
}
public FontFamilyKey Key { get; }
public IEnumerable<FontResource> FontResources
{
get
{
if (_fontResources == null)
{
_fontResources = CreateFontResources();
}
return _fontResources.Values;
}
}
private Dictionary<Uri, FontResource> CreateFontResources()
{
return _fontResourceLoader.GetFontResources(Key).ToDictionary(x => x.Source);
}
}
}

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

@ -0,0 +1,142 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Platform;
namespace Avalonia.Media.Fonts
{
public class FontResourceLoader : IFontResourceLoader
{
private static readonly Dictionary<string, AssemblyDescriptor> s_assemblyNameCache
= new Dictionary<string, AssemblyDescriptor>();
private readonly AssemblyDescriptor _defaultAssembly;
public FontResourceLoader(Assembly assembly = null)
{
if (assembly == null)
{
assembly = Assembly.GetEntryAssembly();
}
if (assembly != null)
{
_defaultAssembly = new AssemblyDescriptor(assembly);
}
}
public IEnumerable<FontResource> GetFontResources(FontFamilyKey fontFamilyKey)
{
return fontFamilyKey.FileName != null
? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
: GetFontResourcesByLocation(fontFamilyKey.Location);
}
private IEnumerable<FontResource> GetFontResourcesByLocation(Uri location)
{
var assembly = GetAssembly(location);
var locationPath = GetLocationPath(location);
var matchingResources = assembly.Resources.Where(x => x.Contains(locationPath));
return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name)));
}
private IEnumerable<FontResource> GetFontResourcesByFileName(Uri location, string fileName)
{
var assembly = GetAssembly(location);
var compareTo = GetLocationPath(location) + "." + fileName.Split('*').First();
var matchingResources = assembly.Resources.Where(x => x.Contains(compareTo));
return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name)));
}
private static Uri GetResourceUri(string path, string assemblyName)
{
return new Uri("resm:" + path + "?assembly=" + assemblyName);
}
private static string GetLocationPath(Uri uri)
{
if (uri.Scheme == "resm") return uri.AbsolutePath;
var path = uri.AbsolutePath.Replace("/", ".");
return path;
}
private AssemblyDescriptor GetAssembly(Uri uri)
{
if (uri == null) return null;
var parameters = ParseParameters(uri);
return parameters.TryGetValue("assembly", out var assemblyName) ? GetAssembly(assemblyName) : null;
}
private AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
{
return _defaultAssembly;
}
if (!s_assemblyNameCache.TryGetValue(name, out var rv))
{
var loadedAssemblies = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies();
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
if (match != null)
{
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(match);
}
else
{
// iOS does not support loading assemblies dynamically!
//
#if NETCOREAPP1_0
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(new AssemblyName(name)));
#elif __IOS__
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
#else
s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
#endif
}
}
return rv;
}
private static Dictionary<string, string> ParseParameters(Uri uri)
{
return uri.Query.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
}
private class AssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (Assembly == null) return;
Resources = assembly.GetManifestResourceNames().ToList();
Name = Assembly.GetName().Name;
}
public string Name { get; }
public Assembly Assembly { get; }
public List<string> Resources { get; }
}
}
}

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

@ -0,0 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace Avalonia.Media.Fonts
{
public interface IFontResourceLoader
{
IEnumerable<FontResource> GetFontResources(FontFamilyKey fontFamilyKey);
}
}

43
src/Skia/Avalonia.Skia/TypefaceCache.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SkiaSharp;
@ -47,7 +48,7 @@ namespace Avalonia.Skia
unsafe static SKTypeface GetTypeface(FontFamily fontFamily, FontKey key)
{
var familyKey = fontFamily.Key.ToString();
var familyKey = fontFamily.Name;
if (!Cache.TryGetValue(familyKey, out var entry))
{
@ -56,21 +57,41 @@ namespace Avalonia.Skia
if (!entry.TryGetValue(key, out var typeface))
{
//if (fontFamily.BaseUri != null)
//{
// var stream = AvaloniaLocator.Current.GetService<IAssetLoader>().Open(fontFamily.BaseUri);
if (fontFamily.Key != null)
{
var cachedFontFamily = FontFamilyCache.GetOrAddFontFamily(fontFamily.Key);
// typeface = SKTypeface.FromStream(stream);
//}
//else
//{
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant);
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
foreach (var fontResource in cachedFontFamily.FontResources)
{
var stream = assetLoader.Open(fontResource.Source);
typeface = SKTypeface.FromStream(stream);
if (typeface.FamilyName != familyKey) continue;
var fontKey = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
entry[fontKey] = typeface;
}
entry.TryGetValue(key, out typeface);
if (typeface == null)
{
typeface = SKTypeface.FromFamilyName(null, SKTypefaceStyle.Normal);
typeface = SKTypeface.FromFamilyName(null);
}
//}
return typeface;
}
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant);
if (typeface == null)
{
typeface = SKTypeface.FromFamilyName(null);
}
entry[key] = typeface;
}

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

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SharpDX;
using DWrite = SharpDX.DirectWrite;
@ -26,17 +27,13 @@ namespace Avalonia.Direct2D1.Media
{
Text = text;
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
DWrite.TextFormat textFormat;
if (typeface.FontFamily.Key != null)
{
var fontFamilyCache = new FontFamilyCache();
var fontFamily = fontFamilyCache.GetOrAddFontFamily(typeface.FontFamily.Key);
var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(fontFamily, factory);
var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(typeface.FontFamily.Key, factory);
textFormat = new DWrite.TextFormat(
factory,
@ -54,6 +51,7 @@ namespace Avalonia.Direct2D1.Media
typeface.FontFamily.Name,
(DWrite.FontWeight)typeface.Weight,
(DWrite.FontStyle)typeface.Style,
DWrite.FontStretch.Normal,
(float)typeface.FontSize);
}
@ -154,13 +152,15 @@ namespace Avalonia.Direct2D1.Media
private static readonly ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection> s_cachedFonts =
new ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection>();
public static DWrite.FontCollection GetOrAddCustomFontCollection(CachedFontFamily fontFamily, DWrite.Factory factory)
public static DWrite.FontCollection GetOrAddCustomFontCollection(FontFamilyKey key, DWrite.Factory factory)
{
return s_cachedFonts.GetOrAdd(fontFamily.Key, x => CreateCustomFontCollection(fontFamily, factory));
return s_cachedFonts.GetOrAdd(key, x => CreateCustomFontCollection(key, factory));
}
private static DWrite.FontCollection CreateCustomFontCollection(CachedFontFamily fontFamily, DWrite.Factory factory)
private static DWrite.FontCollection CreateCustomFontCollection(FontFamilyKey key, DWrite.Factory factory)
{
var fontFamily = FontFamilyCache.GetOrAddFontFamily(key);
var fontLoader = new ResourceFontLoader(factory, fontFamily.FontResources);
return new DWrite.FontCollection(factory, fontLoader, fontLoader.Key);

Loading…
Cancel
Save