Răsfoiți Sursa

IAssetLoader changes
Rename resource to asset

Benedikt Schroeder 7 ani în urmă
părinte
comite
a10a756366

BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf


BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf


BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf


BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf


+ 12 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -11,6 +11,18 @@
     </EmbeddedResource>
     <EmbeddedResource Include="Assets\*" />
   </ItemGroup>
+  <ItemGroup>
+    <None Remove="Assets\Fonts\SourceSansPro-Bold.ttf" />
+    <None Remove="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
+    <None Remove="Assets\Fonts\SourceSansPro-Italic.ttf" />
+    <None Remove="Assets\Fonts\SourceSansPro-Regular.ttf" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
+    <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
+    <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Italic.ttf" />
+    <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Regular.ttf" />
+  </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

+ 7 - 1
samples/ControlCatalog/Pages/TextBoxPage.xaml

@@ -31,6 +31,12 @@
         <TextBox AcceptsReturn="True" Width="200" Height="125"
                  Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
       </StackPanel>
-    </StackPanel>
+        <StackPanel Orientation="Vertical" Gap="8">
+            <TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
+                <TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
+                <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
+                <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
+        </StackPanel>
+      </StackPanel>
   </StackPanel>
 </UserControl>

+ 9 - 1
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
         /// <exception cref="FileNotFoundException">
         /// The resource was not found.
         /// </exception>
-        Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null);
+        (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null);
+
+        /// <summary>
+        /// Gets all assets at a specific location.
+        /// </summary>
+        /// <param name="location">The location of resources.</param>
+        /// <returns>A tuple containing the absolute path to the resource and the owner assembly</returns>
+        IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location);
     }
 }

+ 7 - 4
src/Avalonia.Visuals/Media/FontFamily.cs

@@ -19,7 +19,8 @@ namespace Avalonia.Media
         /// <exception cref="T:System.ArgumentNullException">name</exception>
         public FontFamily(string name)
         {
-            if (name == null) throw new ArgumentNullException();
+            Contract.Requires<ArgumentNullException>(name != null);
+
             FamilyNames = new FamilyNameCollection(new[] { name });
         }
 
@@ -30,14 +31,16 @@ namespace Avalonia.Media
         /// <exception cref="T:System.ArgumentNullException">name</exception>
         public FontFamily(IEnumerable<string> names)
         {
-            if (names == null) throw new ArgumentNullException();
+            Contract.Requires<ArgumentNullException>(names != null);
+
             FamilyNames = new FamilyNameCollection(names);
         }
 
+        /// <inheritdoc />
         /// <summary>
         /// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
         /// </summary>
-        /// <param name="name">The name of the <see cref="FontFamily"/>.</param>
+        /// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param>
         /// <param name="source">The source of font resources.</param>
         public FontFamily(string name, Uri source) : this(name)
         {
@@ -64,7 +67,7 @@ namespace Avalonia.Media
         }
 
         /// <summary>
-        /// Gets the key for associated resources.
+        /// Gets the key for associated assets.
         /// </summary>
         /// <value>
         /// The family familyNames.

+ 25 - 1
src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs

@@ -13,13 +13,23 @@ namespace Avalonia.Media.Fonts
     {
         private readonly ReadOnlyCollection<string> _familyNames;
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FamilyNameCollection"/> class.
+        /// </summary>
+        /// <param name="familyNames">The family names.</param>
+        /// <exception cref="ArgumentException">familyNames</exception>
         public FamilyNameCollection(IEnumerable<string> familyNames)
         {
-            if (familyNames == null) throw new ArgumentNullException(nameof(familyNames));
+            Contract.Requires<ArgumentNullException>(familyNames != null);
+
             var names = new List<string>(familyNames);
+
             if (names.Count == 0) throw new ArgumentException($"{nameof(familyNames)} must not be empty.");
+
             _familyNames = new ReadOnlyCollection<string>(names);
+
             PrimaryFamilyName = _familyNames.First();
+
             HasFallbacks = _familyNames.Count > 1;
         }
 
@@ -39,11 +49,25 @@ namespace Avalonia.Media.Fonts
         /// </value>
         public bool HasFallbacks { get; }
 
+        /// <inheritdoc />
+        /// <summary>
+        /// Returns an enumerator that iterates through the collection.
+        /// </summary>
+        /// <returns>
+        /// An enumerator that can be used to iterate through the collection.
+        /// </returns>
         public IEnumerator<string> GetEnumerator()
         {
             return _familyNames.GetEnumerator();
         }
 
+        /// <inheritdoc />
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>
+        /// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
+        /// </returns>
         IEnumerator IEnumerable.GetEnumerator()
         {
             return GetEnumerator();

+ 4 - 4
src/Avalonia.Visuals/Media/Fonts/FontResource.cs → src/Avalonia.Visuals/Media/Fonts/FontAsset.cs

@@ -5,19 +5,19 @@ using System;
 
 namespace Avalonia.Media.Fonts
 {
-    internal class FontResource
+    internal class FontAsset
     {
         /// <summary>
-        /// Initializes a new instance of the <see cref="FontResource"/> class.
+        /// Initializes a new instance of the <see cref="FontAsset"/> class.
         /// </summary>
         /// <param name="source">The source.</param>
-        public FontResource(Uri source)
+        public FontAsset(Uri source)
         {
             Source = source;
         }
 
         /// <summary>
-        /// Gets the source.
+        /// Gets the source of the asset.
         /// </summary>
         /// <value>
         /// The source.

+ 8 - 2
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@@ -33,15 +33,21 @@ namespace Avalonia.Media.Fonts
         }
 
         /// <summary>
-        /// Location of stored <see cref="FontResource"/> that belong to a <see cref="FontFamily"/>
+        /// Location of stored <see cref="FontAsset"/> that belong to a <see cref="FontFamily"/>
         /// </summary>
         public Uri Location { get; }
 
         /// <summary>
-        /// Optional filename for <see cref="FontResource"/> that belong to a <see cref="FontFamily"/>
+        /// Optional filename for <see cref="FontAsset"/> that belong to a <see cref="FontFamily"/>
         /// </summary>
         public string FileName { get; }
 
+        /// <summary>
+        /// Returns a hash code for this instance.
+        /// </summary>
+        /// <returns>
+        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
+        /// </returns>
         public override int GetHashCode()
         {
             unchecked

+ 22 - 135
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@@ -11,181 +11,68 @@ namespace Avalonia.Media.Fonts
 {
     internal static class FontFamilyLoader
     {
-        private static readonly Dictionary<string, AssemblyDescriptor> s_assemblyNameCache
-            = new Dictionary<string, AssemblyDescriptor>();
-
-        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<IAssetLoader>();
         }
 
-        public static IEnumerable<FontResource> LoadFontResources(FontFamilyKey fontFamilyKey)
+        public static IEnumerable<FontAsset> LoadFontAssets(FontFamilyKey fontFamilyKey)
         {
             return fontFamilyKey.FileName != null
-                ? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
-                : GetFontResourcesByLocation(fontFamilyKey.Location);
+                ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
+                : GetFontAssetsByLocation(fontFamilyKey.Location);
         }
 
         /// <summary>
-        /// 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
         /// </summary>
         /// <param name="location"></param>
         /// <returns></returns>
-        private static IEnumerable<FontResource> GetFontResourcesByLocation(Uri location)
+        private static IEnumerable<FontAsset> GetFontAssetsByLocation(Uri location)
         {
-            var assembly = GetAssembly(location);
-
-            if (assembly == null) return Enumerable.Empty<FontResource>();
+            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)));
         }
 
         /// <summary>
-        /// 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.
         /// <para>Filenames can target multible files with * wildcard. For example "FontFile*.ttf"</para>
         /// </summary>
         /// <param name="location"></param>
         /// <param name="fileName"></param>
         /// <returns></returns>
-        private static IEnumerable<FontResource> GetFontResourcesByFileName(Uri location, string fileName)
+        private static IEnumerable<FontAsset> GetFontAssetsByFileName(Uri location, string fileName)
         {
-            var assembly = GetAssembly(location);
-
-            if (assembly == null) return Enumerable.Empty<FontResource>();
+            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);
-        }
-
-        /// <summary>
-        /// Returns a valid resource <see cref="Uri"/> that follows the resm scheme
-        /// </summary>
-        /// <param name="path"></param>
-        /// <param name="assemblyName"></param>
-        /// <returns></returns>
-        private static Uri GetResourceUri(string path, string assemblyName)
-        {
-            return new Uri("resm:" + path + "?assembly=" + assemblyName);
-        }
-
-        /// <summary>
-        /// Extracts a <see cref="AssemblyDescriptor"/> from a given <see cref="Uri"/>
-        /// </summary>
-        /// <param name="uri"></param>
-        /// <returns></returns>
-        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)));
         }
 
-        /// <summary>
-        /// Returns a <see cref="AssemblyDescriptor"/> that is identified by a given name.
-        /// <para>
-        /// If name is <value>null</value> the default assembly is used.
-        /// </para>
-        /// </summary>
-        /// <param name="name"></param>
-        /// <returns></returns>
-        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<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;
+            return new FontAsset(source);
         }
 
         /// <summary>
-        /// Parses the parameters.
+        /// Returns a <see cref="Uri"/> for a font asset that follows the resm scheme
         /// </summary>
-        /// <param name="uri">The URI.</param>
+        /// <param name="absolutePath"></param>
+        /// <param name="assembly"></param>
         /// <returns></returns>
-        private static Dictionary<string, string> 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;
-            }
-
-            /// <summary>
-            /// Gets the name.
-            /// </summary>
-            /// <value>
-            /// The name.
-            /// </value>
-            public string Name { get; }
-
-            /// <summary>
-            /// Gets the assembly.
-            /// </summary>
-            /// <value>
-            /// The assembly.
-            /// </value>
-            public Assembly Assembly { get; }
-
-            /// <summary>
-            /// Gets the resources.
-            /// </summary>
-            /// <value>
-            /// The resources.
-            /// </value>
-            public List<string> Resources { get; }
+            return new Uri("resm:" + absolutePath + "?assembly=" + assembly.GetName().Name, UriKind.RelativeOrAbsolute);
         }
     }
 }

+ 2 - 2
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)
                 {

+ 13 - 4
src/Shared/PlatformSupport/AssetLoader.cs

@@ -67,8 +67,8 @@ namespace Avalonia.Shared.PlatformSupport
         /// <exception cref="FileNotFoundException">
         /// The resource was not found.
         /// </exception>
-        public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
-        
+        public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Stream;
+
         /// <summary>
         /// 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
         /// <exception cref="FileNotFoundException">
         /// The resource was not found.
         /// </exception>
-        public Tuple<Stream, Assembly> 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")

+ 4 - 4
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<IAssetLoader>();
 
-            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);
             }

+ 6 - 6
src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs

@@ -16,20 +16,20 @@ namespace Avalonia.Direct2D1.Media
         /// Initializes a new instance of the <see cref="DWriteResourceFontLoader"/> class.
         /// </summary>
         /// <param name="factory">The factory.</param>
-        /// <param name="fontResources"></param>
-        public DWriteResourceFontLoader(Factory factory, IEnumerable<FontResource> fontResources)
+        /// <param name="fontAssets"></param>
+        public DWriteResourceFontLoader(Factory factory, IEnumerable<FontAsset> fontAssets)
         {
             var factory1 = factory;
 
             var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
 
-            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;
 

+ 2 - 2
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);
         }

+ 1 - 1
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);
             }