Browse Source

Merge remote-tracking branch 'upstream/master'

Benedikt Stebner 3 years ago
parent
commit
68f083e74f

+ 6 - 1
native/Avalonia.Native/src/OSX/AvnView.mm

@@ -439,7 +439,12 @@
 
     if(_parent != nullptr)
     {
-        _lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
+        auto handled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
+        if (key != LeftCtrl && key != RightCtrl) {
+          _lastKeyHandled = handled;
+        } else {
+          _lastKeyHandled = false;
+        }
     }
 }
 

+ 4 - 0
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@@ -55,6 +55,10 @@ namespace RenderDemo.Pages
             formattedText.SetFontStyle(FontStyle.Italic, 28, 28);
 
             context.DrawText(formattedText, new Point(10, 0));
+
+            var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
+
+            context.DrawGeometry(gradient, null, geometry);
         }
     }
 }

+ 0 - 37
src/Avalonia.Base/GeometryCollection.cs

@@ -1,37 +0,0 @@
-using System.Collections;
-using System.Collections.Generic;
-using Avalonia.Animation;
-
-#nullable enable
-
-namespace Avalonia.Media
-{
-    public class GeometryCollection : Animatable, IList<Geometry>, IReadOnlyList<Geometry>
-    {
-        private List<Geometry> _inner;
-
-        public GeometryCollection() => _inner = new List<Geometry>();
-        public GeometryCollection(IEnumerable<Geometry> collection) => _inner = new List<Geometry>(collection);
-        public GeometryCollection(int capacity) => _inner = new List<Geometry>(capacity);
-
-        public Geometry this[int index] 
-        { 
-            get => _inner[index];
-            set => _inner[index] = value; 
-        }
-
-        public int Count => _inner.Count;
-        public bool IsReadOnly => false;
-
-        public void Add(Geometry item) => _inner.Add(item);
-        public void Clear() => _inner.Clear();
-        public bool Contains(Geometry item) => _inner.Contains(item);
-        public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex);
-        public IEnumerator<Geometry> GetEnumerator() => _inner.GetEnumerator();
-        public int IndexOf(Geometry item) => _inner.IndexOf(item);
-        public void Insert(int index, Geometry item) => _inner.Insert(index, item);
-        public bool Remove(Geometry item) => _inner.Remove(item);
-        public void RemoveAt(int index) => _inner.RemoveAt(index);
-        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-    }
-}

+ 18 - 0
src/Avalonia.Base/Media/DrawingCollection.cs

@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using Avalonia.Collections;
+
+namespace Avalonia.Media
+{
+    public sealed class DrawingCollection : AvaloniaList<Drawing>
+    {
+        public DrawingCollection()
+        {
+            ResetBehavior = ResetBehavior.Remove;
+        }
+
+        public DrawingCollection(IEnumerable<Drawing> items) : base(items)
+        {
+            ResetBehavior = ResetBehavior.Remove;
+        }
+    }
+}

+ 418 - 2
src/Avalonia.Base/Media/DrawingGroup.cs

