Browse Source

Merge branch 'master' into fixFDPopupBug

Max Katz 2 years ago
parent
commit
ad8152ddc4

+ 23 - 12
samples/ControlCatalog/Pages/GesturePage.cs

@@ -6,6 +6,7 @@ using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.Markup.Xaml;
 using Avalonia.Rendering.Composition;
+using Avalonia.Utilities;
 
 namespace ControlCatalog.Pages
 {
@@ -53,6 +54,7 @@ namespace ControlCatalog.Pages
                 {
                     _currentScale = 1;
                     compositionVisual.Scale = new Vector3(1,1,1);
+                    compositionVisual.Offset = default;
                     image.InvalidateMeasure();
                 }
             };
@@ -100,13 +102,19 @@ namespace ControlCatalog.Pages
             {
                 InitComposition(control!);
 
-                isZooming = true;
-
                 if(compositionVisual != null)
                 {
                     var scale = _currentScale * (float)e.Scale;
 
+                    if (scale <= 1)
+                    {
+                        scale = 1;
+                        compositionVisual.Offset = default;
+                    }
+
                     compositionVisual.Scale = new(scale, scale, 1);
+
+                    e.Handled = true;
                 }
             });
 
@@ -114,8 +122,6 @@ namespace ControlCatalog.Pages
             {
                 InitComposition(control!);
 
-                isZooming = false;
-
                 if (compositionVisual != null)
                 {
                     _currentScale = compositionVisual.Scale.X;
@@ -126,11 +132,19 @@ namespace ControlCatalog.Pages
             {
                 InitComposition(control!);
 
-                if (compositionVisual != null && !isZooming)
+                if (compositionVisual != null && _currentScale != 1)
                 {
-                    currentOffset -= new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
+                    currentOffset += new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
+
+                    var currentSize = control.Bounds.Size * _currentScale;
+
+                    currentOffset = new Vector3((float)MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
+                        (float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
+                        0);
 
-                    compositionVisual.Offset = currentOffset;
+                    compositionVisual.Offset = currentOffset * -1;
+
+                    e.Handled = true;
                 }
             });
         }
@@ -173,6 +187,8 @@ namespace ControlCatalog.Pages
                 if (ballCompositionVisual != null)
                 {
                     ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
+
+                    e.Handled = true;
                 }
             });
 
@@ -187,11 +203,6 @@ namespace ControlCatalog.Pages
 
             void InitComposition(Control control)
             {
-                if (ballCompositionVisual != null)
-                {
-                    return;
-                }
-
                 ballCompositionVisual = ElementComposition.GetElementVisual(ball);
 
                 if (ballCompositionVisual != null)

+ 4 - 1
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@@ -57,7 +57,10 @@ namespace Avalonia.Input
 
                     var scale = distance / _initialDistance;
 
-                    _target?.RaiseEvent(new PinchEventArgs(scale, _origin));
+                    var pinchEventArgs = new PinchEventArgs(scale, _origin);
+                    _target?.RaiseEvent(pinchEventArgs);
+
+                    e.Handled = pinchEventArgs.Handled;
                 }
             }
         }

+ 5 - 1
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using Avalonia.Input.GestureRecognizers;
 
 namespace Avalonia.Input
@@ -88,7 +89,10 @@ namespace Avalonia.Input
                 }
 
                 _pullInProgress = true;
-                _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection));
+                var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
+                _target?.RaiseEvent(pullEventArgs);
+
+                e.Handled = pullEventArgs.Handled;
             }
         }
 

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

@@ -11,7 +11,7 @@ namespace Avalonia.Media
     /// Describes how an area is painted.
     /// </summary>
     [TypeConverter(typeof(BrushConverter))]
