Browse Source

Support relative paths for embedded fonts

pull/2145/head
Benedikt Schroeder 7 years ago
parent
commit
f260ec1df4
  1. 8
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  2. 16
      src/Avalonia.Visuals/Media/FontFamily.cs
  3. 66
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  4. 88
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  5. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  6. 9
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  7. 28
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  8. 4
      tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs

8
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -44,10 +44,10 @@
<StackPanel Orientation="Vertical" Spacing="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 regular" FontWeight="Normal" FontStyle="Normal" FontFamily="/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="/Assets/Fonts#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>

16
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 = s.StartsWith("/")
? new Uri(s, UriKind.Relative)
: new Uri(s, UriKind.RelativeOrAbsolute);
return uri.IsAbsoluteUri
? new FontFamily(segments[1], uri)
: new FontFamily(segments[1], uri, baseUri);
}
default:

66
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"/>
///
/// </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,17 @@ namespace Avalonia.Media.Fonts
/// </returns>
public override string ToString()
{
if (FileName == null)
if (Source.IsAbsoluteUri)
{
return Location.PathAndQuery;
return Source.ToString();
}
var builder = new UriBuilder(Location);
builder.Path += "." + FileName;
if (BaseUri != null)
{
return BaseUri + "/" + Source;
}
return builder.ToString();
return Source.ToString();
}
}
}

88
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
@ -20,21 +19,26 @@ namespace Avalonia.Media.Fonts
public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey)
{
return fontFamilyKey.FileName != null
? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
: GetFontAssetsByLocation(fontFamilyKey.Location);
if (fontFamilyKey.Source.OriginalString.Contains(".ttf")
|| fontFamilyKey.Source.OriginalString.Contains(".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 +47,74 @@ 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 availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri);
var compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
string compareTo;
var matchingResources =
availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")));
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().Split('#').First();
var fileNameSegments = fileNameWithExtension.Split('.');
fileExtension = "." + fileNameSegments.Last();
if (fontFamilyKey.BaseUri != null)
{
location = new Uri(
fontFamilyKey.BaseUri,
fontFamilyKey.Source.OriginalString.Split('#')
.First()
.Replace(fileNameWithExtension, string.Empty));
}
else
{
location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty));
}
return fileNameSegments.First();
}
}
}

1
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" />

9
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
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());
}
}
}

4
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs

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

Loading…
Cancel
Save