Преглед изворни кода

Merge branch 'master' into feature/borders_different_dpis

Max Katz пре 3 година
родитељ
комит
ca363badce
77 измењених фајлова са 1960 додато и 654 уклоњено
  1. 1 0
      .editorconfig
  2. BIN
      src/Avalonia.Base/Assets/BiDi.trie
  3. BIN
      src/Avalonia.Base/Assets/GraphemeBreak.trie
  4. BIN
      src/Avalonia.Base/Assets/UnicodeData.trie
  5. 2 1
      src/Avalonia.Base/AvaloniaObject.cs
  6. 2 2
      src/Avalonia.Base/Input/IInputElement.cs
  7. 34 24
      src/Avalonia.Base/Input/InputElement.cs
  8. 3 3
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  9. 63 8
      src/Avalonia.Base/Layout/LayoutHelper.cs
  10. 15 9
      src/Avalonia.Base/Layout/Layoutable.cs
  11. 3 4
      src/Avalonia.Base/Media/GlyphRun.cs
  12. 23 0
      src/Avalonia.Base/Media/TextAlignment.cs
  13. 109 0
      src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
  14. 16 0
      src/Avalonia.Base/Media/TextFormatting/JustificationProperties.cs
  15. 15 15
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  16. 30 1
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  17. 18 48
      src/Avalonia.Base/Media/TextFormatting/TextLine.cs
  18. 66 11
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  19. 42 0
      src/Avalonia.Base/Media/TextFormatting/Unicode/BiDi.trie.cs
  20. 31 0
      src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.trie.cs
  21. 30 9
      src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs
  22. 122 0
      src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.trie.cs
  23. 13 1
      src/Avalonia.Base/Point.cs
  24. 1 0
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  25. 20 7
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  26. 2 0
      src/Avalonia.Base/Visual.cs
  27. 3 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  28. 10 10
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  29. 4 4
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  30. 1 1
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  31. 6 6
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  32. 4 4
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  33. 4 4
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  34. 4 4
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  35. 6 6
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  36. 19 15
      src/Avalonia.Controls/ComboBox.cs
  37. 189 10
      src/Avalonia.Controls/Control.cs
  38. 1 1
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
  39. 1 1
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  40. 2 2
      src/Avalonia.Controls/GridSplitter.cs
  41. 30 22
      src/Avalonia.Controls/MenuItem.cs
  42. 8 8
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  43. 2 2
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  44. 4 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  45. 5 3
      src/Avalonia.Controls/TextBlock.cs
  46. 26 48
      src/Avalonia.Controls/TextBox.cs
  47. 6 6
      src/Avalonia.Controls/ToolTipService.cs
  48. 20 1
      src/Avalonia.Controls/WindowBase.cs
  49. 2 2
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  50. 30 5
      src/Windows/Avalonia.Win32/WindowImpl.cs
  51. 15 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  52. 26 26
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  53. 0 31
      tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs
  54. 140 0
      tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs
  55. 4 2
      tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  56. 58 4
      tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  57. 1 1
      tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
  58. 34 0
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  59. 36 6
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  60. 18 0
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  61. 65 0
      tests/Avalonia.Controls.UnitTests/BorderTests.cs
  62. 6 6
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  63. 100 1
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  64. 41 0
      tests/Avalonia.Controls.UnitTests/DecoratorTests.cs
  65. 57 0
      tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs
  66. 0 28
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  67. 32 32
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  68. 65 1
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs
  69. 2 2
      tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs
  70. 0 28
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  71. 1 109
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  72. 162 3
      tests/Avalonia.LeakTests/ControlTests.cs
  73. 41 56
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  74. 2 2
      tests/Avalonia.UnitTests/MouseTestHelper.cs
  75. 6 1
      tests/Avalonia.UnitTests/TestRoot.cs
  76. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png
  77. BIN
      tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

+ 1 - 0
.editorconfig

@@ -21,6 +21,7 @@ csharp_new_line_before_finally = true
 csharp_new_line_before_members_in_object_initializers = true
 csharp_new_line_before_members_in_anonymous_types = true
 csharp_new_line_between_query_expression_clauses = true
+trim_trailing_whitespace = true
 
 # Indentation preferences
 csharp_indent_block_contents = true

BIN
src/Avalonia.Base/Assets/BiDi.trie


BIN
src/Avalonia.Base/Assets/GraphemeBreak.trie


BIN
src/Avalonia.Base/Assets/UnicodeData.trie


+ 2 - 1
src/Avalonia.Base/AvaloniaObject.cs

@@ -935,7 +935,8 @@ namespace Avalonia
 
             public void Dispose()
             {
-                _subscription.Dispose();
+                // _subscription can be null, if Subscribe failed with an exception.
+                _subscription?.Dispose();
                 _owner._directBindings!.Remove(this);
             }
 

+ 2 - 2
src/Avalonia.Base/Input/IInputElement.cs

@@ -42,12 +42,12 @@ namespace Avalonia.Input
         /// <summary>
         /// Occurs when the pointer enters the control.
         /// </summary>
-        event EventHandler<PointerEventArgs>? PointerEnter;
+        event EventHandler<PointerEventArgs>? PointerEntered;
 
         /// <summary>
         /// Occurs when the pointer leaves the control.
         /// </summary>
-        event EventHandler<PointerEventArgs>? PointerLeave;
+        event EventHandler<PointerEventArgs>? PointerExited;
 
         /// <summary>
         /// Occurs when the pointer is pressed over the control.

+ 34 - 24
src/Avalonia.Base/Input/InputElement.cs

@@ -128,16 +128,20 @@ namespace Avalonia.Input
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
 
         /// <summary>
-        /// Defines the <see cref="PointerEnter"/> event.
+        /// Defines the <see cref="PointerEntered"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerEnterEvent =
-            RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerEnter), RoutingStrategies.Direct);
+        public static readonly RoutedEvent<PointerEventArgs> PointerEnteredEvent =
+            RoutedEvent.Register<InputElement, PointerEventArgs>(
+                nameof(PointerEntered),
+                RoutingStrategies.Direct);
 
         /// <summary>
-        /// Defines the <see cref="PointerLeave"/> event.
+        /// Defines the <see cref="PointerExited"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerLeaveEvent =
-            RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerLeave), RoutingStrategies.Direct);
+        public static readonly RoutedEvent<PointerEventArgs> PointerExitedEvent =
+            RoutedEvent.Register<InputElement, PointerEventArgs>(
+                nameof(PointerExited),
+                RoutingStrategies.Direct);
 
         /// <summary>
         /// Defines the <see cref="PointerMoved"/> event.
@@ -208,8 +212,8 @@ namespace Avalonia.Input
             KeyDownEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyDown(e));
             KeyUpEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyUp(e));
             TextInputEvent.AddClassHandler<InputElement>((x, e) => x.OnTextInput(e));
-            PointerEnterEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerEnterCore(e));
-            PointerLeaveEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerLeaveCore(e));
+            PointerEnteredEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerEnteredCore(e));
+            PointerExitedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerExitedCore(e));
             PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
             PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e));
             PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
@@ -279,19 +283,19 @@ namespace Avalonia.Input
         /// <summary>
         /// Occurs when the pointer enters the control.
         /// </summary>
-        public event EventHandler<PointerEventArgs>? PointerEnter
+        public event EventHandler<PointerEventArgs>? PointerEntered
         {
-            add { AddHandler(PointerEnterEvent, value); }
-            remove { RemoveHandler(PointerEnterEvent, value); }
+            add { AddHandler(PointerEnteredEvent, value); }
+            remove { RemoveHandler(PointerEnteredEvent, value); }
         }
 
         /// <summary>
         /// Occurs when the pointer leaves the control.
         /// </summary>
-        public event EventHandler<PointerEventArgs>? PointerLeave
+        public event EventHandler<PointerEventArgs>? PointerExited
         {
-            add { AddHandler(PointerLeaveEvent, value); }
-            remove { RemoveHandler(PointerLeaveEvent, value); }
+            add { AddHandler(PointerExitedEvent, value); }
+            remove { RemoveHandler(PointerExitedEvent, value); }
         }
 
         /// <summary>
@@ -539,18 +543,18 @@ namespace Avalonia.Input
         }
 
         /// <summary>
-        /// Called before the <see cref="PointerEnter"/> event occurs.
+        /// Called before the <see cref="PointerEntered"/> event occurs.
         /// </summary>
         /// <param name="e">The event args.</param>
-        protected virtual void OnPointerEnter(PointerEventArgs e)
+        protected virtual void OnPointerEntered(PointerEventArgs e)
         {
         }
 
         /// <summary>
-        /// Called before the <see cref="PointerLeave"/> event occurs.
+        /// Called before the <see cref="PointerExited"/> event occurs.
         /// </summary>
         /// <param name="e">The event args.</param>
-        protected virtual void OnPointerLeave(PointerEventArgs e)
+        protected virtual void OnPointerExited(PointerEventArgs e)
         {
         }
 
@@ -561,7 +565,9 @@ namespace Avalonia.Input
         protected virtual void OnPointerMoved(PointerEventArgs e)
         {
             if (_gestureRecognizers?.HandlePointerMoved(e) == true)
+            {
                 e.Handled = true;
+            }
         }
 
         /// <summary>
@@ -571,7 +577,9 @@ namespace Avalonia.Input
         protected virtual void OnPointerPressed(PointerPressedEventArgs e)
         {
             if (_gestureRecognizers?.HandlePointerPressed(e) == true)
+            {
                 e.Handled = true;
+            }
         }
 
         /// <summary>
@@ -581,7 +589,9 @@ namespace Avalonia.Input
         protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
         {
             if (_gestureRecognizers?.HandlePointerReleased(e) == true)
+            {
                 e.Handled = true;
+            }
         }
 
         /// <summary>
@@ -634,23 +644,23 @@ namespace Avalonia.Input
         }
 
         /// <summary>
-        /// Called before the <see cref="PointerEnter"/> event occurs.
+        /// Called before the <see cref="PointerEntered"/> event occurs.
         /// </summary>
         /// <param name="e">The event args.</param>
-        private void OnPointerEnterCore(PointerEventArgs e)
+        private void OnPointerEnteredCore(PointerEventArgs e)
         {
             IsPointerOver = true;
-            OnPointerEnter(e);
+            OnPointerEntered(e);
         }
 
         /// <summary>
-        /// Called before the <see cref="PointerLeave"/> event occurs.
+        /// Called before the <see cref="PointerExited"/> event occurs.
         /// </summary>
         /// <param name="e">The event args.</param>
-        private void OnPointerLeaveCore(PointerEventArgs e)
+        private void OnPointerExitedCore(PointerEventArgs e)
         {
             IsPointerOver = false;
-            OnPointerLeave(e);
+            OnPointerExited(e);
         }
 
         /// <summary>

+ 3 - 3
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@@ -97,7 +97,7 @@ namespace Avalonia.Input
             // Do not pass rootVisual, when we have unknown (negative) position,
             // so GetPosition won't return invalid values.
             var hasPosition = position.X >= 0 && position.Y >= 0;
-            var e = new PointerEventArgs(InputElement.PointerLeaveEvent, element, pointer,
+            var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer,
                 hasPosition ? root : null, hasPosition ? position : default,
                 timestamp, properties, inputModifiers);
 
@@ -177,7 +177,7 @@ namespace Avalonia.Input
 
             el = root.PointerOverElement;
 
-            var e = new PointerEventArgs(InputElement.PointerLeaveEvent, el, pointer, root, position,
+            var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root, position,
                 timestamp, properties, inputModifiers);
             if (el != null && branch != null && !el.IsAttachedToVisualTree)
             {
@@ -195,7 +195,7 @@ namespace Avalonia.Input
             el = root.PointerOverElement = element;
             _lastPointer = (pointer, root.PointToScreen(position));
 
-            e.RoutedEvent = InputElement.PointerEnterEvent;
+            e.RoutedEvent = InputElement.PointerEnteredEvent;
 
             while (el != null && el != branch)
             {

+ 63 - 8
src/Avalonia.Base/Layout/LayoutHelper.cs

@@ -36,11 +36,28 @@ namespace Avalonia.Layout
         public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding,
             Thickness borderThickness)
         {
-            return MeasureChild(control, availableSize, padding + borderThickness);
+            if (IsParentLayoutRounded(control, out double scale))
+            {
+                padding = RoundLayoutThickness(padding, scale, scale);
+                borderThickness = RoundLayoutThickness(borderThickness, scale, scale);
+            }
+
+            if (control != null)
+            {
+                control.Measure(availableSize.Deflate(padding + borderThickness));
+                return control.DesiredSize.Inflate(padding + borderThickness);
+            }
+
+            return new Size().Inflate(padding + borderThickness);
         }
 
         public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding)
         {
+            if (IsParentLayoutRounded(control, out double scale))
+            {
+                padding = RoundLayoutThickness(padding, scale, scale);
+            }
+
             if (control != null)
             {
                 control.Measure(availableSize.Deflate(padding));
@@ -137,7 +154,7 @@ namespace Avalonia.Layout
 
         /// <summary>
         /// Rounds a size to integer values for layout purposes, compensating for high DPI screen
-        /// coordinates.
+        /// coordinates by rounding the size up to the nearest pixel.
         /// </summary>
         /// <param name="size">Input size.</param>
         /// <param name="dpiScaleX">DPI along x-dimension.</param>
@@ -149,9 +166,9 @@ namespace Avalonia.Layout
         /// associated with the UseLayoutRounding property and should not be used as a general rounding
         /// utility.
         /// </remarks>
-        public static Size RoundLayoutSize(Size size, double dpiScaleX, double dpiScaleY)
+        public static Size RoundLayoutSizeUp(Size size, double dpiScaleX, double dpiScaleY)
         {
-            return new Size(RoundLayoutValue(size.Width, dpiScaleX), RoundLayoutValue(size.Height, dpiScaleY));
+            return new Size(RoundLayoutValueUp(size.Width, dpiScaleX), RoundLayoutValueUp(size.Height, dpiScaleY));
         }
 
         /// <summary>
@@ -178,10 +195,9 @@ namespace Avalonia.Layout
             );
         }
 
-
-
         /// <summary>
-        /// Calculates the value to be used for layout rounding at high DPI.
+        /// Calculates the value to be used for layout rounding at high DPI by rounding the value
+        /// up or down to the nearest pixel.
         /// </summary>
         /// <param name="value">Input value to be rounded.</param>
         /// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
@@ -217,7 +233,46 @@ namespace Avalonia.Layout
 
             return newValue;
         }
-        
+
+        /// <summary>
+        /// Calculates the value to be used for layout rounding at high DPI by rounding the value up
+        /// to the nearest pixel.
+        /// </summary>
+        /// <param name="value">Input value to be rounded.</param>
+        /// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
+        /// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns>
+        /// <remarks>
+        /// This is a layout helper method. It takes DPI into account and also does not return
+        /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper
+        /// associated with the UseLayoutRounding property and should not be used as a general rounding
+        /// utility.
+        /// </remarks>
+        public static double RoundLayoutValueUp(double value, double dpiScale)
+        {
+            double newValue;
+
+            // If DPI == 1, don't use DPI-aware rounding.
+            if (!MathUtilities.IsOne(dpiScale))
+            {
+                newValue = Math.Ceiling(value * dpiScale) / dpiScale;
+
+                // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue),
+                // use the original value.
+                if (double.IsNaN(newValue) ||
+                    double.IsInfinity(newValue) ||
+                    MathUtilities.AreClose(newValue, double.MaxValue))
+                {
+                    newValue = value;
+                }
+            }
+            else
+            {
+                newValue = Math.Ceiling(value);
+            }
+
+            return newValue;
+        }
+
         /// <summary>
         /// Calculates the min and max height for a control. Ported from WPF.
         /// </summary>

+ 15 - 9
src/Avalonia.Base/Layout/Layoutable.cs

@@ -548,6 +548,14 @@ namespace Avalonia.Layout
             if (IsVisible)
             {
                 var margin = Margin;
+                var useLayoutRounding = UseLayoutRounding;
+                var scale = 1.0;
+
+                if (useLayoutRounding)
+                {
+                    scale = LayoutHelper.GetLayoutScale(this);
+                    margin = LayoutHelper.RoundLayoutThickness(margin, scale, scale);
+                }
 
                 ApplyStyling();
                 ApplyTemplate();
@@ -584,16 +592,14 @@ namespace Avalonia.Layout
                 height = Math.Min(height, MaxHeight);
                 height = Math.Max(height, MinHeight);
 
-                width = Math.Min(width, availableSize.Width);
-                height = Math.Min(height, availableSize.Height);
-
-                if (UseLayoutRounding)
+                if (useLayoutRounding)
                 {
-                    var scale = LayoutHelper.GetLayoutScale(this);
-                    width = LayoutHelper.RoundLayoutValue(width, scale);
-                    height = LayoutHelper.RoundLayoutValue(height, scale);
+                    (width, height) = LayoutHelper.RoundLayoutSizeUp(new Size(width, height), scale, scale);
                 }
 
+                width = Math.Min(width, availableSize.Width);
+                height = Math.Min(height, availableSize.Height);
+
                 return NonNegative(new Size(width, height).Inflate(margin));
             }
             else
@@ -678,8 +684,8 @@ namespace Avalonia.Layout
 
                 if (useLayoutRounding)
                 {
-                    size = LayoutHelper.RoundLayoutSize(size, scale, scale);
-                    availableSizeMinusMargins = LayoutHelper.RoundLayoutSize(availableSizeMinusMargins, scale, scale);
+                    size = LayoutHelper.RoundLayoutSizeUp(size, scale, scale);
+                    availableSizeMinusMargins = LayoutHelper.RoundLayoutSizeUp(availableSizeMinusMargins, scale, scale);
                 }
 
                 size = ArrangeOverride(size).Constrain(size);

+ 3 - 4
src/Avalonia.Base/Media/GlyphRun.cs

@@ -734,10 +734,9 @@ namespace Avalonia.Media
 
         private void Set<T>(ref T field, T value)
         {
-            if (_glyphRunImpl != null)
-            {
-                throw new InvalidOperationException("GlyphRun can't be changed after it has been initialized.'");
-            }
+            _glyphRunImpl?.Dispose();
+
+            _glyphRunImpl = null;
 
             _glyphRunMetrics = null;
 

+ 23 - 0
src/Avalonia.Base/Media/TextAlignment.cs

@@ -19,5 +19,28 @@ namespace Avalonia.Media
         /// The text is right-aligned.
         /// </summary>
         Right,
+
+        /// <summary>
+        /// The beginning of the text is aligned to the edge of the available space.
+        /// </summary>
+        Start,
+
+        /// <summary>
+        /// The end of the text is aligned to the edge of the available space.
+        /// </summary>
+        End,
+
+        /// <summary>
+        /// Text alignment is inferred from the text content.
+        /// </summary>
+        /// <remarks>
+        /// When the TextAlignment property is set to DetectFromContent, alignment is inferred from the text content of the control. For example, English text is left aligned, and Arabic text is right aligned.
+        /// </remarks>
+        DetectFromContent,
+
+        /// <summary>
+        /// Text is justified within the available space.
+        /// </summary>
+        Justify
     }
 }

+ 109 - 0
src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs

@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+
+namespace Avalonia.Media.TextFormatting
+{
+    internal class InterWordJustification : JustificationProperties
+    {
+        public InterWordJustification(double width)
+        {
+            Width = width;
+        }
+
+        public override double Width { get; }
+
+        public override void Justify(TextLine textLine)
+        {
+            var paragraphWidth = Width;
+
+            if (double.IsInfinity(paragraphWidth))
+            {
+                return;
+            }
+
+            if (textLine.NewLineLength > 0)
+            {
+                return;
+            }
+
+            var textLineBreak = textLine.TextLineBreak;
+
+            if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
+            {
+                if (textLineBreak.RemainingRuns is null || textLineBreak.RemainingRuns.Count == 0)
+                {
+                    return;
+                }
+            }
+
+            var breakOportunities = new Queue<int>();
+
+            foreach (var textRun in textLine.TextRuns)
+            {
+                var text = textRun.Text;
+
+                if (text.IsEmpty)
+                {
+                    continue;
+                }
+
+                var start = text.Start;
+
+                var lineBreakEnumerator = new LineBreakEnumerator(text);
+
+                while (lineBreakEnumerator.MoveNext())
+                {
+                    var currentBreak = lineBreakEnumerator.Current;
+
+                    if (!currentBreak.Required && currentBreak.PositionWrap != text.Length)
+                    {
+                        breakOportunities.Enqueue(start + currentBreak.PositionMeasure);
+                    }
+                }
+            }
+
+            if (breakOportunities.Count == 0)
+            {
+                return;
+            }
+
+            var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace);
+            var spacing = remainingSpace / breakOportunities.Count;
+
+            foreach (var textRun in textLine.TextRuns)
+            {
+                var text = textRun.Text;
+
+                if (text.IsEmpty)
+                {
+                    continue;
+                }
+
+                if (textRun is ShapedTextCharacters shapedText)
+                {
+                    var glyphRun = shapedText.GlyphRun;
+                    var shapedBuffer = shapedText.ShapedBuffer;
+                    var currentPosition = text.Start;
+
+                    while (breakOportunities.Count > 0)
+                    {
+                        var characterIndex = breakOportunities.Dequeue();
+
+                        if (characterIndex < currentPosition)
+                        {
+                            continue;
+                        }
+
+                        var glyphIndex = glyphRun.FindGlyphIndex(characterIndex);
+                        var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex];
+
+                        shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
+                    }
+
+                    glyphRun.GlyphAdvances = shapedBuffer.GlyphAdvances;
+                }
+            }
+        }
+    }
+}

+ 16 - 0
src/Avalonia.Base/Media/TextFormatting/JustificationProperties.cs

@@ -0,0 +1,16 @@
+namespace Avalonia.Media.TextFormatting
+{
+    public abstract class JustificationProperties
+    {
+        /// <summary>
+        /// Gets the width in which the range is justified.
+        /// </summary>
+        public abstract double Width { get; }
+
+        /// <summary>
+        /// Justifies given text line.
+        /// </summary>
+        /// <param name="textLine">Text line to collapse.</param>
+        public abstract void Justify(TextLine textLine);
+    }
+}

+ 15 - 15
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Media.TextFormatting
             TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null)
         {
             var textWrapping = paragraphProperties.TextWrapping;
-            FlowDirection flowDirection;
+            FlowDirection resolvedFlowDirection;
             TextLineBreak? nextLineBreak = null;
             List<DrawableTextRun> drawableTextRuns;
 
@@ -24,17 +24,17 @@ namespace Avalonia.Media.TextFormatting
 
             if (previousLineBreak?.RemainingRuns != null)
             {
-                flowDirection = previousLineBreak.FlowDirection;
+                resolvedFlowDirection = previousLineBreak.FlowDirection;
                 drawableTextRuns = previousLineBreak.RemainingRuns.ToList();
                 nextLineBreak = previousLineBreak;
             }
             else
             {
-                drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out flowDirection);
+                drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection);
 
                 if (nextLineBreak == null && textEndOfLine != null)
                 {
-                    nextLineBreak = new TextLineBreak(textEndOfLine, flowDirection);
+                    nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
                 }
             }
 
@@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting
                 case TextWrapping.NoWrap:
                     {
                         textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength,
-                            paragraphWidth, paragraphProperties, flowDirection, nextLineBreak);
+                            paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
 
                         textLine.FinalizeLine();
 
@@ -55,7 +55,7 @@ namespace Avalonia.Media.TextFormatting
                 case TextWrapping.Wrap:
                     {
                         textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties,
-                            flowDirection, nextLineBreak);
+                            resolvedFlowDirection, nextLineBreak);
                         break;
                     }
                 default:
@@ -404,9 +404,9 @@ namespace Avalonia.Media.TextFormatting
                 {
                     endOfLine = textEndOfLine;
 
-                    textRuns.Add(textRun);
+                    textSourceLength += textEndOfLine.TextSourceLength;
 
-                    textSourceLength += textRun.TextSourceLength;
+                    textRuns.Add(textRun);
 
                     break;
                 }
@@ -431,9 +431,9 @@ namespace Avalonia.Media.TextFormatting
 
                             break;
                         }
-                    case DrawableTextRun drawableTextRun:
+                    default:
                         {
-                            textRuns.Add(drawableTextRun);
+                            textRuns.Add(textRun);
                             break;
                         }
                 }
@@ -552,11 +552,11 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="firstTextSourceIndex">The first text source index.</param>
         /// <param name="paragraphWidth">The paragraph width.</param>
         /// <param name="paragraphProperties">The text paragraph properties.</param>
-        /// <param name="flowDirection"></param>
+        /// <param name="resolvedFlowDirection"></param>
         /// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
         /// <returns>The wrapped text line.</returns>
         private static TextLineImpl PerformTextWrapping(List<DrawableTextRun> textRuns, int firstTextSourceIndex,
-            double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection,
+            double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
             TextLineBreak? currentLineBreak)
         {
             if(textRuns.Count == 0)
@@ -684,16 +684,16 @@ namespace Avalonia.Media.TextFormatting
             var remainingCharacters = splitResult.Second;
 
             var lineBreak = remainingCharacters?.Count > 0 ?
-                new TextLineBreak(currentLineBreak?.TextEndOfLine, flowDirection, remainingCharacters) :
+                new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) :
                 null;
 
             if (lineBreak is null && currentLineBreak?.TextEndOfLine != null)
             {
-                lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, flowDirection);
+                lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
             }
 
             var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength,
-                paragraphWidth, paragraphProperties, flowDirection,
+                paragraphWidth, paragraphProperties, resolvedFlowDirection,
                 lineBreak);
 
             return textLine.FinalizeLine();

+ 30 - 1
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -439,7 +439,7 @@ namespace Avalonia.Media.TextFormatting
                 var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
                     _paragraphProperties, previousLine?.TextLineBreak);
 
