Browse Source

Direct2D1 caching

pull/1535/head
Benedikt Schroeder 8 years ago
parent
commit
1ace429342
  1. 1
      samples/ControlCatalog.Desktop/Program.cs
  2. BIN
      samples/ControlCatalog/Assets/octin_stencil_free.ttf
  3. 1
      samples/ControlCatalog/ControlCatalog.csproj
  4. 4
      samples/ControlCatalog/Pages/BorderPage.xaml
  5. 207
      src/Avalonia.Visuals/Media/FontFamily.cs
  6. 29
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  7. 152
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

1
samples/ControlCatalog.Desktop/Program.cs

@ -16,7 +16,6 @@ namespace ControlCatalog
{ {
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again. // again.
AvaloniaLocator.CurrentMutable.Bind<IFontFamilyCache>().ToConstant(new FontFamilyCache());
BuildAvaloniaApp().Start<MainWindow>(); BuildAvaloniaApp().Start<MainWindow>();
} }

BIN
samples/ControlCatalog/Assets/octin_stencil_free.ttf

Binary file not shown.

1
samples/ControlCatalog/ControlCatalog.csproj

@ -13,6 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Assets\kooten.ttf" /> <None Remove="Assets\kooten.ttf" />
<None Remove="Assets\octin_stencil_free.ttf" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

4
samples/ControlCatalog/Pages/BorderPage.xaml

@ -27,8 +27,8 @@
Padding="16"> Padding="16">
<StackPanel> <StackPanel>
<TextBlock FontSize="14">Rounded Corners</TextBlock> <TextBlock FontSize="14">Rounded Corners</TextBlock>
<TextBlock FontWeight="Bold" FontSize="14" FontFamily="Arial">Rounded Corners</TextBlock> <TextBlock FontSize="14" FontFamily="resm:ControlCatalog.Assets.octin_stencil_free.ttf#Octin Stencil Free">Rounded Corners</TextBlock>
<TextBlock FontSize="14" FontFamily="res:///ControlCatalog/Assets/kooten.ttf#Kootenay">Rounded Corners</TextBlock> <TextBlock FontSize="14" FontFamily="resm:ControlCatalog.Assets.kooten.ttf#Kootenay">Rounded Corners</TextBlock>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>

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