@@ -1,6 +1,10 @@
-using Avalonia.Collections;
+using System;
+using System.Collections.Generic;
+using Avalonia.Media.Imaging;
 using Avalonia.Metadata;
 using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media
 {
@@ -18,6 +22,14 @@ namespace Avalonia.Media
         public static readonly StyledProperty<IBrush> OpacityMaskProperty =
             AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
 
+        public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
+            AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
+                nameof(Children),
+                o => o.Children,
+                (o, v) => o.Children = v);
+
+        private DrawingCollection _children = new DrawingCollection();
+
         public double Opacity
         {
             get => GetValue(OpacityProperty);
@@ -42,8 +54,23 @@ namespace Avalonia.Media
             set => SetValue(OpacityMaskProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets the collection that contains the child geometries.
+        /// </summary>
         [Content]
-        public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
+        public DrawingCollection Children
+        {
+            get => _children;
+            set
+            {
+                SetAndRaise(ChildrenProperty, ref _children, value);
+            }
+        }
+
+        public DrawingContext Open()
+        {
+            return new DrawingContext(new DrawingGroupDrawingContext(this));
+        }
 
         public override void Draw(DrawingContext context)
         {
@@ -75,5 +102,394 @@ namespace Avalonia.Media
 
             return rect;
         }
+
+        private class DrawingGroupDrawingContext : IDrawingContextImpl
+        {
+            private readonly DrawingGroup _drawingGroup;
+            private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
+
+            private Matrix _transform;
+
+            private bool _disposed;
+
+            // Root drawing created by this DrawingContext.
+            //
+            // If there is only a single child of the root DrawingGroup, _rootDrawing
+            // will reference the single child, and the root _currentDrawingGroup
+            // value will be null.  Otherwise, _rootDrawing will reference the
+            // root DrawingGroup, and be the same value as the root _currentDrawingGroup.
+            //
+            // Either way, _rootDrawing always references the root drawing.
+            protected Drawing? _rootDrawing;
+
+            // Current DrawingGroup that new children are added to
+            protected DrawingGroup? _currentDrawingGroup;
+
+            // Previous values of _currentDrawingGroup
+            private Stack<DrawingGroup?>? _previousDrawingGroupStack;
+
+            public DrawingGroupDrawingContext(DrawingGroup drawingGroup)
+            {
+                _drawingGroup = drawingGroup;
+            }
+
+            public Matrix Transform
+            {
+                get => _transform;
+                set
+                {
+                    _transform = value;
+                    PushTransform(new MatrixTransform(value));
+                }
+            }
+
+            public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
+            {
+                if ((brush == null) && (pen == null))
+                {
+                    return;
+                }
+
+                // Instantiate the geometry
+                var geometry = _platformRenderInterface.CreateEllipseGeometry(rect);
+
+                // Add Drawing to the Drawing graph
+                AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
+            }
+
+            public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
+            {
+                if (((brush == null) && (pen == null)) || (geometry == null))
+                {
+                    return;
+                }
+
+                AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
+            }
+
+            public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+            {
+                if (foreground == null || glyphRun == null)
+                {
+                    return;
+                }
+
+                // Add a GlyphRunDrawing to the Drawing graph
+                GlyphRunDrawing glyphRunDrawing = new GlyphRunDrawing
+                {
+                    Foreground = foreground,
+                    GlyphRun = glyphRun,
+                };
+
+                // Add Drawing to the Drawing graph
+                AddDrawing(glyphRunDrawing);
+            }
+
+            public void DrawLine(IPen pen, Point p1, Point p2)
+            {
+                if (pen == null)
+                {
+                    return;
+                }
+
+                // Instantiate the geometry
+                var geometry = _platformRenderInterface.CreateLineGeometry(p1, p2);
+
+                // Add Drawing to the Drawing graph
+                AddNewGeometryDrawing(null, pen, new PlatformGeometry(geometry));
+            }
+
+            public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
+            {
+                if ((brush == null) && (pen == null))
+                {
+                    return;
+                }
+
+                // Instantiate the geometry
+                var geometry = _platformRenderInterface.CreateRectangleGeometry(rect.Rect);
+
+                // Add Drawing to the Drawing graph
+                AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
+            }
+
+            public void Clear(Color color)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IDrawingContextLayerImpl CreateLayer(Size size)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Custom(ICustomDrawOperation custom)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PopBitmapBlendMode()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PopClip()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PopGeometryClip()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PopOpacity()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PopOpacityMask()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushClip(Rect clip)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushClip(RoundedRect clip)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushGeometryClip(IGeometryImpl clip)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushOpacity(double opacity)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PushOpacityMask(IBrush mask, Rect bounds)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Dispose()
+            {
+                // Dispose may be called multiple times without throwing
+                // an exception.
+                if (!_disposed)
+                {
+                    // Match any outstanding Push calls with a Pop
+                    if (_previousDrawingGroupStack != null)
+                    {
+                        int stackCount = _previousDrawingGroupStack.Count;
+                        for (int i = 0; i < stackCount; i++)
+                        {
+                            Pop();
+                        }
+                    }
+
+                    // Call CloseCore with the root DrawingGroup's children
+                    DrawingCollection rootChildren;
+
+                    if (_currentDrawingGroup != null)
+                    {
+                        // If we created a root DrawingGroup because multiple elements
+                        // exist at the root level, provide it's Children collection
+                        // directly.
+                        rootChildren = _currentDrawingGroup.Children;
+                    }
+                    else
+                    {
+                        // Create a new DrawingCollection if we didn't create a
+                        // root DrawingGroup because the root level only contained
+                        // a single child.
+                        //
+                        // This collection is needed by DrawingGroup.Open because
+                        // Open always replaces it's Children collection.  It isn't
+                        // strictly needed for Append, but always using a collection
+                        // simplifies the TransactionalAppend implementation (i.e.,
+                        // a seperate implemention isn't needed for a single element)
+                        rootChildren = new DrawingCollection();
+
+                        //
+                        // We may need to opt-out of inheritance through the new Freezable.
+                        // This is controlled by this.CanBeInheritanceContext.
+                        //
+                        if (_rootDrawing != null)
+                        {
+                            rootChildren.Add(_rootDrawing);
+                        }
+                    }
+
+                    // Inform our derived classes that Close was called
+                    _drawingGroup.Children = rootChildren;
+
+                    _disposed = true;
+                }
+            }
+
+            /// <summary>
+            /// Pop
+            /// </summary>
+            private void Pop()
+            {
+                // Verify that Pop hasn't been called too many times
+                if ((_previousDrawingGroupStack == null) || (_previousDrawingGroupStack.Count == 0))
+                {
+                    throw new InvalidOperationException("DrawingGroupStack count missmatch.");
+                }
+
+                // Restore the previous value of the current drawing group
+                _currentDrawingGroup = _previousDrawingGroupStack.Pop();
+            }
+
+            /// <summary>
+            ///     PushTransform -
+            ///     Push a Transform which will apply to all drawing operations until the corresponding
+            ///     Pop.
+            /// </summary>
+            /// <param name="transform"> The Transform to push. </param>
+            private void PushTransform(Transform transform)
+            {
+                // Instantiate a new drawing group and set it as the _currentDrawingGroup
+                var drawingGroup = PushNewDrawingGroup();
+
+                // Set the transform on the new DrawingGroup
+                drawingGroup.Transform = transform;
+            }
+
+            /// <summary>
+            /// Creates a new DrawingGroup for a Push* call by setting the
+            /// _currentDrawingGroup to a newly instantiated DrawingGroup,
+            /// and saving the previous _currentDrawingGroup value on the
+            /// _previousDrawingGroupStack.
+            /// </summary>
+            private DrawingGroup PushNewDrawingGroup()
+            {
+                // Instantiate a new drawing group
+                DrawingGroup drawingGroup = new DrawingGroup();
+
+                // Add it to the drawing graph, like any other Drawing
+                AddDrawing(drawingGroup);
+
+                // Lazily allocate the stack when it is needed because many uses
+                // of DrawingDrawingContext will have a depth of one.
+                if (null == _previousDrawingGroupStack)
+                {
+                    _previousDrawingGroupStack = new Stack<DrawingGroup?>(2);
+                }
+
+                // Save the previous _currentDrawingGroup value.
+                //
+                // If this is the first call, the value of _currentDrawingGroup
+                // will be null because AddDrawing doesn't create a _currentDrawingGroup
+                // for the first drawing.  Having null on the stack is valid, and simply
+                // denotes that this new DrawingGroup is the first child in the root
+                // DrawingGroup.  It is also possible for the first value on the stack
+                // to be non-null, which means that the root DrawingGroup has other
+                // children.
+                _previousDrawingGroupStack.Push(_currentDrawingGroup);
+
+                // Set this drawing group as the current one so that subsequent drawing's
+                // are added as it's children until Pop is called.
+                _currentDrawingGroup = drawingGroup;
+
+                return drawingGroup;
+            }
+
+            /// <summary>
+            /// Contains the functionality common to GeometryDrawing operations of
+            /// instantiating the GeometryDrawing, setting it's Freezable state,
+            /// and Adding it to the Drawing Graph.
+            /// </summary>
+            private void AddNewGeometryDrawing(IBrush? brush, IPen? pen, Geometry? geometry)
+            {
+                if (geometry == null)
+                {
+                    throw new ArgumentNullException(nameof(geometry));
+                }
+
+                // Instantiate the GeometryDrawing
+                GeometryDrawing geometryDrawing = new GeometryDrawing
+                {
+                    // We may need to opt-out of inheritance through the new Freezable.
+                    // This is controlled by this.CanBeInheritanceContext.
+                    Brush = brush,
+                    Pen = pen,
+                    Geometry = geometry
+                };
+
+                // Add it to the drawing graph
+                AddDrawing(geometryDrawing);
+            }
+
+            /// <summary>
+            /// Adds a new Drawing to the DrawingGraph.
+            ///
+            /// This method avoids creating a DrawingGroup for the common case
+            /// where only a single child exists in the root DrawingGroup.
+            /// </summary>
+            private void AddDrawing(Drawing newDrawing)
+            {
+                if (newDrawing == null)
+                {
+                    throw new ArgumentNullException(nameof(newDrawing));
+                }
+
+                if (_rootDrawing == null)
+                {
+                    // When a DrawingGroup is set, it should be made the root if
+                    // a root drawing didnt exist.
+                    Contract.Requires<NotSupportedException>(_currentDrawingGroup == null);
+
+                    // If this is the first Drawing being added, avoid creating a DrawingGroup
+                    // and set this drawing as the root drawing.  This optimizes the common
+                    // case where only a single child exists in the root DrawingGroup.
+                    _rootDrawing = newDrawing;
+                }
+                else if (_currentDrawingGroup == null)
+                {
+                    // When the second drawing is added at the root level, set a
+                    // DrawingGroup as the root and add both drawings to it.
+
+                    // Instantiate the DrawingGroup
+                    _currentDrawingGroup = new DrawingGroup();
+
+                    // Add both Children
+                    _currentDrawingGroup.Children.Add(_rootDrawing);
+                    _currentDrawingGroup.Children.Add(newDrawing);
+
+                    // Set the new DrawingGroup as the current
+                    _rootDrawing = _currentDrawingGroup;
+                }
+                else
+                {
+                    // If there already is a current drawing group, then simply add
+                    // the new drawing too it.
+                    _currentDrawingGroup.Children.Add(newDrawing);
+                }
+            }
+        }
     }
 }