-    public abstract class Brush : Animatable
+    public abstract class Brush : Animatable, IBrush
     {
         /// <summary>
         /// Defines the <see cref="Opacity"/> property.

+ 28 - 9
src/Avalonia.Base/Media/GlyphRun.cs

@@ -13,14 +13,22 @@ namespace Avalonia.Media
     /// </summary>
     public sealed class GlyphRun : IDisposable
     {
+        private readonly static IPlatformRenderInterface s_renderInterface;
+
         private IRef<IGlyphRunImpl>? _platformImpl;
         private double _fontRenderingEmSize;
         private int _biDiLevel;
         private GlyphRunMetrics? _glyphRunMetrics;
         private ReadOnlyMemory<char> _characters;
         private IReadOnlyList<GlyphInfo> _glyphInfos;
+        private Point? _baselineOrigin;
         private bool _hasOneCharPerCluster; // if true, character index and cluster are similar
 
+        static GlyphRun()
+        {
+            s_renderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
         /// </summary>
@@ -28,15 +36,17 @@ namespace Avalonia.Media
         /// <param name="fontRenderingEmSize">The rendering em size.</param>
         /// <param name="characters">The characters.</param>
         /// <param name="glyphIndices">The glyph indices.</param>
+        /// <param name="baselineOrigin">The baseline origin of the run.</param>
         /// <param name="biDiLevel">The bidi level.</param>
         public GlyphRun(
             IGlyphTypeface glyphTypeface,
             double fontRenderingEmSize,
             ReadOnlyMemory<char> characters,
             IReadOnlyList<ushort> glyphIndices,
+            Point? baselineOrigin = null,
             int biDiLevel = 0)
             : this(glyphTypeface, fontRenderingEmSize, characters,
-                CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), biDiLevel)
+                CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), baselineOrigin, biDiLevel)
         {
             _hasOneCharPerCluster = true;
         }
@@ -48,12 +58,14 @@ namespace Avalonia.Media
         /// <param name="fontRenderingEmSize">The rendering em size.</param>
         /// <param name="characters">The characters.</param>
         /// <param name="glyphInfos">The list of glyphs used.</param>
+        /// <param name="baselineOrigin">The baseline origin of the run.</param>
         /// <param name="biDiLevel">The bidi level.</param>
         public GlyphRun(
             IGlyphTypeface glyphTypeface,
             double fontRenderingEmSize,
             ReadOnlyMemory<char> characters,
             IReadOnlyList<GlyphInfo> glyphInfos,
+            Point? baselineOrigin = null,
             int biDiLevel = 0)
         {
             GlyphTypeface = glyphTypeface;
@@ -64,6 +76,8 @@ namespace Avalonia.Media
 
             _glyphInfos = glyphInfos;
 
+            _baselineOrigin = baselineOrigin;
+
             _biDiLevel = biDiLevel;
         }
 
@@ -72,6 +86,7 @@ namespace Avalonia.Media
             _glyphInfos = Array.Empty<GlyphInfo>();
             GlyphTypeface = Typeface.Default.GlyphTypeface;
             _platformImpl = platformImpl;
+            _baselineOrigin = platformImpl.Item.BaselineOrigin;
         }
 
         private static IReadOnlyList<GlyphInfo> CreateGlyphInfos(IReadOnlyList<ushort> glyphIndices,
@@ -147,9 +162,13 @@ namespace Avalonia.Media
             => _glyphRunMetrics ??= CreateGlyphRunMetrics();
 
         /// <summary>
-        ///     Gets the baseline origin of the<see cref="GlyphRun"/>.
+        ///     Gets or sets the baseline origin of the<see cref="GlyphRun"/>.
         /// </summary>
-        public Point BaselineOrigin => PlatformImpl.Item.BaselineOrigin;
+        public Point BaselineOrigin
+        {
+            get => _baselineOrigin ?? default;
+            set => Set(ref _baselineOrigin, value);
+        }
 
         /// <summary>
         ///     Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
@@ -204,9 +223,7 @@ namespace Avalonia.Media
         /// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
         public Geometry BuildGeometry()
         {
-            var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
-
-            var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this);
+            var geometryImpl = s_renderInterface.BuildGlyphRunGeometry(this);
 
             return new PlatformGeometry(geometryImpl);
         }
@@ -802,9 +819,11 @@ namespace Avalonia.Media
 
         private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
         {
-            var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
-
-            var platformImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos);
+            var platformImpl = s_renderInterface.CreateGlyphRun(
+                GlyphTypeface, 
+                FontRenderingEmSize, 
+                GlyphInfos, 
+                _baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale));
 
             _platformImpl = RefCountable.Create(platformImpl);
 

+ 7 - 4
src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs

@@ -93,16 +93,19 @@ namespace Avalonia.Media.TextFormatting
             [Conditional("DEBUG")]
             public void VerifyAllReturned()
             {
-                if (_pendingReturnCount > 0)
+                var pendingReturnCount = _pendingReturnCount;
+                _pendingReturnCount = 0;
+
+                if (pendingReturnCount > 0)
                 {
                     throw new InvalidOperationException(
-                        $"{_pendingReturnCount} RentedList<{typeof(T).Name} haven't been returned to the pool!");
+                        $"{pendingReturnCount} RentedList<{typeof(T).Name}> haven't been returned to the pool!");
                 }
 
-                if (_pendingReturnCount < 0)
+                if (pendingReturnCount < 0)
                 {
                     throw new InvalidOperationException(
-                        $"{-_pendingReturnCount} RentedList<{typeof(T).Name} extra lists have been returned to the pool!");
+                        $"{-pendingReturnCount} RentedList<{typeof(T).Name}> extra lists have been returned to the pool!");
                 }
             }
         }

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@@ -185,7 +185,7 @@ namespace Avalonia.Media.TextFormatting
                 ShapedBuffer.FontRenderingEmSize,
                 Text,
                 ShapedBuffer,
-                BidiLevel);
+                biDiLevel: BidiLevel);
         }
 
         public void Dispose()

+ 12 - 8
src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs

@@ -113,14 +113,18 @@ namespace Avalonia.Media.TextFormatting
 
             var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool);
 
-            var collapsedRuns = new TextRun[preSplitRuns.Count + 1];
-            preSplitRuns.CopyTo(collapsedRuns);
-            collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol;
-
-            objectPool.TextRunLists.Return(ref preSplitRuns);
-            objectPool.TextRunLists.Return(ref postSplitRuns);
-
-            return collapsedRuns;
+            try
+            {
+                var collapsedRuns = new TextRun[preSplitRuns.Count + 1];
+                preSplitRuns.CopyTo(collapsedRuns);
+                collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol;
+                return collapsedRuns;
+            }
+            finally
+            {
+                objectPool.TextRunLists.Return(ref preSplitRuns);
+                objectPool.TextRunLists.Return(ref postSplitRuns);
+            }
         }
     }
 }

+ 85 - 69
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -32,58 +32,64 @@ namespace Avalonia.Media.TextFormatting
             var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool,
                 out var textEndOfLine, out var textSourceLength);
 
-            RentedList<TextRun>? shapedTextRuns;
+            RentedList<TextRun>? shapedTextRuns = null;
 
-            if (previousLineBreak?.RemainingRuns is { } remainingRuns)
+            try
             {
-                resolvedFlowDirection = previousLineBreak.FlowDirection;
-                textRuns = remainingRuns;
-                nextLineBreak = previousLineBreak;
-                shapedTextRuns = null;
-            }
-            else
-            {
-                shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, out resolvedFlowDirection);
-                textRuns = shapedTextRuns;
-
-                if (nextLineBreak == null && textEndOfLine != null)
+                if (previousLineBreak?.RemainingRuns is { } remainingRuns)
                 {
-                    nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
+                    resolvedFlowDirection = previousLineBreak.FlowDirection;
+                    textRuns = remainingRuns;
+                    nextLineBreak = previousLineBreak;
+                    shapedTextRuns = null;
                 }
-            }
+                else
+                {
+                    shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
+                        out resolvedFlowDirection);
+                    textRuns = shapedTextRuns;
 
-            TextLineImpl textLine;
+                    if (nextLineBreak == null && textEndOfLine != null)
+                    {
+                        nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
+                    }
+                }
 
-            switch (textWrapping)
-            {
-                case TextWrapping.NoWrap:
+                TextLineImpl textLine;
+
+                switch (textWrapping)
                 {
-                    // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class
-                    // which already uses an array: ToArray() won't ever be called in this case
-                    var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray();
+                    case TextWrapping.NoWrap:
+                    {
+                        // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class
+                        // which already uses an array: ToArray() won't ever be called in this case
+                        var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray();
 
-                    textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength,
-                        paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
+                        textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength,
+                            paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
 
-                    textLine.FinalizeLine();
+                        textLine.FinalizeLine();
 
-                    break;
-                }
-                case TextWrapping.WrapWithOverflow:
-                case TextWrapping.Wrap:
-                {
-                    textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth,
-                        paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager);
-                    break;
+                        break;
+                    }
+                    case TextWrapping.WrapWithOverflow:
+                    case TextWrapping.Wrap:
+                    {
+                        textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth,
+                            paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager);
+                        break;
+                    }
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(textWrapping));
                 }
-                default:
-                    throw new ArgumentOutOfRangeException(nameof(textWrapping));
-            }
-
-            objectPool.TextRunLists.Return(ref shapedTextRuns);
-            objectPool.TextRunLists.Return(ref fetchedRuns);
 
-            return textLine;
+                return textLine;
+            }
+            finally
+            {
+                objectPool.TextRunLists.Return(ref shapedTextRuns);
+                objectPool.TextRunLists.Return(ref fetchedRuns);
+            }
         }
 
         /// <summary>
@@ -224,23 +230,26 @@ namespace Avalonia.Media.TextFormatting
                 (resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft;
 
             var processedRuns = objectPool.TextRunLists.Rent();
+            var groupedRuns = objectPool.UnshapedTextRunLists.Rent();
 
-            CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns);
+            try
+            {
+                CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns);
 
-            bidiData.Reset();
-            bidiAlgorithm.Reset();
+                bidiData.Reset();
+                bidiAlgorithm.Reset();
 
-            var groupedRuns = objectPool.UnshapedTextRunLists.Rent();
-            var textShaper = TextShaper.Current;
 
-            for (var index = 0; index < processedRuns.Count; index++)
-            {
-                var currentRun = processedRuns[index];
+                var textShaper = TextShaper.Current;
 
-                switch (currentRun)
+                for (var index = 0; index < processedRuns.Count; index++)
                 {
-                    case UnshapedTextRun shapeableRun:
+                    var currentRun = processedRuns[index];
+
+                    switch (currentRun)
                     {
+                        case UnshapedTextRun shapeableRun:
+                        {
                             groupedRuns.Clear();
                             groupedRuns.Add(shapeableRun);
 
@@ -277,17 +286,20 @@ namespace Avalonia.Media.TextFormatting
 
                             break;
                         }
-                    default:
+                        default:
                         {
                             shapedRuns.Add(currentRun);
 
                             break;
                         }
+                    }
                 }
             }
-
-            objectPool.TextRunLists.Return(ref processedRuns);
-            objectPool.UnshapedTextRunLists.Return(ref groupedRuns);
+            finally
+            {
+                objectPool.TextRunLists.Return(ref processedRuns);
+                objectPool.UnshapedTextRunLists.Return(ref groupedRuns);
+            }
 
             return shapedRuns;
         }
@@ -805,25 +817,29 @@ namespace Avalonia.Media.TextFormatting
 
             var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength, objectPool);
 
-            var textLineBreak = postSplitRuns?.Count > 0 ?
-                new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) :
-                null;
-
-            if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null)
+            try
             {
-                textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
-            }
+                var textLineBreak = postSplitRuns?.Count > 0 ?
+                    new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) :
+                    null;
 
-            var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength,
-                paragraphWidth, paragraphProperties, resolvedFlowDirection,
-                textLineBreak);
-
-            textLine.FinalizeLine();
+                if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null)
+                {
+                    textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
+                }
 
-            objectPool.TextRunLists.Return(ref preSplitRuns);
-            objectPool.TextRunLists.Return(ref postSplitRuns);
+                var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength,
+                    paragraphWidth, paragraphProperties, resolvedFlowDirection,
+                    textLineBreak);
 
