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

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -72,7 +72,6 @@
<Compile Include="Logging\Logger.cs" /> <Compile Include="Logging\Logger.cs" />
<Compile Include="Metadata\DependsOnAttribute.cs" /> <Compile Include="Metadata\DependsOnAttribute.cs" />
<Compile Include="Metadata\ContentAttribute.cs" /> <Compile Include="Metadata\ContentAttribute.cs" />
<Compile Include="AvaloniaDisposable.cs" />
<Compile Include="AvaloniaInternalException.cs" /> <Compile Include="AvaloniaInternalException.cs" />
<Compile Include="AvaloniaLocator.cs" /> <Compile Include="AvaloniaLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.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 else
{ {
// TODO: Pretty sure that measuring "X" isn't the right way to do this... // TODO: Pretty sure that measuring "X" isn't the right way to do this...
using (var formattedText = new FormattedText( return new FormattedText(
"X", "X",
FontFamily, FontFamily,
FontSize, FontSize,
availableSize,
FontStyle, FontStyle,
TextAlignment, TextAlignment,
FontWeight)) FontWeight).Measure();
{
return formattedText.Measure();
}
} }
} }

1
src/Avalonia.Controls/Primitives/AccessText.cs

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

2
src/Avalonia.Controls/TextBlock.cs

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

2
src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs

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

24
src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs

@ -10,12 +10,17 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Defines the platform-specific interface for <see cref="FormattedText"/>. /// Defines the platform-specific interface for <see cref="FormattedText"/>.
/// </summary> /// </summary>
public interface IFormattedTextImpl : IDisposable public interface IFormattedTextImpl
{ {
/// <summary> /// <summary>
/// Gets or sets the constraint of the text. /// Gets the constraint of the text.
/// </summary> /// </summary>
Size Constraint { get; set; } Size Constraint { get; }
/// <summary>
/// The measured size of the text.
/// </summary>
Size Size { get; }
/// <summary> /// <summary>
/// Gets the text. /// Gets the text.
@ -54,12 +59,6 @@ namespace Avalonia.Platform
/// <returns>The character bounds.</returns> /// <returns>The character bounds.</returns>
IEnumerable<Rect> HitTestTextRange(int index, int length); 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> /// <summary>
/// Sets the foreground brush for the specified text range. /// Sets the foreground brush for the specified text range.
/// </summary> /// </summary>
@ -67,5 +66,12 @@ namespace Avalonia.Platform
/// <param name="startIndex">The start of the text range.</param> /// <param name="startIndex">The start of the text range.</param>
/// <param name="length">The length of the text range.</param> /// <param name="length">The length of the text range.</param>
void SetForegroundBrush(IBrush brush, int startIndex, int length); 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="textAlignment">The text alignment.</param>
/// <param name="fontWeight">The font weight.</param> /// <param name="fontWeight">The font weight.</param>
/// <param name="wrapping">The text wrapping mode.</param> /// <param name="wrapping">The text wrapping mode.</param>
/// <param name="constraint">The text layout constraints.</param>
/// <returns>An <see cref="IFormattedTextImpl"/>.</returns> /// <returns>An <see cref="IFormattedTextImpl"/>.</returns>
IFormattedTextImpl CreateFormattedText( IFormattedTextImpl CreateFormattedText(
string text, string text,
@ -29,7 +30,8 @@ namespace Avalonia.Platform
FontStyle fontStyle, FontStyle fontStyle,
TextAlignment textAlignment, TextAlignment textAlignment,
FontWeight fontWeight, FontWeight fontWeight,
TextWrapping wrapping); TextWrapping wrapping,
Size constraint);
/// <summary> /// <summary>
/// Creates a stream geometry implementation. /// 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); var pt = new Point(40, 40);
using ( var txt = new FormattedText($"Frame #{_totalFrames} FPS: {_fps} Updates: {count}", "Arial", 18,
var txt = new FormattedText($"Frame #{_totalFrames} FPS: {_fps} Updates: {count}", "Arial", 18, Size.Infinity,
FontStyle.Normal, FontStyle.Normal,
TextAlignment.Left, TextAlignment.Left,
FontWeight.Normal, FontWeight.Normal,
TextWrapping.NoWrap)) TextWrapping.NoWrap);
{ context.Transform = Matrix.Identity;
context.Transform = Matrix.Identity; context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure())); context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
}
} }
private void UpdateScene() 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) 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; Transform = transform;
Foreground = foreground; Foreground = foreground;
Origin = origin; Origin = origin;

5
src/Gtk/Avalonia.Cairo/CairoPlatform.cs

@ -45,9 +45,10 @@ namespace Avalonia.Cairo
FontStyle fontStyle, FontStyle fontStyle,
TextAlignment textAlignment, TextAlignment textAlignment,
Avalonia.Media.FontWeight fontWeight, 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) 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 public class FormattedTextImpl : IFormattedTextImpl
{ {
private Size _size; private Size _constraint;
static double CorrectScale(double input) static double CorrectScale(double input)
{ {
@ -26,51 +26,37 @@ namespace Avalonia.Cairo.Media
double fontSize, double fontSize,
FontStyle fontStyle, FontStyle fontStyle,
TextAlignment textAlignment, TextAlignment textAlignment,
FontWeight fontWeight) FontWeight fontWeight,
Size constraint)
{ {
Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(context != null);
Contract.Requires<ArgumentNullException>(text != 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 = Create(
Layout.Attributes = new Pango.AttrList(); context,
text,
fontFamily,
fontSize,
(Pango.Style)fontStyle,
textAlignment.ToCairo(),
fontWeight.ToCairo(),
constraint);
Size = Measure();
} }
public string Text { get; } public FormattedTextImpl(Pango.Layout layout)
public Size Constraint
{ {
get Layout = layout;
{ Size = Measure();
return _size;
}
set
{
_size = value;
Layout.Width = double.IsPositiveInfinity(value.Width) ?
-1 : Pango.Units.FromDouble(value.Width);
}
} }
public Pango.Layout Layout public string Text => Layout.Text;
{
get;
}
public void Dispose() public Size Constraint => _constraint;
{
Layout.Dispose(); public Size Size { get; }
}
public Pango.Layout Layout { get; }
public IEnumerable<FormattedTextLine> GetLines() public IEnumerable<FormattedTextLine> GetLines()
{ {
@ -125,15 +111,6 @@ namespace Avalonia.Cairo.Media
return ranges; 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) public void SetForegroundBrush(IBrush brush, int startIndex, int count)
{ {
var scb = brush as SolidColorBrush; var scb = brush as SolidColorBrush;
@ -150,5 +127,57 @@ namespace Avalonia.Cairo.Media
Layout.Attributes.Insert(brushAttr); 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) 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; var textImpl = text as FormattedTextImpl;
textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint); 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 class FormattedTextImpl : IFormattedTextImpl
{ {
public FormattedTextImpl(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, 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; Text = text ?? string.Empty;
@ -36,27 +36,14 @@ namespace Avalonia.Skia
_paint.TextAlign = textAlignment.ToSKTextAlign(); _paint.TextAlign = textAlignment.ToSKTextAlign();
_wrapping = wrapping; _wrapping = wrapping;
_constraint = constraint;
Rebuild(); Rebuild();
} }
public Size Constraint public Size Constraint => _constraint;
{
get { return _constraint; }
set
{
if (_constraint == value)
return;
_constraint = value; public Size Size => _size;
Rebuild();
}
}
public void Dispose()
{
}
public IEnumerable<FormattedTextLine> GetLines() public IEnumerable<FormattedTextLine> GetLines()
{ {
@ -159,11 +146,6 @@ namespace Avalonia.Skia
return result; return result;
} }
public Size Measure()
{
return _size;
}
public void SetForegroundBrush(IBrush brush, int startIndex, int length) public void SetForegroundBrush(IBrush brush, int startIndex, int length)
{ {
var key = new FBrushRange(startIndex, length); var key = new FBrushRange(startIndex, length);
@ -185,6 +167,11 @@ namespace Avalonia.Skia
return Text; return Text;
} }
public IFormattedTextImpl WithConstraint(Size constraint)
{
throw new NotImplementedException();
}
internal void Draw(DrawingContextImpl context, internal void Draw(DrawingContextImpl context,
SKCanvas canvas, SKPoint origin, SKCanvas canvas, SKPoint origin,
DrawingContextImpl.PaintWrapper foreground) 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, 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() 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(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory();
public static void Initialize() => AvaloniaLocator.CurrentMutable public static void Initialize()
.Bind<IPlatformRenderInterface>().ToConstant(s_instance) {
.Bind<IRendererFactory>().ToConstant(s_instance) AvaloniaLocator.CurrentMutable
.BindToSelf(s_d2D1Factory) .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
.BindToSelf(s_dwfactory) .Bind<IRendererFactory>().ToConstant(s_instance)
.BindToSelf(s_imagingFactory); .BindToSelf(s_d2D1Factory)
.BindToSelf(s_dwfactory)
.BindToSelf(s_imagingFactory);
SharpDX.Configuration.EnableReleaseOnFinalizer = true; SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
public IBitmapImpl CreateBitmap(int width, int height) public IBitmapImpl CreateBitmap(int width, int height)
{ {
@ -57,9 +60,18 @@ namespace Avalonia.Direct2D1
FontStyle fontStyle, FontStyle fontStyle,
TextAlignment textAlignment, TextAlignment textAlignment,
FontWeight fontWeight, 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) 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; 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)) using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush))
{ {
if (brush.PlatformBrush != null) if (brush.PlatformBrush != null)

115
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -19,47 +19,34 @@ namespace Avalonia.Direct2D1.Media
FontStyle fontStyle, FontStyle fontStyle,
TextAlignment textAlignment, TextAlignment textAlignment,
FontWeight fontWeight, FontWeight fontWeight,
TextWrapping wrapping) TextWrapping wrapping,
Size constraint)
{ {
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
Text = text; Text = text;
TextLayout = Create(
using (var format = new DWrite.TextFormat( text,
factory,
fontFamily, fontFamily,
(DWrite.FontWeight)fontWeight, fontSize,
(DWrite.FontStyle)fontStyle, (DWrite.FontStyle)fontStyle,
(float)fontSize)) (DWrite.TextAlignment)textAlignment,
{ (DWrite.FontWeight)fontWeight,
format.WordWrapping = wrapping == TextWrapping.Wrap ? wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap,
DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; (float)constraint.Width,
(float)constraint.Height);
TextLayout = new DWrite.TextLayout( Size = Measure();
factory,
text ?? string.Empty,
format,
float.MaxValue,
float.MaxValue);
}
TextLayout.TextAlignment = textAlignment.ToDirect2D();
} }
public Size Constraint public FormattedTextImpl(string text, DWrite.TextLayout textLayout)
{ {
get Text = text;
{ TextLayout = textLayout;
return new Size(TextLayout.MaxWidth, TextLayout.MaxHeight); Size = Measure();
}
set
{
TextLayout.MaxWidth = (float)value.Width;
TextLayout.MaxHeight = (float)value.Height;
}
} }
public Size Constraint => new Size(TextLayout.MaxWidth, TextLayout.MaxHeight);
public Size Size { get; }
public string Text { get; } public string Text { get; }
public DWrite.TextLayout TextLayout { 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)); 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 metrics = TextLayout.Metrics;
var width = metrics.WidthIncludingTrailingWhitespace; var width = metrics.WidthIncludingTrailingWhitespace;
@ -126,12 +168,5 @@ namespace Avalonia.Direct2D1.Media
return new Size(width, TextLayout.Metrics.Height); 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, fontStyle,
textAlignment, textAlignment,
fontWeight, fontWeight,
wrapping); wrapping,
Size.Infinity);
} }
private IFormattedTextImpl Create(string text, double fontSize) private IFormattedTextImpl Create(string text, double fontSize)
@ -109,17 +110,15 @@ namespace Avalonia.Direct2D1.RenderTests.Media
//4.55273438 for font 12 size //4.55273438 for font 12 size
double heightCorr = 0.3793945*fontSize; double heightCorr = 0.3793945*fontSize;
#endif #endif
using (var fmt = Create(input, fontSize)) var fmt = Create(input, fontSize);
{ var size = fmt.Size;
var size = fmt.Measure();
Assert.Equal(expWidth, size.Width, 2); Assert.Equal(expWidth, size.Width, 2);
Assert.Equal(expHeight + heightCorr, size.Height, 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 #if AVALONIA_CAIRO
@ -143,16 +142,16 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double widthConstraint, double widthConstraint,
TextWrapping wrap) TextWrapping wrap)
{ {
using (var fmt = Create(input, FontSize, wrap)) var fmt = Create(input, FontSize, wrap);
{ var constrained = fmt;
if (widthConstraint != -1)
{
fmt.Constraint = new Size(widthConstraint, 10000);
}
var lines = fmt.GetLines().ToArray(); if (widthConstraint != -1)
Assert.Equal(linesCount, lines.Count()); {
constrained = fmt.WithConstraint(new Size(widthConstraint, 10000));
} }
var lines = constrained.GetLines().ToArray();
Assert.Equal(linesCount, lines.Count());
} }
#if AVALONIA_CAIRO #if AVALONIA_CAIRO
@ -186,14 +185,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double x, double y, double x, double y,
bool isInside, bool isTrailing, int pos) bool isInside, bool isTrailing, int pos)
{ {
using (var fmt = Create(input, FontSize)) var fmt = Create(input, FontSize);
{ var htRes = fmt.HitTestPoint(new Point(x, y));
var htRes = fmt.HitTestPoint(new Point(x, y));
Assert.Equal(pos, htRes.TextPosition); Assert.Equal(pos, htRes.TextPosition);
Assert.Equal(isInside, htRes.IsInside); Assert.Equal(isInside, htRes.IsInside);
Assert.Equal(isTrailing, htRes.IsTrailing); Assert.Equal(isTrailing, htRes.IsTrailing);
}
} }
#if AVALONIA_CAIRO #if AVALONIA_CAIRO
@ -213,15 +210,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public void Should_HitTestPosition_Correctly(string input, public void Should_HitTestPosition_Correctly(string input,
int index, double x, double y, double width, double height) int index, double x, double y, double width, double height)
{ {
using (var fmt = Create(input, FontSize)) var fmt = Create(input, FontSize);
{ var r = fmt.HitTestTextPosition(index);
var r = fmt.HitTestTextPosition(index);
Assert.Equal(x, r.X, 2); Assert.Equal(x, r.X, 2);
Assert.Equal(y, r.Y, 2); Assert.Equal(y, r.Y, 2);
Assert.Equal(width, r.Width, 2); Assert.Equal(width, r.Width, 2);
Assert.Equal(height, r.Height, 2); Assert.Equal(height, r.Height, 2);
}
} }
#if AVALONIA_CAIRO #if AVALONIA_CAIRO
@ -237,17 +232,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double x, double y, double width, double height) double x, double y, double width, double height)
{ {
//parse expected //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(x, r.X, 2);
Assert.Equal(y, r.Y, 2); Assert.Equal(y, r.Y, 2);
Assert.Equal(width, r.Width, 2); Assert.Equal(width, r.Width, 2);
Assert.Equal(height, r.Height, 2); Assert.Equal(height, r.Height, 2);
}
} }
#if AVALONIA_CAIRO #if AVALONIA_CAIRO
@ -263,17 +261,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double x, double y, double width, double height) double x, double y, double width, double height)
{ {
//parse expected //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(x, r.X, 2);
Assert.Equal(y, r.Y, 2); Assert.Equal(y, r.Y, 2);
Assert.Equal(width, r.Width, 2); Assert.Equal(width, r.Width, 2);
Assert.Equal(height, r.Height, 2); Assert.Equal(height, r.Height, 2);
}
} }
#if AVALONIA_CAIRO #if AVALONIA_CAIRO
@ -299,22 +300,20 @@ namespace Avalonia.Direct2D1.RenderTests.Media
return new Rect(v[0], v[1], v[2], v[3]); return new Rect(v[0], v[1], v[2], v[3]);
}).ToArray(); }).ToArray();
using (var fmt = Create(input, FontSize)) var fmt = Create(input, FontSize);
{ var htRes = fmt.HitTestTextRange(index, length).ToArray();
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++) for (int i = 0; i < rects.Length; i++)
{ {
var exr = rects[i]; var exr = rects[i];
var r = htRes[i]; var r = htRes[i];
Assert.Equal(exr.X, r.X, 2); Assert.Equal(exr.X, r.X, 2);
Assert.Equal(exr.Y, r.Y, 2); Assert.Equal(exr.Y, r.Y, 2);
Assert.Equal(exr.Width, r.Width, 2); Assert.Equal(exr.Width, r.Width, 2);
Assert.Equal(exr.Height, r.Height, 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() 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 => return Mock.Of<IPlatformRenderInterface>(x =>
x.CreateFormattedText( x.CreateFormattedText(
It.IsAny<string>(), It.IsAny<string>(),
@ -171,7 +174,8 @@ namespace Avalonia.UnitTests
It.IsAny<FontStyle>(), It.IsAny<FontStyle>(),
It.IsAny<TextAlignment>(), It.IsAny<TextAlignment>(),
It.IsAny<FontWeight>(), It.IsAny<FontWeight>(),
It.IsAny<TextWrapping>()) == Mock.Of<IFormattedTextImpl>() && It.IsAny<TextWrapping>(),
It.IsAny<Size>()) == formattedTextImpl.Object &&
x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>( x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>(
y => y.Open() == Mock.Of<IStreamGeometryContextImpl>())); 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( Assert.Throws<ArgumentException>(() => new FormattedText(
"foo", "foo",
"Ariel", "Ariel",
0)); 0,
Size.Infinity));
} }
[Fact] [Fact]
@ -22,6 +23,7 @@ namespace Avalonia.Visuals.UnitTests.Media
"foo", "foo",
"Ariel", "Ariel",
12, 12,
Size.Infinity,
fontWeight: 0)); fontWeight: 0));
} }
} }

3
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

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

Loading…
Cancel
Save