Browse Source

Introduce Typeface.FontStretch and rework Skia typeface matching

pull/6800/head
Benedikt Stebner 5 years ago
parent
commit
0a3e5f5b91
  1. 9
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  2. 51
      src/Avalonia.Controls/TextBlock.cs
  3. 5
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  4. 8
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  5. 14
      src/Avalonia.Visuals/Media/FontManager.cs
  6. 19
      src/Avalonia.Visuals/Media/FontStretch.cs
  7. 3
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  8. 25
      src/Avalonia.Visuals/Media/Typeface.cs
  9. 3
      src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
  10. 25
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  11. 160
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  12. 4
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  13. 5
      src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
  14. 8
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  15. 7
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  16. 9
      tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
  17. 3
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  18. 3
      tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

9
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -152,6 +152,15 @@ namespace Avalonia.Controls.Presenters
set => TextBlock.SetFontWeight(this, value);
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get => TextBlock.GetFontStretch(this);
set => TextBlock.SetFontStretch(this, value);
}
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>

51
src/Avalonia.Controls/TextBlock.cs

@ -64,6 +64,15 @@ namespace Avalonia.Controls
inherits: true,
defaultValue: FontWeight.Normal);
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontStretch>(
nameof(FontStretch),
inherits: true,
defaultValue: FontStretch.Normal);
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
@ -219,6 +228,15 @@ namespace Avalonia.Controls
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// Gets or sets a brush used to paint the text.
@ -297,7 +315,7 @@ namespace Avalonia.Controls
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
/// <returns>The font size.</returns>
public static double GetFontSize(Control control)
{
return control.GetValue(FontSizeProperty);
@ -307,7 +325,7 @@ namespace Avalonia.Controls
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
/// <returns>The font style.</returns>
public static FontStyle GetFontStyle(Control control)
{
return control.GetValue(FontStyleProperty);
@ -317,11 +335,21 @@ namespace Avalonia.Controls
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
/// <returns>The font weight.</returns>
public static FontWeight GetFontWeight(Control control)
{
return control.GetValue(FontWeightProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font stretch.</returns>
public static FontStretch GetFontStretch(Control control)
{
return control.GetValue(FontStretchProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="ForegroundProperty"/> on a control.
@ -338,7 +366,6 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontFamily(Control control, FontFamily value)
{
control.SetValue(FontFamilyProperty, value);
@ -349,7 +376,6 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontSize(Control control, double value)
{
control.SetValue(FontSizeProperty, value);
@ -360,7 +386,6 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontStyle(Control control, FontStyle value)
{
control.SetValue(FontStyleProperty, value);
@ -371,18 +396,26 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontWeight(Control control, FontWeight value)
{
control.SetValue(FontWeightProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStretch(Control control, FontStretch value)
{
control.SetValue(FontStretchProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetForeground(Control control, IBrush? value)
{
control.SetValue(ForegroundProperty, value);
@ -432,7 +465,7 @@ namespace Avalonia.Controls
{
return new TextLayout(
text ?? string.Empty,
new Typeface(FontFamily, FontStyle, FontWeight),
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize,
Foreground ?? Brushes.Transparent,
TextAlignment,

5
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -157,9 +157,10 @@ namespace Avalonia.Headless
return new List<string> { "Arial" };
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
typeface = new Typeface("Arial", fontStyle, fontWeight);
typeface = new Typeface("Arial", fontStyle, fontWeight, fontStretch);
return true;
}
}

8
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -6,6 +6,7 @@ MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Media.FormattedText)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Boolean Avalonia.Media.FontManager.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.FormattedText..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.FormattedText..ctor(Avalonia.Platform.IPlatformRenderInterface)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.FormattedText..ctor(System.String, Avalonia.Media.Typeface, System.Double, Avalonia.Media.TextAlignment, Avalonia.Media.TextWrapping, Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
@ -49,6 +50,8 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult..ctor()'
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsInside.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTrailing.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
@ -121,6 +124,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Platform.IFormattedTextImpl)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontStretch, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
TypesMustExist : Type 'Avalonia.Platform.IFormattedTextImpl' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract.
@ -147,4 +153,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable<System.Int32>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 147
Total Issues: 153

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

@ -118,26 +118,22 @@ namespace Avalonia.Media
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStretch">The font stretch.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <param name="typeface">The matching <see cref="Typeface"/>.</param>
/// <returns>
/// <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch,
FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface)
{
if(_fontFallbacks != null)
{
foreach (var fallback in _fontFallbacks)
{
if(fallback is null)
{
continue;
}
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight);
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
var glyphTypeface = typeface.GlyphTypeface;
@ -147,7 +143,7 @@ namespace Avalonia.Media
}
}
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, fontFamily, culture, out typeface);
}
}
}

19
src/Avalonia.Visuals/Media/FontStretch.cs

@ -0,0 +1,19 @@
namespace Avalonia.Media
{
/// <summary>
/// FontStretch describes relative change from the normal aspect ratio
/// as specified by a font designer for the glyphs in a font.
/// </summary>
public enum FontStretch
{
Normal = 5,
UltraCondensed = 1,
ExtraCondensed = 2,
Condensed = 3,
SemiCondensed = 4,
SemiExpanded = 6,
Expanded = 7,
ExtraExpanded = 8,
UltraExpanded = 9
}
}

3
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -118,7 +118,8 @@ namespace Avalonia.Media.TextFormatting
//ToDo: Fix FontFamily fallback
var matchFound =
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface);
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
out currentTypeface);
if (matchFound && TryGetShapeableLength(text, currentTypeface, out count, out _))
{

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

@ -16,18 +16,26 @@ namespace Avalonia.Media
/// <param name="fontFamily">The font family.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
/// <param name="stretch">The font stretch.</param>
public Typeface([NotNull] FontFamily fontFamily,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
FontWeight weight = FontWeight.Normal,
FontStretch stretch = FontStretch.Normal)
{
if (weight <= 0)
{
throw new ArgumentException("Font weight must be > 0.");
}
if ((int)stretch < 1)
{
throw new ArgumentException("Font stretch must be > 1.");
}
FontFamily = fontFamily;
Style = style;
Weight = weight;
Stretch = stretch;
}
/// <summary>
@ -36,10 +44,12 @@ namespace Avalonia.Media
/// <param name="fontFamilyName">The name of the font family.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
/// <param name="stretch">The font stretch.</param>
public Typeface(string fontFamilyName,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
: this(new FontFamily(fontFamilyName), style, weight)
FontWeight weight = FontWeight.Normal,
FontStretch stretch = FontStretch.Normal)
: this(new FontFamily(fontFamilyName), style, weight, stretch)
{
}
@ -59,6 +69,11 @@ namespace Avalonia.Media
/// Gets the font weight.
/// </summary>
public FontWeight Weight { get; }
/// <summary>
/// Gets the font stretch.
/// </summary>
public FontStretch Stretch { get; }
/// <summary>
/// Gets the glyph typeface.
@ -85,7 +100,8 @@ namespace Avalonia.Media
public bool Equals(Typeface other)
{
return FontFamily == other.FontFamily && Style == other.Style && Weight == other.Weight;
return FontFamily == other.FontFamily && Style == other.Style &&
Weight == other.Weight && Stretch == other.Stretch;
}
public override int GetHashCode()
@ -95,6 +111,7 @@ namespace Avalonia.Media
var hashCode = (FontFamily != null ? FontFamily.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int)Style;
hashCode = (hashCode * 397) ^ (int)Weight;
hashCode = (hashCode * 397) ^ (int)Stretch;
return hashCode;
}
}

3
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@ -23,6 +23,7 @@ namespace Avalonia.Platform
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStretch">The font stretch.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <param name="typeface">The matching typeface.</param>
@ -30,7 +31,7 @@ namespace Avalonia.Platform
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontWeight fontWeight, FontStretch fontStretch,
FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface);
/// <summary>

25
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -29,27 +29,27 @@ namespace Avalonia.Skia
[ThreadStatic] private static string[] t_languageTagBuffer;
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontWeight fontWeight, FontStretch fontStretch,
FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
{
SKFontStyle skFontStyle;
switch (fontWeight)
{
case FontWeight.Normal when fontStyle == FontStyle.Normal:
case FontWeight.Normal when fontStyle == FontStyle.Normal && fontStretch == FontStretch.Normal:
skFontStyle = SKFontStyle.Normal;
break;
case FontWeight.Normal when fontStyle == FontStyle.Italic:
case FontWeight.Normal when fontStyle == FontStyle.Italic && fontStretch == FontStretch.Normal:
skFontStyle = SKFontStyle.Italic;
break;
case FontWeight.Bold when fontStyle == FontStyle.Normal:
case FontWeight.Bold when fontStyle == FontStyle.Normal && fontStretch == FontStretch.Normal:
skFontStyle = SKFontStyle.Bold;
break;
case FontWeight.Bold when fontStyle == FontStyle.Italic:
case FontWeight.Bold when fontStyle == FontStyle.Italic && fontStretch == FontStretch.Normal:
skFontStyle = SKFontStyle.BoldItalic;
break;
default:
skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle);
skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, (SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle);
break;
}
@ -80,7 +80,7 @@ namespace Avalonia.Skia
continue;
}
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight, fontStretch);
return true;
}
@ -91,7 +91,7 @@ namespace Avalonia.Skia
if (skTypeface != null)
{
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight, fontStretch);
return true;
}
@ -109,10 +109,17 @@ namespace Avalonia.Skia
if (typeface.FontFamily.Key == null)
{
var defaultName = SKTypeface.Default.FamilyName;
var fontStyle = new SKFontStyle((SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
var fontStyle = new SKFontStyle((SKFontStyleWeight)typeface.Weight, (SKFontStyleWidth)typeface.Stretch,
(SKFontStyleSlant)typeface.Style);
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
if(familyName == FontFamily.DefaultFontFamilyName)
{
continue;
}
skTypeface = _skFontManager.MatchFamily(familyName, fontStyle);
if (skTypeface is null

160
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -1,6 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using SkiaSharp;
@ -18,48 +16,168 @@ namespace Avalonia.Skia
public SKTypeface Get(Typeface typeface)
{
return GetNearestMatch(_typefaces, typeface);
return GetNearestMatch(typeface);
}
private static SKTypeface GetNearestMatch(IDictionary<Typeface, SKTypeface> typefaces, Typeface key)
private SKTypeface GetNearestMatch(Typeface key)
{
if (typefaces.TryGetValue(key, out var typeface))
if (_typefaces.Count == 0)
{
return typeface;
return null;
}
var initialWeight = (int)key.Weight;
if (_typefaces.TryGetValue(key, out var typeface))
{
return typeface;
}
if(key.Style != FontStyle.Normal)
{
key = new Typeface(key.FontFamily, FontStyle.Normal, key.Weight, key.Stretch);
}
if(key.Stretch != FontStretch.Normal)
{
if(TryFindStretchFallback(key, out typeface))
{
return typeface;
}
if(key.Weight != FontWeight.Normal)
{
if (TryFindStretchFallback(new Typeface(key.FontFamily, key.Style, FontWeight.Normal, key.Stretch), out typeface))
{
return typeface;
}
}
key = new Typeface(key.FontFamily, key.Style, key.Weight, FontStretch.Normal);
}
if(TryFindWeightFallback(key, out typeface))
{
return typeface;
}
//Nothing was found so we try some regular cases.
if (_typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface))
{
return typeface;
}
return _typefaces.TryGetValue(new Typeface(key.FontFamily, FontStyle.Italic), out typeface) ?
typeface :
null;
}
private bool TryFindStretchFallback(Typeface key, out SKTypeface typeface)
{
typeface = null;
var stretch = (int)key.Stretch;
if (stretch < 5)
{
for (var i = 0; stretch + i < 9; i++)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, key.Weight, (FontStretch)(stretch + i)), out typeface))
{
return true;
}
}
}
else
{
for (var i = 0; stretch - i > 1; i++)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, key.Weight, (FontStretch)(stretch - i)), out typeface))
{
return true;
}
}
}
return false;
}
private bool TryFindWeightFallback(Typeface key, out SKTypeface typeface)
{
typeface = null;
var weight = (int)key.Weight;
weight -= weight % 50; // make sure we start at a full weight
//If the target weight given is between 400 and 500 inclusive
if (weight >= 400 && weight <= 500)
{
//Look for available weights between the target and 500, in ascending order.
for (var i = 0; weight + i <= 500; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface))
{
return true;
}
}
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight - i >= 100; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface))
{
return true;
}
}
for (var i = 0; i < 2; i++)
//If no match is found, look for available weights greater than 500, in ascending order.
for (var i = 0; weight + i <= 900; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface))
{
return true;
}
}
}
//If a weight less than 400 is given, look for available weights less than the target, in descending order.
if (weight < 400)
{
for (var j = 0; j < initialWeight; j += 50)
for (var i = 0; weight - i >= 100; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface))
{
return true;
}
}
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight + i <= 900; i += 50)
{
if (weight - j >= 100)
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface))
{
if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
{
return typeface;
}
return true;
}
}
}
if (weight + j > 900)
//If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
if (weight > 500)
{
for (var i = 0; weight + i <= 900; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight + i), key.Stretch), out typeface))
{
continue;
return true;
}
}
if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
//If no match is found, look for available weights less than the target, in descending order.
for (var i = 0; weight - i >= 100; i += 50)
{
if (_typefaces.TryGetValue(new Typeface(key.FontFamily, key.Style, (FontWeight)(weight - i), key.Stretch), out typeface))
{
return typeface;
return true;
}
}
}
//Nothing was found so we try to get a regular typeface.
return typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface) ? typeface : null;
return false;
}
}
}