+ 128 - 1
src/Avalonia.Base/Media/FormattedText.cs

@@ -1223,7 +1223,7 @@ namespace Avalonia.Media
         public double OverhangTrailing
         {
             get
-        {
+            {
                 return BlackBoxMetrics.OverhangTrailing;
             }
         }
@@ -1252,6 +1252,46 @@ namespace Avalonia.Media
             }
         }
 
+        /// <summary>
+        /// Obtains geometry for the text, including underlines and strikethroughs. 
+        /// </summary>
+        /// <param name="origin">The left top origin of the resulting geometry.</param>
+        /// <returns>The geometry returned contains the combined geometry
+        /// of all of the glyphs, underlines and strikeThroughs that represent the formatted text.
+        /// Overlapping contours are merged by performing a Boolean union operation.</returns>
+        public Geometry? BuildGeometry(Point origin)
+        {
+            GeometryGroup? accumulatedGeometry = null;
+            var lineOrigin = origin;
+
+            DrawingGroup drawing = new DrawingGroup();
+
+            using (var ctx = drawing.Open())
+            {
+                using (var enumerator = GetEnumerator())
+                {
+                    while (enumerator.MoveNext())
+                    {
+                        var currentLine = enumerator.Current;
+
+                        if (currentLine != null)
+                        {
+                            currentLine.Draw(ctx, lineOrigin);
+
+                            AdvanceLineOrigin(ref lineOrigin, currentLine);
+                        }
+                    }
+                }
+            }
+
+            Transform? transform = new TranslateTransform(origin.X, origin.Y);
+
+            //  recursively go down the DrawingGroup to build up the geometry
+            CombineGeometryRecursive(drawing, ref transform, ref accumulatedGeometry);
+
+            return accumulatedGeometry;
+        }
+
         /// <summary>
         /// Draws the text object
         /// </summary>
