소스 검색

Fix issue with OverhangLeading (#18438)

* Fix issue with MinTextWidth (Fixes #18372)

* Make sure that MeasureOverride for TextPresenter and TextBlock are using same textWidth

* Revert #16601 that is introducing an invalid calculation for the OverhangLeading

Add tests for OverhangLeading and OverhangTrailing

* Revert MinTextWidth

* Fix tests to not rely on fixed values

* Fix remaining issues

* Fix comment in Direct2D1 GlyphRunImpl.cs

* Fix Direct2D1 rendering

* Fix gold images

* Restore TextLineImpl

* Update gold image

* Restore Math.Max on OverhangLeading and Trailing

* Adopt similar behavior to WPF: don't use OverhangLeading/Trailing for measuring and remove clip by default.

But it requires further support with NeedsClipBounds

* Remove MinTextWidth
Keep ClipToBounds=true default for TextBlock

* Revert change

---------

Co-authored-by: Benedikt Stebner <[email protected]>
Alexandre Mutel 6 달 전
부모
커밋
46d4735868
23개의 변경된 파일233개의 추가작업 그리고 98개의 파일을 삭제
  1. 16 41
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  2. 3 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  3. 3 2
      src/Avalonia.Controls/TextBlock.cs
  4. 0 6
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  5. 24 5
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  6. 1 1
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  7. BIN
      tests/Avalonia.RenderTests/Assets/SourceSerif4_36pt-Italic.ttf
  8. 155 35
      tests/Avalonia.RenderTests/Controls/TextBlockTests.cs
  9. 2 6
      tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
  10. 29 0
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  11. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png
  12. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Account_For_Overhang_Leading_And_Trailing.expected.png
  13. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_MultiLineText_WithOverHandLeadingTrailing.expected.png
  14. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_Run_With_Background.expected.png
  15. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_TextDecorations.expected.png
  16. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Keep_TrailingWhiteSpace.expected.png
  17. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Measure_Arrange_TextBlock_150_NoWrap.expected.png
  18. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Measure_Arrange_TextBlock_44_NoWrap.expected.png
  19. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Measure_Arrange_TextBlock_44_Wrap.expected.png
  20. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png
  21. BIN
      tests/TestFiles/Skia/Controls/TextBlock/Should_Account_For_Overhang_Leading_And_Trailing.expected.png
  22. BIN
      tests/TestFiles/Skia/Controls/TextBlock/Should_Draw_MultiLineText_WithOverHandLeadingTrailing.expected.png
  23. BIN
      tests/TestFiles/Skia/Controls/TextBlock/Should_Keep_TrailingWhiteSpace.expected.png

+ 16 - 41
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -273,11 +273,6 @@ namespace Avalonia.Media.TextFormatting
             }
         }
 
-        /// <summary>
-        /// Get minimum width of all text lines that can be layouted horizontally without trimming or wrapping.
-        /// </summary>
-        internal double MinTextWidth => _metrics.MinTextWidth;
-
         /// <summary>
         /// Draws the text layout.
         /// </summary>
@@ -679,45 +674,30 @@ namespace Avalonia.Media.TextFormatting
 
         private void UpdateMetrics(TextLineImpl currentLine, ref bool first)
         {
-            // 1) Offset each line’s bounding rectangles by the total height so far,
-            //    so we keep an overall bounding box for the entire text block.
-            var lineTop = _metrics.Height;
-
-            // Offset the line's Bounds
-            var lineBoundsRect = new Rect(
-                currentLine.Bounds.X,
-                lineTop + currentLine.Bounds.Y,
-                currentLine.Bounds.Width,
-                currentLine.Bounds.Height);
-
-            _metrics.Bounds = _metrics.Bounds.Union(lineBoundsRect);
-
-            // Offset the line's InkBounds
-            var lineInkRect = new Rect(
-                currentLine.InkBounds.X,
-                lineTop + currentLine.InkBounds.Y,
-                currentLine.InkBounds.Width,
-                currentLine.InkBounds.Height);
-
-            _metrics.InkBounds = _metrics.InkBounds.Union(lineInkRect);
-
-            // 2) Accumulate total layout height by adding the line’s Height.
+            // 1) Accumulate total layout height by adding the line’s Height.
             _metrics.Height += currentLine.Height;
 
-            // 3) For the layout’s Width and WidthIncludingTrailingWhitespace,
+            // 2) For the layout’s Width and WidthIncludingTrailingWhitespace,
             //    use the maximum of the line widths rather than the bounding box.
             _metrics.Width = Math.Max(_metrics.Width, currentLine.Width);
-            _metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);
 
