|
|
@@ -2,41 +2,71 @@
|
|
|
// 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;
|
|
|
|
|
|
namespace Avalonia.Media
|
|
|
{
|
|
|
public class FontFamily
|
|
|
{
|
|
|
- public FontFamily(string familyName = "Courier New", Uri baseUri = null)
|
|
|
+ public FontFamily(string name = "Courier New")
|
|
|
{
|
|
|
- if (familyName == null) throw new ArgumentNullException(nameof(familyName));
|
|
|
-
|
|
|
- Key = new FontFamilyKey(familyName, baseUri);
|
|
|
+ Name = name ?? throw new ArgumentNullException(nameof(name));
|
|
|
}
|
|
|
|
|
|
- public string Name => Key.FriendlyName;
|
|
|
+ public FontFamily(string name, Uri source) : this(name)
|
|
|
+ {
|
|
|
+ Key = new FontFamilyKey(source);
|
|
|
+ }
|
|
|
|
|
|
- public Uri BaseUri => Key.BaseUri;
|
|
|
+ public string Name { get; }
|
|
|
|
|
|
public FontFamilyKey Key { get; }
|
|
|
|
|
|
public override string ToString()
|
|
|
{
|
|
|
- return Key.ToString();
|
|
|
+ if (Key != null)
|
|
|
+ {
|
|
|
+ return Key + "#" + Name;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Name;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class FontFamilyKey
|
|
|
{
|
|
|
- public FontFamilyKey(string friendlyName, Uri baseUri = null)
|
|
|
+ public FontFamilyKey(Uri source)
|
|
|
{
|
|
|
- FriendlyName = friendlyName;
|
|
|
- BaseUri = baseUri;
|
|
|
+ if (source.AbsolutePath.Contains(".ttf"))
|
|
|
+ {
|
|
|
+ var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", "");
|
|
|
+
|
|
|
+ if (source.Scheme == "resm")
|
|
|
+ {
|
|
|
+ var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
|
|
|
+ FileName = fileNameWithoutExtension + ".ttf";
|
|
|
+ Location = new Uri(source.OriginalString.Replace("." + FileName, ""), UriKind.RelativeOrAbsolute);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (source.Scheme == "res")
|
|
|
+ {
|
|
|
+ FileName = source.AbsolutePath.Split('/').Last();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Location = source;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- public string FriendlyName { get; }
|
|
|
+ public Uri Location { get; }
|
|
|
|
|
|
- public Uri BaseUri { get; }
|
|
|
+ public string FileName { get; }
|
|
|
|
|
|
public override int GetHashCode()
|
|
|
{
|
|
|
@@ -44,14 +74,14 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
var hash = (int)2166136261;
|
|
|
|
|
|
- if (FriendlyName != null)
|
|
|
+ if (Location != null)
|
|
|
{
|
|
|
- hash = (hash * 16777619) ^ FriendlyName.GetHashCode();
|
|
|
+ hash = (hash * 16777619) ^ Location.GetHashCode();
|
|
|
}
|
|
|
|
|
|
- if (BaseUri != null)
|
|
|
+ if (FileName != null)
|
|
|
{
|
|
|
- hash = (hash * 16777619) ^ BaseUri.GetHashCode();
|
|
|
+ hash = (hash * 16777619) ^ FileName.GetHashCode();
|
|
|
}
|
|
|
|
|
|
return hash;
|
|
|
@@ -62,117 +92,242 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
if (!(obj is FontFamilyKey other)) return false;
|
|
|
|
|
|
- if (FriendlyName != other.FriendlyName) return false;
|
|
|
+ if (Location != other.Location) return false;
|
|
|
|
|
|
- if (BaseUri != other.BaseUri) return false;
|
|
|
+ if (FileName != other.FileName) return false;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
|
{
|
|
|
- if (BaseUri != null)
|
|
|
+ if (FileName != null)
|
|
|
{
|
|
|
- return BaseUri + "#" + FriendlyName;
|
|
|
+ if (Location.Scheme == "resm")
|
|
|
+ {
|
|
|
+ return Location + "." + FileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Location + "/" + FileName;
|
|
|
}
|
|
|
|
|
|
- return FriendlyName;
|
|
|
+ return Location.ToString();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- //public class FamilyTypeface
|
|
|
- //{
|
|
|
- // public FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
|
|
|
- // {
|
|
|
- // ResourceUri = resourceUri;
|
|
|
- // FontStyle = fontStyle;
|
|
|
- // FontWeight = fontWeight;
|
|
|
- // }
|
|
|
-
|
|
|
- // public Uri ResourceUri { get; }
|
|
|
- // public FontWeight FontWeight { get; }
|
|
|
- // public FontStyle FontStyle { get; }
|
|
|
- //}
|
|
|
-
|
|
|
- //public class FamilyTypefaceKey
|
|
|
- //{
|
|
|
- // public FamilyTypefaceKey(FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
|
|
|
- // {
|
|
|
- // FontWeight = fontWeight;
|
|
|
- // FontStyle = fontStyle;
|
|
|
- // }
|
|
|
-
|
|
|
- // public FontWeight FontWeight { get; }
|
|
|
-
|
|
|
- // public FontStyle FontStyle { get; }
|
|
|
-
|
|
|
- // public override int GetHashCode()
|
|
|
- // {
|
|
|
- // unchecked
|
|
|
- // {
|
|
|
- // var hash = (int)2166136261;
|
|
|
-
|
|
|
- // hash = (hash * 16777619) ^ FontWeight.GetHashCode();
|
|
|
-
|
|
|
- // hash = (hash * 16777619) ^ FontStyle.GetHashCode();
|
|
|
-
|
|
|
- // return hash;
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // public override bool Equals(object obj)
|
|
|
- // {
|
|
|
- // if (!(obj is FamilyTypefaceKey other)) return false;
|
|
|
-
|
|
|
- // if (FontWeight != other.FontWeight) return false;
|
|
|
-
|
|
|
- // if (FontStyle != other.FontStyle) return false;
|
|
|
-
|
|
|
- // return true;
|
|
|
- // }
|
|
|
- //}
|
|
|
-
|
|
|
- //public class CachedFontFamily
|
|
|
- //{
|
|
|
- // private readonly ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface> _typefaces =
|
|
|
- // new ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface>();
|
|
|
-
|
|
|
- // public bool TryGetFamilyTypeface(out FamilyTypeface typeface, FontWeight fontWeight = FontWeight.Normal,
|
|
|
- // FontStyle fontStyle = FontStyle.Normal)
|
|
|
- // {
|
|
|
- // return _typefaces.TryGetValue(new FamilyTypefaceKey(fontWeight, fontStyle), out typeface);
|
|
|
- // }
|
|
|
-
|
|
|
- // public bool TryAddFamilyTypeface(Uri resourceUri, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
|
|
|
- // {
|
|
|
- // var familyTypefaceKeytypefaceKey = new FamilyTypefaceKey(fontWeight, fontStyle);
|
|
|
-
|
|
|
- // return _typefaces.TryAdd(familyTypefaceKeytypefaceKey, CreateFamilyTypeface(familyTypefaceKeytypefaceKey, resourceUri));
|
|
|
- // }
|
|
|
-
|
|
|
- // private static FamilyTypeface CreateFamilyTypeface(FamilyTypefaceKey familyTypefaceKey, Uri resourceUri)
|
|
|
- // {
|
|
|
- // return new FamilyTypeface(resourceUri, familyTypefaceKey.FontWeight, familyTypefaceKey.FontStyle);
|
|
|
- // }
|
|
|
- //}
|
|
|
-
|
|
|
- //public class FontFamilyCache
|
|
|
- //{
|
|
|
- // private readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> _cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
|
|
|
-
|
|
|
- // public bool TryGetCachedFontFamily(FontFamily fontFamily, out CachedFontFamily cachedFontFamily)
|
|
|
- // {
|
|
|
- // return _cachedFontFamilies.TryGetValue(fontFamily.Key, out cachedFontFamily);
|
|
|
- // }
|
|
|
-
|
|
|
- // public CachedFontFamily GetOrAddCachedFontFamily(FontFamily fontFamily)
|
|
|
- // {
|
|
|
- // return _cachedFontFamilies.GetOrAdd(fontFamily.Key, CreateCachedFontFamily);
|
|
|
- // }
|
|
|
-
|
|
|
- // private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey)
|
|
|
- // {
|
|
|
- // return new CachedFontFamily();
|
|
|
- // }
|
|
|
- //}
|
|
|
+ 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 = 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 = new StringBuilder();
|
|
|
+
|
|
|
+ for (var index = 0; index < uri.Segments.Length; index++)
|
|
|
+ {
|
|
|
+ path.Append(uri.Segments[index]);
|
|
|
+ if (index < uri.Segments.Length - 1)
|
|
|
+ {
|
|
|
+ path.Append('.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return path.ToString();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|