Pārlūkot izejas kodu

Make FormattedTextImpl immutable.

Because it needs to be shared between the UI thread and the render
thread. This also required making it non-disposable like the other
graphics primitive impls.
Steven Kirk 9 gadi atpakaļ
vecāks
revīzija
584cdbb133

+ 11 - 13
samples/XamlTestApplicationPcl/TestScrollable.cs

@@ -62,17 +62,16 @@ namespace XamlTestApplication
 
             for (var i = (int)_offset.Y; i < itemCount; ++i)
             {
-                using (var line = new FormattedText(
+                var line = new FormattedText(
                     "Item " + (i + 1),
                     TextBlock.GetFontFamily(this),
                     TextBlock.GetFontSize(this),
+                    Size.Infinity,
                     TextBlock.GetFontStyle(this),
                     TextAlignment.Left,
-                    TextBlock.GetFontWeight(this)))
-                {
-                    context.DrawText(Brushes.Black, new Point(-_offset.X, y), line);
-                    y += _lineSize.Height;
-                }
+                    TextBlock.GetFontWeight(this));
+                context.DrawText(Brushes.Black, new Point(-_offset.X, y), line);
+                y += _lineSize.Height;
             }
         }
 
@@ -88,18 +87,17 @@ namespace XamlTestApplication
 
         protected override Size MeasureOverride(Size availableSize)
         {
-            using (var line = new FormattedText(
+            var line = new FormattedText(
                 "Item 100",
                 TextBlock.GetFontFamily(this),
                 TextBlock.GetFontSize(this),
+                Size.Infinity,
                 TextBlock.GetFontStyle(this),
                 TextAlignment.Left,
-                TextBlock.GetFontWeight(this)))
-            {
-                line.Constraint = availableSize;
-                _lineSize = line.Measure();
-                return new Size(_lineSize.Width, _lineSize.Height * itemCount);
-            }
+                TextBlock.GetFontWeight(this));
+            line.Constraint = availableSize;
+            _lineSize = line.Measure();
+            return new Size(_lineSize.Width, _lineSize.Height * itemCount);
         }
 
         protected override Size ArrangeOverride(Size finalSize)

+ 0 - 1
src/Avalonia.Base/Avalonia.Base.csproj

@@ -72,7 +72,6 @@
     <Compile Include="Logging\Logger.cs" />
     <Compile Include="Metadata\DependsOnAttribute.cs" />
     <Compile Include="Metadata\ContentAttribute.cs" />
-    <Compile Include="AvaloniaDisposable.cs" />
     <Compile Include="AvaloniaInternalException.cs" />
     <Compile Include="AvaloniaLocator.cs" />
     <Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />

+ 0 - 41
src/Avalonia.Base/AvaloniaDisposable.cs

@@ -1,41 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Platform;
-
-namespace Avalonia
-{
-    public abstract class AvaloniaDisposable : IDisposable
-    {
-#if DEBUG_DISPOSE
-        public string DisposedAt { get; private set; }
-#endif
-
-
-        public bool IsDisposed { get; private set; }
-
-        public void Dispose()
-        {
-            IsDisposed = true;
-#if DEBUG_DISPOSE
-            DisposedAt = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetStackTrace();
-#endif
-            DoDispose();
-        }
-
-        protected void CheckDisposed()
-        {
-            if (IsDisposed)
-                throw new ObjectDisposedException(GetType().FullName
-#if DEBUG_DISPOSE
-                    , "Disposed at: \n" + DisposedAt
-#endif
-
-                    );
-        }
-
-        protected abstract void DoDispose();
-    }
-}

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

@@ -226,16 +226,14 @@ namespace Avalonia.Controls.Presenters
             else
             {
                 // TODO: Pretty sure that measuring "X" isn't the right way to do this...
-                using (var formattedText = new FormattedText(
+                return new FormattedText(
                     "X",
                     FontFamily,
                     FontSize,
+                    availableSize,
                     FontStyle,
                     TextAlignment,
-                    FontWeight))
-                {
-                    return formattedText.Measure();
-                }
+                    FontWeight).Measure();
             }
         }
 

+ 1 - 0
src/Avalonia.Controls/Primitives/AccessText.cs

@@ -89,6 +89,7 @@ namespace Avalonia.Controls.Primitives
                 StripAccessKey(Text),
                 FontFamily,
                 FontSize,
+                constraint,
                 FontStyle,
                 TextAlignment,
                 FontWeight);

+ 1 - 1
src/Avalonia.Controls/TextBlock.cs

