Browse Source

Use IGlyphRunImpl in the IDrawingContextImpl

Benedikt Stebner 2 years ago
parent
commit
07c11c75e7
32 changed files with 154 additions and 99 deletions
  1. 1 1
      src/Avalonia.Base/Media/DrawingContext.cs
  2. 2 3
      src/Avalonia.Base/Media/DrawingGroup.cs
  3. 24 31
      src/Avalonia.Base/Media/GlyphRun.cs
  4. 4 11
      src/Avalonia.Base/Media/GlyphRunMetrics.cs
  5. 1 1
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  6. 1 1
      src/Avalonia.Base/Media/TextDecoration.cs
  7. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  8. 1 1
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  9. 18 1
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  10. 1 1
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  11. 1 1
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  12. 1 1
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  13. 1 1
      src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  14. 12 6
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  15. 6 2
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  16. 5 5
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  17. 9 1
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  18. 9 5
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  19. 12 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  20. 4 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  21. 9 3
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  22. 2 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  23. 1 1
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs
  24. 4 0
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  25. 1 1
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  26. 2 1
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  27. 4 4
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  28. 1 3
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  29. 2 1
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
  30. 2 2
      tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
  31. 11 0
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  32. 1 1
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

+ 1 - 1
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);
             }
         }
 

+ 2 - 3
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<IGlyphRunImpl> 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

+ 24 - 31
src/Avalonia.Base/Media/GlyphRun.cs

@@ -13,10 +13,9 @@ namespace Avalonia.Media
     /// </summary>
     public sealed class GlyphRun : IDisposable
     {
-        private IGlyphRunImpl? _glyphRunImpl;
+        private IRef<IGlyphRunImpl>? _platformImpl;
         private double _fontRenderingEmSize;
         private int _biDiLevel;
-        private Point? _baselineOrigin;
         private GlyphRunMetrics? _glyphRunMetrics;
         private ReadOnlyMemory<char> _characters;
         private IReadOnlyList<GlyphInfo> _glyphInfos;
@@ -68,6 +67,13 @@ namespace Avalonia.Media
             _biDiLevel = biDiLevel;
         }
 
+        internal GlyphRun(IRef<IGlyphRunImpl> platformImpl)
+        {
+            _glyphInfos = Array.Empty<GlyphInfo>();
+            GlyphTypeface = Typeface.Default.GlyphTypeface;
+            _platformImpl = platformImpl;
+        }
+
         private static IReadOnlyList<GlyphInfo> CreateGlyphInfos(IReadOnlyList<ushort> glyphIndices,
             double fontRenderingEmSize, IGlyphTypeface glyphTypeface)
         {
@@ -132,7 +138,7 @@ namespace Avalonia.Media
         /// <summary>
         ///     Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
         /// </summary>
-        public Size Size => new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height);
+        public Size Size => PlatformImpl.Item.Size;
 
         /// <summary>
         /// 
@@ -141,13 +147,9 @@ namespace Avalonia.Media
             => _glyphRunMetrics ??= CreateGlyphRunMetrics();
 
         /// <summary>
-        ///     Gets or sets the baseline origin of the<see cref="GlyphRun"/>.
+        ///     Gets the baseline origin of the<see cref="GlyphRun"/>.
         /// </summary>
-        public Point BaselineOrigin
-        {
-            get => _baselineOrigin ??= CalculateBaselineOrigin();
-            set => Set(ref _baselineOrigin, value);
-        }
+        public Point BaselineOrigin => PlatformImpl.Item.BaselineOrigin;
 
         /// <summary>
         ///     Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
@@ -193,8 +195,8 @@ namespace Avalonia.Media
         /// <summary>
         /// The platform implementation of the <see cref="GlyphRun"/>.
         /// </summary>
-        public IGlyphRunImpl GlyphRunImpl
-            => _glyphRunImpl ??= CreateGlyphRunImpl();
+        public IRef<IGlyphRunImpl> PlatformImpl
+            => _platformImpl ??= CreateGlyphRunImpl();
 
         /// <summary>
         /// 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);
         }
 
-        /// <summary>
-        /// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
-        /// </summary>
-        /// <returns>The baseline origin.</returns>
-        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<T>(ref T field, T value)
         {
-            _glyphRunImpl?.Dispose();
+            _platformImpl?.Dispose();
 
-            _glyphRunImpl = null;
+            _platformImpl = null;
 
             _glyphRunMetrics = null;
 
-            _baselineOrigin = null;
-
             field = value;
         }
 
-        private IGlyphRunImpl CreateGlyphRunImpl()
+        private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
         {
             var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
-            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;
         }
     }
 }

+ 4 - 11
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; }
 

+ 1 - 1
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@@ -182,7 +182,7 @@ namespace Avalonia.Media
         /// </summary>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        public void DrawGlyphRun(IImmutableBrush foreground, GlyphRun glyphRun)
+        public void DrawGlyphRun(IImmutableBrush foreground, IRef<IGlyphRunImpl> glyphRun)
         {
             _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
 

+ 1 - 1
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)
                 {

+ 1 - 1
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;
                         }

+ 1 - 1
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@@ -91,7 +91,7 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="foreground">The foreground.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun);
+        void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun);
 
         /// <summary>
         /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

