using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml;
using System.Linq;
using Svg.Transforms;
using System.Reflection;
using System.Threading;
using System.Globalization;
namespace Svg
{
///
/// The base class of which all SVG elements are derived from.
///
public abstract partial class SvgElement : ISvgElement, ISvgTransformable, ICloneable, ISvgNode
{
internal const int StyleSpecificity_PresAttribute = 0;
internal const int StyleSpecificity_InlineStyle = 1 << 16;
//optimization
protected class PropertyAttributeTuple
{
public PropertyDescriptor Property;
public SvgAttributeAttribute Attribute;
}
protected class EventAttributeTuple
{
public FieldInfo Event;
public SvgAttributeAttribute Attribute;
}
//reflection cache
private IEnumerable _svgPropertyAttributes;
private IEnumerable _svgEventAttributes;
internal SvgElement _parent;
private string _elementName;
private SvgAttributeCollection _attributes;
private EventHandlerList _eventHandlers;
private SvgElementCollection _children;
private static readonly object _loadEventKey = new object();
private Region _graphicsClip;
private Matrix _graphicsMatrix;
private SvgCustomAttributeCollection _customAttributes;
private List _nodes = new List();
private Dictionary> _styles = new Dictionary>();
public void AddStyle(string name, string value, int specificity)
{
SortedDictionary rules;
if (!_styles.TryGetValue(name, out rules))
{
rules = new SortedDictionary();
_styles[name] = rules;
}
while (rules.ContainsKey(specificity)) specificity++;
rules[specificity] = value;
}
public void FlushStyles()
{
foreach (var s in _styles)
{
SvgElementFactory.SetPropertyValue(this, s.Key, s.Value.Last().Value, this.OwnerDocument);
}
_styles = null;
}
public bool ContainsAttribute(string name)
{
SortedDictionary rules;
return (this.Attributes.ContainsKey(name) || this.CustomAttributes.ContainsKey(name) ||
(_styles != null && _styles.TryGetValue(name, out rules)) && (rules.ContainsKey(StyleSpecificity_InlineStyle) || rules.ContainsKey(StyleSpecificity_PresAttribute)));
}
public bool TryGetAttribute(string name, out string value)
{
object objValue;
if (this.Attributes.TryGetValue(name, out objValue))
{
value = objValue.ToString();
return true;
}
if (this.CustomAttributes.TryGetValue(name, out value)) return true;
SortedDictionary rules;
if (_styles != null && _styles.TryGetValue(name, out rules))
{
// Get staged styles that are
if (rules.TryGetValue(StyleSpecificity_InlineStyle, out value)) return true;
if (rules.TryGetValue(StyleSpecificity_PresAttribute, out value)) return true;
}
return false;
}
///
/// Gets the name of the element.
///
protected internal string ElementName
{
get
{
if (string.IsNullOrEmpty(this._elementName))
{
var attr = TypeDescriptor.GetAttributes(this).OfType().SingleOrDefault();
if (attr != null)
{
this._elementName = attr.ElementName;
}
}
return this._elementName;
}
internal set { this._elementName = value; }
}
///
/// Gets or sets the color of this element which drives the currentColor property.
///
[SvgAttribute("color", true)]
public virtual SvgPaintServer Color
{
get { return (this.Attributes["color"] == null) ? SvgColourServer.NotSet : (SvgPaintServer)this.Attributes["color"]; }
set { this.Attributes["color"] = value; }
}
///
/// Gets or sets the content of the element.
///
private string _content;
public virtual string Content
{
get
{
return _content;
}
set
{
if(_content != null)
{
var oldVal = _content;
_content = value;
if(_content != oldVal)
OnContentChanged(new ContentEventArgs{ Content = value });
}
else
{
_content = value;
OnContentChanged(new ContentEventArgs{ Content = value });
}
}
}
///
/// Gets an of all events belonging to the element.
///
protected virtual EventHandlerList Events
{
get { return this._eventHandlers; }
}
///
/// Occurs when the element is loaded.
///
public event EventHandler Load
{
add { this.Events.AddHandler(_loadEventKey, value); }
remove { this.Events.RemoveHandler(_loadEventKey, value); }
}
///
/// Gets a collection of all child .
///
public virtual SvgElementCollection Children
{
get { return this._children; }
}
public IList Nodes
{
get { return this._nodes; }
}
public IEnumerable Descendants()
{
return this.AsEnumerable().Descendants();
}
private IEnumerable AsEnumerable()
{
yield return this;
}
///
/// Gets a value to determine whether the element has children.
///
public virtual bool HasChildren()
{
return (this.Children.Count > 0);
}
///
/// Gets the parent .
///
/// An if one exists; otherwise null.
public virtual SvgElement Parent
{
get { return this._parent; }
}
public IEnumerable Parents
{
get
{
var curr = this;
while (curr.Parent != null)
{
curr = curr.Parent;
yield return curr;
}
}
}
public IEnumerable ParentsAndSelf
{
get
{
var curr = this;
yield return curr;
while (curr.Parent != null)
{
curr = curr.Parent;
yield return curr;
}
}
}
///
/// Gets the owner .
///
public virtual SvgDocument OwnerDocument
{
get
{
if (this is SvgDocument)
{
return this as SvgDocument;
}
else
{
if(this.Parent != null)
return Parent.OwnerDocument;
else
return null;
}
}
}
///
/// Gets a collection of element attributes.
///
protected internal virtual SvgAttributeCollection Attributes
{
get
{
if (this._attributes == null)
{
this._attributes = new SvgAttributeCollection(this);
}
return this._attributes;
}
}
///
/// Gets a collection of custom attributes
///
public SvgCustomAttributeCollection CustomAttributes
{
get { return this._customAttributes; }
}
private static readonly Matrix _zeroMatrix = new Matrix(0, 0, 0, 0, 0, 0);
///
/// Applies the required transforms to .
///
/// The to be transformed.
protected internal virtual bool PushTransforms(ISvgRenderer renderer)
{
_graphicsMatrix = renderer.Transform;
_graphicsClip = renderer.GetClip();
// Return if there are no transforms
if (this.Transforms == null || this.Transforms.Count == 0)
{
return true;
}
if (this.Transforms.Count == 1 && this.Transforms[0].Matrix.Equals(_zeroMatrix)) return false;
Matrix transformMatrix = renderer.Transform.Clone();
foreach (SvgTransform transformation in this.Transforms)
{
transformMatrix.Multiply(transformation.Matrix);
}
renderer.Transform = transformMatrix;
return true;
}
///
/// Removes any previously applied transforms from the specified .
///
/// The that should have transforms removed.
protected internal virtual void PopTransforms(ISvgRenderer renderer)
{
renderer.Transform = _graphicsMatrix;
_graphicsMatrix = null;
renderer.SetClip(_graphicsClip);
_graphicsClip = null;
}
///
/// Applies the required transforms to .
///
/// The to be transformed.
void ISvgTransformable.PushTransforms(ISvgRenderer renderer)
{
this.PushTransforms(renderer);
}
///
/// Removes any previously applied transforms from the specified .
///
/// The that should have transforms removed.
void ISvgTransformable.PopTransforms(ISvgRenderer renderer)
{
this.PopTransforms(renderer);
}
///
/// Gets or sets the element transforms.
///
/// The transforms.
[SvgAttribute("transform")]
public SvgTransformCollection Transforms
{
get { return (this.Attributes.GetAttribute("transform")); }
set
{
var old = this.Transforms;
if(old != null)
old.TransformChanged -= Attributes_AttributeChanged;
value.TransformChanged += Attributes_AttributeChanged;
this.Attributes["transform"] = value;
}
}
///
/// Gets or sets the ID of the element.
///
/// The ID is already used within the .
[SvgAttribute("id")]
public string ID
{
get { return this.Attributes.GetAttribute("id"); }
set
{
SetAndForceUniqueID(value, false);
}
}
///
/// Gets or sets the text anchor.
///
/// The text anchor.
[SvgAttribute("space", SvgAttributeAttribute.XmlNamespace)]
public virtual XmlSpaceHandling SpaceHandling
{
get { return (this.Attributes["space"] == null) ? XmlSpaceHandling.@default : (XmlSpaceHandling)this.Attributes["space"]; }
set { this.Attributes["space"] = value; }
}
public void SetAndForceUniqueID(string value, bool autoForceUniqueID = true, Action logElementOldIDNewID = null)
{
// Don't do anything if it hasn't changed
if (string.Compare(this.ID, value) == 0)
{
return;
}
if (this.OwnerDocument != null)
{
this.OwnerDocument.IdManager.Remove(this);
}
this.Attributes["id"] = value;
if (this.OwnerDocument != null)
{
this.OwnerDocument.IdManager.AddAndForceUniqueID(this, null, autoForceUniqueID, logElementOldIDNewID);
}
}
///
/// Only used by the ID Manager
///
///
internal void ForceUniqueID(string newID)
{
this.Attributes["id"] = newID;
}
///
/// Called by the underlying when an element has been added to the
/// collection.
///
/// The that has been added.
/// An representing the index where the element was added to the collection.
protected virtual void AddElement(SvgElement child, int index)
{
}
///
/// Fired when an Element was added to the children of this Element
///
public event EventHandler ChildAdded;
///
/// Calls the method with the specified parameters.
///
/// The that has been added.
/// An representing the index where the element was added to the collection.
internal void OnElementAdded(SvgElement child, int index)
{
this.AddElement(child, index);
SvgElement sibling = null;
if(index < (Children.Count - 1))
{
sibling = Children[index + 1];
}
var handler = ChildAdded;
if(handler != null)
{
handler(this, new ChildAddedEventArgs { NewChild = child, BeforeSibling = sibling });
}
}
///
/// Called by the underlying when an element has been removed from the
/// collection.
///
/// The that has been removed.
protected virtual void RemoveElement(SvgElement child)
{
}
///
/// Calls the method with the specified as the parameter.
///
/// The that has been removed.
internal void OnElementRemoved(SvgElement child)
{
this.RemoveElement(child);
}
///
/// Initializes a new instance of the class.
///
public SvgElement()
{
this._children = new SvgElementCollection(this);
this._eventHandlers = new EventHandlerList();
this._elementName = string.Empty;
this._customAttributes = new SvgCustomAttributeCollection(this);
Transforms = new SvgTransformCollection();
//subscribe to attribute events
Attributes.AttributeChanged += Attributes_AttributeChanged;
CustomAttributes.AttributeChanged += Attributes_AttributeChanged;
//find svg attribute descriptions
_svgPropertyAttributes = from PropertyDescriptor a in TypeDescriptor.GetProperties(this)
let attribute = a.Attributes[typeof(SvgAttributeAttribute)] as SvgAttributeAttribute
where attribute != null
select new PropertyAttributeTuple { Property = a, Attribute = attribute };
_svgEventAttributes = from EventDescriptor a in TypeDescriptor.GetEvents(this)
let attribute = a.Attributes[typeof(SvgAttributeAttribute)] as SvgAttributeAttribute
where attribute != null
select new EventAttributeTuple { Event = a.ComponentType.GetField(a.Name, BindingFlags.Instance | BindingFlags.NonPublic), Attribute = attribute };
}
//dispatch attribute event
void Attributes_AttributeChanged(object sender, AttributeEventArgs e)
{
OnAttributeChanged(e);
}
public virtual void InitialiseFromXML(XmlTextReader reader, SvgDocument document)
{
throw new NotImplementedException();
}
///
/// Renders this element to the .
///
/// The that the element should use to render itself.
public void RenderElement(ISvgRenderer renderer)
{
this.Render(renderer);
}
public void WriteElement(XmlTextWriter writer)
{
//Save previous culture and switch to invariant for writing
var previousCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
this.Write(writer);
//Switch culture back
Thread.CurrentThread.CurrentCulture = previousCulture;
}
protected virtual void WriteStartElement(XmlTextWriter writer)
{
if (this.ElementName != String.Empty)
{
writer.WriteStartElement(this.ElementName);
if (this.ElementName == "svg")
{
foreach (var ns in SvgAttributeAttribute.Namespaces)
{
if (string.IsNullOrEmpty(ns.Key))
writer.WriteAttributeString("xmlns", ns.Value);
else
writer.WriteAttributeString("xmlns:" + ns.Key, ns.Value);
}
writer.WriteAttributeString("version", "1.1");
}
}
this.WriteAttributes(writer);
}
protected virtual void WriteEndElement(XmlTextWriter writer)
{
if (this.ElementName != String.Empty)
{
writer.WriteEndElement();
}
}
protected virtual void WriteAttributes(XmlTextWriter writer)
{
var styles = new Dictionary();
bool writeStyle;
bool forceWrite;
//properties
foreach (var attr in _svgPropertyAttributes)
{
if (attr.Property.Converter.CanConvertTo(typeof(string)) &&
(!attr.Attribute.InAttributeDictionary || _attributes.ContainsKey(attr.Attribute.Name)))
{
object propertyValue = attr.Property.GetValue(this);
string value = (string)attr.Property.Converter.ConvertTo(propertyValue, typeof(string));
forceWrite = false;
writeStyle = (attr.Attribute.Name == "fill");
if ((attr.Attribute.Name == "fill") && (Parent != null))
{
if(propertyValue == SvgColourServer.NotSet) continue;
object parentValue;
if (TryResolveParentAttributeValue(attr.Attribute.Name, out parentValue))
{
if ((parentValue == propertyValue)
|| ((parentValue != null) && parentValue.Equals(propertyValue)))
continue;
forceWrite = true;
}
}
if (propertyValue != null)
{
var type = propertyValue.GetType();
//Only write the attribute's value if it is not the default value, not null/empty, or we're forcing the write.
if ((!string.IsNullOrEmpty(value) && !SvgDefaults.IsDefault(attr.Attribute.Name, value)) || forceWrite)
{
if (writeStyle)
{
styles[attr.Attribute.Name] = value;
}
else
{
writer.WriteAttributeString(attr.Attribute.NamespaceAndName, value);
}
}
}
else if(attr.Attribute.Name == "fill") //if fill equals null, write 'none'
{
if (writeStyle)
{
styles[attr.Attribute.Name] = value;
}
else
{
writer.WriteAttributeString(attr.Attribute.NamespaceAndName, value);
}
}
}
}
//events
if(AutoPublishEvents)
{
foreach (var attr in _svgEventAttributes)
{
var evt = attr.Event.GetValue(this);
//if someone has registered publish the attribute
if (evt != null && !string.IsNullOrEmpty(this.ID))
{
writer.WriteAttributeString(attr.Attribute.Name, this.ID + "/" + attr.Attribute.Name);
}
}
}
//add the custom attributes
foreach (var item in this._customAttributes)
{
writer.WriteAttributeString(item.Key, item.Value);
}
//write the style property
if (styles.Any())
{
writer.WriteAttributeString("style", (from s in styles
select s.Key + ":" + s.Value + ";").Aggregate((p,c) => p + c));
}
}
public bool AutoPublishEvents = true;
private bool TryResolveParentAttributeValue(string attributeKey, out object parentAttributeValue)
{
parentAttributeValue = null;
//attributeKey = char.ToUpper(attributeKey[0]) + attributeKey.Substring(1);
var currentParent = Parent;
var resolved = false;
while (currentParent != null)
{
if (currentParent.Attributes.ContainsKey(attributeKey))
{
resolved = true;
parentAttributeValue = currentParent.Attributes[attributeKey];
if (parentAttributeValue != null)
break;
}
currentParent = currentParent.Parent;
}
return resolved;
}
protected virtual void Write(XmlTextWriter writer)
{
if (this.ElementName != String.Empty)
{
this.WriteStartElement(writer);
this.WriteChildren(writer);
this.WriteEndElement(writer);
}
}
protected virtual void WriteChildren(XmlTextWriter writer)
{
if (this.Nodes.Any())
{
SvgContentNode content;
foreach (var node in this.Nodes)
{
content = node as SvgContentNode;
if (content == null)
{
((SvgElement)node).Write(writer);
}
else if (!string.IsNullOrEmpty(content.Content))
{
writer.WriteString(content.Content);
}
}
}
else
{
//write the content
if(!String.IsNullOrEmpty(this.Content))
writer.WriteString(this.Content);
//write all children
foreach (SvgElement child in this.Children)
{
child.Write(writer);
}
}
}
///
/// Renders the and contents to the specified object.
///
/// The object to render to.
protected virtual void Render(ISvgRenderer renderer)
{
this.PushTransforms(renderer);
this.RenderChildren(renderer);
this.PopTransforms(renderer);
}
///
/// Renders the children of this .
///
/// The to render the child s to.
protected virtual void RenderChildren(ISvgRenderer renderer)
{
foreach (SvgElement element in this.Children)
{
element.Render(renderer);
}
}
///
/// Renders the and contents to the specified object.
///
/// The object to render to.
void ISvgElement.Render(ISvgRenderer renderer)
{
this.Render(renderer);
}
///
/// Recursive method to add up the paths of all children
///
///
///
protected void AddPaths(SvgElement elem, GraphicsPath path)
{
foreach(var child in elem.Children)
{
if (child is SvgVisualElement)
{
if(!(child is SvgGroup))
{
var childPath = ((SvgVisualElement)child).Path(null);
if (childPath != null)
{
childPath = (GraphicsPath)childPath.Clone();
if(child.Transforms != null)
childPath.Transform(child.Transforms.GetMatrix());
if (childPath.PointCount > 0) path.AddPath(childPath, false);
}
}
}
if (!(child is SvgPaintServer)) AddPaths(child, path);
}
}
///
/// Recursive method to add up the paths of all children
///
///
///
protected GraphicsPath GetPaths(SvgElement elem, ISvgRenderer renderer)
{
var ret = new GraphicsPath();
foreach(var child in elem.Children)
{
if (child is SvgVisualElement)
{
if(!(child is SvgGroup))
{
var childPath = ((SvgVisualElement)child).Path(renderer);
if (childPath != null)
{
childPath = (GraphicsPath)childPath.Clone();
if(child.Transforms != null)
childPath.Transform(child.Transforms.GetMatrix());
ret.AddPath(childPath, false);
}
}
else
{
var childPath = GetPaths(child, renderer);
if(child.Transforms != null)
childPath.Transform(child.Transforms.GetMatrix());
}
}
}
return ret;
}
///
/// Creates a new object that is a copy of the current instance.
///
///
/// A new object that is a copy of this instance.
///
public virtual object Clone()
{
return this.MemberwiseClone();
}
public abstract SvgElement DeepCopy();
public virtual SvgElement DeepCopy() where T : SvgElement, new()
{
var newObj = new T();
newObj.ID = this.ID;
newObj.Content = this.Content;
newObj.ElementName = this.ElementName;
// if (this.Parent != null)
// this.Parent.Children.Add(newObj);
if (this.Transforms != null)
{
newObj.Transforms = this.Transforms.Clone() as SvgTransformCollection;
}
foreach (var child in this.Children)
{
newObj.Children.Add(child.DeepCopy());
}
foreach (var attr in this._svgEventAttributes)
{
var evt = attr.Event.GetValue(this);
//if someone has registered also register here
if (evt != null)
{
if(attr.Event.Name == "MouseDown")
newObj.MouseDown += delegate { };
else if (attr.Event.Name == "MouseUp")
newObj.MouseUp += delegate { };
else if (attr.Event.Name == "MouseOver")
newObj.MouseOver += delegate { };
else if (attr.Event.Name == "MouseOut")
newObj.MouseOut += delegate { };
else if (attr.Event.Name == "MouseMove")
newObj.MouseMove += delegate { };
else if (attr.Event.Name == "MouseScroll")
newObj.MouseScroll += delegate { };
else if (attr.Event.Name == "Click")
newObj.Click += delegate { };
else if (attr.Event.Name == "Change") //text element
(newObj as SvgText).Change += delegate { };
}
}
if(this._customAttributes.Count > 0)
{
foreach (var element in _customAttributes)
{
newObj.CustomAttributes.Add(element.Key, element.Value);
}
}
return newObj;
}
///
/// Fired when an Atrribute of this Element has changed
///
public event EventHandler AttributeChanged;
protected void OnAttributeChanged(AttributeEventArgs args)
{
var handler = AttributeChanged;
if(handler != null)
{
handler(this, args);
}
}
///
/// Fired when an Atrribute of this Element has changed
///
public event EventHandler ContentChanged;
protected void OnContentChanged(ContentEventArgs args)
{
var handler = ContentChanged;
if(handler != null)
{
handler(this, args);
}
}
#region graphical EVENTS
/*
onfocusin = ""
onfocusout = ""
onactivate = ""
onclick = ""
onmousedown = ""
onmouseup = ""
onmouseover = ""
onmousemove = ""
onmouseout = ""
*/
#if Net4
///
/// Use this method to provide your implementation ISvgEventCaller which can register Actions
/// and call them if one of the events occurs. Make sure, that your SvgElement has a unique ID.
/// The SvgTextElement overwrites this and regsiters the Change event tor its text content.
///
///
public virtual void RegisterEvents(ISvgEventCaller caller)
{
if (caller != null && !string.IsNullOrEmpty(this.ID))
{
var rpcID = this.ID + "/";
caller.RegisterAction(rpcID + "onclick", CreateMouseEventAction(RaiseMouseClick));
caller.RegisterAction(rpcID + "onmousedown", CreateMouseEventAction(RaiseMouseDown));
caller.RegisterAction(rpcID + "onmouseup", CreateMouseEventAction(RaiseMouseUp));
caller.RegisterAction(rpcID + "onmousemove", CreateMouseEventAction(RaiseMouseMove));
caller.RegisterAction(rpcID + "onmouseover", CreateMouseEventAction(RaiseMouseOver));
caller.RegisterAction(rpcID + "onmouseout", CreateMouseEventAction(RaiseMouseOut));
caller.RegisterAction(rpcID + "onmousescroll", OnMouseScroll);
}
}
///
/// Use this method to provide your implementation ISvgEventCaller to unregister Actions
///
///
public virtual void UnregisterEvents(ISvgEventCaller caller)
{
if (caller != null && !string.IsNullOrEmpty(this.ID))
{
var rpcID = this.ID + "/";
caller.UnregisterAction(rpcID + "onclick");
caller.UnregisterAction(rpcID + "onmousedown");
caller.UnregisterAction(rpcID + "onmouseup");
caller.UnregisterAction(rpcID + "onmousemove");
caller.UnregisterAction(rpcID + "onmousescroll");
caller.UnregisterAction(rpcID + "onmouseover");
caller.UnregisterAction(rpcID + "onmouseout");
}
}
#endif
[SvgAttribute("onclick")]
public event EventHandler Click;
[SvgAttribute("onmousedown")]
public event EventHandler MouseDown;
[SvgAttribute("onmouseup")]
public event EventHandler MouseUp;
[SvgAttribute("onmousemove")]
public event EventHandler MouseMove;
[SvgAttribute("onmousescroll")]
public event EventHandler MouseScroll;
[SvgAttribute("onmouseover")]
public event EventHandler MouseOver;
[SvgAttribute("onmouseout")]
public event EventHandler MouseOut;
#if Net4
protected Action CreateMouseEventAction(Action