-                if(textLine == null || textLine.Length == 0)
+                if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
                 {
                     if(previousLine != null && previousLine.NewLineLength  > 0)
                     {
@@ -501,6 +501,35 @@ namespace Avalonia.Media.TextFormatting
 
             Bounds = new Rect(left, 0, width, height);
 
+            if(_paragraphProperties.TextAlignment == TextAlignment.Justify)
+            {
+                var whitespaceWidth = 0d;
+
+                foreach (var line in textLines)
+                {
+                    var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
+
+                    if(lineWhitespaceWidth > whitespaceWidth)
+                    {
+                        whitespaceWidth = lineWhitespaceWidth;
+                    }
+                }
+
+                var justificationWidth = width - whitespaceWidth;
+
+                if(justificationWidth > 0)
+                {
+                    var justificationProperties = new InterWordJustification(justificationWidth);
+
+                    for (var i = 0; i < textLines.Count - 1; i++)
+                    {
+                        var line = textLines[i];
+
+                        line.Justify(justificationProperties);
+                    }
+                }
+            }
+
             return textLines;
         }
 

+ 18 - 48
src/Avalonia.Base/Media/TextFormatting/TextLine.cs

@@ -15,9 +15,15 @@ namespace Avalonia.Media.TextFormatting
         /// The contained text runs.
         /// </value>
         public abstract IReadOnlyList<TextRun> TextRuns { get; }
-        
+
+        /// <summary>
+        /// Gets the first TextSource position of the current line.
+        /// </summary>
         public abstract int FirstTextSourceIndex { get; }
 
+        /// <summary>
+        /// Gets the total number of TextSource positions of the current line.
+        /// </summary>
         public abstract int Length { get; }
 
         /// <summary>
@@ -56,7 +62,7 @@ namespace Avalonia.Media.TextFormatting
         /// Gets a value that indicates whether content of the line overflows the specified paragraph width.
         /// </summary>
         /// <returns>
-        /// <c>true</c>, it the line overflows the specified paragraph width; otherwise, <c>false</c>.
+        /// <c>true</c>, the line overflows the specified paragraph width; otherwise, <c>false</c>.
         /// </returns>
         public abstract bool HasOverflowed { get; }
 
@@ -75,7 +81,7 @@ namespace Avalonia.Media.TextFormatting
         /// The number of newline characters.
         /// </returns>
         public abstract int NewLineLength { get; }
-        
+
         /// <summary>
         /// Gets the distance that black pixels extend beyond the bottom alignment edge of a line.
         /// </summary>
@@ -149,6 +155,15 @@ namespace Avalonia.Media.TextFormatting
         /// </returns>
         public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
 
+        /// <summary>
+        /// Create a justified line based on justification text properties.
+        /// </summary>
+        /// <param name="justificationProperties">An object that represent the justification text properties.</param>
+        /// <returns>
+        /// A <see cref="TextLine"/> value that represents a justified line that can be displayed.
+        /// </returns>
+        public abstract void Justify(JustificationProperties justificationProperties);
+
         /// <summary>
         /// Gets the character hit corresponding to the specified distance from the beginning of the line.
         /// </summary>
@@ -192,50 +207,5 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="textLength">number of characters of the specified range</param>
         /// <returns>an array of bounding rectangles.</returns>
         public abstract IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength);
-        
-        /// <summary>
-        /// Gets the text line offset x.
-        /// </summary>
-        /// <param name="width">The line width.</param>
-        /// <param name="widthIncludingTrailingWhitespace">The paragraph width including whitespace.</param>
-        /// <param name="paragraphWidth">The paragraph width.</param>
-        /// <param name="textAlignment">The text alignment.</param>
-        /// <param name="flowDirection">The flow direction of the line.</param>
-        /// <returns>The paragraph offset.</returns>
-        internal static double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace,
-            double paragraphWidth, TextAlignment textAlignment, FlowDirection flowDirection)
-        {
-            if (double.IsPositiveInfinity(paragraphWidth))
-            {
-                return 0;
-            }
-
-            if (flowDirection == FlowDirection.LeftToRight)
-            {
-                switch (textAlignment)
-                {
-                    case TextAlignment.Center:
-                        return Math.Max(0, (paragraphWidth - width) / 2);
-
-                    case TextAlignment.Right:
-                        return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace);
-
-                    default:
-                        return 0;
-                }
-            }
-
-            switch (textAlignment)
-            {
-                case TextAlignment.Center:
-                    return Math.Max(0, (paragraphWidth - width) / 2);
-
-                case TextAlignment.Right:
-                    return 0;
-
-                default:
-                    return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace);
-            }
-        }
     }
 }

+ 66 - 11
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -10,10 +10,10 @@ namespace Avalonia.Media.TextFormatting
         private readonly double _paragraphWidth;
         private readonly TextParagraphProperties _paragraphProperties;
         private TextLineMetrics _textLineMetrics;
-        private readonly FlowDirection _flowDirection;
+        private readonly FlowDirection _resolvedFlowDirection;
 
         public TextLineImpl(List<DrawableTextRun> textRuns, int firstTextSourceIndex, int length, double paragraphWidth,
-            TextParagraphProperties paragraphProperties, FlowDirection flowDirection = FlowDirection.LeftToRight,
+            TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight,
             TextLineBreak? lineBreak = null, bool hasCollapsed = false)
         {
             FirstTextSourceIndex = firstTextSourceIndex;
@@ -25,7 +25,7 @@ namespace Avalonia.Media.TextFormatting
             _paragraphWidth = paragraphWidth;
             _paragraphProperties = paragraphProperties;
 
-            _flowDirection = flowDirection;
+            _resolvedFlowDirection = resolvedFlowDirection;
         }
 
         /// <inheritdoc/>
@@ -136,7 +136,7 @@ namespace Avalonia.Media.TextFormatting
             }
 
             var collapsedLine = new TextLineImpl(collapsedRuns, FirstTextSourceIndex, Length, _paragraphWidth, _paragraphProperties,
-                _flowDirection, TextLineBreak, true);
+                _resolvedFlowDirection, TextLineBreak, true);
 
             if (collapsedRuns.Count > 0)
             {
@@ -144,7 +144,14 @@ namespace Avalonia.Media.TextFormatting
             }
 
             return collapsedLine;
+        }
 
+        /// <inheritdoc/>
+        public override void Justify(JustificationProperties justificationProperties)
+        {
+            justificationProperties.Justify(this);
+
+            _textLineMetrics = CreateLineMetrics();
         }
 
         /// <inheritdoc/>
@@ -167,7 +174,7 @@ namespace Avalonia.Media.TextFormatting
                     return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
                 }
 
-                return _flowDirection == FlowDirection.LeftToRight ?
+                return _resolvedFlowDirection == FlowDirection.LeftToRight ?
                     new CharacterHit(FirstTextSourceIndex) :
                     new CharacterHit(FirstTextSourceIndex + Length);
             }
@@ -260,7 +267,7 @@ namespace Avalonia.Media.TextFormatting
                             //Look at the left and right edge of the current run
                             if (currentRun.IsLeftToRight)
                             {
-                                if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
+                                if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
                                 {
                                     if (characterIndex <= currentPosition)
                                     {
@@ -735,7 +742,7 @@ namespace Avalonia.Media.TextFormatting
             // Build up the collection of ordered runs.
             var run = _textRuns[0];
 
-            OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _flowDirection));
+            OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _resolvedFlowDirection));
 
             var current = orderedRun;
 
@@ -743,7 +750,7 @@ namespace Avalonia.Media.TextFormatting
             {
                 run = _textRuns[i];
 
-                current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _flowDirection));
+                current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _resolvedFlowDirection));
 
                 current = current.Next;
             }
@@ -762,7 +769,7 @@ namespace Avalonia.Media.TextFormatting
             {
                 var currentRun = _textRuns[i];
 
-                var level = GetRunBidiLevel(currentRun, _flowDirection);
+                var level = GetRunBidiLevel(currentRun, _resolvedFlowDirection);
 
                 if (level > max)
                 {
@@ -1242,8 +1249,7 @@ namespace Avalonia.Media.TextFormatting
                 }
             }
 
-            var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth,
-                _paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection);
+            var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
 
             if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
             {
@@ -1257,6 +1263,55 @@ namespace Avalonia.Media.TextFormatting
                 -ascent, trailingWhitespaceLength, width, widthIncludingWhitespace);
         }
 