@@ -354,6 +354,7 @@ namespace Avalonia.Controls
                 Text ?? string.Empty,
                 FontFamily,
                 FontSize,
+                constraint,
                 FontStyle,
                 TextAlignment,
                 FontWeight,
@@ -370,7 +371,6 @@ namespace Avalonia.Controls
             if (_formattedText != null)
             {
                 _constraint = _formattedText.Constraint;
-                _formattedText.Dispose();
                 _formattedText = null;
             }
 

+ 1 - 1
src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs

@@ -117,7 +117,7 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
         FormattedText GetText(string str, RFont font)
         {
             var f = ((FontAdapter)font);
-            return new FormattedText(str, f.Name, font.Size, f.FontStyle, TextAlignment.Left, f.Weight);
+            return new FormattedText(str, f.Name, font.Size, Size.Infinity, f.FontStyle, TextAlignment.Left, f.Weight);
         }
 
         public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)

+ 9 - 28
src/Avalonia.Visuals/Media/FormattedText.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Media
     /// <summary>
     /// Represents a piece of text with formatting.
     /// </summary>
-    public class FormattedText : AvaloniaDisposable
+    public class FormattedText
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="FormattedText"/> class.
@@ -18,6 +18,7 @@ namespace Avalonia.Media
         /// <param name="text">The text.</param>
         /// <param name="fontFamilyName">The font family.</param>
         /// <param name="fontSize">The font size.</param>
+        /// <param name="constraint">The text layout constraints.</param>
         /// <param name="fontStyle">The font style.</param>
         /// <param name="textAlignment">The text alignment.</param>
         /// <param name="fontWeight">The font weight.</param>
@@ -26,6 +27,7 @@ namespace Avalonia.Media
             string text,
             string fontFamilyName,
             double fontSize,
+            Size constraint,
             FontStyle fontStyle = FontStyle.Normal,
             TextAlignment textAlignment = TextAlignment.Left,
             FontWeight fontWeight = FontWeight.Normal,
@@ -66,7 +68,8 @@ namespace Avalonia.Media
                 fontStyle,
                 textAlignment,
                 fontWeight,
-                wrapping);
+                wrapping,
+                constraint);
         }
 
         /// <summary>
@@ -74,16 +77,8 @@ namespace Avalonia.Media
         /// </summary>
         public Size Constraint
         {
-            get
-            {
-                CheckDisposed();
-                return PlatformImpl.Constraint;
-            }
-            set
-            {
-                CheckDisposed();
-                PlatformImpl.Constraint = value;
-            }
+            get { return PlatformImpl.Constraint; }
+            set { PlatformImpl = PlatformImpl.WithConstraint(value); }
         }
 
         /// <summary>
@@ -114,7 +109,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets platform-specific platform implementation.
         /// </summary>
-        public IFormattedTextImpl PlatformImpl { get; }
+        public IFormattedTextImpl PlatformImpl { get; private set; }
 
         /// <summary>
         /// Gets the text alignment.
@@ -126,14 +121,6 @@ namespace Avalonia.Media
         /// </summary>
         public TextWrapping Wrapping { get; }
 
-        /// <summary>
-        /// Disposes of unmanaged resources associated with the formatted text.
-        /// </summary>
-        protected override void DoDispose()
-        {
-            PlatformImpl.Dispose();
-        }
-
         /// <summary>
         /// Gets the lines in the text.
         /// </summary>
@@ -142,7 +129,6 @@ namespace Avalonia.Media
         /// </returns>
         public IEnumerable<FormattedTextLine> GetLines()
         {
-            CheckDisposed();
             return PlatformImpl.GetLines();
         }
 
@@ -155,7 +141,6 @@ namespace Avalonia.Media
         /// </returns>
         public TextHitTestResult HitTestPoint(Point point)
         {
-            CheckDisposed();
             return PlatformImpl.HitTestPoint(point);
         }
 
@@ -166,7 +151,6 @@ namespace Avalonia.Media
         /// <returns>The character bounds.</returns>
         public Rect HitTestTextPosition(int index)
         {
-            CheckDisposed();
             return PlatformImpl.HitTestTextPosition(index);
         }
 
@@ -178,7 +162,6 @@ namespace Avalonia.Media
         /// <returns>The character bounds.</returns>
         public IEnumerable<Rect> HitTestTextRange(int index, int length)
         {
-            CheckDisposed();
             return PlatformImpl.HitTestTextRange(index, length);
         }
 