@@ -1284,6 +1324,93 @@ namespace Avalonia.Media
             }
         }
 
+        private void CombineGeometryRecursive(Drawing drawing, ref Transform? transform, ref GeometryGroup? accumulatedGeometry)
+        {
+            if (drawing is DrawingGroup group)
+            {
+                transform = group.Transform;
+
+                if (group.Children is DrawingCollection children)
+                {
+                    // recursively go down for DrawingGroup
+                    foreach (var child in children)
+                    {
+                        CombineGeometryRecursive(child, ref transform, ref accumulatedGeometry);
+                    }
+                }
+            }
+            else
+            {
+                if (drawing is GlyphRunDrawing glyphRunDrawing)
+                {
+                    // process glyph run
+                    var glyphRun = glyphRunDrawing.GlyphRun;
+
+                    if (glyphRun != null)
+                    {
+                        var glyphRunGeometry = glyphRun.BuildGeometry();
+
+                        glyphRunGeometry.Transform = transform;
+
+                        if (accumulatedGeometry == null)
+                        {
+                            accumulatedGeometry = new GeometryGroup
+                            {
+                                FillRule = FillRule.NonZero
+                            };
+                        }
+
+                        accumulatedGeometry.Children.Add(glyphRunGeometry);
+                    }
+                }
+                else
+                {
+                    if (drawing is GeometryDrawing geometryDrawing)
+                    {
+                        // process geometry (i.e. TextDecoration on the line)
+                        var geometry = geometryDrawing.Geometry;
+
+                        if (geometry != null)
+                        {
+                            geometry.Transform = transform;
+
+                            if (geometry is LineGeometry lineGeometry)
+                            {
+                                // For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no 
+                                // bounding area. So this line won't show up. Work aroud it by increase the Bounding rect 
+                                // to be Pen's thickness                        
+
+                                var bounds = lineGeometry.Bounds;
+
+                                if (bounds.Height == 0)
+                                {
+                                    bounds = bounds.WithHeight(geometryDrawing.Pen?.Thickness ?? 0);
+                                }
+                                else if (bounds.Width == 0)
+                                {
+                                    bounds = bounds.WithWidth(geometryDrawing.Pen?.Thickness ?? 0);
+                                }
+
+                                // convert the line geometry into a rectangle geometry
+                                // we lost line cap info here
+                                geometry = new RectangleGeometry(bounds);
+                            }
+
+                            if (accumulatedGeometry == null)
+                            {
+                                accumulatedGeometry = new GeometryGroup
+                                {
+                                    FillRule = FillRule.NonZero
+                                };
+                            }
+
+                            accumulatedGeometry.Children.Add(geometry);
+                        }
+                    }
+                }
+            }
+        }
+
         private CachedMetrics DrawAndCalculateMetrics(DrawingContext? drawingContext, Point drawingOffset, bool getBlackBoxMetrics)
         {
             var metrics = new CachedMetrics();

+ 45 - 0
src/Avalonia.Base/Media/GeometryCollection.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Collections;
+
+#nullable enable
+
+namespace Avalonia.Media
+{
+    public sealed class GeometryCollection : AvaloniaList<Geometry> 
+    {
+        public GeometryCollection()
+        {
+            ResetBehavior = ResetBehavior.Remove;
+
+            this.ForEachItem(
+               x =>
+               {
+                   Parent?.Invalidate();
+               },
+               x =>
+               {
+                   Parent?.Invalidate();
+               },
+               () => throw new NotSupportedException());
+        }
+
+        public GeometryCollection(IEnumerable<Geometry> items) : base(items)
+        {
+            ResetBehavior = ResetBehavior.Remove;
+
+            this.ForEachItem(
+               x =>
+               {
+                   Parent?.Invalidate();
+               },
+               x =>
+               {
+                   Parent?.Invalidate();
+               },
+               () => throw new NotSupportedException());
+        }
+
+        public GeometryGroup? Parent { get; set; }
+    }
+}

+ 6 - 6
src/Avalonia.Base/Media/GeometryDrawing.cs

@@ -21,14 +21,14 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Brush"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBrush> BrushProperty =
-            AvaloniaProperty.Register<GeometryDrawing, IBrush>(nameof(Brush), Brushes.Transparent);
+        public static readonly StyledProperty<IBrush?> BrushProperty =
+            AvaloniaProperty.Register<GeometryDrawing, IBrush?>(nameof(Brush), Brushes.Transparent);
 
         /// <summary>
         /// Defines the <see cref="Pen"/> property.
         /// </summary>
-        public static readonly StyledProperty<Pen> PenProperty =
-            AvaloniaProperty.Register<GeometryDrawing, Pen>(nameof(Pen));
+        public static readonly StyledProperty<Pen?> PenProperty =
+            AvaloniaProperty.Register<GeometryDrawing, Pen?>(nameof(Pen));
 
         /// <summary>
         /// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.
@@ -43,7 +43,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets or sets the <see cref="Avalonia.Media.IBrush"/> used to fill the interior of the shape described by this <see cref="GeometryDrawing"/>.
         /// </summary>
-        public IBrush Brush
+        public IBrush? Brush
         {
             get => GetValue(BrushProperty);
             set => SetValue(BrushProperty, value);
@@ -52,7 +52,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets or sets the <see cref="Avalonia.Media.IPen"/> used to stroke this <see cref="GeometryDrawing"/>.
         /// </summary>
-        public IPen Pen
+        public IPen? Pen
         {
             get => GetValue(PenProperty);
             set => SetValue(PenProperty, value);

+ 41 - 17
src/Avalonia.Base/GeometryGroup.cs → src/Avalonia.Base/Media/GeometryGroup.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.Metadata;
+using Avalonia.Metadata;
 using Avalonia.Platform;
 
 #nullable enable
@@ -13,29 +10,36 @@ namespace Avalonia.Media
     /// </summary>
     public class GeometryGroup : Geometry
     {
-        public static readonly DirectProperty<GeometryGroup, GeometryCollection?> ChildrenProperty =
-            AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection?> (
+        public static readonly DirectProperty<GeometryGroup, GeometryCollection> ChildrenProperty =
+            AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection> (
                 nameof(Children),
                 o => o.Children,
-                (o, v) => o.Children = v);
+                (o, v)=> o.Children = v);
 
         public static readonly StyledProperty<FillRule> FillRuleProperty =
             AvaloniaProperty.Register<GeometryGroup, FillRule>(nameof(FillRule));
 
-        private GeometryCollection? _children;
-        private bool _childrenSet;
+        private GeometryCollection _children;
+
+        public GeometryGroup()
+        {
+            _children = new GeometryCollection
+            {
+                Parent = this
+            };
+        }
 
         /// <summary>
         /// Gets or sets the collection that contains the child geometries.
         /// </summary>
         [Content]
-        public GeometryCollection? Children
+        public GeometryCollection Children
         {
-            get => _children ??= (!_childrenSet ? new GeometryCollection() : null);
+            get => _children;
             set
             {
-                SetAndRaise(ChildrenProperty, ref _children, value);
-                _childrenSet = true;
+                OnChildrenChanged(_children, value);
+                SetAndRaise(ChildrenProperty, ref _children, value);             
             }
         }
 
@@ -52,16 +56,28 @@ namespace Avalonia.Media
         public override Geometry Clone()
         {
             var result = new GeometryGroup { FillRule = FillRule, Transform = Transform };
-            if (_children?.Count > 0)
+
+            if (_children.Count > 0)
+            {
                 result.Children = new GeometryCollection(_children);
+            }
+              
             return result;
         }
 
+        protected void OnChildrenChanged(GeometryCollection oldChildren, GeometryCollection newChildren)
+        {
+            oldChildren.Parent = null;
+
+            newChildren.Parent = this;
+        }
+
         protected override IGeometryImpl? CreateDefiningGeometry()
         {
-            if (_children?.Count > 0)
+            if (_children.Count > 0)
             {
                 var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
+
                 return factory.CreateGeometryGroup(FillRule, _children);
             }
 
@@ -72,10 +88,18 @@ namespace Avalonia.Media
         {
             base.OnPropertyChanged(change);
 
-            if (change.Property == ChildrenProperty || change.Property == FillRuleProperty)
+            switch (change.Property.Name)
             {
-                InvalidateGeometry();
+                case nameof(FillRule):                   
+                case nameof(Children):
+                    InvalidateGeometry();
+                    break;
             }
         }
+
+        internal void Invalidate()
+        {
+            InvalidateGeometry();
+        }
     }
 }

+ 2 - 8
src/Avalonia.Base/Media/GlyphRun.cs

@@ -202,15 +202,9 @@ namespace Avalonia.Media
         {
             var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
-            var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this, out var scale);
+            var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this);
 
-            var geometry = new PlatformGeometry(geometryImpl);
-
-            var transform = new MatrixTransform(Matrix.CreateTranslation(geometry.Bounds.Left, -geometry.Bounds.Top) * scale);
-
-            geometry.Transform = transform;
-
-            return geometry;
+            return new PlatformGeometry(geometryImpl);
         }
 
         /// <summary>

+ 1 - 2
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@@ -62,9 +62,8 @@ namespace Avalonia.Platform
         /// Created a geometry implementation for the glyph run.
         /// </summary>
         /// <param name="glyphRun">The glyph run to build a geometry from.</param>
-        /// <param name="scale">The scaling of the produces geometry.</param>
         /// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
-        IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale);
+        IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun);
 
         /// <summary>
         /// Creates a renderer.

