diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 05ae4e11f3..98f9568316 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -152,6 +152,15 @@ namespace Avalonia.Controls.Presenters set => TextBlock.SetFontWeight(this, value); } + /// + /// Gets or sets the font stretch. + /// + public FontStretch FontStretch + { + get => TextBlock.GetFontStretch(this); + set => TextBlock.SetFontStretch(this, value); + } + /// /// Gets or sets a brush used to paint the text. /// diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index c11f8434f8..9e50a760fb 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -67,6 +67,15 @@ namespace Avalonia.Controls inherits: true, defaultValue: FontWeight.Normal); + /// + /// Defines the property. + /// + public static readonly AttachedProperty FontStretchProperty = + AvaloniaProperty.RegisterAttached( + nameof(FontStretch), + inherits: true, + defaultValue: FontStretch.Normal); + /// /// Defines the property. /// @@ -248,6 +257,15 @@ namespace Avalonia.Controls get { return GetValue(FontWeightProperty); } set { SetValue(FontWeightProperty, value); } } + + /// + /// Gets or sets the font stretch. + /// + public FontStretch FontStretch + { + get { return GetValue(FontStretchProperty); } + set { SetValue(FontStretchProperty, value); } + } /// /// Gets or sets a brush used to paint the text. @@ -326,7 +344,7 @@ namespace Avalonia.Controls /// Gets the value of the attached on a control. /// /// The control. - /// The font family. + /// The font size. public static double GetFontSize(Control control) { return control.GetValue(FontSizeProperty); @@ -336,7 +354,7 @@ namespace Avalonia.Controls /// Gets the value of the attached on a control. /// /// The control. - /// The font family. + /// The font style. public static FontStyle GetFontStyle(Control control) { return control.GetValue(FontStyleProperty); @@ -346,11 +364,21 @@ namespace Avalonia.Controls /// Gets the value of the attached on a control. /// /// The control. - /// The font family. + /// The font weight. public static FontWeight GetFontWeight(Control control) { return control.GetValue(FontWeightProperty); } + + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font stretch. + public static FontStretch GetFontStretch(Control control) + { + return control.GetValue(FontStretchProperty); + } /// /// Gets the value of the attached on a control. @@ -367,7 +395,6 @@ namespace Avalonia.Controls /// /// The control. /// The property value to set. - /// The font family. public static void SetFontFamily(Control control, FontFamily value) { control.SetValue(FontFamilyProperty, value); @@ -378,7 +405,6 @@ namespace Avalonia.Controls /// /// The control. /// The property value to set. - /// The font family. public static void SetFontSize(Control control, double value) { control.SetValue(FontSizeProperty, value); @@ -389,7 +415,6 @@ namespace Avalonia.Controls /// /// The control. /// The property value to set. - /// The font family. public static void SetFontStyle(Control control, FontStyle value) { control.SetValue(FontStyleProperty, value); @@ -400,18 +425,26 @@ namespace Avalonia.Controls /// /// The control. /// The property value to set. - /// The font family. public static void SetFontWeight(Control control, FontWeight value) { control.SetValue(FontWeightProperty, value); } + + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + public static void SetFontStretch(Control control, FontStretch value) + { + control.SetValue(FontStretchProperty, value); + } /// /// Sets the value of the attached on a control. /// /// The control. /// The property value to set. - /// The font family. public static void SetForeground(Control control, IBrush? value) { control.SetValue(ForegroundProperty, value); @@ -478,7 +511,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, diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index b619b9d129..9ac74e5e78 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -157,9 +157,10 @@ namespace Avalonia.Headless return new List { "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; } } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 2a2f4d13f9..b74b678632 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/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, 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)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. -Total Issues: 147 +Total Issues: 153 diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs index 72c1c8dcac..37091b82e3 100644 --- a/src/Avalonia.Visuals/Media/FontManager.cs +++ b/src/Avalonia.Visuals/Media/FontManager.cs @@ -118,26 +118,22 @@ namespace Avalonia.Media /// The codepoint to match against. /// The font style. /// The font weight. + /// The font stretch. /// The font family. This is optional and used for fallback lookup. /// The culture. /// The matching . /// /// True, if the could match the character to specified parameters, False otherwise. /// - 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); } } } diff --git a/src/Avalonia.Visuals/Media/FontStretch.cs b/src/Avalonia.Visuals/Media/FontStretch.cs new file mode 100644 index 0000000000..b110475215 --- /dev/null +++ b/src/Avalonia.Visuals/Media/FontStretch.cs @@ -0,0 +1,19 @@ +namespace Avalonia.Media +{ + /// + /// FontStretch describes relative change from the normal aspect ratio + /// as specified by a font designer for the glyphs in a font. + /// + public enum FontStretch + { + Normal = 5, + UltraCondensed = 1, + ExtraCondensed = 2, + Condensed = 3, + SemiCondensed = 4, + SemiExpanded = 6, + Expanded = 7, + ExtraExpanded = 8, + UltraExpanded = 9 + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs index 09d9d9fbcd..750ec64798 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs @@ -115,7 +115,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 _)) { diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 45540a5812..f0daa841d9 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -16,18 +16,26 @@ namespace Avalonia.Media /// The font family. /// The font style. /// The font weight. + /// The font stretch. 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; } /// @@ -36,10 +44,12 @@ namespace Avalonia.Media /// The name of the font family. /// The font style. /// The font weight. + /// The font stretch. 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. /// public FontWeight Weight { get; } + + /// + /// Gets the font stretch. + /// + public FontStretch Stretch { get; } /// /// 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; } } diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs index 113c72d373..0110287afd 100644 --- a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs @@ -23,6 +23,7 @@ namespace Avalonia.Platform /// The codepoint to match against. /// The font style. /// The font weight. + /// The font stretch. /// The font family. This is optional and used for fallback lookup. /// The culture. /// The matching typeface. @@ -30,7 +31,7 @@ namespace Avalonia.Platform /// True, if the could match the character to specified parameters, False otherwise. /// bool TryMatchCharacter(int codepoint, FontStyle fontStyle, - FontWeight fontWeight, + FontWeight fontWeight, FontStretch fontStretch, FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface); /// diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 6b560ac739..024bd67532 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/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 diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 2b9d0b103e..f66df9e6e9 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/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 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; } } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index 2816146806..5ca7f40d17 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/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); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 78bf25d607..792bf2d0be 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/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); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs index 6d95d759ec..c996337520 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs +++ b/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; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index cc4b727bd9..04b408a666 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/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); diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 002da66070..9f958bdcdd 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/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; diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs index ba3b346f1b..e2678394df 100644 --- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs +++ b/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); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs index a48639a426..2be0209c85 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs @@ -64,7 +64,8 @@ namespace Avalonia.Visuals.UnitTests.Media { AvaloniaLocator.CurrentMutable.Bind().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); }