+        /// <summary>
+        /// Gets the text line offset x.
+        /// </summary>
+        /// <param name="width">The line width.</param>
+        /// <param name="widthIncludingTrailingWhitespace">The paragraph width including whitespace.</param>
+
+        /// <returns>The paragraph offset.</returns>
+        private double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace)
+        {
+            if (double.IsPositiveInfinity(_paragraphWidth))
+            {
+                return 0;
+            }
+
+            var textAlignment = _paragraphProperties.TextAlignment;
+            var paragraphFlowDirection = _paragraphProperties.FlowDirection;
+
+            switch (textAlignment)
+            {
+                case TextAlignment.Start:
+                    {
+                        textAlignment = paragraphFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right;
+                        break;
+                    }
+                case TextAlignment.End:
+                    {
+                        textAlignment = paragraphFlowDirection == FlowDirection.RightToLeft ? TextAlignment.Left : TextAlignment.Right;
+                        break;
+                    }
+                case TextAlignment.DetectFromContent:
+                    {
+                        textAlignment = _resolvedFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right;
+                        break;
+                    }
+            }
+
+            switch (textAlignment)
+            {
+                case TextAlignment.Center:
+                    return Math.Max(0, (_paragraphWidth - width) / 2);
+
+                case TextAlignment.Right:
+                    return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);
+
+                default:
+                    return 0;
+            }
+        }
+
         private sealed class OrderedBidiRun
         {
             public OrderedBidiRun(DrawableTextRun run, sbyte level)

+ 42 - 0
src/Avalonia.Base/Media/TextFormatting/Unicode/BiDi.trie.cs

@@ -0,0 +1,42 @@
+using System;
+
+namespace Avalonia.Media.TextFormatting.Unicode
+{
+   internal static class BiDiTrie
+    {
+        public static ReadOnlySpan<byte> Data => new byte[]
+        {
+            0,16,0,0,0,0,0,0,0,0,174,224,0,68,12,187,243,236,157,11,176,85,85,25,199,151,115,207,189,247,156,203,227,94,244,132,151,128,58,122,53,184,10,74,112,26,206,61,64,105,130,89,22,163,121,85,12,17,38,26,7,197,72,37,152,166,59,56,142,132,236,64,144,16,196,212,6,26,167,98,34,197,158,71,83,10,134,116,116,236,161,165,4,50,38,61,40,64,137,178,
+            28,109,6,164,255,102,175,205,93,44,246,99,61,247,62,192,254,102,126,243,173,189,246,218,235,251,214,183,190,253,60,251,156,51,171,129,144,91,193,87,192,157,224,1,176,14,172,7,143,3,7,172,144,208,63,3,191,0,207,130,223,130,109,224,79,224,85,240,23,176,15,188,5,246,131,183,3,182,63,4,26,115,225,253,247,193,186,51,192,96,208,1,206,7,99,64,23,184,24,92,
+            10,38,131,43,193,117,96,6,152,5,230,128,249,96,30,88,0,22,129,101,224,223,121,66,222,5,7,65,174,64,200,80,212,141,6,99,193,90,44,175,134,94,227,150,115,132,124,15,108,4,143,131,39,192,102,240,28,93,126,17,188,194,44,191,6,118,211,246,251,193,219,116,251,67,57,111,44,141,141,132,20,192,0,112,6,24,12,58,26,123,251,63,191,209,107,127,24,109,47,108,
+            244,236,249,246,29,212,141,109,244,180,203,4,148,39,49,203,159,68,249,74,186,124,53,244,245,180,252,5,232,91,192,92,112,144,246,227,128,30,44,47,4,75,193,125,76,63,142,65,30,162,253,62,194,245,191,193,176,189,31,115,253,61,133,229,167,192,211,24,239,211,116,204,91,176,252,124,163,231,203,75,92,251,237,88,126,148,137,141,3,118,161,110,15,215,110,31,150,107,200,143,
+            183,104,253,255,160,79,107,242,202,5,232,211,105,253,155,232,107,64,147,55,239,131,154,188,249,60,139,182,27,78,245,72,170,29,134,49,168,27,23,80,239,80,182,129,223,180,168,197,232,227,232,247,83,96,50,184,6,244,96,28,27,193,13,40,223,8,22,162,188,24,204,199,24,230,96,121,62,184,131,250,114,119,147,55,110,159,217,76,185,22,192,38,112,15,221,118,37,244,55,185,
+            237,107,224,48,234,182,210,242,90,102,204,223,13,104,91,19,96,67,200,118,47,48,229,223,131,29,133,248,88,241,125,252,153,234,61,224,128,164,95,239,128,67,92,157,19,149,203,77,92,46,55,29,159,3,110,31,141,200,131,45,180,175,124,75,111,223,173,76,185,198,113,38,214,13,5,91,209,231,11,76,191,127,104,234,221,39,55,198,196,229,231,104,187,137,46,239,68,249,175,180,
+            159,55,160,255,195,249,234,128,119,154,142,221,127,30,51,184,223,59,25,36,139,1,57,101,98,80,19,56,126,56,25,36,139,1,201,98,208,144,197,32,139,1,201,98,208,144,197,32,139,1,201,98,208,144,197,160,94,99,176,179,201,187,174,117,159,9,56,2,237,115,205,189,229,66,179,247,44,205,95,190,150,62,163,217,203,212,13,96,218,59,96,16,150,75,180,110,24,179,110,36,
+            182,185,144,46,143,133,254,40,45,175,198,115,156,75,104,249,49,250,156,232,114,44,95,213,236,61,183,115,151,175,109,246,158,211,77,231,108,29,224,158,33,221,200,173,119,50,72,22,3,146,197,160,33,139,65,22,3,146,197,160,33,139,65,22,3,146,197,160,225,196,141,193,205,184,198,107,193,53,104,255,130,247,185,178,10,231,182,28,95,247,29,212,141,4,31,1,75,209,247,189,
+            96,46,103,227,81,172,187,28,117,183,195,135,175,130,187,184,235,205,53,117,240,252,184,212,223,99,6,88,71,203,165,0,182,131,246,86,66,166,128,89,96,61,248,73,107,239,250,255,50,229,18,24,209,134,207,186,193,15,192,223,64,251,0,66,38,130,59,193,19,224,101,80,60,29,109,17,163,85,125,97,31,122,45,88,119,90,60,175,11,182,251,96,159,222,114,55,202,171,192,203,
+            224,69,216,43,130,182,126,98,253,216,162,27,62,172,203,236,147,180,98,208,157,197,159,100,249,71,82,139,65,119,150,127,36,237,252,187,2,231,128,25,253,78,221,249,143,187,62,88,210,124,236,231,221,43,155,123,223,79,89,207,189,167,229,112,92,197,93,223,60,88,135,207,219,54,98,12,223,134,95,235,221,103,134,205,222,251,97,63,106,238,93,239,62,111,124,146,46,255,146,174,
+            255,21,244,175,153,54,47,49,101,135,121,87,104,71,115,239,251,131,238,251,104,187,2,218,57,13,158,253,61,204,186,127,161,252,110,72,91,7,28,196,186,92,222,43,187,244,205,123,253,223,79,183,41,230,143,125,239,111,8,179,236,112,156,21,177,206,1,195,177,126,20,215,102,12,181,55,14,250,99,224,19,116,253,228,152,190,156,0,174,102,182,153,134,242,76,174,143,155,176,124,
+            27,173,155,39,209,255,135,35,222,107,236,65,63,11,21,124,117,100,200,123,122,57,213,78,138,108,166,121,177,138,241,229,225,58,240,203,225,120,30,251,218,79,21,223,169,116,20,120,4,49,216,0,126,200,196,162,70,143,115,19,66,252,168,209,245,155,19,190,119,219,72,245,34,58,151,223,247,215,53,123,250,73,140,97,170,1,159,182,129,45,121,79,63,7,253,59,240,74,130,115,
+            226,104,242,26,124,221,29,227,239,114,197,220,255,99,29,238,51,14,71,13,57,240,25,238,29,212,207,130,107,90,188,119,78,95,207,247,230,206,110,58,158,197,52,135,28,142,21,1,159,1,214,20,222,7,174,25,96,106,139,119,237,193,214,181,50,227,156,137,242,46,193,207,44,157,147,148,189,121,239,251,19,81,12,44,68,175,255,0,214,159,29,210,230,60,90,63,34,166,143,161,
+            17,140,118,191,71,81,16,107,91,166,237,198,115,237,251,23,188,239,144,248,223,23,25,26,192,69,220,186,75,4,109,14,213,160,127,193,27,223,104,166,110,52,37,23,98,127,116,4,147,90,226,109,250,251,250,108,170,167,67,207,101,142,127,53,212,213,40,61,45,225,239,232,187,251,210,93,96,9,88,1,86,211,62,106,76,155,26,248,22,237,99,43,152,89,72,63,223,55,193,159,
+            77,17,108,67,187,109,33,60,19,179,237,166,83,0,66,240,112,54,150,110,128,155,197,35,122,10,45,183,41,210,151,161,155,246,87,161,148,24,42,71,232,36,85,50,156,116,209,229,33,160,200,232,34,104,23,164,200,216,169,112,36,35,211,48,22,2,123,83,143,142,167,66,73,70,22,80,251,61,71,237,183,37,99,184,174,165,45,128,190,33,245,109,22,40,6,228,126,137,214,85,
+            24,92,241,203,238,118,126,217,109,235,226,231,57,223,214,197,173,231,235,216,126,109,137,237,254,235,221,126,82,34,50,151,65,115,47,130,169,237,101,125,209,177,201,82,62,197,9,139,125,156,196,205,93,84,155,147,81,84,198,42,51,47,39,155,4,29,59,220,243,212,196,58,216,39,202,10,76,228,116,57,164,60,241,36,39,31,66,133,146,163,243,92,162,229,34,213,21,74,89,130,
+            92,157,83,182,64,94,144,18,83,206,113,62,229,50,8,31,131,184,152,71,229,38,223,71,80,91,191,141,232,253,168,201,57,74,115,206,77,230,254,201,156,183,73,197,33,237,243,131,12,34,177,240,219,250,231,15,31,190,159,160,62,211,20,223,126,144,31,113,99,14,235,75,100,123,25,255,210,138,81,154,115,35,50,31,42,125,232,138,238,124,184,215,3,46,113,109,88,123,166,108,
+            235,74,84,222,7,249,25,180,63,216,244,69,182,255,176,253,82,116,255,54,33,58,115,26,151,71,108,255,170,199,31,83,194,231,135,175,77,238,207,42,99,59,145,142,173,182,124,229,207,217,62,110,126,185,90,196,151,180,207,227,190,196,229,83,216,190,46,146,59,170,249,101,35,54,97,115,22,54,95,113,146,246,117,134,170,109,209,57,139,186,206,83,149,176,115,94,92,78,197,29,
+            183,101,175,59,85,69,212,127,157,188,79,235,188,147,132,61,62,167,202,100,92,107,149,84,91,187,200,4,232,241,208,230,237,149,21,8,219,150,173,15,18,19,54,146,68,87,100,198,196,174,227,183,79,66,194,108,217,142,135,74,31,124,127,34,109,117,109,165,33,38,99,31,117,126,23,57,215,171,94,7,152,18,247,125,161,100,228,225,129,85,242,208,64,211,199,90,95,76,206,169,
+            136,29,222,166,236,62,24,180,191,233,30,59,253,235,21,182,157,104,126,242,207,224,221,119,98,76,73,26,199,219,168,99,174,104,63,42,54,248,253,89,231,90,223,164,36,225,71,208,121,214,166,45,214,102,212,185,159,247,69,117,31,147,221,71,109,156,219,147,190,214,10,242,39,204,47,118,189,106,223,182,198,19,53,71,65,99,50,45,188,141,168,117,54,99,200,183,211,149,160,249,
+            87,201,81,153,57,143,219,71,131,252,75,83,252,227,174,234,179,31,221,103,70,166,182,119,197,189,94,20,133,125,47,215,149,160,207,29,101,9,123,239,119,4,248,52,56,7,92,20,209,174,157,27,91,123,8,67,40,21,114,71,169,74,22,148,252,235,214,176,246,225,219,47,199,246,203,142,110,175,43,236,92,4,221,99,200,206,179,104,110,70,221,215,132,105,86,68,62,3,9,178,
+            39,226,135,136,191,81,219,203,32,227,187,168,255,114,156,215,81,37,157,29,252,119,30,76,248,109,90,244,199,26,157,115,21,1,218,13,115,34,141,93,180,63,81,223,121,17,189,94,145,181,159,148,184,126,179,223,151,58,222,167,194,168,42,201,143,234,34,125,160,91,160,251,65,247,133,110,133,238,15,61,0,186,13,154,208,109,223,135,229,34,150,207,132,30,8,61,8,186,29,122,
+            48,244,251,153,118,68,58,6,101,14,95,226,230,48,106,93,82,241,247,125,182,97,211,70,254,219,20,27,182,147,28,127,146,241,211,57,86,37,57,183,182,108,242,251,188,219,63,95,23,134,168,63,73,30,3,162,150,195,234,76,217,246,251,230,115,68,214,166,200,123,97,58,34,155,195,162,249,16,135,138,68,109,103,195,158,136,63,54,199,43,98,63,204,23,190,94,182,223,168,49,
+            152,176,225,111,103,162,141,138,77,217,88,233,140,209,244,56,121,159,117,250,178,33,236,59,221,201,157,23,39,28,246,190,31,63,238,112,58,223,207,159,70,237,79,165,246,103,96,121,58,45,207,68,249,243,199,248,149,164,164,121,223,163,123,141,88,175,215,216,73,29,227,69,236,154,56,231,156,8,247,53,186,231,184,176,24,165,113,238,246,237,218,236,219,212,184,162,226,110,82,162,
+            108,196,249,28,118,30,76,99,94,227,108,199,181,211,181,231,47,219,20,27,253,71,197,43,106,126,249,114,26,34,58,135,105,251,108,211,166,232,254,166,154,163,65,219,201,230,76,84,223,34,231,5,155,34,122,204,78,43,215,69,246,199,164,37,42,199,194,252,17,201,207,36,198,34,234,135,168,63,162,62,203,216,21,21,145,220,77,34,151,69,237,217,222,175,85,226,33,114,92,11,
+            138,91,82,49,228,219,132,249,97,226,120,172,42,113,190,218,144,184,123,35,255,25,174,143,238,189,150,201,251,45,219,251,130,72,174,155,234,91,71,252,152,166,113,14,243,69,118,44,113,191,77,104,234,88,99,67,146,176,35,179,159,152,122,95,67,245,184,47,115,110,240,197,84,63,65,253,138,74,146,57,162,98,203,100,158,185,199,110,153,92,176,121,220,78,251,51,104,94,252,99,
+            78,82,191,53,19,246,251,51,124,125,84,187,52,126,39,135,95,167,251,187,68,38,252,115,223,27,181,53,246,122,24,159,42,238,254,20,149,79,162,121,154,86,110,197,249,152,100,28,195,16,253,189,60,27,168,198,218,214,252,37,145,59,174,4,221,151,120,117,5,124,78,153,63,250,57,229,16,250,251,143,67,168,46,74,156,251,138,33,243,93,150,232,163,221,0,188,152,232,211,164,
+            63,178,162,107,199,196,189,167,159,27,58,247,178,238,246,46,252,181,11,155,151,65,215,51,186,231,147,56,191,252,255,71,24,14,46,3,227,193,40,112,5,205,233,82,0,21,9,74,41,83,214,164,162,73,145,209,174,176,117,30,159,123,175,74,174,123,175,139,92,15,61,21,250,6,232,105,208,36,52,230,37,138,223,95,24,174,136,252,166,127,208,182,252,61,181,123,254,208,141,165,
+            10,113,191,75,25,151,255,162,231,70,209,253,167,72,46,45,85,201,164,35,223,55,170,88,102,74,2,54,42,145,20,58,112,126,196,119,97,250,64,183,72,127,39,166,34,137,13,137,182,121,211,176,42,153,53,172,139,204,134,190,25,250,139,208,183,64,127,9,122,14,244,109,208,183,66,207,133,190,29,122,30,244,151,161,137,196,184,158,197,54,207,72,110,67,52,249,7,108,254,29,
+            54,247,66,239,129,126,3,122,31,244,126,232,55,161,15,64,255,211,176,79,95,235,172,146,133,157,93,228,110,232,69,208,14,244,98,232,37,208,95,135,190,7,122,41,244,10,232,123,161,151,67,47,131,94,9,253,13,232,85,208,247,65,223,15,189,26,250,1,232,53,157,102,125,140,99,7,108,110,135,205,157,208,175,38,108,155,128,67,176,123,208,170,93,95,108,143,69,206,70,199,
+            5,85,114,246,5,93,228,92,232,115,160,135,65,127,8,186,19,122,56,52,73,120,30,236,197,72,86,108,231,129,174,232,218,77,235,247,173,125,127,116,255,15,73,213,182,202,239,108,235,92,159,134,137,191,158,239,51,41,49,53,143,182,238,143,226,226,104,99,159,172,199,227,150,234,113,36,109,31,146,242,223,244,248,249,237,85,37,169,248,155,154,195,184,216,198,197,92,103,174,77,
+            140,67,39,31,85,164,30,198,171,147,7,186,199,255,52,206,223,166,8,242,39,105,177,241,31,145,50,232,94,255,164,109,95,20,87,100,234,101,250,213,17,21,159,85,125,20,181,21,214,214,190,252,31,0,0,255,255,0,0,0,255,255,99,102,0,0};
+    }
+}

+ 31 - 0
src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.trie.cs

@@ -0,0 +1,31 @@
+using System;
+
+namespace Avalonia.Media.TextFormatting.Unicode
+{
+   internal static class GraphemeBreakTrie
+    {
+        public static ReadOnlySpan<byte> Data => new byte[]
+        {
+            0,14,16,0,0,0,0,0,0,0,133,64,0,245,7,10,248,236,157,127,136,29,87,21,199,103,157,111,246,157,100,211,138,180,69,177,5,241,7,88,91,40,182,150,210,12,138,161,88,27,163,96,241,15,75,161,210,18,44,149,10,193,46,24,80,49,254,163,33,16,136,54,127,84,8,34,18,253,199,68,196,42,136,154,82,21,149,6,218,82,43,72,107,161,54,22,196,173,10,110,
+            16,140,210,82,250,125,190,51,228,228,228,206,204,157,121,119,230,109,182,243,133,15,231,220,115,207,156,123,239,121,111,247,253,216,129,189,33,207,178,130,220,66,118,147,61,228,126,19,43,230,180,171,228,139,100,31,217,31,145,127,128,28,174,153,63,66,142,146,99,228,56,249,49,249,5,249,21,57,101,242,158,36,127,36,207,145,23,201,26,89,39,255,34,103,201,43,4,200,178,47,
+            147,175,146,131,152,93,91,242,40,217,206,216,55,200,17,114,148,28,35,199,201,9,242,19,114,146,252,134,252,150,60,65,158,54,227,63,145,191,104,254,75,228,140,94,255,63,242,32,89,218,50,91,103,43,237,25,218,55,109,57,87,255,205,244,223,166,227,119,211,190,135,92,110,214,47,152,127,61,99,103,39,51,127,7,253,157,90,175,32,31,162,255,49,29,223,78,123,135,250,119,
+            211,222,71,246,146,255,154,243,238,227,120,63,57,64,14,155,58,5,185,82,206,241,46,229,26,242,126,165,48,185,69,13,95,143,204,43,18,243,16,207,243,16,121,158,231,125,94,207,252,45,142,191,87,246,7,231,231,95,193,248,247,93,15,30,118,227,194,240,51,157,251,37,237,99,234,239,37,47,235,252,83,140,253,129,60,71,78,147,53,178,174,121,103,213,190,26,168,143,101,62,
+            255,150,47,140,151,172,146,203,107,230,139,72,62,201,243,223,165,61,184,201,196,247,146,7,106,206,93,140,100,125,247,224,42,247,248,158,12,60,7,138,4,252,213,252,12,188,61,193,115,170,24,201,198,30,100,99,15,242,177,7,99,15,178,177,7,249,216,131,177,7,217,216,131,124,236,193,216,131,108,236,65,62,246,96,236,65,150,188,7,87,47,207,190,75,43,199,159,136,248,254,
+            226,189,188,230,102,243,153,247,24,191,83,123,133,236,100,108,151,198,111,167,189,131,92,37,179,239,3,239,162,15,250,159,214,249,251,105,87,201,62,29,239,167,253,51,237,129,134,207,210,135,57,127,43,235,124,152,220,70,118,145,143,144,221,228,163,228,214,113,46,27,251,146,141,207,9,25,127,30,198,223,5,89,178,223,5,55,144,29,228,3,164,24,223,139,100,99,15,178,177,7,
+            249,216,131,62,122,112,164,231,191,167,236,87,138,0,187,3,177,51,198,191,199,253,237,179,88,0,187,46,153,113,144,172,109,111,230,20,243,158,13,228,190,229,210,243,199,187,56,62,72,78,145,236,141,179,216,141,180,159,33,223,33,143,144,117,178,115,37,203,62,71,94,220,202,191,125,109,203,50,89,154,113,167,241,197,113,98,229,194,88,136,47,153,26,63,162,255,15,94,119,45,
+            247,113,47,57,176,61,174,198,72,54,246,96,105,236,193,216,131,108,236,193,210,230,235,193,58,95,23,50,190,46,60,202,247,9,167,249,122,248,146,222,231,177,198,239,174,222,202,215,231,119,232,107,244,223,201,239,152,179,194,207,44,143,47,207,230,127,79,123,141,121,13,127,54,240,94,227,36,57,189,124,238,62,178,53,250,235,21,239,73,254,195,248,171,102,110,153,223,131,93,170,
+            247,152,21,1,46,227,220,149,102,254,157,147,115,247,61,77,185,118,114,254,253,95,239,171,169,117,115,205,92,65,174,227,57,111,114,239,87,62,200,107,110,35,31,159,94,11,222,203,164,53,110,113,121,69,4,159,50,235,223,75,255,179,110,63,15,112,188,91,235,126,161,97,175,133,225,43,45,114,139,30,185,175,67,79,138,158,249,46,123,179,99,192,245,246,178,7,39,184,230,195,
+            129,199,228,231,27,228,113,42,90,178,71,239,3,91,85,246,56,86,107,248,60,46,204,127,189,81,204,193,209,134,207,118,47,187,241,177,13,120,111,221,113,238,233,135,21,251,186,34,209,253,159,63,104,121,238,159,6,242,207,246,220,135,175,233,207,255,161,13,240,123,224,65,238,225,50,190,206,127,211,236,229,219,9,246,181,74,126,61,153,217,83,180,79,145,103,54,192,121,139,72,
+            94,224,94,255,214,176,223,67,29,207,243,207,139,160,15,135,220,30,255,125,17,236,185,232,192,35,27,96,15,215,231,75,255,39,231,219,243,60,130,55,24,127,41,242,154,60,130,81,139,211,216,255,44,217,243,56,239,200,162,31,55,188,206,73,41,212,176,153,133,139,144,114,223,165,197,212,9,248,155,93,147,10,98,133,22,164,254,221,149,90,232,129,20,123,26,66,168,97,210,144,
+            227,107,100,53,57,41,53,81,139,148,69,91,10,9,217,204,66,11,46,86,33,130,216,58,214,135,62,215,177,0,36,16,203,52,46,138,31,195,252,108,214,9,117,147,145,18,3,28,226,226,165,38,165,211,81,147,142,215,133,36,166,230,36,148,16,56,87,121,29,166,206,156,130,214,18,199,84,162,216,92,104,12,186,95,49,22,101,226,130,132,10,68,247,8,71,72,147,10,208,1,
+            49,126,149,80,193,68,237,144,130,33,239,161,126,174,181,183,168,159,39,170,153,87,144,66,121,75,134,88,175,73,112,207,61,184,121,113,115,80,242,6,144,128,124,78,250,18,28,161,24,166,193,134,49,76,124,40,193,80,55,39,126,114,64,65,215,135,142,197,128,6,36,144,47,74,89,27,21,196,10,202,34,4,119,158,161,215,246,123,128,163,148,40,126,140,50,208,32,52,204,251,
+            92,216,64,207,130,243,225,206,58,148,224,122,43,26,67,69,46,2,99,84,212,76,181,63,4,98,177,18,205,135,99,42,40,50,29,168,21,141,165,22,148,190,132,10,134,22,140,133,233,39,12,165,68,241,99,148,129,10,193,16,43,196,38,38,22,2,231,204,76,76,166,3,55,6,137,17,34,115,196,128,152,194,9,5,183,190,152,56,20,63,198,52,80,35,40,139,16,212,138,250,
+            18,240,203,60,81,166,18,5,74,157,160,196,8,138,40,139,20,148,210,151,169,19,41,84,196,68,129,193,230,75,13,208,156,121,5,181,162,62,42,136,169,131,26,82,9,145,251,133,201,109,83,27,1,134,18,148,190,215,8,89,43,81,82,158,9,45,145,138,184,173,25,18,90,208,54,31,61,48,175,16,217,55,184,124,113,185,125,74,26,214,66,205,92,91,193,209,69,80,43,234,
+            35,34,183,171,208,245,194,134,154,80,250,20,20,49,62,2,72,3,112,62,34,137,221,99,95,130,65,28,8,196,189,68,129,241,197,1,67,159,130,34,10,122,90,71,204,58,168,161,220,147,181,165,143,26,164,2,68,82,10,1,164,156,28,72,168,65,140,21,7,140,191,40,33,130,174,53,165,237,133,9,37,129,115,136,139,139,155,79,45,184,53,196,0,55,7,51,215,86,210,0,
+            26,16,151,55,175,96,240,99,68,34,13,243,94,240,129,138,57,209,49,76,204,11,3,81,181,214,60,130,97,72,65,73,85,7,74,40,134,10,188,208,48,174,138,165,148,40,80,36,144,131,158,214,20,183,246,80,66,34,186,174,93,55,135,26,250,16,90,32,74,234,245,69,129,67,92,92,148,170,58,67,10,21,136,130,138,113,151,117,98,114,164,41,169,71,161,167,122,48,216,57,
+            148,3,39,40,109,36,230,58,81,234,242,218,74,20,84,248,153,137,45,90,216,228,107,35,64,76,174,29,135,4,71,223,130,33,52,215,70,80,82,74,180,166,164,44,218,81,8,32,21,113,24,250,18,18,215,19,3,34,144,132,107,136,137,137,137,87,9,106,197,128,6,250,16,220,30,196,204,73,197,88,90,212,142,149,40,246,90,84,96,133,0,125,75,12,104,64,2,121,162,182,
+            79,33,97,29,81,80,129,4,242,36,48,143,68,123,234,42,232,94,68,125,24,74,137,3,138,24,127,72,65,73,37,49,160,1,49,121,162,182,141,96,174,19,227,35,128,168,109,146,40,112,72,32,134,166,98,29,36,6,68,32,198,79,37,49,192,81,10,138,56,80,38,168,68,237,144,66,36,67,236,35,54,15,1,68,129,97,30,137,171,5,199,84,168,64,212,166,18,58,146,153,
+            189,160,2,169,240,83,9,21,248,156,210,98,234,24,27,35,9,80,214,192,212,49,113,24,155,82,80,68,73,89,19,134,148,66,13,41,106,103,166,31,210,129,58,193,209,69,104,153,47,202,34,5,197,250,152,14,84,168,161,47,193,209,103,253,33,133,30,73,181,86,147,208,148,208,49,183,173,16,160,42,142,72,230,21,106,104,154,71,36,165,80,131,157,207,106,114,82,11,142,212,
+            245,96,232,83,162,244,85,87,90,176,104,137,50,228,90,178,32,182,110,0,172,182,45,152,149,158,185,196,209,86,219,18,226,181,178,1,240,226,191,181,184,160,103,125,50,244,122,94,175,1,0,0,255,255,0,0,0,255,255,99,102,0,0};
+    }
+}

+ 30 - 9
src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs

@@ -1,4 +1,5 @@
-using System.Runtime.CompilerServices;
+using System.IO;
+using System.Runtime.CompilerServices;
 
 namespace Avalonia.Media.TextFormatting.Unicode
 {
@@ -17,14 +18,14 @@ namespace Avalonia.Media.TextFormatting.Unicode
 
         internal const int SCRIPT_SHIFT = CATEGORY_BITS;
         internal const int LINEBREAK_SHIFT = CATEGORY_BITS + SCRIPT_BITS;
-        
+
         internal const int BIDIPAIREDBRACKEDTYPE_SHIFT = BIDIPAIREDBRACKED_BITS;
         internal const int BIDICLASS_SHIFT = BIDIPAIREDBRACKED_BITS + BIDIPAIREDBRACKEDTYPE_BITS;
-        
+
         internal const int CATEGORY_MASK = (1 << CATEGORY_BITS) - 1;
         internal const int SCRIPT_MASK = (1 << SCRIPT_BITS) - 1;
         internal const int LINEBREAK_MASK = (1 << LINEBREAK_BITS) - 1;
-        
+
         internal const int BIDIPAIREDBRACKED_MASK = (1 << BIDIPAIREDBRACKED_BITS) - 1;
         internal const int BIDIPAIREDBRACKEDTYPE_MASK = (1 << BIDIPAIREDBRACKEDTYPE_BITS) - 1;
         internal const int BIDICLASS_MASK = (1 << BIDICLASS_BITS) - 1;
@@ -35,9 +36,29 @@ namespace Avalonia.Media.TextFormatting.Unicode
 
         static UnicodeData()
         {
-            s_unicodeDataTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.UnicodeData.trie")!);
-            s_graphemeBreakTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.GraphemeBreak.trie")!);
-            s_biDiTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.BiDi.trie")!);
+            unsafe
+            {
+                var unicodeData = UnicodeDataTrie.Data;
+
+                fixed (byte* unicodeDataPtr = unicodeData)
+                {
+                    s_unicodeDataTrie = new UnicodeTrie(new UnmanagedMemoryStream(unicodeDataPtr, unicodeData.Length));
+                }
+
+                var graphemeData = GraphemeBreakTrie.Data;
+
+                fixed (byte* graphemeDataPtr = graphemeData)
+                {
+                    s_graphemeBreakTrie = new UnicodeTrie(new UnmanagedMemoryStream(graphemeDataPtr, graphemeData.Length));
+                }
+
+                var bidiData = BiDiTrie.Data;
+
+                fixed (byte* bidiDataPtr = bidiData)
+                {
+                    s_biDiTrie = new UnicodeTrie(new UnmanagedMemoryStream(bidiDataPtr, bidiData.Length));
+                }
+            }
         }
 
         /// <summary>
@@ -72,7 +93,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
         {
             return (BidiClass)((s_biDiTrie.Get(codepoint) >> BIDICLASS_SHIFT) & BIDICLASS_MASK);
         }
-        
+
         /// <summary>
         /// Gets the <see cref="BidiPairedBracketType"/> for a Unicode codepoint.
         /// </summary>
@@ -83,7 +104,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
         {
             return (BidiPairedBracketType)((s_biDiTrie.Get(codepoint) >> BIDIPAIREDBRACKEDTYPE_SHIFT) & BIDIPAIREDBRACKEDTYPE_MASK);
         }
-        
+
         /// <summary>
         /// Gets the paired bracket for a Unicode codepoint.
         /// </summary>

+ 122 - 0
src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.trie.cs

@@ -0,0 +1,122 @@
+using System;
+
+namespace Avalonia.Media.TextFormatting.Unicode
+{
+    internal static class UnicodeDataTrie
+    {
+        public static ReadOnlySpan<byte> Data => new byte[]
+        {
+            0,16,0,0,0,0,0,0,0,1,154,144,0,161,43,94,212,236,157,15,148,28,85,157,239,107,230,215,147,204,84,146,78,103,152,252,115,128,16,8,241,224,234,238,193,197,221,163,139,231,117,2,132,158,162,51,52,67,155,63,244,152,233,66,130,202,81,247,225,209,221,167,7,125,13,139,74,202,48,84,134,89,16,199,5,70,81,244,249,111,117,209,93,93,229,5,68,157,41,218,
+            177,9,35,168,3,72,16,124,60,149,125,111,87,217,231,62,193,247,190,53,117,59,115,231,166,254,220,234,170,234,10,58,57,231,147,223,253,255,251,221,63,117,235,214,173,59,213,151,100,20,101,47,208,193,91,193,181,224,0,56,4,110,3,147,224,51,224,139,224,31,193,127,7,223,5,223,7,143,130,199,193,51,224,57,240,60,248,13,80,186,20,165,27,228,192,6,112,26,56,19,
+            188,10,188,6,188,30,108,7,26,24,2,151,129,125,224,74,240,14,240,87,224,253,224,131,224,58,96,128,155,192,173,224,239,192,39,193,231,193,87,192,55,193,183,193,247,192,15,192,143,193,83,224,25,240,28,120,30,188,8,104,153,162,172,0,39,129,126,112,6,248,35,240,185,94,69,249,83,200,191,0,231,129,29,96,16,148,193,59,215,41,202,217,39,33,30,252,5,184,7,105,
+            135,17,126,57,184,202,142,95,166,40,239,5,53,112,3,48,193,71,192,237,224,83,224,11,224,171,224,27,224,1,80,7,179,224,49,240,52,248,5,248,21,248,15,208,177,92,81,122,192,26,176,30,108,2,47,7,127,2,206,131,238,63,135,252,79,224,66,48,8,118,129,125,224,74,112,21,120,231,114,199,246,247,66,94,11,14,48,255,69,200,251,94,212,227,16,252,183,128,219,193,
+            39,193,231,89,252,165,136,223,11,116,240,86,112,53,184,100,13,218,22,241,23,160,190,239,129,255,61,140,107,25,31,2,7,24,135,24,223,92,238,200,111,67,78,131,135,152,255,81,38,31,135,124,6,60,7,246,162,252,189,224,121,184,111,67,220,71,99,226,14,240,34,202,188,27,242,110,208,213,173,40,171,192,90,112,10,56,19,124,17,225,95,6,175,130,251,53,224,245,224,235,
+            240,127,3,117,189,0,238,251,224,222,217,237,148,119,9,228,94,160,131,183,130,119,128,105,132,255,21,228,53,96,207,6,69,185,30,242,33,132,29,132,28,7,31,3,119,129,207,117,59,237,251,35,196,61,102,143,157,110,232,0,79,195,253,75,240,107,240,0,252,47,64,214,33,103,193,3,176,225,49,200,39,193,179,224,127,131,127,197,245,180,19,225,117,240,6,123,252,245,34,109,
+            175,115,93,253,166,219,185,62,121,244,53,139,253,74,15,108,71,88,6,114,37,184,2,121,251,32,79,6,91,192,107,215,43,202,54,48,0,94,9,255,57,160,128,122,157,11,121,62,184,26,121,223,3,174,5,22,198,81,17,97,159,66,218,207,130,47,129,175,129,251,192,52,120,8,252,8,60,9,158,5,207,129,171,193,123,64,21,101,62,15,249,34,200,192,221,3,214,108,112,194,
+            47,69,153,21,240,12,202,255,25,120,11,194,222,2,158,227,220,111,7,87,131,247,128,141,96,51,56,11,92,11,14,128,67,224,108,48,6,62,10,62,14,254,27,248,50,211,241,85,91,255,58,69,249,237,58,199,127,45,236,184,1,220,11,247,189,62,60,203,234,242,44,195,100,242,86,112,187,16,247,172,11,85,148,241,90,144,7,111,178,251,162,199,9,107,114,0,237,122,53,194,
+            222,13,14,193,125,27,199,251,16,246,54,244,215,7,32,51,125,138,242,97,200,155,193,4,227,58,212,229,173,208,241,24,210,124,162,199,25,111,159,133,252,14,202,253,142,93,247,158,5,183,200,215,17,119,24,60,141,188,191,4,83,112,175,132,142,6,100,31,228,201,224,215,8,255,33,252,143,129,45,240,111,1,71,225,126,37,228,140,221,54,61,24,163,128,96,67,15,120,100,131,
+            227,94,3,158,128,251,41,240,115,198,47,55,56,121,68,206,65,89,231,44,161,44,181,129,178,212,6,125,242,109,208,156,63,206,9,145,231,156,37,148,165,54,80,150,218,160,111,169,13,150,218,64,89,106,131,190,165,54,88,106,3,101,169,13,250,254,48,219,224,55,120,118,123,1,207,120,231,194,125,238,9,130,162,226,185,19,235,218,229,144,25,236,141,156,143,176,243,125,88,137,
+            52,171,85,103,239,112,18,207,204,159,1,69,132,23,193,122,132,247,33,254,75,108,79,228,171,144,247,130,7,192,38,196,157,14,202,72,247,10,200,97,200,87,67,94,1,249,102,240,58,184,255,18,242,175,193,251,193,118,248,53,240,65,184,13,48,4,247,101,96,12,238,203,33,175,2,39,219,251,80,125,216,23,0,239,132,255,189,224,58,96,128,119,161,157,15,65,94,3,121,13,
+            248,20,210,220,6,255,23,32,191,8,254,30,124,9,124,25,252,3,184,7,124,97,41,78,89,106,23,101,105,76,244,45,93,15,75,115,129,18,219,92,48,137,121,247,211,224,179,224,106,220,23,174,78,17,29,123,165,122,138,236,93,66,89,106,3,101,169,13,214,187,183,65,152,245,244,63,168,11,238,127,132,251,94,240,29,48,3,30,81,157,247,212,54,223,64,252,19,156,255,219,
+            1,60,128,244,218,50,199,253,83,150,239,127,66,62,7,158,183,215,151,235,240,94,13,178,107,197,226,124,43,224,63,9,212,177,222,189,30,107,206,45,88,159,158,5,250,17,118,6,248,163,21,233,206,61,54,15,176,245,120,6,246,157,223,235,216,105,191,139,125,193,126,46,57,9,235,102,112,15,120,18,156,134,118,168,130,113,48,11,186,215,226,93,37,248,47,224,159,192,111,64,
+            55,218,226,92,240,46,112,15,248,53,56,7,122,222,2,198,193,44,88,137,231,139,34,56,104,239,161,171,138,114,39,120,18,156,134,246,168,130,15,131,7,153,124,142,201,15,115,188,106,37,222,215,195,230,14,240,118,184,63,191,114,113,188,205,115,8,91,181,106,193,191,19,238,27,193,247,192,170,44,252,224,70,112,103,71,188,124,143,149,217,181,26,239,179,87,59,238,18,147,119,
+            50,62,8,255,167,192,207,184,240,173,57,39,172,157,108,203,29,223,110,75,40,75,109,176,98,169,13,150,218,64,249,131,104,131,107,83,158,3,21,156,151,82,82,226,190,92,186,207,94,39,2,127,138,62,120,29,216,190,194,57,191,83,199,186,166,206,208,16,54,4,222,0,142,96,29,243,3,240,70,184,247,131,17,236,221,190,157,229,217,193,241,238,21,206,218,232,125,144,31,0,
+            143,193,125,35,228,223,130,219,192,36,120,26,97,159,97,121,103,177,134,249,49,56,104,159,3,234,195,115,50,194,191,6,190,9,190,5,126,221,231,172,133,94,128,60,226,162,47,131,53,87,38,128,31,35,223,83,224,231,46,249,255,13,97,207,131,23,193,74,164,237,178,215,77,43,157,184,181,144,167,128,51,153,127,7,232,91,235,172,191,94,3,54,98,45,247,122,200,243,65,17,
+            156,140,184,45,107,157,116,175,132,44,35,236,28,200,97,200,43,192,219,192,187,192,53,92,121,231,34,254,92,112,61,11,123,202,110,139,149,88,43,162,61,198,33,207,71,220,199,184,244,59,124,184,11,233,138,72,255,57,46,125,25,254,123,224,255,103,46,236,126,184,167,193,67,96,24,241,251,192,143,224,126,18,92,9,247,85,224,89,184,255,5,188,19,238,127,135,252,29,232,196,
+            218,241,189,240,191,31,124,16,168,240,247,130,245,171,22,202,222,4,247,203,193,40,226,255,4,242,207,193,235,193,5,171,156,53,168,104,243,45,72,119,27,120,3,226,222,8,38,225,254,36,216,239,146,118,7,248,60,226,222,142,184,119,179,248,175,192,255,53,240,62,248,63,0,238,91,235,172,115,255,150,197,255,29,228,39,61,202,218,1,166,145,254,243,66,252,207,209,7,31,67,
+            251,127,5,225,223,4,223,2,15,114,105,30,134,251,81,240,19,240,16,242,255,0,252,15,184,31,135,252,41,248,95,112,255,2,242,255,248,232,221,193,248,127,72,147,193,154,121,37,56,9,108,0,167,102,23,226,183,194,253,10,240,106,240,58,144,7,5,46,126,135,15,37,143,116,187,17,190,25,99,119,4,242,87,176,243,87,109,228,205,130,77,143,99,78,121,156,241,151,62,246,
+            252,181,100,157,119,252,30,243,91,180,195,111,99,224,69,38,187,214,121,167,177,159,99,127,235,18,254,95,79,128,126,200,193,182,92,11,172,21,252,31,106,177,46,69,220,139,138,17,185,9,186,111,5,119,112,54,124,2,238,79,131,83,96,219,102,240,247,112,255,19,56,156,80,155,255,27,230,185,187,236,179,183,217,197,225,103,65,247,89,160,129,240,175,67,62,194,197,63,1,247,
+            217,8,59,251,15,148,159,181,161,254,175,109,145,127,97,253,244,239,66,127,238,136,153,109,208,181,221,135,223,49,253,203,176,199,178,10,104,8,211,36,89,179,58,126,123,227,102,8,118,14,49,54,194,222,83,193,214,54,218,93,229,206,239,87,5,254,120,181,35,255,140,201,49,216,248,81,112,231,58,199,127,46,194,95,128,124,1,156,239,98,115,145,229,171,130,50,220,195,46,105,
+            254,175,189,247,216,139,53,237,106,103,223,244,109,144,239,2,215,128,223,217,123,189,171,177,134,5,227,128,16,191,162,119,33,207,139,108,175,117,37,248,45,220,157,189,78,158,9,164,237,131,251,100,112,6,120,5,88,15,54,129,173,224,85,224,19,72,115,63,234,145,217,24,141,79,163,140,207,174,115,228,151,109,247,106,236,157,175,78,127,92,201,242,207,176,245,91,62,246,94,134,
+            58,61,136,248,135,67,214,233,114,228,155,123,9,180,195,85,176,243,42,23,126,202,108,127,13,198,202,235,193,47,218,80,151,95,65,199,89,88,75,255,134,211,165,96,95,161,43,231,157,103,21,226,214,130,83,192,153,224,85,224,53,44,253,185,62,249,118,48,102,236,107,55,183,240,55,44,5,184,75,160,12,246,130,179,237,119,12,27,157,191,91,234,101,82,207,57,239,131,222,2,
+            249,159,193,187,192,53,224,122,22,126,47,199,140,240,55,50,47,67,25,167,131,87,128,18,23,254,106,248,95,11,182,129,129,141,11,225,151,192,189,7,236,3,186,173,127,163,19,254,14,200,119,51,247,12,184,6,238,26,243,223,0,121,16,182,140,231,156,185,167,201,199,56,255,93,112,127,14,220,195,194,190,206,228,253,144,223,229,234,97,162,172,143,176,114,191,207,218,233,78,248,
+            31,97,238,42,87,246,19,224,103,224,151,224,95,89,25,85,97,78,253,15,166,167,210,227,188,115,225,219,234,222,54,211,179,38,253,115,123,73,177,134,181,237,57,39,128,45,231,36,204,198,53,254,237,144,180,254,115,78,96,54,167,92,255,179,82,190,198,239,61,1,56,39,166,107,249,222,151,32,61,109,28,127,103,191,132,219,233,222,152,218,250,181,224,18,172,89,46,97,236,16,
+            214,59,27,216,119,28,54,184,176,109,205,241,233,95,106,236,245,56,251,179,247,15,128,129,53,233,239,233,164,77,41,211,113,140,109,249,46,9,50,202,182,90,143,178,237,112,183,67,30,110,169,124,93,129,236,175,169,74,165,214,161,84,14,99,189,171,144,178,47,223,9,63,128,187,130,176,97,60,222,236,169,57,113,85,59,78,233,84,118,43,153,121,89,57,220,161,12,29,238,148,
+            162,98,167,87,58,149,170,93,142,226,48,175,87,33,156,23,107,15,118,93,42,168,195,30,212,103,4,122,119,129,17,48,133,184,169,54,48,12,253,85,244,229,158,60,36,244,110,67,251,255,161,255,219,230,54,46,15,199,55,190,131,216,175,216,99,16,207,174,24,243,251,48,54,246,49,116,244,79,165,214,173,140,0,29,110,235,80,183,114,217,97,167,223,182,163,15,117,54,118,116,
+            228,171,34,125,25,233,202,243,233,51,74,129,229,173,176,252,101,96,33,255,94,228,47,51,127,217,142,87,236,179,146,201,141,247,42,116,212,125,226,167,18,166,10,253,83,1,105,234,191,231,114,42,166,52,245,19,88,214,125,234,53,229,147,166,46,184,235,46,121,234,33,242,76,5,164,159,10,136,155,146,212,85,119,73,51,5,44,23,123,44,129,58,120,144,203,255,160,135,123,42,
+            130,156,138,177,127,167,124,108,171,199,168,167,126,130,200,41,15,234,62,253,94,119,105,147,122,68,125,83,47,17,172,54,235,155,150,96,0,247,223,129,0,70,4,6,108,106,221,32,195,100,247,98,191,194,165,113,201,63,210,164,230,220,243,143,161,176,176,121,153,113,181,119,196,142,83,22,179,41,227,208,212,53,32,196,143,180,136,137,53,143,185,132,50,223,6,74,135,35,243,33,
+            176,243,40,30,132,41,39,239,80,92,65,74,1,20,153,28,176,251,105,197,130,159,176,70,36,48,192,252,5,70,5,207,82,69,46,158,24,35,43,156,126,182,227,42,76,22,25,132,248,34,39,139,172,172,98,12,16,87,102,209,135,66,130,20,5,119,209,69,103,81,82,214,151,97,190,1,245,144,178,224,161,175,234,210,222,5,183,246,95,254,210,166,144,50,197,19,68,234,160,116,
+            97,215,60,38,187,214,75,112,95,204,40,158,32,118,22,19,144,197,22,243,22,78,16,251,11,17,36,97,30,92,217,73,109,129,216,156,223,3,119,191,7,221,39,0,253,157,157,202,250,237,153,99,246,110,70,216,102,112,234,246,206,121,127,110,103,215,75,142,245,59,51,243,178,127,21,29,11,235,231,221,55,118,204,75,18,238,207,36,160,238,164,182,65,1,186,251,97,127,255,170,
+            227,211,249,113,93,7,29,199,118,196,220,9,121,39,99,226,218,206,121,110,7,21,172,91,38,58,58,149,73,132,79,2,115,27,230,70,73,42,216,47,190,14,146,160,119,226,90,236,219,193,111,160,12,35,69,6,20,249,180,205,251,128,41,195,54,57,198,238,235,148,98,194,238,131,251,24,130,77,70,202,109,104,156,64,76,96,92,25,1,227,114,187,226,63,118,15,32,238,0,23,
+            63,233,146,214,96,250,198,208,31,99,18,24,156,141,147,12,195,182,119,115,107,216,215,208,117,144,6,48,117,216,195,220,70,155,49,161,219,140,9,98,115,146,193,235,232,120,105,163,109,57,49,41,93,129,245,172,36,26,210,83,136,123,10,113,28,249,97,167,20,214,6,236,69,165,76,99,79,87,32,211,72,55,13,142,130,185,121,58,149,185,143,116,204,135,17,171,115,3,233,158,
+            216,211,57,79,227,141,200,199,49,125,10,244,248,248,27,12,98,101,205,33,126,174,69,8,249,181,62,244,119,194,148,202,24,39,12,98,118,87,16,78,226,181,188,217,31,59,61,165,140,145,192,28,64,33,202,165,16,182,154,146,107,12,147,191,111,17,230,236,243,186,148,81,6,217,182,17,116,251,64,205,182,33,119,127,88,40,32,47,177,120,226,251,133,220,109,178,235,98,192,63,
+            202,213,201,20,32,150,118,148,171,51,113,126,147,149,65,1,237,61,202,229,37,151,54,49,56,251,76,65,247,216,183,176,14,144,192,96,249,111,255,27,172,183,193,56,220,227,34,127,131,245,183,157,230,60,39,253,4,211,71,205,249,231,34,204,33,96,6,16,252,22,246,165,44,23,72,168,139,37,132,91,30,249,172,0,40,32,47,9,186,44,15,55,177,186,16,228,12,171,207,12,
+            171,91,131,133,19,71,67,8,111,120,164,109,184,228,37,151,118,176,4,187,200,239,62,247,32,238,99,18,52,152,61,22,87,126,3,254,185,128,242,137,191,238,47,194,216,2,163,128,236,113,183,2,99,38,0,18,210,145,100,62,67,162,44,195,39,158,60,210,18,87,23,3,254,81,86,159,81,86,55,83,128,92,234,61,202,165,167,230,245,184,34,218,125,196,206,111,0,83,40,119,
+            12,125,55,38,193,4,242,222,126,145,179,47,226,167,195,244,169,103,238,178,46,37,203,32,248,213,151,225,249,222,7,98,229,170,30,126,53,36,20,144,151,4,93,170,143,77,57,212,65,133,63,11,153,99,117,202,9,16,75,155,229,234,76,156,63,199,133,145,11,57,174,108,18,226,84,15,59,115,66,185,189,63,238,148,98,51,203,223,231,1,73,142,179,198,229,152,3,78,103,115,
+            11,164,229,2,241,115,210,233,139,195,101,210,18,115,147,16,70,46,233,200,67,15,185,132,91,18,144,80,223,25,212,119,6,52,152,156,1,228,18,63,195,194,103,56,127,131,75,107,185,148,77,92,25,36,217,254,71,158,194,124,44,193,44,244,205,50,142,122,240,196,229,157,243,146,220,230,234,55,97,142,226,48,129,113,6,230,24,31,200,158,35,206,240,246,135,133,34,230,55,88,
+            25,212,156,191,206,192,252,197,234,98,10,245,27,5,196,234,109,50,72,240,155,44,140,60,48,185,52,6,116,25,12,114,155,75,207,112,226,248,60,100,207,215,63,197,124,44,1,249,216,49,241,137,140,50,142,178,199,61,152,4,86,14,247,239,18,198,41,99,206,204,204,135,89,62,144,61,142,115,222,254,176,80,136,252,228,145,150,154,243,82,9,243,18,252,51,144,13,48,227,2,
+            177,116,51,156,127,134,185,27,12,242,105,215,25,46,31,121,96,9,182,54,132,114,143,52,112,125,74,64,92,25,36,57,55,16,71,110,23,238,19,32,203,80,215,97,254,15,128,144,79,245,241,171,33,200,50,253,57,14,66,121,89,206,38,18,252,57,102,231,102,166,155,56,84,161,236,62,200,62,15,84,142,28,167,155,64,239,35,184,31,74,208,231,83,126,31,99,243,45,157,190,
+            109,217,24,193,120,97,144,221,159,155,208,159,49,64,252,120,219,20,79,153,124,217,86,136,116,36,97,7,113,246,54,88,91,16,127,93,141,44,180,147,29,223,96,16,151,126,134,75,51,227,2,121,221,47,127,130,235,73,2,18,108,153,19,236,38,9,140,45,106,170,152,130,219,244,128,4,187,247,225,28,185,225,81,230,129,128,178,76,48,129,125,214,177,167,113,95,146,96,226,138,
+            204,60,228,214,126,107,160,19,16,115,19,23,102,112,144,71,184,17,1,10,208,105,184,96,10,110,51,0,131,149,79,62,245,58,192,164,233,81,6,53,215,9,15,161,61,37,32,15,125,22,250,204,242,129,236,107,122,139,127,154,168,144,208,255,13,236,217,55,90,96,6,227,169,225,18,62,199,133,91,46,250,27,33,116,80,4,251,26,49,65,176,225,40,234,116,212,102,11,214,240,
+            46,52,144,238,168,71,28,217,249,183,56,238,185,187,50,14,87,48,137,48,29,41,116,142,185,45,29,243,144,203,181,90,239,193,217,51,23,136,197,145,71,30,98,238,41,184,167,34,96,72,64,246,184,239,57,62,204,45,220,16,210,24,156,52,66,230,55,132,114,140,22,243,27,39,120,253,140,16,118,25,109,172,159,17,51,196,116,154,3,120,86,99,76,32,124,98,0,247,50,91,
+            246,184,51,30,51,196,223,43,123,162,213,105,82,18,242,88,235,20,113,230,180,152,0,196,202,47,216,103,90,151,29,79,51,126,255,222,140,162,109,196,123,188,54,48,188,81,81,246,236,21,214,42,120,79,105,72,82,201,103,142,113,51,252,55,51,140,0,200,103,173,169,98,190,86,67,66,46,249,114,184,95,228,24,20,102,173,187,18,54,198,128,89,196,245,196,168,176,54,34,9,
+            253,106,23,236,143,129,220,5,168,59,160,144,107,125,11,239,125,173,144,144,144,143,236,53,133,142,123,59,160,144,250,179,57,213,149,156,224,206,73,210,95,202,48,58,148,158,166,63,135,115,121,144,167,150,240,156,203,210,17,211,223,139,61,138,94,9,200,195,254,62,148,221,39,1,121,228,159,192,51,246,4,168,216,127,203,93,235,80,38,118,57,99,231,214,91,48,39,219,113,183,
+            32,204,102,29,198,24,158,255,77,198,117,235,156,245,204,24,158,241,199,36,32,175,241,191,14,227,55,97,200,239,250,91,23,156,223,174,175,1,73,110,249,187,144,38,1,72,114,252,106,189,152,91,83,130,160,191,52,132,115,31,140,65,129,146,75,56,9,246,15,114,105,7,61,40,113,144,144,95,103,118,16,163,50,134,113,12,134,142,116,74,161,157,174,198,2,53,251,227,116,239,
+            56,25,12,164,55,66,64,17,243,27,146,229,18,99,12,239,68,198,36,24,23,202,152,132,127,18,88,25,204,217,9,209,56,31,247,0,48,195,104,0,98,250,231,16,63,7,52,60,179,107,49,51,8,74,156,44,121,64,246,245,178,102,113,250,160,60,37,46,221,160,64,73,18,98,109,80,186,164,75,25,194,94,198,144,4,228,209,255,67,17,243,87,96,79,197,131,1,159,56,98,
+            249,205,48,103,176,243,11,220,196,185,41,228,26,129,98,100,116,59,222,145,1,147,201,81,96,116,98,206,247,128,196,235,239,126,92,95,18,76,108,199,253,219,166,19,247,240,166,123,187,195,36,194,38,37,48,153,157,166,15,147,18,229,16,127,191,34,92,131,46,148,112,126,105,16,148,24,131,28,37,33,174,196,197,81,64,123,87,80,118,133,67,197,253,66,229,200,226,158,146,245,
+            33,23,64,86,72,71,156,238,254,33,172,253,4,122,113,207,233,149,128,184,114,84,206,222,177,31,161,127,37,48,240,188,101,184,80,224,254,86,171,224,1,249,180,103,29,207,178,245,54,65,46,58,251,177,222,239,247,129,2,198,131,201,205,3,21,132,152,156,223,148,100,48,32,94,67,185,26,135,233,18,166,185,196,13,10,101,80,128,253,102,130,144,135,174,2,206,46,21,124,40,
+            6,80,240,128,88,253,138,30,249,136,197,23,2,244,19,43,131,60,100,33,33,251,11,18,118,145,132,253,23,5,80,240,40,187,224,97,255,69,96,132,197,141,112,20,66,230,31,113,201,87,112,169,87,209,165,223,70,124,242,23,36,218,127,132,131,92,218,145,130,236,255,112,230,88,222,237,53,231,239,127,120,200,14,207,35,92,130,178,226,124,243,193,206,67,160,92,235,158,15,43,
+            11,84,57,134,145,110,79,94,81,166,15,177,180,118,158,26,135,100,126,130,156,150,252,134,198,52,7,241,115,72,30,247,150,8,152,104,3,179,133,181,215,77,28,166,139,223,12,1,69,88,127,61,140,246,120,152,81,103,223,36,121,88,160,140,62,209,145,90,119,153,131,171,136,171,10,232,46,84,89,223,233,62,80,27,214,155,122,128,13,122,194,246,232,33,244,235,33,108,212,99,
+            46,87,119,209,163,75,164,91,169,182,142,173,163,27,178,59,37,200,94,91,45,91,252,253,11,155,163,30,212,133,180,141,29,120,198,102,212,89,24,185,140,129,185,15,226,253,231,14,188,27,101,204,34,221,28,11,155,194,154,110,202,5,66,190,41,143,247,38,83,92,184,101,159,193,59,163,117,220,202,39,198,52,226,231,222,36,183,143,238,70,195,62,235,217,19,237,253,146,204,251,
+            52,242,193,8,200,111,36,28,191,155,189,139,24,134,187,194,189,191,169,184,161,176,119,60,77,153,119,190,211,104,83,1,212,202,252,115,16,249,219,0,37,164,139,78,144,250,82,92,247,131,26,202,11,1,185,148,161,173,234,81,180,27,241,204,20,36,111,76,152,48,122,87,5,196,1,98,245,51,217,58,103,36,223,49,207,192,78,7,59,45,185,180,135,122,61,246,6,218,4,217,
+            250,14,194,29,3,196,141,135,178,128,30,97,124,144,223,124,184,186,39,18,155,97,247,230,148,32,123,236,31,196,120,105,51,100,183,115,205,65,189,59,60,182,221,196,216,12,255,230,152,32,137,254,86,79,130,13,17,24,123,6,251,105,18,24,103,226,158,119,166,156,77,97,176,176,127,106,185,240,176,36,13,236,209,54,192,28,220,115,216,251,157,19,32,73,59,234,18,223,200,164,
+            152,235,78,130,110,139,123,166,157,98,126,75,64,195,30,160,198,24,196,121,128,65,80,98,12,2,29,225,58,71,73,56,51,80,198,255,101,23,116,198,190,154,243,109,105,62,15,241,243,75,63,198,65,140,76,76,96,207,126,158,142,121,40,160,173,6,247,161,158,28,37,6,5,228,171,236,195,122,11,12,61,129,247,55,18,144,215,245,118,10,174,27,15,114,111,196,30,185,36,89,
+            6,133,24,39,253,182,158,226,178,182,67,156,13,217,139,187,148,254,213,216,139,118,227,98,188,3,104,178,122,113,156,157,119,0,255,247,126,31,251,255,18,144,88,247,213,78,57,198,122,188,191,21,48,193,1,151,112,195,133,177,71,49,159,73,96,184,228,37,187,255,151,161,15,2,200,225,121,49,231,66,150,145,243,240,147,204,124,191,108,177,30,25,91,178,92,217,189,223,65,251,
+            74,64,205,118,95,134,118,199,115,108,63,67,59,3,239,92,129,6,40,161,249,144,24,26,211,51,0,42,12,117,61,234,37,73,118,55,218,149,145,101,244,239,70,61,128,138,248,30,70,150,165,33,73,187,140,128,231,85,35,98,60,181,160,219,144,124,94,157,138,225,27,199,133,21,139,253,246,253,106,4,165,143,184,220,55,10,24,63,133,8,168,66,127,102,133,254,204,9,125,155,
+            21,250,178,23,215,114,175,4,228,117,189,173,196,28,200,65,33,199,176,122,16,249,98,130,196,178,235,8,111,51,36,94,163,7,163,173,185,211,88,227,83,138,76,249,92,87,20,114,108,117,99,205,219,45,64,110,99,112,167,243,221,71,217,239,48,158,182,74,46,157,219,247,27,85,159,112,226,226,201,195,173,6,96,68,252,86,209,29,12,138,225,254,100,196,244,237,36,138,80,22,
+            37,124,15,54,66,218,115,251,181,206,183,51,197,189,30,179,69,42,74,39,246,73,237,189,85,251,55,158,216,239,52,225,28,111,5,12,179,247,134,149,195,222,237,96,199,15,11,178,130,235,176,2,134,5,127,133,177,139,163,210,140,207,59,229,85,176,95,85,177,169,57,236,230,202,25,246,208,83,1,85,176,155,201,42,131,236,242,106,220,111,90,177,48,10,104,127,138,105,236,25,
+            17,199,220,118,188,107,166,249,58,116,28,171,231,62,174,46,21,48,204,181,69,213,14,203,99,207,156,201,10,228,144,36,21,214,238,85,86,78,117,190,252,142,216,246,234,212,22,160,69,247,121,242,77,163,198,24,79,156,222,125,236,119,161,170,104,143,17,182,103,184,15,254,125,246,251,108,197,121,199,215,124,87,91,101,232,12,66,60,73,176,157,59,31,160,43,206,251,223,102,156,
+            133,51,91,86,8,168,133,60,86,74,101,19,87,30,69,44,155,90,108,47,43,166,252,86,4,157,196,81,193,59,171,10,131,132,184,50,254,47,39,12,113,250,116,110,44,235,94,172,144,163,204,208,57,72,86,135,178,24,59,31,53,109,92,209,158,115,16,94,232,130,109,122,27,48,185,51,44,26,246,152,181,54,67,92,253,173,46,140,229,136,80,132,246,55,217,153,170,114,155,32,
+            65,255,108,150,22,65,18,54,91,72,103,53,81,163,243,176,100,26,114,177,69,195,30,177,150,0,165,10,246,141,5,200,77,255,153,72,159,2,100,207,181,251,177,223,182,21,126,14,18,237,219,186,56,94,164,114,101,70,185,20,242,82,1,146,28,191,198,203,176,230,75,1,98,250,199,126,140,253,88,9,200,195,254,34,202,42,198,8,9,229,23,16,86,104,35,36,232,87,177,175,
+            167,250,64,1,253,171,98,61,175,70,128,66,204,133,253,49,60,175,26,88,151,24,9,65,41,234,167,152,234,79,17,219,151,130,198,203,114,244,187,11,196,197,147,79,58,53,38,136,211,65,130,125,196,185,45,140,57,43,1,8,229,207,109,195,89,63,184,103,125,208,54,164,135,109,35,249,80,70,154,114,0,20,113,188,88,43,209,94,49,65,92,121,228,162,107,22,225,179,2,218,
+            201,201,80,246,128,56,123,42,195,25,197,192,122,212,104,3,228,210,30,19,118,220,218,100,17,117,142,35,108,156,139,31,143,8,9,229,142,135,196,194,59,227,6,222,89,55,24,132,178,26,156,155,4,26,92,218,6,176,144,223,226,32,123,12,174,246,246,39,5,185,216,71,46,182,207,34,237,108,0,228,115,189,206,225,221,253,156,31,171,253,243,27,125,232,119,1,179,140,189,92,
+            238,183,19,168,217,167,125,232,35,129,137,50,206,130,184,49,43,55,23,105,120,7,162,37,0,241,215,245,118,236,127,120,96,97,14,176,18,128,154,115,220,201,232,67,31,44,60,219,88,49,65,110,115,108,63,244,248,160,194,6,53,6,200,167,143,251,17,223,207,65,33,239,73,125,200,211,231,1,69,189,223,109,68,219,5,64,17,117,144,15,7,241,204,126,48,38,168,5,253,55,
+            32,223,13,49,65,62,122,198,17,63,238,130,134,51,82,26,71,9,103,172,74,28,20,96,255,208,227,56,255,37,1,121,217,213,1,59,82,130,236,241,183,21,99,76,2,66,218,198,149,184,143,128,71,177,71,64,205,241,187,117,33,190,21,202,167,97,29,228,129,6,40,32,191,138,52,106,12,228,170,56,7,34,73,31,210,247,113,244,187,64,49,93,159,6,206,180,24,17,25,247,
+            129,98,158,79,52,236,33,104,9,65,18,250,75,231,227,186,245,160,114,62,238,185,54,25,236,115,9,144,56,46,51,24,131,17,24,122,0,215,189,4,20,161,173,75,168,83,255,37,56,159,6,40,68,190,78,238,236,136,129,177,106,68,132,124,116,141,61,137,253,54,9,200,107,60,117,161,239,61,40,93,128,126,21,24,116,9,43,9,144,61,111,127,27,237,47,65,229,2,140,23,
+            14,13,122,7,153,30,13,110,10,104,107,11,207,84,86,76,52,46,197,185,117,200,185,155,177,110,244,120,134,35,23,102,55,97,173,21,35,20,102,62,200,161,175,98,128,90,44,107,176,132,190,98,148,56,6,185,176,65,46,188,82,66,63,219,228,48,47,112,238,18,251,214,190,134,115,133,154,7,196,226,201,37,29,5,228,213,2,160,16,249,43,56,215,72,94,247,147,77,184,102,
+            35,48,58,210,117,12,51,0,114,155,15,126,130,235,93,2,242,176,63,119,17,238,193,32,203,32,132,169,120,103,170,250,64,44,175,234,225,87,67,66,1,121,73,208,165,122,164,49,243,78,93,108,127,150,213,39,199,213,45,203,213,145,64,214,195,159,229,194,84,159,247,199,89,46,29,113,168,46,246,101,133,114,115,204,182,156,11,228,210,63,57,143,120,10,96,116,15,198,22,48,
+            57,70,153,52,176,191,104,112,76,236,193,115,62,207,6,82,198,126,136,241,35,193,4,203,67,208,57,129,124,166,80,62,133,176,153,98,36,183,31,237,14,114,12,21,239,246,84,208,207,36,5,228,239,125,6,231,147,37,32,175,249,122,19,230,144,22,25,196,53,63,200,40,9,16,43,127,144,75,51,200,197,15,114,238,202,29,152,115,71,24,119,224,28,151,205,38,204,107,77,70,
+            184,248,144,104,200,175,113,148,4,251,74,187,160,223,254,230,231,46,128,111,92,106,30,223,185,36,15,134,30,193,186,65,2,242,200,63,97,127,103,244,150,112,120,149,69,45,160,158,142,113,38,73,238,114,140,85,144,99,50,203,220,57,23,178,76,170,62,191,87,69,246,248,125,10,227,83,2,74,232,250,211,58,212,182,65,205,49,215,161,30,99,144,81,18,24,116,9,43,113,101,
+            80,115,252,221,135,241,37,65,25,121,203,160,130,119,82,21,134,14,191,218,141,190,13,65,182,128,190,101,228,36,200,114,238,126,228,39,193,254,35,152,155,142,72,48,139,185,112,54,0,10,217,247,22,242,88,246,59,192,229,238,28,75,183,124,177,59,40,143,229,226,182,90,100,230,194,46,87,8,101,207,112,110,2,13,184,27,44,172,1,44,150,223,98,178,1,230,46,196,30,61,
+            131,36,218,232,200,119,209,254,18,144,87,27,175,71,61,124,32,201,116,86,4,102,118,163,77,24,13,1,106,182,221,110,199,207,167,157,97,97,22,202,152,187,21,207,96,172,44,74,104,46,34,23,42,47,199,253,239,205,184,94,121,238,6,8,47,189,57,216,22,11,207,235,13,236,97,53,92,152,97,52,56,44,164,183,56,230,238,196,88,169,58,4,233,162,4,80,237,119,6,253,
+            201,226,167,223,34,180,67,0,36,153,206,138,200,204,121,232,35,31,200,30,199,231,185,199,53,243,90,40,103,238,60,244,167,0,73,246,199,145,111,225,122,151,96,22,122,102,91,132,56,125,115,227,176,111,188,67,177,240,238,207,138,1,106,94,239,101,180,71,11,144,61,239,150,253,211,240,241,51,66,94,10,192,192,57,71,195,3,226,226,41,32,173,17,1,83,195,243,151,11,196,
+            217,105,50,191,201,197,153,30,249,76,96,176,114,41,160,254,99,22,158,215,36,32,143,252,26,244,104,46,16,23,71,62,233,180,152,24,68,93,7,5,8,122,75,144,37,230,30,100,225,37,78,106,30,231,92,137,49,132,186,15,73,64,73,205,199,125,152,51,99,34,135,107,34,7,178,140,126,132,245,3,138,211,222,147,162,149,247,196,199,157,223,182,141,10,181,160,123,238,114,156,
+            15,194,187,54,163,13,144,135,13,55,35,238,230,22,33,228,159,192,187,195,9,1,10,209,6,134,164,157,148,16,22,158,89,172,22,33,228,191,191,187,195,7,69,185,191,16,80,127,252,173,173,225,1,181,163,254,157,168,75,194,144,159,254,117,72,147,18,4,253,71,176,119,115,68,2,18,175,221,93,88,183,0,138,216,254,170,253,205,118,138,78,179,188,28,214,128,57,129,254,16,
+            235,191,105,140,187,105,142,185,157,25,229,104,136,177,120,228,123,104,47,9,8,105,103,81,238,172,7,100,143,141,85,232,167,152,33,183,49,184,42,217,242,195,208,135,51,139,125,9,209,255,6,188,91,182,89,11,55,32,183,249,232,100,204,61,146,144,144,215,28,198,58,16,225,163,144,163,45,66,62,109,99,34,222,228,56,0,93,7,66,210,243,166,14,165,103,175,243,183,196,3,
+            192,204,5,127,115,138,103,176,136,181,28,160,86,175,247,79,224,122,77,16,10,208,111,224,221,167,145,0,20,99,253,41,226,53,68,62,104,73,127,51,243,70,111,252,236,34,89,251,87,57,223,210,228,191,167,73,33,48,132,239,61,82,80,127,221,134,62,105,35,36,218,219,141,241,37,9,185,213,183,91,62,191,95,57,20,178,60,98,233,39,225,54,177,55,110,130,137,66,230,184,
+            239,237,83,130,99,157,92,208,241,191,158,0,20,131,126,74,216,70,61,164,173,212,74,251,174,112,254,118,188,132,247,228,37,134,14,63,181,169,127,203,248,191,28,35,116,130,140,47,93,210,150,114,204,245,47,135,108,147,34,254,47,182,64,193,7,66,185,133,128,52,5,70,81,162,188,66,140,20,153,125,69,206,77,204,79,66,155,144,71,27,145,68,187,21,60,218,132,2,218,134,
+            66,180,93,161,197,190,43,250,216,89,244,169,35,73,142,25,10,72,67,33,235,88,136,216,223,69,159,250,20,125,236,39,201,235,132,98,168,15,69,188,30,139,45,244,119,53,160,221,10,156,93,67,135,177,143,27,3,147,167,210,34,204,125,93,199,220,19,251,176,31,199,115,106,180,251,144,93,182,41,64,30,225,166,36,20,241,126,151,195,158,122,206,3,10,136,207,73,66,33,117,
+            145,71,90,74,224,126,175,102,177,110,14,9,241,237,55,8,219,60,232,65,218,30,15,136,229,239,157,193,249,29,9,200,197,238,205,45,254,141,17,241,229,108,69,157,66,144,187,18,117,227,232,253,25,236,147,128,92,116,159,122,101,167,162,97,31,69,19,160,230,90,100,45,214,11,1,148,222,128,53,162,7,36,81,255,41,252,63,197,209,192,51,69,195,131,105,110,254,33,198,17,
+            204,33,71,36,32,33,223,28,131,98,160,140,255,203,45,162,215,112,246,137,243,239,19,252,101,151,58,83,130,246,148,99,66,143,152,159,88,93,140,14,60,143,114,144,75,152,145,32,196,233,35,230,38,206,54,10,97,15,249,212,135,132,178,73,192,112,137,51,60,242,27,45,216,79,18,229,145,75,126,35,68,223,144,100,30,138,169,109,141,22,251,155,92,218,217,8,208,67,9,234,
+            167,16,220,137,50,238,140,80,142,206,190,193,168,75,66,2,122,204,80,72,244,24,116,233,17,203,208,219,0,73,183,71,119,11,80,236,223,141,214,149,229,237,225,198,214,199,142,211,94,212,86,168,5,253,36,97,63,197,92,127,106,19,122,30,125,24,166,13,243,225,210,7,234,175,69,235,195,200,249,149,120,9,221,254,74,2,248,212,151,34,182,31,197,140,30,178,110,20,115,251,
+            81,76,246,81,139,249,169,77,227,131,132,254,166,86,251,171,150,32,108,110,161,8,101,80,179,156,60,67,244,231,37,137,88,23,242,169,7,121,196,147,11,186,132,14,242,65,143,1,106,177,124,106,1,61,106,125,149,24,175,25,123,125,174,132,207,19,133,33,201,125,92,242,42,163,22,255,28,45,133,226,232,79,227,247,24,181,132,127,31,134,66,180,131,22,241,247,99,180,151,248,
+            111,215,104,105,219,169,56,191,63,224,121,125,40,201,98,255,77,191,25,1,138,168,63,147,239,78,5,98,250,235,61,148,42,197,101,233,34,254,6,104,61,38,57,21,64,157,251,173,181,130,15,197,0,10,17,243,235,236,62,88,180,101,205,249,109,208,249,61,14,133,133,177,240,2,243,23,25,5,70,145,15,171,113,249,240,251,36,58,123,103,90,116,65,231,169,177,125,21,46,174,
+            200,108,43,50,127,221,110,179,67,221,139,242,23,184,242,234,107,28,105,135,105,2,5,78,87,65,200,87,117,161,200,165,43,112,249,171,92,57,83,107,28,121,220,222,124,173,219,65,241,136,19,220,43,213,116,177,219,109,202,229,55,113,167,2,198,120,221,227,55,116,167,2,174,129,105,70,189,201,50,132,131,122,74,178,184,28,253,11,138,33,165,6,74,23,118,41,23,115,84,88,
+            88,73,130,10,210,14,180,160,183,24,81,14,48,74,204,142,17,140,193,17,197,233,11,190,239,234,33,231,185,122,76,114,58,196,220,89,23,198,227,148,132,28,96,245,29,113,169,179,21,115,253,234,30,215,73,221,231,250,169,7,200,10,214,76,245,67,237,193,254,109,175,10,244,237,201,43,243,191,61,181,11,20,113,222,181,152,34,133,132,57,24,227,247,85,15,182,64,253,204,116,
+            89,137,51,11,43,83,164,136,235,178,216,6,10,9,81,124,137,219,95,144,168,79,181,77,186,171,41,183,115,245,247,164,94,213,4,251,168,126,130,48,149,22,61,237,163,2,125,211,98,248,26,119,92,159,7,151,47,172,191,227,98,0,229,14,128,105,143,240,1,142,41,15,91,167,124,24,88,238,31,159,36,211,46,20,2,158,183,47,10,160,16,49,255,84,27,199,219,148,27,107,
+            218,135,189,70,159,134,156,230,40,224,153,173,144,34,35,88,3,79,97,109,60,213,6,134,177,254,174,178,245,119,181,230,248,163,174,111,45,156,17,183,34,112,67,140,223,255,191,161,5,166,176,70,157,74,145,110,172,81,187,83,196,58,37,25,26,111,196,25,98,142,105,151,176,6,48,48,31,27,192,196,222,129,105,239,99,228,157,223,138,29,251,110,167,20,19,200,123,128,149,97,
+            180,194,186,5,14,112,110,163,93,108,76,150,3,30,76,236,205,204,51,45,204,135,211,109,96,128,187,135,79,167,160,127,154,179,227,178,195,138,178,23,12,99,46,220,131,121,113,216,67,86,216,184,172,52,169,117,204,239,253,84,154,126,69,112,11,236,62,156,153,167,194,151,161,144,178,127,222,173,28,251,125,115,29,12,20,59,20,13,242,82,188,35,27,14,176,107,152,73,157,229,
+            29,14,72,183,59,223,113,204,189,135,229,187,20,122,46,13,192,20,222,83,101,139,14,246,111,179,15,136,228,187,142,217,115,41,87,198,128,93,175,188,243,219,239,205,120,227,38,14,251,111,204,111,114,145,110,172,14,136,115,137,175,64,247,128,210,163,28,184,184,99,30,3,97,234,221,241,211,243,230,14,249,244,39,37,75,143,7,253,151,102,230,49,112,15,50,66,112,0,76,236,
+            199,220,241,201,142,121,105,132,204,111,8,140,224,26,24,73,136,1,9,140,245,106,36,14,248,196,77,114,24,96,20,152,76,26,140,61,236,218,31,62,54,207,40,243,227,212,224,199,241,106,111,236,241,108,120,93,7,55,181,143,129,230,247,81,46,116,238,227,54,163,192,136,114,111,94,222,58,77,253,38,179,97,148,249,71,57,251,76,15,70,5,76,206,109,216,101,231,133,247,246,
+            92,94,35,192,46,21,247,124,181,77,228,118,117,205,163,194,157,133,204,2,107,11,41,71,239,202,28,99,206,102,11,57,242,46,219,221,177,224,190,162,233,119,1,225,71,231,243,117,204,203,163,140,198,21,88,215,129,163,92,216,81,14,35,226,245,102,196,200,40,119,77,154,28,163,46,97,166,75,220,168,224,55,192,216,163,88,151,74,48,177,27,115,168,205,122,204,167,28,134,164,
+            173,134,128,41,196,141,114,24,46,97,163,30,105,12,151,58,27,45,96,186,216,60,234,129,201,217,55,38,217,126,163,66,254,73,134,90,92,150,46,7,83,166,158,46,6,246,116,140,20,185,245,2,172,73,240,187,76,198,9,196,36,152,136,171,188,83,210,69,205,225,26,75,145,28,35,203,185,115,46,100,25,70,26,207,244,235,22,99,226,190,107,2,67,8,55,146,224,116,212,57,
+            69,180,53,233,98,110,71,91,115,140,2,163,19,237,210,38,76,166,211,116,97,148,147,163,140,156,238,144,5,234,102,92,95,41,144,101,54,228,56,178,140,28,231,207,113,168,44,111,239,209,78,41,84,15,221,26,165,139,218,155,46,214,25,233,178,232,155,131,171,122,218,254,141,195,221,246,179,238,106,143,125,31,81,222,148,48,237,210,115,211,2,42,246,197,212,52,185,62,93,194,
+            252,189,128,150,0,106,2,251,127,106,8,140,136,251,103,70,68,172,54,124,207,219,242,65,179,239,1,155,113,126,20,247,20,13,82,99,148,4,191,230,17,166,69,196,232,79,151,193,125,93,243,104,120,23,170,165,192,32,211,63,40,137,41,177,127,102,74,96,120,236,143,77,112,24,182,252,16,158,231,184,61,189,161,239,119,74,161,173,70,253,18,164,116,49,206,59,251,80,177,223,
+            235,92,156,81,84,251,153,233,148,214,201,161,172,28,35,11,84,232,86,83,34,199,108,200,114,54,229,56,219,178,46,126,21,103,28,212,20,209,206,192,115,65,138,148,56,89,226,252,154,139,95,115,137,83,177,39,168,166,73,29,125,184,18,123,59,237,162,222,94,125,113,124,67,200,0,119,180,136,145,18,123,216,59,39,203,126,6,233,77,17,236,105,89,105,146,77,23,109,107,186,
+            68,57,187,101,197,128,138,61,91,53,77,58,210,197,192,53,96,164,136,245,178,228,56,202,152,245,193,178,207,160,173,77,15,213,30,131,167,182,151,62,70,191,237,223,208,126,250,56,140,62,127,38,3,226,141,136,104,246,51,96,103,122,88,56,11,103,165,136,134,53,190,150,34,150,125,15,216,154,30,218,105,237,167,204,49,120,62,158,215,192,32,208,50,136,111,51,37,166,191,228,
+            65,238,18,135,44,80,113,102,81,109,51,89,166,59,203,236,200,113,100,133,240,126,164,239,7,157,182,188,4,231,203,64,233,2,212,131,161,97,189,165,181,153,220,8,108,3,89,160,110,66,157,82,36,203,236,200,50,155,114,1,100,61,236,238,31,65,219,218,108,114,220,98,190,126,132,103,153,187,247,39,120,255,34,129,106,151,117,7,246,43,88,153,253,12,3,126,35,37,76,216,
+            111,216,247,168,13,233,50,186,7,239,234,24,102,0,42,246,84,213,20,201,238,199,152,97,228,60,200,114,50,43,164,215,208,238,90,154,172,75,151,65,188,167,31,100,148,2,24,228,210,13,50,169,158,142,126,72,147,238,116,209,216,239,208,151,36,208,144,86,75,144,146,135,222,65,166,187,36,132,87,238,206,28,247,123,247,86,74,52,170,56,71,25,146,25,206,61,87,197,217,77,
+            134,133,242,230,238,132,27,168,62,191,123,175,182,1,3,107,62,35,69,44,140,81,43,85,148,5,10,28,98,186,130,24,214,76,231,145,62,105,155,11,238,122,189,126,139,216,104,59,138,98,236,4,17,203,177,82,166,177,19,215,175,7,115,59,113,13,55,89,133,107,26,28,21,48,66,252,30,171,145,0,106,194,191,87,170,6,96,36,244,123,165,134,44,55,225,222,211,230,51,52,
+            154,15,106,155,127,143,83,21,48,236,57,162,59,61,90,249,189,35,53,78,182,166,139,248,91,67,90,155,49,183,225,185,8,108,87,22,220,237,228,200,211,157,82,204,110,193,126,180,4,71,175,200,28,251,251,150,6,39,31,219,162,40,63,188,66,57,38,103,16,54,3,74,56,179,80,74,17,83,252,91,165,188,28,205,252,149,26,206,232,49,118,49,134,37,255,38,117,24,82,71,
+            191,235,2,131,40,127,80,208,167,187,196,15,10,108,15,192,12,64,103,229,155,18,105,244,152,49,19,210,99,70,248,45,61,51,102,38,241,62,99,146,195,140,169,156,73,151,114,39,3,210,76,50,70,35,252,182,252,104,12,12,197,244,187,141,67,45,242,240,161,238,112,172,161,197,28,106,49,31,227,210,21,233,83,22,208,125,184,25,247,204,155,83,164,220,252,222,107,45,29,100,
+            127,179,168,156,4,10,250,0,223,98,208,109,89,235,96,208,162,249,110,56,196,189,103,15,39,203,73,181,151,178,216,190,32,202,168,79,89,2,61,33,202,129,253,79,210,54,150,19,178,187,28,129,113,172,57,199,83,100,22,123,176,179,49,115,52,128,39,62,222,121,140,178,61,198,148,20,105,105,206,33,142,238,72,236,199,185,219,253,94,40,29,139,252,219,149,46,229,58,172,197,
+            174,59,188,252,216,26,114,55,194,119,35,221,110,91,230,157,111,182,236,70,185,21,27,101,241,247,98,108,247,101,130,191,194,165,173,216,28,230,64,121,111,58,220,173,92,1,182,187,172,95,247,67,111,165,214,233,143,178,96,195,188,158,60,242,216,40,88,155,51,42,28,85,165,147,251,182,68,199,162,244,21,31,170,76,238,106,134,229,133,111,220,52,81,22,194,230,219,52,31,188,
+            78,79,146,76,74,191,127,144,97,116,229,213,84,169,216,227,226,240,194,184,60,54,86,14,47,192,135,93,214,116,231,133,126,117,233,231,221,243,82,153,247,239,182,251,93,225,198,34,220,21,137,251,223,176,248,221,34,197,35,109,208,189,181,198,16,194,171,12,221,135,225,32,221,74,235,52,245,87,57,134,153,190,170,71,124,53,1,134,37,215,69,213,20,116,14,135,148,213,20,219,
+            173,218,70,221,85,143,177,162,115,99,75,119,113,235,30,227,80,143,145,170,168,191,230,92,159,77,169,39,68,53,44,181,238,5,89,99,110,62,174,198,133,241,110,69,200,139,223,27,169,218,40,46,233,196,176,26,43,183,137,226,82,94,83,214,92,224,211,40,46,122,196,180,138,79,157,130,168,121,212,57,40,157,18,162,124,62,111,205,167,109,252,252,74,68,226,44,191,38,217,102,
+            178,241,74,140,117,140,171,44,37,6,14,71,47,35,169,121,68,247,160,154,130,78,157,211,93,141,25,253,4,169,131,222,42,53,129,208,249,187,23,214,142,53,230,118,67,9,81,14,243,87,221,242,213,66,166,19,210,234,110,241,53,9,251,100,237,175,133,40,79,34,93,53,0,157,215,87,19,244,207,187,201,61,141,226,204,103,209,234,76,137,141,231,106,8,244,128,178,244,192,122,
+            116,75,247,223,62,172,81,246,121,81,243,137,11,162,22,16,198,220,186,120,189,214,146,99,164,182,252,56,54,227,204,197,230,20,209,35,142,55,61,42,53,151,177,210,78,148,54,216,35,234,80,18,108,15,175,114,20,89,253,2,243,97,221,199,151,173,120,216,125,92,56,45,158,51,107,112,47,10,167,197,191,87,173,180,1,94,183,34,198,177,58,132,237,55,37,110,27,61,218,179,
+            221,237,83,147,161,217,183,62,125,220,12,175,137,233,197,48,209,205,245,147,239,245,74,62,215,5,121,216,76,194,248,235,246,73,79,238,99,166,198,16,127,127,93,17,210,37,65,13,182,242,254,195,216,19,20,152,204,224,28,65,138,232,7,83,166,217,63,181,116,216,12,27,54,167,132,30,131,253,147,56,15,58,153,18,122,202,250,39,153,13,186,95,255,222,157,46,126,103,65,244,
+            54,48,41,121,142,105,50,33,116,123,14,172,181,0,63,135,214,98,96,209,188,76,194,125,166,59,2,116,60,121,110,189,20,55,10,35,201,121,81,241,209,225,21,158,231,238,173,110,237,144,119,137,143,147,188,139,110,89,106,49,234,143,131,90,192,88,18,211,230,37,235,210,140,207,71,168,115,179,255,21,137,241,224,151,87,137,56,54,221,168,69,196,175,108,37,65,187,101,168,37,
+            132,221,79,121,143,49,22,87,249,53,73,157,113,160,180,185,29,91,209,147,247,185,214,69,148,136,227,198,139,195,220,115,8,206,235,232,77,20,159,122,136,246,214,90,28,15,110,117,175,181,216,159,34,121,143,251,143,23,249,22,239,13,126,113,97,202,23,245,200,232,142,10,95,190,232,22,210,138,255,254,63,0,0,0,255,255,0,0,0,255,255,99,102,0,0};
+    }
+}

+ 13 - 1
src/Avalonia.Base/Point.cs

@@ -188,7 +188,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Returns a boolean indicating whether the point is equal to the other given point.
+        /// Returns a boolean indicating whether the point is equal to the other given point (bitwise).
         /// </summary>
         /// <param name="other">The other point to test equality against.</param>
         /// <returns>True if this point is equal to other; False otherwise.</returns>
@@ -200,6 +200,18 @@ namespace Avalonia
             // ReSharper enable CompareOfFloatsByEqualityOperator
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the point is equal to the other given point
+        /// (numerically).
+        /// </summary>
+        /// <param name="other">The other point to test equality against.</param>
+        /// <returns>True if this point is equal to other; False otherwise.</returns>
+        public bool NearlyEquals(Point other)
+        {
+            return MathUtilities.AreClose(_x, other._x) &&
+                   MathUtilities.AreClose(_y, other._y);
+        }
+
         /// <summary>
         /// Checks for equality between a point and an object.
         /// </summary>

+ 1 - 0
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -121,6 +121,7 @@ namespace Avalonia.PropertyStore
 
         public void ClearLocalValue()
         {
+            _localValue = default;
             UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
                 _owner,
                 Property,

+ 20 - 7
src/Avalonia.Base/Styling/PropertySetterInstance.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Styling
         private readonly DirectPropertyBase<T>? _directProperty;
         private readonly T _value;
         private IDisposable? _subscription;
-        private bool _isActive;
+        private State _state;
 
         public PropertySetterInstance(
             IStyleable target,
@@ -40,6 +40,8 @@ namespace Avalonia.Styling
             _value = value;
         }
 
+        private bool IsActive => _state == State.Active;
+
         public void Start(bool hasActivator)
         {
             if (hasActivator)
@@ -70,31 +72,35 @@ namespace Avalonia.Styling
 
         public void Activate()
         {
-            if (!_isActive)
+            if (!IsActive)
             {
-                _isActive = true;
+                _state = State.Active;
                 PublishNext();
             }
         }
 
         public void Deactivate()
         {
-            if (_isActive)
+            if (IsActive)
             {
-                _isActive = false;
+                _state = State.Inactive;
                 PublishNext();
             }
         }
 
         public override void Dispose()
         {
+            if (_state == State.Disposed)
+                return;
+            _state = State.Disposed;
+
             if (_subscription is object)
             {
                 var sub = _subscription;
                 _subscription = null;
                 sub.Dispose();
             }
-            else if (_isActive)
+            else if (IsActive)
             {
                 if (_styledProperty is object)
                 {
@@ -114,7 +120,14 @@ namespace Avalonia.Styling
 
         private void PublishNext()
         {
-            PublishNext(_isActive ? new BindingValue<T>(_value) : default);
+            PublishNext(IsActive ? new BindingValue<T>(_value) : default);
+        }
+
+        private enum State
+        {
+            Inactive,
+            Active,
+            Disposed,
         }
     }
 }

+ 2 - 0
src/Avalonia.Base/Visual.cs

@@ -376,7 +376,9 @@ namespace Avalonia
                     if (e.OldValue is IAffectsRender oldValue)
                     {
                         if (sender._affectsRenderWeakSubscriber != null)
+                        {
                             InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber);
+                        }
                     }
 
                     if (e.NewValue is IAffectsRender newValue)

+ 3 - 3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -49,9 +49,9 @@ namespace Avalonia.Build.Tasks
             string projectDirectory,
             string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch)
         {
-            var typeSystem = new CecilTypeSystem(references
-                .Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll"))
-                .Concat(new[] { input }), input);
+            var typeSystem = new CecilTypeSystem(
+                references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")),
+                input);
 
             var asm = typeSystem.TargetAssemblyDefinition;
 

+ 10 - 10
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@@ -128,8 +128,8 @@ namespace Avalonia.Controls.Primitives
 
             if (_inputTarget != null)
             {
-                _inputTarget.PointerEnter += InputTarget_PointerEnter;
-                _inputTarget.PointerLeave += InputTarget_PointerLeave;
+                _inputTarget.PointerEntered += InputTarget_PointerEntered;
+                _inputTarget.PointerExited += InputTarget_PointerExited;
                 _inputTarget.PointerPressed += InputTarget_PointerPressed;
                 _inputTarget.PointerMoved += InputTarget_PointerMoved;
                 _inputTarget.PointerReleased += InputTarget_PointerReleased;
@@ -194,8 +194,8 @@ namespace Avalonia.Controls.Primitives
 
             if (_inputTarget != null)
             {
-                _inputTarget.PointerEnter -= InputTarget_PointerEnter;
-                _inputTarget.PointerLeave -= InputTarget_PointerLeave;
+                _inputTarget.PointerEntered -= InputTarget_PointerEntered;
+                _inputTarget.PointerExited -= InputTarget_PointerExited;
                 _inputTarget.PointerPressed -= InputTarget_PointerPressed;
                 _inputTarget.PointerMoved -= InputTarget_PointerMoved;
                 _inputTarget.PointerReleased -= InputTarget_PointerReleased;
@@ -362,7 +362,7 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <inheritdoc/>
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
             // We only want to bother with the color name tool tip if we can provide color names.
             if (_selectionEllipsePanel != null &&
@@ -373,7 +373,7 @@ namespace Avalonia.Controls.Primitives
 
             UpdatePseudoClasses();
 
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
         }
 
         /// <inheritdoc/>
@@ -848,16 +848,16 @@ namespace Avalonia.Controls.Primitives
             UpdatePseudoClasses();
         }
 
-        /// <inheritdoc cref="InputElement.PointerEnter"/>
-        private void InputTarget_PointerEnter(object? sender, PointerEventArgs args)
+        /// <inheritdoc cref="InputElement.PointerEntered"/>
+        private void InputTarget_PointerEntered(object? sender, PointerEventArgs args)
         {
             _isPointerOver = true;
             UpdatePseudoClasses();
             args.Handled = true;
         }
 
-        /// <inheritdoc cref="InputElement.PointerLeave"/>
-        private void InputTarget_PointerLeave(object? sender, PointerEventArgs args)
+        /// <inheritdoc cref="InputElement.PointerExited"/>
+        private void InputTarget_PointerExited(object? sender, PointerEventArgs args)
         {
             _isPointerOver = false;
             UpdatePseudoClasses();

+ 4 - 4
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@@ -139,18 +139,18 @@ namespace Avalonia.Controls
             }
 
         }
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
 
             if (OwningRow != null)
             {
                 IsMouseOver = true;
             }
         }
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
 
             if (OwningRow != null)
             {

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@@ -855,7 +855,7 @@ namespace Avalonia.Controls
             if (OwningGrid != null && OwningGrid.UseLayoutRounding)
             {
                 var scale = LayoutHelper.GetLayoutScale(HeaderCell);
-                var roundSize = LayoutHelper.RoundLayoutSize(new Size(leftEdge + ActualWidth, 1), scale, scale);
+                var roundSize = LayoutHelper.RoundLayoutSizeUp(new Size(leftEdge + ActualWidth, 1), scale, scale);
                 LayoutRoundedWidth = roundSize.Width - leftEdge;
             }
             else

+ 6 - 6
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -83,9 +83,9 @@ namespace Avalonia.Controls
         {
             PointerPressed += DataGridColumnHeader_PointerPressed;
             PointerReleased += DataGridColumnHeader_PointerReleased;
-            PointerMoved += DataGridColumnHeader_PointerMove;
-            PointerEnter += DataGridColumnHeader_PointerEnter;
-            PointerLeave += DataGridColumnHeader_PointerLeave;
+            PointerMoved += DataGridColumnHeader_PointerMoved;
+            PointerEntered += DataGridColumnHeader_PointerEntered;
+            PointerExited += DataGridColumnHeader_PointerExited;
         }
 
         private void OnAreSeparatorsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
@@ -452,7 +452,7 @@ namespace Avalonia.Controls
             SetDragCursor(mousePosition);
         }
 
-        private void DataGridColumnHeader_PointerEnter(object sender, PointerEventArgs e)
+        private void DataGridColumnHeader_PointerEntered(object sender, PointerEventArgs e)
         {
             if (!IsEnabled)
             {
@@ -464,7 +464,7 @@ namespace Avalonia.Controls
             UpdatePseudoClasses();
         }
 
-        private void DataGridColumnHeader_PointerLeave(object sender, PointerEventArgs e)
+        private void DataGridColumnHeader_PointerExited(object sender, PointerEventArgs e)
         {
             if (!IsEnabled)
             {
@@ -506,7 +506,7 @@ namespace Avalonia.Controls
             UpdatePseudoClasses();
         }
 
-        private void DataGridColumnHeader_PointerMove(object sender, PointerEventArgs e)
+        private void DataGridColumnHeader_PointerMoved(object sender, PointerEventArgs e)
         {
             if (OwningGrid == null || !IsEnabled)
             {

+ 4 - 4
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -607,15 +607,15 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
             IsMouseOver = true;
         }
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
             IsMouseOver = false;
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
         }
 
         internal void ApplyCellsState()

+ 4 - 4
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@@ -375,7 +375,7 @@ namespace Avalonia.Controls
             ApplyHeaderStatus();
         }
 
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
             if (IsEnabled)
             {
@@ -383,10 +383,10 @@ namespace Avalonia.Controls
                 UpdatePseudoClasses();
             }
 
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
         }
 
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
             if (IsEnabled)
             {
@@ -394,7 +394,7 @@ namespace Avalonia.Controls
                 UpdatePseudoClasses();
             }
 
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
         }
 
         private void SetIsCheckedNoCallBack(bool value)

+ 4 - 4
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@@ -158,23 +158,23 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
             if (OwningRow != null)
             {
                 OwningRow.IsMouseOver = true;
             }
 
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
         }
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
             if (OwningRow != null)
             {
                 OwningRow.IsMouseOver = false;
             }
 
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
         }
 
         //TODO TabStop