+ 54 - 41
src/Avalonia.Base/Styling/Styles.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Styling
         IStyle,
         IResourceProvider
     {
-        private readonly AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
+        private readonly AvaloniaList<IStyle> _styles = new();
         private IResourceHost? _owner;
         private IResourceDictionary? _resources;
         private StyleCache? _cache;
@@ -62,16 +62,18 @@ namespace Avalonia.Styling
             {
                 value = value ?? throw new ArgumentNullException(nameof(Resources));
 
-                if (Owner is object)
+                var currentOwner = Owner;
+
+                if (currentOwner is not null)
                 {
-                    _resources?.RemoveOwner(Owner);
+                    _resources?.RemoveOwner(currentOwner);
                 }
 
                 _resources = value;
 
-                if (Owner is object)
+                if (currentOwner is not null)
                 {
-                    _resources.AddOwner(Owner);
+                    _resources.AddOwner(currentOwner);
                 }
             }
         }
@@ -89,7 +91,7 @@ namespace Avalonia.Styling
 
                 foreach (var i in this)
                 {
-                    if (i is IResourceProvider p && p.HasResources)
+                    if (i is IResourceProvider { HasResources: true })
                     {
                         return true;
                     }
@@ -190,7 +192,7 @@ namespace Avalonia.Styling
         {
             owner = owner ?? throw new ArgumentNullException(nameof(owner));
 
-            if (Owner != null)
+            if (Owner is not null)
             {
                 throw new InvalidOperationException("The Styles already has a owner.");
             }
@@ -227,70 +229,81 @@ namespace Avalonia.Styling
             }
         }
 
-        private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        private static IReadOnlyList<T> ToReadOnlyList<T>(ICollection list)
         {
-            static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
+            if (list is IReadOnlyList<T> readOnlyList)
             {
-                if (list is IReadOnlyList<T>)
-                {
-                    return (IReadOnlyList<T>)list;
-                }
-                else
-                {
-                    var result = new T[list.Count];
-                    list.CopyTo(result, 0);
-                    return result;
-                }
+                return readOnlyList;
             }
 
-            void Add(IList items)
+            var result = new T[list.Count];
+            list.CopyTo(result, 0);
+            return result;
+        }
+
+        private static void InternalAdd(IList items, IResourceHost? owner, ref StyleCache? cache)
+        {
+            if (owner is not null)
             {
                 for (var i = 0; i < items.Count; ++i)
                 {
-                    var style = (IStyle)items[i]!;
-
-                    if (Owner is object && style is IResourceProvider resourceProvider)
+                    if (items[i] is IResourceProvider provider)
                     {
-                        resourceProvider.AddOwner(Owner);
+                        provider.AddOwner(owner);
                     }
-
-                    _cache = null;
                 }
 
-                (Owner as IStyleHost)?.StylesAdded(ToReadOnlyList<IStyle>(items));
+                (owner as IStyleHost)?.StylesAdded(ToReadOnlyList<IStyle>(items));
+            }
+
+            if (items.Count > 0)
+            {
+                cache = null;
             }
+        }
 
-            void Remove(IList items)
+        private static void InternalRemove(IList items, IResourceHost? owner, ref StyleCache? cache)
+        {
+            if (owner is not null)
             {
                 for (var i = 0; i < items.Count; ++i)
                 {
-                    var style = (IStyle)items[i]!;
-
-                    if (Owner is object && style is IResourceProvider resourceProvider)
+                    if (items[i] is IResourceProvider provider)
                     {
-                        resourceProvider.RemoveOwner(Owner);
+                        provider.RemoveOwner(owner);
                     }
-
-                    _cache = null;
                 }
 
-                (Owner as IStyleHost)?.StylesRemoved(ToReadOnlyList<IStyle>(items));
+                (owner as IStyleHost)?.StylesRemoved(ToReadOnlyList<IStyle>(items));
+            }
+
+            if (items.Count > 0)
+            {
+                cache = null;
+            }
+        }
+
+        private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Reset)
+            {
+                throw new InvalidOperationException("Reset should not be called on Styles.");
             }
 
+            var currentOwner = Owner;
+
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
-                    Add(e.NewItems!);
+                    InternalAdd(e.NewItems!, currentOwner, ref _cache);
                     break;
                 case NotifyCollectionChangedAction.Remove:
-                    Remove(e.OldItems!);
+                    InternalRemove(e.OldItems!, currentOwner, ref _cache);
                     break;
                 case NotifyCollectionChangedAction.Replace:
-                    Remove(e.OldItems!);
-                    Add(e.NewItems!);
+                    InternalRemove(e.OldItems!, currentOwner, ref _cache);
+                    InternalAdd(e.NewItems!, currentOwner, ref _cache);
                     break;
-                case NotifyCollectionChangedAction.Reset:
-                    throw new InvalidOperationException("Reset should not be called on Styles.");
             }
 
             CollectionChanged?.Invoke(this, e);

+ 1 - 3
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -114,10 +114,8 @@ namespace Avalonia.Headless
             return new HeadlessGlyphRunStub();
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
-            scale = Matrix.Identity;
-
             return new HeadlessGeometryStub(new Rect(glyphRun.Size));
         }
 

+ 17 - 9
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -62,7 +62,7 @@ namespace Avalonia.Skia
             return new CombinedGeometryImpl(combineMode, g1, g2);
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
             if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
             {
@@ -79,21 +79,29 @@ namespace Avalonia.Skia
             };
 
             SKPath path = new SKPath();
-            var matrix = SKMatrix.Identity;
 
-            var currentX = 0f;
+            var (currentX, currentY) = glyphRun.BaselineOrigin;
 
-            foreach (var glyph in glyphRun.GlyphIndices)
+            for (var i = 0; i < glyphRun.GlyphIndices.Count; i++)
             {
-                var p = skFont.GetGlyphPath(glyph);
+                var glyph = glyphRun.GlyphIndices[i];
+                var glyphPath = skFont.GetGlyphPath(glyph);
 
-                path.AddPath(p, currentX, 0);
+                if (!glyphPath.IsEmpty)
+                {
+                    path.AddPath(glyphPath, (float)currentX, (float)currentY);
+                }
 
-                currentX += p.Bounds.Right;
+                if (glyphRun.GlyphAdvances != null)
+                {
+                    currentX += glyphRun.GlyphAdvances[i];
+                }
+                else
+                {
+                    currentX += glyphPath.Bounds.Right;
+                }
             }
 
-            scale = Matrix.CreateScale(matrix.ScaleX, matrix.ScaleY);
-
             return new StreamGeometryImpl(path);
         }
 

+ 18 - 4
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -13,6 +13,7 @@ using Avalonia.Media.Imaging;
 using SharpDX.DirectWrite;
 using GlyphRun = Avalonia.Media.GlyphRun;
 using TextAlignment = Avalonia.Media.TextAlignment;
