Commit 3987f281 authored by James Welle's avatar James Welle
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 e71c9d97
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