+ 6 - 6
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -189,7 +189,7 @@ namespace Avalonia.Controls.Primitives
 
                 EventHandler<PointerPressedEventArgs> cellMouseLeftButtonDown = Cell_MouseLeftButtonDown;
                 EventHandler<PointerReleasedEventArgs> cellMouseLeftButtonUp = Cell_MouseLeftButtonUp;
-                EventHandler<PointerEventArgs> cellMouseEnter = Cell_MouseEnter;
+                EventHandler<PointerEventArgs> cellMouseEntered = Cell_MouseEntered;
                 EventHandler<RoutedEventArgs> cellClick = Cell_Click;
 
                 for (int i = 1; i < Calendar.RowsPerMonth; i++)
@@ -206,7 +206,7 @@ namespace Avalonia.Controls.Primitives
                         cell.SetValue(Grid.ColumnProperty, j);
                         cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown;
                         cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp;
-                        cell.PointerEnter += cellMouseEnter;
+                        cell.PointerEntered += cellMouseEntered;
                         cell.Click += cellClick;
                         children.Add(cell);
                     }
@@ -222,7 +222,7 @@ namespace Avalonia.Controls.Primitives
 
                 EventHandler<PointerPressedEventArgs> monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown;
                 EventHandler<PointerReleasedEventArgs> monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp;
-                EventHandler<PointerEventArgs> monthMouseEnter = Month_MouseEnter;
+                EventHandler<PointerEventArgs> monthMouseEntered = Month_MouseEntered;
 
                 for (int i = 0; i < Calendar.RowsPerYear; i++)
                 {
@@ -238,7 +238,7 @@ namespace Avalonia.Controls.Primitives
                         month.SetValue(Grid.ColumnProperty, j);
                         month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown;
                         month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp;
-                        month.PointerEnter += monthMouseEnter;
+                        month.PointerEntered += monthMouseEntered;
                         children.Add(month);
                     }
                 }
@@ -876,7 +876,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        internal void Cell_MouseEnter(object? sender, PointerEventArgs e)
+        internal void Cell_MouseEntered(object? sender, PointerEventArgs e)
         {
             if (Owner != null)
             {
@@ -1162,7 +1162,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        private void Month_MouseEnter(object? sender, PointerEventArgs e)
+        private void Month_MouseEntered(object? sender, PointerEventArgs e)
         {
             if (_isMouseLeftButtonDownYearView)
             {

+ 19 - 15
src/Avalonia.Controls/ComboBox.cs

@@ -181,26 +181,13 @@ namespace Avalonia.Controls
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTree(e);
-            this.UpdateSelectionBoxItem(SelectedItem);
+            UpdateSelectionBoxItem(SelectedItem);
         }
 
-        // Because the SelectedItem isn't connected to the visual tree
         public override void InvalidateMirrorTransform()
         {
             base.InvalidateMirrorTransform();
-
-            if (SelectedItem is Control selectedControl)
-            {
-                selectedControl.InvalidateMirrorTransform();
-
-                foreach (var visual in selectedControl.GetVisualDescendants())
-                {
-                    if (visual is Control childControl)
-                    {
-                        childControl.InvalidateMirrorTransform();
-                    }
-                }
-            }
+            UpdateFlowDirection();
         }
 
         /// <inheritdoc/>
@@ -365,6 +352,8 @@ namespace Avalonia.Controls
             {
                 parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen);
             }
+
+            UpdateFlowDirection();
         }
 
         private void IsVisibleChanged(bool isVisible)
@@ -432,6 +421,8 @@ namespace Avalonia.Controls
                         }
                     };
                 }
