Commit 9e269139 authored by James Welle's avatar James Welle Committed by Eric Domke
Browse files

Gradient Improvements

- Added support for the "gradientTransform" attribute on both linear and
  radial gradients. The matrix in this attribute needs to be applied to
  attributes with coordinate values on the gradient element in order to
  transform them into the correct coordinate space.

- Added support for a value of "pad" for the "spreadMode" attribute on
  both linear and radial gradients. This is the default value but was not
  implemented correctly. In order to implement, we examine the properties
  of the gradient along with the element to which the gradient is being
  applied to determine if we need to expand the bounds of the gradient to
  fill the element. If so, we do so and adjust the color stops and
  positions so they are correct for the new gradient bounds.

- Divided ISvgStylable into ISvgBoundable and ISvgStylable. The
  SvgUnit.ToDeviceValue method just needs bounds so it can take
  ISvgBoundable. Moved SvgDocument.GetDimensions() to SvgFragment and made
  SvgFragment ISvgBoundable.

- Fixed a bug in SvgFragment.PushTransforms where it was calling the
  SvgUnit.ToDeviceValue overload that takes no parameters. This overload
  doesn't work if the value being converted is a percentage. (The overload
  should probably be removed entirely, but we didn't take that on in this
  commit.)

- Fixed an issue in SvgGroup.Bounds where a child with empty bounds would
  cause the group's bounds to be reported as empty.

- Fixed broken build by adding missing SvgMarker.MarkerUnits property.

- Converted files that we touched with mixed tabs and spaces to spaces.
  Also removed unused usings from files we touched.

- Converted SvgLinearGradientServer to use properties without backing
  fields for X1, Y1, etc. in order to match SvgRadialGradientServer.

