diff --git a/Avalonia.sln b/Avalonia.sln
index 568a16ce0e..e40ebae4d6 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -128,6 +128,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\BuildTargets.targets = build\BuildTargets.targets
+ build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@@ -201,7 +202,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
new file mode 100644
index 0000000000..f8767c7599
--- /dev/null
+++ b/build/HarfBuzzSharp.props
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index c03ad0fefd..796bd8e596 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 5931fec350..e0cc9aa128 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -297,7 +297,8 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
- Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
+ Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
+ FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,
}.Bounds.Size;
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index b9603b91ed..c7855ddfd1 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -352,10 +352,11 @@ namespace Avalonia.Controls
return new FormattedText
{
Constraint = constraint,
- Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
+ Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
+ FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
- Wrapping = TextWrapping,
+ TextWrapping = TextWrapping,
};
}
diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs
index a486723d86..b57b4a0ca8 100644
--- a/src/Avalonia.Visuals/Media/FontFamily.cs
+++ b/src/Avalonia.Visuals/Media/FontFamily.cs
@@ -5,12 +5,16 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media.Fonts;
-using Avalonia.Platform;
namespace Avalonia.Media
{
- public class FontFamily
+ public sealed class FontFamily
{
+ static FontFamily()
+ {
+ Default = new FontFamily(FontManager.Default.DefaultFontFamilyName);
+ }
+
///
///
/// Initializes a new instance of the class.
@@ -30,9 +34,7 @@ namespace Avalonia.Media
{
if (string.IsNullOrEmpty(name))
{
- FamilyNames = new FamilyNameCollection(string.Empty);
-
- return;
+ throw new ArgumentNullException(nameof(name));
}
var fontFamilySegment = GetFontFamilyIdentifier(name);
@@ -53,13 +55,16 @@ namespace Avalonia.Media
///
/// Represents the default font family
///
- public static FontFamily Default => new FontFamily(string.Empty);
+ public static FontFamily Default { get; }
///
/// Represents all font families in the system. This can be an expensive call depending on platform implementation.
///
+ ///
+ /// Consider using the new instead.
+ ///
public static IEnumerable SystemFontFamilies =>
- AvaloniaLocator.Current.GetService().InstalledFontNames.Select(name => new FontFamily(name));
+ FontManager.Default.GetInstalledFontFamilyNames().Select(name => new FontFamily(name));
///
/// Gets the primary family name of the font family.
@@ -181,7 +186,14 @@ namespace Avalonia.Media
{
var hash = (int)2186146271;
- hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
+ if (Key != null)
+ {
+ hash = (hash * 15768619) ^ Key.GetHashCode();
+ }
+ else
+ {
+ hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
+ }
if (Key != null)
{
diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs
new file mode 100644
index 0000000000..be1bd269ed
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/FontManager.cs
@@ -0,0 +1,112 @@
+// 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.Collections.Generic;
+using System.Globalization;
+using Avalonia.Platform;
+
+namespace Avalonia.Media
+{
+ ///
+ /// The font manager is used to query the system's installed fonts and is responsible for caching loaded fonts.
+ /// It is also responsible for the font fallback.
+ ///
+ public abstract class FontManager
+ {
+ public static readonly FontManager Default = CreateDefault();
+
+ ///
+ /// Gets the system's default font family's name.
+ ///
+ public string DefaultFontFamilyName
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// Get all installed fonts in the system.
+ /// If true the font collection is updated.
+ ///
+ public abstract IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false);
+
+ ///
+ /// Get a cached typeface from specified parameters.
+ ///
+ /// The font family.
+ /// The font weight.
+ /// The font style.
+ ///
+ /// The cached typeface.
+ ///
+ public abstract Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle);
+
+ ///
+ /// Tries to match a specified character to a typeface that supports specified font properties.
+ /// Returns null if no fallback was found.
+ ///
+ /// The codepoint to match against.
+ /// The font weight.
+ /// The font style.
+ /// The font family. This is optional and used for fallback lookup.
+ /// The culture.
+ ///
+ /// The matched typeface.
+ ///
+ public abstract Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
+ FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null);
+
+ public static FontManager CreateDefault()
+ {
+ var platformImpl = AvaloniaLocator.Current.GetService();
+
+ if (platformImpl != null)
+ {
+ return new PlatformFontManager(platformImpl);
+ }
+
+ return new EmptyFontManager();
+ }
+
+ private class PlatformFontManager : FontManager
+ {
+ private readonly IFontManagerImpl _platformImpl;
+
+ public PlatformFontManager(IFontManagerImpl platformImpl)
+ {
+ _platformImpl = platformImpl;
+
+ DefaultFontFamilyName = _platformImpl.DefaultFontFamilyName;
+ }
+
+ public override IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
+ _platformImpl.GetInstalledFontFamilyNames(checkForUpdates);
+
+ public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) =>
+ _platformImpl.GetTypeface(fontFamily, fontWeight, fontStyle);
+
+ public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
+ FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null) =>
+ _platformImpl.MatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture);
+ }
+
+ private class EmptyFontManager : FontManager
+ {
+ public EmptyFontManager()
+ {
+ DefaultFontFamilyName = "Empty";
+ }
+
+ public override IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
+ new[] { DefaultFontFamilyName };
+
+ public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => new Typeface(fontFamily, fontWeight, fontStyle);
+
+ public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
+ FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null) => null;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
index acf0bbdb11..e777d93315 100644
--- a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
+++ b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
@@ -9,7 +9,7 @@ using System.Text;
namespace Avalonia.Media.Fonts
{
- public class FamilyNameCollection : IEnumerable
+ public sealed class FamilyNameCollection : IReadOnlyList
{
///
/// Initializes a new instance of the class.
@@ -130,5 +130,9 @@ namespace Avalonia.Media.Fonts
return other.ToString().Equals(ToString());
}
+
+ public int Count => Names.Count;
+
+ public string this[int index] => Names[index];
}
}
diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs
index e20e03e296..5013f925b3 100644
--- a/src/Avalonia.Visuals/Media/FormattedText.cs
+++ b/src/Avalonia.Visuals/Media/FormattedText.cs
@@ -16,9 +16,10 @@ namespace Avalonia.Media
private IFormattedTextImpl _platformImpl;
private IReadOnlyList _spans;
private Typeface _typeface;
+ private double _fontSize;
private string _text;
private TextAlignment _textAlignment;
- private TextWrapping _wrapping;
+ private TextWrapping _textWrapping;
///
/// Initializes a new instance of the class.
@@ -37,6 +38,31 @@ namespace Avalonia.Media
_platform = platform;
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public FormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment,
+ TextWrapping textWrapping, Size constraint)
+ {
+ _text = text;
+
+ _typeface = typeface;
+
+ _fontSize = fontSize;
+
+ _textAlignment = textAlignment;
+
+ _textWrapping = textWrapping;
+
+ _constraint = constraint;
+ }
+
///
/// Gets the bounds of the text within the .
///
@@ -61,6 +87,16 @@ namespace Avalonia.Media
set => Set(ref _typeface, value);
}
+
+ ///
+ /// Gets or sets the font size.
+ ///
+ public double FontSize
+ {
+ get => _fontSize;
+ set => Set(ref _fontSize, value);
+ }
+
///
/// Gets or sets a collection of spans that describe the formatting of subsections of the
/// text.
@@ -92,10 +128,10 @@ namespace Avalonia.Media
///
/// Gets or sets the text wrapping.
///
- public TextWrapping Wrapping
+ public TextWrapping TextWrapping
{
- get => _wrapping;
- set => Set(ref _wrapping, value);
+ get => _textWrapping;
+ set => Set(ref _textWrapping, value);
}
///
@@ -110,8 +146,9 @@ namespace Avalonia.Media
_platformImpl = _platform.CreateFormattedText(
_text,
_typeface,
+ _fontSize,
_textAlignment,
- _wrapping,
+ _textWrapping,
_constraint,
_spans);
}
diff --git a/src/Avalonia.Visuals/Media/GlyphTypeface.cs b/src/Avalonia.Visuals/Media/GlyphTypeface.cs
new file mode 100644
index 0000000000..3ba31f7e84
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/GlyphTypeface.cs
@@ -0,0 +1,111 @@
+// 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 Avalonia.Platform;
+
+namespace Avalonia.Media
+{
+ public sealed class GlyphTypeface : IDisposable
+ {
+ private static readonly IPlatformRenderInterface s_platformRenderInterface =
+ AvaloniaLocator.Current.GetService();
+
+ public GlyphTypeface(Typeface typeface) : this(s_platformRenderInterface.CreateGlyphTypeface(typeface))
+ {
+ }
+
+ public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
+ {
+ PlatformImpl = platformImpl;
+ }
+
+ public IGlyphTypefaceImpl PlatformImpl { get; }
+
+ ///
+ /// Gets the font design units per em.
+ ///
+ public short DesignEmHeight => PlatformImpl.DesignEmHeight;
+
+ ///
+ /// Gets the recommended distance above the baseline in design em size.
+ ///
+ public int Ascent => PlatformImpl.Ascent;
+
+ ///
+ /// Gets the recommended distance under the baseline in design em size.
+ ///
+ public int Descent => PlatformImpl.Descent;
+
+ ///
+ /// Gets the recommended additional space between two lines of text in design em size.
+ ///
+ public int LineGap => PlatformImpl.LineGap;
+
+ ///
+ /// Gets the recommended line height.
+ ///
+ public int LineHeight => Descent - Ascent + LineGap;
+
+ ///
+ /// Gets a value that indicates the distance of the underline from the baseline in design em size.
+ ///
+ public int UnderlinePosition => PlatformImpl.UnderlinePosition;
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ public int UnderlineThickness => PlatformImpl.UnderlineThickness;
+
+ ///
+ /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
+ ///
+ public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
+
+ ///
+ /// Returns an glyph index for the specified codepoint.
+ ///
+ ///
+ /// Returns 0 if a glyph isn't found.
+ ///
+ /// The codepoint.
+ ///
+ /// A glyph index.
+ ///
+ public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
+
+ ///
+ /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0.
+ ///
+ /// The codepoints to map.
+ ///
+ public ushort[] GetGlyphs(ReadOnlySpan codepoints) => PlatformImpl.GetGlyphs(codepoints);
+
+ ///
+ /// Returns the glyph advance for the specified glyph.
+ ///
+ /// The glyph.
+ ///
+ /// The advance.
+ ///
+ public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
+
+ ///
+ /// Returns an array of glyph advances in design em size.
+ ///
+ /// The glyph indices.
+ ///
+ public int[] GetGlyphAdvances(ReadOnlySpan glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
+
+ void IDisposable.Dispose()
+ {
+ PlatformImpl?.Dispose();
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs
index 37ac0953bf..a6d5c8a43c 100644
--- a/src/Avalonia.Visuals/Media/Typeface.cs
+++ b/src/Avalonia.Visuals/Media/Typeface.cs
@@ -1,39 +1,38 @@
-using System;
+// 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.Diagnostics;
+using JetBrains.Annotations;
namespace Avalonia.Media
{
///
/// Represents a typeface.
///
- public class Typeface
+ [DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")]
+ public class Typeface : IEquatable
{
public static readonly Typeface Default = new Typeface(FontFamily.Default);
+ private GlyphTypeface _glyphTypeface;
+
///
/// Initializes a new instance of the class.
///
/// The font family.
- /// The font size, in DIPs.
- /// The font style.
/// The font weight.
- public Typeface(
- FontFamily fontFamily,
- double fontSize = 12,
- FontStyle style = FontStyle.Normal,
- FontWeight weight = FontWeight.Normal)
+ /// The font style.
+ public Typeface([NotNull]FontFamily fontFamily,
+ FontWeight weight = FontWeight.Normal,
+ FontStyle style = FontStyle.Normal)
{
- if (fontSize <= 0)
- {
- throw new ArgumentException("Font size must be > 0.");
- }
-
if (weight <= 0)
{
throw new ArgumentException("Font weight must be > 0.");
}
FontFamily = fontFamily;
- FontSize = fontSize;
Style = style;
Weight = weight;
}
@@ -42,15 +41,12 @@ namespace Avalonia.Media
/// Initializes a new instance of the class.
///
/// The name of the font family.
- /// The font size, in DIPs.
/// The font style.
/// The font weight.
- public Typeface(
- string fontFamilyName,
- double fontSize = 12,
- FontStyle style = FontStyle.Normal,
- FontWeight weight = FontWeight.Normal)
- : this(new FontFamily(fontFamilyName), fontSize, style, weight)
+ public Typeface(string fontFamilyName,
+ FontWeight weight = FontWeight.Normal,
+ FontStyle style = FontStyle.Normal)
+ : this(new FontFamily(fontFamilyName), weight, style)
{
}
@@ -59,11 +55,6 @@ namespace Avalonia.Media
///
public FontFamily FontFamily { get; }
- ///
- /// Gets the size of the font in DIPs.
- ///
- public double FontSize { get; }
-
///
/// Gets the font style.
///
@@ -73,5 +64,59 @@ namespace Avalonia.Media
/// Gets the font weight.
///
public FontWeight Weight { get; }
+
+ ///
+ /// Gets the glyph typeface.
+ ///
+ ///
+ /// The glyph typeface.
+ ///
+ public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this));
+
+ public static bool operator !=(Typeface a, Typeface b)
+ {
+ return !(a == b);
+ }
+
+ public static bool operator ==(Typeface a, Typeface b)
+ {
+ if (ReferenceEquals(a, b))
+ {
+ return true;
+ }
+
+ return !(a is null) && a.Equals(b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Typeface typeface)
+ {
+ return Equals(typeface);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Typeface other)
+ {
+ if (other is null)
+ {
+ return false;
+ }
+
+ return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = (FontFamily != null ? FontFamily.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (int)Style;
+ hashCode = (hashCode * 397) ^ (int)Weight;
+ return hashCode;
+ }
+ }
}
}
diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
new file mode 100644
index 0000000000..254b5d07d1
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
@@ -0,0 +1,48 @@
+// 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.Collections.Generic;
+using System.Globalization;
+using Avalonia.Media;
+
+namespace Avalonia.Platform
+{
+ public interface IFontManagerImpl
+ {
+ ///
+ /// Gets the system's default font family's name.
+ ///
+ string DefaultFontFamilyName { get; }
+
+ ///
+ /// Get all installed fonts in the system.
+ /// If true the font collection is updated.
+ ///
+ IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false);
+
+ ///
+ /// Get a typeface from specified parameters.
+ ///
+ /// The font family.
+ /// The font weight.
+ /// The font style.
+ ///
+ /// The typeface.
+ ///
+ Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle);
+
+ ///
+ /// Tries to match a specified character to a typeface that supports specified font properties.
+ ///
+ /// The codepoint to match against.
+ /// The font weight.
+ /// The font style.
+ /// The font family. This is optional and used for fallback lookup.
+ /// The culture.
+ ///
+ /// The typeface.
+ ///
+ Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null);
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Visuals/Platform/IGlyphTypefaceImpl.cs
new file mode 100644
index 0000000000..8c043a5129
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IGlyphTypefaceImpl.cs
@@ -0,0 +1,89 @@
+// 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.Platform
+{
+ public interface IGlyphTypefaceImpl : IDisposable
+ {
+ ///
+ /// Gets the font design units per em.
+ ///
+ short DesignEmHeight { get; }
+
+ ///
+ /// Gets the recommended distance above the baseline in design em size.
+ ///
+ int Ascent { get; }
+
+ ///
+ /// Gets the recommended distance under the baseline in design em size.
+ ///
+ int Descent { get; }
+
+ ///
+ /// Gets the recommended additional space between two lines of text in design em size.
+ ///
+ int LineGap { get; }
+
+ ///
+ /// Gets a value that indicates the distance of the underline from the baseline in design em size.
+ ///
+ int UnderlinePosition { get; }
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ int UnderlineThickness { get; }
+
+ ///
+ /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
+ ///
+ int StrikethroughPosition { get; }
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ int StrikethroughThickness { get; }
+
+ ///
+ /// Returns an glyph index for the specified codepoint.
+ ///
+ ///
+ /// Returns 0 if a glyph isn't found.
+ ///
+ /// The codepoint.
+ ///
+ /// A glyph index.
+ ///
+ ushort GetGlyph(uint codepoint);
+
+ ///
+ /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0.
+ ///
+ /// The codepoints to map.
+ ///
+ /// An array of glyph indices.
+ ///
+ ushort[] GetGlyphs(ReadOnlySpan codepoints);
+
+ ///
+ /// Returns the glyph advance for the specified glyph.
+ ///
+ /// The glyph.
+ ///
+ /// The advance.
+ ///
+ int GetGlyphAdvance(ushort glyph);
+
+ ///
+ /// Returns an array of glyph advances in design em size.
+ ///
+ /// The glyph indices.
+ ///
+ /// An array of glyph advances.
+ ///
+ int[] GetGlyphAdvances(ReadOnlySpan glyphs);
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index 87db9251e1..5a0a7b2f19 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -13,16 +13,12 @@ namespace Avalonia.Platform
///
public interface IPlatformRenderInterface
{
- ///
- /// Get all installed fonts in the system
- ///
- IEnumerable InstalledFontNames { get; }
-
///
/// Creates a formatted text implementation.
///
/// The text.
/// The base typeface.
+ /// The font size.
/// The text alignment.
/// The text wrapping mode.
/// The text layout constraints.
@@ -31,6 +27,7 @@ namespace Avalonia.Platform
IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -114,5 +111,14 @@ namespace Avalonia.Platform
/// The number of bytes per row.
/// An .
IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride);
+
+ ///
+ /// Creates a glyph typeface for specified typeface.
+ ///
+ /// The typeface.
+ ///
+ /// The glyph typeface implementation.
+ ///
+ IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs
index 7b10fc1212..e341f02901 100644
--- a/src/Avalonia.Visuals/Rendering/RendererBase.cs
+++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs
@@ -7,7 +7,8 @@ namespace Avalonia.Rendering
{
public class RendererBase
{
- private static readonly Typeface s_fpsTypeface = new Typeface("Arial", 18);
+ private static readonly Typeface s_fpsTypeface = new Typeface("Arial");
+ private static int s_fontSize = 18;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond;
private int _fps;
@@ -18,7 +19,8 @@ namespace Avalonia.Rendering
{
_fpsText = new FormattedText
{
- Typeface = s_fpsTypeface
+ Typeface = s_fpsTypeface,
+ FontSize = s_fontSize
};
}
diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
index 4f884cdf33..68da513528 100644
--- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
+++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
@@ -12,5 +12,6 @@
+
diff --git a/src/Skia/Avalonia.Skia/FontKey.cs b/src/Skia/Avalonia.Skia/FontKey.cs
new file mode 100644
index 0000000000..bb3fe230c1
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/FontKey.cs
@@ -0,0 +1,40 @@
+// 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 Avalonia.Media;
+
+namespace Avalonia.Skia
+{
+ internal readonly struct FontKey : IEquatable
+ {
+ public readonly FontStyle Style;
+ public readonly FontWeight Weight;
+
+ public FontKey(FontWeight weight, FontStyle style)
+ {
+ Style = style;
+ Weight = weight;
+ }
+
+ public override int GetHashCode()
+ {
+ var hash = 17;
+ hash = hash * 31 + (int)Style;
+ hash = hash * 31 + (int)Weight;
+
+ return hash;
+ }
+
+ public override bool Equals(object other)
+ {
+ return other is FontKey key && Equals(key);
+ }
+
+ public bool Equals(FontKey other)
+ {
+ return Style == other.Style &&
+ Weight == other.Weight;
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
new file mode 100644
index 0000000000..03de82178a
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
@@ -0,0 +1,82 @@
+// 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.Collections.Generic;
+using System.Globalization;
+using Avalonia.Media;
+using Avalonia.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ internal class FontManagerImpl : IFontManagerImpl
+ {
+ private SKFontManager _skFontManager = SKFontManager.Default;
+
+ public FontManagerImpl()
+ {
+ DefaultFontFamilyName = SKTypeface.Default.FamilyName;
+ }
+
+ public string DefaultFontFamilyName { get; }
+
+ public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false)
+ {
+ if (checkForUpdates)
+ {
+ _skFontManager = SKFontManager.CreateDefault();
+ }
+
+ return _skFontManager.FontFamilies;
+ }
+
+ public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
+ {
+ return TypefaceCache.Get(fontFamily.Name, fontWeight, fontStyle).Typeface;
+ }
+
+ public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null)
+ {
+ var fontFamilyName = FontFamily.Default.Name;
+
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentUICulture;
+ }
+
+ if (fontFamily != null)
+ {
+ foreach (var familyName in fontFamily.FamilyNames)
+ {
+ var skTypeface = _skFontManager.MatchCharacter(familyName, (SKFontStyleWeight)fontWeight,
+ SKFontStyleWidth.Normal,
+ (SKFontStyleSlant)fontStyle,
+ new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint);
+
+ if (skTypeface == null)
+ {
+ continue;
+ }
+
+ fontFamilyName = familyName;
+
+ break;
+ }
+ }
+ else
+ {
+ var skTypeface = _skFontManager.MatchCharacter(null, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal,
+ (SKFontStyleSlant)fontStyle,
+ new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint);
+
+ if (skTypeface != null)
+ {
+ fontFamilyName = skTypeface.FamilyName;
+ }
+ }
+
+ return GetTypeface(fontFamilyName, fontWeight, fontStyle);
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index 07e3700fc2..a9358cb458 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -18,6 +18,7 @@ namespace Avalonia.Skia
public FormattedTextImpl(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -28,47 +29,22 @@ namespace Avalonia.Skia
// Replace 0 characters with zero-width spaces (200B)
Text = Text.Replace((char)0, (char)0x200B);
- SKTypeface skiaTypeface = null;
+ var entry = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style);
- if (typeface.FontFamily.Key != null)
+ _paint = new SKPaint
{
- var typefaces = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
- skiaTypeface = typefaces.GetTypeFace(typeface);
- }
- else
- {
- if (typeface.FontFamily.FamilyNames.HasFallbacks)
- {
- foreach (var familyName in typeface.FontFamily.FamilyNames)
- {
- skiaTypeface = TypefaceCache.GetTypeface(
- familyName,
- typeface.Style,
- typeface.Weight);
- if (skiaTypeface.FamilyName != TypefaceCache.DefaultFamilyName) break;
- }
- }
- else
- {
- skiaTypeface = TypefaceCache.GetTypeface(
- typeface.FontFamily.Name,
- typeface.Style,
- typeface.Weight);
- }
- }
-
- _paint = new SKPaint();
+ TextEncoding = SKTextEncoding.Utf16,
+ IsStroke = false,
+ IsAntialias = true,
+ LcdRenderText = true,
+ SubpixelText = true,
+ Typeface = entry.SKTypeface,
+ TextSize = (float)fontSize,
+ TextAlign = textAlignment.ToSKTextAlign()
+ };
//currently Skia does not measure properly with Utf8 !!!
//Paint.TextEncoding = SKTextEncoding.Utf8;
- _paint.TextEncoding = SKTextEncoding.Utf16;
- _paint.IsStroke = false;
- _paint.IsAntialias = true;
- _paint.LcdRenderText = true;
- _paint.SubpixelText = true;
- _paint.Typeface = skiaTypeface;
- _paint.TextSize = (float)typeface.FontSize;
- _paint.TextAlign = textAlignment.ToSKTextAlign();
_wrapping = wrapping;
_constraint = constraint;
diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
new file mode 100644
index 0000000000..e46f766255
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
@@ -0,0 +1,179 @@
+// 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.Runtime.InteropServices;
+using Avalonia.Media;
+using Avalonia.Platform;
+using HarfBuzzSharp;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ public class GlyphTypefaceImpl : IGlyphTypefaceImpl
+ {
+ private bool _isDisposed;
+
+ public GlyphTypefaceImpl(Typeface typeface)
+ {
+ Typeface = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style).SKTypeface;
+
+ Face = new Face(GetTable)
+ {
+ UnitsPerEm = Typeface.UnitsPerEm
+ };
+
+ Font = new Font(Face);
+
+ Font.SetFunctionsOpenType();
+
+ Font.GetScale(out var xScale, out _);
+
+ DesignEmHeight = (short)xScale;
+
+ if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
+ {
+ Font.TryGetVerticalFontExtents(out fontExtents);
+ }
+
+ Ascent = -fontExtents.Ascender;
+
+ Descent = -fontExtents.Descender;
+
+ LineGap = fontExtents.LineGap;
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
+ {
+ UnderlinePosition = underlinePosition;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
+ {
+ UnderlineThickness = underlineThickness;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
+ {
+ StrikethroughPosition = strikethroughPosition;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
+ {
+ StrikethroughThickness = strikethroughThickness;
+ }
+ }
+
+ public Face Face { get; }
+
+ public Font Font { get; }
+
+ public SKTypeface Typeface { get; }
+
+ ///
+ public short DesignEmHeight { get; }
+
+ ///
+ public int Ascent { get; }
+
+ ///
+ public int Descent { get; }
+
+ ///
+ public int LineGap { get; }
+
+ //ToDo: Get these values from HarfBuzz
+ ///
+ public int UnderlinePosition { get; }
+
+ ///
+ public int UnderlineThickness { get; }
+
+ ///
+ public int StrikethroughPosition { get; }
+
+ ///
+ public int StrikethroughThickness { get; }
+
+ ///
+ public ushort GetGlyph(uint codepoint)
+ {
+ if (Font.TryGetGlyph(codepoint, out var glyph))
+ {
+ return (ushort)glyph;
+ }
+
+ return 0;
+ }
+
+ ///
+ public ushort[] GetGlyphs(ReadOnlySpan codepoints)
+ {
+ var glyphs = new ushort[codepoints.Length];
+
+ for (var i = 0; i < codepoints.Length; i++)
+ {
+ if (Font.TryGetGlyph(codepoints[i], out var glyph))
+ {
+ glyphs[i] = (ushort)glyph;
+ }
+ }
+
+ return glyphs;
+ }
+
+ ///
+ public int GetGlyphAdvance(ushort glyph)
+ {
+ return Font.GetHorizontalGlyphAdvance(glyph);
+ }
+
+ ///
+ public int[] GetGlyphAdvances(ReadOnlySpan glyphs)
+ {
+ var glyphIndices = new uint[glyphs.Length];
+
+ for (var i = 0; i < glyphs.Length; i++)
+ {
+ glyphIndices[i] = glyphs[i];
+ }
+
+ return Font.GetHorizontalGlyphAdvances(glyphIndices);
+ }
+
+ private Blob GetTable(Face face, Tag tag)
+ {
+ var size = Typeface.GetTableSize(tag);
+
+ var data = Marshal.AllocCoTaskMem(size);
+
+ var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data));
+
+ return Typeface.TryGetTableData(tag, 0, size, data) ?
+ new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null;
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+
+ if (!disposing)
+ {
+ return;
+ }
+
+ Font?.Dispose();
+ Face?.Dispose();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 15f38b1c4f..ee0cfb2f06 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -2,6 +2,7 @@
// 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 Avalonia.Controls.Platform.Surfaces;
@@ -17,12 +18,13 @@ namespace Avalonia.Skia
///
internal class PlatformRenderInterface : IPlatformRenderInterface
{
+ private readonly ConcurrentDictionary _glyphTypefaceCache =
+ new ConcurrentDictionary();
+
private readonly ICustomSkiaGpu _customSkiaGpu;
private GRContext GrContext { get; }
- public IEnumerable InstalledFontNames => SKFontManager.Default.FontFamilies;
-
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu)
{
if (customSkiaGpu != null)
@@ -52,12 +54,13 @@ namespace Avalonia.Skia
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
IReadOnlyList spans)
{
- return new FormattedTextImpl(text, typeface, textAlignment, wrapping, constraint, spans);
+ return new FormattedTextImpl(text, typeface,fontSize, textAlignment, wrapping, constraint, spans);
}
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
@@ -151,5 +154,10 @@ namespace Avalonia.Skia
{
return new WriteableBitmapImpl(size, dpi, format);
}
+
+ public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ {
+ return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface));
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
index 17448127b0..8ec2a9c3f8 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
@@ -4,114 +4,59 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
-
using Avalonia.Media;
-using SkiaSharp;
-
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
- private readonly ConcurrentDictionary> _fontFamilies =
- new ConcurrentDictionary>();
+ private readonly ConcurrentDictionary> _fontFamilies =
+ new ConcurrentDictionary>();
- public void AddTypeFace(SKTypeface typeface)
+ public void AddEntry(string familyName, FontKey key, TypefaceCollectionEntry entry)
{
- var key = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
-
- if (!_fontFamilies.TryGetValue(typeface.FamilyName, out var fontFamily))
+ if (!_fontFamilies.TryGetValue(familyName, out var fontFamily))
{
- fontFamily = new ConcurrentDictionary();
+ fontFamily = new ConcurrentDictionary();
- _fontFamilies.TryAdd(typeface.FamilyName, fontFamily);
+ _fontFamilies.TryAdd(familyName, fontFamily);
}
- fontFamily.TryAdd(key, typeface);
+ fontFamily.TryAdd(key, entry);
}
- public SKTypeface GetTypeFace(Typeface typeface)
+ public TypefaceCollectionEntry Get(string familyName, FontWeight fontWeight, FontStyle fontStyle)
{
- var styleSlant = SKFontStyleSlant.Upright;
-
- switch (typeface.Style)
- {
- case FontStyle.Italic:
- styleSlant = SKFontStyleSlant.Italic;
- break;
-
- case FontStyle.Oblique:
- styleSlant = SKFontStyleSlant.Oblique;
- break;
- }
+ var key = new FontKey(fontWeight, fontStyle);
- if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily))
- {
- return TypefaceCache.GetTypeface(TypefaceCache.DefaultFamilyName, typeface.Style, typeface.Weight);
- }
-
- var weight = (SKFontStyleWeight)typeface.Weight;
-
- var key = new FontKey(weight, styleSlant);
-
- return fontFamily.GetOrAdd(key, GetFallback(fontFamily, key));
+ return _fontFamilies.TryGetValue(familyName, out var fontFamily) ?
+ fontFamily.GetOrAdd(key, GetFallback(fontFamily, key)) :
+ null;
}
- private static SKTypeface GetFallback(IDictionary fontFamily, FontKey key)
+ private static TypefaceCollectionEntry GetFallback(IDictionary fontFamily, FontKey key)
{
var keys = fontFamily.Keys.Where(
- x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Slant == key.Slant).ToArray();
+ x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Style == key.Style).ToArray();
if (!keys.Any())
{
keys = fontFamily.Keys.Where(
- x => x.Weight == key.Weight && (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
+ x => x.Weight == key.Weight && (x.Style >= key.Style || x.Style < key.Style)).ToArray();
if (!keys.Any())
{
keys = fontFamily.Keys.Where(
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) &&
- (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
+ (x.Style >= key.Style || x.Style < key.Style)).ToArray();
}
}
key = keys.FirstOrDefault();
- fontFamily.TryGetValue(key, out var typeface);
-
- return typeface;
- }
-
- private struct FontKey
- {
- public readonly SKFontStyleSlant Slant;
- public readonly SKFontStyleWeight Weight;
-
- public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant)
- {
- Slant = slant;
- Weight = weight;
- }
-
- public override int GetHashCode()
- {
- var hash = 17;
- hash = (hash * 31) + (int)Slant;
- hash = (hash * 31) + (int)Weight;
-
- return hash;
- }
+ fontFamily.TryGetValue(key, out var entry);
- public override bool Equals(object other)
- {
- return other is FontKey key && this.Equals(key);
- }
-
- private bool Equals(FontKey other)
- {
- return Slant == other.Slant &&
- Weight == other.Weight;
- }
+ return entry;
}
}
}
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
index ab8ee85a54..4bb42c7118 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
@@ -45,9 +45,13 @@ namespace Avalonia.Skia
{
var assetStream = assetLoader.Open(asset);
- var typeface = SKTypeface.FromStream(assetStream);
+ var skTypeface = SKTypeface.FromStream(assetStream);
- typeFaceCollection.AddTypeFace(typeface);
+ var typeface = new Typeface(fontFamily, (FontWeight)skTypeface.FontWeight, (FontStyle)skTypeface.FontSlant);
+
+ var entry = new TypefaceCollectionEntry(typeface, skTypeface);
+
+ typeFaceCollection.AddEntry(skTypeface.FamilyName, new FontKey(typeface.Weight, typeface.Style), entry);
}
return typeFaceCollection;
diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs
index f16e967f42..ce3aef755b 100644
--- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs
+++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs
@@ -25,6 +25,11 @@ namespace Avalonia.Skia
AvaloniaLocator.CurrentMutable
.Bind().ToConstant(renderInterface);
+
+ var fontManager = new FontManagerImpl();
+
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToConstant(fontManager);
}
///
diff --git a/src/Skia/Avalonia.Skia/TypefaceCache.cs b/src/Skia/Avalonia.Skia/TypefaceCache.cs
index 9e270114d2..1c2b855032 100644
--- a/src/Skia/Avalonia.Skia/TypefaceCache.cs
+++ b/src/Skia/Avalonia.Skia/TypefaceCache.cs
@@ -1,7 +1,7 @@
// 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.Collections.Generic;
+using System.Collections.Concurrent;
using Avalonia.Media;
using SkiaSharp;
@@ -12,88 +12,36 @@ namespace Avalonia.Skia
///
internal static class TypefaceCache
{
- public static readonly string DefaultFamilyName = CreateDefaultFamilyName();
+ private static readonly ConcurrentDictionary> s_cache =
+ new ConcurrentDictionary>();
- private static readonly Dictionary> s_cache =
- new Dictionary>();
-
- struct FontKey
+ public static TypefaceCollectionEntry Get(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
{
- public readonly SKFontStyleSlant Slant;
- public readonly SKFontStyleWeight Weight;
-
- public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant)
+ if (fontFamily.Key != null)
{
- Slant = slant;
- Weight = weight;
+ return SKTypefaceCollectionCache.GetOrAddTypefaceCollection(fontFamily)
+ .Get(fontFamily.Name, fontWeight, fontStyle);
}
- public override int GetHashCode()
- {
- int hash = 17;
- hash = hash * 31 + (int)Slant;
- hash = hash * 31 + (int)Weight;
-
- return hash;
- }
-
- public override bool Equals(object other)
- {
- return other is FontKey ? Equals((FontKey)other) : false;
- }
-
- public bool Equals(FontKey other)
- {
- return Slant == other.Slant &&
- Weight == other.Weight;
- }
-
- // Equals and GetHashCode ommitted
- }
-
- private static string CreateDefaultFamilyName()
- {
- var defaultTypeface = SKTypeface.CreateDefault();
+ var typefaceCollection = s_cache.GetOrAdd(fontFamily.Name, new ConcurrentDictionary());
- return defaultTypeface.FamilyName;
- }
+ var key = new FontKey(fontWeight, fontStyle);
- private static SKTypeface GetTypeface(string name, FontKey key)
- {
- var familyKey = name;
-
- if (!s_cache.TryGetValue(familyKey, out var entry))
+ if (typefaceCollection.TryGetValue(key, out var entry))
{
- s_cache[familyKey] = entry = new Dictionary();
+ return entry;
}
- if (!entry.TryGetValue(key, out var typeface))
- {
- typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) ??
- GetTypeface(DefaultFamilyName, key);
+ var skTypeface = SKTypeface.FromFamilyName(fontFamily.Name, (SKFontStyleWeight)fontWeight,
+ SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle) ?? SKTypeface.Default;
- entry[key] = typeface;
- }
+ var typeface = new Typeface(fontFamily.Name, fontWeight, fontStyle);
- return typeface;
- }
-
- public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
- {
- var skStyle = SKFontStyleSlant.Upright;
+ entry = new TypefaceCollectionEntry(typeface, skTypeface);
- switch (style)
- {
- case FontStyle.Italic:
- skStyle = SKFontStyleSlant.Italic;
- break;
-
- case FontStyle.Oblique:
- skStyle = SKFontStyleSlant.Oblique;
- break;
- }
+ typefaceCollection[key] = entry;
- return GetTypeface(name, new FontKey((SKFontStyleWeight)weight, skStyle));
+ return entry;
}
}
}
diff --git a/src/Skia/Avalonia.Skia/TypefaceCollectionEntry.cs b/src/Skia/Avalonia.Skia/TypefaceCollectionEntry.cs
new file mode 100644
index 0000000000..ef9f889819
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/TypefaceCollectionEntry.cs
@@ -0,0 +1,19 @@
+// 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 Avalonia.Media;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ internal class TypefaceCollectionEntry
+ {
+ public TypefaceCollectionEntry(Typeface typeface, SKTypeface skTypeface)
+ {
+ Typeface = typeface;
+ SKTypeface = skTypeface;
+ }
+ public Typeface Typeface { get; }
+ public SKTypeface SKTypeface { get; }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index 458d8f9cbb..7d47b95ede 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 5ab9a8f74d..1bda5157a5 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -2,6 +2,7 @@
// 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 Avalonia.Controls;
@@ -27,6 +28,8 @@ namespace Avalonia.Direct2D1
{
public class Direct2D1Platform : IPlatformRenderInterface
{
+ private readonly ConcurrentDictionary _glyphTypefaceCache =
+ new ConcurrentDictionary();
private static readonly Direct2D1Platform s_instance = new Direct2D1Platform();
public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; }
@@ -41,20 +44,6 @@ namespace Avalonia.Direct2D1
public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; }
- public IEnumerable InstalledFontNames
- {
- get
- {
- var cache = Direct2D1FontCollectionCache.s_installedFontCollection;
- var length = cache.FontFamilyCount;
- for (int i = 0; i < length; i++)
- {
- var names = cache.GetFontFamily(i).FamilyNames;
- yield return names.GetString(0);
- }
- }
- }
-
private static readonly object s_initLock = new object();
private static bool s_initialized = false;
@@ -120,6 +109,7 @@ namespace Avalonia.Direct2D1
{
InitializeDirect2D();
AvaloniaLocator.CurrentMutable.Bind().ToConstant(s_instance);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(new FontManagerImpl());
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
@@ -131,6 +121,7 @@ namespace Avalonia.Direct2D1
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -139,6 +130,7 @@ namespace Avalonia.Direct2D1
return new FormattedTextImpl(
text,
typeface,
+ fontSize,
textAlignment,
wrapping,
constraint,
@@ -201,5 +193,10 @@ namespace Avalonia.Direct2D1
{
return new WicBitmapImpl(format, data, size, dpi, stride);
}
+
+ public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ {
+ return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface));
+ }
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
index d93a59d384..b455c4fbee 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
@@ -1,62 +1,61 @@
using System.Collections.Concurrent;
using Avalonia.Media;
using Avalonia.Media.Fonts;
+using SharpDX.DirectWrite;
+using FontFamily = Avalonia.Media.FontFamily;
+using FontStyle = SharpDX.DirectWrite.FontStyle;
+using FontWeight = SharpDX.DirectWrite.FontWeight;
namespace Avalonia.Direct2D1.Media
{
internal static class Direct2D1FontCollectionCache
{
- private static readonly ConcurrentDictionary s_cachedCollections;
- internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection;
+ private static readonly ConcurrentDictionary s_cachedCollections;
+ internal static readonly FontCollection InstalledFontCollection;
static Direct2D1FontCollectionCache()
{
- s_cachedCollections = new ConcurrentDictionary();
+ s_cachedCollections = new ConcurrentDictionary();
- s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false);
+ InstalledFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false);
}
- public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface)
+ public static Font GetFont(Typeface typeface)
{
var fontFamily = typeface.FontFamily;
var fontCollection = GetOrAddFontCollection(fontFamily);
- var fontFamilyName = FontFamily.Default.Name;
- // Should this be cached?
foreach (var familyName in fontFamily.FamilyNames)
{
- if (!fontCollection.FindFamilyName(familyName, out _))
+ if (fontCollection.FindFamilyName(familyName, out var index))
{
- continue;
+ return fontCollection.GetFontFamily(index).GetFirstMatchingFont(
+ (FontWeight)typeface.Weight,
+ FontStretch.Normal,
+ (FontStyle)typeface.Style);
}
-
- fontFamilyName = familyName;
-
- break;
}
- return new SharpDX.DirectWrite.TextFormat(
- Direct2D1Platform.DirectWriteFactory,
- fontFamilyName,
- fontCollection,
- (SharpDX.DirectWrite.FontWeight)typeface.Weight,
- (SharpDX.DirectWrite.FontStyle)typeface.Style,
- SharpDX.DirectWrite.FontStretch.Normal,
- (float)typeface.FontSize);
+ InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i);
+
+ return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont(
+ (FontWeight)typeface.Weight,
+ FontStretch.Normal,
+ (FontStyle)typeface.Style);
}
- private static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily)
+ private static FontCollection GetOrAddFontCollection(FontFamily fontFamily)
{
- return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection);
+ return fontFamily.Key == null ? InstalledFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection);
}
- private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key)
+ private static FontCollection CreateFontCollection(FontFamilyKey key)
{
var assets = FontFamilyLoader.LoadFontAssets(key);
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets);
- return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key);
+ return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key);
}
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
new file mode 100644
index 0000000000..94de397652
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
@@ -0,0 +1,71 @@
+// 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.Collections.Generic;
+using System.Globalization;
+using Avalonia.Media;
+using Avalonia.Platform;
+using SharpDX.DirectWrite;
+using FontFamily = Avalonia.Media.FontFamily;
+using FontStyle = Avalonia.Media.FontStyle;
+using FontWeight = Avalonia.Media.FontWeight;
+
+namespace Avalonia.Direct2D1.Media
+{
+ internal class FontManagerImpl : IFontManagerImpl
+ {
+ public FontManagerImpl()
+ {
+ //ToDo: Implement a real lookup of the system's default font.
+ DefaultFontFamilyName = "segoe ui";
+ }
+
+ public string DefaultFontFamilyName { get; }
+
+ public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false)
+ {
+ var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
+
+ var fontFamilies = new string[familyCount];
+
+ for (var i = 0; i < familyCount; i++)
+ {
+ fontFamilies[i] = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i).FamilyNames.GetString(0);
+ }
+
+ return fontFamilies;
+ }
+
+ public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
+ {
+ //ToDo: Implement caching.
+ return new Typeface(fontFamily, fontWeight, fontStyle);
+ }
+
+ public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
+ FontFamily fontFamily = null, CultureInfo culture = null)
+ {
+ var fontFamilyName = FontFamily.Default.Name;
+
+ var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
+
+ for (var i = 0; i < familyCount; i++)
+ {
+ var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i)
+ .GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal,
+ (SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0);
+
+ if (!font.HasCharacter(codepoint))
+ {
+ continue;
+ }
+
+ fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
+
+ break;
+ }
+
+ return GetTypeface(new FontFamily(fontFamilyName), fontWeight, fontStyle);
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
index b73deb1f0a..b1a177ad24 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Direct2D1.Media
public FormattedTextImpl(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -21,20 +22,20 @@ namespace Avalonia.Direct2D1.Media
{
Text = text;
- using (var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface))
+ using (var font = Direct2D1FontCollectionCache.GetFont(typeface))
+ using (var textFormat = new DWrite.TextFormat(Direct2D1Platform.DirectWriteFactory,
+ typeface.FontFamily.Name, font.FontFamily.FontCollection, (DWrite.FontWeight)typeface.Weight,
+ (DWrite.FontStyle)typeface.Style, DWrite.FontStretch.Normal, (float)fontSize))
{
textFormat.WordWrapping =
wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap;
TextLayout = new DWrite.TextLayout(
- Direct2D1Platform.DirectWriteFactory,
- Text ?? string.Empty,
- textFormat,
- (float)constraint.Width,
- (float)constraint.Height)
- {
- TextAlignment = textAlignment.ToDirect2D()
- };
+ Direct2D1Platform.DirectWriteFactory,
+ Text ?? string.Empty,
+ textFormat,
+ (float)constraint.Width,
+ (float)constraint.Height) { TextAlignment = textAlignment.ToDirect2D() };
}
if (spans != null)
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
new file mode 100644
index 0000000000..32def01c39
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
@@ -0,0 +1,188 @@
+// 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 Avalonia.Media;
+using Avalonia.Platform;
+using HarfBuzzSharp;
+using SharpDX.DirectWrite;
+
+namespace Avalonia.Direct2D1.Media
+{
+ public class GlyphTypefaceImpl : IGlyphTypefaceImpl
+ {
+ private bool _isDisposed;
+
+ public GlyphTypefaceImpl(Typeface typeface)
+ {
+ DWFont = Direct2D1FontCollectionCache.GetFont(typeface);
+
+ FontFace = new FontFace(DWFont);
+
+ Face = new Face(GetTable);
+
+ Font = new HarfBuzzSharp.Font(Face);
+
+ Font.SetFunctionsOpenType();
+
+ Font.GetScale(out var xScale, out _);
+
+ DesignEmHeight = (short)xScale;
+
+ if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
+ {
+ Font.TryGetVerticalFontExtents(out fontExtents);
+ }
+
+ Ascent = -fontExtents.Ascender;
+
+ Descent = -fontExtents.Descender;
+
+ LineGap = fontExtents.LineGap;
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
+ {
+ UnderlinePosition = underlinePosition;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
+ {
+ UnderlineThickness = underlineThickness;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
+ {
+ StrikethroughPosition = strikethroughPosition;
+ }
+
+ if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
+ {
+ StrikethroughThickness = strikethroughThickness;
+ }
+ }
+
+ private Blob GetTable(Face face, Tag tag)
+ {
+ var dwTag = (int)SwapBytes(tag);
+
+ if (FontFace.TryGetFontTable(dwTag, out var tableData, out _))
+ {
+ return new Blob(tableData.Pointer, tableData.Size, MemoryMode.ReadOnly, () => { });
+ }
+
+ return null;
+ }
+
+ private static uint SwapBytes(uint x)
+ {
+ x = (x >> 16) | (x << 16);
+
+ return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8);
+ }
+
+ public SharpDX.DirectWrite.Font DWFont { get; }
+
+ public FontFace FontFace { get; }
+
+ public Face Face { get; }
+
+ public HarfBuzzSharp.Font Font { get; }
+
+ ///
+ public short DesignEmHeight { get; }
+
+ ///
+ public int Ascent { get; }
+
+ ///
+ public int Descent { get; }
+
+ ///
+ public int LineGap { get; }
+
+ //ToDo: Read font table for these values
+ ///
+ public int UnderlinePosition { get; }
+
+ ///
+ public int UnderlineThickness { get; }
+
+ ///
+ public int StrikethroughPosition { get; }
+
+ ///
+ public int StrikethroughThickness { get; }
+
+ ///
+ public ushort GetGlyph(uint codepoint)
+ {
+ if (Font.TryGetGlyph(codepoint, out var glyph))
+ {
+ return (ushort)glyph;
+ }
+
+ return 0;
+ }
+
+ ///
+ public ushort[] GetGlyphs(ReadOnlySpan codepoints)
+ {
+ var glyphs = new ushort[codepoints.Length];
+
+ for (var i = 0; i < codepoints.Length; i++)
+ {
+ if (Font.TryGetGlyph(codepoints[i], out var glyph))
+ {
+ glyphs[i] = (ushort)glyph;
+ }
+ }
+
+ return glyphs;
+ }
+
+ ///
+ public int GetGlyphAdvance(ushort glyph)
+ {
+ return Font.GetHorizontalGlyphAdvance(glyph);
+ }
+
+ ///
+ public int[] GetGlyphAdvances(ReadOnlySpan glyphs)
+ {
+ var glyphIndices = new uint[glyphs.Length];
+
+ for (var i = 0; i < glyphs.Length; i++)
+ {
+ glyphIndices[i] = glyphs[i];
+ }
+
+ return Font.GetHorizontalGlyphAdvances(glyphIndices);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+
+ if (!disposing)
+ {
+ return;
+ }
+
+ Font?.Dispose();
+ Face?.Dispose();
+ FontFace?.Dispose();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
index 6cf38b6121..a683e5cfca 100644
--- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
@@ -175,6 +175,7 @@ namespace Avalonia.Layout.UnitTests
x.CreateFormattedText(
It.IsAny(),
It.IsAny(),
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
It.IsAny(),
diff --git a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
index 353123ab2a..bca34dd69d 100644
--- a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
+++ b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
@@ -53,7 +53,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
var r = AvaloniaLocator.Current.GetService();
return r.CreateFormattedText(text,
- new Typeface(fontFamily, fontSize, fontStyle, fontWeight),
+ new Typeface(fontFamily, fontWeight, fontStyle),
+ fontSize,
textAlignment,
wrapping,
widthConstraint == -1 ? Size.Infinity : new Size(widthConstraint, double.PositiveInfinity),
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index a3cc3dec17..187853283f 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -9,11 +9,10 @@ namespace Avalonia.UnitTests
{
public class MockPlatformRenderInterface : IPlatformRenderInterface
{
- public IEnumerable InstalledFontNames => new string[0];
-
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -79,5 +78,10 @@ namespace Avalonia.UnitTests
{
throw new NotImplementedException();
}
+
+ public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ {
+ return Mock.Of();
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs
index f7a878feba..d189aa3165 100644
--- a/tests/Avalonia.UnitTests/TestServices.cs
+++ b/tests/Avalonia.UnitTests/TestServices.cs
@@ -169,6 +169,7 @@ namespace Avalonia.UnitTests
x.CreateFormattedText(
It.IsAny(),
It.IsAny(),
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
It.IsAny(),
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs
index 75ae43a1fa..5d47333d51 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs
@@ -19,12 +19,48 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(new FontFamily("Arial"), fontFamily);
}
- [Fact]
- public void Should_Be_Equal()
+ [InlineData("Font A")]
+ [InlineData("Font A, Font B")]
+ [InlineData("resm: Avalonia.Visuals.UnitTests#MyFont")]
+ [InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts#MyFont")]
+ [Theory]
+ public void Should_Have_Equal_Hash(string s)
{
- var fontFamily = new FontFamily("Arial");
+ var fontFamily = new FontFamily(s);
- Assert.Equal(new FontFamily("Arial"), fontFamily);
+ Assert.Equal(new FontFamily(s).GetHashCode(), fontFamily.GetHashCode());
+ }
+
+ [InlineData("Font A, Font B", "Font B, Font A")]
+ [InlineData("Font A, Font B", "Font A, Font C")]
+ [Theory]
+ public void Should_Not_Have_Equal_Hash(string a, string b)
+ {
+ var fontFamily = new FontFamily(b);
+
+ Assert.NotEqual(new FontFamily(a).GetHashCode(), fontFamily.GetHashCode());
+ }
+
+ [InlineData("Font A")]
+ [InlineData("Font A, Font B")]
+ [InlineData("resm: Avalonia.Visuals.UnitTests#MyFont")]
+ [InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts#MyFont")]
+ [Theory]
+ public void Should_Be_Equal(string s)
+ {
+ var fontFamily = new FontFamily(s);
+
+ Assert.Equal(new FontFamily(s), fontFamily);
+ }
+
+ [InlineData("Font A, Font B", "Font B, Font A")]
+ [InlineData("Font A, Font B", "Font A, Font C")]
+ [Theory]
+ public void Should_Not_Be_Equal(string a, string b)
+ {
+ var fontFamily = new FontFamily(b);
+
+ Assert.NotEqual(new FontFamily(a), fontFamily);
}
[Fact]
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
index 2c8f8eb9b2..0e43c76da1 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
@@ -7,15 +7,21 @@ namespace Avalonia.Visuals.UnitTests.Media
public class TypefaceTests
{
[Fact]
- public void Exception_Should_Be_Thrown_If_FontSize_LessThanEqualTo_0()
+ public void Exception_Should_Be_Thrown_If_FontWeight_LessThanEqualTo_Zero()
{
- Assert.Throws(() => new Typeface("foo", 0));
+ Assert.Throws(() => new Typeface("foo", 0, (FontStyle)12));
}
[Fact]
- public void Exception_Should_Be_Thrown_If_FontWeight_LessThanEqualTo_0()
+ public void Should_Be_Equal()
{
- Assert.Throws(() => new Typeface("foo", 12, weight: 0));
+ Assert.Equal(new Typeface("Font A"), new Typeface("Font A"));
+ }
+
+ [Fact]
+ public void Should_Have_Equal_Hash()
+ {
+ Assert.Equal(new Typeface("Font A").GetHashCode(), new Typeface("Font A").GetHashCode());
}
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index d31210bc71..032b6582a9 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -13,6 +13,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
+ double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@@ -51,6 +52,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ {
+ throw new NotImplementedException();
+ }
+
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? fmt)
{
throw new NotImplementedException();