+
+                UpdateFlowDirection();
             }
             else
             {
@@ -439,6 +430,19 @@ namespace Avalonia.Controls
             }
         }
 
+        private void UpdateFlowDirection()
+        {
+            if (SelectionBoxItem is Rectangle rectangle)
+            {
+                if ((rectangle.Fill as VisualBrush)?.Visual is Control content)
+                {
+                    var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? 
+                        FlowDirection.LeftToRight;
+                    rectangle.FlowDirection = flowDirection;
+                }
+            }
+        }
+
         private void SelectFocusedItem()
         {
             foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)

+ 189 - 10
src/Avalonia.Controls/Control.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.ComponentModel;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Documents;
@@ -10,6 +11,7 @@ using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.Styling;
+using Avalonia.Threading;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
@@ -53,21 +55,57 @@ namespace Avalonia.Controls
         /// Event raised when an element wishes to be scrolled into view.
         /// </summary>
         public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
-            RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
+            RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>(
+                "RequestBringIntoView",
+                RoutingStrategies.Bubble);
 
         /// <summary>
         /// Provides event data for the <see cref="ContextRequested"/> event.
         /// </summary>
         public static readonly RoutedEvent<ContextRequestedEventArgs> ContextRequestedEvent =
-            RoutedEvent.Register<Control, ContextRequestedEventArgs>(nameof(ContextRequested),
+            RoutedEvent.Register<Control, ContextRequestedEventArgs>(
+                nameof(ContextRequested),
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
         
+        /// <summary>
+        /// Defines the <see cref="Loaded"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<RoutedEventArgs> LoadedEvent =
+            RoutedEvent.Register<Control, RoutedEventArgs>(
+                nameof(Loaded),
+                RoutingStrategies.Direct);
+
+        /// <summary>
+        /// Defines the <see cref="Unloaded"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<RoutedEventArgs> UnloadedEvent =
+            RoutedEvent.Register<Control, RoutedEventArgs>(
+                nameof(Unloaded),
+                RoutingStrategies.Direct);
+
         /// <summary>
         /// Defines the <see cref="FlowDirection"/> property.
         /// </summary>
         public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
-            AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
-
+            AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(
+                nameof(FlowDirection),
+                inherits: true);
+
+        // Note the following:
+        // _loadedQueue :
+        //   Is the queue where any control will be added to indicate that its loaded
+        //   event should be scheduled and called later.
+        // _loadedProcessingQueue :
+        //   Contains a copied snapshot of the _loadedQueue at the time when processing
+        //   starts and individual events are being fired. This was needed to avoid
+        //   exceptions if new controls were added in the Loaded event itself.
+
+        private static bool _isLoadedProcessing = false;
+        private static readonly HashSet<Control> _loadedQueue = new HashSet<Control>();
+        private static readonly HashSet<Control> _loadedProcessingQueue = new HashSet<Control>();
+
+        private bool _isAttachedToVisualTree = false;
+        private bool _isLoaded = false;
         private DataTemplates? _dataTemplates;
         private IControl? _focusAdorner;
         private AutomationPeer? _automationPeer;
@@ -108,6 +146,15 @@ namespace Avalonia.Controls
             set => SetValue(ContextFlyoutProperty, value);
         }
 
+        /// <summary>
+        /// Gets a value indicating whether the control is fully constructed in the visual tree
+        /// and both layout and render are complete.
+        /// </summary>
+        /// <remarks>
+        /// This is set to true while raising the <see cref="Loaded"/> event.
+        /// </remarks>
+        public bool IsLoaded => _isLoaded;
+
         /// <summary>
         /// Gets or sets a user-defined object attached to the control.
         /// </summary>
@@ -135,6 +182,35 @@ namespace Avalonia.Controls
             remove => RemoveHandler(ContextRequestedEvent, value);
         }
 
+        /// <summary>
+        /// Occurs when the control has been fully constructed in the visual tree and both
+        /// layout and render are complete.
+        /// </summary>
+        /// <remarks>
+        /// This event is guaranteed to occur after the control template is applied and references
+        /// to objects created after the template is applied are available. This makes it different
+        /// from OnAttachedToVisualTree which doesn't have these references. This event occurs at the
+        /// latest possible time in the control creation life-cycle.
+        /// </remarks>
+        public event EventHandler<RoutedEventArgs>? Loaded
+        {
+            add => AddHandler(LoadedEvent, value);
+            remove => RemoveHandler(LoadedEvent, value);
+        }
+
+        /// <summary>
+        /// Occurs when the control is removed from the visual tree.
+        /// </summary>
+        /// <remarks>
+        /// This is API symmetrical with <see cref="Loaded"/> and exists for compatibility with other
+        /// XAML frameworks; however, it behaves the same as OnDetachedFromVisualTree.
+        /// </remarks>
+        public event EventHandler<RoutedEventArgs>? Unloaded
+        {
+            add => AddHandler(UnloadedEvent, value);
+            remove => RemoveHandler(UnloadedEvent, value);
+        }
+
         public new IControl? Parent => (IControl?)base.Parent;
 
         /// <summary>
@@ -215,18 +291,124 @@ namespace Avalonia.Controls
         /// <returns>The control that receives the focus adorner.</returns>
         protected virtual IControl? GetTemplateFocusTarget() => this;
 
+        private static Action loadedProcessingAction = () =>
+        {
+            // Copy the loaded queue for processing
+            // There was a possibility of the "Collection was modified; enumeration operation may not execute."
+            // exception when only a single hash set was used. This could happen when new controls are added
+            // within the Loaded callback/event itself. To fix this, two hash sets are used and while one is
+            // being processed the other accepts adding new controls to process next.
+            _loadedProcessingQueue.Clear();
+            foreach (Control control in _loadedQueue)
+            {
+                _loadedProcessingQueue.Add(control);
+            }
+            _loadedQueue.Clear();
+
+            foreach (Control control in _loadedProcessingQueue)
+            {
+                control.OnLoadedCore();
+            }
+
+            _loadedProcessingQueue.Clear();
+            _isLoadedProcessing = false;
+
+            // Restart if any controls were added to the queue while processing
+            if (_loadedQueue.Count > 0)
+            {
+                _isLoadedProcessing = true;
+                Dispatcher.UIThread.Post(loadedProcessingAction!, DispatcherPriority.Loaded);
+            }
+        };
+
+        /// <summary>
+        /// Schedules <see cref="OnLoadedCore"/> to be called for this control.
+        /// For performance, it will be queued with other controls.
+        /// </summary>
+        internal void ScheduleOnLoadedCore()
+        {
+            if (_isLoaded == false)
+            {
+                bool isAdded = _loadedQueue.Add(this);
+
+                if (isAdded &&
+                    _isLoadedProcessing == false)
+                {
+                    _isLoadedProcessing = true;
+                    Dispatcher.UIThread.Post(loadedProcessingAction!, DispatcherPriority.Loaded);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Invoked as the first step of marking the control as loaded and raising the
+        /// <see cref="Loaded"/> event.
+        /// </summary>
+        internal void OnLoadedCore()
+        {
+            if (_isLoaded == false &&
+                _isAttachedToVisualTree)
+            {
+                _isLoaded = true;
+                OnLoaded();
+            }
+        }
+
+        /// <summary>
+        /// Invoked as the first step of marking the control as unloaded and raising the
+        /// <see cref="Unloaded"/> event.
+        /// </summary>
+        internal void OnUnloadedCore()
+        {
+            if (_isLoaded)
+            {
+                // Remove from the loaded event queue here as a failsafe in case the control
+                // is detached before the dispatcher runs the Loaded jobs.
+                _loadedQueue.Remove(this);
+
+                _isLoaded = false;
+                OnUnloaded();
+            }
+        }
+
+        /// <summary>
+        /// Invoked just before the <see cref="Loaded"/> event.
+        /// </summary>
+        protected virtual void OnLoaded()
+        {
+            var eventArgs = new RoutedEventArgs(LoadedEvent);
+            eventArgs.Source = null;
+            RaiseEvent(eventArgs);
+        }
+
+        /// <summary>
+        /// Invoked just before the <see cref="Unloaded"/> event.
+        /// </summary>
+        protected virtual void OnUnloaded()
+        {
+            var eventArgs = new RoutedEventArgs(UnloadedEvent);
+            eventArgs.Source = null;
+            RaiseEvent(eventArgs);
+        }
+
         /// <inheritdoc/>
         protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTreeCore(e);
+            _isAttachedToVisualTree = true;
 
             InitializeIfNeeded();
+
+            ScheduleOnLoadedCore();
         }
 
         /// <inheritdoc/>
         protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
             base.OnDetachedFromVisualTreeCore(e);
+            _isAttachedToVisualTree = false;
+
+            OnUnloadedCore();
         }
 
         /// <inheritdoc/>
@@ -324,7 +506,9 @@ namespace Avalonia.Controls
                 var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.OpenContextMenu;
 
                 if (keymap is null)
+                {
                     return;
+                }
 
                 var matches = false;
 
@@ -378,17 +562,12 @@ namespace Avalonia.Controls
             bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
             bool parentBypassFlowDirectionPolicies = false;
 
-            var parent = this.FindAncestorOfType<Control>();
+            var parent = ((IVisual)this).VisualParent as Control;
             if (parent != null)
             {
                 parentFlowDirection = parent.FlowDirection;
                 parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
             }
-            else if (Parent is Control logicalParent)
-            {
-                parentFlowDirection = logicalParent.FlowDirection;
-                parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies;
-            }
 
             bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
             bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;

+ 1 - 1
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@@ -26,7 +26,7 @@ namespace Avalonia.Controls.Converters
 
             if (visibility == ScrollBarVisibility.Auto)
             {
-                if (extent == viewport)
+                if (MathUtilities.AreClose(extent, viewport))
                 {
                     return false;
                 }

+ 1 - 1
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@@ -194,7 +194,7 @@ namespace Avalonia.Controls
 
             if (ClockIdentifier == "12HourClock")
             {
-                hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr;
+                hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
             }
 
             Time = new TimeSpan(hr, min, 0);

+ 2 - 2
src/Avalonia.Controls/GridSplitter.cs

@@ -349,9 +349,9 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
 
             GridResizeDirection direction = GetEffectiveResizeDirection();
 

+ 30 - 22
src/Avalonia.Controls/MenuItem.cs

@@ -79,25 +79,33 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Click"/> event.
         /// </summary>
         public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
-            RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble);
+            RoutedEvent.Register<MenuItem, RoutedEventArgs>(
+                nameof(Click),
+                RoutingStrategies.Bubble);
 
         /// <summary>
-        /// Defines the <see cref="PointerEnterItem"/> event.
+        /// Defines the <see cref="PointerEnteredItem"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerEnterItemEvent =
-            RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble);
+        public static readonly RoutedEvent<PointerEventArgs> PointerEnteredItemEvent =
+            RoutedEvent.Register<MenuItem, PointerEventArgs>(
+                nameof(PointerEnteredItem),
+                RoutingStrategies.Bubble);
 
         /// <summary>
-        /// Defines the <see cref="PointerLeaveItem"/> event.
+        /// Defines the <see cref="PointerExitedItem"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerLeaveItemEvent =
-            RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
+        public static readonly RoutedEvent<PointerEventArgs> PointerExitedItemEvent =
+            RoutedEvent.Register<MenuItem, PointerEventArgs>(
+                nameof(PointerExitedItem),
+                RoutingStrategies.Bubble);
 
         /// <summary>
         /// Defines the <see cref="SubmenuOpened"/> event.
         /// </summary>
         public static readonly RoutedEvent<RoutedEventArgs> SubmenuOpenedEvent =
-            RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(SubmenuOpened), RoutingStrategies.Bubble);
+            RoutedEvent.Register<MenuItem, RoutedEventArgs>(
+                nameof(SubmenuOpened),
+                RoutingStrategies.Bubble);
 
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
@@ -174,24 +182,24 @@ namespace Avalonia.Controls
         /// Occurs when the pointer enters a menu item.
         /// </summary>
         /// <remarks>
-        /// A bubbling version of the <see cref="InputElement.PointerEnter"/> event for menu items.
+        /// A bubbling version of the <see cref="InputElement.PointerEntered"/> event for menu items.
         /// </remarks>
-        public event EventHandler<PointerEventArgs>? PointerEnterItem
+        public event EventHandler<PointerEventArgs>? PointerEnteredItem
         {
-            add { AddHandler(PointerEnterItemEvent, value); }
-            remove { RemoveHandler(PointerEnterItemEvent, value); }
+            add { AddHandler(PointerEnteredItemEvent, value); }
+            remove { RemoveHandler(PointerEnteredItemEvent, value); }
         }
 
         /// <summary>
         /// Raised when the pointer leaves a menu item.
         /// </summary>
         /// <remarks>
-        /// A bubbling version of the <see cref="InputElement.PointerLeave"/> event for menu items.
+        /// A bubbling version of the <see cref="InputElement.PointerExited"/> event for menu items.
         /// </remarks>
-        public event EventHandler<PointerEventArgs>? PointerLeaveItem
+        public event EventHandler<PointerEventArgs>? PointerExitedItem
         {
-            add { AddHandler(PointerLeaveItemEvent, value); }
-            remove { RemoveHandler(PointerLeaveItemEvent, value); }
+            add { AddHandler(PointerExitedItemEvent, value); }
+            remove { RemoveHandler(PointerExitedItemEvent, value); }
         }
 
         /// <summary>
@@ -437,22 +445,22 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
 
             var point = e.GetCurrentPoint(null);
-            RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+            RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
                 e.Timestamp, point.Properties, e.KeyModifiers));
         }
 
         /// <inheritdoc/>
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
 
             var point = e.GetCurrentPoint(null);
-            RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+            RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
                 e.Timestamp, point.Properties, e.KeyModifiers));
         }
 

+ 8 - 8
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -53,9 +53,9 @@ namespace Avalonia.Controls.Platform
             Menu.PointerPressed += PointerPressed;
             Menu.PointerReleased += PointerReleased;
             Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
-            Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
-            Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
-            Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
+            Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened);
+            Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
+            Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
             Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
 
             _root = Menu.VisualRoot;
@@ -89,9 +89,9 @@ namespace Avalonia.Controls.Platform
             Menu.PointerPressed -= PointerPressed;
             Menu.PointerReleased -= PointerReleased;
             Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
-            Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
-            Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
-            Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
+            Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened);
+            Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
+            Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited);
             Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
 
             if (_root is InputElement inputRoot)
@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Platform
             e.Handled = true;
         }
 
-        protected internal virtual void PointerEnter(object? sender, PointerEventArgs e)
+        protected internal virtual void PointerEntered(object? sender, PointerEventArgs e)
         {
             var item = GetMenuItem(e.Source as IControl);
 
@@ -358,7 +358,7 @@ namespace Avalonia.Controls.Platform
             }
         }
 
-        protected internal virtual void PointerLeave(object? sender, PointerEventArgs e)
+        protected internal virtual void PointerExited(object? sender, PointerEventArgs e)
         {
             var item = GetMenuItem(e.Source as IControl);
 

+ 2 - 2
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -635,8 +635,8 @@ namespace Avalonia.Controls.Presenters
 
             if (useLayoutRounding)
             {
-                sizeForChild = LayoutHelper.RoundLayoutSize(sizeForChild, scale, scale);
-                availableSize = LayoutHelper.RoundLayoutSize(availableSize, scale, scale);
+                sizeForChild = LayoutHelper.RoundLayoutSizeUp(sizeForChild, scale, scale);
+                availableSize = LayoutHelper.RoundLayoutSizeUp(availableSize, scale, scale);
             }
 
             switch (horizontalContentAlignment)

+ 4 - 4
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -218,9 +218,9 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnPointerEnter(PointerEventArgs e)
+        protected override void OnPointerEntered(PointerEventArgs e)
         {
-            base.OnPointerEnter(e);
+            base.OnPointerEntered(e);
 
             if (AllowAutoHide)
             {
@@ -228,9 +228,9 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnPointerLeave(PointerEventArgs e)
+        protected override void OnPointerExited(PointerEventArgs e)
         {
-            base.OnPointerLeave(e);
+            base.OnPointerExited(e);
 
             if (AllowAutoHide)
             {

+ 5 - 3
src/Avalonia.Controls/TextBlock.cs

@@ -113,7 +113,9 @@ namespace Avalonia.Controls
         /// Defines the <see cref="TextAlignment"/> property.
         /// </summary>
         public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
-            AvaloniaProperty.RegisterAttached<TextBlock, Control, TextAlignment>(nameof(TextAlignment), 
+            AvaloniaProperty.RegisterAttached<TextBlock, Control, TextAlignment>(
+                nameof(TextAlignment), 
+                defaultValue: TextAlignment.Start,
                 inherits: true);
 
         /// <summary>
@@ -748,14 +750,14 @@ namespace Avalonia.Controls
             {
                 if (textSourceIndex > _text.Length)
                 {
-                    return null;
+                    return new TextEndOfParagraph();
                 }
 
                 var runText = _text.Skip(textSourceIndex);
 
                 if (runText.IsEmpty)
                 {
-                    return null;
+                    return new TextEndOfParagraph();
                 }
 
                 return new TextCharacters(runText, _defaultProperties);

+ 26 - 48
src/Avalonia.Controls/TextBox.cs

@@ -53,7 +53,7 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<char> PasswordCharProperty =
             AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
-
+            
         public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
             AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush));
 
@@ -196,7 +196,6 @@ namespace Avalonia.Controls
         private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
         private bool _isUndoingRedoing;
-        private bool _ignoreTextChanges;
         private bool _canCut;
         private bool _canCopy;
         private bool _canPaste;
@@ -276,7 +275,7 @@ namespace Avalonia.Controls
             get => GetValue(IsReadOnlyProperty);
             set => SetValue(IsReadOnlyProperty, value);
         }
-
+        
         public char PasswordChar
         {
             get => GetValue(PasswordCharProperty);
@@ -368,21 +367,17 @@ namespace Avalonia.Controls
             get => _text;
             set
             {
-                if (!_ignoreTextChanges)
-                {
-                    var caretIndex = CaretIndex;
-                    var selectionStart = SelectionStart;
-                    var selectionEnd = SelectionEnd;
+                var caretIndex = CaretIndex;
+                var selectionStart = SelectionStart;
+                var selectionEnd = SelectionEnd;
                     
-                    CaretIndex = CoerceCaretIndex(caretIndex, value);
-                    SelectionStart = CoerceCaretIndex(selectionStart, value);
-                    SelectionEnd = CoerceCaretIndex(selectionEnd, value);
-
-                    if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
-                    {
-                        _undoRedoHelper.Clear();
-                        SnapshotUndoRedo(); // so we always have an initial state
-                    }
+                CaretIndex = CoerceCaretIndex(caretIndex, value);
+                SelectionStart = CoerceCaretIndex(selectionStart, value);
+                SelectionEnd = CoerceCaretIndex(selectionEnd, value);
+                if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
+                {
+                    _undoRedoHelper.Clear();
+                    SnapshotUndoRedo(); // so we always have an initial state
                 }
             }
         }
@@ -736,32 +731,23 @@ namespace Avalonia.Controls
             {
                 var oldText = _text;
 
-                _ignoreTextChanges = true;
-
-                try
-                {
-                    DeleteSelection(false);
-                    var caretIndex = CaretIndex;
-                    text = Text ?? string.Empty;
-                    SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
-                    ClearSelection();
-                    
-                    if (IsUndoEnabled)
-                    {
-                        _undoRedoHelper.DiscardRedo();
-                    }
-
-                    if (_text != oldText)
-                    {
-                        RaisePropertyChanged(TextProperty, oldText, _text);
-                    }
+                DeleteSelection(false);
+                var caretIndex = CaretIndex;
+                text = Text ?? string.Empty;
+                SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
+                ClearSelection();
                     
-                    CaretIndex = caretIndex + input.Length;
+                if (IsUndoEnabled)
+                {
+                    _undoRedoHelper.DiscardRedo();
                 }
-                finally
+
+                if (_text != oldText)
                 {
-                    _ignoreTextChanges = false;
+                    RaisePropertyChanged(TextProperty, oldText, _text);
                 }
+                    
+                CaretIndex = caretIndex + input.Length;
             }
         }
 
@@ -1499,15 +1485,7 @@ namespace Avalonia.Controls
         {
             if (raiseTextChanged)
             {
-                try
-                {
-                    _ignoreTextChanges = true;
-                    SetAndRaise(TextProperty, ref _text, value);
-                }
-                finally
-                {
-                    _ignoreTextChanges = false;
-                }
+                SetAndRaise(TextProperty, ref _text, value);
             }
             else
             {

+ 6 - 6
src/Avalonia.Controls/ToolTipService.cs

@@ -26,14 +26,14 @@ namespace Avalonia.Controls
 
             if (e.OldValue != null)
             {
-                control.PointerEnter -= ControlPointerEnter;
-                control.PointerLeave -= ControlPointerLeave;
+                control.PointerEntered -= ControlPointerEntered;
+                control.PointerExited -= ControlPointerExited;
             }
 
             if (e.NewValue != null)
             {
-                control.PointerEnter += ControlPointerEnter;
-                control.PointerLeave += ControlPointerLeave;
+                control.PointerEntered += ControlPointerEntered;
+                control.PointerExited += ControlPointerExited;
             }
 
             if (ToolTip.GetIsOpen(control) && e.NewValue != e.OldValue && !(e.NewValue is ToolTip))
@@ -80,7 +80,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void ControlPointerEnter(object? sender, PointerEventArgs e)
+        private void ControlPointerEntered(object? sender, PointerEventArgs e)
         {
             StopTimer();
 
@@ -101,7 +101,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void ControlPointerLeave(object? sender, PointerEventArgs e)
+        private void ControlPointerExited(object? sender, PointerEventArgs e)
         {
             var control = (Control)sender!;
             Close(control);

+ 20 - 1
src/Avalonia.Controls/WindowBase.cs

@@ -169,7 +169,6 @@ namespace Avalonia.Controls
             }
         }
 
-
         [Obsolete("No longer used. Has no effect.")]
         protected IDisposable BeginAutoSizing() => Disposable.Empty;
 
@@ -186,6 +185,26 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <inheritdoc/>
+        protected override void OnClosed(EventArgs e)
+        {
+            // Window must manually raise Loaded/Unloaded events as it is a visual root and
+            // does not raise OnAttachedToVisualTreeCore/OnDetachedFromVisualTreeCore events
+            OnUnloadedCore();
+
+            base.OnClosed(e);
+        }
+
+        /// <inheritdoc/>
+        protected override void OnOpened(EventArgs e)
+        {
+            // Window must manually raise Loaded/Unloaded events as it is a visual root and
+            // does not raise OnAttachedToVisualTreeCore/OnDetachedFromVisualTreeCore events
+            ScheduleOnLoadedCore();
+
+            base.OnOpened(e);
+        }
+
         protected override void HandleClosed()
         {
             _ignoreVisibilityChange = true;

+ 2 - 2
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@@ -121,8 +121,8 @@ namespace Avalonia.Diagnostics.Views
 
             if (header != null)
             {
-                header.PointerEnter += AddAdorner;
-                header.PointerLeave += RemoveAdorner;
+                header.PointerEntered += AddAdorner;
+                header.PointerExited += RemoveAdorner;
             }
 
             item.TemplateApplied -= TreeViewItemTemplateApplied;

+ 30 - 5
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -472,10 +472,14 @@ namespace Avalonia.Win32
             {
                 GetWindowRect(_hwnd, out var rc);
 
-                return new PixelPoint(rc.left, rc.top);
+                var border = HiddenBorderSize;
+                return new PixelPoint(rc.left + border.Width, rc.top + border.Height);
             }
             set
             {
+                var border = HiddenBorderSize;
+                value = new PixelPoint(value.X - border.Width, value.Y - border.Height);
+
                 SetWindowPos(
                     Handle.Handle,
                     IntPtr.Zero,
@@ -489,6 +493,24 @@ namespace Avalonia.Win32
 
         private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full;
 
+        private PixelSize HiddenBorderSize
+        {
+            get
+            {
+                // Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing
+                if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations)
+                {
+                    return PixelSize.Empty;
+                }
+
+                DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var clientRect, Marshal.SizeOf(typeof(RECT)));
+                GetWindowRect(_hwnd, out var frameRect);
+                var borderWidth = GetSystemMetrics(SystemMetric.SM_CXBORDER);
+
+                return new PixelSize(clientRect.left - frameRect.left - borderWidth, 0);
+            }
+        }
+
         public void Move(PixelPoint point) => Position = point;
 
         public void SetMinMaxSize(Size minSize, Size maxSize)
@@ -907,17 +929,20 @@ namespace Avalonia.Win32
                 borderCaptionThickness.top = 1;
             }
 
+            //using a default margin of 0 when using WinUiComp removes artefacts when resizing. See issue #8316
+            var defaultMargin = _isUsingComposition ? 0 : 1;
+
             MARGINS margins = new MARGINS();
-            margins.cxLeftWidth = 1;
-            margins.cxRightWidth = 1;
-            margins.cyBottomHeight = 1;
+            margins.cxLeftWidth = defaultMargin;
+            margins.cxRightWidth = defaultMargin;
+            margins.cyBottomHeight = defaultMargin;
 
             if (_extendTitleBarHint != -1)
             {
                 borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
             }
 
-            margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
+            margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : defaultMargin;
 
             if (WindowState == WindowState.Maximized)
             {

+ 15 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@@ -17,6 +17,21 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
         }
 
+        [Fact]
+        public void ClearValue_Resets_Value_To_Style_value()
+        {
+            Class1 target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
+            target.SetValue(Class1.FooProperty, "local");
+
+            Assert.Equal("local", target.GetValue(Class1.FooProperty));
+
+            target.ClearValue(Class1.FooProperty);
+
+            Assert.Equal("style", target.GetValue(Class1.FooProperty));
+        }
+
         [Fact]
         public void ClearValue_Raises_PropertyChanged()
         {

+ 26 - 26
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@@ -189,7 +189,7 @@ namespace Avalonia.Base.UnitTests.Input
 
             // Ensure that e.Handled is reset between controls.
             root.PointerMoved += (s, e) => e.Handled = true;
-            decorator.PointerEnter += (s, e) => e.Handled = true;
+            decorator.PointerEntered += (s, e) => e.Handled = true;
 
             SetHit(renderer, decorator);
             impl.Object.Input!(CreateRawPointerMovedArgs(device, root));
@@ -231,7 +231,7 @@ namespace Avalonia.Base.UnitTests.Input
                 }
             });
 
-            AddEnterLeaveHandlers(HandleEvent, canvas, decorator);
+            AddEnteredExitedHandlers(HandleEvent, canvas, decorator);
 
             // Enter decorator
             SetHit(renderer, decorator);
@@ -246,17 +246,17 @@ namespace Avalonia.Base.UnitTests.Input
             Assert.Equal(
                 new[]
                 {
-                        ((object?)decorator, "PointerEnter"),
-                        (decorator, "PointerMoved"),
-                        (decorator, "PointerLeave"),
-                        (canvas, "PointerEnter"),
-                        (canvas, "PointerMoved")
+                        ((object?)decorator, nameof(InputElement.PointerEntered)),
+                        (decorator, nameof(InputElement.PointerMoved)),
+                        (decorator, nameof(InputElement.PointerExited)),
+                        (canvas, nameof(InputElement.PointerEntered)),
+                        (canvas, nameof(InputElement.PointerMoved))
                 },
                 result);
         }
 
         [Fact]
-        public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order()
+        public void PointerEntered_Exited_Should_Be_Raised_In_Correct_Order()
         {
             using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
 
@@ -289,7 +289,7 @@ namespace Avalonia.Base.UnitTests.Input
             SetHit(renderer, canvas);
             impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
 
-            AddEnterLeaveHandlers(HandleEvent, root, canvas, border, decorator);
+            AddEnteredExitedHandlers(HandleEvent, root, canvas, border, decorator);
 
             SetHit(renderer, decorator);
             impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
@@ -297,16 +297,16 @@ namespace Avalonia.Base.UnitTests.Input
             Assert.Equal(
                 new[]
                 {
-                    ((object?)canvas, "PointerLeave"),
-                    (decorator, "PointerEnter"),
-                    (border, "PointerEnter"),
+                    ((object?)canvas, nameof(InputElement.PointerExited)),
+                    (decorator, nameof(InputElement.PointerEntered)),
+                    (border, nameof(InputElement.PointerEntered)),
                 },
                 result);
         }
 
         // https://github.com/AvaloniaUI/Avalonia/issues/7896
         [Fact]
-        public void PointerEnter_Leave_Should_Set_Correct_Position()
+        public void PointerEntered_Exited_Should_Set_Correct_Position()
         {
             using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
 
@@ -331,7 +331,7 @@ namespace Avalonia.Base.UnitTests.Input
                 }
             });
 
-            AddEnterLeaveHandlers(HandleEvent, root, canvas);
+            AddEnteredExitedHandlers(HandleEvent, root, canvas);
 
             SetHit(renderer, canvas);
             impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, expectedPosition));
@@ -342,10 +342,10 @@ namespace Avalonia.Base.UnitTests.Input
             Assert.Equal(
                 new[]
                 {
-                    ((object?)canvas, "PointerEnter", expectedPosition),
-                    (root, "PointerEnter", expectedPosition),
-                    (canvas, "PointerLeave", expectedPosition),
-                    (root, "PointerLeave", expectedPosition)
+                    ((object?)canvas, nameof(InputElement.PointerEntered), expectedPosition),
+                    (root, nameof(InputElement.PointerEntered), expectedPosition),
+                    (canvas, nameof(InputElement.PointerExited), expectedPosition),
+                    (root, nameof(InputElement.PointerExited), expectedPosition)
                 },
                 result);
         }
@@ -415,7 +415,7 @@ namespace Avalonia.Base.UnitTests.Input
                 }
             });
 
-            AddEnterLeaveHandlers(HandleEvent, root, canvas);
+            AddEnteredExitedHandlers(HandleEvent, root, canvas);
 
             // Init pointer over.
             SetHit(renderer, canvas);
@@ -429,22 +429,22 @@ namespace Avalonia.Base.UnitTests.Input
             Assert.Equal(
                 new[]
                 {
-                    ((object?)canvas, "PointerEnter", lastClientPosition),
-                    (root, "PointerEnter", lastClientPosition),
-                    (canvas, "PointerLeave", lastClientPosition),
-                    (root, "PointerLeave", lastClientPosition),
+                    ((object?)canvas, nameof(InputElement.PointerEntered), lastClientPosition),
+                    (root, nameof(InputElement.PointerEntered), lastClientPosition),
+                    (canvas, nameof(InputElement.PointerExited), lastClientPosition),
+                    (root, nameof(InputElement.PointerExited), lastClientPosition),
                 },
                 result);
         }
 
-        private static void AddEnterLeaveHandlers(
+        private static void AddEnteredExitedHandlers(
             EventHandler<PointerEventArgs> handler,
             params IInputElement[] controls)
         {
             foreach (var c in controls)
             {
-                c.PointerEnter += handler;
-                c.PointerLeave += handler;
+                c.PointerEntered += handler;
+                c.PointerExited += handler;
                 c.PointerMoved += handler;
             }
         }

+ 0 - 31
tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs

@@ -173,37 +173,6 @@ namespace Avalonia.Base.UnitTests.Layout
             target.Verify(x => x.InvalidateMeasure(root), Times.Once());
         }
 
-        [Theory]
-        [InlineData(16, 6, 5.333333333333333)]
-        [InlineData(18, 10, 4)]
-        public void UseLayoutRounding_Arranges_Center_Alignment_Correctly_With_Fractional_Scaling(
-            double containerWidth,
-            double childWidth,
-            double expectedX)
-        {
-            Border target;
-            var root = new TestRoot
-            {
-                LayoutScaling = 1.5,
-                UseLayoutRounding = true,
-                Child = new Decorator
-                {
-                    Width = containerWidth,
-                    Height = 100,
-                    Child = target = new Border
-                    {
-                        Width = childWidth,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                    }
-                }
-            };
-
-            root.Measure(new Size(100, 100));
-            root.Arrange(new Rect(target.DesiredSize));
-
-            Assert.Equal(new Rect(expectedX, 0, childWidth, 100), target.Bounds);
-        }
-
         [Fact]
         public void LayoutUpdated_Is_Called_At_End_Of_Layout_Pass()
         {

+ 140 - 0
tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs

@@ -0,0 +1,140 @@
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.UnitTests;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Avalonia.Base.UnitTests.Layout
+{
+    public class LayoutableTests_LayoutRounding
+    {
+        [Theory]
+        [InlineData(100, 100)]
+        [InlineData(101, 101.33333333333333)]
+        [InlineData(103, 103.33333333333333)]
+        public void Measure_Adjusts_DesiredSize_Upwards_When_Constraint_Allows(double desiredSize, double expectedSize)
+        {
+            var target = new TestLayoutable(new Size(desiredSize, desiredSize));
+            var root = CreateRoot(1.5, target);
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            Assert.Equal(new Size(expectedSize, expectedSize), target.DesiredSize);
+        }
+
+        [Fact]
+        public void Measure_Constrains_Adjusted_DesiredSize_To_Constraint()
+        {
+            var target = new TestLayoutable(new Size(101, 101));
+            var root = CreateRoot(1.5, target, constraint: new Size(101, 101));
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            // Desired width/height with layout rounding is 101.3333 but constraint is 101,101 so
+            // layout rounding should be ignored.
+            Assert.Equal(new Size(101, 101), target.DesiredSize);
+        }
+
+        [Fact]
+        public void Measure_Adjusts_DesiredSize_Upwards_When_Margin_Present()
+        {
+            var target = new TestLayoutable(new Size(101, 101), margin: 1);
+            var root = CreateRoot(1.5, target);
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            // - 1 pixel margin is rounded up to 1.3333; for both sides it is 2.6666
+            // - Size of 101 gets rounded up to 101.3333
+            // - Final size = 101.3333 + 2.6666 = 104
+            AssertEqual(new Size(104, 104), target.DesiredSize);
+        }
+
+        [Fact]
+        public void Arrange_Adjusts_Bounds_Upwards_With_Margin()
+        {
+            var target = new TestLayoutable(new Size(101, 101), margin: 1);
+            var root = CreateRoot(1.5, target);
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            // - 1 pixel margin is rounded up to 1.3333
+            // - Size of 101 gets rounded up to 101.3333
+            AssertEqual(new Point(1.3333333333333333, 1.3333333333333333), target.Bounds.Position);
+            AssertEqual(new Size(101.33333333333333, 101.33333333333333), target.Bounds.Size);
+        }
+
+        [Theory]
+        [InlineData(16, 6, 5.333333333333333)]
+        [InlineData(18, 10, 4)]
+        public void Arranges_Center_Alignment_Correctly_With_Fractional_Scaling(
+            double containerWidth,
+            double childWidth,
+            double expectedX)
+        {
+            Border target;
+            var root = new TestRoot
+            {
+                LayoutScaling = 1.5,
+                UseLayoutRounding = true,
+                Child = new Decorator
+                {
+                    Width = containerWidth,
+                    Height = 100,
+                    Child = target = new Border
+                    {
+                        Width = childWidth,
+                        HorizontalAlignment = HorizontalAlignment.Center,
+                    }
+                }
+            };
+
+            root.Measure(new Size(100, 100));
+            root.Arrange(new Rect(target.DesiredSize));
+
+            Assert.Equal(new Rect(expectedX, 0, childWidth, 100), target.Bounds);
+        }
+
+        private static TestRoot CreateRoot(
+            double scaling,
+            Control child,
+            Size? constraint = null)
+        {
+            return new TestRoot
+            {
+                LayoutScaling = scaling,
+                UseLayoutRounding = true,
+                Child = child,
+                ClientSize = constraint ?? new Size(1000, 1000),
+            };
+        }
+
+        private static void AssertEqual(Point expected, Point actual)
+        {
+            if (!expected.NearlyEquals(actual))
+            {
+                throw new EqualException(expected, actual);
+            }
+        }
+
+        private static void AssertEqual(Size expected, Size actual)
+        {
+            if (!expected.NearlyEquals(actual))
+            {
+                throw new EqualException(expected, actual);
+            }
+        }
+
+        private class TestLayoutable : Control
+        {
+            private Size _desiredSize;
+
+            public TestLayoutable(Size desiredSize, double margin = 0)
+            {
+                _desiredSize = desiredSize;
+                Margin = new Thickness(margin);
+            }
+
+            protected override Size MeasureOverride(Size availableSize) => _desiredSize;
+        }
+    }
+}

+ 4 - 2
tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs

@@ -16,10 +16,12 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
                 Directory.CreateDirectory("Generated");
             }
 
+            var trie = GenerateBreakTypeTrie();
+
+            UnicodeDataGenerator.GenerateTrieClass("GraphemeBreak", trie);
+
             using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
             {
-                var trie = GenerateBreakTypeTrie();
-
                 trie.Save(stream);
             }
         }

+ 58 - 4
tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs

@@ -58,17 +58,69 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
             {
                 PairedBracketTypes = biDiPairedBracketTypeEntries, BiDiClasses = biDiClassEntries
             };
+
+            var trie = biDiTrieBuilder.Freeze();
+
+            GenerateTrieClass("BiDi", trie);
             
             using (var stream = File.Create("Generated\\BiDi.trie"))
             {
-                var trie = biDiTrieBuilder.Freeze();
-
                 trie.Save(stream);
 
                 return trie;
             }
         }
 
+        public static void GenerateTrieClass(string name, UnicodeTrie trie)
+        {
+            var stream = new MemoryStream();
+
+            trie.Save(stream);
+
+            using (var fileStream = File.Create($"Generated\\{name}.trie.cs"))
+            using (var writer = new StreamWriter(fileStream))
+            {
+                writer.WriteLine("using System;");
+                writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode");
+                writer.WriteLine("{");
+                writer.WriteLine($"   internal static class {name}Trie");
+                writer.WriteLine("    {");
+                writer.WriteLine("        public static ReadOnlySpan<byte> Data => new byte[]");
+                writer.WriteLine("        {");
+
+                stream.Position = 0;
+
+                writer.Write("            ");
+
+                while (true)
+                {
+                    var b = stream.ReadByte();
+
+                    if(b == -1)
+                    {
+                        break;
+                    }
+                    
+                    writer.Write(b.ToString());
+
+                    writer.Write(',');
+
+                    if (stream.Position % 100 == 0)
+                    {
+                        writer.Write(Environment.NewLine);
+
+                        writer.Write("            ");
+
+                        continue;
+                    }
+                }
+
+                writer.WriteLine("        };");              
+                writer.WriteLine("    }");
+                writer.WriteLine("}");
+            }
+        }
+
         public static UnicodeTrie GenerateUnicodeDataTrie(out UnicodeDataEntries dataEntries, out Dictionary<int, UnicodeDataItem> unicodeData)
         {
             var generalCategoryEntries =
@@ -105,10 +157,12 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
                 LineBreakClasses = lineBreakClassEntries
             };
 
+            var trie = unicodeDataTrieBuilder.Freeze();
+
+            GenerateTrieClass("UnicodeData", trie);
+
             using (var stream = File.Create("Generated\\UnicodeData.trie"))
             {
-                var trie = unicodeDataTrieBuilder.Freeze();
-
                 trie.Save(stream);
                 
                 return trie;

+ 1 - 1
tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

@@ -102,7 +102,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
 
             using (var stream =
                 typeof(UnicodeEnumsGenerator).Assembly.GetManifestResourceStream(
-                    "Avalonia.Visuals.UnitTests.Media.TextFormatting.BreakPairTable.txt"))
+                    "Avalonia.Base.UnitTests.Media.TextFormatting.BreakPairTable.txt"))
             using (var reader = new StreamReader(stream))
             {
                 while (!reader.EndOfStream)

+ 34 - 0
tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -349,6 +349,39 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
             }
         }
 
+        [Fact]
+        public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct()
+        {
+            using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+            {
+                Border border;
+                var tree = new TestRoot
+                {
+                    Width = 400,
+                    Height = 200,
+                    Child = border = new Border
+                    {
+                        HorizontalAlignment = HorizontalAlignment.Left,
+                        Background = Brushes.Red,
+                        Width = 100,
+                        RenderTransform = new ScaleTransform(0.5, 1),
+                        FlowDirection = FlowDirection.RightToLeft
+                    }
+                };
+
+                tree.Measure(Size.Infinity);
+                tree.Arrange(new Rect(tree.DesiredSize));
+
+                var scene = new Scene(tree);
+                var sceneBuilder = new SceneBuilder();
+                sceneBuilder.UpdateAll(scene);
+
+                var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0);
+                var borderNode = scene.FindNode(border);
+                Assert.Equal(expectedTransform, borderNode.Transform);
+            }
+        }
+
         [Fact]
         public void Should_Update_Border_Background_Node()
         {
@@ -805,6 +838,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
                 Canvas canvas;
                 var tree = new TestRoot
                 {
+                    ClientSize = new Size(100, 100),
                     Child = decorator = new Decorator
                     {
                         Margin = new Thickness(0, 10, 0, 0),

+ 36 - 6
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@@ -150,13 +150,43 @@ namespace Avalonia.Base.UnitTests.Styling
             Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority);
         }
 
-        private IBinding CreateMockBinding(AvaloniaProperty property)
+        [Fact]
+        public void Disposing_Setter_Should_Preserve_LocalValue()
         {
-            var subject = new Subject<object>();
-            var descriptor = InstancedBinding.OneWay(subject);
-            var binding = Mock.Of<IBinding>(x => 
-                x.Initiate(It.IsAny<IAvaloniaObject>(), property, null, false) == descriptor);
-            return binding;
+            var control = new Canvas();
+            var setter = new Setter(TextBlock.TagProperty, "foo");
+
+            var instance = setter.Instance(control);
+            instance.Start(true);
+            instance.Activate();
+
+            control.Tag = "bar";
+
+            instance.Dispose();
+
+            Assert.Equal("bar", control.Tag);
+        }
+
+        [Fact]
+        public void Disposing_Binding_Setter_Should_Preserve_LocalValue()
+        {
+            var control = new Canvas();
+            var source = new { Foo = "foo" };
+            var setter = new Setter(TextBlock.TagProperty, new Binding
+            {
+                Source = source,
+                Path = nameof(source.Foo),
+            });
+
+            var instance = setter.Instance(control);
+            instance.Start(true);
+            instance.Activate();
+
+            control.Tag = "bar";
+
+            instance.Dispose();
+
+            Assert.Equal("bar", control.Tag);
         }
 
         private class TestConverter : IValueConverter

+ 18 - 0
tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
+using Avalonia.Threading;
 using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 
@@ -37,6 +38,21 @@ namespace Avalonia.Benchmarks.Layout
             _root.Child = calendar;
 
             _root.LayoutManager.ExecuteLayoutPass();
+            Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+        }
+
+        [Benchmark]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public void CreateCalendarWithLoaded()
+        {
+            using var subscription = Control.LoadedEvent.AddClassHandler<Control>((c, s) => { });
+
+            var calendar = new Calendar();
+
+            _root.Child = calendar;
+
+            _root.LayoutManager.ExecuteLayoutPass();
+            Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
         }
 
         [Benchmark]
@@ -48,6 +64,7 @@ namespace Avalonia.Benchmarks.Layout
             _root.Child = button;
 
             _root.LayoutManager.ExecuteLayoutPass();
+            Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
         }
 
         [Benchmark]
@@ -59,6 +76,7 @@ namespace Avalonia.Benchmarks.Layout
             _root.Child = textBox;
 
             _root.LayoutManager.ExecuteLayoutPass();
+            Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
         }
 
         public void Dispose()

+ 65 - 0
tests/Avalonia.Controls.UnitTests/BorderTests.cs

@@ -1,6 +1,8 @@
+using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
+using Avalonia.VisualTree;
 using Moq;
 using Xunit;
 
@@ -60,5 +62,68 @@ namespace Avalonia.Controls.UnitTests
 
             renderer.Verify(x => x.AddDirty(target), Times.Once);
         }
+
+        public class UseLayoutRounding
+        {
+            [Fact]
+            public void Measure_Rounds_Padding()
+            {
+                var target = new Border 
+                { 
+                    Padding = new Thickness(1),
+                    Child = new Canvas
+                    {
+                        Width = 101,
+                        Height = 101,
+                    }
+                };
+
+                var root = CreatedRoot(1.5, target);
+
+                root.LayoutManager.ExecuteInitialLayoutPass();
+
+                // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666
+                // - Size of 101 gets rounded up to 101.3333
+                // - Desired size = 101.3333 + 2.6666 = 104
+                Assert.Equal(new Size(104, 104), target.DesiredSize);
+            }
+
+            [Fact]
+            public void Measure_Rounds_BorderThickness()
+            {
+                var target = new Border
+                {
+                    BorderThickness = new Thickness(1),
+                    Child = new Canvas
+                    {
+                        Width = 101,
+                        Height = 101,
+                    }
+                };
+
+                var root = CreatedRoot(1.5, target);
+
+                root.LayoutManager.ExecuteInitialLayoutPass();
+
+                // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666
+                // - Size of 101 gets rounded up to 101.3333
+                // - Desired size = 101.3333 + 2.6666 = 104
+                Assert.Equal(new Size(104, 104), target.DesiredSize);
+            }
+
+            private static TestRoot CreatedRoot(
+                double scaling,
+                Control child,
+                Size? constraint = null)
+            {
+                return new TestRoot
+                {
+                    LayoutScaling = scaling,
+                    UseLayoutRounding = true,
+                    Child = child,
+                    ClientSize = constraint ?? new Size(1000, 1000),
+                };
+            }
+        }
     }
 }

+ 6 - 6
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@@ -150,7 +150,7 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target);
+            RaisePointerEntered(target);
             RaisePointerMove(target, pt);
             RaisePointerPressed(target, 1, MouseButton.Left, pt);
 
@@ -182,10 +182,10 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target);
+            RaisePointerEntered(target);
             RaisePointerMove(target, new Point(50,50));
             RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50));
-            RaisePointerLeave(target);
+            RaisePointerExited(target);
 
             Assert.Equal(_helper.Captured, target);
 
@@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target);
+            RaisePointerEntered(target);
             RaisePointerMove(target, pt);
             RaisePointerPressed(target, 1, MouseButton.Left, pt);
 
@@ -422,12 +422,12 @@ namespace Avalonia.Controls.UnitTests
             _helper.Up(button, mouseButton, pt);
         }
 
-        private void RaisePointerEnter(Button button)
+        private void RaisePointerEntered(Button button)
         {
             _helper.Enter(button);
         }
 
-        private void RaisePointerLeave(Button button)
+        private void RaisePointerExited(Button button)
         {
             _helper.Leave(button);
         }

+ 100 - 1
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@@ -8,7 +8,7 @@ using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
-using Avalonia.Threading;
+using Avalonia.VisualTree;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -336,5 +336,104 @@ namespace Avalonia.Controls.UnitTests
                 Assert.Equal(1, count);
             }
         }
+
+        [Fact]
+        public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight()
+        {
+            var items = new[]
+            {
+                new ComboBoxItem()
+                { 
+                    Content = new Control()
+                }
+            };
+            var target = new ComboBox
+            {
+                FlowDirection = FlowDirection.RightToLeft,
+                Items = items,
+                Template = GetTemplate()
+            };
+
+            var root = new TestRoot(target);
+            target.ApplyTemplate();
+            target.SelectedIndex = 0;
+
+            var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
+
+            Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
+        }
+
+        [Fact]
+        public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform()
+        {
+            var parentContent = new Decorator()
+            {
+                Child = new Control()
+            };
+            var items = new[]
+            { 
+                new ComboBoxItem()
+                {
+                    Content = parentContent.Child
+                }
+            };
+            var target = new ComboBox
+            {
+                Items = items,
+                Template = GetTemplate()
+            };
+
+            var root = new TestRoot(target);
+            target.ApplyTemplate();
+            target.SelectedIndex = 0;
+
+            var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
+            Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
+
+            parentContent.FlowDirection = FlowDirection.RightToLeft;
+            target.FlowDirection = FlowDirection.RightToLeft;
+            
+            Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
+        }
+
+        [Fact]
+        public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var parentContent = new Decorator()
+                {
+                    Child = new Control()
+                };
+                var items = new[]
+                { 
+                    new ComboBoxItem()
+                    {
+                        Content = parentContent.Child
+                    }
+                };
+                var target = new ComboBox
+                {
+                    FlowDirection = FlowDirection.RightToLeft,
+                    Items = items,
+                    Template = GetTemplate()
+                };
+
+                var root = new TestRoot(target);
+                target.ApplyTemplate();
+                target.SelectedIndex = 0;
+
+                var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
+                Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
+
+                parentContent.FlowDirection = FlowDirection.RightToLeft;
+
+                var popup = target.GetVisualDescendants().OfType<Popup>().First();
+                popup.PlacementTarget = new Window();
+                popup.Open();
+                
+                Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
+            }
+        }
     }
 }

+ 41 - 0
tests/Avalonia.Controls.UnitTests/DecoratorTests.cs

@@ -1,6 +1,7 @@
 using System.Collections.Specialized;
 using System.Linq;
 using Avalonia.LogicalTree;
+using Avalonia.UnitTests;
 using Xunit;
 
 namespace Avalonia.Controls.UnitTests
@@ -116,5 +117,45 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.Equal(new Size(16, 16), target.DesiredSize);
         }
+
+        public class UseLayoutRounding
+        {
+            [Fact]
+            public void Measure_Rounds_Padding()
+            {
+                var target = new Decorator
+                {
+                    Padding = new Thickness(1),
+                    Child = new Canvas
+                    {
+                        Width = 101,
+                        Height = 101,
+                    }
+                };
+
+                var root = CreatedRoot(1.5, target);
+
+                root.LayoutManager.ExecuteInitialLayoutPass();
+
+                // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666
+                // - Size of 101 gets rounded up to 101.3333
+                // - Desired size = 101.3333 + 2.6666 = 104
+                Assert.Equal(new Size(104, 104), target.DesiredSize);
+            }
+
+            private static TestRoot CreatedRoot(
+                double scaling,
+                Control child,
+                Size? constraint = null)
+            {
+                return new TestRoot
+                {
+                    LayoutScaling = scaling,
+                    UseLayoutRounding = true,
+                    Child = child,
+                    ClientSize = constraint ?? new Size(1000, 1000),
+                };
+            }
+        }
     }
 }

+ 57 - 0
tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs

@@ -0,0 +1,57 @@
+using Avalonia.Media;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class FlowDirectionTests
+    {
+        [Fact]
+        public void HasMirrorTransform_Should_Be_True()
+        {
+            var target = new Control
+            {
+                FlowDirection = FlowDirection.RightToLeft,
+            };
+
+            Assert.True(target.HasMirrorTransform);    
+        }
+
+        [Fact]
+        public void HasMirrorTransform_Of_LTR_Children_Should_Be_True_For_RTL_Parent()
+        {
+            Control child;
+            var target = new Decorator
+            {
+                FlowDirection = FlowDirection.RightToLeft,
+                Child = child = new Control()
+            };
+
+            child.FlowDirection = FlowDirection.LeftToRight;
+
+            Assert.True(target.HasMirrorTransform);
+            Assert.True(child.HasMirrorTransform);  
+        }
+
+        [Fact]
+        public void HasMirrorTransform_Of_Children_Is_Updated_After_Parent_Changeed()
+        {
+            Control child;
+            var target = new Decorator
+            {
+                FlowDirection = FlowDirection.LeftToRight,
+                Child = child = new Control()
+                {
+                    FlowDirection = FlowDirection.LeftToRight,
+                }
+            };
+
+            Assert.False(target.HasMirrorTransform);
+            Assert.False(child.HasMirrorTransform);
+
+            target.FlowDirection = FlowDirection.RightToLeft;
+
+            Assert.True(target.HasMirrorTransform);
+            Assert.True(child.HasMirrorTransform);
+        }
+    }
+}