-            return textLine;
+                textLine.FinalizeLine();
+                return textLine;
+            }
+            finally
+            {
+                objectPool.TextRunLists.Return(ref preSplitRuns);
+                objectPool.TextRunLists.Return(ref postSplitRuns);
+            }
         }
 
         private struct TextRunEnumerator

+ 86 - 81
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -441,128 +441,133 @@ namespace Avalonia.Media.TextFormatting
 
             var textLines = objectPool.TextLines.Rent();
 
-            double left = double.PositiveInfinity, width = 0.0, height = 0.0;
-
-            _textSourceLength = 0;
+            try
+            {
+                double left = double.PositiveInfinity, width = 0.0, height = 0.0;
 
-            TextLine? previousLine = null;
+                _textSourceLength = 0;
 
-            var textFormatter = TextFormatter.Current;
+                TextLine? previousLine = null;
 
-            while (true)
-            {
-                var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties,
-                    previousLine?.TextLineBreak);
+                var textFormatter = TextFormatter.Current;
 
-                if (textLine.Length == 0)
+                while (true)
                 {
-                    if (previousLine != null && previousLine.NewLineLength > 0)
+                    var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth,
+                        _paragraphProperties, previousLine?.TextLineBreak);
+
+                    if (textLine.Length == 0)
                     {
-                        var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth,
-                            _paragraphProperties, fontManager);
+                        if (previousLine != null && previousLine.NewLineLength > 0)
+                        {
+                            var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth,
+                                _paragraphProperties, fontManager);
 
-                        textLines.Add(emptyTextLine);
+                            textLines.Add(emptyTextLine);
 
-                        UpdateBounds(emptyTextLine, ref left, ref width, ref height);
-                    }
+                            UpdateBounds(emptyTextLine, ref left, ref width, ref height);
+                        }
 
-                    break;
-                }
+                        break;
+                    }
 
-                _textSourceLength += textLine.Length;
+                    _textSourceLength += textLine.Length;
 
-                //Fulfill max height constraint
-                if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
-                {
-                    if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None)
+                    //Fulfill max height constraint
+                    if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight)
+                        && height + textLine.Height > MaxHeight)
                     {
-                        var collapsedLine =
-                            previousLine.Collapse(GetCollapsingProperties(MaxWidth));
+                        if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None)
+                        {
+                            var collapsedLine =
+                                previousLine.Collapse(GetCollapsingProperties(MaxWidth));
 
-                        textLines[textLines.Count - 1] = collapsedLine;
-                    }
+                            textLines[textLines.Count - 1] = collapsedLine;
+                        }
 
-                    break;
-                }
+                        break;
+                    }
 
-                var hasOverflowed = textLine.HasOverflowed;
+                    var hasOverflowed = textLine.HasOverflowed;
 
-                if (hasOverflowed && _textTrimming != TextTrimming.None)
-                {
-                    textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth));
-                }
+                    if (hasOverflowed && _textTrimming != TextTrimming.None)
+                    {
+                        textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth));
+                    }
 
-                textLines.Add(textLine);
+                    textLines.Add(textLine);
 
-                UpdateBounds(textLine, ref left, ref width, ref height);
+                    UpdateBounds(textLine, ref left, ref width, ref height);
 
-                previousLine = textLine;
+                    previousLine = textLine;
 
-                //Fulfill max lines constraint
-                if (MaxLines > 0 && textLines.Count >= MaxLines)
-                {
-                    if(textLine.TextLineBreak?.RemainingRuns is not null)
+                    //Fulfill max lines constraint
+                    if (MaxLines > 0 && textLines.Count >= MaxLines)
                     {
-                        textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
+                        if (textLine.TextLineBreak?.RemainingRuns is not null)
+                        {
+                            textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
+                        }
+
+                        break;
                     }
 
-                    break;
+                    if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
+                    {
+                        break;
+                    }
                 }
 
-                if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
+                //Make sure the TextLayout always contains at least on empty line
+                if (textLines.Count == 0)
                 {
-                    break;
-                }
-            }
+                    var textLine =
+                        TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager);
 
-            //Make sure the TextLayout always contains at least on empty line
-            if (textLines.Count == 0)
-            {
-                var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager);
-
-                textLines.Add(textLine);
-
-                UpdateBounds(textLine, ref left, ref width, ref height);
-            }
+                    textLines.Add(textLine);
 
-            Bounds = new Rect(left, 0, width, height);
+                    UpdateBounds(textLine, ref left, ref width, ref height);
+                }
 
-            if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
-            {
-                var whitespaceWidth = 0d;
+                Bounds = new Rect(left, 0, width, height);
 
-                for (var i = 0; i < textLines.Count; i++)
+                if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
                 {
-                    var line = textLines[i];
-                    var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
+                    var whitespaceWidth = 0d;
 
-                    if (lineWhitespaceWidth > whitespaceWidth)
+                    for (var i = 0; i < textLines.Count; i++)
                     {
-                        whitespaceWidth = lineWhitespaceWidth;
-                    }
-                }
+                        var line = textLines[i];
+                        var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
 
-                var justificationWidth = width - whitespaceWidth;
+                        if (lineWhitespaceWidth > whitespaceWidth)
+                        {
+                            whitespaceWidth = lineWhitespaceWidth;
+                        }
+                    }
 
-                if (justificationWidth > 0)
-                {
-                    var justificationProperties = new InterWordJustification(justificationWidth);
+                    var justificationWidth = width - whitespaceWidth;
 
-                    for (var i = 0; i < textLines.Count - 1; i++)
+                    if (justificationWidth > 0)
                     {
-                        var line = textLines[i];
+                        var justificationProperties = new InterWordJustification(justificationWidth);
 
-                        line.Justify(justificationProperties);
+                        for (var i = 0; i < textLines.Count - 1; i++)
+                        {
+                            var line = textLines[i];
+
+                            line.Justify(justificationProperties);
+                        }
                     }
                 }
-            }
 
-            var result = textLines.ToArray();
-
-            objectPool.TextLines.Return(ref textLines);
-            objectPool.VerifyAllReturned();
-
-            return result;
+                return textLines.ToArray();
+            }
+            finally
+            {
+                objectPool.TextLines.Return(ref textLines);
+                objectPool.VerifyAllReturned();
+            }
         }
 
         /// <summary>

+ 4 - 8
src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@@ -86,7 +86,6 @@ namespace Avalonia.Media.TextFormatting
 
                                     RentedList<TextRun>? rentedPreSplitRuns = null;
                                     RentedList<TextRun>? rentedPostSplitRuns = null;
-                                    TextRun[]? results;
 
                                     try
                                     {
@@ -113,9 +112,7 @@ namespace Avalonia.Media.TextFormatting
 
                                         if (measuredLength <= _prefixLength || effectivePostSplitRuns is null)
                                         {
-                                            results = collapsedRuns.ToArray();
-                                            objectPool.TextRunLists.Return(ref collapsedRuns);
-                                            return results;
+                                            return collapsedRuns.ToArray();
                                         }
 
                                         var availableSuffixWidth = availableWidth;
@@ -157,16 +154,15 @@ namespace Avalonia.Media.TextFormatting
                                                 }
                                             }
                                         }
+
+                                        return collapsedRuns.ToArray();
                                     }
                                     finally
                                     {
                                         objectPool.TextRunLists.Return(ref rentedPreSplitRuns);
                                         objectPool.TextRunLists.Return(ref rentedPostSplitRuns);
+                                        objectPool.TextRunLists.Return(ref collapsedRuns);
                                     }
-
-                                    results = collapsedRuns.ToArray();
-                                    objectPool.TextRunLists.Return(ref collapsedRuns);
-                                    return results;
                                 }
 
                                 return new TextRun[] { shapedSymbol };

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

@@ -168,8 +168,9 @@ namespace Avalonia.Platform
         /// <param name="glyphTypeface">The glyph typeface.</param>
         /// <param name="fontRenderingEmSize">The font rendering em size.</param>
         /// <param name="glyphInfos">The list of glyphs.</param>