4
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -51,13 +51,13 @@ namespace Avalonia.Skia
if (typeface == null)
throw new InvalidOperationException("Typeface could not be loaded.");
if (typeface.FamilyName != fontFamily.Name)
if (!typeface.FamilyName.Contains(fontFamily.Name))
{
continue;
}
var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(),
(FontWeight)typeface.FontWeight);
(FontWeight)typeface.FontWeight, (FontStretch)typeface.FontWidth);
typeFaceCollection.AddTypeface(key, typeface);
}

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

@ -5,6 +5,7 @@ using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
using FontStyle = SharpDX.DirectWrite.FontStyle;
using FontWeight = SharpDX.DirectWrite.FontWeight;
using FontStretch = SharpDX.DirectWrite.FontStretch;
namespace Avalonia.Direct2D1.Media
{
@ -32,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
{
return fontCollection.GetFontFamily(index).GetFirstMatchingFont(
(FontWeight)typeface.Weight,
FontStretch.Normal,
(FontStretch)typeface.Stretch,
(FontStyle)typeface.Style);
}
}
@ -41,7 +42,7 @@ namespace Avalonia.Direct2D1.Media
return InstalledFontCollection.GetFontFamily(index).GetFirstMatchingFont(
(FontWeight)typeface.Weight,
FontStretch.Normal,
(FontStretch)typeface.Stretch,
(FontStyle)typeface.Style);
}

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

