diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 39759f78f1..a2b5f3f8b4 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -127,11 +127,11 @@ namespace Avalonia.Controls.Presenters
base.Render(context);
if (selectionStart == selectionEnd)
- {
+ {
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
var caretBrush = Brushes.Black;
- if(backgroundColor.HasValue)
+ if (backgroundColor.HasValue)
{
byte red = (byte)~(backgroundColor.Value.R);
byte green = (byte)~(backgroundColor.Value.G);
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
}
-
+
if (_caretBlink)
{
var charPos = FormattedText.HitTestTextPosition(CaretIndex);
@@ -212,7 +212,10 @@ namespace Avalonia.Controls.Presenters
if (length > 0)
{
- result.SetForegroundBrush(Brushes.White, start, length);
+ result.Spans = new[]
+ {
+ new FormattedTextStyleSpan(start, length, foregroundBrush: Brushes.White),
+ };
}
return result;
@@ -228,17 +231,13 @@ namespace Avalonia.Controls.Presenters
}
else
{
- // TODO: Pretty sure that measuring "X" isn't the right way to do this...
- using (var formattedText = new FormattedText(
- "X",
- FontFamily,
- FontSize,
- FontStyle,
- TextAlignment,
- FontWeight))
+ return new FormattedText
{
- return formattedText.Measure();
- }
+ Text = "X",
+ Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
+ TextAlignment = TextAlignment,
+ Constraint = availableSize,
+ }.Measure();
}
}
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index 42a6f9123b..4bb80e6d3f 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -85,15 +85,14 @@ namespace Avalonia.Controls.Primitives
/// A object.
protected override FormattedText CreateFormattedText(Size constraint)
{
- var result = new FormattedText(
- StripAccessKey(Text),
- FontFamily,
- FontSize,
- FontStyle,
- TextAlignment,
- FontWeight);
- result.Constraint = constraint;
- return result;
+ return new FormattedText
+ {
+ Constraint = constraint,
+ Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
+ Text = StripAccessKey(Text),
+ TextAlignment = TextAlignment,
+ Wrapping = TextWrapping,
+ };
}
///
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 028df729a5..c7a77bdf0e 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -116,7 +116,7 @@ namespace Avalonia.Controls
this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
this.GetObservable(FontSizeProperty).Select(_ => Unit.Default),
this.GetObservable(FontStyleProperty).Select(_ => Unit.Default),
- this.GetObservable(FontWeightProperty).Select(_=>Unit.Default))
+ this.GetObservable(FontWeightProperty).Select(_ => Unit.Default))
.Subscribe(_ =>
{
InvalidateFormattedText();
@@ -350,16 +350,14 @@ namespace Avalonia.Controls
/// A object.
protected virtual FormattedText CreateFormattedText(Size constraint)
{
- var result = new FormattedText(
- Text ?? string.Empty,
- FontFamily,
- FontSize,
- FontStyle,
- TextAlignment,
- FontWeight,
- TextWrapping);
- result.Constraint = constraint;
- return result;
+ return new FormattedText
+ {
+ Constraint = constraint,
+ Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
+ Text = Text ?? string.Empty,
+ TextAlignment = TextAlignment,
+ Wrapping = TextWrapping,
+ };
}
///
@@ -370,7 +368,6 @@ namespace Avalonia.Controls
if (_formattedText != null)
{
_constraint = _formattedText.Constraint;
- _formattedText.Dispose();
_formattedText = null;
}
diff --git a/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs
index c9dd2a786c..93f8790741 100644
--- a/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs
+++ b/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs
@@ -117,7 +117,11 @@ 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
+ {
+ Text = str,
+ Typeface = new Typeface(f.Name, font.Size, f.FontStyle, f.Weight),
+ };
}
public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs
index f6aeaf5f5f..9a052f4eb9 100644
--- a/src/Avalonia.Visuals/Media/FormattedText.cs
+++ b/src/Avalonia.Visuals/Media/FormattedText.cs
@@ -10,63 +10,32 @@ namespace Avalonia.Media
///
/// Represents a piece of text with formatting.
///
- public class FormattedText : AvaloniaDisposable
+ public class FormattedText
{
+ private readonly IPlatformRenderInterface _platform;
+ private Size _constraint = Size.Infinity;
+ private IFormattedTextImpl _platformImpl;
+ private IReadOnlyList _spans;
+ private Typeface _typeface;
+ private string _text;
+ private TextAlignment _textAlignment;
+ private TextWrapping _wrapping;
+
///
/// Initializes a new instance of the class.
///
- /// The text.
- /// The font family.
- /// The font size.
- /// The font style.
- /// The text alignment.
- /// The font weight.
- /// The text wrapping mode.
- public FormattedText(
- string text,
- string fontFamilyName,
- double fontSize,
- FontStyle fontStyle = FontStyle.Normal,
- TextAlignment textAlignment = TextAlignment.Left,
- FontWeight fontWeight = FontWeight.Normal,
- TextWrapping wrapping = TextWrapping.Wrap)
+ public FormattedText()
{
- Contract.Requires(text != null);
- Contract.Requires(fontFamilyName != null);
-
- if (fontSize <= 0)
- {
- throw new ArgumentException("FontSize must be greater than 0");
- }
-
- if (fontWeight <= 0)
- {
- throw new ArgumentException("FontWeight must be greater than 0");
- }
-
- Text = text;
- FontFamilyName = fontFamilyName;
- FontSize = fontSize;
- FontStyle = fontStyle;
- FontWeight = fontWeight;
- TextAlignment = textAlignment;
- Wrapping = wrapping;
-
- var platform = AvaloniaLocator.Current.GetService();
-
- if (platform == null)
- {
- throw new Exception("Could not create FormattedText: IPlatformRenderInterface not registered.");
- }
+ _platform = AvaloniaLocator.Current.GetService();
+ }
- PlatformImpl = platform.CreateFormattedText(
- text,
- fontFamilyName,
- fontSize,
- fontStyle,
- textAlignment,
- fontWeight,
- wrapping);
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The platform render interface.
+ public FormattedText(IPlatformRenderInterface platform)
+ {
+ _platform = platform;
}
///
@@ -74,64 +43,76 @@ namespace Avalonia.Media
///
public Size Constraint
{
- get
- {
- CheckDisposed();
- return PlatformImpl.Constraint;
- }
- set
- {
- CheckDisposed();
- PlatformImpl.Constraint = value;
- }
+ get => _constraint;
+ set => Set(ref _constraint, value);
}
///
- /// Gets the font family.
+ /// Gets or sets the base typeface.
///
- public string FontFamilyName { get; }
+ public Typeface Typeface
+ {
+ get => _typeface;
+ set => Set(ref _typeface, value);
+ }
///
- /// Gets the font size.
+ /// Gets or sets a collection of spans that describe the formatting of subsections of the
+ /// text.
///
- public double FontSize { get; }
+ public IReadOnlyList Spans
+ {
+ get => _spans;
+ set => Set(ref _spans, value);
+ }
///
- /// Gets the font style.
+ /// Gets or sets the text.
///
- public FontStyle FontStyle { get; }
+ public string Text
+ {
+ get => _text;
+ set => Set(ref _text, value);
+ }
///
- /// Gets the font weight.
+ /// Gets or sets the aligment of the text.
///
- public FontWeight FontWeight { get; }
+ public TextAlignment TextAlignment
+ {
+ get => _textAlignment;
+ set => Set(ref _textAlignment, value);
+ }
///
- /// Gets the text.
+ /// Gets or sets the text wrapping.
///
- public string Text { get; }
+ public TextWrapping Wrapping
+ {
+ get => _wrapping;
+ set => Set(ref _wrapping, value);
+ }
///
/// Gets platform-specific platform implementation.
///
- public IFormattedTextImpl PlatformImpl { get; }
-
- ///
- /// Gets the text alignment.
- ///
- public TextAlignment TextAlignment { get; }
-
- ///
- /// Gets the text wrapping.
- ///
- public TextWrapping Wrapping { get; }
-
- ///
- /// Disposes of unmanaged resources associated with the formatted text.
- ///
- protected override void DoDispose()
+ public IFormattedTextImpl PlatformImpl
{
- PlatformImpl.Dispose();
+ get
+ {
+ if (_platformImpl == null)
+ {
+ _platformImpl = _platform.CreateFormattedText(
+ _text,
+ _typeface,
+ _textAlignment,
+ _wrapping,
+ _constraint,
+ _spans);
+ }
+
+ return _platformImpl;
+ }
}
///
@@ -142,7 +123,6 @@ namespace Avalonia.Media
///
public IEnumerable GetLines()
{
- CheckDisposed();
return PlatformImpl.GetLines();
}
@@ -155,7 +135,6 @@ namespace Avalonia.Media
///
public TextHitTestResult HitTestPoint(Point point)
{
- CheckDisposed();
return PlatformImpl.HitTestPoint(point);
}
@@ -166,7 +145,6 @@ namespace Avalonia.Media
/// The character bounds.
public Rect HitTestTextPosition(int index)
{
- CheckDisposed();
return PlatformImpl.HitTestTextPosition(index);
}
@@ -178,7 +156,6 @@ namespace Avalonia.Media
/// The character bounds.
public IEnumerable HitTestTextRange(int index, int length)
{
- CheckDisposed();
return PlatformImpl.HitTestTextRange(index, length);
}
@@ -188,20 +165,13 @@ namespace Avalonia.Media
/// The bounds box of the text.
public Size Measure()
{
- CheckDisposed();
- return PlatformImpl.Measure();
+ return PlatformImpl.Size;
}
- ///
- /// Sets the foreground brush for the specified text range.
- ///
- /// The brush.
- /// The start of the text range.
- /// The length of the text range.
- public void SetForegroundBrush(IBrush brush, int startIndex, int length)
+ private void Set(ref T field, T value)
{
- CheckDisposed();
- PlatformImpl.SetForegroundBrush(brush, startIndex, length);
+ field = value;
+ _platformImpl = null;
}
}
}
diff --git a/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs b/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs
new file mode 100644
index 0000000000..96a5c05c74
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Describes the formatting for a span of text in a object.
+ ///
+ public class FormattedTextStyleSpan
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The index of the first character in the span.
+ /// The length of the span.
+ /// The span's foreground brush.
+ public FormattedTextStyleSpan(
+ int startIndex,
+ int length,
+ IBrush foregroundBrush = null)
+ {
+ StartIndex = startIndex;
+ Length = length;
+ ForegroundBrush = foregroundBrush;
+ }
+
+ ///
+ /// Gets the index of the first character in the span.
+ ///
+ public int StartIndex { get; }
+
+ ///
+ /// Gets the length of the span.
+ ///
+ public int Length { get; }
+
+ ///
+ /// Gets the span's foreground brush.
+ ///
+ public IBrush ForegroundBrush { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs
index 092aea0c61..591c7d0468 100644
--- a/src/Avalonia.Visuals/Media/Geometry.cs
+++ b/src/Avalonia.Visuals/Media/Geometry.cs
@@ -22,10 +22,7 @@ namespace Avalonia.Media
///
static Geometry()
{
- TransformProperty.Changed.Subscribe(x =>
- {
- ((Geometry)x.Sender).PlatformImpl.Transform = ((Transform)x.NewValue).Value;
- });
+ TransformProperty.Changed.AddClassHandler(x => x.TransformChanged);
}
///
@@ -68,7 +65,7 @@ namespace Avalonia.Media
}
///
- /// Indicates whether the geometry contains the specified point.
+ /// Indicates whether the geometry's fill contains the specified point.
///
/// The point.
/// true if the geometry contains the point; otherwise, false.
@@ -76,5 +73,22 @@ namespace Avalonia.Media
{
return PlatformImpl.FillContains(point);
}
+
+ ///
+ /// Indicates whether the geometry's stroke contains the specified point.
+ ///
+ /// The pen to use.
+ /// The point.
+ /// true if the geometry contains the point; otherwise, false.
+ public bool StrokeContains(Pen pen, Point point)
+ {
+ return PlatformImpl.StrokeContains(pen, point);
+ }
+
+ private void TransformChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var transform = (Transform)e.NewValue;
+ PlatformImpl = PlatformImpl.WithTransform(transform.Value);
+ }
}
}
diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs
new file mode 100644
index 0000000000..12540b67e7
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Typeface.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Represents a typeface.
+ ///
+ public class Typeface
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the font family.
+ /// The font size, in DIPs.
+ /// The font style.
+ /// The font weight.
+ public Typeface(
+ string fontFamilyName,
+ double fontSize,
+ FontStyle style = FontStyle.Normal,
+ FontWeight weight = FontWeight.Normal)
+ {
+ if (fontSize <= 0)
+ {
+ throw new ArgumentException("Font size must be > 0.");
+ }
+
+ if (weight <= 0)
+ {
+ throw new ArgumentException("Font weight must be > 0.");
+ }
+
+ FontFamilyName = fontFamilyName;
+ FontSize = fontSize;
+ Style = style;
+ Weight = weight;
+ }
+
+ ///
+ /// Gets the name of the font family.
+ ///
+ public string FontFamilyName { get; }
+
+ ///
+ /// Gets the size of the font in DIPs.
+ ///
+ public double FontSize { get; }
+
+ ///
+ /// Gets the font style.
+ ///
+ public FontStyle Style { get; }
+
+ ///
+ /// Gets the font weight.
+ ///
+ public FontWeight Weight { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
index e3a44c437e..606da02c49 100644
--- a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
@@ -10,12 +10,17 @@ namespace Avalonia.Platform
///
/// Defines the platform-specific interface for .
///
- public interface IFormattedTextImpl : IDisposable
+ public interface IFormattedTextImpl
{
///
- /// Gets or sets the constraint of the text.
+ /// Gets the constraint of the text.
///
- Size Constraint { get; set; }
+ Size Constraint { get; }
+
+ ///
+ /// The measured size of the text.
+ ///
+ Size Size { get; }
///
/// Gets the text.
@@ -53,19 +58,5 @@ namespace Avalonia.Platform
/// The number of characters in the text range.
/// The character bounds.
IEnumerable HitTestTextRange(int index, int length);
-
- ///
- /// Gets the size of the text, taking into account.
- ///
- /// The bounds box of the text.
- Size Measure();
-
- ///
- /// Sets the foreground brush for the specified text range.
- ///
- /// The brush.
- /// The start of the text range.
- /// The length of the text range.
- void SetForegroundBrush(IBrush brush, int startIndex, int length);
}
}
diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
index e18a76d739..132e00e56b 100644
--- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Avalonia.Media;
+
namespace Avalonia.Platform
{
///
@@ -14,9 +16,9 @@ namespace Avalonia.Platform
Rect Bounds { get; }
///
- /// Gets or sets a transform to apply to the geometry.
+ /// Gets the transform to applied to the geometry.
///
- Matrix Transform { get; set; }
+ Matrix Transform { get; }
///
/// Gets the geometry's bounding rectangle with the specified stroke thickness.
@@ -26,10 +28,32 @@ namespace Avalonia.Platform
Rect GetRenderBounds(double strokeThickness);
///
- /// Indicates whether the geometry contains the specified point.
+ /// Indicates whether the geometry's fill contains the specified point.
///
/// The point.
/// true if the geometry contains the point; otherwise, false.
bool FillContains(Point point);
+
+ ///
+ /// Intersects the geometry with another geometry.
+ ///
+ /// The other geometry.
+ /// A new representing the intersection.
+ IGeometryImpl Intersect(IGeometryImpl geometry);
+
+ ///
+ /// Indicates whether the geometry's stroke contains the specified point.
+ ///
+ /// The stroke to use.
+ /// The point.
+ /// true if the geometry contains the point; otherwise, false.
+ bool StrokeContains(Pen pen, Point point);
+
+ ///
+ /// Makes a clone of the geometry with the specified transform.
+ ///
+ /// The transform.
+ /// The cloned geometry.
+ IGeometryImpl WithTransform(Matrix transform);
}
}
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index ef58d52b4f..ba7b758484 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -17,21 +17,19 @@ namespace Avalonia.Platform
/// Creates a formatted text implementation.
///
/// The text.
- /// The font family.
- /// The font size.
- /// The font style.
+ /// The base typeface.
/// The text alignment.
- /// The font weight.
/// The text wrapping mode.
+ /// The text layout constraints.
+ /// The style spans.
/// An .
IFormattedTextImpl CreateFormattedText(
string text,
- string fontFamilyName,
- double fontSize,
- FontStyle fontStyle,
+ Typeface typeface,
TextAlignment textAlignment,
- FontWeight fontWeight,
- TextWrapping wrapping);
+ TextWrapping wrapping,
+ Size constraint,
+ IReadOnlyList spans);
///
/// Creates a stream geometry implementation.
diff --git a/src/Avalonia.Visuals/Rendering/RendererMixin.cs b/src/Avalonia.Visuals/Rendering/RendererMixin.cs
index 3d13501edd..a80f89930b 100644
--- a/src/Avalonia.Visuals/Rendering/RendererMixin.cs
+++ b/src/Avalonia.Visuals/Rendering/RendererMixin.cs
@@ -58,16 +58,13 @@ namespace Avalonia.Rendering
s_lastMeasure = now;
}
var pt = new Point(40, 40);
- using (
- var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18,
- FontStyle.Normal,
- TextAlignment.Left,
- FontWeight.Normal,
- TextWrapping.NoWrap))
+ var txt = new FormattedText
{
- ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
- ctx.DrawText(Brushes.Black, pt, txt);
- }
+ Text = "Frame #" + s_frameNum + " FPS: " + s_fps,
+ Typeface = new Typeface("Arial", 18)
+ };
+ ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
+ ctx.DrawText(Brushes.Black, pt, txt);
}
}
}
diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
index 9cf16312e9..dde92d5870 100644
--- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
+++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
@@ -42,14 +42,20 @@ namespace Avalonia.Cairo
public IFormattedTextImpl CreateFormattedText(
string text,
- string fontFamily,
- double fontSize,
- FontStyle fontStyle,
+ Typeface typeface,
TextAlignment textAlignment,
- Avalonia.Media.FontWeight fontWeight,
- TextWrapping wrapping)
+ TextWrapping wrapping,
+ Size constraint,
+ IReadOnlyList spans)
{
- return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight);
+ return new FormattedTextImpl(
+ s_pangoContext,
+ text,
+ typeface,
+ textAlignment,
+ wrapping,
+ constraint,
+ spans);
}
public IRenderTarget CreateRenderTarget(IEnumerable
public abstract class GeometryImpl : IGeometryImpl
{
- private TransformedGeometry _transformed;
-
- ///
- /// Gets the geometry's bounding rectangle.
- ///
- public abstract Rect Bounds
+ public GeometryImpl(Geometry geometry)
{
- get;
+ Geometry = geometry;
}
- ///
- /// Gets the geomentry without any transforms applied.
- ///
- public abstract Geometry DefiningGeometry
+ ///
+ public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
+
+ ///
+ public Geometry Geometry { get; }
+
+ ///
+ public virtual Matrix Transform => Matrix.Identity;
+
+ ///
+ public Rect GetRenderBounds(double strokeThickness)
{
- get;
+ return Geometry.GetWidenedBounds((float)strokeThickness).ToAvalonia();
}
- ///
- /// Gets the Direct2D .
- ///
- public Geometry Geometry => _transformed ?? DefiningGeometry;
+ ///
+ public bool FillContains(Point point)
+ {
+ return Geometry.FillContainsPoint(point.ToSharpDX());
+ }
- ///
- /// Gets or sets the transform for the geometry.
- ///
- public Matrix Transform
+ ///
+ public IGeometryImpl Intersect(IGeometryImpl geometry)
{
- get
- {
- return _transformed != null ?
- _transformed.Transform.ToAvalonia() :
- Matrix.Identity;
- }
+ var result = new PathGeometry(Geometry.Factory);
- set
+ using (var sink = result.Open())
{
- if (value != Transform)
- {
- if (_transformed != null)
- {
- _transformed.Dispose();
- _transformed = null;
- }
-
- if (!value.IsIdentity)
- {
- Factory factory = AvaloniaLocator.Current.GetService();
- _transformed = new TransformedGeometry(
- factory,
- DefiningGeometry,
- value.ToDirect2D());
- }
- }
+ Geometry.Combine(((GeometryImpl)geometry).Geometry, CombineMode.Intersect, sink);
+ return new StreamGeometryImpl(result);
}
}
- ///
- /// Gets the geometry's bounding rectangle with the specified stroke thickness.
- ///
- /// The stroke thickness.
- /// The bounding rectangle.
- public Rect GetRenderBounds(double strokeThickness)
+ ///
+ public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
{
- if (_transformed != null)
- {
- return _transformed.GetWidenedBounds((float)strokeThickness).ToAvalonia();
- }
- else
- {
- return DefiningGeometry.GetWidenedBounds((float)strokeThickness).ToAvalonia();
- }
+ return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)pen.Thickness);
}
-
- public bool FillContains(Point point)
+ ///
+ public IGeometryImpl WithTransform(Matrix transform)
{
- return Geometry.FillContainsPoint(point.ToSharpDX());
+ var factory = AvaloniaLocator.Current.GetService();
+ return new TransformedGeometryImpl(
+ new TransformedGeometry(
+ factory,
+ GetSourceGeometry(),
+ transform.ToDirect2D()));
}
+ protected virtual Geometry GetSourceGeometry() => Geometry;
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
index 213c4b5c03..4c1bc3d6f7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
@@ -3,7 +3,6 @@
using Avalonia.Platform;
using SharpDX.Direct2D1;
-using D2DGeometry = SharpDX.Direct2D1.Geometry;
namespace Avalonia.Direct2D1.Media
{
@@ -12,55 +11,44 @@ namespace Avalonia.Direct2D1.Media
///
public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
{
- private readonly PathGeometry _path;
-
///
/// Initializes a new instance of the class.
///
public StreamGeometryImpl()
+ : base(CreateGeometry())
{
- Factory factory = AvaloniaLocator.Current.GetService();
- _path = new PathGeometry(factory);
}
///
/// Initializes a new instance of the class.
///
/// An existing Direct2D .
- protected StreamGeometryImpl(PathGeometry geometry)
+ public StreamGeometryImpl(PathGeometry geometry)
+ : base(geometry)
{
- _path = geometry;
}
///
- public override Rect Bounds => _path.GetWidenedBounds(0).ToAvalonia();
-
- ///
- public override D2DGeometry DefiningGeometry => _path;
-
- ///
- /// Clones the geometry.
- ///
- /// A cloned geometry.
public IStreamGeometryImpl Clone()
{
Factory factory = AvaloniaLocator.Current.GetService();
var result = new PathGeometry(factory);
var sink = result.Open();
- _path.Stream(sink);
+ ((PathGeometry)Geometry).Stream(sink);
sink.Close();
return new StreamGeometryImpl(result);
}
- ///
- /// Opens the geometry to start defining it.
- ///
- ///
- /// An which can be used to define the geometry.
- ///
+ ///
public IStreamGeometryContextImpl Open()
{
- return new StreamGeometryContextImpl(_path.Open());
+ return new StreamGeometryContextImpl(((PathGeometry)Geometry).Open());
+ }
+
+ private static Geometry CreateGeometry()
+ {
+ Factory factory = AvaloniaLocator.Current.GetService();
+ return new PathGeometry(factory);
}
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs
new file mode 100644
index 0000000000..4043e180dc
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs
@@ -0,0 +1,24 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ public class TransformedGeometryImpl : GeometryImpl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An existing Direct2D .
+ public TransformedGeometryImpl(TransformedGeometry geometry)
+ : base(geometry)
+ {
+ }
+
+ ///
+ public override Matrix Transform => ((TransformedGeometry)Geometry).Transform.ToAvalonia();
+
+ protected override Geometry GetSourceGeometry() => ((TransformedGeometry)Geometry).SourceGeometry;
+ }
+}
diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
index a70130990a..6d1f321b5f 100644
--- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
+++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
@@ -335,7 +335,7 @@ namespace Avalonia.Input.UnitTests
class MockRenderInterface : IPlatformRenderInterface
{
- public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping)
+ public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList spans)
{
throw new NotImplementedException();
}
@@ -414,11 +414,26 @@ namespace Avalonia.Input.UnitTests
throw new NotImplementedException();
}
+ public IGeometryImpl Intersect(IGeometryImpl geometry)
+ {
+ throw new NotImplementedException();
+ }
+
public IStreamGeometryContextImpl Open()
{
return _impl;
}
+ public bool StrokeContains(Pen pen, Point point)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IGeometryImpl WithTransform(Matrix transform)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List points = new List();
diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
index d996f881d2..6b7c73da2a 100644
--- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
@@ -142,6 +142,8 @@ namespace Avalonia.Layout.UnitTests
public string Text { get; }
+ public Size Size => new Size();
+
public void Dispose()
{
}
@@ -155,20 +157,22 @@ namespace Avalonia.Layout.UnitTests
public IEnumerable HitTestTextRange(int index, int length) => new Rect[0];
public Size Measure() => Constraint;
-
- public void SetForegroundBrush(IBrush brush, int startIndex, int length)
- {
- }
}
private void RegisterServices()
{
var globalStyles = new Mock();
var renderInterface = new Mock();
- renderInterface.Setup(x => x.CreateFormattedText(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny(), It.IsAny(), It.IsAny()))
+ renderInterface.Setup(x =>
+ x.CreateFormattedText(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>()))
.Returns(new FormattedTextMock("TEST"));
-
+
var windowImpl = new Mock();
Size clientSize = default(Size);
diff --git a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
index 813db56fed..4f8aeb93ac 100644
--- a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
+++ b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
@@ -50,37 +50,40 @@ namespace Avalonia.Direct2D1.RenderTests.Media
FontStyle fontStyle,
TextAlignment textAlignment,
FontWeight fontWeight,
- TextWrapping wrapping)
+ TextWrapping wrapping,
+ double widthConstraint)
{
var r = AvaloniaLocator.Current.GetService();
return r.CreateFormattedText(text,
- fontFamily,
- fontSize,
- fontStyle,
+ new Typeface(fontFamily, fontSize, fontStyle, fontWeight),
textAlignment,
- fontWeight,
- wrapping);
+ wrapping,
+ widthConstraint == -1 ? Size.Infinity : new Size(widthConstraint, double.PositiveInfinity),
+ null);
}
private IFormattedTextImpl Create(string text, double fontSize)
{
return Create(text, FontName, fontSize,
FontStyle.Normal, TextAlignment.Left,
- FontWeight.Normal, TextWrapping.NoWrap);
+ FontWeight.Normal, TextWrapping.NoWrap,
+ -1);
}
- private IFormattedTextImpl Create(string text, double fontSize, TextAlignment alignment)
+ private IFormattedTextImpl Create(string text, double fontSize, TextAlignment alignment, double widthConstraint)
{
return Create(text, FontName, fontSize,
FontStyle.Normal, alignment,
- FontWeight.Normal, TextWrapping.NoWrap);
+ FontWeight.Normal, TextWrapping.NoWrap,
+ widthConstraint);
}
- private IFormattedTextImpl Create(string text, double fontSize, TextWrapping wrap)
+ private IFormattedTextImpl Create(string text, double fontSize, TextWrapping wrap, double widthConstraint)
{
return Create(text, FontName, fontSize,
FontStyle.Normal, TextAlignment.Left,
- FontWeight.Normal, wrap);
+ FontWeight.Normal, wrap,
+ widthConstraint);
}
#if AVALONIA_CAIRO
@@ -101,17 +104,15 @@ namespace Avalonia.Direct2D1.RenderTests.Media
[InlineData(stringmiddlenewlines, FontSize, 72.01, 4 * FontSizeHeight)]
public void Should_Measure_String_Correctly(string input, double fontSize, double expWidth, double expHeight)
{
- 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, size.Height, 2);
+ Assert.Equal(expWidth, size.Width, 2);
+ Assert.Equal(expHeight, 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
@@ -135,16 +136,11 @@ 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, widthConstraint);
+ var constrained = fmt;
- var lines = fmt.GetLines().ToArray();
- Assert.Equal(linesCount, lines.Count());
- }
+ var lines = constrained.GetLines().ToArray();
+ Assert.Equal(linesCount, lines.Count());
}
#if AVALONIA_CAIRO
@@ -178,14 +174,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
@@ -205,15 +199,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
@@ -229,17 +221,14 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double x, double y, double width, double height)
{
//parse expected
- using (var fmt = Create(input, FontSize, TextAlignment.Right))
- {
- fmt.Constraint = new Size(widthConstraint, 100);
-
- var r = fmt.HitTestTextPosition(index);
+ var fmt = Create(input, FontSize, TextAlignment.Right, widthConstraint);
+ var constrained = fmt;
+ 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
@@ -255,17 +244,14 @@ namespace Avalonia.Direct2D1.RenderTests.Media
double x, double y, double width, double height)
{
//parse expected
- using (var fmt = Create(input, FontSize, TextAlignment.Center))
- {
- fmt.Constraint = new Size(widthConstraint, 100);
+ var fmt = Create(input, FontSize, TextAlignment.Center, widthConstraint);
+ var constrained = fmt;
+ var r = constrained.HitTestTextPosition(index);
- 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
@@ -291,22 +277,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);
}
}
}
diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs
index 3efbd6c9be..8c887343ee 100644
--- a/tests/Avalonia.UnitTests/TestServices.cs
+++ b/tests/Avalonia.UnitTests/TestServices.cs
@@ -13,6 +13,7 @@ using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia.Rendering;
using System.Reactive.Concurrency;
+using System.Collections.Generic;
namespace Avalonia.UnitTests
{
@@ -163,12 +164,11 @@ namespace Avalonia.UnitTests
return Mock.Of(x =>
x.CreateFormattedText(
It.IsAny(),
- It.IsAny(),
- It.IsAny(),
- It.IsAny(),
+ It.IsAny(),
It.IsAny(),
- It.IsAny(),
- It.IsAny()) == Mock.Of() &&
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>()) == Mock.Of() &&
x.CreateStreamGeometry() == Mock.Of(
y => y.Open() == Mock.Of()));
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/FormattedTextTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/FormattedTextTests.cs
deleted file mode 100644
index 648595fa2b..0000000000
--- a/tests/Avalonia.Visuals.UnitTests/Media/FormattedTextTests.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using Avalonia.Media;
-using Xunit;
-
-namespace Avalonia.Visuals.UnitTests.Media
-{
- public class FormattedTextTests
- {
- [Fact]
- public void Exception_Should_Be_Thrown_If_FontSize_0()
- {
- Assert.Throws(() => new FormattedText(
- "foo",
- "Ariel",
- 0));
- }
-
- [Fact]
- public void Exception_Should_Be_Thrown_If_FontWeight_0()
- {
- Assert.Throws(() => new FormattedText(
- "foo",
- "Ariel",
- 12,
- fontWeight: 0));
- }
- }
-}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
new file mode 100644
index 0000000000..2c8f8eb9b2
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
@@ -0,0 +1,21 @@
+using System;
+using Avalonia.Media;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Media
+{
+ public class TypefaceTests
+ {
+ [Fact]
+ public void Exception_Should_Be_Thrown_If_FontSize_LessThanEqualTo_0()
+ {
+ Assert.Throws(() => new Typeface("foo", 0));
+ }
+
+ [Fact]
+ public void Exception_Should_Be_Thrown_If_FontWeight_LessThanEqualTo_0()
+ {
+ Assert.Throws(() => new Typeface("foo", 12, weight: 0));
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index 303736be9c..57399f3df0 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -10,12 +10,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
{
public IFormattedTextImpl CreateFormattedText(
string text,
- string fontFamilyName,
- double fontSize,
- FontStyle fontStyle,
+ Typeface typeface,
TextAlignment textAlignment,
- FontWeight fontWeight,
- TextWrapping wrapping)
+ TextWrapping wrapping,
+ Size constraint,
+ IReadOnlyList spans)
{
throw new NotImplementedException();
}
@@ -94,11 +93,26 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IGeometryImpl Intersect(IGeometryImpl geometry)
+ {
+ throw new NotImplementedException();
+ }
+
public IStreamGeometryContextImpl Open()
{
return _impl;
}
+ public bool StrokeContains(Pen pen, Point point)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IGeometryImpl WithTransform(Matrix transform)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List points = new List();