+        /// <param name="baselineOrigin">The baseline origin of the run. Can be null.</param>
         /// <returns>An <see cref="IGlyphRunImpl"/>.</returns>
-        IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos);
+        IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin);
 
         /// <summary>
         /// Creates a backend-specific object using a low-level API graphics context

+ 13 - 4
src/Avalonia.Controls/TopLevel.cs

@@ -581,12 +581,21 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         private void HandleInput(RawInputEventArgs e)
         {
-            if (e is RawPointerEventArgs pointerArgs)
+            if (PlatformImpl != null)
             {
-                pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position);
-            }
+                if (e is RawPointerEventArgs pointerArgs)
+                {
+                    pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position);
+                }
 
-            _inputManager?.ProcessInput(e);
+                _inputManager?.ProcessInput(e);
+            }
+            else
+            {
+                Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
+                    this,
+                    "PlatformImpl is null, couldn't handle input.");
+            }
         }
 
         private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e)

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

@@ -120,7 +120,11 @@ namespace Avalonia.Headless
             return new HeadlessGeometryStub(new Rect(glyphRun.Size));
         }
 
-        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(
+            IGlyphTypeface glyphTypeface, 
+            double fontRenderingEmSize,
+            IReadOnlyList<GlyphInfo> glyphInfos, 
+            Point baselineOrigin)
         {
             return new HeadlessGlyphRunStub();
         }

+ 5 - 2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -201,7 +201,11 @@ namespace Avalonia.Skia
             return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
         }
 
-        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(
+            IGlyphTypeface glyphTypeface,
+            double fontRenderingEmSize, 
+            IReadOnlyList<GlyphInfo> glyphInfos,
+            Point baselineOrigin)
         {
             if (glyphTypeface == null)
             {
@@ -252,7 +256,6 @@ namespace Avalonia.Skia
 
             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);
         }

+ 3 - 3
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -158,7 +158,8 @@ 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 IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, 
+            IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
         {
             var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
 
@@ -207,7 +208,6 @@ namespace Avalonia.Direct2D1
 
             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);
         }
@@ -257,7 +257,7 @@ namespace Avalonia.Direct2D1
                 sink.Close();
             }
 
-            var (baselineOriginX, baselineOriginY) = glyphRun.BaselineOrigin;
+            var (baselineOriginX, baselineOriginY) = glyphRun.PlatformImpl.Item.BaselineOrigin;
 
             var transformedGeometry = new SharpDX.Direct2D1.TransformedGeometry(
                 Direct2D1Factory,

+ 1 - 1
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@@ -188,7 +188,7 @@ namespace Avalonia.Base.UnitTests.Media
                 glyphInfos[i] = new GlyphInfo(0, glyphClusters[i], glyphAdvances[i]);
             }
 
-            return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, bidiLevel);
+            return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, biDiLevel: bidiLevel);
         }
     }
 }

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

@@ -77,7 +77,8 @@ namespace Avalonia.Base.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
-        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, 
+            IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
         {
             throw new NotImplementedException();
         }

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

@@ -123,7 +123,8 @@ namespace Avalonia.Benchmarks
             return new MockStreamGeometryImpl();
         }
 
-        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, 
+            IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
         {
             return new MockGlyphRun(glyphInfos);
         }

+ 1 - 1
tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs

@@ -217,7 +217,7 @@ namespace Avalonia.Skia.UnitTests.Media
                 shapedBuffer.FontRenderingEmSize,
                 shapedBuffer.Text,
                 shapedBuffer.GlyphInfos,
-                shapedBuffer.BidiLevel);
+                biDiLevel: shapedBuffer.BidiLevel);
 
             if(shapedBuffer.BidiLevel == 1)
             {

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

@@ -149,7 +149,8 @@ namespace Avalonia.UnitTests
             throw new NotImplementedException();
         }
 
-        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
+        public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, 
+            IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
         {
             return new MockGlyphRun(glyphInfos);
         }