Commit 2462bf13 authored by Tebjan Halm's avatar Tebjan Halm
Browse files

Merge pull request #85 from articulate/GradientTransform

Gradient Improvements
parents e71c9d97 3987f281
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Diagnostics;
namespace Svg
{
/// <summary>
/// The class that all SVG elements should derive from when they are to be rendered.
/// </summary>
public abstract partial class SvgVisualElement : SvgElement, ISvgStylable, ISvgClipable
public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
{
private bool _dirty;
private bool _requiresSmoothRendering;
......@@ -23,6 +17,23 @@ namespace Svg
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
public abstract GraphicsPath Path { get; protected set; }
PointF ISvgBoundable.Location
{
get
{
return Bounds.Location;
}
}
SizeF ISvgBoundable.Size
{
get
{
return Bounds.Size;
}
}
/// <summary>
/// Gets the bounds of the element.
/// </summary>
......@@ -61,15 +72,15 @@ namespace Svg
set { this.Attributes["clip-rule"] = value; }
}
/// <summary>
/// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
/// </summary>
[SvgAttribute("filter")]
public virtual Uri Filter
{
get { return this.Attributes.GetAttribute<Uri>("filter"); }
set { this.Attributes["filter"] = value; }
}
/// <summary>
/// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
/// </summary>
[SvgAttribute("filter")]
public virtual Uri Filter
{
get { return this.Attributes.GetAttribute<Uri>("filter"); }
set { this.Attributes["filter"] = value; }
}
/// <summary>
/// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
......@@ -131,7 +142,7 @@ namespace Svg
{
if (brush != null)
{
this.Path.FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
this.Path.FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
renderer.FillPath(brush, this.Path);
}
}
......@@ -209,31 +220,31 @@ namespace Svg
this.ResetClip(renderer);
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgVisualElement;
newObj.ClipPath = this.ClipPath;
newObj.ClipRule = this.ClipRule;
newObj.Filter = this.Filter;
newObj.Visible = this.Visible;
if (this.Fill != null)
newObj.Fill = this.Fill;
if (this.Stroke != null)
newObj.Stroke = this.Stroke;
newObj.FillRule = this.FillRule;
newObj.FillOpacity = this.FillOpacity;
newObj.StrokeWidth = this.StrokeWidth;
newObj.StrokeLineCap = this.StrokeLineCap;
newObj.StrokeLineJoin = this.StrokeLineJoin;
newObj.StrokeMiterLimit = this.StrokeMiterLimit;
newObj.StrokeDashArray = this.StrokeDashArray;
newObj.StrokeDashOffset = this.StrokeDashOffset;
newObj.StrokeOpacity = this.StrokeOpacity;
newObj.Opacity = this.Opacity;
return newObj;
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgVisualElement;
newObj.ClipPath = this.ClipPath;
newObj.ClipRule = this.ClipRule;
newObj.Filter = this.Filter;
newObj.Visible = this.Visible;
if (this.Fill != null)
newObj.Fill = this.Fill;
if (this.Stroke != null)
newObj.Stroke = this.Stroke;
newObj.FillRule = this.FillRule;
newObj.FillOpacity = this.FillOpacity;
newObj.StrokeWidth = this.StrokeWidth;
newObj.StrokeLineCap = this.StrokeLineCap;
newObj.StrokeLineJoin = this.StrokeLineJoin;
newObj.StrokeMiterLimit = this.StrokeMiterLimit;
newObj.StrokeDashArray = this.StrokeDashArray;
newObj.StrokeDashOffset = this.StrokeDashOffset;
newObj.StrokeOpacity = this.StrokeOpacity;
newObj.Opacity = this.Opacity;
return newObj;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Globalization;
namespace Svg
......@@ -73,16 +70,16 @@ namespace Svg
/// Converts the current unit to one that can be used at render time.
/// </summary>
/// <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>
/// Converts the current unit to one that can be used at render time.
/// </summary>
/// <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 (this._deviceValue.HasValue)
......@@ -131,14 +128,14 @@ namespace Svg
break;
case SvgUnitType.Percentage:
// Can't calculate if there is no style owner
if (styleOwner == null)
if (boundable == null)
{
_deviceValue = this.Value;
break;
}
// 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;
break;
default:
......@@ -165,43 +162,43 @@ namespace Svg
}
}
#region Equals and GetHashCode implementation
public override bool Equals(object obj)
{
if (obj == null) return false;
#region Equals and GetHashCode implementation
public override bool Equals(object obj)
{
if (obj == null) return false;
if (!(obj.GetType() == typeof (SvgUnit))) return false;
var unit = (SvgUnit)obj;
return (unit.Value == this.Value && unit.Type == this.Type);
}
public bool Equals(SvgUnit other)
{
return this._type == other._type && (this._value == other._value);
}
public override int GetHashCode()
{
int hashCode = 0;
unchecked {
hashCode += 1000000007 * _type.GetHashCode();
hashCode += 1000000009 * _value.GetHashCode();
hashCode += 1000000021 * _isEmpty.GetHashCode();
hashCode += 1000000033 * _deviceValue.GetHashCode();
}
return hashCode;
}
public static bool operator ==(SvgUnit lhs, SvgUnit rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(SvgUnit lhs, SvgUnit rhs)
{
return !(lhs == rhs);
}
#endregion
}
public bool Equals(SvgUnit other)
{
return this._type == other._type && (this._value == other._value);
}
public override int GetHashCode()
{
int hashCode = 0;
unchecked {
hashCode += 1000000007 * _type.GetHashCode();
hashCode += 1000000009 * _value.GetHashCode();
hashCode += 1000000021 * _isEmpty.GetHashCode();
hashCode += 1000000033 * _deviceValue.GetHashCode();
}
return hashCode;
}
public static bool operator ==(SvgUnit lhs, SvgUnit rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(SvgUnit lhs, SvgUnit rhs)
{
return !(lhs == rhs);
}
#endregion
public override string ToString()
{
......
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;
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.
/// </summary>
[SvgElement("svg")]
public class SvgFragment : SvgElement, ISvgViewPort
public class SvgFragment : SvgElement, ISvgViewPort, ISvgBoundable
{
/// <summary>
/// Gets the SVG namespace string.
/// </summary>
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 _y;
......@@ -28,15 +48,15 @@ namespace Svg
[SvgAttribute("x")]
public SvgUnit X
{
get { return _x; }
set
{
if(_x != value)
{
_x = value;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "x", Value = value });
}
}
get { return _x; }
set
{
if(_x != value)
{
_x = value;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "x", Value = value });
}
}
}
/// <summary>
......@@ -45,15 +65,15 @@ namespace Svg
[SvgAttribute("y")]
public SvgUnit Y
{
get { return _y; }
set
{
if(_y != value)
{
_y = value;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "y", Value = value });
}
}
get { return _y; }
set
{
if(_y != value)
{
_y = value;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "y", Value = value });
}
}
}
/// <summary>
......@@ -64,7 +84,7 @@ namespace Svg
public SvgUnit Width
{
get { return this.Attributes.GetAttribute<SvgUnit>("width"); }
set { this.Attributes["width"] = value; }
set { this.Attributes["width"] = value; }
}
/// <summary>
......@@ -75,15 +95,15 @@ namespace Svg
public SvgUnit Height
{
get { return this.Attributes.GetAttribute<SvgUnit>("height"); }
set { this.Attributes["height"] = value; }
set { this.Attributes["height"] = value; }
}
[SvgAttribute("overflow")]
public virtual SvgOverflow Overflow
{
get { return this.Attributes.GetAttribute<SvgOverflow>("overflow"); }
set { this.Attributes["overflow"] = value; }
}
[SvgAttribute("overflow")]
public virtual SvgOverflow Overflow
{
get { return this.Attributes.GetAttribute<SvgOverflow>("overflow"); }
set { this.Attributes["overflow"] = value; }
}
/// <summary>
/// Gets or sets the viewport of the element.
......@@ -100,11 +120,11 @@ namespace Svg
/// Gets or sets the aspect of the viewport.
/// </summary>
/// <value></value>
[SvgAttribute("preserveAspectRatio")]
[SvgAttribute("preserveAspectRatio")]
public SvgAspectRatio AspectRatio
{
get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
set { this.Attributes["preserveAspectRatio"] = value; }
get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
set { this.Attributes["preserveAspectRatio"] = value; }
}
/// <summary>
......@@ -117,68 +137,68 @@ namespace Svg
if (!this.ViewBox.Equals(SvgViewBox.Empty))
{
float fScaleX = this.Width.ToDeviceValue() / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue() / this.ViewBox.Height;
float fMinX = -this.ViewBox.MinX;
float fMinY = -this.ViewBox.MinY;
if (AspectRatio.Align != SvgPreserveAspectRatio.none)
{
if (AspectRatio.Slice)
{
fScaleX = Math.Max(fScaleX, fScaleY);
fScaleY = Math.Max(fScaleX, fScaleY);
}
else
{
fScaleX = Math.Min(fScaleX, fScaleY);
fScaleY = Math.Min(fScaleX, fScaleY);
}
float fViewMidX = (this.ViewBox.Width / 2) * fScaleX;
float fViewMidY = (this.ViewBox.Height / 2) * fScaleY;
float fMidX = this.Width.ToDeviceValue() / 2;
float fMidY = this.Height.ToDeviceValue() / 2;
switch (AspectRatio.Align)
{
case SvgPreserveAspectRatio.xMinYMin:
break;
case SvgPreserveAspectRatio.xMidYMin:
fMinX += (fMidX - fViewMidX) / fScaleX;
break;
case SvgPreserveAspectRatio.xMaxYMin:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
break;
case SvgPreserveAspectRatio.xMinYMid:
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMidYMid:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMaxYMid:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMinYMax:
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMidYMax:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMaxYMax:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
break;
default:
break;
}
}
renderer.TranslateTransform(_x, _y);
renderer.TranslateTransform(fMinX, fMinY);
renderer.ScaleTransform(fScaleX, fScaleY);
float fScaleX = this.Width.ToDeviceValue(this, false) / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue(this, true) / this.ViewBox.Height;
float fMinX = -this.ViewBox.MinX;
float fMinY = -this.ViewBox.MinY;
if (AspectRatio.Align != SvgPreserveAspectRatio.none)
{
if (AspectRatio.Slice)
{
fScaleX = Math.Max(fScaleX, fScaleY);
fScaleY = Math.Max(fScaleX, fScaleY);
}
else
{
fScaleX = Math.Min(fScaleX, fScaleY);
fScaleY = Math.Min(fScaleX, fScaleY);
}
float fViewMidX = (this.ViewBox.Width / 2) * fScaleX;
float fViewMidY = (this.ViewBox.Height / 2) * fScaleY;
float fMidX = this.Width.ToDeviceValue(this, false) / 2;
float fMidY = this.Height.ToDeviceValue(this, true) / 2;
switch (AspectRatio.Align)
{
case SvgPreserveAspectRatio.xMinYMin:
break;
case SvgPreserveAspectRatio.xMidYMin:
fMinX += (fMidX - fViewMidX) / fScaleX;
break;
case SvgPreserveAspectRatio.xMaxYMin:
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
break;
case SvgPreserveAspectRatio.xMinYMid:
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMidYMid:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMaxYMid:
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMinYMax:
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMidYMax:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMaxYMax:
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
default:
break;
}
}
renderer.TranslateTransform(_x, _y);
renderer.TranslateTransform(fMinX, fMinY);
renderer.ScaleTransform(fScaleX, fScaleY);
}
}
......@@ -190,11 +210,11 @@ namespace Svg
{
get
{
var path = new GraphicsPath();
var path = new GraphicsPath();
AddPaths(this, path);
AddPaths(this, path);
return path;
return path;
}
}
......@@ -204,10 +224,10 @@ namespace Svg
/// <value>The bounds.</value>
public RectangleF Bounds
{
get
{
return this.Path.GetBounds();
}
get
{
return this.Path.GetBounds();
}
}
/// <summary>
......@@ -215,7 +235,7 @@ namespace Svg
/// </summary>
public SvgFragment()
{
_x = 0.0f;
_x = 0.0f;
_y = 0.0f;
this.Height = new SvgUnit(SvgUnitType.Percentage, 100.0f);
this.Width = new SvgUnit(SvgUnitType.Percentage, 100.0f);
......@@ -223,21 +243,41 @@ namespace Svg
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()
{
return DeepCopy<SvgFragment>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgFragment;
newObj.Height = this.Height;
newObj.Width = this.Width;
newObj.Overflow = this.Overflow;
newObj.ViewBox = this.ViewBox;
newObj.AspectRatio = this.AspectRatio;
return newObj;
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgFragment>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgFragment;
newObj.Height = this.Height;
newObj.Width = this.Width;
newObj.Overflow = this.Overflow;
newObj.ViewBox = this.ViewBox;
newObj.AspectRatio = this.AspectRatio;
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.Drawing2D;
using System.Linq;
using Svg.Transforms;
namespace Svg
{
......@@ -27,10 +21,10 @@ namespace Svg
{
get
{
//var path = new GraphicsPath();
//AddPaths(this, path);
//var path = new GraphicsPath();
//AddPaths(this, path);
return GetPaths(this);
return GetPaths(this);
}
protected set
{ }
......@@ -44,21 +38,29 @@ namespace Svg
{
get
{
var r = new RectangleF();
foreach(var c in this.Children)
{
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
// 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
r = RectangleF.Union(r, ((SvgVisualElement)c).Bounds);
{
var childBounds = ((SvgVisualElement)c).Bounds;
if (!childBounds.IsEmpty)
{
r = RectangleF.Union(r, childBounds);
}
}
}
}
return r;
}
return r;
}
}
......@@ -78,18 +80,18 @@ namespace Svg
this.PopTransforms(renderer);
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgGroup>();
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgGroup>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgGroup;
if (this.Fill != null)
newObj.Fill = this.Fill.DeepCopy() as SvgPaintServer;
return newObj;
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgGroup;
if (this.Fill != null)
newObj.Fill = this.Fill.DeepCopy() as SvgPaintServer;
return newObj;
}
}
}
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;
namespace Svg
......@@ -24,6 +20,5 @@ namespace Svg
SvgUnitCollection StrokeDashArray { get; set; }
SvgUnit StrokeDashOffset { get; set; }
GraphicsPath Path { get; }
RectangleF Bounds { get; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using Svg.Transforms;
namespace Svg
{
......@@ -10,7 +12,7 @@ namespace Svg
public abstract class SvgGradientServer : SvgPaintServer
{
private SvgCoordinateUnits _gradientUnits;
private SvgGradientSpreadMethod _spreadMethod = SvgGradientSpreadMethod.Pad;
private SvgGradientSpreadMethod _spreadMethod;
private SvgGradientServer _inheritGradient;
private List<SvgGradientStop> _stops;
......@@ -20,6 +22,7 @@ namespace Svg
internal SvgGradientServer()
{
this.GradientUnits = SvgCoordinateUnits.ObjectBoundingBox;
this.SpreadMethod = SvgGradientSpreadMethod.Pad;
this._stops = new List<SvgGradientStop>();
}
......@@ -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>
/// Gets a <see cref="ColorBlend"/> representing the <see cref="SvgGradientServer"/>'s gradient stops.
/// </summary>
/// <param name="owner">The parent <see cref="SvgVisualElement"/>.</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;
bool insertStart = false;
......@@ -201,16 +231,49 @@ namespace Svg
}
}
protected ISvgBoundable CalculateBoundable(SvgVisualElement renderingElement)
{
return (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? (ISvgBoundable)renderingElement : renderingElement.OwnerDocument;
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgGradientServer;
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>()
{
var newObj = base.DeepCopy<T>() as SvgGradientServer;
newObj.SpreadMethod = this.SpreadMethod;
newObj.GradientUnits = this.GradientUnits;
newObj.InheritGradient = this.InheritGradient;
newObj.GradientUnits = this.GradientUnits;
newObj.InheritGradient = this.InheritGradient;
newObj.GradientTransform = this.GradientTransform;
return newObj;
}
return newObj;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg
{
[SvgElement("linearGradient")]
public sealed class SvgLinearGradientServer : SvgGradientServer
{
private SvgUnit _x1;
private SvgUnit _y1;
private SvgUnit _x2;
private SvgUnit _y2;
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x1")]
[SvgAttribute("x1")]
public SvgUnit X1
{
get { return this._x1; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("x1");
}
set
{
this._x1 = value;
Attributes["x1"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y1")]
[SvgAttribute("y1")]
public SvgUnit Y1
{
get { return this._y1; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("y1");
}
set
{
this._y1 = value;
this.Attributes["y1"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x2")]
[SvgAttribute("x2")]
public SvgUnit X2
{
get { return this._x2; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("x2");
}
set
{
this._x2 = value;
Attributes["x2"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y2")]
[SvgAttribute("y2")]
public SvgUnit Y2
{
get { return this._y2; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("y2");
}
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()
{
this._x1 = new SvgUnit(0.0f);
this._y1 = new SvgUnit(0.0f);
this._x2 = new SvgUnit(0.0f);
this._y2 = new SvgUnit(0.0f);
X1 = new SvgUnit(SvgUnitType.Percentage, 0F);
Y1 = new SvgUnit(SvgUnitType.Percentage, 0F);
X2 = new SvgUnit(SvgUnitType.Percentage, 100F);
Y2 = new SvgUnit(SvgUnitType.Percentage, 0F);
}
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{
if (IsInvalid)
{
return null;
}
var boundable = CalculateBoundable(renderingElement);
var specifiedStart = CalculateStart(boundable);
var specifiedEnd = CalculateEnd(boundable);
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
if (NeedToExpandGradient(renderingElement, specifiedStart, specifiedEnd))
{
var expansion = ExpandGradient(renderingElement, specifiedStart, specifiedEnd);
effectiveStart = expansion.Item1;
effectiveEnd = expansion.Item2;
}
return new LinearGradientBrush(effectiveStart, effectiveEnd, Color.Transparent, Color.Transparent)
{
InterpolationColors = CalculateColorBlend(renderingElement, opacity, specifiedStart, effectiveStart, specifiedEnd, effectiveEnd),
WrapMode = WrapMode.TileFlipX
};
}
private PointF CalculateStart(ISvgBoundable boundable)
{
return TransformPoint(new PointF(this.X1.ToDeviceValue(boundable), this.Y1.ToDeviceValue(boundable, true)));
}
public SvgPoint Start
private PointF CalculateEnd(ISvgBoundable boundable)
{
get { return new SvgPoint(this.X1, this.Y1); }
return TransformPoint(new PointF(this.X2.ToDeviceValue(boundable), this.Y2.ToDeviceValue(boundable, true)));
}
public SvgPoint End
private bool NeedToExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
get { return new SvgPoint(this.X2, this.Y2); }
return SpreadMethod == SvgGradientSpreadMethod.Pad && (boundable.Bounds.Contains(specifiedStart) || boundable.Bounds.Contains(specifiedEnd));
}
public override Brush GetBrush(SvgVisualElement owner, float opacity)
private Tuple<PointF, PointF> ExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
// Need at least 2 colours to do the gradient fill
if (this.Stops.Count < 2)
if (!NeedToExpandGradient(boundable, specifiedStart, specifiedEnd))
{
return null;
Debug.Fail("Unexpectedly expanding gradient when not needed!");
return new Tuple<PointF, PointF>(specifiedStart, specifiedEnd);
}
PointF start;
PointF end;
RectangleF bounds = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? owner.Bounds : owner.OwnerDocument.GetDimensions();
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);
// Have start/end points been set? If not the gradient is horizontal
if (!this.End.IsEmpty())
if (boundable.Bounds.Contains(specifiedStart))
{
// Get the points to work out an angle
if (this.Start.IsEmpty())
{
start = bounds.Location;
}
else
{
start = new PointF(this.Start.X.ToDeviceValue(owner), this.Start.Y.ToDeviceValue(owner, true));
}
effectiveStart = CalculateClosestIntersectionPoint(expandedStart, intersectionPoints);
effectiveStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -1);
}
if (boundable.Bounds.Contains(specifiedEnd))
{
effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints);
effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1);
}
return new Tuple<PointF, PointF>(effectiveStart, effectiveEnd);
}
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))
{
return colorBlend;
}
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
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]);
float x = (this.End.X.IsEmpty) ? start.X : this.End.X.ToDeviceValue(owner);
end = new PointF(x, this.End.Y.ToDeviceValue(owner, true));
var distanceFromEffectiveStart = CalculateDistance(effectiveStart, originalPoint);
colorBlend.Positions[i] = (float) Math.Max(0F, Math.Min((distanceFromEffectiveStart / effectiveLength), 1.0F));
}
if (startDelta > 0)
{
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
}
else
if (endDelta > 0)
{
// Default: horizontal
start = bounds.Location;
end = new PointF(bounds.Right, bounds.Top);
colorBlend.Positions = colorBlend.Positions.Concat(new[] { 1F }).ToArray();
colorBlend.Colors = colorBlend.Colors.Concat(new[] { colorBlend.Colors.Last() }).ToArray();
}
LinearGradientBrush gradient = new LinearGradientBrush(start, end, Color.Transparent, Color.Transparent);
gradient.InterpolationColors = base.GetColourBlend(owner, opacity, false);
return colorBlend;
}
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()
{
return DeepCopy<SvgLinearGradientServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgLinearGradientServer;
newObj.X1 = this.X1;
newObj.Y1 = this.Y1;
newObj.X2 = this.X2;
newObj.Y2 = this.Y2;
return newObj;
// Needed to fix an issue where the gradient was being wrapped when though it had the correct bounds
gradient.WrapMode = WrapMode.TileFlipX;
return gradient;
}
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;
public override SvgElement DeepCopy()
{
return DeepCopy<SvgLinearGradientServer>();
}
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;
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgLinearGradientServer;
newObj.X1 = this.X1;
newObj.Y1 = this.Y1;
newObj.X2 = this.X2;
newObj.Y2 = this.Y2;
return newObj;
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.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.Drawing2D;
using System.Linq;
using Svg.DataTypes;
namespace Svg
......@@ -14,7 +9,7 @@ namespace Svg
[SvgElement("marker")]
public class SvgMarker : SvgVisualElement, ISvgViewPort
{
private SvgOrient _svgOrient = new SvgOrient();
private SvgOrient _svgOrient = new SvgOrient();
[SvgAttribute("refX")]
public virtual SvgUnit RefX
......@@ -31,68 +26,75 @@ namespace Svg
}
[SvgAttribute("orient")]
public virtual SvgOrient Orient
{
get { return _svgOrient; }
set { _svgOrient = value; }
}
[SvgAttribute("overflow")]
public virtual SvgOverflow Overflow
{
get { return this.Attributes.GetAttribute<SvgOverflow>("overflow"); }
set { this.Attributes["overflow"] = value; }
}
[SvgAttribute("viewBox")]
public virtual SvgViewBox ViewBox
{
get { return this.Attributes.GetAttribute<SvgViewBox>("viewBox"); }
set { this.Attributes["viewBox"] = value; }
}
[SvgAttribute("preserveAspectRatio")]
public virtual SvgAspectRatio AspectRatio
{
get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
set { this.Attributes["preserveAspectRatio"] = value; }
}
[SvgAttribute("markerWidth")]
public virtual SvgUnit MarkerWidth
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerWidth"); }
set { this.Attributes["markerWidth"] = value; }
}
[SvgAttribute("markerHeight")]
public virtual SvgUnit MarkerHeight
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerHeight"); }
set { this.Attributes["markerHeight"] = value; }
}
public SvgMarker()
{
MarkerUnits = SvgMarkerUnits.strokeWidth;
MarkerHeight = 3;
MarkerWidth = 3;
Overflow = SvgOverflow.hidden;
}
public override System.Drawing.Drawing2D.GraphicsPath Path
[SvgAttribute("orient")]
public virtual SvgOrient Orient
{
get { return _svgOrient; }
set { _svgOrient = value; }
}
[SvgAttribute("overflow")]
public virtual SvgOverflow Overflow
{
get { return this.Attributes.GetAttribute<SvgOverflow>("overflow"); }
set { this.Attributes["overflow"] = value; }
}
[SvgAttribute("viewBox")]
public virtual SvgViewBox ViewBox
{
get { return this.Attributes.GetAttribute<SvgViewBox>("viewBox"); }
set { this.Attributes["viewBox"] = value; }
}
[SvgAttribute("preserveAspectRatio")]
public virtual SvgAspectRatio AspectRatio
{
get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
set { this.Attributes["preserveAspectRatio"] = value; }
}
[SvgAttribute("markerWidth")]
public virtual SvgUnit MarkerWidth
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerWidth"); }
set { this.Attributes["markerWidth"] = value; }
}
[SvgAttribute("markerHeight")]
public virtual SvgUnit MarkerHeight
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerHeight"); }
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()
{
MarkerUnits = SvgMarkerUnits.strokeWidth;
MarkerHeight = 3;
MarkerWidth = 3;
Overflow = SvgOverflow.hidden;
}
public override System.Drawing.Drawing2D.GraphicsPath Path
{
get
{
var path = this.Children.FirstOrDefault(x => x is SvgPath);
if (path != null)
return (path as SvgPath).Path;
return null;
var path = this.Children.FirstOrDefault(x => x is SvgPath);
if (path != null)
return (path as SvgPath).Path;
return null;
}
protected set
{
......@@ -104,172 +106,172 @@ namespace Svg
{
get
{
var path = this.Path;
if (path != null)
return path.GetBounds();
return new System.Drawing.RectangleF();
var path = this.Path;
if (path != null)
return path.GetBounds();
return new System.Drawing.RectangleF();
}
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgMarker>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgMarker;
newObj.RefX = this.RefX;
newObj.RefY = this.RefY;
newObj.Orient = this.Orient;
newObj.ViewBox = this.ViewBox;
newObj.Overflow = this.Overflow;
newObj.AspectRatio = this.AspectRatio;
return newObj;
}
/// <summary>
/// Render this marker using the slope of the given line segment
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2(fAngle1, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Render this marker using the average of the slopes of the two given line segments
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
/// <param name="pMarkerPoint3"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
xDiff = pMarkerPoint3.X - pMarkerPoint2.X;
yDiff = pMarkerPoint3.Y - pMarkerPoint2.Y;
float fAngle2 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2((fAngle1 + fAngle2) / 2, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Common code for rendering a marker once the orientation angle has been calculated
/// </summary>
/// <param name="fAngle"></param>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint"></param>
private void RenderPart2(float fAngle, SvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint)
{
Pen pRenderPen = CreatePen(pOwner);
GraphicsPath markerPath = GetClone(pOwner);
Matrix transMatrix = new Matrix();
transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y);
if (Orient.IsAuto)
transMatrix.Rotate(fAngle);
else
transMatrix.Rotate(Orient.Angle);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
transMatrix.Translate(AdjustForViewBoxWidth(-RefX * pOwner.StrokeWidth), AdjustForViewBoxHeight(-RefY * pOwner.StrokeWidth));
break;
case SvgMarkerUnits.userSpaceOnUse:
transMatrix.Translate(-RefX, -RefY);
break;
}
markerPath.Transform(transMatrix);
pRenderer.DrawPath(pRenderPen, markerPath);
SvgPaintServer pFill = Fill;
SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for?
float fOpacity = FillOpacity;
if (pFill != null)
{
Brush pBrush = pFill.GetBrush(this, fOpacity);
pRenderer.FillPath(pBrush, markerPath);
pBrush.Dispose();
}
pRenderPen.Dispose();
markerPath.Dispose();
transMatrix.Dispose();
}
/// <summary>
/// Create a pen that can be used to render this marker
/// </summary>
/// <param name="pStroke"></param>
/// <returns></returns>
private Pen CreatePen(SvgPath pPath)
{
Brush pBrush = pPath.Stroke.GetBrush(this, Opacity);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
return (new Pen(pBrush, StrokeWidth * pPath.StrokeWidth));
case SvgMarkerUnits.userSpaceOnUse:
return (new Pen(pBrush, StrokeWidth));
}
return (new Pen(pBrush, StrokeWidth));
}
/// <summary>
/// Get a clone of the current path, scaled for the stroke with
/// </summary>
/// <returns></returns>
private GraphicsPath GetClone(SvgPath pPath)
{
GraphicsPath pRet = Path.Clone() as GraphicsPath;
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
Matrix transMatrix = new Matrix();
transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
pRet.Transform(transMatrix);
break;
case SvgMarkerUnits.userSpaceOnUse:
break;
}
return (pRet);
}
/// <summary>
/// Adjust the given value to account for the width of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxWidth(float fWidth)
{
// TODO: We know this isn't correct
return (fWidth / ViewBox.Width);
}
/// <summary>
/// Adjust the given value to account for the height of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxHeight(float fHeight)
{
// TODO: We know this isn't correct
return (fHeight / ViewBox.Height);
}
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgMarker>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgMarker;
newObj.RefX = this.RefX;
newObj.RefY = this.RefY;
newObj.Orient = this.Orient;
newObj.ViewBox = this.ViewBox;
newObj.Overflow = this.Overflow;
newObj.AspectRatio = this.AspectRatio;
return newObj;
}
/// <summary>
/// Render this marker using the slope of the given line segment
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2(fAngle1, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Render this marker using the average of the slopes of the two given line segments
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
/// <param name="pMarkerPoint3"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
xDiff = pMarkerPoint3.X - pMarkerPoint2.X;
yDiff = pMarkerPoint3.Y - pMarkerPoint2.Y;
float fAngle2 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2((fAngle1 + fAngle2) / 2, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Common code for rendering a marker once the orientation angle has been calculated
/// </summary>
/// <param name="fAngle"></param>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint"></param>
private void RenderPart2(float fAngle, SvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint)
{
Pen pRenderPen = CreatePen(pOwner);
GraphicsPath markerPath = GetClone(pOwner);
Matrix transMatrix = new Matrix();
transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y);
if (Orient.IsAuto)
transMatrix.Rotate(fAngle);
else
transMatrix.Rotate(Orient.Angle);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
transMatrix.Translate(AdjustForViewBoxWidth(-RefX * pOwner.StrokeWidth), AdjustForViewBoxHeight(-RefY * pOwner.StrokeWidth));
break;
case SvgMarkerUnits.userSpaceOnUse:
transMatrix.Translate(-RefX, -RefY);
break;
}
markerPath.Transform(transMatrix);
pRenderer.DrawPath(pRenderPen, markerPath);
SvgPaintServer pFill = Fill;
SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for?
float fOpacity = FillOpacity;
if (pFill != null)
{
Brush pBrush = pFill.GetBrush(this, fOpacity);
pRenderer.FillPath(pBrush, markerPath);
pBrush.Dispose();
}
pRenderPen.Dispose();
markerPath.Dispose();
transMatrix.Dispose();
}
/// <summary>
/// Create a pen that can be used to render this marker
/// </summary>
/// <param name="pStroke"></param>
/// <returns></returns>
private Pen CreatePen(SvgPath pPath)
{
Brush pBrush = pPath.Stroke.GetBrush(this, Opacity);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
return (new Pen(pBrush, StrokeWidth * pPath.StrokeWidth));
case SvgMarkerUnits.userSpaceOnUse:
return (new Pen(pBrush, StrokeWidth));
}
return (new Pen(pBrush, StrokeWidth));
}
/// <summary>
/// Get a clone of the current path, scaled for the stroke with
/// </summary>
/// <returns></returns>
private GraphicsPath GetClone(SvgPath pPath)
{
GraphicsPath pRet = Path.Clone() as GraphicsPath;
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
Matrix transMatrix = new Matrix();
transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
pRet.Transform(transMatrix);
break;
case SvgMarkerUnits.userSpaceOnUse:
break;
}
return (pRet);
}
/// <summary>
/// Adjust the given value to account for the width of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxWidth(float fWidth)
{
// TODO: We know this isn't correct
return (fWidth / ViewBox.Width);
}
/// <summary>
/// Adjust the given value to account for the height of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxHeight(float fHeight)
{
// TODO: We know this isn't correct
return (fHeight / ViewBox.Height);
}
}
}
\ No newline at end of file
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg
{
......@@ -9,22 +12,40 @@ namespace Svg
[SvgAttribute("cx")]
public SvgUnit CenterX
{
get { return this.Attributes.GetAttribute<SvgUnit>("cx"); }
set { this.Attributes["cx"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("cx");
}
set
{
this.Attributes["cx"] = value;
}
}
[SvgAttribute("cy")]
public SvgUnit CenterY
{
get { return this.Attributes.GetAttribute<SvgUnit>("cy"); }
set { this.Attributes["cy"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("cy");
}
set
{
this.Attributes["cy"] = value;
}
}
[SvgAttribute("r")]
public SvgUnit Radius
{
get { return this.Attributes.GetAttribute<SvgUnit>("r"); }
set { this.Attributes["r"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("r");
}
set
{
this.Attributes["r"] = value;
}
}
[SvgAttribute("fx")]
......@@ -41,8 +62,10 @@ namespace Svg
return value;
}
set { this.Attributes["fx"] = value; }
set
{
this.Attributes["fx"] = value;
}
}
[SvgAttribute("fy")]
......@@ -59,72 +82,150 @@ namespace Svg
return value;
}
set { this.Attributes["fy"] = value; }
set
{
this.Attributes["fy"] = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgRadialGradientServer"/> class.
/// </summary>
public SvgRadialGradientServer()
{
//Apply default values of 50% to cX,cY and r
CenterX = new SvgUnit(SvgUnitType.Percentage, 50);
CenterY = new SvgUnit(SvgUnitType.Percentage, 50);
Radius = new SvgUnit(SvgUnitType.Percentage, 50);
CenterX = new SvgUnit(SvgUnitType.Percentage, 50F);
CenterY = new SvgUnit(SvgUnitType.Percentage, 50F);
Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
}
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{
float radius = this.Radius.ToDeviceValue(renderingElement);
var origin = CalculateOrigin(renderingElement);
var centerPoint = CalculateCenterPoint(renderingElement, origin);
var focalPoint = CalculateFocalPoint(renderingElement, origin);
var specifiedRadius = CalculateRadius(renderingElement);
var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius);
if (radius <= 0)
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;
}
private PointF CalculateOrigin(SvgVisualElement renderingElement)
{
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;
}
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;
}
GraphicsPath path = new GraphicsPath();
float left = this.CenterX.ToDeviceValue(renderingElement);
float top = this.CenterY.ToDeviceValue(renderingElement, true);
RectangleF boundingBox = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? renderingElement.Bounds : renderingElement.OwnerDocument.GetDimensions();
path.AddEllipse(
boundingBox.Left + left - radius,
boundingBox.Top + top - radius,
radius * 2,
radius * 2);
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;
}
PathGradientBrush brush = new PathGradientBrush(path);
ColorBlend blend = base.GetColourBlend(renderingElement, opacity, true);
private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
{
var path = new GraphicsPath();
brush.InterpolationColors = blend;
brush.CenterPoint =
new PointF(
boundingBox.Left + this.FocalX.ToDeviceValue(renderingElement),
boundingBox.Top + this.FocalY.ToDeviceValue(renderingElement, true));
path.AddEllipse(
origin.X + centerPoint.X - effectiveRadius,
origin.Y + centerPoint.Y - effectiveRadius,
effectiveRadius * 2,
effectiveRadius * 2
);
return brush;
return path;
}
private ColorBlend CalculateColorBlend(SvgVisualElement renderingElement, float opacity, float specifiedRadius, float effectiveRadius)
{
var colorBlend = GetColorBlend(renderingElement, opacity, true);
public override SvgElement DeepCopy()
{
return DeepCopy<SvgRadialGradientServer>();
}
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]);
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgRadialGradientServer;
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
newObj.CenterX = this.CenterX;
newObj.CenterY = this.CenterY;
newObj.Radius = this.Radius;
newObj.FocalX = this.FocalX;
newObj.FocalY = this.FocalY;
return colorBlend;
}
return newObj;
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgRadialGradientServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgRadialGradientServer;
newObj.CenterX = this.CenterX;
newObj.CenterY = this.CenterY;
newObj.Radius = this.Radius;
newObj.FocalX = this.FocalX;
newObj.FocalY = this.FocalY;
return newObj;
}
}
}
\ No newline at end of file
......@@ -52,7 +52,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>Full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\vvvv\public\common\src\thirdparty\</OutputPath>
<OutputPath>..\..\vvvv\public\common\src\thirdparty\</OutputPath>
<DefineConstants>TRACE;DEBUG;REFLECTION</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
......@@ -114,6 +114,7 @@
<Compile Include="DataTypes\SvgViewBox.cs" />
<Compile Include="Document Structure\SvgTitle.cs" />
<Compile Include="Document Structure\SvgDocumentMetadata.cs" />
<Compile Include="Painting\ISvgBoundable.cs" />
<Compile Include="Painting\SvgMarker.cs" />
<Compile Include="Document Structure\SvgDefinitionList.cs" />
<Compile Include="Document Structure\SvgDescription.cs" />
......
......@@ -19,14 +19,12 @@ namespace Svg
public static readonly int PointsPerInch = 96;
private SvgElementIdManager _idManager;
/// <summary>
/// Initializes a new instance of the <see cref="SvgDocument"/> class.
/// </summary>
public SvgDocument()
{
Ppi = PointsPerInch;
Ppi = PointsPerInch;
}
/// <summary>
......@@ -53,7 +51,7 @@ namespace Svg
/// <param name="manager"></param>
public void OverwriteIdManager(SvgElementIdManager manager)
{
_idManager = manager;
_idManager = manager;
}
/// <summary>
......@@ -293,25 +291,6 @@ namespace Svg
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>
/// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>.
/// </summary>
......@@ -413,6 +392,5 @@ namespace Svg
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