Browse Source

Deal with non standard oblique typefaces on Windows (#19876)

release/11.3.8
Benedikt Stebner 4 months ago
committed by Julien Lebosquain
parent
commit
0496286040
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 14
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  2. 26
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  3. 58
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  4. 47
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

14
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -50,7 +50,7 @@ namespace Avalonia.Skia
skFontStyle = SKFontStyle.BoldItalic; skFontStyle = SKFontStyle.BoldItalic;
break; break;
default: default:
skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, (SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle); skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, (SKFontStyleWidth)fontStretch, fontStyle.ToSkia());
break; break;
} }
@ -63,7 +63,12 @@ namespace Avalonia.Skia
if (skTypeface != null) if (skTypeface != null)
{ {
fontKey = new Typeface(skTypeface.FamilyName, (FontStyle)skTypeface.FontStyle.Slant, (FontWeight)skTypeface.FontStyle.Weight, (FontStretch)skTypeface.FontStyle.Width); // ToDo: create glyph typeface here to get the correct style/weight/stretch
fontKey = new Typeface(
skTypeface.FamilyName,
skTypeface.FontStyle.Slant.ToAvalonia(),
(FontWeight)skTypeface.FontStyle.Weight,
(FontStretch)skTypeface.FontStyle.Width);
return true; return true;
} }
@ -78,8 +83,7 @@ namespace Avalonia.Skia
{ {
glyphTypeface = null; glyphTypeface = null;
var fontStyle = new SKFontStyle((SKFontStyleWeight)weight, (SKFontStyleWidth)stretch, var fontStyle = new SKFontStyle((SKFontStyleWeight)weight, (SKFontStyleWidth)stretch, style.ToSkia());
(SKFontStyleSlant)style);
var skTypeface = _skFontManager.MatchFamily(familyName, fontStyle); var skTypeface = _skFontManager.MatchFamily(familyName, fontStyle);
@ -127,7 +131,7 @@ namespace Avalonia.Skia
var set = _skFontManager.GetFontStyles(familyName); var set = _skFontManager.GetFontStyles(familyName);
if(set.Count == 0) if (set.Count == 0)
{ {
return false; return false;
} }

26
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -16,7 +16,6 @@ namespace Avalonia.Skia
internal class GlyphTypefaceImpl : IGlyphTypeface2 internal class GlyphTypefaceImpl : IGlyphTypeface2
{ {
private bool _isDisposed; private bool _isDisposed;
private readonly SKTypeface _typeface;
private readonly NameTable? _nameTable; private readonly NameTable? _nameTable;
private readonly OS2Table? _os2Table; private readonly OS2Table? _os2Table;
private readonly HorizontalHeadTable? _hhTable; private readonly HorizontalHeadTable? _hhTable;
@ -24,7 +23,7 @@ namespace Avalonia.Skia
public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations) public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations)
{ {
_typeface = typeface ?? throw new ArgumentNullException(nameof(typeface)); SKTypeface = typeface ?? throw new ArgumentNullException(nameof(typeface));
Face = new Face(GetTable) { UnitsPerEm = typeface.UnitsPerEm }; Face = new Face(GetTable) { UnitsPerEm = typeface.UnitsPerEm };
@ -96,6 +95,11 @@ namespace Avalonia.Skia
var style = _os2Table != null ? GetFontStyle(_os2Table.FontStyle) : FontStyle.Normal; var style = _os2Table != null ? GetFontStyle(_os2Table.FontStyle) : FontStyle.Normal;
if (typeface.FontStyle.Slant == SKFontStyleSlant.Oblique)
{
style = FontStyle.Oblique;
}
Style = (fontSimulations & FontSimulations.Oblique) != 0 ? FontStyle.Italic : style; Style = (fontSimulations & FontSimulations.Oblique) != 0 ? FontStyle.Italic : style;
var stretch = _os2Table != null ? (FontStretch)_os2Table.WidthClass : FontStretch.Normal; var stretch = _os2Table != null ? (FontStretch)_os2Table.WidthClass : FontStretch.Normal;
@ -205,6 +209,8 @@ namespace Avalonia.Skia
} }
} }
public SKTypeface SKTypeface { get; }
public Face Face { get; } public Face Face { get; }
public Font Font { get; } public Font Font { get; }
@ -300,12 +306,12 @@ namespace Avalonia.Skia
private static FontStyle GetFontStyle(OS2Table.FontStyleSelection styleSelection) private static FontStyle GetFontStyle(OS2Table.FontStyleSelection styleSelection)
{ {
if((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0) if ((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0)
{ {
return FontStyle.Italic; return FontStyle.Italic;
} }
if((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0) if ((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0)
{ {
return FontStyle.Oblique; return FontStyle.Oblique;
} }
@ -315,18 +321,18 @@ namespace Avalonia.Skia
private Blob? GetTable(Face face, Tag tag) private Blob? GetTable(Face face, Tag tag)
{ {
var size = _typeface.GetTableSize(tag); var size = SKTypeface.GetTableSize(tag);
var data = Marshal.AllocCoTaskMem(size); var data = Marshal.AllocCoTaskMem(size);
var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data)); var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data));
return _typeface.TryGetTableData(tag, 0, size, data) ? return SKTypeface.TryGetTableData(tag, 0, size, data) ?
new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null; new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null;
} }
public SKFont CreateSKFont(float size) public SKFont CreateSKFont(float size)
=> new(_typeface, size, skewX: (FontSimulations & FontSimulations.Oblique) != 0 ? -0.3f : 0.0f) => new(SKTypeface, size, skewX: (FontSimulations & FontSimulations.Oblique) != 0 ? -0.3f : 0.0f)
{ {
LinearMetrics = true, LinearMetrics = true,
Embolden = (FontSimulations & FontSimulations.Bold) != 0 Embolden = (FontSimulations & FontSimulations.Bold) != 0
@ -348,7 +354,7 @@ namespace Avalonia.Skia
Font.Dispose(); Font.Dispose();
Face.Dispose(); Face.Dispose();
_typeface.Dispose(); SKTypeface.Dispose();
} }
public void Dispose() public void Dispose()
@ -359,14 +365,14 @@ namespace Avalonia.Skia
public bool TryGetTable(uint tag, out byte[] table) public bool TryGetTable(uint tag, out byte[] table)
{ {
return _typeface.TryGetTableData(tag, out table); return SKTypeface.TryGetTableData(tag, out table);
} }
public bool TryGetStream([NotNullWhen(true)] out Stream? stream) public bool TryGetStream([NotNullWhen(true)] out Stream? stream)
{ {
try try
{ {
var asset = _typeface.OpenStream(); var asset = SKTypeface.OpenStream();
var size = asset.Length; var size = asset.Length;
var buffer = new byte[size]; var buffer = new byte[size];

58
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -67,7 +67,7 @@ namespace Avalonia.Skia
{ {
return new SKPoint((float)p.X, (float)p.Y); return new SKPoint((float)p.X, (float)p.Y);
} }
public static SKPoint ToSKPoint(this Vector p) public static SKPoint ToSKPoint(this Vector p)
{ {
return new SKPoint((float)p.X, (float)p.Y); return new SKPoint((float)p.X, (float)p.Y);
@ -77,17 +77,17 @@ namespace Avalonia.Skia
{ {
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom); return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
} }
internal static SKRect ToSKRect(this LtrbRect r) internal static SKRect ToSKRect(this LtrbRect r)
{ {
return new SKRect((float)r.Left, (float)r.Right, (float)r.Right, (float)r.Bottom); return new SKRect((float)r.Left, (float)r.Right, (float)r.Right, (float)r.Bottom);
} }
public static SKRectI ToSKRectI(this PixelRect r) public static SKRectI ToSKRectI(this PixelRect r)
{ {
return new SKRectI(r.X, r.Y, r.Right, r.Bottom); return new SKRectI(r.X, r.Y, r.Right, r.Bottom);
} }
internal static SKRectI ToSKRectI(this LtrbPixelRect r) internal static SKRectI ToSKRectI(this LtrbPixelRect r)
{ {
return new SKRectI(r.Left, r.Top, r.Right, r.Bottom); return new SKRectI(r.Left, r.Top, r.Right, r.Bottom);
@ -103,7 +103,7 @@ namespace Avalonia.Skia
{ {
r.RadiiTopLeft.ToSKPoint(), r.RadiiTopRight.ToSKPoint(), r.RadiiTopLeft.ToSKPoint(), r.RadiiTopRight.ToSKPoint(),
r.RadiiBottomRight.ToSKPoint(), r.RadiiBottomLeft.ToSKPoint(), r.RadiiBottomRight.ToSKPoint(), r.RadiiBottomLeft.ToSKPoint(),
}); });
return result; return result;
} }
@ -112,17 +112,17 @@ namespace Avalonia.Skia
{ {
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
} }
internal static LtrbRect ToAvaloniaLtrbRect(this SKRect r) internal static LtrbRect ToAvaloniaLtrbRect(this SKRect r)
{ {
return new LtrbRect(r.Left, r.Top, r.Right, r.Bottom); return new LtrbRect(r.Left, r.Top, r.Right, r.Bottom);
} }
public static PixelRect ToAvaloniaPixelRect(this SKRectI r) public static PixelRect ToAvaloniaPixelRect(this SKRectI r)
{ {
return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
} }
internal static LtrbPixelRect ToAvaloniaLtrbPixelRect(this SKRectI r) internal static LtrbPixelRect ToAvaloniaLtrbPixelRect(this SKRectI r)
{ {
return new LtrbPixelRect(r.Left, r.Top, r.Right, r.Bottom); return new LtrbPixelRect(r.Left, r.Top, r.Right, r.Bottom);
@ -220,9 +220,12 @@ namespace Avalonia.Skia
switch (m) switch (m)
{ {
default: default:
case GradientSpreadMethod.Pad: return SKShaderTileMode.Clamp; case GradientSpreadMethod.Pad:
case GradientSpreadMethod.Reflect: return SKShaderTileMode.Mirror; return SKShaderTileMode.Clamp;
case GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat; case GradientSpreadMethod.Reflect:
return SKShaderTileMode.Mirror;
case GradientSpreadMethod.Repeat:
return SKShaderTileMode.Repeat;
} }
} }
@ -231,9 +234,12 @@ namespace Avalonia.Skia
switch (a) switch (a)
{ {
default: default:
case TextAlignment.Left: return SKTextAlign.Left; case TextAlignment.Left:
case TextAlignment.Center: return SKTextAlign.Center; return SKTextAlign.Left;
case TextAlignment.Right: return SKTextAlign.Right; case TextAlignment.Center:
return SKTextAlign.Center;
case TextAlignment.Right:
return SKTextAlign.Right;
} }
} }
@ -262,9 +268,12 @@ namespace Avalonia.Skia
switch (a) switch (a)
{ {
default: default:
case SKTextAlign.Left: return TextAlignment.Left; case SKTextAlign.Left:
case SKTextAlign.Center: return TextAlignment.Center; return TextAlignment.Left;
case SKTextAlign.Right: return TextAlignment.Right; case SKTextAlign.Center:
return TextAlignment.Center;
case SKTextAlign.Right:
return TextAlignment.Right;
} }
} }
@ -275,7 +284,18 @@ namespace Avalonia.Skia
SKFontStyleSlant.Upright => FontStyle.Normal, SKFontStyleSlant.Upright => FontStyle.Normal,
SKFontStyleSlant.Italic => FontStyle.Italic, SKFontStyleSlant.Italic => FontStyle.Italic,
SKFontStyleSlant.Oblique => FontStyle.Oblique, SKFontStyleSlant.Oblique => FontStyle.Oblique,
_ => throw new ArgumentOutOfRangeException(nameof (slant), slant, null) _ => throw new ArgumentOutOfRangeException(nameof(slant), slant, null)
};
}
public static SKFontStyleSlant ToSkia(this FontStyle style)
{
return style switch
{
FontStyle.Normal => SKFontStyleSlant.Upright,
FontStyle.Italic => SKFontStyleSlant.Italic,
FontStyle.Oblique => SKFontStyleSlant.Oblique,
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null)
}; };
} }