- Moved default value assignments into constructors for consistency.
parent 4f3df864
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Diagnostics;
namespace Svg namespace Svg
{ {
/// <summary> /// <summary>
/// The class that all SVG elements should derive from when they are to be rendered. /// The class that all SVG elements should derive from when they are to be rendered.
/// </summary> /// </summary>
public abstract partial class SvgVisualElement : SvgElement, ISvgStylable, ISvgClipable public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
{ {
private bool _dirty; private bool _dirty;
private bool _requiresSmoothRendering; private bool _requiresSmoothRendering;
...@@ -23,6 +17,23 @@ namespace Svg ...@@ -23,6 +17,23 @@ namespace Svg
/// Gets the <see cref="GraphicsPath"/> for this element. /// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary> /// </summary>
public abstract GraphicsPath Path { get; protected set; } public abstract GraphicsPath Path { get; protected set; }
PointF ISvgBoundable.Location
{
get
{
return Bounds.Location;
}
}
SizeF ISvgBoundable.Size
{
get
{
return Bounds.Size;
}
}
/// <summary> /// <summary>
/// Gets the bounds of the element. /// Gets the bounds of the element.
/// </summary> /// </summary>
......
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel; using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Globalization; using System.Globalization;
namespace Svg namespace Svg
...@@ -73,16 +70,16 @@ namespace Svg ...@@ -73,16 +70,16 @@ namespace Svg
/// Converts the current unit to one that can be used at render time. /// Converts the current unit to one that can be used at render time.
/// </summary> /// </summary>
/// <returns>The representation of the current unit in a device value (usually pixels).</returns> /// <returns>The representation of the current unit in a device value (usually pixels).</returns>
public float ToDeviceValue(ISvgStylable styleOwner) public float ToDeviceValue(ISvgBoundable boundable)
{ {
return this.ToDeviceValue(styleOwner, false); return this.ToDeviceValue(boundable, false);
} }
/// <summary> /// <summary>
/// Converts the current unit to one that can be used at render time. /// Converts the current unit to one that can be used at render time.
/// </summary> /// </summary>
/// <returns>The representation of the current unit in a device value (usually pixels).</returns> /// <returns>The representation of the current unit in a device value (usually pixels).</returns>
public float ToDeviceValue(ISvgStylable styleOwner, bool vertical) public float ToDeviceValue(ISvgBoundable boundable, bool vertical)
{ {
// If it's already been calculated // If it's already been calculated
if (this._deviceValue.HasValue) if (this._deviceValue.HasValue)
...@@ -131,14 +128,14 @@ namespace Svg ...@@ -131,14 +128,14 @@ namespace Svg
break; break;
case SvgUnitType.Percentage: case SvgUnitType.Percentage:
// Can't calculate if there is no style owner // Can't calculate if there is no style owner
if (styleOwner == null) if (boundable == null)
{ {
_deviceValue = this.Value; _deviceValue = this.Value;
break; break;
} }
// TODO : Support height percentages // TODO : Support height percentages
System.Drawing.RectangleF size = styleOwner.Bounds; System.Drawing.SizeF size = boundable.Bounds.Size;
_deviceValue = (((vertical) ? size.Height : size.Width) / 100) * this.Value; _deviceValue = (((vertical) ? size.Height : size.Width) / 100) * this.Value;
break; break;
default: default:
......
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing.Drawing2D;
using System.Drawing; using System.Drawing;
using System.ComponentModel; using System.Drawing.Drawing2D;
namespace Svg namespace Svg
{ {
...@@ -12,13 +8,37 @@ namespace Svg ...@@ -12,13 +8,37 @@ namespace Svg
/// An <see cref="SvgFragment"/> represents an SVG fragment that can be the root element or an embedded fragment of an SVG document. /// An <see cref="SvgFragment"/> represents an SVG fragment that can be the root element or an embedded fragment of an SVG document.
/// </summary> /// </summary>
[SvgElement("svg")] [SvgElement("svg")]
public class SvgFragment : SvgElement, ISvgViewPort public class SvgFragment : SvgElement, ISvgViewPort, ISvgBoundable
{ {
/// <summary> /// <summary>
/// Gets the SVG namespace string. /// Gets the SVG namespace string.
/// </summary> /// </summary>
public static readonly Uri Namespace = new Uri("http://www.w3.org/2000/svg"); public static readonly Uri Namespace = new Uri("http://www.w3.org/2000/svg");
PointF ISvgBoundable.Location
{
get
{
return PointF.Empty;
}
}
SizeF ISvgBoundable.Size
{
get
{
return GetDimensions();
}
}
RectangleF ISvgBoundable.Bounds
{
get
{
return new RectangleF(((ISvgBoundable)this).Location, ((ISvgBoundable)this).Size);
}
}
private SvgUnit _x; private SvgUnit _x;
private SvgUnit _y; private SvgUnit _y;
...@@ -117,8 +137,8 @@ namespace Svg ...@@ -117,8 +137,8 @@ namespace Svg
if (!this.ViewBox.Equals(SvgViewBox.Empty)) if (!this.ViewBox.Equals(SvgViewBox.Empty))
{ {
float fScaleX = this.Width.ToDeviceValue() / this.ViewBox.Width; float fScaleX = this.Width.ToDeviceValue(this, false) / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue() / this.ViewBox.Height; float fScaleY = this.Height.ToDeviceValue(this, true) / this.ViewBox.Height;
float fMinX = -this.ViewBox.MinX; float fMinX = -this.ViewBox.MinX;
float fMinY = -this.ViewBox.MinY; float fMinY = -this.ViewBox.MinY;
...@@ -136,8 +156,8 @@ namespace Svg ...@@ -136,8 +156,8 @@ namespace Svg
} }
float fViewMidX = (this.ViewBox.Width / 2) * fScaleX; float fViewMidX = (this.ViewBox.Width / 2) * fScaleX;
float fViewMidY = (this.ViewBox.Height / 2) * fScaleY; float fViewMidY = (this.ViewBox.Height / 2) * fScaleY;
float fMidX = this.Width.ToDeviceValue() / 2; float fMidX = this.Width.ToDeviceValue(this, false) / 2;
float fMidY = this.Height.ToDeviceValue() / 2; float fMidY = this.Height.ToDeviceValue(this, true) / 2;
switch (AspectRatio.Align) switch (AspectRatio.Align)
{ {
...@@ -147,7 +167,7 @@ namespace Svg ...@@ -147,7 +167,7 @@ namespace Svg
fMinX += (fMidX - fViewMidX) / fScaleX; fMinX += (fMidX - fViewMidX) / fScaleX;
break; break;
case SvgPreserveAspectRatio.xMaxYMin: case SvgPreserveAspectRatio.xMaxYMin:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width; fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
break; break;
case SvgPreserveAspectRatio.xMinYMid: case SvgPreserveAspectRatio.xMinYMid:
fMinY += (fMidY - fViewMidY) / fScaleY; fMinY += (fMidY - fViewMidY) / fScaleY;
...@@ -157,19 +177,19 @@ namespace Svg ...@@ -157,19 +177,19 @@ namespace Svg
fMinY += (fMidY - fViewMidY) / fScaleY; fMinY += (fMidY - fViewMidY) / fScaleY;
break; break;
case SvgPreserveAspectRatio.xMaxYMid: case SvgPreserveAspectRatio.xMaxYMid:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width; fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (fMidY - fViewMidY) / fScaleY; fMinY += (fMidY - fViewMidY) / fScaleY;
break; break;
case SvgPreserveAspectRatio.xMinYMax: case SvgPreserveAspectRatio.xMinYMax:
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height; fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break; break;
case SvgPreserveAspectRatio.xMidYMax: case SvgPreserveAspectRatio.xMidYMax:
fMinX += (fMidX - fViewMidX) / fScaleX; fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height; fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break; break;
case SvgPreserveAspectRatio.xMaxYMax: case SvgPreserveAspectRatio.xMaxYMax:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width; fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height; fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break; break;
default: default:
break; break;
...@@ -223,6 +243,24 @@ namespace Svg ...@@ -223,6 +243,24 @@ namespace Svg
this.AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid); this.AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid);
} }
public SizeF GetDimensions()
{
var w = Width.ToDeviceValue();
var h = Height.ToDeviceValue();
RectangleF bounds = new RectangleF();
var isWidthperc = Width.Type == SvgUnitType.Percentage;
var isHeightperc = Height.Type == SvgUnitType.Percentage;
if (isWidthperc || isHeightperc)
{
bounds = this.Bounds; //do just one call to the recursive bounds property
if (isWidthperc) w = (bounds.Width + bounds.X) * (w * 0.01f);
if (isHeightperc) h = (bounds.Height + bounds.Y) * (h * 0.01f);
}
return new SizeF(w, h);
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
...@@ -239,5 +277,7 @@ namespace Svg ...@@ -239,5 +277,7 @@ namespace Svg
newObj.AspectRatio = this.AspectRatio; newObj.AspectRatio = this.AspectRatio;
return newObj; return newObj;
} }
} }
} }
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Linq;
using Svg.Transforms;
namespace Svg namespace Svg
{ {
...@@ -52,9 +46,17 @@ namespace Svg ...@@ -52,9 +46,17 @@ namespace Svg
// First it should check if rectangle is empty or it will return the wrong Bounds. // 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 // 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) if (r.IsEmpty)
{
r = ((SvgVisualElement)c).Bounds; r = ((SvgVisualElement)c).Bounds;
}
else else
r = RectangleF.Union(r, ((SvgVisualElement)c).Bounds); {
var childBounds = ((SvgVisualElement)c).Bounds;
if (!childBounds.IsEmpty)
{
r = RectangleF.Union(r, childBounds);
}
}
} }
} }
......
using System.Drawing;
namespace Svg
{
public interface ISvgBoundable
{
PointF Location
{
get;
}
SizeF Size
{
get;
}
RectangleF Bounds
{
get;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
namespace Svg namespace Svg
...@@ -24,6 +20,5 @@ namespace Svg ...@@ -24,6 +20,5 @@ namespace Svg
SvgUnitCollection StrokeDashArray { get; set; } SvgUnitCollection StrokeDashArray { get; set; }
SvgUnit StrokeDashOffset { get; set; } SvgUnit StrokeDashOffset { get; set; }
GraphicsPath Path { get; } GraphicsPath Path { get; }
RectangleF Bounds { get; }
} }
} }
\ No newline at end of file
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using Svg.Transforms;
namespace Svg namespace Svg
{ {
...@@ -10,7 +12,7 @@ namespace Svg ...@@ -10,7 +12,7 @@ namespace Svg
public abstract class SvgGradientServer : SvgPaintServer public abstract class SvgGradientServer : SvgPaintServer
{ {
private SvgCoordinateUnits _gradientUnits; private SvgCoordinateUnits _gradientUnits;
private SvgGradientSpreadMethod _spreadMethod = SvgGradientSpreadMethod.Pad; private SvgGradientSpreadMethod _spreadMethod;
private SvgGradientServer _inheritGradient; private SvgGradientServer _inheritGradient;
private List<SvgGradientStop> _stops; private List<SvgGradientStop> _stops;
...@@ -20,6 +22,7 @@ namespace Svg ...@@ -20,6 +22,7 @@ namespace Svg
internal SvgGradientServer() internal SvgGradientServer()
{ {
this.GradientUnits = SvgCoordinateUnits.ObjectBoundingBox; this.GradientUnits = SvgCoordinateUnits.ObjectBoundingBox;
this.SpreadMethod = SvgGradientSpreadMethod.Pad;
this._stops = new List<SvgGradientStop>(); this._stops = new List<SvgGradientStop>();
} }
...@@ -96,12 +99,39 @@ namespace Svg ...@@ -96,12 +99,39 @@ namespace Svg
} }
} }
[SvgAttribute("gradientTransform")]
public SvgTransformCollection GradientTransform
{
get
{
return (this.Attributes.GetAttribute<SvgTransformCollection>("gradientTransform"));
}
set
{
this.Attributes["gradientTransform"] = value;
}
}
private Matrix EffectiveGradientTransform
{
get
{
var transform = new Matrix();
if (GradientTransform != null)
{
transform.Multiply(GradientTransform.GetMatrix());
}
return transform;
}
}
/// <summary> /// <summary>
/// Gets a <see cref="ColorBlend"/> representing the <see cref="SvgGradientServer"/>'s gradient stops. /// Gets a <see cref="ColorBlend"/> representing the <see cref="SvgGradientServer"/>'s gradient stops.
/// </summary> /// </summary>
/// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param> /// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the colour blend.</param> /// <param name="opacity">The opacity of the colour blend.</param>
protected ColorBlend GetColourBlend(SvgVisualElement owner, float opacity, bool radial) protected ColorBlend GetColorBlend(SvgVisualElement owner, float opacity, bool radial)
{ {
int colourBlends = this.Stops.Count; int colourBlends = this.Stops.Count;
bool insertStart = false; bool insertStart = false;
...@@ -201,6 +231,38 @@ namespace Svg ...@@ -201,6 +231,38 @@ namespace Svg
} }
} }
protected ISvgBoundable CalculateBoundable(SvgVisualElement renderingElement)
{
return (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? (ISvgBoundable)renderingElement : renderingElement.OwnerDocument;
}
protected PointF TransformPoint(PointF originalPoint)
{
var newPoint = new[] { originalPoint };
EffectiveGradientTransform.TransformPoints(newPoint);
return newPoint[0];
}
protected PointF TransformVector(PointF originalVector)
{
var newVector = new[] { originalVector };
EffectiveGradientTransform.TransformVectors(newVector);
return newVector[0];
}
protected static double CalculateDistance(PointF first, PointF second)
{
return Math.Sqrt(Math.Pow(first.X - second.X, 2) + Math.Pow(first.Y - second.Y, 2));
}
protected static float CalculateLength(PointF vector)
{
return (float)Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}
public override SvgElement DeepCopy<T>() public override SvgElement DeepCopy<T>()
{ {
...@@ -209,6 +271,7 @@ namespace Svg ...@@ -209,6 +271,7 @@ namespace Svg
newObj.SpreadMethod = this.SpreadMethod; newObj.SpreadMethod = this.SpreadMethod;
newObj.GradientUnits = this.GradientUnits; newObj.GradientUnits = this.GradientUnits;
newObj.InheritGradient = this.InheritGradient; newObj.InheritGradient = this.InheritGradient;
newObj.GradientTransform = this.GradientTransform;
return newObj; return newObj;
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.Drawing; using System.Drawing;
using System.ComponentModel; using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg namespace Svg
{ {
[SvgElement("linearGradient")] [SvgElement("linearGradient")]
public sealed class SvgLinearGradientServer : SvgGradientServer public sealed class SvgLinearGradientServer : SvgGradientServer
{ {
private SvgUnit _x1; [SvgAttribute("x1")]
private SvgUnit _y1;
private SvgUnit _x2;
private SvgUnit _y2;
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x1")]
public SvgUnit X1 public SvgUnit X1
{ {
get { return this._x1; } get
{
return this.Attributes.GetAttribute<SvgUnit>("x1");
}
set set
{ {
this._x1 = value; Attributes["x1"] = value;
} }
} }
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y1")] [SvgAttribute("y1")]
public SvgUnit Y1 public SvgUnit Y1
{ {
get { return this._y1; } get
{
return this.Attributes.GetAttribute<SvgUnit>("y1");
}
set set
{ {
this._y1 = value; this.Attributes["y1"] = value;
} }
} }
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x2")] [SvgAttribute("x2")]
public SvgUnit X2 public SvgUnit X2
{ {
get { return this._x2; } get
{
return this.Attributes.GetAttribute<SvgUnit>("x2");
}
set set
{ {
this._x2 = value; Attributes["x2"] = value;
} }
} }
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y2")] [SvgAttribute("y2")]
public SvgUnit Y2 public SvgUnit Y2
{ {
get { return this._y2; } get
{
return this.Attributes.GetAttribute<SvgUnit>("y2");
}
set set
{ {
this._y2 = value; this.Attributes["y2"] = value;
}
}
private bool IsInvalid
{
get
{
// Need at least 2 colours to do the gradient fill
return this.Stops.Count < 2;
} }
} }
public SvgLinearGradientServer() public SvgLinearGradientServer()
{ {
this._x1 = new SvgUnit(0.0f); X1 = new SvgUnit(SvgUnitType.Percentage, 0F);
this._y1 = new SvgUnit(0.0f); Y1 = new SvgUnit(SvgUnitType.Percentage, 0F);
this._x2 = new SvgUnit(0.0f); X2 = new SvgUnit(SvgUnitType.Percentage, 100F);
this._y2 = new SvgUnit(0.0f); Y2 = new SvgUnit(SvgUnitType.Percentage, 0F);
} }
public SvgPoint Start public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{ {
get { return new SvgPoint(this.X1, this.Y1); } if (IsInvalid)
{
return null;
} }
public SvgPoint End var boundable = CalculateBoundable(renderingElement);
var specifiedStart = CalculateStart(boundable);
var specifiedEnd = CalculateEnd(boundable);
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
if (NeedToExpandGradient(renderingElement, specifiedStart, specifiedEnd))
{ {
get { return new SvgPoint(this.X2, this.Y2); } var expansion = ExpandGradient(renderingElement, specifiedStart, specifiedEnd);
effectiveStart = expansion.Item1;
effectiveEnd = expansion.Item2;
} }
public override Brush GetBrush(SvgVisualElement owner, float opacity) return new LinearGradientBrush(effectiveStart, effectiveEnd, Color.Transparent, Color.Transparent)
{ {
// Need at least 2 colours to do the gradient fill InterpolationColors = CalculateColorBlend(renderingElement, opacity, specifiedStart, effectiveStart, specifiedEnd, effectiveEnd),
if (this.Stops.Count < 2) WrapMode = WrapMode.TileFlipX
};
}
private PointF CalculateStart(ISvgBoundable boundable)
{ {
return null; return TransformPoint(new PointF(this.X1.ToDeviceValue(boundable), this.Y1.ToDeviceValue(boundable, true)));
} }
PointF start; private PointF CalculateEnd(ISvgBoundable boundable)
PointF end; {
RectangleF bounds = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? owner.Bounds : owner.OwnerDocument.GetDimensions(); return TransformPoint(new PointF(this.X2.ToDeviceValue(boundable), this.Y2.ToDeviceValue(boundable, true)));
}
// Have start/end points been set? If not the gradient is horizontal private bool NeedToExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
if (!this.End.IsEmpty())
{ {
// Get the points to work out an angle return SpreadMethod == SvgGradientSpreadMethod.Pad && (boundable.Bounds.Contains(specifiedStart) || boundable.Bounds.Contains(specifiedEnd));
if (this.Start.IsEmpty()) }
private Tuple<PointF, PointF> ExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
if (!NeedToExpandGradient(boundable, specifiedStart, specifiedEnd))
{
Debug.Fail("Unexpectedly expanding gradient when not needed!");
return new Tuple<PointF, PointF>(specifiedStart, specifiedEnd);
}
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
var elementDiagonal = (float)CalculateDistance(new PointF(boundable.Bounds.Left, boundable.Bounds.Top), new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom));
var expandedStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -elementDiagonal);
var expandedEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, elementDiagonal);
var intersectionPoints = new LineF(expandedStart.X, expandedStart.Y, expandedEnd.X, expandedEnd.Y).Intersection(boundable.Bounds);
if (boundable.Bounds.Contains(specifiedStart))
{
effectiveStart = CalculateClosestIntersectionPoint(expandedStart, intersectionPoints);
effectiveStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -1);
}
if (boundable.Bounds.Contains(specifiedEnd))
{ {
start = bounds.Location; effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints);
effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1);
}
return new Tuple<PointF, PointF>(effectiveStart, effectiveEnd);
} }
else
private ColorBlend CalculateColorBlend(SvgVisualElement owner, float opacity, PointF specifiedStart, PointF effectiveStart, PointF specifiedEnd, PointF effectiveEnd)
{
var colorBlend = GetColorBlend(owner, opacity, false);
var startDelta = CalculateDistance(specifiedStart, effectiveStart);
var endDelta = CalculateDistance(specifiedEnd, effectiveEnd);
if (!(startDelta > 0) && !(endDelta > 0))
{ {
start = new PointF(this.Start.X.ToDeviceValue(owner), this.Start.Y.ToDeviceValue(owner, true)); return colorBlend;
} }
float x = (this.End.X.IsEmpty) ? start.X : this.End.X.ToDeviceValue(owner); var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
end = new PointF(x, this.End.Y.ToDeviceValue(owner, true)); var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var effectiveLength = CalculateDistance(effectiveStart, effectiveEnd);
for (var i = 0; i < colorBlend.Positions.Length; i++)
{
var originalPoint = MovePointAlongVector(specifiedStart, specifiedUnitVector, (float) specifiedLength * colorBlend.Positions[i]);
var distanceFromEffectiveStart = CalculateDistance(effectiveStart, originalPoint);
colorBlend.Positions[i] = (float) Math.Max(0F, Math.Min((distanceFromEffectiveStart / effectiveLength), 1.0F));
} }
else
if (startDelta > 0)
{ {
// Default: horizontal colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
start = bounds.Location; colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
end = new PointF(bounds.Right, bounds.Top);
} }
LinearGradientBrush gradient = new LinearGradientBrush(start, end, Color.Transparent, Color.Transparent); if (endDelta > 0)
gradient.InterpolationColors = base.GetColourBlend(owner, opacity, false); {
colorBlend.Positions = colorBlend.Positions.Concat(new[] { 1F }).ToArray();
colorBlend.Colors = colorBlend.Colors.Concat(new[] { colorBlend.Colors.Last() }).ToArray();
}
// Needed to fix an issue where the gradient was being wrapped when though it had the correct bounds return colorBlend;
gradient.WrapMode = WrapMode.TileFlipX;
return gradient;
} }
private static PointF CalculateClosestIntersectionPoint(PointF sourcePoint, IList<PointF> targetPoints)
{
Debug.Assert(targetPoints.Count == 2, "Unexpected number of intersection points!");
return CalculateDistance(sourcePoint, targetPoints[0]) < CalculateDistance(sourcePoint, targetPoints[1]) ? targetPoints[0] : targetPoints[1];
}
private static PointF MovePointAlongVector(PointF start, PointF unitVector, float distance)
{
return start + new SizeF(unitVector.X * distance, unitVector.Y * distance);
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
return DeepCopy<SvgLinearGradientServer>(); return DeepCopy<SvgLinearGradientServer>();
} }
public override SvgElement DeepCopy<T>() public override SvgElement DeepCopy<T>()
{ {
var newObj = base.DeepCopy<T>() as SvgLinearGradientServer; var newObj = base.DeepCopy<T>() as SvgLinearGradientServer;
...@@ -134,5 +229,106 @@ namespace Svg ...@@ -134,5 +229,106 @@ namespace Svg
return newObj; return newObj;
} }
private sealed class LineF
{
private float X1
{
get;
set;
}
private float Y1
{
get;
set;
}
private float X2
{
get;
set;
}
private float Y2
{
get;
set;
}
public LineF(float x1, float y1, float x2, float y2)
{
X1 = x1;
Y1 = y1;
X2 = x2;
Y2 = y2;
}
public List<PointF> Intersection(RectangleF rectangle)
{
var result = new List<PointF>();
AddIfIntersect(this, new LineF(rectangle.X, rectangle.Y, rectangle.Right, rectangle.Y), result);
AddIfIntersect(this, new LineF(rectangle.Right, rectangle.Y, rectangle.Right, rectangle.Bottom), result);
AddIfIntersect(this, new LineF(rectangle.Right, rectangle.Bottom, rectangle.X, rectangle.Bottom), result);
AddIfIntersect(this, new LineF(rectangle.X, rectangle.Bottom, rectangle.X, rectangle.Y), result);
return result;
}
private PointF? Intersection(LineF other)
{
var a1 = Y2 - Y1;
var b1 = X1 - X2;
var c1 = X2 * Y1 - X1 * Y2;
var r3 = a1 * other.X1 + b1 * other.Y1 + c1;
var r4 = a1 * other.X2 + b1 * other.Y2 + c1;
if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4))
{
return null;
}
var a2 = other.Y2 - other.Y1;
var b2 = other.X1 - other.X2;
var c2 = other.X2 * other.Y1 - other.X1 * other.Y2;
var r1 = a2 * X1 + b2 * Y1 + c2;
var r2 = a2 * X2 + b2 * Y2 + c2;
if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2))
{
return (null);
}
var denom = a1 * b2 - a2 * b1;
if (denom == 0)
{
return null;
}
var offset = denom < 0 ? -denom / 2 : denom / 2;
var num = b1 * c2 - b2 * c1;
var x = (num < 0 ? num - offset : num + offset) / denom;
num = a2 * c1 - a1 * c2;
var y = (num < 0 ? num - offset : num + offset) / denom;
return new PointF(x, y);
}
private static void AddIfIntersect(LineF first, LineF second, ICollection<PointF> result)
{
var intersection = first.Intersection(second);
if (intersection != null)
{
result.Add(intersection.Value);
}
}
}
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Drawing.Drawing2D;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using Svg.DataTypes; using Svg.DataTypes;
namespace Svg namespace Svg
...@@ -77,6 +72,13 @@ namespace Svg ...@@ -77,6 +72,13 @@ namespace Svg
set { this.Attributes["markerHeight"] = value; } set { this.Attributes["markerHeight"] = value; }
} }
[SvgAttribute("markerUnits")]
public virtual SvgMarkerUnits MarkerUnits
{
get { return this.Attributes.GetAttribute<SvgMarkerUnits>("markerUnits"); }
set { this.Attributes["markerUnits"] = value; }
}
public SvgMarker() public SvgMarker()
{ {
MarkerUnits = SvgMarkerUnits.strokeWidth; MarkerUnits = SvgMarkerUnits.strokeWidth;
......
using System;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg namespace Svg
{ {
...@@ -9,22 +12,40 @@ namespace Svg ...@@ -9,22 +12,40 @@ namespace Svg
[SvgAttribute("cx")] [SvgAttribute("cx")]
public SvgUnit CenterX public SvgUnit CenterX
{ {
get { return this.Attributes.GetAttribute<SvgUnit>("cx"); } get
set { this.Attributes["cx"] = value; } {
return this.Attributes.GetAttribute<SvgUnit>("cx");
}
set
{
this.Attributes["cx"] = value;
}
} }
[SvgAttribute("cy")] [SvgAttribute("cy")]
public SvgUnit CenterY public SvgUnit CenterY
{ {
get { return this.Attributes.GetAttribute<SvgUnit>("cy"); } get
set { this.Attributes["cy"] = value; } {
return this.Attributes.GetAttribute<SvgUnit>("cy");
}
set
{
this.Attributes["cy"] = value;
}
} }
[SvgAttribute("r")] [SvgAttribute("r")]
public SvgUnit Radius public SvgUnit Radius
{ {
get { return this.Attributes.GetAttribute<SvgUnit>("r"); } get
set { this.Attributes["r"] = value; } {
return this.Attributes.GetAttribute<SvgUnit>("r");
}
set
{
this.Attributes["r"] = value;
}
} }
[SvgAttribute("fx")] [SvgAttribute("fx")]
...@@ -41,8 +62,10 @@ namespace Svg ...@@ -41,8 +62,10 @@ namespace Svg
return value; return value;
} }
set
set { this.Attributes["fx"] = value; } {
this.Attributes["fx"] = value;
}
} }
[SvgAttribute("fy")] [SvgAttribute("fy")]
...@@ -59,61 +82,139 @@ namespace Svg ...@@ -59,61 +82,139 @@ namespace Svg
return value; return value;
} }
set
set { this.Attributes["fy"] = value; } {
this.Attributes["fy"] = value;
}
} }
/// <summary>
/// Initializes a new instance of the <see cref="SvgRadialGradientServer"/> class.
/// </summary>
public SvgRadialGradientServer() public SvgRadialGradientServer()
{ {
//Apply default values of 50% to cX,cY and r CenterX = new SvgUnit(SvgUnitType.Percentage, 50F);
CenterX = new SvgUnit(SvgUnitType.Percentage, 50); CenterY = new SvgUnit(SvgUnitType.Percentage, 50F);
CenterY = new SvgUnit(SvgUnitType.Percentage, 50); Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
Radius = new SvgUnit(SvgUnitType.Percentage, 50);
} }
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity) public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{ {
float radius = this.Radius.ToDeviceValue(renderingElement); var origin = CalculateOrigin(renderingElement);
if (radius <= 0) var centerPoint = CalculateCenterPoint(renderingElement, origin);
var focalPoint = CalculateFocalPoint(renderingElement, origin);
var specifiedRadius = CalculateRadius(renderingElement);
var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius);
var brush = new PathGradientBrush(CreateGraphicsPath(origin, centerPoint, effectiveRadius))
{ {
return null; InterpolationColors = CalculateColorBlend(renderingElement, opacity, specifiedRadius, effectiveRadius),
CenterPoint = focalPoint
};
Debug.Assert(brush.Rectangle.Contains(renderingElement.Bounds), "Brush rectangle does not contain rendering element bounds!");
return brush;
} }
GraphicsPath path = new GraphicsPath(); private PointF CalculateOrigin(SvgVisualElement renderingElement)
float left = this.CenterX.ToDeviceValue(renderingElement); {
float top = this.CenterY.ToDeviceValue(renderingElement, true); return CalculateBoundable(renderingElement).Location;
}
private PointF CalculateCenterPoint(ISvgBoundable boundable, PointF origin)
{
var deviceCenterX = origin.X + CenterX.ToDeviceValue(boundable);
var deviceCenterY = origin.Y + CenterY.ToDeviceValue(boundable, true);
var transformedCenterPoint = TransformPoint(new PointF(deviceCenterX, deviceCenterY));
return transformedCenterPoint;
}
private PointF CalculateFocalPoint(ISvgBoundable boundable, PointF origin)
{
var deviceFocalX = origin.X + FocalX.ToDeviceValue(boundable);
var deviceFocalY = origin.Y + FocalY.ToDeviceValue(boundable, true);
var transformedFocalPoint = TransformPoint(new PointF(deviceFocalX, deviceFocalY));
return transformedFocalPoint;
}
RectangleF boundingBox = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? renderingElement.Bounds : renderingElement.OwnerDocument.GetDimensions(); private float CalculateRadius(ISvgBoundable boundable)
{
var radius = Radius.ToDeviceValue(boundable);
var transformRadiusVector = TransformVector(new PointF(radius, 0));
var transformedRadius = CalculateLength(transformRadiusVector);
return transformedRadius;
}
private float CalculateEffectiveRadius(ISvgBoundable boundable, PointF centerPoint, float specifiedRadius)
{
if (SpreadMethod != SvgGradientSpreadMethod.Pad)
{
return specifiedRadius;
}
var topLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Top);
var topRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Top);
var bottomRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom);
var bottomLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Bottom);
var effectiveRadius = (float)Math.Ceiling(
Math.Max(
Math.Max(
CalculateDistance(centerPoint, topLeft),
CalculateDistance(centerPoint, topRight)
),
Math.Max(
CalculateDistance(centerPoint, bottomRight),
CalculateDistance(centerPoint, bottomLeft)
)
)
);
effectiveRadius = Math.Max(effectiveRadius, specifiedRadius);
return effectiveRadius;
}
private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
{
var path = new GraphicsPath();
path.AddEllipse( path.AddEllipse(
boundingBox.Left + left - radius, origin.X + centerPoint.X - effectiveRadius,
boundingBox.Top + top - radius, origin.Y + centerPoint.Y - effectiveRadius,
radius * 2, effectiveRadius * 2,
radius * 2); effectiveRadius * 2
);
PathGradientBrush brush = new PathGradientBrush(path); return path;
ColorBlend blend = base.GetColourBlend(renderingElement, opacity, true); }
brush.InterpolationColors = blend; private ColorBlend CalculateColorBlend(SvgVisualElement renderingElement, float opacity, float specifiedRadius, float effectiveRadius)
brush.CenterPoint = {
new PointF( var colorBlend = GetColorBlend(renderingElement, opacity, true);
boundingBox.Left + this.FocalX.ToDeviceValue(renderingElement),
boundingBox.Top + this.FocalY.ToDeviceValue(renderingElement, true));
return brush; if (specifiedRadius >= effectiveRadius)
{
return colorBlend;
} }
for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
{
colorBlend.Positions[i] = 1 - (specifiedRadius / effectiveRadius) * (1 - colorBlend.Positions[i]);
}
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
return colorBlend;
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
return DeepCopy<SvgRadialGradientServer>(); return DeepCopy<SvgRadialGradientServer>();
} }
public override SvgElement DeepCopy<T>() public override SvgElement DeepCopy<T>()
{ {
var newObj = base.DeepCopy<T>() as SvgRadialGradientServer; var newObj = base.DeepCopy<T>() as SvgRadialGradientServer;
......
...@@ -114,6 +114,7 @@ ...@@ -114,6 +114,7 @@
<Compile Include="DataTypes\SvgViewBox.cs" /> <Compile Include="DataTypes\SvgViewBox.cs" />
<Compile Include="Document Structure\SvgTitle.cs" /> <Compile Include="Document Structure\SvgTitle.cs" />
<Compile Include="Document Structure\SvgDocumentMetadata.cs" /> <Compile Include="Document Structure\SvgDocumentMetadata.cs" />
<Compile Include="Painting\ISvgBoundable.cs" />
<Compile Include="Painting\SvgMarker.cs" /> <Compile Include="Painting\SvgMarker.cs" />
<Compile Include="Document Structure\SvgDefinitionList.cs" /> <Compile Include="Document Structure\SvgDefinitionList.cs" />
<Compile Include="Document Structure\SvgDescription.cs" /> <Compile Include="Document Structure\SvgDescription.cs" />
......
...@@ -19,8 +19,6 @@ namespace Svg ...@@ -19,8 +19,6 @@ namespace Svg
public static readonly int PointsPerInch = 96; public static readonly int PointsPerInch = 96;
private SvgElementIdManager _idManager; private SvgElementIdManager _idManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SvgDocument"/> class. /// Initializes a new instance of the <see cref="SvgDocument"/> class.
/// </summary> /// </summary>
...@@ -293,25 +291,6 @@ namespace Svg ...@@ -293,25 +291,6 @@ namespace Svg
return null; return null;
} }
public RectangleF GetDimensions()
{
var w = Width.ToDeviceValue();
var h = Height.ToDeviceValue();
RectangleF bounds = new RectangleF();
var isWidthperc = Width.Type == SvgUnitType.Percentage;
var isHeightperc = Height.Type == SvgUnitType.Percentage;
if(isWidthperc || isHeightperc)
{
bounds = this.Bounds; //do just one call to the recursive bounds property
if(isWidthperc) w = (bounds.Width + bounds.X) * (w * 0.01f);
if(isHeightperc) h = (bounds.Height + bounds.Y) * (h * 0.01f);
}
return new RectangleF(0, 0, w, h);
}
/// <summary> /// <summary>
/// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>. /// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>.
/// </summary> /// </summary>
...@@ -413,6 +392,5 @@ namespace Svg ...@@ -413,6 +392,5 @@ namespace Svg
this.Write(fs); this.Write(fs);
} }
} }
} }
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment