Browse Source

Requested changes

pull/1564/head
Benedikt Schroeder 8 years ago
parent
commit
2c8953de97
  1. 6
      src/Avalonia.Base/Platform/IAssetLoader.cs
  2. 116
      src/Avalonia.Visuals/Media/FontFamily.cs
  3. 69
      src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
  4. 27
      src/Avalonia.Visuals/Media/Fonts/FontAsset.cs
  5. 12
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  6. 23
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  7. 35
      src/Avalonia.Visuals/Media/IFontFamily.cs
  8. 14
      src/Avalonia.Visuals/Media/Typeface.cs
  9. 2
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  10. 4
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  11. 6
      src/Shared/PlatformSupport/AssetLoader.cs
  12. 68
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  13. 20
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  14. 1
      src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontFileStream.cs
  15. 8
      src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs
  16. 34
      src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
  17. 8
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  18. 4
      tests/Avalonia.UnitTests/MockAssetLoader.cs
  19. 14
      tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs
  20. 8
      tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs

6
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -59,13 +59,13 @@ namespace Avalonia.Platform
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
(Stream Stream, Assembly 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>
/// <param name="location">The location of assets.</param>
/// <returns>A tuple containing the absolute path to the resource and the owner assembly</returns>
IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location);
IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri location);
}
}

116
src/Avalonia.Visuals/Media/FontFamily.cs

@ -8,10 +8,8 @@ using Avalonia.Media.Fonts;
namespace Avalonia.Media
{
public class FontFamily
public class FontFamily : IFontFamily
{
public static FontFamily Default => new FontFamily("Courier New");
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
/// </summary>
@ -47,56 +45,25 @@ namespace Avalonia.Media
Key = new FontFamilyKey(source);
}
/// <summary>
/// Gets the name of the font family.
/// </summary>
/// <value>
/// The name of the font family.
/// </value>
public static FontFamily Default => new FontFamily("Courier New");
public string Name => FamilyNames.PrimaryFamilyName;
FamilyNameCollection IFontFamily.FamilyNames => FamilyNames;
/// <summary>
/// Gets the family names.
/// </summary>
/// <value>
/// The family familyNames.
/// </value>
internal FamilyNameCollection FamilyNames
{
get;
}
FontFamilyKey IFontFamily.Key => Key;
/// <summary>
/// Gets the key for associated assets.
/// </summary>
/// <value>
/// The family familyNames.
/// </value>
internal FontFamilyKey Key { get; }
internal FamilyNameCollection FamilyNames { get; }
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
{
if (Key != null)
{
return Key + "#" + Name;
}
return Name;
}
internal FontFamilyKey Key { get; }
/// <summary>
/// Implicit conversion of FontFamily to string
/// Implicit conversion of string to FontFamily
/// </summary>
/// <param name="fontFamily"></param>
public static implicit operator string(FontFamily fontFamily)
public static implicit operator FontFamily(string fontFamily)
{
return fontFamily.ToString();
return new FontFamily(fontFamily);
}
/// <summary>
@ -116,21 +83,74 @@ namespace Avalonia.Media
switch (segments.Length)
{
case 1:
{
var names = segments[0].Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x));
{
var names = segments[0].Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x));
return new FontFamily(names);
}
case 2:
{
return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute));
}
default:
{
throw new ArgumentException("Specified family is not supported.");
}
}
}
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
{
if (Key != null)
{
return Key + "#" + FamilyNames;
}
return FamilyNames.ToString();
}
/// <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
{
var hash = (int)2186146271;
hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
if (Key != null)
{
hash = (hash * 15768619) ^ Key.GetHashCode();
}
return hash;
}
}
public override bool Equals(object obj)
{
if (!(obj is FontFamily other)) return false;
if (Key != null)
{
return other.FamilyNames.Equals(FamilyNames) && other.Key.Equals(Key);
}
return other.FamilyNames.Equals(FamilyNames);
}
}
}

69
src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs

@ -9,10 +9,10 @@ using System.Linq;
namespace Avalonia.Media.Fonts
{
internal class FamilyNameCollection : IEnumerable<string>
{
private readonly ReadOnlyCollection<string> _familyNames;
using System.Text;
public class FamilyNameCollection : IEnumerable<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="FamilyNameCollection"/> class.
/// </summary>
@ -26,11 +26,11 @@ namespace Avalonia.Media.Fonts
if (names.Count == 0) throw new ArgumentException($"{nameof(familyNames)} must not be empty.");
_familyNames = new ReadOnlyCollection<string>(names);
Names = new ReadOnlyCollection<string>(names);
PrimaryFamilyName = _familyNames.First();
PrimaryFamilyName = Names.First();
HasFallbacks = _familyNames.Count > 1;
HasFallbacks = Names.Count > 1;
}
/// <summary>
@ -49,6 +49,14 @@ namespace Avalonia.Media.Fonts
/// </value>
public bool HasFallbacks { get; }
/// <summary>
/// Gets the internal collection of names.
/// </summary>
/// <value>
/// The names.
/// </value>
internal ReadOnlyCollection<string> Names { get; }
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates through the collection.
@ -58,7 +66,7 @@ namespace Avalonia.Media.Fonts
/// </returns>
public IEnumerator<string> GetEnumerator()
{
return _familyNames.GetEnumerator();
return Names.GetEnumerator();
}
/// <inheritdoc />
@ -72,5 +80,52 @@ namespace Avalonia.Media.Fonts
{
return GetEnumerator();
}
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
{
var builder = new StringBuilder();
for (var index = 0; index < Names.Count; index++)
{
builder.Append(Names[index]);
if (index == Names.Count - 1) break;
builder.Append(", ");
}
return builder.ToString();
}
/// <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()
{
return ToString().GetHashCode();
}
/// <summary>
/// Determines whether the specified <see cref="object" />, is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
if (!(obj is FamilyNameCollection other)) return false;
return other.ToString().Equals(ToString());
}
}
}

27
src/Avalonia.Visuals/Media/Fonts/FontAsset.cs

@ -1,27 +0,0 @@
// 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
{
internal class FontAsset
{
/// <summary>
/// Initializes a new instance of the <see cref="FontAsset"/> class.
/// </summary>
/// <param name="source">The source.</param>
public FontAsset(Uri source)
{
Source = source;
}
/// <summary>
/// Gets the source of the asset.
/// </summary>
/// <value>
/// The source.
/// </value>
public Uri Source { get; }
}
}

12
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@ -7,9 +7,9 @@ using System.Linq;
namespace Avalonia.Media.Fonts
{
/// <summary>
/// Represents an idetifier for a <see cref="FontFamily"/>
/// Represents an identifier for a <see cref="IFontFamily"/>
/// </summary>
internal class FontFamilyKey
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"/>
@ -21,10 +21,10 @@ namespace Avalonia.Media.Fonts
if (source.AbsolutePath.Contains(".ttf"))
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", "");
var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty);
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".ttf";
Location = new Uri(source.OriginalString.Replace("." + FileName, ""), UriKind.RelativeOrAbsolute);
Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
}
else
{
@ -33,12 +33,12 @@ namespace Avalonia.Media.Fonts
}
/// <summary>
/// Location of stored <see cref="FontAsset"/> that belong to a <see cref="FontFamily"/>
/// Location of stored font asset that belongs to a <see cref="IFontFamily"/>
/// </summary>
public Uri Location { get; }
/// <summary>
/// Optional filename for <see cref="FontAsset"/> that belong to a <see cref="FontFamily"/>
/// Optional filename for a font asset that belongs to a <see cref="IFontFamily"/>
/// </summary>
public string FileName { get; }

23
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@ -9,7 +9,7 @@ using Avalonia.Platform;
namespace Avalonia.Media.Fonts
{
internal static class FontFamilyLoader
public static class FontFamilyLoader
{
private static readonly IAssetLoader s_assetLoader;
@ -18,7 +18,7 @@ namespace Avalonia.Media.Fonts
s_assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
}
public static IEnumerable<FontAsset> LoadFontAssets(FontFamilyKey fontFamilyKey)
public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey)
{
return fontFamilyKey.FileName != null
? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
@ -30,38 +30,33 @@ namespace Avalonia.Media.Fonts
/// </summary>
/// <param name="location"></param>
/// <returns></returns>
private static IEnumerable<FontAsset> GetFontAssetsByLocation(Uri location)
private static IEnumerable<Uri> GetFontAssetsByLocation(Uri location)
{
var availableAssets = s_assetLoader.GetAssets(location);
var locationPath = location.AbsolutePath;
var mathchingAssets = availableAssets.Where(x => x.AbsolutePath.Contains(locationPath) && x.AbsolutePath.EndsWith(".ttf"));
var matchingAssets = availableAssets.Where(x => x.absolutePath.Contains(locationPath) && x.absolutePath.EndsWith(".ttf"));
return mathchingAssets.Select(x => CreateFontAsset(GetAssetUri(x.AbsolutePath, x.Assembly)));
return matchingAssets.Select(x => GetAssetUri(x.absolutePath, x.assembly));
}
/// <summary>
/// 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>
/// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para>
/// </summary>
/// <param name="location"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private static IEnumerable<FontAsset> GetFontAssetsByFileName(Uri location, string fileName)
private static IEnumerable<Uri> GetFontAssetsByFileName(Uri location, string fileName)
{
var availableResources = s_assetLoader.GetAssets(location);
var compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
var matchingResources = availableResources.Where(x => x.AbsolutePath.Contains(compareTo));
var matchingResources = availableResources.Where(x => x.absolutePath.Contains(compareTo));
return matchingResources.Select(x => CreateFontAsset(GetAssetUri(x.AbsolutePath, x.Assembly)));
}
private static FontAsset CreateFontAsset(Uri source)
{
return new FontAsset(source);
return matchingResources.Select(x => GetAssetUri(x.absolutePath, x.assembly));
}
/// <summary>

35
src/Avalonia.Visuals/Media/IFontFamily.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Media
{
using Avalonia.Media.Fonts;
public interface IFontFamily
{
/// <summary>
/// Gets the name of the font family.
/// </summary>
/// <value>
/// The name of the font family.
/// </value>
string Name { get; }
/// <summary>
/// Gets the family names.
/// </summary>
/// <value>
/// The family familyNames.
/// </value>
FamilyNameCollection FamilyNames { get; }
/// <summary>
/// Gets the key for associated assets.
/// </summary>
/// <value>
/// The family familyNames.
/// </value>
FontFamilyKey Key { get; }
}
}

14
src/Avalonia.Visuals/Media/Typeface.cs

@ -14,8 +14,11 @@ namespace Avalonia.Media
/// <param name="fontSize">The font size, in DIPs.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface(FontFamily fontFamily, double fontSize = 12, FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
public Typeface(
IFontFamily fontFamily,
double fontSize = 12,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
{
if (fontSize <= 0)
{
@ -44,12 +47,15 @@ namespace Avalonia.Media
string fontFamilyName,
double fontSize = 12,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal) : this(new FontFamily(fontFamilyName), fontSize, style, weight) { }
FontWeight weight = FontWeight.Normal)
: this(new FontFamily(fontFamilyName), fontSize, style, weight)
{
}
/// <summary>
/// Gets the font family.
/// </summary>
public FontFamily FontFamily { get; }
public IFontFamily FontFamily { get; }
/// <summary>
/// Gets the size of the font in DIPs.

2
src/Avalonia.Visuals/Properties/AssemblyInfo.cs

@ -10,7 +10,5 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]

4
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.Stream)
using (var stream = asset.stream)
{
try
{
return Load(stream, asset.Assembly, rootInstance, uri);
return Load(stream, asset.assembly, rootInstance, uri);
}
catch (Exception e)
{

6
src/Shared/PlatformSupport/AssetLoader.cs

@ -67,7 +67,7 @@ namespace Avalonia.Shared.PlatformSupport
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Stream;
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
@ -83,7 +83,7 @@ namespace Avalonia.Shared.PlatformSupport
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
{
var asset = GetAsset(uri, baseUri);
@ -95,7 +95,7 @@ namespace Avalonia.Shared.PlatformSupport
return (asset.GetStream(), asset.Assembly);
}
public IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location)
public IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri location)
{
var assembly = GetAssembly(location);

68
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -6,7 +6,39 @@ namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
struct FontKey
private readonly ConcurrentDictionary<FontKey, SKTypeface> _cachedTypefaces =
new ConcurrentDictionary<FontKey, SKTypeface>();
public void AddTypeFace(SKTypeface typeface)
{
var key = new FontKey(typeface.FamilyName, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
_cachedTypefaces.TryAdd(key, typeface);
}
public SKTypeface GetTypeFace(Typeface typeface)
{
SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
switch (typeface.Style)
{
case FontStyle.Italic:
skStyle = SKFontStyleSlant.Italic;
break;
case FontStyle.Oblique:
skStyle = SKFontStyleSlant.Oblique;
break;
}
var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle);
return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : TypefaceCache.Default;
}
private struct FontKey
{
public readonly string Name;
public readonly SKFontStyleSlant Slant;
@ -34,43 +66,11 @@ namespace Avalonia.Skia
return other is FontKey ? Equals((FontKey)other) : false;
}
public bool Equals(FontKey other)
private bool Equals(FontKey other)
{
return Name == other.Name && Slant == other.Slant &&
Weight == other.Weight;
}
// Equals and GetHashCode ommitted
}
private readonly ConcurrentDictionary<FontKey, SKTypeface> _cachedTypefaces =
new ConcurrentDictionary<FontKey, SKTypeface>();
public void AddTypeFace(SKTypeface typeface)
{
var key = new FontKey(typeface.FamilyName, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
_cachedTypefaces.TryAdd(key, typeface);
}
public SKTypeface GetTypeFace(Typeface typeface)
{
SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
switch (typeface.Style)
{
case FontStyle.Italic:
skStyle = SKFontStyleSlant.Italic;
break;
case FontStyle.Oblique:
skStyle = SKFontStyleSlant.Oblique;
break;
}
var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle);
return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : TypefaceCache.Default;
}
}
}

20
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -15,22 +15,32 @@ namespace Avalonia.Skia
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SKTypefaceCollection>();
}
public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily)
/// <summary>
/// Gets the or add typeface collection.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <returns></returns>
public static SKTypefaceCollection GetOrAddTypefaceCollection(IFontFamily fontFamily)
{
return s_cachedCollections.GetOrAdd(fontFamily.Key, x => CreateCustomFontCollection(fontFamily));
}
private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily)
/// <summary>
/// Creates the custom font collection.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <returns></returns>
private static SKTypefaceCollection CreateCustomFontCollection(IFontFamily fontFamily)
{
var assets = FontFamilyLoader.LoadFontAssets(fontFamily.Key);
var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamily.Key);
var typeFaceCollection = new SKTypefaceCollection();
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
foreach (var fontAsset in assets)
foreach (var asset in fontAssets)
{
var assetStream = assetLoader.Open(fontAsset.Source);
var assetStream = assetLoader.Open(asset);
var typeface = SKTypeface.FromStream(assetStream);

1
src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontFileStream.cs

@ -40,7 +40,6 @@ namespace Avalonia.Direct2D1.Media
_stream.Position = fileOffset;
fragmentStart = _stream.PositionPointer;
}
}

8
src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs

@ -6,6 +6,8 @@ using SharpDX.DirectWrite;
namespace Avalonia.Direct2D1.Media
{
using System;
internal class DWriteResourceFontLoader : CallbackBase, FontCollectionLoader, FontFileLoader
{
private readonly List<DWriteResourceFontFileStream> _fontStreams = new List<DWriteResourceFontFileStream>();
@ -17,15 +19,15 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="factory">The factory.</param>
/// <param name="fontAssets"></param>
public DWriteResourceFontLoader(Factory factory, IEnumerable<FontAsset> fontAssets)
public DWriteResourceFontLoader(Factory factory, IEnumerable<Uri> fontAssets)
{
var factory1 = factory;
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
foreach (var font in fontAssets)
foreach (var asset in fontAssets)
{
var assetStream = assetLoader.Open(font.Source);
var assetStream = assetLoader.Open(asset);
var dataStream = new DataStream((int)assetStream.Length, true, true);

34
src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs

@ -13,31 +13,24 @@ namespace Avalonia.Direct2D1.Media
static Direct2D1FontCollectionCache()
{
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>();
s_factory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
s_installedFontCollection = s_factory.GetSystemFontCollection(false);
}
public static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily)
public static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(IFontFamily fontFamily)
{
return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection);
}
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key)
{
var assets = FontFamilyLoader.LoadFontAssets(key);
var fontLoader = new DWriteResourceFontLoader(s_factory, assets);
return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key);
}
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface)
{
var fontFamily = typeface.FontFamily;
var fontCollection = GetOrAddFontCollection(fontFamily);
var fontFamilyName = FontFamily.Default.Name;
//Should this be cached?
// Should this be cached?
foreach (var familyName in fontFamily.FamilyNames)
{
if (!fontCollection.FindFamilyName(familyName, out _)) continue;
@ -45,8 +38,23 @@ namespace Avalonia.Direct2D1.Media
break;
}
return new SharpDX.DirectWrite.TextFormat(s_factory, fontFamilyName, fontCollection, (SharpDX.DirectWrite.FontWeight)typeface.Weight,
(SharpDX.DirectWrite.FontStyle)typeface.Style, SharpDX.DirectWrite.FontStretch.Normal, (float)typeface.FontSize);
return new SharpDX.DirectWrite.TextFormat(
s_factory,
fontFamilyName,
fontCollection,
(SharpDX.DirectWrite.FontWeight)typeface.Weight,
(SharpDX.DirectWrite.FontStyle)typeface.Style,
SharpDX.DirectWrite.FontStretch.Normal,
(float)typeface.FontSize);
}
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key)
{
var assets = FontFamilyLoader.LoadFontAssets(key);
var fontLoader = new DWriteResourceFontLoader(s_factory, assets);
return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key);
}
}
}

8
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -28,8 +28,12 @@ namespace Avalonia.Direct2D1.Media
textFormat.WordWrapping =
wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap;
TextLayout = new DWrite.TextLayout(factory, Text ?? string.Empty, textFormat, (float)constraint.Width,
(float)constraint.Height)
TextLayout = new DWrite.TextLayout(
factory,
Text ?? string.Empty,
textFormat,
(float)constraint.Width,
(float)constraint.Height)
{
TextAlignment = textAlignment.ToDirect2D()
};

4
tests/Avalonia.UnitTests/MockAssetLoader.cs

@ -27,12 +27,12 @@ namespace Avalonia.UnitTests
return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri]));
}
public (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
{
return (Open(uri, baseUri), (Assembly)null);
}
public IEnumerable<(string AbsolutePath, Assembly Assembly)> GetAssets(Uri location)
public IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri location)
{
return _assets.Keys.Select(x => (x.AbsolutePath, Assembly.GetEntryAssembly()));
}

14
tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs

@ -25,11 +25,19 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void ShouldImplicitlyConvertToString()
public void Should_Implicitly_Convert_String_To_FontFamily()
{
var fontFamily = new FontFamily("Arial");
FontFamily fontFamily = "Arial";
Assert.Equal("Arial", fontFamily);
Assert.Equal(new FontFamily("Arial"), fontFamily);
}
[Fact]
public void Should_Be_Equal()
{
var fontFamily = new FontFamily("Arial");
Assert.Equal(new FontFamily("Arial"), fontFamily);
}
[Fact]

8
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs

@ -21,5 +21,13 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
{
Assert.Throws<ArgumentException>(() => new FamilyNameCollection(Enumerable.Empty<string>()));
}
[Fact]
public void Should_Be_Equal()
{
var familyNames = new FamilyNameCollection(new[] { "Arial", "Times New Roman" });
Assert.Equal(new FamilyNameCollection(new[] { "Arial", "Times New Roman" }), familyNames);
}
}
}

Loading…
Cancel
Save