+using SharpDX.Mathematics.Interop;
 
 namespace Avalonia
 {
@@ -159,7 +160,7 @@ namespace Avalonia.Direct2D1
         public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => new GeometryGroupImpl(fillRule, children);
         public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
             if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
             {
@@ -182,10 +183,23 @@ namespace Avalonia.Direct2D1
                 sink.Close();
             }
 
-            scale = Matrix.Identity;
+            var (baselineOriginX, baselineOriginY) = glyphRun.BaselineOrigin;
 
-            return new StreamGeometryImpl(pathGeometry);
-        }      
+            var transformedGeometry = new SharpDX.Direct2D1.TransformedGeometry(
+                Direct2D1Factory,
+                pathGeometry,
+                new RawMatrix3x2(1.0f, 0.0f, 0.0f, 1.0f, (float)baselineOriginX, (float)baselineOriginY));
+
+            return new TransformedGeometryWrapper(transformedGeometry);
+        }
+
+        private class TransformedGeometryWrapper : GeometryImpl
+        {
+            public TransformedGeometryWrapper(SharpDX.Direct2D1.TransformedGeometry geometry) : base(geometry)
+            {
+
+            }
+        }
 
         /// <inheritdoc />
         public IBitmapImpl LoadBitmap(string fileName)

+ 11 - 3
tests/Avalonia.Base.UnitTests/Media/GeometryGroupTests.cs

@@ -14,13 +14,21 @@ namespace Avalonia.Visuals.UnitTests.Media
         }
 
         [Fact]
-        public void Children_Can_Be_Set_To_Null()
+        public void Children_Change_Should_Raise_Changed()
         {
             var target = new GeometryGroup();
 
-            target.Children = null;
+            var children = new GeometryCollection();
 
-            Assert.Null(target.Children);
+            target.Children = children;
+
+            var isCalled = false;
+
+            target.Changed += (s, e) => isCalled = true;
+
+            children.Add(new StreamGeometry());
+
+            Assert.True(isCalled);
         }
     }
 }

+ 1 - 1
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@@ -121,7 +121,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
             throw new NotImplementedException();
         }

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

@@ -117,7 +117,7 @@ namespace Avalonia.Benchmarks
             return new NullGlyphRun();
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
             throw new NotImplementedException();
         }

+ 16 - 19
tests/Avalonia.RenderTests/Media/GlyphRunTests.cs

@@ -23,26 +23,28 @@ namespace Avalonia.Direct2D1.RenderTests.Media
         [Fact]
         public async Task Should_Render_GlyphRun_Geometry()
         {
-            Decorator target = new Decorator
+            var control = new GlyphRunGeometryControl
             {
-                Padding = new Thickness(8),
-                Width = 200,
-                Height = 100,
-                Child = new GlyphRunGeometryControl
+                [TextElement.ForegroundProperty] = new LinearGradientBrush
                 {
-                    [TextElement.ForegroundProperty] = new LinearGradientBrush
-                    {
-                        StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
-                        EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
-                        GradientStops =
+                    StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+                    EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+                    GradientStops =
                         {
                             new GradientStop { Color = Colors.Red, Offset = 0 },
                             new GradientStop { Color = Colors.Blue, Offset = 1 }
                         }
-                    }
                 }
             };
 
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 190,
+                Height = 120,
+                Child = control
+            };
+
             await RenderToFile(target);
 
             CompareImages();
@@ -50,8 +52,6 @@ namespace Avalonia.Direct2D1.RenderTests.Media
 
         public class GlyphRunGeometryControl : Control
         {
-            private readonly Geometry _geometry;
-
             public GlyphRunGeometryControl()
             {
                 var glyphTypeface = new Typeface(TestFontFamily).GlyphTypeface;
@@ -62,19 +62,16 @@ namespace Avalonia.Direct2D1.RenderTests.Media
 
                 var glyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices);
 
-                _geometry = glyphRun.BuildGeometry();
+                Geometry = glyphRun.BuildGeometry();
             }
 
-            protected override Size MeasureOverride(Size availableSize)
-            {
-                return _geometry.Bounds.Size;
-            }
+           public Geometry Geometry { get; }
 
             public override void Render(DrawingContext context)
             {
                 var foreground = TextElement.GetForeground(this);
 
-                context.DrawGeometry(foreground, null, _geometry);
+                context.DrawGeometry(foreground, null, Geometry);
             }
         }
     }

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

@@ -122,10 +122,8 @@ namespace Avalonia.UnitTests
             return Mock.Of<IGlyphRunImpl>();
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
-            scale = Matrix.Identity;
-
             return Mock.Of<IGeometryImpl>();
         }
 

BIN
tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png


BIN
tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png