diff --git a/Source/Basic Shapes/SvgPathBasedElement.Drawing.cs b/Source/Basic Shapes/SvgPathBasedElement.Drawing.cs
index b2fe09bd8..1b2e266a5 100644
--- a/Source/Basic Shapes/SvgPathBasedElement.Drawing.cs
+++ b/Source/Basic Shapes/SvgPathBasedElement.Drawing.cs
@@ -1,4 +1,4 @@
-#if !NO_SDC
+#if !NO_SDC
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -6,24 +6,10 @@ namespace Svg
{
public abstract partial class SvgPathBasedElement : SvgVisualElement
{
- public override RectangleF Bounds
- {
- get
- {
- var path = Path(null);
- if (path == null)
- return new RectangleF();
- if (Transforms == null || Transforms.Count == 0)
- return path.GetBounds();
-
- using (path = (GraphicsPath)path.Clone())
- using (var matrix = Transforms.GetMatrix())
- {
- path.Transform(matrix);
- return path.GetBounds();
- }
- }
- }
+ ///
+ public override RectangleF Bounds => TransformedBoundsFromPathToClone(Path(null));
+ ///
+ public override RectangleF BoundsRelativeToTop => TransformedBoundsPlusParentsFromPathToClone(Path(null));
}
}
#endif
diff --git a/Source/Basic Shapes/SvgVisualElement.Drawing.cs b/Source/Basic Shapes/SvgVisualElement.Drawing.cs
index 11e6c8910..ff66789a3 100644
--- a/Source/Basic Shapes/SvgVisualElement.Drawing.cs
+++ b/Source/Basic Shapes/SvgVisualElement.Drawing.cs
@@ -36,11 +36,77 @@ SizeF ISvgBoundable.Size
}
///
- /// Gets the bounds of the element.
+ /// Gets the bounds of the element relative to .
///
- /// The bounds.
+ /// The bounds of the element.
public abstract RectangleF Bounds { get; }
+ ///
+ /// Gets the bounds of the element relative to the top-level .
+ ///
+ /// The bounds of the element.
+
+ public abstract RectangleF BoundsRelativeToTop { get; }
+ ///
+ /// Just like but considers all .
+ ///
+ /// The rectangle to be transformed.
+ /// The transformed rectangle, or the original rectangle if no transformation exists.
+ protected RectangleF TransformedBoundsPlusParents(RectangleF bounds)
+ {
+ GraphicsPath path = null;
+ try
+ {
+ foreach (var p in ParentsAndSelf)
+ if (p.Transforms is { Count: > 0 } t)
+ {
+ if (path is null)
+ {
+ path = new();
+ path.AddRectangle(bounds);
+ }
+ using var m = t.GetMatrix();
+ path.Transform(m);
+ }
+ return path?.GetBounds() ?? bounds;
+ }
+ finally { path?.Dispose(); }
+ }
+ protected RectangleF TransformedBoundsPlusParentsFromPathToClone(GraphicsPath path)
+ {
+ if (path is null) return default;
+ GraphicsPath pathCloned = null;
+ try
+ {
+ foreach (var p in ParentsAndSelf)
+ if (p.Transforms is { Count: > 0 } t)
+ {
+ pathCloned ??= (GraphicsPath)path.Clone();
+ using var m = t.GetMatrix();
+ pathCloned.Transform(m);
+ }
+ return pathCloned?.GetBounds() ?? path.GetBounds();
+ }
+ finally { pathCloned?.Dispose(); }
+ }
+ protected RectangleF BoundsFromChildren(Func boundsGetter, Func transform)
+ {
+ var r = new RectangleF();
+ foreach (var c in this.Children)
+ {
+ if (c is SvgVisualElement v)
+ {
+ // First it should check if rectangle is empty or it will return the wrong Bounds.
+ // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
+ if (r.IsEmpty)
+ r = boundsGetter(v);
+ else if (boundsGetter(v) is { IsEmpty: false } childBounds)
+ r = RectangleF.Union(r, childBounds);
+ }
+ }
+ return transform(r);
+ }
+
///
/// Renders the and contents to the specified object.
///
diff --git a/Source/Document Structure/SvgGroup.Drawing.cs b/Source/Document Structure/SvgGroup.Drawing.cs
index 705b1a49f..6e4c53d32 100644
--- a/Source/Document Structure/SvgGroup.Drawing.cs
+++ b/Source/Document Structure/SvgGroup.Drawing.cs
@@ -1,4 +1,4 @@
-#if !NO_SDC
+#if !NO_SDC
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -15,38 +15,10 @@ public override GraphicsPath Path(ISvgRenderer renderer)
return GetPaths(this, renderer);
}
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
- {
- get
- {
- var r = new RectangleF();
- foreach (var c in this.Children)
- {
- if (c is SvgVisualElement)
- {
- // First it should check if rectangle is empty or it will return the wrong Bounds.
- // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
- if (r.IsEmpty)
- {
- r = ((SvgVisualElement)c).Bounds;
- }
- else
- {
- var childBounds = ((SvgVisualElement)c).Bounds;
- if (!childBounds.IsEmpty)
- {
- r = RectangleF.Union(r, childBounds);
- }
- }
- }
- }
- return TransformedBounds(r);
- }
- }
+ ///
+ public override RectangleF Bounds => BoundsFromChildren(e => e.Bounds, TransformedBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => BoundsFromChildren(e => e.BoundsRelativeToTop, r => r);
}
}
#endif
diff --git a/Source/Document Structure/SvgImage.Drawing.cs b/Source/Document Structure/SvgImage.Drawing.cs
index b210f5df2..45c64cdc8 100644
--- a/Source/Document Structure/SvgImage.Drawing.cs
+++ b/Source/Document Structure/SvgImage.Drawing.cs
@@ -15,11 +15,7 @@ public partial class SvgImage : SvgVisualElement
private bool _gettingBounds;
private GraphicsPath _path;
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
+ RectangleF RawBounds
{
get
{
@@ -31,13 +27,18 @@ public override RectangleF Bounds
return new RectangleF();
}
_gettingBounds = true;
- var bounds = TransformedBounds(new RectangleF(Location.ToDeviceValue(null, this),
+ var bounds = new RectangleF(Location.ToDeviceValue(null, this),
new SizeF(Width.ToDeviceValue(null, UnitRenderingType.Horizontal, this),
- Height.ToDeviceValue(null, UnitRenderingType.Vertical, this))));
+ Height.ToDeviceValue(null, UnitRenderingType.Vertical, this)));
_gettingBounds = false;
return bounds;
}
}
+ ///
+ public override RectangleF Bounds => TransformedBounds(RawBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => TransformedBoundsPlusParents(RawBounds);
+
///
/// Gets the for this element.
diff --git a/Source/Document Structure/SvgSwitch.Drawing.cs b/Source/Document Structure/SvgSwitch.Drawing.cs
index 1b81b29ab..2bae35940 100644
--- a/Source/Document Structure/SvgSwitch.Drawing.cs
+++ b/Source/Document Structure/SvgSwitch.Drawing.cs
@@ -1,4 +1,4 @@
-#if !NO_SDC
+#if !NO_SDC
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -15,39 +15,10 @@ public override GraphicsPath Path(ISvgRenderer renderer)
return GetPaths(this, renderer);
}
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
- {
- get
- {
- var r = new RectangleF();
- foreach (var c in this.Children)
- {
- if (c is SvgVisualElement)
- {
- // First it should check if rectangle is empty or it will return the wrong Bounds.
- // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
- if (r.IsEmpty)
- {
- r = ((SvgVisualElement)c).Bounds;
- }
- else
- {
- var childBounds = ((SvgVisualElement)c).Bounds;
- if (!childBounds.IsEmpty)
- {
- r = RectangleF.Union(r, childBounds);
- }
- }
- }
- }
-
- return TransformedBounds(r);
- }
- }
+ ///
+ public override RectangleF Bounds => BoundsFromChildren(e => e.Bounds, TransformedBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => BoundsFromChildren(e => e.BoundsRelativeToTop, r => r);
///
/// Renders the and contents to the specified object.
diff --git a/Source/Document Structure/SvgSymbol.Drawing.cs b/Source/Document Structure/SvgSymbol.Drawing.cs
index c6ccfe44c..b8a9e8819 100644
--- a/Source/Document Structure/SvgSymbol.Drawing.cs
+++ b/Source/Document Structure/SvgSymbol.Drawing.cs
@@ -15,39 +15,10 @@ public override GraphicsPath Path(ISvgRenderer renderer)
return GetPaths(this, renderer);
}
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
- {
- get
- {
- var r = new RectangleF();
- foreach (var c in this.Children)
- {
- if (c is SvgVisualElement)
- {
- // First it should check if rectangle is empty or it will return the wrong Bounds.
- // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
- if (r.IsEmpty)
- {
- r = ((SvgVisualElement)c).Bounds;
- }
- else
- {
- var childBounds = ((SvgVisualElement)c).Bounds;
- if (!childBounds.IsEmpty)
- {
- r = RectangleF.Union(r, childBounds);
- }
- }
- }
- }
-
- return TransformedBounds(r);
- }
- }
+ ///
+ public override RectangleF Bounds => BoundsFromChildren(e => e.Bounds, TransformedBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => BoundsFromChildren(e => e.BoundsRelativeToTop, r => r);
///
/// Applies the required transforms to .
diff --git a/Source/Document Structure/SvgUse.Drawing.cs b/Source/Document Structure/SvgUse.Drawing.cs
index fca4dfbfd..c06420ed8 100644
--- a/Source/Document Structure/SvgUse.Drawing.cs
+++ b/Source/Document Structure/SvgUse.Drawing.cs
@@ -27,28 +27,24 @@ public override GraphicsPath Path(ISvgRenderer renderer)
return (element != null && !this.HasRecursiveReference()) ? element.Path(renderer) : null;
}
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
+ RectangleF RawBounds(Func boundsGetter, Func transform)
{
- get
+ var ew = this.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, this);
+ var eh = this.Height.ToDeviceValue(null, UnitRenderingType.Vertical, this);
+ if (ew > 0 && eh > 0)
+ return transform(new RectangleF(this.Location.ToDeviceValue(null, this),
+ new SizeF(ew, eh)));
+ if (this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) is SvgVisualElement element)
{
- var ew = this.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, this);
- var eh = this.Height.ToDeviceValue(null, UnitRenderingType.Vertical, this);
- if (ew > 0 && eh > 0)
- return TransformedBounds(new RectangleF(this.Location.ToDeviceValue(null, this),
- new SizeF(ew, eh)));
- var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement;
- if (element != null)
- {
- return element.Bounds;
- }
-
- return new RectangleF();
+ return boundsGetter(element);
}
+
+ return new RectangleF();
}
+ ///
+ public override RectangleF Bounds => RawBounds(e => e.Bounds, TransformedBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => RawBounds(e => e.BoundsRelativeToTop, r => r);
protected override void RenderChildren(ISvgRenderer renderer)
{
diff --git a/Source/Extensibility/SvgForeignObject.Drawing.cs b/Source/Extensibility/SvgForeignObject.Drawing.cs
index 6d283aa52..2619cf986 100644
--- a/Source/Extensibility/SvgForeignObject.Drawing.cs
+++ b/Source/Extensibility/SvgForeignObject.Drawing.cs
@@ -1,4 +1,4 @@
-#if !NO_SDC
+#if !NO_SDC
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -15,39 +15,10 @@ public override GraphicsPath Path(ISvgRenderer renderer)
return GetPaths(this, renderer);
}
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
- {
- get
- {
- var r = new RectangleF();
- foreach (var c in this.Children)
- {
- if (c is SvgVisualElement)
- {
- // First it should check if rectangle is empty or it will return the wrong Bounds.
- // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
- if (r.IsEmpty)
- {
- r = ((SvgVisualElement)c).Bounds;
- }
- else
- {
- var childBounds = ((SvgVisualElement)c).Bounds;
- if (!childBounds.IsEmpty)
- {
- r = RectangleF.Union(r, childBounds);
- }
- }
- }
- }
-
- return TransformedBounds(r);
- }
- }
+ ///
+ public override RectangleF Bounds => BoundsFromChildren(e => e.Bounds, TransformedBounds);
+ ///
+ public override RectangleF BoundsRelativeToTop => BoundsFromChildren(e => e.BoundsRelativeToTop, r => r);
/////
///// Renders the and contents to the specified object.
diff --git a/Source/SvgElement.Drawing.cs b/Source/SvgElement.Drawing.cs
index f3ff097b9..05a962ebf 100644
--- a/Source/SvgElement.Drawing.cs
+++ b/Source/SvgElement.Drawing.cs
@@ -79,7 +79,7 @@ void ISvgTransformable.PopTransforms(ISvgRenderer renderer)
/// The transformed rectangle, or the original rectangle if no transformation exists.
protected RectangleF TransformedBounds(RectangleF bounds)
{
- if (Transforms != null && Transforms.Count > 0)
+ if (Transforms != null && Transforms.Count > 0 && !bounds.IsEmpty)
{
using (var path = new GraphicsPath())
using (var matrix = Transforms.GetMatrix())
@@ -91,6 +91,20 @@ protected RectangleF TransformedBounds(RectangleF bounds)
}
return bounds;
}
+ protected RectangleF TransformedBoundsFromPathToClone(GraphicsPath path)
+ {
+ if (path is null) return default;
+ if (Transforms != null && Transforms.Count > 0)
+ {
+ using (path = (GraphicsPath)path.Clone())
+ using (var matrix = Transforms.GetMatrix())
+ {
+ path.Transform(matrix);
+ return path.GetBounds();
+ }
+ }
+ return path.GetBounds();
+ }
///
/// Renders this element to the .
diff --git a/Source/Text/SvgTextBase.Drawing.cs b/Source/Text/SvgTextBase.Drawing.cs
index 88fcbf787..52baa3671 100644
--- a/Source/Text/SvgTextBase.Drawing.cs
+++ b/Source/Text/SvgTextBase.Drawing.cs
@@ -9,11 +9,7 @@ namespace Svg
{
public abstract partial class SvgTextBase : SvgVisualElement
{
- ///
- /// Gets the bounds of the element.
- ///
- /// The bounds.
- public override RectangleF Bounds
+ GraphicsPath RawPath
{
get
{
@@ -26,17 +22,13 @@ public override RectangleF Bounds
continue;
path.AddPath(elem.Path(null), false);
}
- if (Transforms == null || Transforms.Count == 0)
- return path.GetBounds();
-
- using (path = (GraphicsPath)path.Clone())
- using (var matrix = Transforms.GetMatrix())
- {
- path.Transform(matrix);
- return path.GetBounds();
- }
+ return path;
}
}
+ ///
+ public override RectangleF Bounds => TransformedBoundsFromPathToClone(RawPath);
+ ///
+ public override RectangleF BoundsRelativeToTop => TransformedBoundsPlusParentsFromPathToClone(RawPath);
//private static GraphicsPath GetPath(string text, Font font)
//{
diff --git a/Tests/Svg.UnitTests/BoundsTests.cs b/Tests/Svg.UnitTests/BoundsTests.cs
index 9a2f5261e..9cc1177f7 100644
--- a/Tests/Svg.UnitTests/BoundsTests.cs
+++ b/Tests/Svg.UnitTests/BoundsTests.cs
@@ -89,17 +89,16 @@ public void TestBoundsIndempotent()
AssertEqualBounds("line-xform", 19.5f, -40.5f, 21, 21);
AssertEqualBounds("line-xform", 19.5f, -40.5f, 21, 21);
}
-
- private void AssertEqualBounds(string elementId, float x, float y, float width, float height)
+ void AssertEqualBoundsCore(string elementId, float x, float y, float width, float height, System.Func boundsGetter)
{
const float Epsilon = 0.01f;
- var element = GetElement(elementId);
- var elementBounds = element.Bounds;
+ var elementBounds = boundsGetter(elementId);
Assert.AreEqual(x, elementBounds.X, Epsilon);
Assert.AreEqual(y, elementBounds.Y, Epsilon);
Assert.AreEqual(width, elementBounds.Width, Epsilon);
Assert.AreEqual(height, elementBounds.Height, Epsilon);
}
+ private void AssertEqualBounds(string elementId, float x, float y, float width, float height) => AssertEqualBoundsCore(elementId, x, y, width, height, id => GetElement(id).Bounds);
private SvgVisualElement GetElement(string elementId)
{
@@ -109,5 +108,81 @@ private SvgVisualElement GetElement(string elementId)
}
return testDocument.GetElementById(elementId);
}
+ [Test]
+ public void BoundsUseParentTransforms()
+ {
+ var doc = SvgDocument.FromSvg(@"
+
+");
+ void AssertEqualBounds(string elementId, float x, float y, float width, float height) => AssertEqualBoundsCore(elementId, x, y, width, height, id => doc.GetElementById(id).BoundsRelativeToTop);
+ var a = doc.GetElementById("a");
+ var b = doc.GetElementById("b");
+ var c = doc.GetElementById("c");
+ var d = doc.GetElementById("d");
+ var e = doc.GetElementById("e");
+ var f = doc.GetElementById("f");
+ AssertEqualBounds("a", -5.38f, -3.18f, 72.41f, 100.73f);
+ AssertEqualBounds("b", -5.38f, -3.18f, 24.87f, 14.49f);
+ AssertEqualBounds("c", -5.38f, -3.18f, 24.87f, 14.49f);
+ AssertEqualBounds("d", -5.38f, -3.18f, 24.87f, 14.49f);
+ AssertEqualBounds("e", -1.77f, 22.78f, 68.79f, 74.76f);
+ AssertEqualBounds("f", -0.01f, -8.74f, 24.07f, 8.88f);
+ var aBounds = a.BoundsRelativeToTop;
+ var bBounds = b.BoundsRelativeToTop;
+ var cBounds = c.BoundsRelativeToTop;
+ var dBounds = d.BoundsRelativeToTop;
+ var eBounds = e.BoundsRelativeToTop;
+ var fBounds = f.BoundsRelativeToTop;
+ Assert.AreEqual(bBounds, cBounds);
+ Assert.AreEqual(cBounds, dBounds);
+ Assert.AreNotEqual(cBounds, fBounds);
+ Assert.AreEqual(c.Bounds, f.Bounds); // Important difference between Bounds and BoundsRelativeToTop
+ Assert.AreEqual(aBounds, System.Drawing.RectangleF.Union(dBounds, eBounds));
+ Assert.AreNotEqual(a.Bounds, System.Drawing.RectangleF.Union(d.Bounds, e.Bounds));
+
+ Assert.True(a.Children.Remove(e));
+ Assert.AreNotEqual(aBounds, a.BoundsRelativeToTop);
+ Assert.AreEqual(bBounds, b.BoundsRelativeToTop);
+ Assert.AreEqual(cBounds, c.BoundsRelativeToTop);
+ Assert.AreEqual(dBounds, d.BoundsRelativeToTop);
+ Assert.AreNotEqual(eBounds, e.BoundsRelativeToTop);
+ a.Children.Add(e);
+ Assert.AreEqual(aBounds, a.BoundsRelativeToTop);
+ Assert.AreEqual(bBounds, b.BoundsRelativeToTop);
+ Assert.AreEqual(cBounds, c.BoundsRelativeToTop);
+ Assert.AreEqual(dBounds, d.BoundsRelativeToTop);
+ Assert.AreEqual(eBounds, e.BoundsRelativeToTop);
+
+ var bBoundsRelativeToA = b.Bounds;
+ var t = a.Transforms[1];
+ a.Transforms.Remove(t);
+ Assert.AreNotEqual(aBounds, a.BoundsRelativeToTop);
+ Assert.AreNotEqual(bBounds, b.BoundsRelativeToTop);
+ Assert.AreNotEqual(cBounds, c.BoundsRelativeToTop);
+ Assert.AreNotEqual(dBounds, d.BoundsRelativeToTop);
+ Assert.AreNotEqual(eBounds, e.BoundsRelativeToTop);
+ Assert.AreEqual(bBoundsRelativeToA, b.Bounds);
+ a.Transforms.Add(t);
+ Assert.AreEqual(aBounds, a.BoundsRelativeToTop);
+ Assert.AreEqual(bBounds, b.BoundsRelativeToTop);
+ Assert.AreEqual(cBounds, c.BoundsRelativeToTop);
+ Assert.AreEqual(dBounds, d.BoundsRelativeToTop);
+ Assert.AreEqual(eBounds, e.BoundsRelativeToTop);
+
+ d.Text = "ABCDE";
+ Assert.AreEqual(aBounds, a.BoundsRelativeToTop);
+ Assert.AreNotEqual(bBounds, b.BoundsRelativeToTop);
+ Assert.AreNotEqual(cBounds, c.BoundsRelativeToTop);
+ Assert.AreNotEqual(dBounds, d.BoundsRelativeToTop);
+ Assert.AreEqual(eBounds, e.BoundsRelativeToTop);
+ }
}
}