|
|
|
@ -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)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|