From 2a547d716101c67d2077655d460bae5fbe9e5bfb Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 24 Oct 2022 14:27:53 +0200 Subject: [PATCH 1/3] Introduce GlyphMetrics --- src/Avalonia.Base/Media/FontSimulations.cs | 27 ++++++++++++++++ src/Avalonia.Base/Media/GlyphMetrics.cs | 24 ++++++++++++++ src/Avalonia.Base/Media/IGlyphTypeface.cs | 13 ++++++++ .../HeadlessPlatformStubs.cs | 13 ++++++++ src/Skia/Avalonia.Skia/FontManagerImpl.cs | 16 +++++++--- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 28 +++++++++++++--- .../Media/GlyphTypefaceImpl.cs | 25 ++++++++++++++- .../HarfBuzzGlyphTypefaceImpl.cs | 32 +++++++++++++------ tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 13 ++++++++ 9 files changed, 173 insertions(+), 18 deletions(-) create mode 100644 src/Avalonia.Base/Media/FontSimulations.cs create mode 100644 src/Avalonia.Base/Media/GlyphMetrics.cs diff --git a/src/Avalonia.Base/Media/FontSimulations.cs b/src/Avalonia.Base/Media/FontSimulations.cs new file mode 100644 index 0000000000..2faf53f1d8 --- /dev/null +++ b/src/Avalonia.Base/Media/FontSimulations.cs @@ -0,0 +1,27 @@ +using System; + +namespace Avalonia.Media +{ + /// + /// Specifies algorithmic style simulations to be applied to the typeface. + /// Bold and oblique simulations can be combined via bitwise OR operation. + /// + [Flags] + public enum FontSimulations : byte + { + /// + /// No simulations are performed. + /// + None = 0x0000, + + /// + /// Algorithmic emboldening is performed. + /// + Bold = 0x0001, + + /// + /// Algorithmic italicization is performed. + /// + Oblique = 0x0002 + } +} diff --git a/src/Avalonia.Base/Media/GlyphMetrics.cs b/src/Avalonia.Base/Media/GlyphMetrics.cs new file mode 100644 index 0000000000..2ee1f87d38 --- /dev/null +++ b/src/Avalonia.Base/Media/GlyphMetrics.cs @@ -0,0 +1,24 @@ +namespace Avalonia.Media; + +public readonly struct GlyphMetrics +{ + /// + /// Distance from the x-origin to the left extremum of the glyph. + /// + public int XBearing { get; init; } + + /// + /// Distance from the top extremum of the glyph to the y-origin. + /// + public int YBearing{ get; init; } + + /// + /// Distance from the left extremum of the glyph to the right extremum. + /// + public int Width{ get; init; } + + /// + /// Distance from the top extremum of the glyph to the bottom extremum. + /// + public int Height{ get; init; } +} diff --git a/src/Avalonia.Base/Media/IGlyphTypeface.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs index de2a2309ee..e65a3cb6bf 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -19,6 +19,19 @@ namespace Avalonia.Media /// FontMetrics Metrics { get; } + /// + /// Gets the algorithmic style simulations applied to this glyph typeface. + /// + FontSimulations FontSimulations { get; } + + /// + /// + /// + /// + /// + /// + bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics); + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index c8ac947c16..cfeb0d92d0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -102,6 +102,8 @@ namespace Avalonia.Headless public int GlyphCount => 1337; + public FontSimulations FontSimulations => throw new NotImplementedException(); + public void Dispose() { } @@ -138,6 +140,17 @@ namespace Avalonia.Headless table = null; return false; } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = new GlyphMetrics + { + Height = 10, + Width = 10 + }; + + return true; + } } class HeadlessTextShaperStub : ITextShaperImpl diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 6b5e0b3db5..90ff9652d8 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -148,11 +148,19 @@ namespace Avalonia.Skia $"Could not create glyph typeface for: {typeface.FontFamily.Name}."); } - var isFakeBold = (int)typeface.Weight >= 600 && !skTypeface.IsBold; + var fontSimulations = FontSimulations.None; - var isFakeItalic = typeface.Style == FontStyle.Italic && !skTypeface.IsItalic; - - return new GlyphTypefaceImpl(skTypeface, isFakeBold, isFakeItalic); + if((int)typeface.Weight >= 600 && !skTypeface.IsBold) + { + fontSimulations |= FontSimulations.Bold; + } + + if(typeface.Style == FontStyle.Italic && !skTypeface.IsItalic) + { + fontSimulations |= FontSimulations.Oblique; + } + + return new GlyphTypefaceImpl(skTypeface, fontSimulations); } } } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index 7f3faf251f..d11f4aa7d3 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -12,7 +12,7 @@ namespace Avalonia.Skia { private bool _isDisposed; - public GlyphTypefaceImpl(SKTypeface typeface, bool isFakeBold = false, bool isFakeItalic = false) + public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations) { Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface)); @@ -52,9 +52,7 @@ namespace Avalonia.Skia GlyphCount = Typeface.GlyphCount; - IsFakeBold = isFakeBold; - - IsFakeItalic = isFakeItalic; + FontSimulations = fontSimulations; } public Face Face { get; } @@ -63,6 +61,8 @@ namespace Avalonia.Skia public SKTypeface Typeface { get; } + public FontSimulations FontSimulations { get; } + public int ReplacementCodepoint { get; } public FontMetrics Metrics { get; } @@ -73,6 +73,26 @@ namespace Avalonia.Skia public bool IsFakeItalic { get; } + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } + /// public ushort GetGlyph(uint codepoint) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 77d0e58d3d..705c715455 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -1,10 +1,11 @@ using System; -using System.Drawing.Drawing2D; using Avalonia.Media; using Avalonia.Metadata; using HarfBuzzSharp; using SharpDX.DirectWrite; using FontMetrics = Avalonia.Media.FontMetrics; +using FontSimulations = Avalonia.Media.FontSimulations; +using GlyphMetrics = Avalonia.Media.GlyphMetrics; namespace Avalonia.Direct2D1.Media { @@ -82,6 +83,8 @@ namespace Avalonia.Direct2D1.Media public int GlyphCount { get; set; } + public FontSimulations FontSimulations => FontSimulations.None; + /// public ushort GetGlyph(uint codepoint) { @@ -135,6 +138,26 @@ namespace Avalonia.Direct2D1.Media return Font.GetHorizontalGlyphAdvances(glyphIndices); } + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } + private void Dispose(bool disposing) { if (_isDisposed) diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 3bcbc2efbd..5b11345f16 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.UnitTests private bool _isDisposed; private Blob _blob; - public HarfBuzzGlyphTypefaceImpl(Stream data, bool isFakeBold = false, bool isFakeItalic = false) + public HarfBuzzGlyphTypefaceImpl(Stream data) { _blob = Blob.FromStream(data); @@ -45,10 +45,6 @@ namespace Avalonia.UnitTests }; GlyphCount = Face.GlyphCount; - - IsFakeBold = isFakeBold; - - IsFakeItalic = isFakeItalic; } public FontMetrics Metrics { get; } @@ -58,10 +54,8 @@ namespace Avalonia.UnitTests public Font Font { get; } public int GlyphCount { get; set; } - - public bool IsFakeBold { get; } - - public bool IsFakeItalic { get; } + + public FontSimulations FontSimulations { get; } /// public ushort GetGlyph(uint codepoint) @@ -162,5 +156,25 @@ namespace Avalonia.UnitTests Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } } } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index a1c492a7f1..bd9d8e5adf 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -15,6 +15,8 @@ namespace Avalonia.UnitTests public int GlyphCount => 1337; + public FontSimulations FontSimulations => throw new NotImplementedException(); + public ushort GetGlyph(uint codepoint) { return (ushort)codepoint; @@ -56,5 +58,16 @@ namespace Avalonia.UnitTests table = null; return false; } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = new GlyphMetrics + { + Width = 10, + Height = 10 + }; + + return true; + } } } From 04b8f3b38833b6ee6c0ade8ead624b328b137d65 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Oct 2022 10:58:18 +0200 Subject: [PATCH 2/3] Fix headless --- src/Avalonia.Headless/HeadlessPlatformStubs.cs | 2 +- tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index cfeb0d92d0..76948e9286 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -102,7 +102,7 @@ namespace Avalonia.Headless public int GlyphCount => 1337; - public FontSimulations FontSimulations => throw new NotImplementedException(); + public FontSimulations FontSimulations { get; } public void Dispose() { diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 7998c95877..a748f6cf00 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -103,7 +103,7 @@ namespace Avalonia.Skia.UnitTests.Media } } - return new GlyphTypefaceImpl(skTypeface); + return new GlyphTypefaceImpl(skTypeface, FontSimulations.None); } } } From f2fda7bcdb4253efd5df7ff86ab39b23d7ba77ae Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Oct 2022 11:38:37 +0200 Subject: [PATCH 3/3] Fix xml comment --- src/Avalonia.Base/Media/IGlyphTypeface.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/IGlyphTypeface.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs index e65a3cb6bf..9e1e52cb73 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -25,11 +25,13 @@ namespace Avalonia.Media FontSimulations FontSimulations { get; } /// - /// + /// Tries to get a glyph's metrics in em units. /// - /// - /// - /// + /// The glyph id. + /// The glyph metrics. + /// + /// true if an glyph's metrics was found, false otherwise. + /// bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics); ///