+ 0 - 28
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@@ -179,34 +179,6 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        [Fact]
-        public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int()
-        {
-            using (Start())
-            {
-                var source = new Class1();
-                var target = new MaskedTextBox
-                {
-                    DataContext = source,
-                    Template = CreateTemplate(),
-                };
-
-                target.ApplyTemplate();
-                target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay));
-
-                Assert.Equal("0", target.Text);
-
-                target.CaretIndex = 1;
-                target.RaiseEvent(new TextInputEventArgs
-                {
-                    RoutedEvent = InputElement.TextInputEvent,
-                    Text = "2",
-                });
-
-                Assert.Equal("02", target.Text);
-            }
-        }
-
         [Fact]
         public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
         {

+ 32 - 32
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests.Platform
             }
 
             [Fact]
-            public void PointerEnter_Opens_Item_When_Old_Item_Is_Open()
+            public void PointerEntered_Opens_Item_When_Old_Item_Is_Open()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = new Mock<IMenu>();
@@ -187,11 +187,11 @@ namespace Avalonia.Controls.UnitTests.Platform
                     x.IsTopLevel == true &&
                     x.HasSubMenu == true &&
                     x.Parent == menu.Object);
-                var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem);
+                var e = CreateArgs(MenuItem.PointerEnteredItemEvent, nextItem);
 
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
 
-                target.PointerEnter(nextItem, e);
+                target.PointerEntered(nextItem, e);
 
                 Mock.Get(item).Verify(x => x.Close());
                 menu.VerifySet(x => x.SelectedItem = nextItem);
@@ -202,31 +202,31 @@ namespace Avalonia.Controls.UnitTests.Platform
             }
 
             [Fact]
-            public void PointerLeave_Deselects_Item_When_Menu_Not_Open()
+            public void PointerExited_Deselects_Item_When_Menu_Not_Open()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = new Mock<IMenu>();
                 var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
-                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
-                target.PointerLeave(item, e);
+                target.PointerExited(item, e);
 
                 menu.VerifySet(x => x.SelectedItem = null);
                 Assert.False(e.Handled);
             }
 
             [Fact]
-            public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open()
+            public void PointerExited_Doesnt_Deselect_Item_When_Menu_Open()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = new Mock<IMenu>();
                 var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
-                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
                 menu.SetupGet(x => x.IsOpen).Returns(true);
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
-                target.PointerLeave(item, e);
+                target.PointerExited(item, e);
 
                 menu.VerifySet(x => x.SelectedItem = null, Times.Never);
                 Assert.False(e.Handled);
@@ -382,31 +382,31 @@ namespace Avalonia.Controls.UnitTests.Platform
             }
 
             [Fact]
-            public void PointerEnter_Selects_Item()
+            public void PointerEntered_Selects_Item()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item);
 
-                target.PointerEnter(item, e);
+                target.PointerEntered(item, e);
 
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
                 Assert.False(e.Handled);
             }
 
             [Fact]
-            public void PointerEnter_Opens_Submenu_After_Delay()
+            public void PointerEntered_Opens_Submenu_After_Delay()
             {
                 var timer = new TestTimer();
                 var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce);
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
-                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item);
 
-                target.PointerEnter(item, e);
+                target.PointerEntered(item, e);
                 Mock.Get(item).Verify(x => x.Open(), Times.Never);
 
                 timer.Pulse();
@@ -416,7 +416,7 @@ namespace Avalonia.Controls.UnitTests.Platform
             }
 
             [Fact]
-            public void PointerEnter_Closes_Sibling_Submenu_After_Delay()
+            public void PointerEntered_Closes_Sibling_Submenu_After_Delay()
             {
                 var timer = new TestTimer();
                 var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce);
@@ -424,11 +424,11 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
                 var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
-                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
 
-                target.PointerEnter(item, e);
+                target.PointerEntered(item, e);
                 Mock.Get(sibling).Verify(x => x.Close(), Times.Never);
 
                 timer.Pulse();
@@ -438,48 +438,48 @@ namespace Avalonia.Controls.UnitTests.Platform
             }
 
             [Fact]
-            public void PointerLeave_Deselects_Item()
+            public void PointerExited_Deselects_Item()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
-                target.PointerLeave(item, e);
+                target.PointerExited(item, e);
 
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null);
                 Assert.False(e.Handled);
             }
 
             [Fact]
-            public void PointerLeave_Doesnt_Deselect_Sibling()
+            public void PointerExited_Doesnt_Deselect_Sibling()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
                 var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
-                target.PointerLeave(item, e);
+                target.PointerExited(item, e);
 
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never);
                 Assert.False(e.Handled);
             }
 
             [Fact]
-            public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu()
+            public void PointerExited_Doesnt_Deselect_Item_If_Pointer_Over_Submenu()
             {
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
-                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var e = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
-                target.PointerLeave(item, e);
+                target.PointerExited(item, e);
 
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never);
                 Assert.False(e.Handled);
@@ -510,11 +510,11 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
                 var childItem = Mock.Of<IMenuItem>(x => x.Parent == item);
-                var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item);
-                var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
+                var enter = CreateArgs(MenuItem.PointerEnteredItemEvent, item);
+                var leave = CreateArgs(MenuItem.PointerExitedItemEvent, item);
 
                 // Pointer enters item; item is selected.
-                target.PointerEnter(item, enter);
+                target.PointerEntered(item, enter);
                 Assert.True(timer.ActionIsQueued);
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
                 Mock.Get(parentItem).Invocations.Clear();
@@ -526,13 +526,13 @@ namespace Avalonia.Controls.UnitTests.Platform
                 Mock.Get(item).Invocations.Clear();
 
                 // Pointer briefly exits item, but submenu remains open.
-                target.PointerLeave(item, leave);
+                target.PointerExited(item, leave);
                 Mock.Get(item).Verify(x => x.Close(), Times.Never);
                 Mock.Get(item).Invocations.Clear();
 
                 // Pointer enters child item; is selected.
                 enter.Source = childItem;
-                target.PointerEnter(childItem, enter);
+                target.PointerEntered(childItem, enter);
                 Mock.Get(item).VerifySet(x => x.SelectedItem = childItem);
                 Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
                 Mock.Get(item).Invocations.Clear();

+ 65 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs

@@ -1,5 +1,6 @@
 using Avalonia.Controls.Presenters;
 using Avalonia.Layout;
+using Avalonia.UnitTests;
 using Xunit;
 
 namespace Avalonia.Controls.UnitTests.Presenters
@@ -232,5 +233,68 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             Assert.Equal(new Rect(32, 32, 0, 0), content.Bounds);
         }
+
+        public class UseLayoutRounding
+        {
+            [Fact]
+            public void Measure_Rounds_Padding()
+            {
+                var target = new ContentPresenter
+                {
+                    Padding = new Thickness(1),
+                    Content = new Canvas
+                    {
+                        Width = 101,
+                        Height = 101,
+                    }
+                };
+
+                var root = CreatedRoot(1.5, target);
+
+                root.LayoutManager.ExecuteInitialLayoutPass();
+
+                // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666
+                // - Size of 101 gets rounded up to 101.3333
+                // - Desired size = 101.3333 + 2.6666 = 104
+                Assert.Equal(new Size(104, 104), target.DesiredSize);
+            }
+
+            [Fact]
+            public void Measure_Rounds_BorderThickness()
+            {
+                var target = new ContentPresenter
+                {
+                    BorderThickness = new Thickness(1),
+                    Content = new Canvas
+                    {
+                        Width = 101,
+                        Height = 101,
+                    }
+                };
+
+                var root = CreatedRoot(1.5, target);
+
+                root.LayoutManager.ExecuteInitialLayoutPass();
+
+                // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666
+                // - Size of 101 gets rounded up to 101.3333
+                // - Desired size = 101.3333 + 2.6666 = 104
+                Assert.Equal(new Size(104, 104), target.DesiredSize);
+            }
+
+            private static TestRoot CreatedRoot(
+                double scaling,
+                Control child,
+                Size? constraint = null)
+            {
+                return new TestRoot
+                {
+                    LayoutScaling = scaling,
+                    UseLayoutRounding = true,
+                    Child = child,
+                    ClientSize = constraint ?? new Size(1000, 1000),
+                };
+            }
+        }
     }
-}
+}

+ 2 - 2
tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs

@@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.Measure(new Size(100, 100));
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(33, 0, 33, 12), thumb.Bounds);
+            Assert.Equal(new Rect(33, 0, 34, 12), thumb.Bounds);
         }
 
         [Fact]
@@ -92,7 +92,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.Measure(new Size(100, 100));
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(0, 33, 12, 33), thumb.Bounds);
+            Assert.Equal(new Rect(0, 33, 12, 34), thumb.Bounds);
         }
 
         [Fact]

+ 0 - 28
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@@ -180,34 +180,6 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        [Fact]
-        public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int()
-        {
-            using (UnitTestApplication.Start(Services))
-            {
-                var source = new Class1();
-                var target = new TextBox
-                {
-                    DataContext = source,
-                    Template = CreateTemplate(),
-                };
-
-                target.ApplyTemplate();
-                target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay));
-
-                Assert.Equal("0", target.Text);
-
-                target.CaretIndex = 1;
-                target.RaiseEvent(new TextInputEventArgs
-                {
-                    RoutedEvent = InputElement.TextInputEvent,
-                    Text = "2",
-                });
-
-                Assert.Equal("02", target.Text);
-            }
-        }
-
         [Fact]
         public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
         {

+ 1 - 109
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@@ -12,6 +12,7 @@ using Moq;
 using Xunit;
 using Avalonia.Input.Raw;
 using Factory = System.Func<int, System.Action<object>, Avalonia.Controls.Window, Avalonia.AvaloniaObject>;
+using Avalonia.Threading;
 
 namespace Avalonia.Controls.UnitTests.Utils
 {
@@ -60,115 +61,6 @@ namespace Avalonia.Controls.UnitTests.Utils
             }
         }
 
-        [Fact]
-        public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
-        {
-            using (AvaloniaLocator.EnterScope())
-            {
-                var styler = new Mock<Styler>();
-
-                AvaloniaLocator.CurrentMutable
-                    .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
-                    .Bind<IStyler>().ToConstant(styler.Object);
-
-                var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
-
-                WeakReference reference = null;
-
-                var tl = new Window();
-
-                new Action(() =>
-                {
-                    var button = new Button();
-                    reference = new WeakReference(button, true);
-                    tl.Content = button;
-                    tl.Template = CreateWindowTemplate();
-                    tl.ApplyTemplate();
-                    tl.Presenter.ApplyTemplate();
-                    HotKeyManager.SetHotKey(button, gesture1);
-
-                    // Detach the button from the logical tree, so there is no reference to it
-                    tl.Content = null;
-                    tl.ApplyTemplate();
-                })();
-
-
-                // The button should be collected since it's detached from the listbox
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-
-                Assert.Null(reference?.Target);
-            }
-        }
-
-        [Fact]
-        public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                var styler = new Mock<Styler>();
-
-                AvaloniaLocator.CurrentMutable
-                    .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
-                    .Bind<IStyler>().ToConstant(styler.Object);
-
-                var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
-
-                var weakReferences = new List<WeakReference>();
-                var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
-                var lm = tl.LayoutManager;
-
-                var keyGestures = new AvaloniaList<KeyGesture> { gesture1 };
-                var listBox = new ListBox
-                {
-                    Width = 100,
-                    Height = 100,
-                    VirtualizationMode = ItemVirtualizationMode.None,
-                    // Create a button with binding to the KeyGesture in the template and add it to references list
-                    ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) =>
-                    {
-                        var keyGesture = o as KeyGesture;
-                        var button = new Button
-                        {
-                            DataContext = keyGesture, [!Button.HotKeyProperty] = new Binding("")
-                        };
-                        weakReferences.Add(new WeakReference(button, true));
-                        return button;
-                    })
-                };
-                // Add the listbox and render it
-                tl.Content = listBox;
-                lm.ExecuteInitialLayoutPass();
-                listBox.Items = keyGestures;
-                lm.ExecuteLayoutPass();
-
-                // Let the button detach when clearing the source items
-                keyGestures.Clear();
-                lm.ExecuteLayoutPass();
-                
-                // Add it again to double check,and render
-                keyGestures.Add(gesture1);
-                lm.ExecuteLayoutPass();
-                
-                keyGestures.Clear();
-                lm.ExecuteLayoutPass();
-                
-                // The button should be collected since it's detached from the listbox
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-                
-                Assert.True(weakReferences.Count > 0);
-                foreach (var weakReference in weakReferences)
-                {
-                    Assert.Null(weakReference.Target);
-                }
-            }
-        }
-
         [Theory]
         [MemberData(nameof(ElementsFactory), parameters: true)]
         public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factory factory)

+ 162 - 3
tests/Avalonia.LeakTests/ControlTests.cs

@@ -3,7 +3,10 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Reactive.Disposables;
+
+using Avalonia.Collections;
 using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
@@ -67,6 +70,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<DataGrid>()).ObjectsCount));
             }
@@ -100,6 +106,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
             }
@@ -141,6 +150,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
             }
@@ -179,6 +191,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
                 dotMemory.Check(memory =>
@@ -216,6 +231,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
             }
@@ -261,6 +279,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
                 dotMemory.Check(memory =>
@@ -351,6 +372,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
             }
@@ -384,6 +408,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
             }
@@ -421,6 +448,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TabItem>()).ObjectsCount));
             }
@@ -496,6 +526,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
             }
@@ -536,9 +569,12 @@ namespace Avalonia.LeakTests
                     initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
                     initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
                 });
-                
+
                 AttachShowAndDetachContextMenu(window);
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 Mock.Get(window.PlatformImpl).Invocations.Clear();
                 dotMemory.Check(memory =>
                     Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
@@ -580,10 +616,13 @@ namespace Avalonia.LeakTests
                     initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
                     initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
                 });
-                
+
                 BuildAndShowContextMenu(window);
                 BuildAndShowContextMenu(window);
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 Mock.Get(window.PlatformImpl).Invocations.Clear();
                 dotMemory.Check(memory =>
                     Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
@@ -623,6 +662,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Path>()).ObjectsCount));
 
@@ -657,6 +699,9 @@ namespace Avalonia.LeakTests
 
                 var result = run();
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ItemsRepeater>()).ObjectsCount));
             }
@@ -725,14 +770,128 @@ namespace Avalonia.LeakTests
 
                 Assert.Empty(lb.ItemContainerGenerator.Containers);
 
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
             }
         }
 
+        [Fact]
+        public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
+        {
+            using (Start())
+            {
+                Func<Window> run = () =>
+                {
+                    var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
+                    var tl = new Window
+                    {
+                        Content = new ItemsRepeater(),
+                    };
+
+                    tl.Show();
+
+                    var button = new Button();
+                    tl.Content = button;
+                    tl.Template = CreateWindowTemplate();
+                    tl.ApplyTemplate();
+                    tl.Presenter.ApplyTemplate();
+                    HotKeyManager.SetHotKey(button, gesture1);
+
+                    // Detach the button from the logical tree, so there is no reference to it
+                    tl.Content = null;
+                    tl.ApplyTemplate();
+
+                    return tl;
+                };
+
+                var result = run();
+
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
+                dotMemory.Check(memory =>
+                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Button>()).ObjectsCount));
+            }
+        }
+
+        [Fact]
+        public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached()
+        {
+            using (Start())
+            {
+                Func<Window> run = () =>
+                {
+                    var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
+
+                    var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
+                    var lm = tl.LayoutManager;
+                    tl.Show();
+
+                    var keyGestures = new AvaloniaList<KeyGesture> { gesture1 };
+                    var listBox = new ListBox
+                    {
+                        Width = 100,
+                        Height = 100,
+                        VirtualizationMode = ItemVirtualizationMode.None,
+                        // Create a button with binding to the KeyGesture in the template and add it to references list
+                        ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) =>
+                        {
+                            var keyGesture = o as KeyGesture;
+                            return new Button
+                            {
+                                DataContext = keyGesture,
+                                [!Button.HotKeyProperty] = new Binding("")
+                            };
+                        })
+                    };
+                    // Add the listbox and render it
+                    tl.Content = listBox;
+                    lm.ExecuteInitialLayoutPass();
+                    listBox.Items = keyGestures;
+                    lm.ExecuteLayoutPass();
+
+                    // Let the button detach when clearing the source items
+                    keyGestures.Clear();
+                    lm.ExecuteLayoutPass();
+
+                    // Add it again to double check,and render
+                    keyGestures.Add(gesture1);
+                    lm.ExecuteLayoutPass();
+
+                    keyGestures.Clear();
+                    lm.ExecuteLayoutPass();
+
+                    return tl;
+                };
+
+                var result = run();
+
+                // Process all Loaded events to free control reference(s)
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+
+                dotMemory.Check(memory =>
+                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Button>()).ObjectsCount));
+            }
+        }
+
+        private FuncControlTemplate CreateWindowTemplate()
+        {
+            return new FuncControlTemplate<Window>((parent, scope) =>
+            {
+                return new ContentPresenter
+                {
+                    Name = "PART_ContentPresenter",
+                    [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
+                }.RegisterInNameScope(scope);
+            });
+        }
+
         private IDisposable Start()
         {
-            void Cleanup()
+            static void Cleanup()
             {
                 // KeyboardDevice holds a reference to the focused item.
                 KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);

+ 41 - 56
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@@ -134,7 +134,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 var defaultProperties = new GenericTextRunProperties(Typeface.Default);
 
                 const string text = "👍 👍 👍 👍";
-                
+
                 var textSource = new SingleBufferTextSource(text, defaultProperties);
 
                 var formatter = new TextFormatterImpl();
@@ -144,7 +144,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                         new GenericTextParagraphProperties(defaultProperties));
 
                 Assert.Equal(1, textLine.TextRuns.Count);
-            } 
+            }
         }
 
         [Fact]
@@ -163,9 +163,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 var textLine =
                     formatter.FormatLine(textSource, 0, double.PositiveInfinity,
                         new GenericTextParagraphProperties(defaultProperties));
-                
+
                 var firstRun = textLine.TextRuns[0];
-                
+
                 Assert.Equal(4, firstRun.Text.Length);
             }
         }
@@ -191,7 +191,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 {
                     var textLine =
                         formatter.FormatLine(textSource, currentPosition, 1,
-                            new GenericTextParagraphProperties(defaultProperties, textWrap : TextWrapping.WrapWithOverflow));
+                            new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow));
 
                     if (text.Length - currentPosition > expectedCharactersPerLine)
                     {
@@ -347,8 +347,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
-        [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor",  
-            new []{ "Lorem ipsum ", "dolor sit amet, ", "consectetur ", "adipisicing elit, ", "sed do eiusmod "})]
+        [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor",
+            new[] { "Lorem ipsum ", "dolor sit amet, ", "consectetur ", "adipisicing elit, ", "sed do eiusmod " })]
 
         [Theory]
         public void Should_Produce_Wrapped_And_Trimmed_Lines(string text, string[] expectedLines)
@@ -368,7 +368,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                     new ValueSpan<TextRunProperties>(28, 28,
                         new GenericTextRunProperties(new Typeface("Verdana", FontStyle.Italic),32))
                 };
-                
+
                 var textSource = new FormattedTextSource(text.AsMemory(), defaultProperties, styleSpans);
 
                 var formatter = new TextFormatterImpl();
@@ -389,19 +389,19 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
 
                     if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
                     {
-                        textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice<char>(new[] {TextTrimming.s_defaultEllipsisChar}), 300, defaultProperties));
+                        textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice<char>(new[] { TextTrimming.s_defaultEllipsisChar }), 300, defaultProperties));
                     }
-                    
+
                     currentHeight += textLine.Height;
 
                     var currentText = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
-                    
+
                     Assert.Equal(expectedLines[currentLineIndex], currentText);
 
                     currentLineIndex++;
                 }
-                
-                Assert.Equal(expectedLines.Length,currentLineIndex);
+
+                Assert.Equal(expectedLines.Length, currentLineIndex);
             }
         }
 
@@ -412,11 +412,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
         [InlineData("0123456789", TextAlignment.Left, FlowDirection.RightToLeft)]
         [InlineData("0123456789", TextAlignment.Center, FlowDirection.RightToLeft)]
         [InlineData("0123456789", TextAlignment.Right, FlowDirection.RightToLeft)]
-        
+
         [InlineData("שנבגק", TextAlignment.Left, FlowDirection.RightToLeft)]
         [InlineData("שנבגק", TextAlignment.Center, FlowDirection.RightToLeft)]
         [InlineData("שנבגק", TextAlignment.Right, FlowDirection.RightToLeft)]
-        
+
         [Theory]
         public void Should_Align_TextLine(string text, TextAlignment textAlignment, FlowDirection flowDirection)
         {
@@ -426,44 +426,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
 
                 var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true,
                     defaultProperties, TextWrapping.NoWrap, 0, 0);
-                
+
                 var textSource = new SingleBufferTextSource(text, defaultProperties);
                 var formatter = new TextFormatterImpl();
-                
+
                 var textLine =
                     formatter.FormatLine(textSource, 0, 100, paragraphProperties);
 
                 var expectedOffset = 0d;
 
-                if (flowDirection == FlowDirection.LeftToRight)
+                switch (textAlignment)
                 {
-                    switch (textAlignment)
-                    {
-                        case TextAlignment.Center:
-                            expectedOffset = 50 - textLine.Width / 2;
-                            break;
-                        case TextAlignment.Right:
-                            expectedOffset = 100 - textLine.WidthIncludingTrailingWhitespace;
-                            break;
-                    }
-                }
-                else
-                {
-                    switch (textAlignment)
-                    {
-                        case TextAlignment.Left:
-                            expectedOffset = 100 - textLine.WidthIncludingTrailingWhitespace;
-                            break;
-                        case TextAlignment.Center:
-                            expectedOffset = 50 - textLine.Width / 2;
-                            break;
-                    }
+                    case TextAlignment.Center:
+                        expectedOffset = 50 - textLine.Width / 2;
+                        break;
+                    case TextAlignment.Right:
+                        expectedOffset = 100 - textLine.WidthIncludingTrailingWhitespace;
+                        break;
                 }
 
                 Assert.Equal(expectedOffset, textLine.Start);
             }
         }
-        
+
         [Fact]
         public void Should_Wrap_Syriac()
         {
@@ -488,7 +473,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                         formatter.FormatLine(textSource, textPosition, 50, paragraphProperties, lastBreak);
 
                     Assert.Equal(textLine.Length, textLine.TextRuns.Sum(x => x.TextSourceLength));
-                    
+
                     textPosition += textLine.Length;
 
                     lastBreak = textLine.TextLineBreak;
@@ -503,13 +488,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             {
                 var defaultProperties = new GenericTextRunProperties(Typeface.Default);
                 var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap);
-                
+
                 var textSource = new SingleBufferTextSource("0123456789_0123456789_0123456789_0123456789", defaultProperties);
                 var formatter = new TextFormatterImpl();
-                
+
                 var textLine =
                     formatter.FormatLine(textSource, 0, 33, paragraphProperties);
-                
+
                 Assert.NotNull(textLine.TextLineBreak?.RemainingRuns);
             }
         }
@@ -524,12 +509,12 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             using (Start())
             {
                 var formatter = new TextFormatterImpl();
-                
+
                 var defaultProperties = new GenericTextRunProperties(Typeface.Default);
 
                 var paragraphProperties =
                     new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.NoWrap);
-                
+
                 var foreground = new SolidColorBrush(Colors.Red).ToImmutable();
 
                 var expectedTextLine = formatter.FormatLine(new SingleBufferTextSource(text, defaultProperties),
@@ -548,16 +533,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                             new ValueSpan<TextRunProperties>(i, j,
                                 new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: foreground))
                         };
-                        
+
                         var textSource = new FormattedTextSource(text.AsMemory(), defaultProperties, spans);
-                
+
                         var textLine =
                             formatter.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
-                        
+
                         var shapedRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList();
 
                         var actualGlyphs = shapedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList();
-                        
+
                         Assert.Equal(expectedGlyphs, actualGlyphs);
                     }
                 }
@@ -575,9 +560,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             {
                 var textLine =
                     TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
-                
+
                 Assert.Equal(3, textLine.TextRuns.Count);
-                
+
                 Assert.True(textLine.TextRuns[1] is RectangleRun);
             }
         }
@@ -590,12 +575,12 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 var defaultRunProperties = new GenericTextRunProperties(Typeface.Default);
                 var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
                 var textSource = new EndOfLineTextSource();
-                
+
                 var textLine =
                     TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
-                
+
                 Assert.NotNull(textLine.TextLineBreak);
-                
+
                 Assert.Equal(TextRun.DefaultTextSourceLength, textLine.Length);
             }
         }
@@ -616,7 +601,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             {
                 _text = text;
             }
-            
+
             public TextRun GetTextRun(int textSourceIndex)
             {
                 if (textSourceIndex >= _text.Length + TextRun.DefaultTextSourceLength + _text.Length)

+ 2 - 2
tests/Avalonia.UnitTests/MouseTestHelper.cs

@@ -107,13 +107,13 @@ namespace Avalonia.UnitTests
         
         public void Enter(IInteractive target)
         {
-            target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default,
+            target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnteredEvent, target, _pointer, (IVisual)target, default,
                 Timestamp(), new PointerPointProperties((RawInputModifiers)_pressedButtons, PointerUpdateKind.Other), KeyModifiers.None));
         }
 
         public void Leave(IInteractive target)
         {
-            target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default,
+            target.RaiseEvent(new PointerEventArgs(InputElement.PointerExitedEvent, target, _pointer, (IVisual)target, default,
                 Timestamp(), new PointerPointProperties((RawInputModifiers)_pressedButtons, PointerUpdateKind.Other), KeyModifiers.None));
         }
 

+ 6 - 1
tests/Avalonia.UnitTests/TestRoot.cs

@@ -41,7 +41,7 @@ namespace Avalonia.UnitTests
             Child = child;
         }
 
-        public Size ClientSize { get; set; } = new Size(100, 100);
+        public Size ClientSize { get; set; } = new Size(1000, 1000);
 
         public Size MaxClientSize { get; set; } = Size.Infinity;
 
@@ -110,5 +110,10 @@ namespace Avalonia.UnitTests
             }
             Visit(this, true);
         }
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            return base.MeasureOverride(ClientSize);
+        }
     }
 }

BIN
tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png


BIN
tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png