@@ -188,8 +171,7 @@ namespace Avalonia.Media
         /// <returns>The bounds box of the text.</returns>
         public Size Measure()
         {
-            CheckDisposed();
-            return PlatformImpl.Measure();
+            return PlatformImpl.Size;
         }
 
         /// <summary>
@@ -200,7 +182,6 @@ namespace Avalonia.Media
         /// <param name="length">The length of the text range.</param>
         public void SetForegroundBrush(IBrush brush, int startIndex, int length)
         {
-            CheckDisposed();
             PlatformImpl.SetForegroundBrush(brush, startIndex, length);
         }
     }

+ 15 - 9
src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs

@@ -10,12 +10,17 @@ namespace Avalonia.Platform
     /// <summary>
     /// Defines the platform-specific interface for <see cref="FormattedText"/>.
     /// </summary>
-    public interface IFormattedTextImpl : IDisposable
+    public interface IFormattedTextImpl
     {
         /// <summary>
-        /// Gets or sets the constraint of the text.
+        /// Gets the constraint of the text.
         /// </summary>
-        Size Constraint { get; set; }
+        Size Constraint { get; }
+
+        /// <summary>
+        /// The measured size of the text.
+        /// </summary>
+        Size Size { get; }
 
         /// <summary>
         /// Gets the text.
@@ -54,12 +59,6 @@ namespace Avalonia.Platform
         /// <returns>The character bounds.</returns>
         IEnumerable<Rect> HitTestTextRange(int index, int length);
 
-        /// <summary>
-        /// Gets the size of the text, taking <see cref="Constraint"/> into account.
-        /// </summary>
-        /// <returns>The bounds box of the text.</returns>
-        Size Measure();
-
         /// <summary>
         /// Sets the foreground brush for the specified text range.
         /// </summary>
@@ -67,5 +66,12 @@ namespace Avalonia.Platform
         /// <param name="startIndex">The start of the text range.</param>
         /// <param name="length">The length of the text range.</param>
         void SetForegroundBrush(IBrush brush, int startIndex, int length);
+
+        /// <summary>
+        /// Makes a clone of the formatted text with the specified constraint.
+        /// </summary>
+        /// <param name="constraint">The constraint.</param>
+        /// <returns>The cloned formatted text.</returns>
+        IFormattedTextImpl WithConstraint(Size constraint);
     }
 }

+ 3 - 1
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@@ -21,6 +21,7 @@ namespace Avalonia.Platform
         /// <param name="textAlignment">The text alignment.</param>
         /// <param name="fontWeight">The font weight.</param>
         /// <param name="wrapping">The text wrapping mode.</param>
+        /// <param name="constraint">The text layout constraints.</param>
         /// <returns>An <see cref="IFormattedTextImpl"/>.</returns>
         IFormattedTextImpl CreateFormattedText(
             string text,
@@ -29,7 +30,8 @@ namespace Avalonia.Platform
             FontStyle fontStyle,
             TextAlignment textAlignment,
             FontWeight fontWeight,
-            TextWrapping wrapping);
+            TextWrapping wrapping,
+            Size constraint);
 
         /// <summary>
         /// Creates a stream geometry implementation.

+ 9 - 11
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -105,17 +105,15 @@ namespace Avalonia.Rendering
             }
 
             var pt = new Point(40, 40);
-            using (
-                var txt = new FormattedText($"Frame #{_totalFrames} FPS: {_fps} Updates: {count}", "Arial", 18,
-                    FontStyle.Normal,
-                    TextAlignment.Left,
-                    FontWeight.Normal,
-                    TextWrapping.NoWrap))
-            {
-                context.Transform = Matrix.Identity;
-                context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
-                context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
-            }
+            var txt = new FormattedText($"Frame #{_totalFrames} FPS: {_fps} Updates: {count}", "Arial", 18,
+                Size.Infinity,
+                FontStyle.Normal,
+                TextAlignment.Left,
+                FontWeight.Normal,
+                TextWrapping.NoWrap);
+            context.Transform = Matrix.Identity;
+            context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
+            context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
         }
 
         private void UpdateScene()

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public TextNode(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text)
         {
-            Bounds = new Rect(origin, text.Measure()) * transform;
+            Bounds = new Rect(origin, text.Size) * transform;
             Transform = transform;
             Foreground = foreground;
             Origin = origin;

+ 3 - 2
src/Gtk/Avalonia.Cairo/CairoPlatform.cs

@@ -45,9 +45,10 @@ namespace Avalonia.Cairo
             FontStyle fontStyle,
             TextAlignment textAlignment,
             Avalonia.Media.FontWeight fontWeight,
-            TextWrapping wrapping)
+            TextWrapping wrapping,
+            Size constraint)
         {
-            return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight);
+            return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, constraint);
         }
 
         public IRenderTarget CreateRenderTarget(IPlatformHandle handle)

