diff --git a/Avalonia.sln b/Avalonia.sln index c000f56d09..461de8530b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs + src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 402bc3a099..bdb5882ef2 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -23,6 +23,7 @@ + @@ -46,6 +47,7 @@ + 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..9e1e52cb73 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -19,6 +19,21 @@ namespace Avalonia.Media /// FontMetrics Metrics { get; } + /// + /// Gets the algorithmic style simulations applied to this glyph typeface. + /// + 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); + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 73dd3366aa..5c43a1c94f 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -477,25 +477,28 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextCharacters shapedTextCharacters: { - var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0]; - var lastCluster = firstCluster; - - for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++) + if(shapedTextCharacters.ShapedBuffer.Length > 0) { - var glyphInfo = shapedTextCharacters.ShapedBuffer[i]; + var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0]; + var lastCluster = firstCluster; - if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) + for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++) { - measuredLength += Math.Max(0, lastCluster - firstCluster); + var glyphInfo = shapedTextCharacters.ShapedBuffer[i]; - goto found; - } + if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) + { + measuredLength += Math.Max(0, lastCluster - firstCluster); - lastCluster = glyphInfo.GlyphCluster; - currentWidth += glyphInfo.GlyphAdvance; - } + goto found; + } - measuredLength += currentRun.TextSourceLength; + lastCluster = glyphInfo.GlyphCluster; + currentWidth += glyphInfo.GlyphAdvance; + } + + measuredLength += currentRun.TextSourceLength; + } break; } diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index c8ac947c16..76948e9286 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 { get; } + 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/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index d0c39f0289..75746273c2 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -9,6 +9,7 @@ CS1591 + @@ -68,4 +69,8 @@ + + + + diff --git a/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs b/src/Shared/StringCompatibilityExtensions.cs similarity index 83% rename from src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs rename to src/Shared/StringCompatibilityExtensions.cs index 45e41b44d6..7c894dba97 100644 --- a/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs +++ b/src/Shared/StringCompatibilityExtensions.cs @@ -3,7 +3,7 @@ namespace System; #if !NET6_0_OR_GREATER -public static class StringCompatibilityExtensions +internal static class StringCompatibilityExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Contains(this string str, char search) => 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/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 1c79e93f5c..2080352020 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -46,7 +46,6 @@ namespace Avalonia.iOS ); _topLevelImpl.Surfaces = new[] { new EaglLayerSurface(l) }; MultipleTouchEnabled = true; - AddSubviews(new UIView[] { new UIKit.UIButton(UIButtonType.InfoDark) }); } public override bool CanBecomeFirstResponder => true; 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); } } } 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; + } } }