@ -4,6 +4,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
using FontStretch = Avalonia.Media.FontStretch;
using FontStyle = Avalonia.Media.FontStyle;
using FontWeight = Avalonia.Media.FontWeight;
@ -32,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontWeight fontWeight, FontStretch fontStretch,
FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@ -40,7 +41,8 @@ namespace Avalonia.Direct2D1.Media
for (var i = 0; i < familyCount; i++)
{
var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i)
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal,
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight,
(SharpDX.DirectWrite.FontStretch)fontStretch,
(SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0);
if (!font.HasCharacter(codepoint))
@ -50,7 +52,7 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
typeface = new Typeface(fontFamilyName, fontStyle, fontWeight);
typeface = new Typeface(fontFamilyName, fontStyle, fontWeight, fontStretch);
return true;
}

7
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -16,7 +16,7 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly Typeface _defaultTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
private readonly Typeface _italicTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans");
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic);
private readonly Typeface _emojiTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Twitter Color Emoji");
@ -38,7 +38,8 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
FontFamily fontFamily,
CultureInfo culture, out Typeface typeface)
{
foreach (var customTypeface in _customTypefaces)
@ -54,7 +55,7 @@ namespace Avalonia.Skia.UnitTests.Media
}
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
(SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);

9
tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs

@ -36,8 +36,8 @@ namespace Avalonia.UnitTests
return _customTypefaces.Select(x => x.FontFamily!.Name);
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out Typeface fontKey)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
{
foreach (var customTypeface in _customTypefaces)
{
@ -62,11 +62,6 @@ namespace Avalonia.UnitTests
{
var fontFamily = typeface.FontFamily;
if (fontFamily == null)
{
return null;
}
if (fontFamily.IsDefault)
{
fontFamily = _defaultTypeface.FontFamily;

3
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -24,7 +24,8 @@ namespace Avalonia.UnitTests
return new[] { _defaultFamilyName };
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch, FontFamily fontFamily,
CultureInfo culture, out Typeface fontKey)
{
fontKey = new Typeface(_defaultFamilyName);

3
tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

@ -64,7 +64,8 @@ namespace Avalonia.Visuals.UnitTests.Media
{
AvaloniaLocator.CurrentMutable.Bind<FontManagerOptions>().ToConstant(options);
FontManager.Current.TryMatchCharacter(1, FontStyle.Normal, FontWeight.Normal, FontFamily.Default, null, out var typeface);
FontManager.Current.TryMatchCharacter(1, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal,
FontFamily.Default, null, out var typeface);
Assert.Equal("MyFont", typeface.FontFamily.Name);
}

Loading…
Cancel
Save