diff --git a/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf new file mode 100644 index 0000000000..f6986468bd Binary files /dev/null and b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf differ diff --git a/samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf new file mode 100644 index 0000000000..5c00b64faf Binary files /dev/null and b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf differ diff --git a/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf new file mode 100644 index 0000000000..82e8762011 Binary files /dev/null and b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf differ diff --git a/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf new file mode 100644 index 0000000000..278ad8aa0a Binary files /dev/null and b/samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf differ diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index b8a8479a49..390d8d6bcb 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,6 +11,18 @@ + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index db1c9cb69d..fb0d8fb7b1 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -31,6 +31,12 @@ - + + + + + + + \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index ba30af60bf..26d79d5567 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -2,6 +2,7 @@ // 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.IO; using System.Reflection; @@ -58,6 +59,13 @@ namespace Avalonia.Platform /// /// The resource was not found. /// - Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null); + (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null); + + /// + /// Gets all assets at a specific location. + /// + /// The location of resources. + /// A tuple containing the absolute path to the resource and the owner assembly + IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location); } } diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index e8709138c8..cdd3161171 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -19,7 +19,8 @@ namespace Avalonia.Media /// name public FontFamily(string name) { - if (name == null) throw new ArgumentNullException(); + Contract.Requires(name != null); + FamilyNames = new FamilyNameCollection(new[] { name }); } @@ -30,14 +31,16 @@ namespace Avalonia.Media /// name public FontFamily(IEnumerable names) { - if (names == null) throw new ArgumentNullException(); + Contract.Requires(names != null); + FamilyNames = new FamilyNameCollection(names); } + /// /// /// Initializes a new instance of the class. /// - /// The name of the . + /// The name of the . /// The source of font resources. public FontFamily(string name, Uri source) : this(name) { @@ -64,7 +67,7 @@ namespace Avalonia.Media } /// - /// Gets the key for associated resources. + /// Gets the key for associated assets. /// /// /// The family familyNames. diff --git a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs index 6296b947cc..839f2ce34b 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs @@ -13,13 +13,23 @@ namespace Avalonia.Media.Fonts { private readonly ReadOnlyCollection _familyNames; + /// + /// Initializes a new instance of the class. + /// + /// The family names. + /// familyNames public FamilyNameCollection(IEnumerable familyNames) { - if (familyNames == null) throw new ArgumentNullException(nameof(familyNames)); + Contract.Requires(familyNames != null); + var names = new List(familyNames); + if (names.Count == 0) throw new ArgumentException($"{nameof(familyNames)} must not be empty."); + _familyNames = new ReadOnlyCollection(names); + PrimaryFamilyName = _familyNames.First(); + HasFallbacks = _familyNames.Count > 1; } @@ -39,11 +49,25 @@ namespace Avalonia.Media.Fonts /// public bool HasFallbacks { get; } + /// + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// An enumerator that can be used to iterate through the collection. + /// public IEnumerator GetEnumerator() { return _familyNames.GetEnumerator(); } + /// + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontResource.cs b/src/Avalonia.Visuals/Media/Fonts/FontAsset.cs similarity index 73% rename from src/Avalonia.Visuals/Media/Fonts/FontResource.cs rename to src/Avalonia.Visuals/Media/Fonts/FontAsset.cs index 84e2153b42..a1cff79371 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontResource.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontAsset.cs @@ -5,19 +5,19 @@ using System; namespace Avalonia.Media.Fonts { - internal class FontResource + internal class FontAsset { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The source. - public FontResource(Uri source) + public FontAsset(Uri source) { Source = source; } /// - /// Gets the source. + /// Gets the source of the asset. /// /// /// The source. diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index b248b5145d..852b39615a 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -33,15 +33,21 @@ namespace Avalonia.Media.Fonts } /// - /// Location of stored that belong to a + /// Location of stored that belong to a /// public Uri Location { get; } /// - /// Optional filename for that belong to a + /// Optional filename for that belong to a /// public string FileName { get; } + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// public override int GetHashCode() { unchecked diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 89d5e07e5c..ffe6c10acb 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -11,181 +11,68 @@ namespace Avalonia.Media.Fonts { internal static class FontFamilyLoader { - private static readonly Dictionary s_assemblyNameCache - = new Dictionary(); - - private static readonly AssemblyDescriptor s_defaultAssembly; + private static readonly IAssetLoader s_assetLoader; static FontFamilyLoader() { - s_defaultAssembly = new AssemblyDescriptor(Assembly.GetEntryAssembly()); + s_assetLoader = AvaloniaLocator.Current.GetService(); } - public static IEnumerable LoadFontResources(FontFamilyKey fontFamilyKey) + public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { return fontFamilyKey.FileName != null - ? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) - : GetFontResourcesByLocation(fontFamilyKey.Location); + ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) + : GetFontAssetsByLocation(fontFamilyKey.Location); } /// - /// Searches for font resources at a given location and returns a quanity of found resources + /// Searches for font assets at a given location and returns a quanity of found assets /// /// /// - private static IEnumerable GetFontResourcesByLocation(Uri location) + private static IEnumerable GetFontAssetsByLocation(Uri location) { - var assembly = GetAssembly(location); - - if (assembly == null) return Enumerable.Empty(); + var availableAssets = s_assetLoader.GetAssets(location); var locationPath = location.AbsolutePath; - var matchingResources = assembly.Resources.Where(x => x.Contains(locationPath)); + var mathchingAssets = availableAssets.Where(x => x.AbsolutePath.Contains(locationPath) && x.AbsolutePath.EndsWith(".ttf")); - return matchingResources.Select(x => CreateResource(GetResourceUri(x, assembly.Name))); + return mathchingAssets.Select(x => CreateFontAsset(GetAssetUri(x.AbsolutePath, x.Assembly))); } /// - /// Searches for font resources at a given location and only accepts resources that fit to a given filename expression. + /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// Filenames can target multible files with * wildcard. For example "FontFile*.ttf" /// /// /// /// - private static IEnumerable GetFontResourcesByFileName(Uri location, string fileName) + private static IEnumerable GetFontAssetsByFileName(Uri location, string fileName) { - var assembly = GetAssembly(location); - - if (assembly == null) return Enumerable.Empty(); + var availableResources = s_assetLoader.GetAssets(location); var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); - var matchingResources = assembly.Resources.Where(x => x.Contains(compareTo)); - - return matchingResources.Select(x => CreateResource(GetResourceUri(x, assembly.Name))); - } - - private static FontResource CreateResource(Uri source) - { - return new FontResource(source); - } - - /// - /// Returns a valid resource that follows the resm scheme - /// - /// - /// - /// - private static Uri GetResourceUri(string path, string assemblyName) - { - return new Uri("resm:" + path + "?assembly=" + assemblyName); - } - - /// - /// Extracts a from a given - /// - /// - /// - private static AssemblyDescriptor GetAssembly(Uri uri) - { - if (uri == null) return null; - - var parameters = ParseParameters(uri); + var matchingResources = availableResources.Where(x => x.AbsolutePath.Contains(compareTo)); - return parameters.TryGetValue("assembly", out var assemblyName) ? GetAssembly(assemblyName) : s_defaultAssembly; + return matchingResources.Select(x => CreateFontAsset(GetAssetUri(x.AbsolutePath, x.Assembly))); } - /// - /// Returns a that is identified by a given name. - /// - /// If name is null the default assembly is used. - /// - /// - /// - /// - private static AssemblyDescriptor GetAssembly(string name) + private static FontAsset CreateFontAsset(Uri source) { - if (name == null) - { - return s_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; + return new FontAsset(source); } /// - /// Parses the parameters. + /// Returns a for a font asset that follows the resm scheme /// - /// The URI. + /// + /// /// - private static Dictionary ParseParameters(Uri uri) + private static Uri GetAssetUri(string absolutePath, Assembly assembly) { - 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; - } - - /// - /// Gets the name. - /// - /// - /// The name. - /// - public string Name { get; } - - /// - /// Gets the assembly. - /// - /// - /// The assembly. - /// - public Assembly Assembly { get; } - - /// - /// Gets the resources. - /// - /// - /// The resources. - /// - public List Resources { get; } + return new Uri("resm:" + absolutePath + "?assembly=" + assembly.GetName().Name, UriKind.RelativeOrAbsolute); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 79523dd498..a6321f1452 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -126,11 +126,11 @@ namespace Avalonia.Markup.Xaml } var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); - using (var stream = asset.Item1) + using (var stream = asset.Stream) { try { - return Load(stream, asset.Item2, rootInstance, uri); + return Load(stream, asset.Assembly, rootInstance, uri); } catch (Exception e) { diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 6fee855352..2e331daeda 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -67,8 +67,8 @@ namespace Avalonia.Shared.PlatformSupport /// /// The resource was not found. /// - public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; - + public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Stream; + /// /// Opens the resource with the requested URI and returns the resource string and the /// assembly containing the resource. @@ -83,7 +83,7 @@ namespace Avalonia.Shared.PlatformSupport /// /// The resource was not found. /// - public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) + public (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); @@ -92,9 +92,18 @@ namespace Avalonia.Shared.PlatformSupport throw new FileNotFoundException($"The resource {uri} could not be found."); } - return Tuple.Create(asset.GetStream(), asset.Assembly); + return (asset.GetStream(), asset.Assembly); + } + + public IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location) + { + var assembly = GetAssembly(location); + + return assembly?.Resources.Select(x => (x.Key, x.Value.Assembly)) ?? + Enumerable.Empty<(string AbsolutePath, Assembly Assembly)>(); } + private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) { if (!uri.IsAbsoluteUri || uri.Scheme == "resm") diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index 254f6cff1b..13cce3f3da 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -22,17 +22,17 @@ namespace Avalonia.Skia private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily) { - var resources = FontFamilyLoader.LoadFontResources(fontFamily.Key); + var assets = FontFamilyLoader.LoadFontAssets(fontFamily.Key); var typeFaceCollection = new SKTypefaceCollection(); var assetLoader = AvaloniaLocator.Current.GetService(); - foreach (var fontResource in resources) + foreach (var fontAsset in assets) { - var stream = assetLoader.Open(fontResource.Source); + var assetStream = assetLoader.Open(fontAsset.Source); - var typeface = SKTypeface.FromStream(stream); + var typeface = SKTypeface.FromStream(assetStream); typeFaceCollection.AddTypeFace(typeface); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs index da2ec8c0a6..ba8e48a02c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs @@ -16,20 +16,20 @@ namespace Avalonia.Direct2D1.Media /// Initializes a new instance of the class. /// /// The factory. - /// - public DWriteResourceFontLoader(Factory factory, IEnumerable fontResources) + /// + public DWriteResourceFontLoader(Factory factory, IEnumerable fontAssets) { var factory1 = factory; var assetLoader = AvaloniaLocator.Current.GetService(); - foreach (var font in fontResources) + foreach (var font in fontAssets) { - var resourceStream = assetLoader.Open(font.Source); + var assetStream = assetLoader.Open(font.Source); - var dataStream = new DataStream((int)resourceStream.Length, true, true); + var dataStream = new DataStream((int)assetStream.Length, true, true); - resourceStream.CopyTo(dataStream); + assetStream.CopyTo(dataStream); dataStream.Position = 0; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 881f27351a..4e80cd3ec1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -24,9 +24,9 @@ namespace Avalonia.Direct2D1.Media private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key) { - var resources = FontFamilyLoader.LoadFontResources(key); + var assets = FontFamilyLoader.LoadFontAssets(key); - var fontLoader = new DWriteResourceFontLoader(s_factory, resources); + var fontLoader = new DWriteResourceFontLoader(s_factory, assets); return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index 10a841ba84..ef7171d651 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var key = new FontFamilyKey(source); - var resources = FontFamilyLoader.LoadFontResources(key); + var resources = FontFamilyLoader.LoadFontAssets(key); Assert.Single(resources); }