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
// again.
AvaloniaLocator.CurrentMutable.Bind<IFontFamilyCache>().ToConstant(new FontFamilyCache());
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>
<None Remove="Assets\kooten.ttf" />
<None Remove="Assets\octin_stencil_free.ttf" />
</ItemGroup>
<ItemGroup>

4
samples/ControlCatalog/Pages/BorderPage.xaml

@ -27,8 +27,8 @@
Padding="16">
<StackPanel>
<TextBlock FontSize="14">Rounded Corners</TextBlock>
<TextBlock FontWeight="Bold" FontSize="14" FontFamily="Arial">Rounded Corners</TextBlock>
<TextBlock FontSize="14" FontFamily="res:///ControlCatalog/Assets/kooten.ttf#Kootenay">Rounded Corners</TextBlock>
<TextBlock FontSize="14" FontFamily="resm:ControlCatalog.Assets.octin_stencil_free.ttf#Octin Stencil Free">Rounded Corners</TextBlock>
<TextBlock FontSize="14" FontFamily="resm:ControlCatalog.Assets.kooten.ttf#Kootenay">Rounded Corners</TextBlock>
</StackPanel>
</Border>
</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.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Avalonia.Media
{
public class FontFamily
{
public FontFamily(string familyName) : this(familyName, null) { }
public FontFamily(string familyName, Uri baseUri)
public FontFamily(string familyName = "Courier New", Uri baseUri = null)
{
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 FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
public override string ToString()
{
ResourceUri = resourceUri;
FontStyle = fontStyle;
FontWeight = fontWeight;
return Key.ToString();
}
public Uri ResourceUri { get; }
public FontWeight FontWeight { get; }
public FontStyle FontStyle { get; }
}
public class FontFamilyKey
{
public FontFamilyKey(string friendlyName) : this(friendlyName, null) { }
public FontFamilyKey(string friendlyName, Uri baseUri)
public FontFamilyKey(string friendlyName, Uri baseUri = null)
{
FriendlyName = friendlyName;
BaseUri = baseUri;
@ -85,87 +68,111 @@ namespace Avalonia.Media
return true;
}
}
public class FamilyTypefaceKey
{
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()
public override string ToString()
{
unchecked
if (BaseUri != null)
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ FontWeight.GetHashCode();
hash = (hash * 16777619) ^ FontStyle.GetHashCode();
return hash;
return BaseUri + "#" + FriendlyName;
}
}
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
{
private readonly ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface> _typefaces =
new ConcurrentDictionary<FamilyTypefaceKey, FamilyTypeface>();
public IEnumerable<FamilyTypeface> SupportedTypefaces => _typefaces.Values;
public bool TryGetFamilyTypeface(out FamilyTypeface typeface, FontWeight fontWeight = FontWeight.Normal,
FontStyle fontStyle = FontStyle.Normal)
{
return _typefaces.TryGetValue(new FamilyTypefaceKey(fontWeight, fontStyle), out typeface);
}
public FamilyTypeface GetOrAddFamilyTypeface(Uri resourceUri, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
{
return _typefaces.GetOrAdd(new FamilyTypefaceKey(fontWeight, fontStyle), x => CreateFamilyTypeface(x, resourceUri));
}
private static FamilyTypeface CreateFamilyTypeface(FamilyTypefaceKey familyTypefaceKey, Uri resourceUri)
{
return new FamilyTypeface(resourceUri, familyTypefaceKey.FontWeight, familyTypefaceKey.FontStyle);
}
}
public interface IFontFamilyCache
{
CachedFontFamily GetOrAddFontFamily(FontFamily fontFamily);
}
public class FontFamilyCache : IFontFamilyCache
{
private readonly ConcurrentDictionary<FontFamilyKey, CachedFontFamily> _cachedFontFamilies = new ConcurrentDictionary<FontFamilyKey, CachedFontFamily>();
public CachedFontFamily GetOrAddFontFamily(FontFamily fontFamily)
{
return _cachedFontFamilies.GetOrAdd(fontFamily.FontFamilyKey, CreateCachedFontFamily);
}
private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey)
{
return new CachedFontFamily();
}
}
//public class FamilyTypeface
//{
// public FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
// {
// ResourceUri = resourceUri;
// FontStyle = fontStyle;
// FontWeight = fontWeight;
// }
// public Uri ResourceUri { get; }
// public FontWeight FontWeight { get; }
// public FontStyle FontStyle { get; }
//}
//public class FamilyTypefaceKey
//{
// 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
// {
// var hash = (int)2166136261;
// 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;
// }
//}
//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 (!s.StartsWith("resm:")) return new FontFamily(s);
var fontFamilyExpression = s.Split('#');
string familyName;
Uri baseUri = null;
var fontWeight = FontWeight.Normal;
var fontStyle = FontStyle.Normal;
switch (fontFamilyExpression.Length)
{
case 1:
@ -41,34 +39,13 @@ namespace Avalonia.Markup.Xaml.Converters
familyName = fontFamilyExpression[1];
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:
{
throw new ArgumentException("Specified family is not supported.");
}
}
var fontFamily = new FontFamily(familyName, baseUri);
var cachedFontFamily = AvaloniaLocator.Current.GetService<IFontFamilyCache>().GetOrAddFontFamily(fontFamily);
cachedFontFamily.GetOrAddFamilyTypeface(baseUri, fontWeight, fontStyle);
return fontFamily;
return new FontFamily(familyName, baseUri);
}
}
}

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.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Media;
using Avalonia.Platform;
@ -26,59 +26,46 @@ namespace Avalonia.Direct2D1.Media
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
DWrite.TextFormat textFormat;
if (typeface.FontFamily.BaseUri != null)
{
var fontFamily = AvaloniaLocator.Current.GetService<IFontFamilyCache>().GetOrAddFontFamily(typeface.FontFamily);
fontFamily.TryGetFamilyTypeface(out var familyTypeface);
var fontLoader = new ResourceFontLoader(factory, familyTypeface.ResourceUri);
var fontCollection = Direct2D1CustomFontResourceCache.GetOrAddCustomFontResource(typeface.FontFamily, factory);
var fontCollection = new DWrite.FontCollection(factory, fontLoader, fontLoader.Key);
using (var textFormat =
new DWrite.TextFormat(factory, typeface.FontFamily.Name, fontCollection, DWrite.FontWeight.Normal,
DWrite.FontStyle.Normal, DWrite.FontStretch.Normal, (float)typeface.FontSize))
{
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 = new DWrite.TextFormat(
factory,
typeface.FontFamily.Name,
fontCollection,
(DWrite.FontWeight)typeface.Weight,
(DWrite.FontStyle)typeface.Style,
DWrite.FontStretch.Normal,
(float)typeface.FontSize);
}
else
{
using (var format = new DWrite.TextFormat(
textFormat = new DWrite.TextFormat(
factory,
typeface?.FontFamily.Name ?? "Courier New",
(DWrite.FontWeight)(typeface.Weight),
(DWrite.FontStyle)(typeface.Style),
(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()
};
}
typeface.FontFamily.Name,
(DWrite.FontWeight)typeface.Weight,
(DWrite.FontStyle)typeface.Style,
(float)typeface.FontSize);
}
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)
{
foreach (var span in spans)
@ -98,10 +85,10 @@ namespace Avalonia.Direct2D1.Media
public DWrite.TextLayout TextLayout { get; }
public void Dispose()
{
TextLayout.Dispose();
}
//public void Dispose()
//{
// TextLayout.Dispose();
//}
public IEnumerable<FormattedTextLine> GetLines()
{
@ -111,14 +98,11 @@ namespace Avalonia.Direct2D1.Media
public TextHitTestResult HitTestPoint(Point point)
{
SharpDX.Mathematics.Interop.RawBool isTrailingHit;
SharpDX.Mathematics.Interop.RawBool isInside;
var result = TextLayout.HitTestPoint(
(float)point.X,
(float)point.Y,
out isTrailingHit,
out isInside);
out var isTrailingHit,
out var isInside);
return new TextHitTestResult
{
@ -130,14 +114,7 @@ namespace Avalonia.Direct2D1.Media
public Rect HitTestTextPosition(int index)
{
float x;
float y;
var result = TextLayout.HitTestTextPosition(
index,
false,
out x,
out y);
var result = TextLayout.HitTestTextPosition(index, false, out _, out _);
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
{
private readonly List<ResourceFontFileStream> _fontStreams = new List<ResourceFontFileStream>();
private readonly List<ResourceFontFileEnumerator> _enumerators = new List<ResourceFontFileEnumerator>();
private readonly DataStream _keyStream;
private readonly DWrite.Factory _factory;
/// <summary>
@ -190,7 +184,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="fontResource"></param>
public ResourceFontLoader(DWrite.Factory factory, Uri fontResource)
{
_factory = factory;
var factory1 = factory;
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
@ -215,8 +209,8 @@ namespace Avalonia.Direct2D1.Media
_keyStream.Position = 0;
// Register the
_factory.RegisterFontFileLoader(this);
_factory.RegisterFontCollectionLoader(this);
factory1.RegisterFontFileLoader(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.
/// </summary>
/// <value>The key.</value>
public DataStream Key
{
get
{
return _keyStream;
}
}
public DataStream Key => _keyStream;
/// <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.
@ -282,7 +270,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="stream">The stream.</param>
public ResourceFontFileStream(DataStream stream)
{
this._stream = stream;
_stream = stream;
}
/// <summary>
@ -384,19 +372,15 @@ namespace Avalonia.Direct2D1.Media
{
bool moveNext = keyStream.RemainingLength != 0;
if (moveNext)
{
if (_currentFontFile != null)
{
_currentFontFile.Dispose();
}
if (!moveNext) return false;
_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>

Loading…
Cancel
Save