From ec35cdde79c4396d213bca3e39b4a854fe871520 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 21 May 2016 13:38:34 +0200 Subject: [PATCH] Fixed TextBlock text wrapping. And added render test (so far only to D2D backend) --- .../Avalonia.Controls.csproj | 1 - src/Avalonia.Controls/TextBlock.cs | 4 +- .../Avalonia.SceneGraph.csproj | 3 +- .../Media/FormattedText.cs | 57 ++++++------------ .../Media}/TextWrapping.cs | 4 +- .../Platform/IPlatformRenderInterface.cs | 4 +- .../Rendering/RendererMixin.cs | 3 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 3 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 5 +- .../Media/FormattedTextImpl.cs | 6 +- .../Avalonia.Direct2D1.RenderTests.csproj | 3 +- .../Controls/TextBlockTests.cs | 47 +++++++++++++++ tests/Avalonia.RenderTests/TestBase.cs | 5 ++ tests/Avalonia.UnitTests/TestServices.cs | 3 +- .../TextBlock/Wrapping_NoWrap.expected.png | Bin 0 -> 1206 bytes 16 files changed, 99 insertions(+), 51 deletions(-) rename src/{Avalonia.Controls => Avalonia.SceneGraph/Media}/TextWrapping.cs (81%) create mode 100644 tests/Avalonia.RenderTests/Controls/TextBlockTests.cs create mode 100644 tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 6f7dd4bcf8..6358a8c9d9 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -164,7 +164,6 @@ - diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 05ab49ed79..028df729a5 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -99,6 +99,7 @@ namespace Avalonia.Controls /// static TextBlock() { + ClipToBoundsProperty.OverrideDefaultValue(true); AffectsRender(ForegroundProperty); AffectsRender(FontWeightProperty); AffectsRender(FontSizeProperty); @@ -355,7 +356,8 @@ namespace Avalonia.Controls FontSize, FontStyle, TextAlignment, - FontWeight); + FontWeight, + TextWrapping); result.Constraint = constraint; return result; } diff --git a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj index 681f53f8fc..151ad0997e 100644 --- a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj +++ b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj @@ -1,4 +1,4 @@ - + @@ -68,6 +68,7 @@ + diff --git a/src/Avalonia.SceneGraph/Media/FormattedText.cs b/src/Avalonia.SceneGraph/Media/FormattedText.cs index af065d2f86..1169a56343 100644 --- a/src/Avalonia.SceneGraph/Media/FormattedText.cs +++ b/src/Avalonia.SceneGraph/Media/FormattedText.cs @@ -21,13 +21,15 @@ namespace Avalonia.Media /// The font style. /// The text alignment. /// The font weight. + /// The text wrapping mode. public FormattedText( string text, string fontFamilyName, double fontSize, - FontStyle fontStyle, - TextAlignment textAlignment, - FontWeight fontWeight) + FontStyle fontStyle = FontStyle.Normal, + TextAlignment textAlignment = TextAlignment.Left, + FontWeight fontWeight = FontWeight.Normal, + TextWrapping wrapping = TextWrapping.Wrap) { Contract.Requires(text != null); Contract.Requires(fontFamilyName != null); @@ -39,6 +41,7 @@ namespace Avalonia.Media FontStyle = fontStyle; FontWeight = fontWeight; TextAlignment = textAlignment; + Wrapping = wrapping; var platform = AvaloniaLocator.Current.GetService(); @@ -53,7 +56,8 @@ namespace Avalonia.Media fontSize, fontStyle, textAlignment, - fontWeight); + fontWeight, + wrapping); } /// @@ -76,63 +80,42 @@ namespace Avalonia.Media /// /// Gets the font family. /// - public string FontFamilyName - { - get; - private set; - } + public string FontFamilyName { get; } /// /// Gets the font size. /// - public double FontSize - { - get; - private set; - } + public double FontSize { get; } /// /// Gets the font style. /// - public FontStyle FontStyle - { - get; - private set; - } + public FontStyle FontStyle { get; } /// /// Gets the font weight. /// - public FontWeight FontWeight - { - get; - private set; - } + public FontWeight FontWeight { get; } /// /// Gets the text. /// - public string Text - { - get; - private set; - } + public string Text { get; } /// /// Gets platform-specific platform implementation. /// - public IFormattedTextImpl PlatformImpl - { - get; } + public IFormattedTextImpl PlatformImpl { get; } /// /// Gets the text alignment. /// - public TextAlignment TextAlignment - { - get; - private set; - } + public TextAlignment TextAlignment { get; } + + /// + /// Gets the text wrapping. + /// + public TextWrapping Wrapping { get; } /// /// Disposes of unmanaged resources associated with the formatted text. diff --git a/src/Avalonia.Controls/TextWrapping.cs b/src/Avalonia.SceneGraph/Media/TextWrapping.cs similarity index 81% rename from src/Avalonia.Controls/TextWrapping.cs rename to src/Avalonia.SceneGraph/Media/TextWrapping.cs index e1047414a9..7d50e10799 100644 --- a/src/Avalonia.Controls/TextWrapping.cs +++ b/src/Avalonia.SceneGraph/Media/TextWrapping.cs @@ -1,10 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Avalonia.Controls +namespace Avalonia.Media { /// - /// Controls the wrapping mode in a . + /// Controls the wrapping mode of text. /// public enum TextWrapping { diff --git a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs index 4d40957a60..6beb787aab 100644 --- a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -20,6 +20,7 @@ namespace Avalonia.Platform /// The font style. /// The text alignment. /// The font weight. + /// The text wrapping mode. /// An . IFormattedTextImpl CreateFormattedText( string text, @@ -27,7 +28,8 @@ namespace Avalonia.Platform double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight); + FontWeight fontWeight, + TextWrapping wrapping); /// /// Creates a stream geometry implementation. diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index cd18b0f152..3157feb369 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -62,7 +62,8 @@ namespace Avalonia.Rendering var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18, FontStyle.Normal, TextAlignment.Left, - FontWeight.Normal)) + FontWeight.Normal, + TextWrapping.NoWrap)) { ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure())); ctx.DrawText(Brushes.Black, pt, txt); diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 8092764c1b..5b85e6c68f 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -44,7 +44,8 @@ namespace Avalonia.Cairo double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - Avalonia.Media.FontWeight fontWeight) + Avalonia.Media.FontWeight fontWeight, + TextWrapping wrapping) { return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index da256f07b8..ec61aea552 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -18,7 +18,7 @@ namespace Avalonia.Skia } public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, - TextAlignment textAlignment, FontWeight fontWeight) + TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping) { return FormattedTextImpl.Create(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight); } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index ae2b5d314d..ff0bebd359 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -49,9 +49,10 @@ namespace Avalonia.Direct2D1 double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight) + FontWeight fontWeight, + TextWrapping wrapping) { - return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); + return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, wrapping); } public IRenderTarget CreateRenderer(IPlatformHandle handle) diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 5e77d4399c..45d06b5e27 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -18,7 +18,8 @@ namespace Avalonia.Direct2D1.Media double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight) + FontWeight fontWeight, + TextWrapping wrapping) { var factory = AvaloniaLocator.Current.GetService(); @@ -29,6 +30,9 @@ namespace Avalonia.Direct2D1.Media (DWrite.FontStyle)fontStyle, (float)fontSize)) { + format.WordWrapping = wrapping == TextWrapping.Wrap ? + DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; + TextLayout = new DWrite.TextLayout( factory, text ?? string.Empty, diff --git a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index f09c0d08e6..fbc639e335 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -73,6 +73,7 @@ + diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs new file mode 100644 index 0000000000..5b1d76cd0f --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_CAIRO +namespace Avalonia.Cairo.RenderTests.Controls +#elif AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Controls +#endif +{ + public class TextBlockTests : TestBase + { + public TextBlockTests() + : base(@"Controls\TextBlock") + { + } + + [Fact] + public void Wrapping_NoWrap() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new TextBlock + { + Background = Brushes.Red, + FontSize = 12, + Foreground = Brushes.Black, + Text = "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit", + VerticalAlignment = VerticalAlignment.Top, + TextWrapping = TextWrapping.NoWrap, + } + }; + + RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 9f23f81e9a..9bf1b9ae0a 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -58,6 +58,11 @@ namespace Avalonia.Direct2D1.RenderTests protected void RenderToFile(Control target, [CallerMemberName] string testName = "") { + if (!Directory.Exists(OutputPath)) + { + Directory.CreateDirectory(OutputPath); + } + string path = Path.Combine(OutputPath, testName + ".out.png"); using (RenderTargetBitmap bitmap = new RenderTargetBitmap( diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index a3f13af8a5..db0f600a0a 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -143,7 +143,8 @@ namespace Avalonia.UnitTests It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny()) == Mock.Of() && + It.IsAny(), + It.IsAny()) == Mock.Of() && x.CreateStreamGeometry() == Mock.Of( y => y.Open() == Mock.Of())); } diff --git a/tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png b/tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2296e02d68707f6276a8a875cb38fa99ad3309b5 GIT binary patch literal 1206 zcmc&z>rdJT6vaAiYo|?@Ewh_W+c=G}K_@j<1R0CgQmeCF8^t2NWsV9~Xng>(wwvoC z(G;DEI@qj*WfjS!VtEKI%LG(pTK6-Eh}kNL7K!C0D0JBU5&Lk?$vq!V?#;dTUVdJ7 zXvha4AP^`NbuROgUzPhf6zpeS;V{cDZ{d)+ND%1P_9J^`LH_!1-8nP~1Pc3o9{~#D z&y{{NoSaoi{(@LdZYZm-0@an(5XfLc6*>AeI0>9~4bqb5&pD0CL|$&hT7U<&%V*yW z%1>dOpTD1gN?rAQSvSBG5R|Zx<6TghPC>Zh_JK-M1VgV1cLp-*C8Hidb=0* z{s98AGOh?U0*q%QbR<&db0x1vC7MvPYm2d(3O!IeyMjNrZ5of@ z6>D^I7r7x4TD?04L-Vzs2%J2|#SlM{Hn(0?p}|!X;ImPMvy#>Lz6WeE_yS>|V{6cO zfGh>Zz+Q?PPz!OYaNA>*Bc0G@^K3RTWEQr}cEa_Wi84hY)*u#3;fV9Y-iHn*b8}%( zFKwB$xTzUwoMDpEEuCzBZKEtXq$G`NM&xxGC9xJ0#)-)s8})#!*zV(dnwnoo{H9xK1|gN$^Pue6@TCIc;i=iPA<#M))_7g^jZF;1D78Ksvh2^W(hs<2Y@@VE%aIPz(+q=cM}n<^lnmMN zdW=F#C(7{izI88}FJf^^_;YUI)+s(v`sS>Jk+QfB<&UI0c4;j`lq7&vsKr!bF+r+W za8P5{3RFF$g^Ap>W_q)?TEC-uscXVkwMhtfQ@Ww+?aJlt*9FNw{kI-qqe|MGWf31el_O@3}8) zGc@&P_17oa@j4TF^(Ra1{o{>aQu@k+KU!Mh^xXPRI2K?1pZ(_P{&xz4!)bvXLFJJX S;fKrjyFq2;We$H