diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index daa6ba69db..ae458f52c6 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -23,7 +23,7 @@ namespace ControlCatalog /// This method is needed for IDE previewer infrastructure /// public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure().LogToDebug().UsePlatformDetect(); + => AppBuilder.Configure().LogToDebug().UsePlatformDetect().UseSkia(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 0eb960018a..7f63930f1a 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/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 _fontResources; - private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader(); - - public FontResourceCollection(FontFamilyKey key) - { - Key = key; - } - - public FontFamilyKey Key { get; } - - public IEnumerable FontResources - { - get - { - if (_fontResources == null) - { - _fontResources = CreateFontResources(); - } - - return _fontResources.Values; - } - } - - private Dictionary CreateFontResources() - { - return _fontResourceLoader.GetFontResources(Key).ToDictionary(x => x.Source); - } - } - - public interface IFontResourceLoader - { - IEnumerable GetFontResources(FontFamilyKey fontFamilyKey); - } - - public class FontResourceLoader : IFontResourceLoader - { - private static readonly Dictionary s_assemblyNameCache - = new Dictionary(); - - private readonly AssemblyDescriptor _defaultAssembly; - - public FontResourceLoader(Assembly assembly = null) - { - if (assembly == null) - { - assembly = Assembly.GetEntryAssembly(); - } - if (assembly != null) - { - _defaultAssembly = new AssemblyDescriptor(assembly); - } - } - - public IEnumerable GetFontResources(FontFamilyKey fontFamilyKey) - { - return fontFamilyKey.FileName != null - ? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) - : GetFontResourcesByLocation(fontFamilyKey.Location); - } - - private IEnumerable 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 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().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 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 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 FontResources => _fontResourceCollection.FontResources; - } - - public class FontFamilyCache - { - private static readonly ConcurrentDictionary s_cachedFontFamilies = new ConcurrentDictionary(); - - public CachedFontFamily GetOrAddFontFamily(FontFamilyKey key) - { - return s_cachedFontFamilies.GetOrAdd(key, CreateCachedFontFamily); - } - - private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey) - { - return new CachedFontFamily(fontFamilyKey, new FontResourceCollection(fontFamilyKey)); - } - } } diff --git a/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs b/src/Avalonia.Visuals/Media/Fonts/CachedFontFamily.cs new file mode 100644 index 0000000000..d53ee69a5b --- /dev/null +++ b/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 FontResources => _fontResourceCollection.FontResources; + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyCache.cs new file mode 100644 index 0000000000..52a4ddb311 --- /dev/null +++ b/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 s_cachedFontFamilies = new ConcurrentDictionary(); + + 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)); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs new file mode 100644 index 0000000000..8ced4ea6bb --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResource.cs b/src/Avalonia.Visuals/Media/Fonts/FontResource.cs new file mode 100644 index 0000000000..470e17d7d6 --- /dev/null +++ b/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; } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FontResourceCollection.cs new file mode 100644 index 0000000000..347380e121 --- /dev/null +++ b/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 _fontResources; + private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader(); + + public FontResourceCollection(FontFamilyKey key) + { + Key = key; + } + + public FontFamilyKey Key { get; } + + public IEnumerable FontResources + { + get + { + if (_fontResources == null) + { + _fontResources = CreateFontResources(); + } + + return _fontResources.Values; + } + } + + private Dictionary CreateFontResources() + { + return _fontResourceLoader.GetFontResources(Key).ToDictionary(x => x.Source); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontResourceLoader.cs new file mode 100644 index 0000000000..2f450679b1 --- /dev/null +++ b/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 s_assemblyNameCache + = new Dictionary(); + + private readonly AssemblyDescriptor _defaultAssembly; + + public FontResourceLoader(Assembly assembly = null) + { + if (assembly == null) + { + assembly = Assembly.GetEntryAssembly(); + } + if (assembly != null) + { + _defaultAssembly = new AssemblyDescriptor(assembly); + } + } + + public IEnumerable GetFontResources(FontFamilyKey fontFamilyKey) + { + return fontFamilyKey.FileName != null + ? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) + : GetFontResourcesByLocation(fontFamilyKey.Location); + } + + private IEnumerable 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 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().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 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 Resources { get; } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs b/src/Avalonia.Visuals/Media/Fonts/IFontResourceLoader.cs new file mode 100644 index 0000000000..44e665e3a1 --- /dev/null +++ b/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 GetFontResources(FontFamilyKey fontFamilyKey); + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/TypefaceCache.cs b/src/Skia/Avalonia.Skia/TypefaceCache.cs index f4598df8ec..96696488c3 100644 --- a/src/Skia/Avalonia.Skia/TypefaceCache.cs +++ b/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().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(); + + 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; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 732089b79d..b6457adc41 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/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(); + var factory = AvaloniaLocator.Current.GetService(); 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 s_cachedFonts = new ConcurrentDictionary(); - 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);