47
tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Fonts.Inter; using Avalonia.Fonts.Inter;
using Avalonia.Headless; using Avalonia.Headless;
using Avalonia.Media; using Avalonia.Media;
@ -103,7 +101,7 @@ namespace Avalonia.Skia.UnitTests.Media
{ {
Assert.True(FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out _)); Assert.True(FontManager.Current.TryGetGlyphTypeface(Typeface.Default, out _));
for (int i = 0;i < 10; i++) for (int i = 0; i < 10; i++)
{ {
FontManager.Current.TryGetGlyphTypeface(new Typeface("Unknown"), out _); FontManager.Current.TryGetGlyphTypeface(new Typeface("Unknown"), out _);
} }
@ -313,7 +311,7 @@ namespace Avalonia.Skia.UnitTests.Media
{ {
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("微軟正黑體"), out var glyphTypeface)); Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("微軟正黑體"), out var glyphTypeface));
Assert.Equal("Microsoft JhengHei",glyphTypeface.FamilyName); Assert.Equal("Microsoft JhengHei", glyphTypeface.FamilyName);
} }
} }
} }
@ -325,7 +323,7 @@ namespace Avalonia.Skia.UnitTests.Media
{ {
using (AvaloniaLocator.EnterScope()) using (AvaloniaLocator.EnterScope())
{ {
FontManager.Current.AddFontCollection(new InterFontCollection()); FontManager.Current.AddFontCollection(new InterFontCollection());
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:Inter#Inter"), Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:Inter#Inter"),
out var glyphTypeface)); out var glyphTypeface));
@ -346,12 +344,12 @@ namespace Avalonia.Skia.UnitTests.Media
{ {
using (AvaloniaLocator.EnterScope()) using (AvaloniaLocator.EnterScope())
{ {
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions
{ {
DefaultFamilyName = s_fontUri, DefaultFamilyName = s_fontUri,
FontFamilyMappings = new Dictionary<string, FontFamily> FontFamilyMappings = new Dictionary<string, FontFamily>
{ {
{ "Segoe UI", new FontFamily("fonts:Inter#Inter") } { "Segoe UI", new FontFamily("fonts:Inter#Inter") }
} }
}); });
@ -428,6 +426,33 @@ namespace Avalonia.Skia.UnitTests.Media
} }
} }
[Win32Fact("Windows specific font")]
public void Should_Get_Regular_Font_After_Matching_Italic_Font()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
{
using (AvaloniaLocator.EnterScope())
{
Assert.True(FontManager.Current.TryMatchCharacter('こ', FontStyle.Italic, FontWeight.Normal, FontStretch.Normal, null, null, out var italicTypeface));
Assert.Equal(FontSimulations.None, italicTypeface.GlyphTypeface.FontSimulations);
Assert.Equal("Yu Gothic UI", italicTypeface.GlyphTypeface.FamilyName);
Assert.NotEqual(FontStyle.Normal, italicTypeface.Style);
Assert.True(FontManager.Current.TryMatchCharacter('こ', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var regularTypeface));
Assert.Equal("Yu Gothic UI", regularTypeface.GlyphTypeface.FamilyName);
Assert.Equal(FontStyle.Normal, regularTypeface.Style);
Assert.NotEqual(((GlyphTypefaceImpl)italicTypeface.GlyphTypeface).SKTypeface, ((GlyphTypefaceImpl)regularTypeface.GlyphTypeface).SKTypeface);
}
}
}
[Fact] [Fact]
public void Should_Fallback_When_Font_Family_Is_Empty() public void Should_Fallback_When_Font_Family_Is_Empty()
{ {

Loading…
Cancel
Save