-            // 4) Extent is the max black-pixel extent among lines.
+            // 3) Extent is the max black-pixel extent among lines.
             _metrics.Extent = Math.Max(_metrics.Extent, currentLine.Extent);
 
-            // 5) We can track min-text-width or overhangs similarly if needed.
-            _metrics.MinTextWidth = Math.Max(_metrics.MinTextWidth, currentLine.Width);
+            // 4) TextWidth is the max of the text width among lines.
+            // We choose to update all related metrics at once (OverhangLeading, WidthIncludingTrailingWhitespace, OverhangTrailing)
+            // if the current line has a larger text width.
+            var previousTextWidth = _metrics.OverhangLeading + _metrics.WidthIncludingTrailingWhitespace + _metrics.OverhangTrailing;
+            var textWidth = currentLine.OverhangLeading + currentLine.WidthIncludingTrailingWhitespace + currentLine.OverhangTrailing;
+            if (previousTextWidth < textWidth)
+            {
+                _metrics.WidthIncludingTrailingWhitespace = currentLine.WidthIncludingTrailingWhitespace;
+                _metrics.OverhangLeading = currentLine.OverhangLeading;
+                _metrics.OverhangTrailing = currentLine.OverhangTrailing;
+            }
 
-            _metrics.OverhangLeading = Math.Max(_metrics.OverhangLeading, currentLine.OverhangLeading);
-            _metrics.OverhangTrailing = Math.Max(_metrics.OverhangTrailing, currentLine.OverhangTrailing);
-            _metrics.OverhangAfter = Math.Max(_metrics.OverhangAfter, currentLine.OverhangAfter);
+            // 5) OverhangAfter is the last line’s OverhangAfter.
+            _metrics.OverhangAfter = currentLine.OverhangAfter;
 
             // 6) Capture the baseline from the first line.
             if (first)
@@ -768,11 +748,6 @@ namespace Avalonia.Media.TextFormatting
             // horizontal bounding box metrics
             public double OverhangLeading;
             public double OverhangTrailing;
-
-            public Rect Bounds;
-            public Rect InkBounds;
-
-            public double MinTextWidth;
         }
     }
 }

+ 3 - 2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -627,8 +627,8 @@ namespace Avalonia.Controls.Presenters
 
             InvalidateArrange();
 
+            // The textWidth used here is matching that TextBlock uses to measure the text.
             var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
-
             return new Size(textWidth, TextLayout.Height);
         }
 
@@ -636,7 +636,8 @@ namespace Avalonia.Controls.Presenters
         {
             var finalWidth = finalSize.Width;
 
-            var textWidth = Math.Ceiling(TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing);
+            var textWidth = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
+            textWidth = Math.Ceiling(textWidth);
 
             if (finalSize.Width < textWidth)
             {

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

@@ -628,7 +628,7 @@ namespace Avalonia.Controls
 
         protected virtual void RenderTextLayout(DrawingContext context, Point origin)
         {
-            TextLayout.Draw(context, origin + new Point(TextLayout.OverhangLeading, 0));
+            TextLayout.Draw(context, origin);
         }
 
         private bool _clearTextInternal;
@@ -740,7 +740,8 @@ namespace Avalonia.Controls
             //This implicitly recreated the TextLayout with a new constraint if we previously reset it.
             var textLayout = TextLayout;
 
-            var size = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.MinTextWidth, textLayout.Height).Inflate(padding), 1);
+            // The textWidth used here is matching that TextPresenter uses to measure the text.
+            var size = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.WidthIncludingTrailingWhitespace, textLayout.Height).Inflate(padding), 1);
 
             return size;
         }

+ 0 - 6
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@@ -80,12 +80,6 @@ namespace Avalonia.Skia
 
                 currentX += advance;
             }
-
-            if (runBounds.Left < 0)
-            {
-                runBounds = runBounds.Translate(new Vector(-runBounds.Left, 0));
-            }
-
             ArrayPool<SKRect>.Shared.Return(glyphBounds);
 
             BaselineOrigin = baselineOrigin;

+ 24 - 5
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@@ -50,7 +50,10 @@ namespace Avalonia.Direct2D1.Media
             }
 
             _glyphOffsets = new GlyphOffset[glyphCount];
-
+            
+            var runBounds = new Rect();
+            var currentX = 0.0;
+            var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
             for (var i = 0; i < glyphCount; i++)
             {
                 var (x, y) = glyphInfos[i].GlyphOffset;
@@ -60,13 +63,29 @@ namespace Avalonia.Direct2D1.Media
                     AdvanceOffset = (float)x,
                     AscenderOffset = (float)y
                 };
-            }
 
-            var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
+                if (_glyphTypefaceImpl.TryGetGlyphMetrics(glyphInfos[i].GlyphIndex, out var metrics))
+                {
+                    // Found metrics with negative height, prefer to adjust it to positive.
+                    var ybearing = metrics.YBearing;
+                    var height = metrics.Height;
+                    if (height < 0)
+                    {
+                        ybearing += height;
+                        height = -height;
+                    }
+
+                    // Not entirely sure about why we need to do this, but it seems to work
+                    var xOffset = metrics.XBearing * scale;
+                    var xWidth = xOffset > 0 ? xOffset : 0;
+                    var xBearing = xOffset < 0 ? xOffset : 0;
+                    runBounds = runBounds.Union(new Rect(currentX + xBearing, baselineOrigin.Y + ybearing, xWidth + metrics.Width * scale, height * scale));
+                }
 
-            var height = glyphTypeface.Metrics.LineSpacing * scale;
+                currentX += glyphInfos[i].GlyphAdvance;
+            }
 
-            Bounds = new Rect(baselineOrigin.X, 0, width, height);
+            Bounds = runBounds.Translate(new Vector(baselineOrigin.X, 0));
         }
 
         public SharpDX.DirectWrite.GlyphRun GlyphRun

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests
 
                 var textLayout = textBlock.TextLayout;
 
-                var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.MinTextWidth, textLayout.Height), 1);
+                var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.Width, textLayout.Height), 1);
 
                 Assert.Equal(textBlock.DesiredSize, constraint);
             }

BIN
tests/Avalonia.RenderTests/Assets/SourceSerif4_36pt-Italic.ttf


+ 155 - 35
tests/Avalonia.RenderTests/Controls/TextBlockTests.cs

@@ -1,3 +1,5 @@
+using System;
+using System.IO;
 using System.Net;
 using System.Threading.Tasks;
 using Avalonia.Controls;
@@ -5,7 +7,6 @@ using Avalonia.Controls.Documents;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Xunit;
-using static System.Net.Mime.MediaTypeNames;
 
 #if AVALONIA_SKIA
 namespace Avalonia.Skia.RenderTests
@@ -217,60 +218,60 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
                 TextWrapping = textWrapping 
             });
 