+ 74 - 45
src/Gtk/Avalonia.Cairo/Media/FormattedTextImpl.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Cairo.Media
 {
     public class FormattedTextImpl : IFormattedTextImpl
     {
-        private Size _size;
+        private Size _constraint;
 
         static double CorrectScale(double input)
         {
@@ -26,51 +26,37 @@ namespace Avalonia.Cairo.Media
             double fontSize,
             FontStyle fontStyle,
             TextAlignment textAlignment,
-            FontWeight fontWeight)
+            FontWeight fontWeight,
+            Size constraint)
         {
             Contract.Requires<ArgumentNullException>(context != null);
             Contract.Requires<ArgumentNullException>(text != null);
-            Layout = new Pango.Layout(context);
-            Text = text;
-            Layout.SetText(text);
-            Layout.FontDescription = new Pango.FontDescription
-            {
-                Family = fontFamily,
-                Size = Pango.Units.FromDouble(CorrectScale(fontSize)),
-                Style = (Pango.Style)fontStyle,
-                Weight = fontWeight.ToCairo()
-            };
 
-            Layout.Alignment = textAlignment.ToCairo();
-            Layout.Attributes = new Pango.AttrList();
+            Layout = Create(
+                context,
+                text,
+                fontFamily,
+                fontSize,
+                (Pango.Style)fontStyle,
+                textAlignment.ToCairo(),
+                fontWeight.ToCairo(),
+                constraint);
+            Size = Measure();
         }
 
-        public string Text { get; }
-
-        public Size Constraint
+        public FormattedTextImpl(Pango.Layout layout)
         {
-            get
-            {
-                return _size;
-            }
-
-            set
-            {
-                _size = value;
-                Layout.Width = double.IsPositiveInfinity(value.Width) ?
-                    -1 : Pango.Units.FromDouble(value.Width);
-            }
+            Layout = layout;
+            Size = Measure();
         }
 
-        public Pango.Layout Layout
-        {
-            get;
-        }
+        public string Text => Layout.Text;
 
-        public void Dispose()
-        {
-            Layout.Dispose();
-        }
+        public Size Constraint => _constraint;
+
+        public Size Size { get; }
+
+        public Pango.Layout Layout { get; }
 
         public IEnumerable<FormattedTextLine> GetLines()
         {
@@ -125,15 +111,6 @@ namespace Avalonia.Cairo.Media
             return ranges;
         }
 
-        public Size Measure()
-        {
-            int width;
-            int height;
-            Layout.GetPixelSize(out width, out height);
-
-            return new Size(width, height);
-        }
-
         public void SetForegroundBrush(IBrush brush, int startIndex, int count)
         {
             var scb = brush as SolidColorBrush;
@@ -150,5 +127,57 @@ namespace Avalonia.Cairo.Media
                 Layout.Attributes.Insert(brushAttr);
             }
         }
+
+        public IFormattedTextImpl WithConstraint(Size constraint)
+        {
+            return new FormattedTextImpl(Create(
+                Layout.Context,
+                Layout.Text,
+                Layout.FontDescription.Family,
+                Layout.FontDescription.Size,
+                Layout.FontDescription.Style,
+                Layout.Alignment,
+                Layout.FontDescription.Weight,
+                constraint));
+        }
+
+        private Pango.Layout Create(
+            Pango.Context context,
+            string text,
+            string fontFamily,
+            double fontSize,
+            Pango.Style fontStyle,
+            Pango.Alignment textAlignment,
+            Pango.Weight fontWeight,
+            Size constraint)
+        {
+            Contract.Requires<ArgumentNullException>(context != null);
+            Contract.Requires<ArgumentNullException>(text != null);
+            var result = new Pango.Layout(context);
+
+            result.SetText(text);
+
+            result.FontDescription = new Pango.FontDescription
+            {
+                Family = fontFamily,
+                Size = Pango.Units.FromDouble(CorrectScale(fontSize)),
+                Style = (Pango.Style)fontStyle,
+                Weight = fontWeight
+            };
+
+            result.Alignment = textAlignment;
+            result.Attributes = new Pango.AttrList();
+            result.Width = double.IsPositiveInfinity(constraint.Width) ? -1 : Pango.Units.FromDouble(constraint.Width);
+            return result;
+        }
+
+        private Size Measure()
+        {
+            int width;
+            int height;
+            Layout.GetPixelSize(out width, out height);
+
+            return new Size(width, height);
+        }
     }
 }

+ 1 - 1
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -281,7 +281,7 @@ namespace Avalonia.Skia
 
         public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
         {
-            using (var paint = CreatePaint(foreground, text.Measure()))
+            using (var paint = CreatePaint(foreground, text.Size))
             {
                 var textImpl = text as FormattedTextImpl;
                 textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint);

+ 9 - 22
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Skia
     public class FormattedTextImpl : IFormattedTextImpl
     {
         public FormattedTextImpl(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
-                    TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping)
+                    TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping, Size constraint)
         {
             Text = text ?? string.Empty;
 
@@ -36,27 +36,14 @@ namespace Avalonia.Skia
             _paint.TextAlign = textAlignment.ToSKTextAlign();
 
             _wrapping = wrapping;
+            _constraint = constraint;
 
             Rebuild();
         }
 
-        public Size Constraint
-        {
-            get { return _constraint; }
-            set
-            {
-                if (_constraint == value)
-                    return;
+        public Size Constraint => _constraint;
 
-                _constraint = value;
-
-                Rebuild();
-            }
-        }
-
-        public void Dispose()
-        {
-        }
+        public Size Size => _size;
 
         public IEnumerable<FormattedTextLine> GetLines()
         {
@@ -159,11 +146,6 @@ namespace Avalonia.Skia
             return result;
         }
 
-        public Size Measure()
-        {
-            return _size;
-        }
-
         public void SetForegroundBrush(IBrush brush, int startIndex, int length)
         {
             var key = new FBrushRange(startIndex, length);
@@ -185,6 +167,11 @@ namespace Avalonia.Skia
             return Text;
         }
 
+        public IFormattedTextImpl WithConstraint(Size constraint)
+        {
+            throw new NotImplementedException();
+        }
+
         internal void Draw(DrawingContextImpl context,
                            SKCanvas canvas, SKPoint origin,
                            DrawingContextImpl.PaintWrapper foreground)

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

@@ -15,9 +15,9 @@ namespace Avalonia.Skia
         }
 
         public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
-            TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping)
+            TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping, Size constraint)
         {
-            return new FormattedTextImpl(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight, wrapping);
+            return new FormattedTextImpl(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight, wrapping, constraint);
         }
 
         public IStreamGeometryImpl CreateStreamGeometry()

+ 20 - 8
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -37,13 +37,16 @@ namespace Avalonia.Direct2D1
 
         private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory();
 
-        public static void Initialize() => AvaloniaLocator.CurrentMutable
-            .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
-            .Bind<IRendererFactory>().ToConstant(s_instance)
-            .BindToSelf(s_d2D1Factory)
-            .BindToSelf(s_dwfactory)
-            .BindToSelf(s_imagingFactory);
+        public static void Initialize()
+        {
+            AvaloniaLocator.CurrentMutable
+                .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
+                .Bind<IRendererFactory>().ToConstant(s_instance)
+                .BindToSelf(s_d2D1Factory)
+                .BindToSelf(s_dwfactory)
+                .BindToSelf(s_imagingFactory);
             SharpDX.Configuration.EnableReleaseOnFinalizer = true;
+        }
 
         public IBitmapImpl CreateBitmap(int width, int height)
         {
@@ -57,9 +60,18 @@ namespace Avalonia.Direct2D1
             FontStyle fontStyle,
             TextAlignment textAlignment,
             FontWeight fontWeight,
-            TextWrapping wrapping)
+            TextWrapping wrapping,
+            Size constraint)
         {
-            return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, wrapping);
+            return new FormattedTextImpl(
+                text,
+                fontFamily,
+                fontSize,
+                fontStyle,
+                textAlignment,
+                fontWeight,
+                wrapping,
+                constraint);
         }
 
         public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -194,7 +194,7 @@ namespace Avalonia.Direct2D1.Media
             {
                 var impl = (FormattedTextImpl)text;
 
-                using (var brush = CreateBrush(foreground, impl.Measure()))
+                using (var brush = CreateBrush(foreground, impl.Size))
                 using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush))
                 {
                     if (brush.PlatformBrush != null)

+ 75 - 40
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@@ -19,47 +19,34 @@ namespace Avalonia.Direct2D1.Media
             FontStyle fontStyle,
             TextAlignment textAlignment,
             FontWeight fontWeight,
-            TextWrapping wrapping)
+            TextWrapping wrapping,
+            Size constraint)
         {
-            var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
-
             Text = text;
-
-            using (var format = new DWrite.TextFormat(
-                factory,
+            TextLayout = Create(
+                text,
                 fontFamily,
-                (DWrite.FontWeight)fontWeight,
+                fontSize,
                 (DWrite.FontStyle)fontStyle,
-                (float)fontSize))
-            {
-                format.WordWrapping = wrapping == TextWrapping.Wrap ? 
-                    DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap;
-
-                TextLayout = new DWrite.TextLayout(
-                    factory,
-                    text ?? string.Empty,
-                    format,
-                    float.MaxValue,
-                    float.MaxValue);
-            }
-
-            TextLayout.TextAlignment = textAlignment.ToDirect2D();
+                (DWrite.TextAlignment)textAlignment,
+                (DWrite.FontWeight)fontWeight,
+                wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap,
+                (float)constraint.Width,
+                (float)constraint.Height);
+            Size = Measure();
         }
 
-        public Size Constraint
+        public FormattedTextImpl(string text, DWrite.TextLayout textLayout)
         {
-            get
-            {
-                return new Size(TextLayout.MaxWidth, TextLayout.MaxHeight);
-            }
-
-            set
-            {
-                TextLayout.MaxWidth = (float)value.Width;
-                TextLayout.MaxHeight = (float)value.Height;
-            }
+            Text = text;
+            TextLayout = textLayout;
+            Size = Measure();
         }
 
+        public Size Constraint => new Size(TextLayout.MaxWidth, TextLayout.MaxHeight);
+
+        public Size Size { get; }
+
         public string Text { get; }
 
         public DWrite.TextLayout TextLayout { get; }
@@ -114,7 +101,62 @@ namespace Avalonia.Direct2D1.Media
             return result.Select(x => new Rect(x.Left, x.Top, x.Width, x.Height));
         }
 
-        public Size Measure()
+        public void SetForegroundBrush(IBrush brush, int startIndex, int count)
+        {
+            TextLayout.SetDrawingEffect(
+                new BrushWrapper(brush),
+                new DWrite.TextRange(startIndex, count));
+        }
+
+        public IFormattedTextImpl WithConstraint(Size constraint)
+        {
+            var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
+            return new FormattedTextImpl(Text, Create(
+                Text,
+                TextLayout.FontFamilyName,
+                TextLayout.FontSize,
+                TextLayout.FontStyle,
+                TextLayout.TextAlignment,
+                TextLayout.FontWeight,
+                TextLayout.WordWrapping,
+                (float)constraint.Width,
+                (float)constraint.Height));
+        }
+
+        private static DWrite.TextLayout Create(
+            string text,
+            string fontFamily,
+            double fontSize,
+            DWrite.FontStyle fontStyle,
+            DWrite.TextAlignment textAlignment,
+            DWrite.FontWeight fontWeight,
+            DWrite.WordWrapping wrapping,
+            float constraintX,
+            float constraintY)
+        {
+            var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
+
+            using (var format = new DWrite.TextFormat(
+                factory,
+                fontFamily,
+                fontWeight,
+                fontStyle,
+                (float)fontSize))
+            {
+                format.WordWrapping = wrapping;
+
+                var result = new DWrite.TextLayout(
+                    factory,
+                    text ?? string.Empty,
+                    format,
+                    constraintX,
+                    constraintY);
+                result.TextAlignment = textAlignment;
+                return result;
+            }
+        }
+
+        private Size Measure()
         {
             var metrics = TextLayout.Metrics;
             var width = metrics.WidthIncludingTrailingWhitespace;
@@ -126,12 +168,5 @@ namespace Avalonia.Direct2D1.Media
 
             return new Size(width, TextLayout.Metrics.Height);
         }
-
-        public void SetForegroundBrush(IBrush brush, int startIndex, int count)
-        {
-            TextLayout.SetDrawingEffect(
-                new BrushWrapper(brush),
-                new DWrite.TextRange(startIndex, count));
-        }
     }
 }

+ 60 - 61
tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs

@@ -59,7 +59,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                 fontStyle,
                 textAlignment,
                 fontWeight,
-                wrapping);
+                wrapping,
+                Size.Infinity);
         }
 
         private IFormattedTextImpl Create(string text, double fontSize)
@@ -109,17 +110,15 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             //4.55273438 for font 12 size
             double heightCorr = 0.3793945*fontSize;
 #endif
-            using (var fmt = Create(input, fontSize))
-            {
-                var size = fmt.Measure();
+            var fmt = Create(input, fontSize);
+            var size = fmt.Size;
 
-                Assert.Equal(expWidth, size.Width, 2);
-                Assert.Equal(expHeight + heightCorr, size.Height, 2);
+            Assert.Equal(expWidth, size.Width, 2);
+            Assert.Equal(expHeight + heightCorr, size.Height, 2);
 
-                var linesHeight = fmt.GetLines().Sum(l => l.Height);
+            var linesHeight = fmt.GetLines().Sum(l => l.Height);
 
-                Assert.Equal(expHeight, linesHeight, 2);
-            }
+            Assert.Equal(expHeight, linesHeight, 2);
         }
 
 #if AVALONIA_CAIRO
@@ -143,16 +142,16 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                                                             double widthConstraint,
                                                             TextWrapping wrap)
         {
-            using (var fmt = Create(input, FontSize, wrap))
-            {
-                if (widthConstraint != -1)
-                {
-                    fmt.Constraint = new Size(widthConstraint, 10000);
-                }
+            var fmt = Create(input, FontSize, wrap);
+            var constrained = fmt;
 
-                var lines = fmt.GetLines().ToArray();
-                Assert.Equal(linesCount, lines.Count());
+            if (widthConstraint != -1)
+            {
+                constrained = fmt.WithConstraint(new Size(widthConstraint, 10000));
             }
+
+            var lines = constrained.GetLines().ToArray();
+            Assert.Equal(linesCount, lines.Count());
         }
 
 #if AVALONIA_CAIRO
@@ -186,14 +185,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                                     double x, double y,
                                     bool isInside, bool isTrailing, int pos)
         {
-            using (var fmt = Create(input, FontSize))
-            {
-                var htRes = fmt.HitTestPoint(new Point(x, y));
+            var fmt = Create(input, FontSize);
+            var htRes = fmt.HitTestPoint(new Point(x, y));
 
-                Assert.Equal(pos, htRes.TextPosition);
-                Assert.Equal(isInside, htRes.IsInside);
-                Assert.Equal(isTrailing, htRes.IsTrailing);
-            }
+            Assert.Equal(pos, htRes.TextPosition);
+            Assert.Equal(isInside, htRes.IsInside);
+            Assert.Equal(isTrailing, htRes.IsTrailing);
         }
 
 #if AVALONIA_CAIRO
@@ -213,15 +210,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
         public void Should_HitTestPosition_Correctly(string input,
                     int index, double x, double y, double width, double height)
         {
-            using (var fmt = Create(input, FontSize))
-            {
-                var r = fmt.HitTestTextPosition(index);
+            var fmt = Create(input, FontSize);
+            var r = fmt.HitTestTextPosition(index);
 
-                Assert.Equal(x, r.X, 2);
-                Assert.Equal(y, r.Y, 2);
-                Assert.Equal(width, r.Width, 2);
-                Assert.Equal(height, r.Height, 2);
-            }
+            Assert.Equal(x, r.X, 2);
+            Assert.Equal(y, r.Y, 2);
+            Assert.Equal(width, r.Width, 2);
+            Assert.Equal(height, r.Height, 2);
         }
 
 #if AVALONIA_CAIRO
@@ -237,17 +232,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                                                     double x, double y, double width, double height)
         {
             //parse expected
-            using (var fmt = Create(input, FontSize, TextAlignment.Right))
+            var fmt = Create(input, FontSize, TextAlignment.Right);
+            var constrained = fmt;
+
+            if (widthConstraint != -1)
             {
-                fmt.Constraint = new Size(widthConstraint, 100);
+                constrained = fmt.WithConstraint(new Size(widthConstraint, 100));
+            }
 
-                var r = fmt.HitTestTextPosition(index);
+            var r = constrained.HitTestTextPosition(index);
 
-                Assert.Equal(x, r.X, 2);
-                Assert.Equal(y, r.Y, 2);
-                Assert.Equal(width, r.Width, 2);
-                Assert.Equal(height, r.Height, 2);
-            }
+            Assert.Equal(x, r.X, 2);
+            Assert.Equal(y, r.Y, 2);
+            Assert.Equal(width, r.Width, 2);
+            Assert.Equal(height, r.Height, 2);
         }
 
 #if AVALONIA_CAIRO
