From 07c11c75e7b216a66ba2c965dbc1fdf4ccb13717 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 20 Jan 2023 20:28:54 +0100 Subject: [PATCH] Use IGlyphRunImpl in the IDrawingContextImpl --- src/Avalonia.Base/Media/DrawingContext.cs | 2 +- src/Avalonia.Base/Media/DrawingGroup.cs | 5 +- src/Avalonia.Base/Media/GlyphRun.cs | 55 ++++++++----------- src/Avalonia.Base/Media/GlyphRunMetrics.cs | 15 ++--- .../Media/ImmediateDrawingContext.cs | 2 +- src/Avalonia.Base/Media/TextDecoration.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 2 +- .../Platform/IDrawingContextImpl.cs | 2 +- src/Avalonia.Base/Platform/IGlyphRunImpl.cs | 19 ++++++- .../Drawing/CompositionDrawingContext.cs | 2 +- .../Composition/Server/DrawingContextProxy.cs | 2 +- .../Composition/Server/FpsCounter.cs | 2 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 2 +- .../Rendering/SceneGraph/GlyphRunNode.cs | 18 ++++-- .../HeadlessPlatformRenderInterface.cs | 8 ++- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 10 ++-- src/Skia/Avalonia.Skia/GlyphRunImpl.cs | 10 +++- .../Avalonia.Skia/PlatformRenderInterface.cs | 14 +++-- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 ++++- .../Media/DrawingContextImpl.cs | 8 +-- .../Avalonia.Direct2D1/Media/GlyphRunImpl.cs | 12 +++- .../Media/GlyphRunTests.cs | 4 +- .../NullDrawingContextImpl.cs | 2 +- tests/Avalonia.Benchmarks/NullGlyphRun.cs | 4 ++ .../NullRenderingPlatform.cs | 2 +- .../DatePickerTests.cs | 3 +- .../MaskedTextBoxTests.cs | 8 +-- .../Primitives/SelectingItemsControlTests.cs | 4 +- .../TimePickerTests.cs | 3 +- .../Media/GlyphRunTests.cs | 4 +- tests/Avalonia.UnitTests/MockGlyphRun.cs | 11 ++++ .../MockPlatformRenderInterface.cs | 2 +- 32 files changed, 154 insertions(+), 99 deletions(-) diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index 077816c645..d295111d72 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -246,7 +246,7 @@ namespace Avalonia.Media if (foreground != null) { - PlatformImpl.DrawGlyphRun(foreground, glyphRun); + PlatformImpl.DrawGlyphRun(foreground, glyphRun.PlatformImpl); } } diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 7d3b4c056e..481329c20c 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -167,18 +167,17 @@ namespace Avalonia.Media AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry)); } - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { if (foreground == null || glyphRun == null) { return; } - // Add a GlyphRunDrawing to the Drawing graph GlyphRunDrawing glyphRunDrawing = new GlyphRunDrawing { Foreground = foreground, - GlyphRun = glyphRun, + GlyphRun = new GlyphRun(glyphRun) }; // Add Drawing to the Drawing graph diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index b637c94d88..65575617d0 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -13,10 +13,9 @@ namespace Avalonia.Media /// public sealed class GlyphRun : IDisposable { - private IGlyphRunImpl? _glyphRunImpl; + private IRef? _platformImpl; private double _fontRenderingEmSize; private int _biDiLevel; - private Point? _baselineOrigin; private GlyphRunMetrics? _glyphRunMetrics; private ReadOnlyMemory _characters; private IReadOnlyList _glyphInfos; @@ -68,6 +67,13 @@ namespace Avalonia.Media _biDiLevel = biDiLevel; } + internal GlyphRun(IRef platformImpl) + { + _glyphInfos = Array.Empty(); + GlyphTypeface = Typeface.Default.GlyphTypeface; + _platformImpl = platformImpl; + } + private static IReadOnlyList CreateGlyphInfos(IReadOnlyList glyphIndices, double fontRenderingEmSize, IGlyphTypeface glyphTypeface) { @@ -132,7 +138,7 @@ namespace Avalonia.Media /// /// Gets or sets the conservative bounding box of the . /// - public Size Size => new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height); + public Size Size => PlatformImpl.Item.Size; /// /// @@ -141,13 +147,9 @@ namespace Avalonia.Media => _glyphRunMetrics ??= CreateGlyphRunMetrics(); /// - /// Gets or sets the baseline origin of the. + /// Gets the baseline origin of the. /// - public Point BaselineOrigin - { - get => _baselineOrigin ??= CalculateBaselineOrigin(); - set => Set(ref _baselineOrigin, value); - } + public Point BaselineOrigin => PlatformImpl.Item.BaselineOrigin; /// /// Gets or sets the list of UTF16 code points that represent the Unicode content of the . @@ -193,8 +195,8 @@ namespace Avalonia.Media /// /// The platform implementation of the . /// - public IGlyphRunImpl GlyphRunImpl - => _glyphRunImpl ??= CreateGlyphRunImpl(); + public IRef PlatformImpl + => _platformImpl ??= CreateGlyphRunImpl(); /// /// Obtains geometry for the glyph run. @@ -233,7 +235,7 @@ namespace Avalonia.Media if (characterIndex > Metrics.LastCluster) { - return Metrics.WidthIncludingTrailingWhitespace; + return Size.Width; } var glyphIndex = FindGlyphIndex(characterIndex); @@ -607,15 +609,6 @@ namespace Avalonia.Media return new CharacterHit(cluster, clusterLength); } - /// - /// Calculates the default baseline origin of the . - /// - /// The baseline origin. - private Point CalculateBaselineOrigin() - { - return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale); - } - private GlyphRunMetrics CreateGlyphRunMetrics() { int firstCluster, lastCluster; @@ -668,8 +661,6 @@ namespace Avalonia.Media return new GlyphRunMetrics( width, - widthIncludingTrailingWhitespace, - height, trailingWhitespaceLength, newLineLength, firstCluster, @@ -800,28 +791,30 @@ namespace Avalonia.Media private void Set(ref T field, T value) { - _glyphRunImpl?.Dispose(); + _platformImpl?.Dispose(); - _glyphRunImpl = null; + _platformImpl = null; _glyphRunMetrics = null; - _baselineOrigin = null; - field = value; } - private IGlyphRunImpl CreateGlyphRunImpl() + private IRef CreateGlyphRunImpl() { var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); - return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos); + var platformImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos); + + _platformImpl = RefCountable.Create(platformImpl); + + return _platformImpl; } public void Dispose() { - _glyphRunImpl?.Dispose(); - _glyphRunImpl = null; + _platformImpl?.Dispose(); + _platformImpl = null; } } } diff --git a/src/Avalonia.Base/Media/GlyphRunMetrics.cs b/src/Avalonia.Base/Media/GlyphRunMetrics.cs index 492b5214cd..09b183d044 100644 --- a/src/Avalonia.Base/Media/GlyphRunMetrics.cs +++ b/src/Avalonia.Base/Media/GlyphRunMetrics.cs @@ -2,27 +2,20 @@ { public readonly record struct GlyphRunMetrics { - public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, double height, - int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) + public GlyphRunMetrics(double width, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) { Width = width; - WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace; - Height = height; TrailingWhitespaceLength = trailingWhitespaceLength; - NewLineLength= newLineLength; + NewLineLength = newLineLength; FirstCluster = firstCluster; LastCluster = lastCluster; } public double Width { get; } - public double WidthIncludingTrailingWhitespace { get; } - - public double Height { get; } - public int TrailingWhitespaceLength { get; } - - public int NewLineLength { get; } + + public int NewLineLength { get; } public int FirstCluster { get; } diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index eb6f105680..7d9534c414 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -182,7 +182,7 @@ namespace Avalonia.Media /// /// The foreground brush. /// The glyph run. - public void DrawGlyphRun(IImmutableBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IImmutableBrush foreground, IRef glyphRun) { _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index d6b2841214..dc9e5cb907 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -218,7 +218,7 @@ namespace Avalonia.Media { var offsetY = glyphRun.BaselineOrigin.Y - origin.Y; - var intersections = glyphRun.GlyphRunImpl.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); + var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); if (intersections != null && intersections.Count > 0) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 245104f8fe..260fcaccbe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1320,7 +1320,7 @@ namespace Avalonia.Media.TextFormatting newLineLength = textRun.GlyphRun.Metrics.NewLineLength; } - widthIncludingWhitespace += textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace; + widthIncludingWhitespace += textRun.Size.Width; break; } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 6aa5eeea3d..c05c04c22e 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -91,7 +91,7 @@ namespace Avalonia.Platform /// /// The foreground. /// The glyph run. - void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun); + void DrawGlyphRun(IBrush foreground, IRef glyphRun); /// /// Creates a new that can be used as a render layer diff --git a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs index 6a8ae4d954..46b065b04e 100644 --- a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs @@ -10,6 +10,23 @@ namespace Avalonia.Platform [Unstable] public interface IGlyphRunImpl : IDisposable { - IReadOnlyList GetIntersections(float lowerBound, float upperBound); + + /// + /// Gets the conservative bounding box of the glyph run./>. + /// + Size Size { get; } + + /// + /// Gets the baseline origin of the glyph run./>. + /// + Point BaselineOrigin { get; } + + /// + /// Gets the intersections of specified upper and lower limit. + /// + /// Upper limit. + /// Lower limit. + /// + IReadOnlyList GetIntersections(float lowerLimit, float upperLimit); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index aae1fcb90e..05488a558f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -159,7 +159,7 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW public object? GetFeature(Type t) => null; /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { var next = NextDrawAs(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index e6bbba6ec0..c58beebe7f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.DrawEllipse(brush, pen, rect); } - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { _impl.DrawGlyphRun(foreground, glyphRun); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index 32923a5257..ebab39cee8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -72,7 +72,7 @@ internal class FpsCounter { var run = _runs[ch - FirstChar]; context.Transform = Matrix.CreateTranslation(offset, 0); - context.DrawGlyphRun(Brushes.White, run); + context.DrawGlyphRun(Brushes.White, run.PlatformImpl); offset += run.Size.Width; } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index d6766fa9b8..b1d8301557 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -206,7 +206,7 @@ namespace Avalonia.Rendering.SceneGraph public object? GetFeature(Type t) => null; /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { var next = NextDrawAs(); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1d85e95835..a2d914bdd7 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Utilities; namespace Avalonia.Rendering.SceneGraph { @@ -19,13 +20,13 @@ namespace Avalonia.Rendering.SceneGraph public GlyphRunNode( Matrix transform, IBrush foreground, - GlyphRun glyphRun, + IRef glyphRun, IDisposable? aux = null) - : base(new Rect(glyphRun.Size), transform, aux) + : base(new Rect(glyphRun.Item.Size), transform, aux) { Transform = transform; Foreground = foreground.ToImmutable(); - GlyphRun = glyphRun; + GlyphRun = glyphRun.Clone(); } /// @@ -41,7 +42,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the glyph run to draw. /// - public GlyphRun GlyphRun { get; } + public IRef GlyphRun { get; } /// public override void Render(IDrawingContextImpl context) @@ -61,14 +62,19 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - internal bool Equals(Matrix transform, IBrush foreground, GlyphRun glyphRun) + internal bool Equals(Matrix transform, IBrush foreground, IRef glyphRun) { return transform == Transform && Equals(foreground, Foreground) && - Equals(glyphRun, GlyphRun); + Equals(glyphRun.Item, GlyphRun.Item); } /// public override bool HitTest(Point p) => Bounds.ContainsExclusive(p); + + public override void Dispose() + { + GlyphRun?.Dispose(); + } } } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index e368ddc373..739a648bac 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -126,13 +126,17 @@ namespace Avalonia.Headless class HeadlessGlyphRunStub : IGlyphRunImpl { + public Size Size => new Size(8, 12); + + public Point BaselineOrigin => new Point(0, 8); + public void Dispose() { } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) { - throw new NotImplementedException(); + return null; } } @@ -463,7 +467,7 @@ namespace Avalonia.Headless { } - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 8b71f4e17e..dcb20d2a44 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -492,15 +492,15 @@ namespace Avalonia.Skia } /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { CheckLease(); - using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) + using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size)) { - var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; + var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; - Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.BaselineOrigin.X, - (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); + Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.Item.BaselineOrigin.X, + (float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint); } } diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index dd7ed31a6e..cc669f9aaa 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -11,9 +11,13 @@ namespace Avalonia.Skia [Unstable] public class GlyphRunImpl : IGlyphRunImpl { - public GlyphRunImpl(SKTextBlob textBlob) + public GlyphRunImpl(SKTextBlob textBlob, Size size, Point baselineOrigin) { TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob)); + + Size = size; + + BaselineOrigin = baselineOrigin; } /// @@ -21,6 +25,10 @@ namespace Avalonia.Skia /// public SKTextBlob TextBlob { get; } + public Size Size { get; } + + public Point BaselineOrigin { get; } + public IReadOnlyList GetIntersections(float upperBound, float lowerBound) => TextBlob.GetIntercepts(lowerBound, upperBound); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 3fb7491898..6630f0707e 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -81,7 +81,7 @@ namespace Avalonia.Skia SKPath path = new SKPath(); - var (currentX, currentY) = glyphRun.BaselineOrigin; + var (currentX, currentY) = glyphRun.PlatformImpl.Item.BaselineOrigin; for (var i = 0; i < glyphRun.GlyphInfos.Count; i++) { @@ -236,7 +236,7 @@ namespace Avalonia.Skia var glyphSpan = runBuffer.GetGlyphSpan(); var positionSpan = runBuffer.GetPositionSpan(); - var currentX = 0.0; + var width = 0.0; for (int i = 0; i < count; i++) { @@ -245,12 +245,16 @@ namespace Avalonia.Skia glyphSpan[i] = glyphInfo.GlyphIndex; - positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y); + positionSpan[i] = new SKPoint((float)(width + offset.X), (float)offset.Y); - currentX += glyphInfo.GlyphAdvance; + width += glyphInfo.GlyphAdvance; } - return new GlyphRunImpl(builder.Build()); + var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; + var height = glyphTypeface.Metrics.LineSpacing * scale; + var baselineOrigin = new Point(0, -glyphTypeface.Metrics.Ascent * scale); + + return new GlyphRunImpl(builder.Build(), new Size(width, height), baselineOrigin); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index d9cd0590fc..a2f99d9d71 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -181,9 +181,15 @@ namespace Avalonia.Direct2D1 run.Advances = new float[glyphCount]; + var width = 0.0; + for (var i = 0; i < glyphCount; i++) { - run.Advances[i] = (float)glyphInfos[i].GlyphAdvance; + var advance = glyphInfos[i].GlyphAdvance; + + width += advance; + + run.Advances[i] = (float)advance; } run.Offsets = new GlyphOffset[glyphCount]; @@ -199,7 +205,11 @@ namespace Avalonia.Direct2D1 }; } - return new GlyphRunImpl(run); + var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; + var height = glyphTypeface.Metrics.LineSpacing * scale; + var baselineOrigin = new Point(0, -glyphTypeface.Metrics.Ascent * scale); + + return new GlyphRunImpl(run, new Size(width, height), baselineOrigin); } class D2DApi : IPlatformRenderInterfaceContext diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 3f2298eb22..d5d6cd8c29 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -390,13 +390,13 @@ namespace Avalonia.Direct2D1.Media /// /// The foreground. /// The glyph run. - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { - using (var brush = CreateBrush(foreground, glyphRun.Size)) + using (var brush = CreateBrush(foreground, glyphRun.Item.Size)) { - var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; + var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; - _renderTarget.DrawGlyphRun(glyphRun.BaselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun, + _renderTarget.DrawGlyphRun(glyphRun.Item.BaselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun, brush.PlatformBrush, MeasuringMode.Natural); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs index 67418613a4..24b8fc04b3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs @@ -1,20 +1,26 @@ using System.Collections.Generic; using Avalonia.Platform; +using SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { internal class GlyphRunImpl : IGlyphRunImpl { - public GlyphRunImpl(SharpDX.DirectWrite.GlyphRun glyphRun) + public GlyphRunImpl(GlyphRun glyphRun, Size size, Point baselineOrigin) { + Size = size; + BaselineOrigin = baselineOrigin; GlyphRun = glyphRun; } - public SharpDX.DirectWrite.GlyphRun GlyphRun { get; } + public Size Size { get; } + + public Point BaselineOrigin { get; } + + public GlyphRun GlyphRun { get; } public void Dispose() { - //SharpDX already handles this. //GlyphRun?.Dispose(); } diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index 3573ba6b07..a05bfbea4c 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -25,7 +25,7 @@ namespace Avalonia.Base.UnitTests.Media [Theory] public void Should_Get_Distance_From_CharacterHit(double[] advances, int[] clusters, int start, int trailingLength, double expectedDistance) { - using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + using(UnitTestApplication.Start(TestServices.StyledWindow)) using (var glyphRun = CreateGlyphRun(advances, clusters)) { var characterHit = new CharacterHit(start, trailingLength); @@ -44,7 +44,7 @@ namespace Avalonia.Base.UnitTests.Media public void Should_Get_CharacterHit_FromDistance(double[] advances, int[] clusters, double distance, int start, int trailingLengthExpected, bool isInsideExpected) { - using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + using(UnitTestApplication.Start(TestServices.StyledWindow)) using (var glyphRun = CreateGlyphRun(advances, clusters)) { var textBounds = glyphRun.GetCharacterHitFromDistance(distance, out var isInside); diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs index 8436881122..e83b2d7598 100644 --- a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -44,7 +44,7 @@ namespace Avalonia.Benchmarks { } - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { } diff --git a/tests/Avalonia.Benchmarks/NullGlyphRun.cs b/tests/Avalonia.Benchmarks/NullGlyphRun.cs index 1c2c7c0d7d..c4707c78c8 100644 --- a/tests/Avalonia.Benchmarks/NullGlyphRun.cs +++ b/tests/Avalonia.Benchmarks/NullGlyphRun.cs @@ -5,6 +5,10 @@ namespace Avalonia.Benchmarks { internal class NullGlyphRun : IGlyphRunImpl { + public Size Size => default; + + public Point BaselineOrigin => default; + public void Dispose() { } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index a802cd0958..7a17efa59b 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -123,7 +123,7 @@ namespace Avalonia.Benchmarks public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { - return new MockGlyphRun(); + return new MockGlyphRun(glyphInfos); } public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index 894dc5b996..3b2ca51976 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs @@ -205,7 +205,8 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new MockFontManagerImpl(), standardCursorFactory: Mock.Of(), - textShaperImpl: new MockTextShaperImpl()); + textShaperImpl: new MockTextShaperImpl(), + renderInterface: new MockPlatformRenderInterface()); private static IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index c1d9fad6f4..2732f6a5fb 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void CaretIndex_Can_Moved_To_Position_After_The_End_Of_Text_With_Arrow_Key() { - using (Start()) + using (Start(TestServices.StyledWindow)) { var target = new MaskedTextBox { @@ -182,7 +182,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { - using (Start()) + using (Start(TestServices.StyledWindow)) { MaskedTextBox textBox = new MaskedTextBox { @@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection() { - using (Start()) + using (Start(TestServices.StyledWindow)) { var textBox = new MaskedTextBox { @@ -810,7 +810,7 @@ namespace Avalonia.Controls.UnitTests bool fromClipboard, string expected) { - using (Start()) + using (Start(TestServices.StyledWindow)) { var target = new MaskedTextBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 1775d7ef70..7d129d987e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2131,9 +2131,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private static IDisposable Start() { - return UnitTestApplication.Start(new TestServices( - fontManagerImpl: new MockFontManagerImpl(), - textShaperImpl: new MockTextShaperImpl())); + return UnitTestApplication.Start(TestServices.StyledWindow); } private static void Prepare(SelectingItemsControl target) diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index 2fd10010a6..e0bf230d99 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs @@ -101,7 +101,8 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new MockFontManagerImpl(), standardCursorFactory: Mock.Of(), - textShaperImpl: new MockTextShaperImpl()); + textShaperImpl: new MockTextShaperImpl(), + renderInterface: new MockPlatformRenderInterface()); private static IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs index f2d6670be5..59c7ac3786 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Skia.UnitTests.Media if (glyphRun.IsLeftToRight) { var characterHit = - glyphRun.GetCharacterHitFromDistance(glyphRun.Metrics.WidthIncludingTrailingWhitespace, out _); + glyphRun.GetCharacterHitFromDistance(glyphRun.Size.Width, out _); Assert.Equal(glyphRun.Characters.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength); } @@ -159,7 +159,7 @@ namespace Avalonia.Skia.UnitTests.Media { var height = glyphRun.Size.Height; - var currentX = glyphRun.IsLeftToRight ? 0d : glyphRun.Metrics.WidthIncludingTrailingWhitespace; + var currentX = glyphRun.IsLeftToRight ? 0d : glyphRun.Size.Width; var rects = new List(glyphRun.GlyphInfos!.Count); diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs index f525e4736b..45e7b47f62 100644 --- a/tests/Avalonia.UnitTests/MockGlyphRun.cs +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -1,10 +1,21 @@ using System.Collections.Generic; +using System.Linq; +using Avalonia.Media.TextFormatting; using Avalonia.Platform; namespace Avalonia.UnitTests { public class MockGlyphRun : IGlyphRunImpl { + public MockGlyphRun(IReadOnlyList glyphInfos) + { + Size = new Size(glyphInfos.Sum(x=> x.GlyphAdvance), 10); + } + + public Size Size { get; } + + public Point BaselineOrigin => new Point(0, 8); + public void Dispose() { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index d56e360e22..2a00ef8e40 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -149,7 +149,7 @@ namespace Avalonia.UnitTests public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) { - return Mock.Of(); + return new MockGlyphRun(glyphInfos); } public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;