@ -2,50 +2,33 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Avalonia.Media namespace Avalonia.Media
{ {
public class FontFamily public class FontFamily
{ {
public FontFamily(string familyName) : this(familyName, null) { } public FontFamily(string familyName = "Courier New", Uri baseUri = null)
public FontFamily(string familyName, Uri baseUri)
{ {
if (familyName == null) throw new ArgumentNullException(nameof(familyName)); if (familyName == null) throw new ArgumentNullException(nameof(familyName));
FontFamilyKey = new FontFamilyKey(familyName, baseUri); Key = new FontFamilyKey(familyName, baseUri);
} }
public string Name => FontFamilyKey.FriendlyName; public string Name => Key.FriendlyName;
public Uri BaseUri => FontFamilyKey.BaseUri; public Uri BaseUri => Key.BaseUri;
internal FontFamilyKey FontFamilyKey { get; } public FontFamilyKey Key { get; }
}
public class FamilyTypeface public override string ToString()
{
public FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
{ {
ResourceUri = resourceUri; return Key.ToString();
FontStyle = fontStyle;
FontWeight = fontWeight;
} }
public Uri ResourceUri { get; }
public FontWeight FontWeight { get; }
public FontStyle FontStyle { get; }
} }
public class FontFamilyKey public class FontFamilyKey
{ {
public FontFamilyKey(string friendlyName) : this(friendlyName, null) { } public FontFamilyKey(string friendlyName, Uri baseUri = null)
public FontFamilyKey(string friendlyName, Uri baseUri)
{ {
FriendlyName = friendlyName; FriendlyName = friendlyName;
BaseUri = baseUri; BaseUri = baseUri;
@ -85,87 +68,111 @@ namespace Avalonia.Media
return true; return true;
} }
}
public class FamilyTypefaceKey public override string ToString()
{
public FamilyTypefaceKey(FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
{
FontWeight = fontWeight;
FontStyle = fontStyle;
}
public FontWeight FontWeight { get; }
public FontStyle FontStyle { get; }
public override int GetHashCode()
{ {
unchecked if (BaseUri != null)
{ {
var hash = (int)2166136261; return BaseUri + "#" + FriendlyName;
hash = (hash * 16777619) ^ FontWeight.GetHashCode();
hash = (hash * 16777619) ^ FontStyle.GetHashCode();
return hash;
} }
}
public override bool Equals(object obj)
{
if (!(obj is FamilyTypefaceKey other)) return false;
if (FontWeight != other.FontWeight) return false;
if (FontStyle != other.FontStyle) return false;
return true; return FriendlyName;
} }
} }
public class CachedFontFamily //public class FamilyTypeface
{ //{
private readonly ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface> _typefaces = // public FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
new ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface>(); // {
// ResourceUri = resourceUri;
public IEnumerable<FamilyTypeface> SupportedTypefaces => _typefaces.Values; // FontStyle = fontStyle;
// FontWeight = fontWeight;
public bool TryGetFamilyTypeface(out FamilyTypeface typeface, FontWeight fontWeight = FontWeight.Normal, // }
FontStyle fontStyle = FontStyle.Normal)
{ // public Uri ResourceUri { get; }
return _typefaces.TryGetValue(new FamilyTypefaceKey(fontWeight, fontStyle), out typeface); // public FontWeight FontWeight { get; }
} // public FontStyle FontStyle { get; }
//}
public FamilyTypeface GetOrAddFamilyTypeface(Uri resourceUri, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
{ //public class FamilyTypefaceKey
return _typefaces.GetOrAdd(new FamilyTypefaceKey(fontWeight, fontStyle), x => CreateFamilyTypeface(x, resourceUri)); //{
} // public FamilyTypefaceKey(FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
// {
private static FamilyTypeface CreateFamilyTypeface(FamilyTypefaceKey familyTypefaceKey, Uri resourceUri) // FontWeight = fontWeight;
{ // FontStyle = fontStyle;
return new FamilyTypeface(resourceUri, familyTypefaceKey.FontWeight, familyTypefaceKey.FontStyle); // }
}
} // public FontWeight FontWeight { get; }
public interface IFontFamilyCache // public FontStyle FontStyle { get; }
{
CachedFontFamily GetOrAddFontFamily(FontFamily fontFamily); // public override int GetHashCode()
} // {
// unchecked
public class FontFamilyCache : IFontFamilyCache // {
{ // var hash = (int)2166136261;
private readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> _cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
// hash = (hash * 16777619) ^ FontWeight.GetHashCode();
public CachedFontFamily GetOrAddFontFamily(FontFamily fontFamily)
{ // hash = (hash * 16777619) ^ FontStyle.GetHashCode();
return _cachedFontFamilies.GetOrAdd(fontFamily.FontFamilyKey, CreateCachedFontFamily);
} // return hash;
// }
private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey) // }
{
return new CachedFontFamily(); // public override bool Equals(object obj)
} // {
} // if (!(obj is FamilyTypefaceKey other)) return false;
// if (FontWeight != other.FontWeight) return false;
// if (FontStyle != other.FontStyle) return false;
// return true;
// }
//}
//public class CachedFontFamily
//{
// private readonly ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface> _typefaces =
// new ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface>();
// public bool TryGetFamilyTypeface(out FamilyTypeface typeface, FontWeight fontWeight = FontWeight.Normal,
// FontStyle fontStyle = FontStyle.Normal)
// {
// return _typefaces.TryGetValue(new FamilyTypefaceKey(fontWeight, fontStyle), out typeface);
// }
// public bool TryAddFamilyTypeface(Uri resourceUri, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
// {
// var familyTypefaceKeytypefaceKey = new FamilyTypefaceKey(fontWeight, fontStyle);
// return _typefaces.TryAdd(familyTypefaceKeytypefaceKey, CreateFamilyTypeface(familyTypefaceKeytypefaceKey, resourceUri));
// }
// private static FamilyTypeface CreateFamilyTypeface(FamilyTypefaceKey familyTypefaceKey, Uri resourceUri)
// {
// return new FamilyTypeface(resourceUri, familyTypefaceKey.FontWeight, familyTypefaceKey.FontStyle);
// }
//}
//public class FontFamilyCache
//{
// private readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> _cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
// public bool TryGetCachedFontFamily(FontFamily fontFamily, out CachedFontFamily cachedFontFamily)
// {
// return _cachedFontFamilies.TryGetValue(fontFamily.Key, out cachedFontFamily);
// }
// public CachedFontFamily GetOrAddCachedFontFamily(FontFamily fontFamily)
// {
// return _cachedFontFamilies.GetOrAdd(fontFamily.Key, CreateCachedFontFamily);
// }
// private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey)
// {
// return new CachedFontFamily();
// }
//}
} }

29
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@ -18,16 +18,14 @@ namespace Avalonia.Markup.Xaml.Converters
if (string.IsNullOrEmpty(s)) throw new ArgumentException("Specified family is not supported."); if (string.IsNullOrEmpty(s)) throw new ArgumentException("Specified family is not supported.");
if (!s.StartsWith("resm:")) return new FontFamily(s);
var fontFamilyExpression = s.Split('#'); var fontFamilyExpression = s.Split('#');
string familyName; string familyName;
Uri baseUri = null; Uri baseUri = null;
var fontWeight = FontWeight.Normal;
var fontStyle = FontStyle.Normal;
switch (fontFamilyExpression.Length) switch (fontFamilyExpression.Length)
{ {
case 1: case 1:
@ -41,34 +39,13 @@ namespace Avalonia.Markup.Xaml.Converters
familyName = fontFamilyExpression[1]; familyName = fontFamilyExpression[1];
break; break;
} }
//case 3:
// {
// baseUri = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute);
// familyName = fontFamilyExpression[1];
// fontWeight = (FontWeight)Enum.Parse(typeof(FontWeight), fontFamilyExpression[2]);
// break;
// }
//case 4:
// {
// baseUri = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute);
// familyName = fontFamilyExpression[1];
// fontWeight = (FontWeight)Enum.Parse(typeof(FontWeight), fontFamilyExpression[2]);
// fontStyle = (FontStyle)Enum.Parse(typeof(FontStyle), fontFamilyExpression[3]);
// break;
// }
default: default:
{ {
throw new ArgumentException("Specified family is not supported."); throw new ArgumentException("Specified family is not supported.");
} }
} }
var fontFamily = new FontFamily(familyName, baseUri); return new FontFamily(familyName, baseUri);
var cachedFontFamily = AvaloniaLocator.Current.GetService<IFontFamilyCache>().GetOrAddFontFamily(fontFamily);
cachedFontFamily.GetOrAddFamilyTypeface(baseUri, fontWeight, fontStyle);
return fontFamily;
} }
} }
} }

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

@ -2,8 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
@ -26,59 +26,46 @@ namespace Avalonia.Direct2D1.Media
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>(); var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
DWrite.TextFormat textFormat;
if (typeface.FontFamily.BaseUri != null) if (typeface.FontFamily.BaseUri != null)
{ {
var fontFamily = AvaloniaLocator.Current.GetService<IFontFamilyCache>().GetOrAddFontFamily(typeface.FontFamily); var fontCollection = Direct2D1CustomFontResourceCache.GetOrAddCustomFontResource(typeface.FontFamily, factory);
fontFamily.TryGetFamilyTypeface(out var familyTypeface);
var fontLoader = new ResourceFontLoader(factory, familyTypeface.ResourceUri);
var fontCollection = new DWrite.FontCollection(factory, fontLoader, fontLoader.Key); textFormat = new DWrite.TextFormat(
factory,
using (var textFormat = typeface.FontFamily.Name,
new DWrite.TextFormat(factory, typeface.FontFamily.Name, fontCollection, DWrite.FontWeight.Normal, fontCollection,
DWrite.FontStyle.Normal, DWrite.FontStretch.Normal, (float)typeface.FontSize)) (DWrite.FontWeight)typeface.Weight,
{ (DWrite.FontStyle)typeface.Style,
textFormat.TextAlignment = DWrite.TextAlignment.Center; DWrite.FontStretch.Normal,
textFormat.ParagraphAlignment = DWrite.ParagraphAlignment.Center; (float)typeface.FontSize);
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)
{
TextAlignment = textAlignment.ToDirect2D()
};
}
} }
else else
{ {
using (var format = new DWrite.TextFormat( textFormat = new DWrite.TextFormat(
factory, factory,
typeface?.FontFamily.Name ?? "Courier New", typeface.FontFamily.Name,
(DWrite.FontWeight)(typeface.Weight), (DWrite.FontWeight)typeface.Weight,
(DWrite.FontStyle)(typeface.Style), (DWrite.FontStyle)typeface.Style,
(float)typeface.FontSize)) (float)typeface.FontSize);
{
format.WordWrapping = wrapping == TextWrapping.Wrap ?
DWrite.WordWrapping.Wrap :
DWrite.WordWrapping.NoWrap;
TextLayout = new DWrite.TextLayout(
factory,
text ?? string.Empty,
format,
(float)constraint.Width,
(float)constraint.Height)
{
TextAlignment = textAlignment.ToDirect2D()
};
}
} }
textFormat.TextAlignment = DWrite.TextAlignment.Center;
textFormat.ParagraphAlignment = DWrite.ParagraphAlignment.Center;
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)
{
TextAlignment = textAlignment.ToDirect2D()
};
textFormat.Dispose();
if (spans != null) if (spans != null)
{ {
foreach (var span in spans) foreach (var span in spans)
@ -98,10 +85,10 @@ namespace Avalonia.Direct2D1.Media
public DWrite.TextLayout TextLayout { get; } public DWrite.TextLayout TextLayout { get; }
public void Dispose() //public void Dispose()
{ //{
TextLayout.Dispose(); // TextLayout.Dispose();
} //}
public IEnumerable<FormattedTextLine> GetLines() public IEnumerable<FormattedTextLine> GetLines()
{ {
@ -111,14 +98,11 @@ namespace Avalonia.Direct2D1.Media
public TextHitTestResult HitTestPoint(Point point) public TextHitTestResult HitTestPoint(Point point)
{ {
SharpDX.Mathematics.Interop.RawBool isTrailingHit;
SharpDX.Mathematics.Interop.RawBool isInside;
var result = TextLayout.HitTestPoint( var result = TextLayout.HitTestPoint(
(float)point.X, (float)point.X,
(float)point.Y, (float)point.Y,
out isTrailingHit, out var isTrailingHit,
out isInside); out var isInside);
return new TextHitTestResult return new TextHitTestResult
{ {
@ -130,14 +114,7 @@ namespace Avalonia.Direct2D1.Media
public Rect HitTestTextPosition(int index) public Rect HitTestTextPosition(int index)
{ {
float x; var result = TextLayout.HitTestTextPosition(index, false, out _, out _);
float y;
var result = TextLayout.HitTestTextPosition(
index,
false,
out x,
out y);
return new Rect(result.Left, result.Top, result.Width, result.Height); return new Rect(result.Left, result.Top, result.Width, result.Height);
} }
@ -175,12 +152,29 @@ namespace Avalonia.Direct2D1.Media
} }
} }
internal static class Direct2D1CustomFontResourceCache
{
private static readonly ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection> s_cachedFonts =
new ConcurrentDictionary<FontFamilyKey, DWrite.FontCollection>();
public static DWrite.FontCollection GetOrAddCustomFontResource(FontFamily fontFamily, DWrite.Factory factory)
{
return s_cachedFonts.GetOrAdd(fontFamily.Key, x => CreateCustomFontResource(x, factory));
}
private static DWrite.FontCollection CreateCustomFontResource(FontFamilyKey fontFamilyKey, DWrite.Factory factory)
{
var fontLoader = new ResourceFontLoader(factory, fontFamilyKey.BaseUri);
return new DWrite.FontCollection(factory, fontLoader, fontLoader.Key);
}
}
public class ResourceFontLoader : CallbackBase, DWrite.FontCollectionLoader, DWrite.FontFileLoader public class ResourceFontLoader : CallbackBase, DWrite.FontCollectionLoader, DWrite.FontFileLoader
{ {
private readonly List<ResourceFontFileStream> _fontStreams = new List<ResourceFontFileStream>(); private readonly List<ResourceFontFileStream> _fontStreams = new List<ResourceFontFileStream>();
private readonly List<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>(); private readonly List<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>();
private readonly DataStream _keyStream; private readonly DataStream _keyStream;
private readonly DWrite.Factory _factory;
/// <summary> /// <summary>
@ -190,7 +184,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="fontResource"></param> /// <param name="fontResource"></param>
public ResourceFontLoader(DWrite.Factory factory, Uri fontResource) public ResourceFontLoader(DWrite.Factory factory, Uri fontResource)
{ {
_factory = factory; var factory1 = factory;
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>(); var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
@ -215,8 +209,8 @@ namespace Avalonia.Direct2D1.Media
_keyStream.Position = 0; _keyStream.Position = 0;
// Register the // Register the
_factory.RegisterFontFileLoader(this); factory1.RegisterFontFileLoader(this);
_factory.RegisterFontCollectionLoader(this); factory1.RegisterFontCollectionLoader(this);
} }
@ -224,13 +218,7 @@ namespace Avalonia.Direct2D1.Media
/// Gets the key used to identify the FontCollection as well as storing index for fonts. /// Gets the key used to identify the FontCollection as well as storing index for fonts.
/// </summary> /// </summary>
/// <value>The key.</value> /// <value>The key.</value>
public DataStream Key public DataStream Key => _keyStream;
{
get
{
return _keyStream;
}
}
/// <summary> /// <summary>
/// Creates a font file enumerator object that encapsulates a collection of font files. The font system calls back to this interface to create a font collection. /// Creates a font file enumerator object that encapsulates a collection of font files. The font system calls back to this interface to create a font collection.
@ -282,7 +270,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public ResourceFontFileStream(DataStream stream) public ResourceFontFileStream(DataStream stream)
{ {
this._stream = stream; _stream = stream;
} }
/// <summary> /// <summary>
@ -384,19 +372,15 @@ namespace Avalonia.Direct2D1.Media
{ {
bool moveNext = keyStream.RemainingLength != 0; bool moveNext = keyStream.RemainingLength != 0;
if (moveNext) if (!moveNext) return false;
{
if (_currentFontFile != null)
{
_currentFontFile.Dispose();
}
_currentFontFile = new DWrite.FontFile(_factory, keyStream.PositionPointer, 4, _loader); _currentFontFile?.Dispose();
keyStream.Position += 4; _currentFontFile = new DWrite.FontFile(_factory, keyStream.PositionPointer, 4, _loader);
}
keyStream.Position += 4;
return moveNext; return true;
} }
/// <summary> /// <summary>

Loading…
Cancel
Save