+ 18 - 1
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@@ -10,6 +10,23 @@ namespace Avalonia.Platform
     [Unstable]
     public interface IGlyphRunImpl : IDisposable 
     {
-        IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound);
+
+        /// <summary>
+        ///     Gets the conservative bounding box of the glyph run./>.
+        /// </summary>
+        Size Size { get; }
+
+        /// <summary>
+        ///     Gets the baseline origin of the glyph run./>.
+        /// </summary>
+        Point BaselineOrigin { get; }
+
+        /// <summary>
+        /// Gets the intersections of specified upper and lower limit.
+        /// </summary>
+        /// <param name="lowerLimit">Upper limit.</param>
+        /// <param name="upperLimit">Lower limit.</param>
+        /// <returns></returns>
+        IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit);
     }
 }

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@@ -159,7 +159,7 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
     public object? GetFeature(Type t) => null;
 
     /// <inheritdoc/>
-    public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+    public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
     {
         var next = NextDrawAs<GlyphRunNode>();
 

+ 1 - 1
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<IGlyphRunImpl> glyphRun)
     {
         _impl.DrawGlyphRun(foreground, glyphRun);
     }

+ 1 - 1
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;
         }
     }

+ 1 - 1
src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -206,7 +206,7 @@ namespace Avalonia.Rendering.SceneGraph
         public object? GetFeature(Type t) => null;
 
         /// <inheritdoc/>
-        public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+        public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
         {
             var next = NextDrawAs<GlyphRunNode>();
 

+ 12 - 6
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<IGlyphRunImpl> 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();
         }
 
         /// <summary>
@@ -41,7 +42,7 @@ namespace Avalonia.Rendering.SceneGraph
         /// <summary>
         /// Gets the glyph run to draw.
         /// </summary>
-        public GlyphRun GlyphRun { get; }
+        public IRef<IGlyphRunImpl> GlyphRun { get; }
 
         /// <inheritdoc/>
         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.
         /// </remarks>
-        internal bool Equals(Matrix transform, IBrush foreground, GlyphRun glyphRun)
+        internal bool Equals(Matrix transform, IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
         {
             return transform == Transform &&
                    Equals(foreground, Foreground) &&
-                   Equals(glyphRun, GlyphRun);
+                   Equals(glyphRun.Item, GlyphRun.Item);
         }
 
         /// <inheritdoc/>
         public override bool HitTest(Point p) => Bounds.ContainsExclusive(p);
+
+        public override void Dispose()
+        {
+            GlyphRun?.Dispose();
+        }
     }
 }

+ 6 - 2
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<float> 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<IGlyphRunImpl> glyphRun)
             {
                 
             }

+ 5 - 5
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -492,15 +492,15 @@ namespace Avalonia.Skia
         }
        
         /// <inheritdoc />
-        public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+        public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> 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);
             }
         }
 

+ 9 - 1
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;
         }
 
         /// <summary>
@@ -21,6 +25,10 @@ namespace Avalonia.Skia
         /// </summary>
         public SKTextBlob TextBlob { get; }
 
+        public Size Size { get; }
+
+        public Point BaselineOrigin { get; }
+
         public IReadOnlyList<float> GetIntersections(float upperBound, float lowerBound) => 
             TextBlob.GetIntercepts(lowerBound, upperBound);
 

+ 9 - 5
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);
         }
     }
 }

+ 12 - 2
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

+ 4 - 4
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -390,13 +390,13 @@ namespace Avalonia.Direct2D1.Media
         /// </summary>
         /// <param name="foreground">The foreground.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+        public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> 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);
             }
         }

+ 9 - 3
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();
         }
 

+ 2 - 2
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);

+ 1 - 1
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<IGlyphRunImpl> glyphRun)
         {
         }
 

+ 4 - 0
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()
         {
         }

+ 1 - 1
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@@ -123,7 +123,7 @@ namespace Avalonia.Benchmarks
 
         public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
         {
-            return new MockGlyphRun();
+            return new MockGlyphRun(glyphInfos);
         }
 
         public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)

+ 2 - 1
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<ICursorFactory>(),
-            textShaperImpl: new MockTextShaperImpl());
+            textShaperImpl: new MockTextShaperImpl(),
+            renderInterface: new MockPlatformRenderInterface());
 
         private static IControlTemplate CreateTemplate()
         {

+ 4 - 4
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
                 {

+ 1 - 3
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)

+ 2 - 1
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<ICursorFactory>(),
-            textShaperImpl: new MockTextShaperImpl());
+            textShaperImpl: new MockTextShaperImpl(),
+            renderInterface: new MockPlatformRenderInterface());
 
         private static IControlTemplate CreateTemplate()
         {

+ 2 - 2
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<Rect>(glyphRun.GlyphInfos!.Count);
 

+ 11 - 0
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<GlyphInfo> glyphInfos)
+        {
+            Size = new Size(glyphInfos.Sum(x=> x.GlyphAdvance), 10);
+        }
+
+        public Size Size { get; }
+
+        public Point BaselineOrigin => new Point(0, 8);
+
         public void Dispose()
         {
             

+ 1 - 1
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -149,7 +149,7 @@ namespace Avalonia.UnitTests
 
         public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
         {
-            return Mock.Of<IGlyphRunImpl>();
+            return new MockGlyphRun(glyphInfos);
         }
 
         public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;