Browse Source

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.
scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
584cdbb133
  1. 24
      samples/XamlTestApplicationPcl/TestScrollable.cs
  2. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  3. 41
      src/Avalonia.Base/AvaloniaDisposable.cs
  4. 8
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  5. 1
      src/Avalonia.Controls/Primitives/AccessText.cs
  6. 2
      src/Avalonia.Controls/TextBlock.cs
  7. 2
      src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs
  8. 37
      src/Avalonia.Visuals/Media/FormattedText.cs
  9. 24
      src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
  10. 4
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  11. 20
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  12. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  13. 5
      src/Gtk/Avalonia.Cairo/CairoPlatform.cs
  14. 119
      src/Gtk/Avalonia.Cairo/Media/FormattedTextImpl.cs
  15. 2
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  16. 31
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  17. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  18. 28
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  19. 2
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  20. 115
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  21. 121
      tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
  22. 6
      tests/Avalonia.UnitTests/TestServices.cs
  23. 4
      tests/Avalonia.Visuals.UnitTests/Media/FormattedTextTests.cs
  24. 3
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

24
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)

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" />

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();
}
}

8
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
src/Avalonia.Controls/Primitives/AccessText.cs

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

2
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;
}

2
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)

37
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);
}
}

24
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);
}
}

4
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.

20
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()

2
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;

5
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)

119
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);
}
}
}

2
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);

31
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)

4
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()

28
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)

2
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)

115
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));
}
}
}

121
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);
}
}
}

6
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>()));
}

4
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));
}
}

3
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();
}

Loading…
Cancel
Save