@@ -263,17 +261,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                                                     double x, double y, double width, double height)
         {
             //parse expected
-            using (var fmt = Create(input, FontSize, TextAlignment.Center))
+            var fmt = Create(input, FontSize, TextAlignment.Center);
+            var constrained = fmt;
+
+            if (widthConstraint != -1)
             {
-                fmt.Constraint = new Size(widthConstraint, 100);
+                constrained = fmt.WithConstraint(new Size(widthConstraint, 100));
+            }
 
-                var r = fmt.HitTestTextPosition(index);
+            var r = constrained.HitTestTextPosition(index);
 
-                Assert.Equal(x, r.X, 2);
-                Assert.Equal(y, r.Y, 2);
-                Assert.Equal(width, r.Width, 2);
-                Assert.Equal(height, r.Height, 2);
-            }
+            Assert.Equal(x, r.X, 2);
+            Assert.Equal(y, r.Y, 2);
+            Assert.Equal(width, r.Width, 2);
+            Assert.Equal(height, r.Height, 2);
         }
 
 #if AVALONIA_CAIRO
@@ -299,22 +300,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                 return new Rect(v[0], v[1], v[2], v[3]);
             }).ToArray();
 
-            using (var fmt = Create(input, FontSize))
-            {
-                var htRes = fmt.HitTestTextRange(index, length).ToArray();
+            var fmt = Create(input, FontSize);
+            var htRes = fmt.HitTestTextRange(index, length).ToArray();
 
-                Assert.Equal(rects.Length, htRes.Length);
+            Assert.Equal(rects.Length, htRes.Length);
 
-                for (int i = 0; i < rects.Length; i++)
-                {
-                    var exr = rects[i];
-                    var r = htRes[i];
+            for (int i = 0; i < rects.Length; i++)
+            {
+                var exr = rects[i];
+                var r = htRes[i];
 
-                    Assert.Equal(exr.X, r.X, 2);
-                    Assert.Equal(exr.Y, r.Y, 2);
-                    Assert.Equal(exr.Width, r.Width, 2);
-                    Assert.Equal(exr.Height, r.Height, 2);
-                }
+                Assert.Equal(exr.X, r.X, 2);
+                Assert.Equal(exr.Y, r.Y, 2);
+                Assert.Equal(exr.Width, r.Width, 2);
+                Assert.Equal(exr.Height, r.Height, 2);
             }
         }
     }

+ 5 - 1
tests/Avalonia.UnitTests/TestServices.cs

@@ -163,6 +163,9 @@ namespace Avalonia.UnitTests
 
         private static IPlatformRenderInterface CreateRenderInterfaceMock()
         {
+            var formattedTextImpl = new Mock<IFormattedTextImpl>();
+            formattedTextImpl.Setup(x => x.WithConstraint(It.IsAny<Size>())).Returns(() => formattedTextImpl.Object);
+
             return Mock.Of<IPlatformRenderInterface>(x => 
                 x.CreateFormattedText(
                     It.IsAny<string>(),
@@ -171,7 +174,8 @@ namespace Avalonia.UnitTests
                     It.IsAny<FontStyle>(),
                     It.IsAny<TextAlignment>(),
                     It.IsAny<FontWeight>(),
-                    It.IsAny<TextWrapping>()) == Mock.Of<IFormattedTextImpl>() &&
+                    It.IsAny<TextWrapping>(),
+                    It.IsAny<Size>()) == formattedTextImpl.Object &&
                 x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>(
                     y => y.Open() == Mock.Of<IStreamGeometryContextImpl>()));
         }

+ 3 - 1
tests/Avalonia.Visuals.UnitTests/Media/FormattedTextTests.cs

@@ -12,7 +12,8 @@ namespace Avalonia.Visuals.UnitTests.Media
             Assert.Throws<ArgumentException>(() => new FormattedText(
                 "foo",
                 "Ariel",
-                0));
+                0,
+                Size.Infinity));
         }
 
         [Fact]
@@ -22,6 +23,7 @@ namespace Avalonia.Visuals.UnitTests.Media
                 "foo",
                 "Ariel",
                 12,
+                Size.Infinity,
                 fontWeight: 0));
         }
     }

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

@@ -15,7 +15,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
             FontStyle fontStyle,
             TextAlignment textAlignment,
             FontWeight fontWeight,
-            TextWrapping wrapping)
+            TextWrapping wrapping,
+            Size constraint)
         {
             throw new NotImplementedException();
         }