// THIS FILE IS PART OF SVG PROJECT // THE SVG PROJECT IS AN OPENSOURCE LIBRARY LICENSED UNDER THE MS-PL License. // COPYRIGHT (C) svg-net. ALL RIGHTS RESERVED. // GITHUB: https://github.com/svg-net/SVG using AntdUI.Svg.Transforms; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; namespace AntdUI.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; internal SvgElement _parent; private string _elementName; private SvgAttributeCollection _attributes; private EventHandlerList _eventHandlers; private SvgElementCollection _children; 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() { if (_styles.Any()) { var styles = new Dictionary>(); foreach (var s in _styles) { if (!SvgElementFactory.SetPropertyValue(this, s.Key, s.Value.Last().Value, OwnerDocument, isStyle: true)) { styles.Add(s.Key, s.Value); } } _styles = styles; } } /// /// Gets the name of the element. /// protected internal string ElementName { get { if (string.IsNullOrEmpty(_elementName)) { _elementName = ClassName; } return _elementName; } internal set { _elementName = value; } } public virtual string ClassName => string.Empty; /// /// Gets or sets the color of this element which drives the currentColor property. /// [SvgAttribute("color", true)] public virtual SvgPaintServer Color { get { return (Attributes["color"] == null) ? SvgColourServer.NotSet : (SvgPaintServer)Attributes["color"]; } set { 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 _eventHandlers; } } /// /// Gets a collection of all child objects. /// public virtual SvgElementCollection Children { get { return _children; } } public IList Nodes { get { return _nodes; } } public IEnumerable Descendants() { return AsEnumerable().Descendants(); } private IEnumerable AsEnumerable() { yield return this; } /// /// Gets a value to determine whether the element has children. /// public virtual bool HasChildren() { return (Children.Count > 0); } /// /// Gets the parent . /// /// An if one exists; otherwise null. public virtual SvgElement Parent { get { return _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 (Parent != null) return Parent.OwnerDocument; else return null; } } } /// /// Gets a collection of element attributes. /// protected internal virtual SvgAttributeCollection Attributes { get { if (_attributes == null) { _attributes = new SvgAttributeCollection(this); } return _attributes; } } /// /// Gets a collection of custom attributes /// public SvgCustomAttributeCollection CustomAttributes { get { return _customAttributes; } } /// /// 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 (Transforms == null || Transforms.Count == 0) return true; Matrix transformMatrix = renderer.Transform.Clone(); var bound = renderer.GetBoundable(); foreach (SvgTransform transformation in Transforms) { transformMatrix.Multiply(transformation.Matrix(bound.Bounds.Width, bound.Bounds.Height)); } 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) { PushTransforms(renderer); } /// /// Removes any previously applied transforms from the specified . /// /// The that should have transforms removed. void ISvgTransformable.PopTransforms(ISvgRenderer renderer) { PopTransforms(renderer); } /// /// Gets or sets the element transforms. /// /// The transforms. [SvgAttribute("transform")] public SvgTransformCollection Transforms { get { return (Attributes.GetAttribute("transform")); } set { var old = Transforms; if (old != null) old.TransformChanged -= Attributes_AttributeChanged; value.TransformChanged += Attributes_AttributeChanged; Attributes["transform"] = value; } } /// /// Transforms the given rectangle with the set transformation, if any. /// Can be applied to bounds calculated without considering the element transformation. /// /// The rectangle to be transformed. /// The transformed rectangle, or the original rectangle if no transformation exists. protected RectangleF TransformedBounds(RectangleF bounds) { if (Transforms != null && Transforms.Count > 0) { var path = new GraphicsPath(); path.AddRectangle(bounds); path.Transform(Transforms.GetMatrix()); return path.GetBounds(); } return bounds; } /// /// Gets or sets the ID of the element. /// /// The ID is already used within the . [SvgAttribute("id")] public string ID { get { return Attributes.GetAttribute("id"); } set { SetAndForceUniqueID(value, false); } } /// /// Gets or sets the space handling. /// /// The space handling. [SvgAttribute("space", SvgAttributeAttribute.XmlNamespace)] public virtual XmlSpaceHandling SpaceHandling { get { return (Attributes["space"] == null) ? XmlSpaceHandling.@default : (XmlSpaceHandling)Attributes["space"]; } set { 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(ID, value) == 0) { return; } if (OwnerDocument != null) { OwnerDocument.IdManager.Remove(this); } Attributes["id"] = value; if (OwnerDocument != null) { OwnerDocument.IdManager.AddAndForceUniqueID(this, null, autoForceUniqueID, logElementOldIDNewID); } } /// /// Only used by the ID Manager /// /// internal void ForceUniqueID(string newID) { 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) { 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) { RemoveElement(child); } /// /// Initializes a new instance of the class. /// public SvgElement() { _children = new SvgElementCollection(this); _eventHandlers = new EventHandlerList(); _customAttributes = new SvgCustomAttributeCollection(this); Transforms = new SvgTransformCollection(); //subscribe to attribute events Attributes.AttributeChanged += Attributes_AttributeChanged; CustomAttributes.AttributeChanged += Attributes_AttributeChanged; } //dispatch attribute event void Attributes_AttributeChanged(object sender, AttributeEventArgs e) { OnAttributeChanged(e); } /// /// Renders this element to the . /// /// The that the element should use to render itself. public void RenderElement(ISvgRenderer renderer) { Render(renderer); } /// Derrived classes may decide that the element should not be written. For example, the text element shouldn't be written if it's empty. public virtual bool ShouldWriteElement() { //Write any element who has a name. return (ElementName != String.Empty); } /// /// Renders the and contents to the specified object. /// /// The object to render to. protected virtual void Render(ISvgRenderer renderer) { PushTransforms(renderer); RenderChildren(renderer); PopTransforms(renderer); } /// /// Renders the children of this . /// /// The to render the child s to. protected virtual void RenderChildren(ISvgRenderer renderer) { foreach (SvgElement element in Children) { element.Render(renderer); } } /// /// Renders the and contents to the specified object. /// /// The object to render to. void ISvgElement.Render(ISvgRenderer renderer) { 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) { // Skip to avoid double calculate Symbol element // symbol element is only referenced by use element // So here we need to skip when it is directly considered if (child is Svg.Document_Structure.SvgSymbol) continue; 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); // Non-group element can have child element which we have to consider. i.e tspan in text element if (child.Children.Count > 0) childPath.AddPath(GetPaths(child, renderer), false); if (childPath != null && childPath.PointCount > 0) { childPath = (GraphicsPath)childPath.Clone(); if (child.Transforms != null) childPath.Transform(child.Transforms.GetMatrix()); ret.AddPath(childPath, false); } } else { var childPath = GetPaths(child, renderer); if (childPath != null && childPath.PointCount > 0) { if (child.Transforms != null) childPath.Transform(child.Transforms.GetMatrix()); ret.AddPath(childPath, false); } } } } 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 MemberwiseClone(); } protected void OnAttributeChanged(AttributeEventArgs 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); } } } public class SVGArg : EventArgs { public string SessionID; } /// /// Describes the Attribute which was set /// public class AttributeEventArgs : SVGArg { public string Attribute; public object Value; } /// /// Content of this whas was set /// public class ContentEventArgs : SVGArg { public string Content; } /// /// Describes the Attribute which was set /// public class ChildAddedEventArgs : SVGArg { public SvgElement NewChild; public SvgElement BeforeSibling; } /// /// Represents a string argument /// public class StringArg : SVGArg { public string s; } public class MouseScrollArg : SVGArg { public int Scroll; /// /// Alt modifier key pressed /// public bool AltKey; /// /// Shift modifier key pressed /// public bool ShiftKey; /// /// Control modifier key pressed /// public bool CtrlKey; } public interface ISvgNode { string Content { get; } } /// This interface mostly indicates that a node is not to be drawn when rendering the SVG. public interface ISvgDescriptiveElement { } internal interface ISvgElement { SvgElement Parent { get; } SvgElementCollection Children { get; } IList Nodes { get; } string ClassName { get; } void Render(ISvgRenderer renderer); } }