Bläddra i källkod

Merge pull request #2145 from Gillibald/feature/RelativePathEmbeddedFonts

Relative path support for embedded fonts
Nikita Tsukanov 6 år sedan
förälder
incheckning
7fc8ced6c7

+ 2 - 2
samples/ControlCatalog/Pages/TextBoxPage.xaml

@@ -46,8 +46,8 @@
             <TextBlock Classes="h2">res fonts</TextBlock>
             <TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
             <TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
-            <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="avares://ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
-            <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="avares://ControlCatalog/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
+            <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
+            <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
         </StackPanel>
       </StackPanel>
   </StackPanel>

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

@@ -40,9 +40,10 @@ namespace Avalonia.Media
         /// </summary>
         /// <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)
+        /// <param name="baseUri"></param>
+        public FontFamily(string name, Uri source, Uri baseUri = null) : this(name)
         {
-            Key = new FontFamilyKey(source);
+            Key = new FontFamilyKey(source, baseUri);
         }
 
         /// <summary>
@@ -87,11 +88,12 @@ namespace Avalonia.Media
         /// Parses a <see cref="T:Avalonia.Media.FontFamily"/> string.
         /// </summary>
         /// <param name="s">The <see cref="T:Avalonia.Media.FontFamily"/> string.</param>
+        /// <param name="baseUri"></param>
         /// <returns></returns>
         /// <exception cref="ArgumentException">
         /// Specified family is not supported.
         /// </exception>
-        public static FontFamily Parse(string s)
+        public static FontFamily Parse(string s, Uri baseUri = null)
         {
             if (string.IsNullOrEmpty(s))
             {
@@ -112,7 +114,13 @@ namespace Avalonia.Media
 
                 case 2:
                     {
-                        return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute));
+                        var uri = segments[0].StartsWith("/")
+                                      ? new Uri(segments[0], UriKind.Relative)
+                                      : new Uri(segments[0], UriKind.RelativeOrAbsolute);
+
+                        return uri.IsAbsoluteUri
+                                   ? new FontFamily(segments[1], uri)
+                                   : new FontFamily(segments[1], uri, baseUri);
                     }
 
                 default:

+ 18 - 45
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@@ -2,7 +2,6 @@
 // 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
 {
@@ -12,48 +11,26 @@ namespace Avalonia.Media.Fonts
     public class FontFamilyKey
     {
         /// <summary>
-        /// Creates a new instance of <see cref="FontFamilyKey"/> and extracts <see cref="Location"/> and <see cref="FileName"/> from given <see cref="Uri"/>
+        /// Creates a new instance of <see cref="FontFamilyKey"/>
         /// </summary>
         /// <param name="source"></param>
-        public FontFamilyKey(Uri source)
+        /// <param name="baseUri"></param>
+        public FontFamilyKey(Uri source, Uri baseUri = null)
         {
-            if (source == null)
-            {
-                throw new ArgumentNullException(nameof(source));
-            }
+            Source = source ?? throw new ArgumentNullException(nameof(source));
 
-            if (source.AbsolutePath.Contains(".ttf"))
-            {
-                var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty);
-                var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
-                FileName = fileNameWithoutExtension + ".ttf";
-                Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
-            }
-            else
-            {
-                if (source.AbsolutePath.Contains(".otf"))
-                {
-                    var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty);
-                    var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
-                    FileName = fileNameWithoutExtension + ".otf";
-                    Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
-                }
-                else
-                {
-                    Location = source;
-                }
-            }
+            BaseUri = baseUri;
         }
 
         /// <summary>
-        /// Location of stored font asset that belongs to a <see cref="FontFamily"/>
+        /// Source of stored font asset that belongs to a <see cref="FontFamily"/>
         /// </summary>
-        public Uri Location { get; }
+        public Uri Source { get; }
 
         /// <summary>
-        /// Optional filename for a font asset that belongs to a <see cref="FontFamily"/>
+        /// A base URI to use if <see cref="Source"/> is relative
         /// </summary>
-        public string FileName { get; }
+        public Uri BaseUri { get; }
 
         /// <summary>
         /// Returns a hash code for this instance.
@@ -67,14 +44,14 @@ namespace Avalonia.Media.Fonts
             {
                 var hash = (int)2166136261;
 
-                if (Location != null)
+                if (Source != null)
                 {
-                    hash = (hash * 16777619) ^ Location.GetHashCode();
+                    hash = (hash * 16777619) ^ Source.GetHashCode();
                 }
 
-                if (FileName != null)
+                if (BaseUri != null)
                 {
-                    hash = (hash * 16777619) ^ FileName.GetHashCode();
+                    hash = (hash * 16777619) ^ BaseUri.GetHashCode();
                 }
 
                 return hash;
@@ -95,12 +72,12 @@ namespace Avalonia.Media.Fonts
                 return false;
             }
 
-            if (Location != other.Location)
+            if (Source != other.Source)
             {
                 return false;
             }
 
-            if (FileName != other.FileName)
+            if (BaseUri != other.BaseUri)
             {
                 return false;
             }
@@ -116,16 +93,12 @@ namespace Avalonia.Media.Fonts
         /// </returns>
         public override string ToString()
         {
-            if (FileName == null)
+            if (!Source.IsAbsoluteUri && BaseUri != null)
             {
-                return Location.PathAndQuery;
+                return BaseUri.Authority + Source;
             }
 
-            var builder = new UriBuilder(Location);
-
-            builder.Path += "." + FileName;
-
-            return builder.ToString();
+            return Source.ToString();
         }
     }
 }

+ 79 - 15
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Reflection;
 using Avalonia.Platform;
 
 namespace Avalonia.Media.Fonts
@@ -18,23 +17,35 @@ namespace Avalonia.Media.Fonts
             s_assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
         }
 
+        /// <summary>
+        /// Loads all font assets that belong to the specified <see cref="FontFamilyKey"/>
+        /// </summary>
+        /// <param name="fontFamilyKey"></param>
+        /// <returns></returns>
         public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey)
         {
-            return fontFamilyKey.FileName != null
-                ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
-                : GetFontAssetsByLocation(fontFamilyKey.Location);
+            var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First();
+
+            if (sourceWithoutArguments.EndsWith(".ttf")
+                || sourceWithoutArguments.EndsWith(".otf"))
+            {
+                return GetFontAssetsByExpression(fontFamilyKey);
+            }
+
+            return GetFontAssetsBySource(fontFamilyKey);
         }
 
         /// <summary>
         /// Searches for font assets at a given location and returns a quantity of found assets
         /// </summary>
-        /// <param name="location"></param>
+        /// <param name="fontFamilyKey"></param>
         /// <returns></returns>
-        private static IEnumerable<Uri> GetFontAssetsByLocation(Uri location)
+        private static IEnumerable<Uri> GetFontAssetsBySource(FontFamilyKey fontFamilyKey)
         {
-            var availableAssets = s_assetLoader.GetAssets(location, null);
+            var availableAssets = s_assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri);
 
-            var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"));
+            var matchingAssets =
+                availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"));
 
             return matchingAssets;
         }
@@ -43,20 +54,73 @@ namespace Avalonia.Media.Fonts
         /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression.
         /// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para>
         /// </summary>
-        /// <param name="location"></param>
-        /// <param name="fileName"></param>
+        /// <param name="fontFamilyKey"></param>
         /// <returns></returns>
-        private static IEnumerable<Uri> GetFontAssetsByFileName(Uri location, string fileName)
+        private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey)
         {
-            var availableResources = s_assetLoader.GetAssets(location, null);
+            var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location);
 
-            var compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
+            var availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri);
 
-            var matchingResources =
-                availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")));
+            string compareTo;
+
+            if (fontFamilyKey.Source.IsAbsoluteUri)
+            {
+                if (fontFamilyKey.Source.Scheme == "resm")
+                {
+                    compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
+                }
+                else
+                {
+                    compareTo = location.AbsolutePath + fileName.Split('*').First();
+                }
+            }
+            else
+            {
+                compareTo = location.AbsolutePath + fileName.Split('*').First();
+            }
+
+            var matchingResources = availableResources.Where(
+                x => x.AbsolutePath.Contains(compareTo)
+                     && x.AbsolutePath.EndsWith(fileExtension));
 
             return matchingResources;
         }
 
+        private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location)
+        {
+            if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm")
+            {
+                fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault();
+
+                var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault();
+
+                location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute);
+
+                return fileName;
+            }
+
+            var pathSegments = fontFamilyKey.Source.OriginalString.Split('/');
+
+            var fileNameWithExtension = pathSegments.Last();
+
+            var fileNameSegments = fileNameWithExtension.Split('.');
+
+            fileExtension = "." + fileNameSegments.Last();
+
+            if (fontFamilyKey.BaseUri != null)
+            {
+                var relativePath = fontFamilyKey.Source.OriginalString
+                    .Replace(fileNameWithExtension, string.Empty);
+
+                location = new Uri(fontFamilyKey.BaseUri, relativePath);
+            }
+            else
+            {
+                location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty));
+            }
+
+            return fileNameSegments.First();
+        }
     }
 }

+ 1 - 0
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -10,6 +10,7 @@
     <ItemGroup>
         <Compile Include="AvaloniaXamlLoader.cs" />
         <Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
+        <Compile Include="Converters\FontFamilyTypeConverter.cs" />
         <Compile Include="Converters\MemberSelectorTypeConverter.cs" />
         <Compile Include="Converters\ParseTypeConverter.cs" />
         <Compile Include="Converters\SetterValueTypeConverter.cs" />

+ 6 - 3
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@@ -11,6 +11,8 @@ using Avalonia.Controls.Templates;
 
 namespace Avalonia.Markup.Xaml
 {
+    using Avalonia.Media;
+
     /// <summary>
     /// Maintains a repository of <see cref="TypeConverter"/>s for XAML parsing on top of those
     /// maintained by <see cref="TypeDescriptor"/>.
@@ -37,8 +39,9 @@ namespace Avalonia.Markup.Xaml
             { typeof(Selector), typeof(SelectorTypeConverter) },
             { typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
             { typeof(WindowIcon), typeof(IconTypeConverter) },
-            { typeof(CultureInfo), typeof(CultureInfoConverter)},
-            { typeof(Uri), typeof(AvaloniaUriTypeConverter)}
+            { typeof(CultureInfo), typeof(CultureInfoConverter) },
+            { typeof(Uri), typeof(AvaloniaUriTypeConverter) },
+            { typeof(FontFamily), typeof(FontFamilyTypeConverter) }
         };
 
         /// <summary>
@@ -84,4 +87,4 @@ namespace Avalonia.Markup.Xaml
         /// <param name="converterType">The converter type. Maybe be a non-constructed generic type.</param>
         public static void Register(Type type, Type converterType) => _converters[type] = converterType;
     }
-}
+}

+ 28 - 0
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@@ -0,0 +1,28 @@
+// 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.ComponentModel;
+using System.Globalization;
+
+using Avalonia.Media;
+
+using Portable.Xaml.ComponentModel;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+    public class FontFamilyTypeConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            var s = (string)value;
+
+            return FontFamily.Parse(s, context.GetBaseUri());
+        }
+    }
+}

+ 2 - 6
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs

@@ -22,9 +22,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
 
             var fontFamilyKey = new FontFamilyKey(source);
 
-            Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location);
-
-            Assert.Null(fontFamilyKey.FileName);
+            Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source);
         }
 
         [Fact]
@@ -34,9 +32,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
 
             var fontFamilyKey = new FontFamilyKey(source);
 
-            Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location);
-
-            Assert.Equal("MyFont.ttf", fontFamilyKey.FileName);
+            Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests.MyFont.ttf"), fontFamilyKey.Source);
         }
     }
 }