-            target.Children.Add(new TextBlock 
+            target.Children.Add(new TextBlock
             {
                 Text = text,
                 Background = Brushes.Red,
                 HorizontalAlignment = HorizontalAlignment.Center,
-                TextAlignment = TextAlignment.Left, 
-                Width = width, 
+                TextAlignment = TextAlignment.Left,
+                Width = width,
                 TextWrapping = textWrapping
             });
-            target.Children.Add(new TextBlock 
-            { 
-                Text = text, 
-                Background = Brushes.Red, 
-                HorizontalAlignment = HorizontalAlignment.Center, 
-                TextAlignment = TextAlignment.Center, 
+            target.Children.Add(new TextBlock
+            {
+                Text = text,
+                Background = Brushes.Red,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                TextAlignment = TextAlignment.Center,
                 Width = width,
                 TextWrapping = textWrapping
             });
-            target.Children.Add(new TextBlock 
-            { 
+            target.Children.Add(new TextBlock
+            {
                 Text = text,
                 Background = Brushes.Red,
                 HorizontalAlignment = HorizontalAlignment.Center,
-                TextAlignment = TextAlignment.Right, 
+                TextAlignment = TextAlignment.Right,
                 Width = width,
-                TextWrapping = textWrapping 
+                TextWrapping = textWrapping
             });
 
-            target.Children.Add(new TextBlock 
-            { 
+            target.Children.Add(new TextBlock
+            {
                 Text = text,
-                Background = Brushes.Red, 
+                Background = Brushes.Red,
                 HorizontalAlignment = HorizontalAlignment.Right,
-                TextAlignment = TextAlignment.Left, 
-                Width = width, 
+                TextAlignment = TextAlignment.Left,
+                Width = width,
                 TextWrapping = textWrapping
             });
-            target.Children.Add(new TextBlock 
-            { 
-                Text = text, 
-                Background = Brushes.Red, 
-                HorizontalAlignment = HorizontalAlignment.Right, 
-                TextAlignment = TextAlignment.Center, 
-                Width = width, 
-                TextWrapping = textWrapping 
+            target.Children.Add(new TextBlock
+            {
+                Text = text,
+                Background = Brushes.Red,
+                HorizontalAlignment = HorizontalAlignment.Right,
+                TextAlignment = TextAlignment.Center,
+                Width = width,
+                TextWrapping = textWrapping
             });
-            target.Children.Add(new TextBlock 
-            { 
-                Text = text, 
-                Background = Brushes.Red, 
-                HorizontalAlignment = HorizontalAlignment.Right, 
-                TextAlignment = TextAlignment.Right, 
-                Width = width, 
-                TextWrapping = textWrapping 
+            target.Children.Add(new TextBlock
+            {
+                Text = text,
+                Background = Brushes.Red,
+                HorizontalAlignment = HorizontalAlignment.Right,
+                TextAlignment = TextAlignment.Right,
+                Width = width,
+                TextWrapping = textWrapping
             });
 
             var testName = $"Should_Measure_Arrange_TextBlock_{width}_{textWrapping}";
@@ -278,5 +279,124 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
             await RenderToFile(target, testName);
             CompareImages(testName);
         }
+
+        [Win32Fact("Has text")]
+        public async Task Should_Keep_TrailingWhiteSpace()
+        {
+            // <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
+            //   <TextBlock Margin="0 10 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Magenta" FontSize="44" Text="aaaa" FontFamily="Courier New"/>
+            //   <TextBlock Margin="0 10 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Magenta" FontSize="44" Text="a a " FontFamily="Courier New"/>
+            //   <TextBlock Margin="0 10 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Magenta" FontSize="44" Text="    " FontFamily="Courier New"/>
+            //   <TextBlock Margin="0 10 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Magenta" FontSize="44" Text="LLLL" FontFamily="Courier New"/>
+            // </StackPanel>
+
+            var target = new StackPanel
+            {
+                VerticalAlignment = VerticalAlignment.Center,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                Width = 300,
+                Height = 300,
+            };
+            target.Children.Add(CreateText("aaaa"));
+            target.Children.Add(CreateText("a a "));
+            target.Children.Add(CreateText("    ")); // This one does not render correctly on Direct2D1
+            target.Children.Add(CreateText("LLLL"));
+
+            var testName = $"Should_Keep_TrailingWhiteSpace";
+            await RenderToFile(target, testName);
+            CompareImages(testName);
+
+            static TextBlock CreateText(string text) => new TextBlock
+            {
+                Margin = new Thickness(0, 10, 0, 0),
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center,
+                Background = Brushes.Magenta,
+                FontSize = 44,
+                Text = text,
+                FontFamily = new FontFamily("Courier New")
+            };
+        }
+
+        [Win32Fact("Has text")]
+        public async Task Should_Account_For_Overhang_Leading_And_Trailing()
+        {
+            var target = new StackPanel
+            {
+                VerticalAlignment = VerticalAlignment.Center,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                Width = 300,
+                Height = 600,
+                Background = new SolidColorBrush(Colors.White), // Required antialiasing to work for Overhang
+            };
+
+            target.Children.Add(CreateText("f"));
+            target.Children.Add(CreateText("y"));
+            target.Children.Add(CreateText("ff"));
+            target.Children.Add(CreateText("yy"));
+            target.Children.Add(CreateText("faaf"));
+            target.Children.Add(CreateText("yaay"));
+            target.Children.Add(CreateText("y y "));
+            target.Children.Add(CreateText("f f "));
+
+            var testName = $"Should_Account_For_Overhang_Leading_And_Trailing";
+            await RenderToFile(target, testName);
+            CompareImages(testName);
+
+#if AVALONIA_SKIA
+            const string symbolsFont = "resm:Avalonia.Skia.RenderTests.Assets?assembly=Avalonia.Skia.RenderTests#Source Serif 4 36pt";
+#else
+            const string symbolsFont = "resm:Avalonia.Direct2D1.RenderTests.Assets?assembly=Avalonia.Direct2D1.RenderTests#Source Serif 4 36pt";
+#endif
+            static TextBlock CreateText(string text) => new TextBlock
+            {
+                ClipToBounds = false,
+                Margin = new Thickness(4),
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center,
+                Background = Brushes.Magenta,
+                FontStyle = FontStyle.Italic,
+                FontSize = 44,
+                Text = text,
+                FontFamily = new FontFamily(symbolsFont)
+            };
+        }
+
+        [Win32Fact("Has text")]
+        public async Task Should_Draw_MultiLineText_WithOverHandLeadingTrailing()
+        {
+            var target = new StackPanel
+            {
+                VerticalAlignment = VerticalAlignment.Center,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                Width = 600,
+                Height = 200,
+                Background = new SolidColorBrush(Colors.White), // Required antialiasing to work for Overhang
+            };
+
+            target.Children.Add(CreateText("fff Why this is a\nbig text yyy\nyyy with multiple lines fff"));
+
+            var testName = $"Should_Draw_MultiLineText_WithOverHandLeadingTrailing";
+            await RenderToFile(target, testName);
+            CompareImages(testName);
+
+#if AVALONIA_SKIA
+            const string symbolsFont = "resm:Avalonia.Skia.RenderTests.Assets?assembly=Avalonia.Skia.RenderTests#Source Serif 4 36pt";
+#else
+            const string symbolsFont = "resm:Avalonia.Direct2D1.RenderTests.Assets?assembly=Avalonia.Direct2D1.RenderTests#Source Serif 4 36pt";
+#endif
+            static TextBlock CreateText(string text) => new TextBlock
+            {
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center,
+                TextAlignment = TextAlignment.Center,
+                Background = Brushes.Magenta,
+                FontStyle = FontStyle.Italic,
+                FontSize = 44,
+                Text = text,
+                FontFamily = new FontFamily(symbolsFont)
+            };
+        }
+
     }
 }

+ 2 - 6
tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj

@@ -10,12 +10,8 @@
   <Import Project="..\..\build\SharedVersion.props" />
   <ItemGroup>
     <EmbeddedResource Include="..\Avalonia.RenderTests\*\*.ttf" />
-    <None Remove="Fonts\DejaVuSans.ttf" />
-    <None Remove="Fonts\Manrope-Light.ttf" />
-    <None Remove="Fonts\WinSymbols3.ttf" />
-    <EmbeddedResource Include="Fonts\DejaVuSans.ttf" />
-    <EmbeddedResource Include="Fonts\Manrope-Light.ttf" />
-    <EmbeddedResource Include="Fonts\WinSymbols3.ttf" />
+    <None Remove="**\*.ttf" />
+    <EmbeddedResource Include="**\*.ttf" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />

+ 29 - 0
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@@ -1184,6 +1184,35 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
+        [Fact]
+        public void Should_Measure_TextLayoutSymbolWithAndWidthIncludingTrailingWhitespaceAndMinTextWidth()
+        {
+            using (Start())
+            {
+                var typeFace = new Typeface("Courier New");
+                var textLayout0 = new TextLayout("aaaa", typeFace, 12.0, Brushes.White);
+                Assert.Equal(textLayout0.WidthIncludingTrailingWhitespace, textLayout0.Width);
+
+                var textLayout01 = new TextLayout("a a", typeFace, 12.0, Brushes.White);
+                var textLayout1 = new TextLayout("a a ", typeFace, 12.0, Brushes.White);
+                Assert.Equal(new Size(textLayout0.Width, textLayout0.Height), new Size(textLayout1.WidthIncludingTrailingWhitespace, textLayout1.Height));
+                Assert.Equal(textLayout0.WidthIncludingTrailingWhitespace, textLayout1.WidthIncludingTrailingWhitespace);
+
+
+                var textLayout2 = new TextLayout(" aa ", typeFace, 12.0, Brushes.White);
+                Assert.Equal(new Size(textLayout1.Width, textLayout1.Height), new Size(textLayout2.Width, textLayout2.Height));
+                Assert.Equal(textLayout0.WidthIncludingTrailingWhitespace, textLayout2.WidthIncludingTrailingWhitespace);
+                Assert.Equal(textLayout01.Width, textLayout2.Width);
+                
+                var textLayout3 = new TextLayout("    ", typeFace, 12.0, Brushes.White);
+                Assert.Equal(new Size(0, textLayout0.Height), new Size(textLayout3.Width, textLayout3.Height));
+                Assert.Equal(textLayout0.WidthIncludingTrailingWhitespace, textLayout3.WidthIncludingTrailingWhitespace);
+                Assert.Equal(0, textLayout3.Width);
+            }
+        }
+        
+        private static void AssertGreaterThan(double x, double y, string message) => Assert.True(x > y, $"{message}. {x} is not > {y}");
+
         private static IDisposable Start()
         {
             var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

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


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


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


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


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


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


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


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


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


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


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


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


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