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;
break;
default:
skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, (SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle);
skFontStyle = new SKFontStyle((SKFontStyleWeight)fontWeight, (SKFontStyleWidth)fontStretch, fontStyle.ToSkia());
break;
}
@ -63,7 +63,12 @@ namespace Avalonia.Skia
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;
}
@ -78,8 +83,7 @@ namespace Avalonia.Skia
{
glyphTypeface = null;
var fontStyle = new SKFontStyle((SKFontStyleWeight)weight, (SKFontStyleWidth)stretch,
(SKFontStyleSlant)style);
var fontStyle = new SKFontStyle((SKFontStyleWeight)weight, (SKFontStyleWidth)stretch, style.ToSkia());
var skTypeface = _skFontManager.MatchFamily(familyName, fontStyle);
@ -127,7 +131,7 @@ namespace Avalonia.Skia
var set = _skFontManager.GetFontStyles(familyName);
if(set.Count == 0)
if (set.Count == 0)
{
return false;
}

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

@ -16,7 +16,6 @@ namespace Avalonia.Skia
internal class GlyphTypefaceImpl : IGlyphTypeface2
{
private bool _isDisposed;
private readonly SKTypeface _typeface;
private readonly NameTable? _nameTable;
private readonly OS2Table? _os2Table;
private readonly HorizontalHeadTable? _hhTable;
@ -24,7 +23,7 @@ namespace Avalonia.Skia
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 };
@ -96,6 +95,11 @@ namespace Avalonia.Skia
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;
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 Font Font { get; }
@ -300,12 +306,12 @@ namespace Avalonia.Skia
private static FontStyle GetFontStyle(OS2Table.FontStyleSelection styleSelection)
{
if((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0)
if ((styleSelection & OS2Table.FontStyleSelection.ITALIC) != 0)
{
return FontStyle.Italic;
}
if((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0)
if ((styleSelection & OS2Table.FontStyleSelection.OBLIQUE) != 0)
{
return FontStyle.Oblique;
}
@ -315,18 +321,18 @@ namespace Avalonia.Skia
private Blob? GetTable(Face face, Tag tag)
{
var size = _typeface.GetTableSize(tag);
var size = SKTypeface.GetTableSize(tag);
var data = Marshal.AllocCoTaskMem(size);
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;
}
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,
Embolden = (FontSimulations & FontSimulations.Bold) != 0
@ -348,7 +354,7 @@ namespace Avalonia.Skia
Font.Dispose();
Face.Dispose();
_typeface.Dispose();
SKTypeface.Dispose();
}
public void Dispose()
@ -359,14 +365,14 @@ namespace Avalonia.Skia
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)
{
try
{
var asset = _typeface.OpenStream();
var asset = SKTypeface.OpenStream();
var size = asset.Length;
var buffer = new byte[size];

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

@ -1,8 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
@ -67,7 +67,7 @@ namespace Avalonia.Skia
{
return new SKPoint((float)p.X, (float)p.Y);
}
public static SKPoint ToSKPoint(this Vector p)
{
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);
}
internal static SKRect ToSKRect(this LtrbRect r)
{
return new SKRect((float)r.Left, (float)r.Right, (float)r.Right, (float)r.Bottom);
}
public static SKRectI ToSKRectI(this PixelRect r)
{
return new SKRectI(r.X, r.Y, r.Right, r.Bottom);
}
internal static SKRectI ToSKRectI(this LtrbPixelRect r)
{
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.RadiiBottomRight.ToSKPoint(), r.RadiiBottomLeft.ToSKPoint(),
});
});
return result;
}
@ -112,17 +112,17 @@ namespace Avalonia.Skia
{
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
}
internal static LtrbRect ToAvaloniaLtrbRect(this SKRect r)
{
return new LtrbRect(r.Left, r.Top, r.Right, r.Bottom);
}
public static PixelRect ToAvaloniaPixelRect(this SKRectI r)
{
return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
}
internal static LtrbPixelRect ToAvaloniaLtrbPixelRect(this SKRectI r)
{
return new LtrbPixelRect(r.Left, r.Top, r.Right, r.Bottom);
@ -220,9 +220,12 @@ namespace Avalonia.Skia
switch (m)
{
default:
case GradientSpreadMethod.Pad: return SKShaderTileMode.Clamp;
case GradientSpreadMethod.Reflect: return SKShaderTileMode.Mirror;
case GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat;
case GradientSpreadMethod.Pad:
return SKShaderTileMode.Clamp;
case GradientSpreadMethod.Reflect:
return SKShaderTileMode.Mirror;
case GradientSpreadMethod.Repeat:
return SKShaderTileMode.Repeat;
}
}
@ -231,9 +234,12 @@ namespace Avalonia.Skia
switch (a)
{
default:
case TextAlignment.Left: return SKTextAlign.Left;
case TextAlignment.Center: return SKTextAlign.Center;
case TextAlignment.Right: return SKTextAlign.Right;
case TextAlignment.Left:
return SKTextAlign.Left;
case TextAlignment.Center:
return SKTextAlign.Center;
case TextAlignment.Right:
return SKTextAlign.Right;
}
}
@ -262,9 +268,12 @@ namespace Avalonia.Skia
switch (a)
{
default:
case SKTextAlign.Left: return TextAlignment.Left;
case SKTextAlign.Center: return TextAlignment.Center;
case SKTextAlign.Right: return TextAlignment.Right;
case SKTextAlign.Left:
return TextAlignment.Left;
case SKTextAlign.Center:
return TextAlignment.Center;
case SKTextAlign.Right:
return TextAlignment.Right;
}
}
@ -275,7 +284,18 @@ namespace Avalonia.Skia
SKFontStyleSlant.Upright => FontStyle.Normal,
SKFontStyleSlant.Italic => FontStyle.Italic,
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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Fonts.Inter;
using Avalonia.Headless;
using Avalonia.Media;
@ -103,7 +101,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
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 _);
}
@ -313,7 +311,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
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())
{
FontManager.Current.AddFontCollection(new InterFontCollection());
FontManager.Current.AddFontCollection(new InterFontCollection());
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:Inter#Inter"),
out var glyphTypeface));
@ -346,12 +344,12 @@ namespace Avalonia.Skia.UnitTests.Media
{
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions
{
DefaultFamilyName = s_fontUri,
FontFamilyMappings = new Dictionary<string, FontFamily>
{
{ "Segoe UI", new FontFamily("fonts:Inter#Inter") }
AvaloniaLocator.CurrentMutable.BindToSelf(new FontManagerOptions
{
DefaultFamilyName = s_fontUri,
FontFamilyMappings = new Dictionary<string, FontFamily>
{
{ "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]
public void Should_Fallback_When_Font_Family_Is_Empty()
{

Loading…
Cancel
Save