From 3b308d8d19149dec7cc4db594e062bdc1a06593a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 30 Apr 2018 23:01:26 +0200 Subject: [PATCH] Direct2D1 experimental --- samples/ControlCatalog/Assets/kooten.ttf | Bin 0 -> 42164 bytes samples/ControlCatalog/ControlCatalog.csproj | 3 + samples/ControlCatalog/Pages/BorderPage.xaml | 40 +-- .../Primitives/TemplatedControl.cs | 4 +- src/Avalonia.Controls/TextBlock.cs | 10 +- src/Avalonia.Visuals/Media/FontFamily.cs | 27 +- src/Avalonia.Visuals/Media/Typeface.cs | 4 +- .../Rendering/RendererBase.cs | 2 +- .../Avalonia.Markup.Xaml.csproj | 1 + .../Converters/FontFamilyTypeConverter.cs | 40 +++ .../AvaloniaDefaultTypeConverters.cs | 1 + src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 8 +- .../Media/FormattedTextImpl.cs | 303 +++++++++++++++++- 13 files changed, 383 insertions(+), 60 deletions(-) create mode 100644 samples/ControlCatalog/Assets/kooten.ttf create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs diff --git a/samples/ControlCatalog/Assets/kooten.ttf b/samples/ControlCatalog/Assets/kooten.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3b052635fbc7c71317e8f90f450c3ac411f7554c GIT binary patch literal 42164 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�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 iC%0|Wwil1CT4JwVaCgR + + + diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index a81bd13ddd..a95bc945ae 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -1,32 +1,36 @@ - - Border - A control which decorates a child with a border and background + + Border + A control which decorates a child with a border and background - - - Border - - + Border + + - Border and Background - - Border and Background + + - Rounded Corners - - Rounded Corners + + - Rounded Corners - - - + + Rounded Corners + Rounded Corners + Rounded Corners + + + + \ No newline at end of file diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 77735f3f12..65b245e36c 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls.Primitives /// /// Defines the property. /// - public static readonly StyledProperty FontFamilyProperty = + public static readonly StyledProperty FontFamilyProperty = TextBlock.FontFamilyProperty.AddOwner(); /// @@ -141,7 +141,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the font family used to draw the control's text. /// - public string FontFamily + public FontFamily FontFamily { get { return GetValue(FontFamilyProperty); } set { SetValue(FontFamilyProperty, value); } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 88a9fe077d..bb2bae2ac3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -28,10 +28,10 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly AttachedProperty FontFamilyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty FontFamilyProperty = + AvaloniaProperty.RegisterAttached( nameof(FontFamily), - defaultValue: "Courier New", + defaultValue: new FontFamily("Courier New"), inherits: true); /// @@ -146,7 +146,7 @@ namespace Avalonia.Controls /// /// Gets or sets the font family. /// - public string FontFamily + public FontFamily FontFamily { get { return GetValue(FontFamilyProperty); } set { SetValue(FontFamilyProperty, value); } @@ -227,7 +227,7 @@ namespace Avalonia.Controls /// /// The control. /// The font family. - public static string GetFontFamily(Control control) + public static FontFamily GetFontFamily(Control control) { return control.GetValue(FontFamilyProperty); } diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index ea2d3701b2..d702a311a2 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -26,18 +26,9 @@ namespace Avalonia.Media internal FontFamilyKey FontFamilyKey { get; } - internal IFontFamily LoadedFamily - { - get - { - if (_loadedFamily == null) - { - _loadedFamily = AvaloniaLocator.Current.GetService().LoadFontFamily(FontFamilyKey); - } - - return _loadedFamily; - } - } + internal IFontFamily LoadedFamily => _loadedFamily ?? (_loadedFamily = AvaloniaLocator.Current + .GetService() + .LoadFontFamily(FontFamilyKey)); public IEnumerable AvailableTypefaces => LoadedFamily.SupportedTypefaces; } @@ -92,6 +83,18 @@ namespace Avalonia.Media public IEnumerable SupportedTypefaces { get; } } + internal class CustomFont : IFontFamily + { + public CustomFont() : this(new List { new FamilyTypeface() }) { } + + public CustomFont(IEnumerable supportedTypefaces) + { + SupportedTypefaces = new ReadOnlyCollection(new List(supportedTypefaces)); + } + + public IEnumerable SupportedTypefaces { get; } + } + internal interface IFontFamilyLoader { IFontFamily LoadFontFamily(FontFamilyKey fontFamilyKey); diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index c85144d48e..6baec1a246 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// public class Typeface { - public Typeface(FontFamily fontFamily, double fontSize, FontStyle style = FontStyle.Normal, + public Typeface(FontFamily fontFamily, double fontSize = 12, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal) { if (fontSize <= 0) @@ -35,7 +35,7 @@ namespace Avalonia.Media /// The font weight. public Typeface( string fontFamilyName, - double fontSize, + double fontSize = 12, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal) : this(new FontFamily(fontFamilyName), fontSize, style, weight) { } diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs index eac362e997..ed464ec7f9 100644 --- a/src/Avalonia.Visuals/Rendering/RendererBase.cs +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering { _fpsText = new FormattedText { - Typeface = new Typeface(null, 18), + Typeface = s_fpsTypeface }; } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index bd6acfdad1..6447dca2b2 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -32,6 +32,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs new file mode 100644 index 0000000000..51d35fabff --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using Avalonia.Media; + +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; + + if (string.IsNullOrEmpty(s)) throw new ArgumentException("Specified family is not supported."); + + var fontFamilyExpression = s.Split('#'); + + switch (fontFamilyExpression.Length) + { + case 1: + { + return new FontFamily(fontFamilyExpression[0]); + } + case 2: + { + return new FontFamily(fontFamilyExpression[1], new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute)); + } + default: + { + throw new ArgumentException("Specified family is not supported."); + } + } + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs index 1ae24c8a34..6b523cae6f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs @@ -49,6 +49,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml { typeof(Cursor), typeof(CursorTypeConverter) }, { typeof(WindowIcon), typeof(IconTypeConverter) }, //{ typeof(FontWeight), typeof(FontWeightConverter) }, + { typeof(FontFamily), typeof(FontFamilyTypeConverter)} }; public static Type GetTypeConverter(Type type) diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index f22722a0b5..baf3a210d5 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -26,9 +26,9 @@ namespace Avalonia.Skia Text = Text.Replace((char)0, (char)0x200B); var skiaTypeface = TypefaceCache.GetTypeface( - typeface?.FontFamilyName ?? "monospace", - typeface?.Style ?? FontStyle.Normal, - typeface?.Weight ?? FontWeight.Normal); + typeface.FontFamily.Name ?? "monospace", + typeface.Style, + typeface.Weight); _paint = new SKPaint(); @@ -40,7 +40,7 @@ namespace Avalonia.Skia _paint.LcdRenderText = true; _paint.SubpixelText = true; _paint.Typeface = skiaTypeface; - _paint.TextSize = (float)(typeface?.FontSize ?? 12); + _paint.TextSize = (float)typeface.FontSize; _paint.TextAlign = textAlignment.ToSKTextAlign(); _wrapping = wrapping; diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 5578abc32c..2b3aa31c64 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Avalonia.Media; using Avalonia.Platform; +using SharpDX; using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media @@ -21,28 +23,56 @@ namespace Avalonia.Direct2D1.Media IReadOnlyList spans) { Text = text; + var factory = AvaloniaLocator.Current.GetService(); - using (var format = new DWrite.TextFormat( - factory, - typeface?.FontFamilyName ?? "Courier New", - (DWrite.FontWeight)(typeface?.Weight ?? FontWeight.Normal), - (DWrite.FontStyle)(typeface?.Style ?? FontStyle.Normal), - (float)(typeface?.FontSize ?? 12))) + if (typeface.FontFamily.BaseUri != null) { - format.WordWrapping = wrapping == TextWrapping.Wrap ? - DWrite.WordWrapping.Wrap : - DWrite.WordWrapping.NoWrap; + var fontLoader = new ResourceFontLoader(factory, typeface.FontFamily.BaseUri); + + 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( + TextLayout = new DWrite.TextLayout(factory, Text ?? string.Empty, textFormat, (float)constraint.Width, + (float)constraint.Height) + { + TextAlignment = textAlignment.ToDirect2D() + }; + } + } + else + { + using (var format = new DWrite.TextFormat( factory, - text ?? string.Empty, - format, - (float)constraint.Width, - (float)constraint.Height) + typeface?.FontFamily.Name ?? "Courier New", + (DWrite.FontWeight)(typeface.Weight), + (DWrite.FontStyle)(typeface.Style), + (float)typeface.FontSize)) { - TextAlignment = textAlignment.ToDirect2D() - }; + 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() + }; + } } if (spans != null) @@ -140,4 +170,245 @@ namespace Avalonia.Direct2D1.Media return new Size(width, TextLayout.Metrics.Height); } } + + public class ResourceFontLoader : CallbackBase, DWrite.FontCollectionLoader, DWrite.FontFileLoader + { + private readonly List _fontStreams = new List(); + private readonly List _enumerators = new List(); + private readonly DataStream _keyStream; + private readonly DWrite.Factory _factory; + + + /// + /// Initializes a new instance of the class. + /// + /// The factory. + /// + public ResourceFontLoader(DWrite.Factory factory, Uri fontResource) + { + _factory = factory; + + var assets = AvaloniaLocator.Current.GetService(); + + var resourceStream = assets.Open(fontResource); + + var dataStream = new DataStream((int)resourceStream.Length, true, true); + + resourceStream.CopyTo(dataStream); + + dataStream.Position = 0; + + _fontStreams.Add(new ResourceFontFileStream(dataStream)); + + // Build a Key storage that stores the index of the font + _keyStream = new DataStream(sizeof(int) * _fontStreams.Count, true, true); + + for (int i = 0; i < _fontStreams.Count; i++) + { + _keyStream.Write(i); + } + + _keyStream.Position = 0; + + // Register the + _factory.RegisterFontFileLoader(this); + _factory.RegisterFontCollectionLoader(this); + } + + + /// + /// Gets the key used to identify the FontCollection as well as storing index for fonts. + /// + /// The key. + public DataStream Key + { + get + { + return _keyStream; + } + } + + /// + /// 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. + /// + /// Pointer to the object that was used to create the current font collection. + /// A font collection key that uniquely identifies the collection of font files within the scope of the font collection loader being used. The buffer allocated for this key must be at least the size, in bytes, specified by collectionKeySize. + /// + /// a reference to the newly created font file enumerator. + /// + /// HRESULT IDWriteFontCollectionLoader::CreateEnumeratorFromKey([None] IDWriteFactory* factory,[In, Buffer] const void* collectionKey,[None] int collectionKeySize,[Out] IDWriteFontFileEnumerator** fontFileEnumerator) + DWrite.FontFileEnumerator DWrite.FontCollectionLoader.CreateEnumeratorFromKey(DWrite.Factory factory, DataPointer collectionKey) + { + var enumerator = new ResourceFontFileEnumerator(factory, this, collectionKey); + + _enumerators.Add(enumerator); + + return enumerator; + } + + /// + /// Creates a font file stream object that encapsulates an open file resource. + /// + /// A reference to a font file reference key that uniquely identifies the font file resource within the scope of the font loader being used. The buffer allocated for this key must at least be the size, in bytes, specified by fontFileReferenceKeySize. + /// + /// a reference to the newly created object. + /// + /// + /// The resource is closed when the last reference to fontFileStream is released. + /// + /// HRESULT IDWriteFontFileLoader::CreateStreamFromKey([In, Buffer] const void* fontFileReferenceKey,[None] int fontFileReferenceKeySize,[Out] IDWriteFontFileStream** fontFileStream) + DWrite.FontFileStream DWrite.FontFileLoader.CreateStreamFromKey(DataPointer fontFileReferenceKey) + { + var index = SharpDX.Utilities.Read(fontFileReferenceKey.Pointer); + + return _fontStreams[index]; + } + } + + /// + /// This FontFileStream implem is reading data from a . + /// + public class ResourceFontFileStream : CallbackBase, DWrite.FontFileStream + { + private readonly DataStream _stream; + + /// + /// Initializes a new instance of the class. + /// + /// The stream. + public ResourceFontFileStream(DataStream stream) + { + this._stream = stream; + } + + /// + /// Reads a fragment from a font file. + /// + /// When this method returns, contains an address of a reference to the start of the font file fragment. This parameter is passed uninitialized. + /// The offset of the fragment, in bytes, from the beginning of the font file. + /// The size of the file fragment, in bytes. + /// When this method returns, contains the address of + /// + /// Note that ReadFileFragment implementations must check whether the requested font file fragment is within the file bounds. Otherwise, an error should be returned from ReadFileFragment. {{DirectWrite}} may invoke methods on the same object from multiple threads simultaneously. Therefore, ReadFileFragment implementations that rely on internal mutable state must serialize access to such state across multiple threads. For example, an implementation that uses separate Seek and Read operations to read a file fragment must place the code block containing Seek and Read calls under a lock or a critical section. + /// + /// HRESULT IDWriteFontFileStream::ReadFileFragment([Out, Buffer] const void** fragmentStart,[None] __int64 fileOffset,[None] __int64 fragmentSize,[Out] void** fragmentContext) + void DWrite.FontFileStream.ReadFileFragment(out IntPtr fragmentStart, long fileOffset, long fragmentSize, out IntPtr fragmentContext) + { + lock (this) + { + fragmentContext = IntPtr.Zero; + + _stream.Position = fileOffset; + + fragmentStart = _stream.PositionPointer; + + } + } + + /// + /// Releases a fragment from a file. + /// + /// A reference to the client-defined context of a font fragment returned from {{ReadFileFragment}}. + /// void IDWriteFontFileStream::ReleaseFileFragment([None] void* fragmentContext) + void DWrite.FontFileStream.ReleaseFileFragment(IntPtr fragmentContext) + { + // Nothing to release. No context are used + } + + /// + /// Obtains the total size of a file. + /// + /// the total size of the file. + /// + /// Implementing GetFileSize() for asynchronously loaded font files may require downloading the complete file contents. Therefore, this method should be used only for operations that either require a complete font file to be loaded (for example, copying a font file) or that need to make decisions based on the value of the file size (for example, validation against a persisted file size). + /// + /// HRESULT IDWriteFontFileStream::GetFileSize([Out] __int64* fileSize) + long DWrite.FontFileStream.GetFileSize() + { + return _stream.Length; + } + + /// + /// Obtains the last modified time of the file. + /// + /// + /// the last modified time of the file in the format that represents the number of 100-nanosecond intervals since January 1, 1601 (UTC). + /// + /// + /// The "last modified time" is used by DirectWrite font selection algorithms to determine whether one font resource is more up to date than another one. + /// + /// HRESULT IDWriteFontFileStream::GetLastWriteTime([Out] __int64* lastWriteTime) + long DWrite.FontFileStream.GetLastWriteTime() + { + return 0; + } + } + + /// + /// Resource FontFileEnumerator. + /// + public class ResourceFontFileEnumerator : CallbackBase, DWrite.FontFileEnumerator + { + private DWrite.Factory _factory; + private DWrite.FontFileLoader _loader; + private DataStream keyStream; + private DWrite.FontFile _currentFontFile; + + /// + /// Initializes a new instance of the class. + /// + /// The factory. + /// The loader. + /// The key. + public ResourceFontFileEnumerator(DWrite.Factory factory, DWrite.FontFileLoader loader, DataPointer key) + { + _factory = factory; + + _loader = loader; + + keyStream = new DataStream(key.Pointer, key.Size, true, false); + } + + /// + /// Advances to the next font file in the collection. When it is first created, the enumerator is positioned before the first element of the collection and the first call to MoveNext advances to the first file. + /// + /// + /// the value TRUE if the enumerator advances to a file; otherwise, FALSE if the enumerator advances past the last file in the collection. + /// + /// HRESULT IDWriteFontFileEnumerator::MoveNext([Out] BOOL* hasCurrentFile) + bool DWrite.FontFileEnumerator.MoveNext() + { + bool moveNext = keyStream.RemainingLength != 0; + + if (moveNext) + { + if (_currentFontFile != null) + { + _currentFontFile.Dispose(); + } + + _currentFontFile = new DWrite.FontFile(_factory, keyStream.PositionPointer, 4, _loader); + + keyStream.Position += 4; + } + + return moveNext; + } + + /// + /// Gets a reference to the current font file. + /// + /// + /// a reference to the newly created object. + /// HRESULT IDWriteFontFileEnumerator::GetCurrentFontFile([Out] IDWriteFontFile** fontFile) + DWrite.FontFile DWrite.FontFileEnumerator.CurrentFontFile + { + get + { + ((IUnknown)_currentFontFile).AddReference(); + + return _currentFontFile; + } + } + } }