11 changed files with 382 additions and 310 deletions
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue