using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Diagnostics; using System.Linq; namespace Svg { /// /// The class that all SVG elements should derive from when they are to be rendered. /// public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable { private bool _requiresSmoothRendering; private Region _previousClip; /// /// Gets the for this element. /// public abstract GraphicsPath Path(ISvgRenderer renderer); PointF ISvgBoundable.Location { get { return Bounds.Location; } } SizeF ISvgBoundable.Size { get { return Bounds.Size; } } /// /// Gets the bounds of the element. /// /// The bounds. public abstract RectangleF Bounds { get; } /// /// Gets the associated if one has been specified. /// [SvgAttribute("clip")] public virtual string Clip { get { return this.Attributes.GetInheritedAttribute("clip"); } set { this.Attributes["clip"] = value; } } /// /// Gets the associated if one has been specified. /// [SvgAttribute("clip-path")] public virtual Uri ClipPath { get { return this.Attributes.GetInheritedAttribute("clip-path"); } set { this.Attributes["clip-path"] = value; } } /// /// Gets or sets the algorithm which is to be used to determine the clipping region. /// [SvgAttribute("clip-rule")] public SvgClipRule ClipRule { get { return this.Attributes.GetAttribute("clip-rule", SvgClipRule.NonZero); } set { this.Attributes["clip-rule"] = value; } } /// /// Gets the associated if one has been specified. /// [SvgAttribute("filter")] public virtual Uri Filter { get { return this.Attributes.GetInheritedAttribute("filter"); } set { this.Attributes["filter"] = value; } } /// /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered. /// protected virtual bool RequiresSmoothRendering { get { return this._requiresSmoothRendering; } } /// /// Initializes a new instance of the class. /// public SvgVisualElement() { this.IsPathDirty = true; this._requiresSmoothRendering = false; } protected virtual bool Renderable { get { return true; } } /// /// Renders the and contents to the specified object. /// /// The object to render to. protected override void Render(ISvgRenderer renderer) { this.Render(renderer, true); } private void Render(ISvgRenderer renderer, bool renderFilter) { if (this.Visible && this.Displayable && this.PushTransforms(renderer) && (!Renderable || this.Path(renderer) != null)) { bool renderNormal = true; if (renderFilter && this.Filter != null) { var filterPath = this.Filter; if (filterPath.ToString().StartsWith("url(")) { filterPath = new Uri(filterPath.ToString().Substring(4, filterPath.ToString().Length - 5), UriKind.RelativeOrAbsolute); } var filter = this.OwnerDocument.IdManager.GetElementById(filterPath) as FilterEffects.SvgFilter; if (filter != null) { this.PopTransforms(renderer); try { filter.ApplyFilter(this, renderer, (r) => this.Render(r, false)); } catch (Exception ex) { Debug.Print(ex.ToString()); } renderNormal = false; } } if (renderNormal) { this.SetClip(renderer); if (Renderable) { // If this element needs smoothing enabled turn anti-aliasing on if (this.RequiresSmoothRendering) { renderer.SmoothingMode = SmoothingMode.AntiAlias; } this.RenderFill(renderer); this.RenderStroke(renderer); // Reset the smoothing mode if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias) { renderer.SmoothingMode = SmoothingMode.Default; } } else { base.RenderChildren(renderer); } this.ResetClip(renderer); this.PopTransforms(renderer); } } } /// /// Renders the fill of the to the specified /// /// The object to render to. protected internal virtual void RenderFill(ISvgRenderer renderer) { if (this.Fill != null) { using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1))) { if (brush != null) { this.Path(renderer).FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate; renderer.FillPath(brush, this.Path(renderer)); } } } } /// /// Renders the stroke of the to the specified /// /// The object to render to. protected internal virtual bool RenderStroke(ISvgRenderer renderer) { if (this.Stroke != null && this.Stroke != SvgColourServer.None) { float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true)) { if (brush != null) { var path = this.Path(renderer); var bounds = path.GetBounds(); if (path.PointCount < 1) return false; if (bounds.Width <= 0 && bounds.Height <= 0) { switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: using (var capPath = new GraphicsPath()) { capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth); renderer.FillPath(brush, capPath); } break; case SvgStrokeLineCap.Square: using (var capPath = new GraphicsPath()) { capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth)); renderer.FillPath(brush, capPath); } break; } } else { using (var pen = new Pen(brush, strokeWidth)) { if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0) { /* divide by stroke width - GDI behaviour that I don't quite understand yet.*/ pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray(); } switch (this.StrokeLineJoin) { case SvgStrokeLineJoin.Bevel: pen.LineJoin = LineJoin.Bevel; break; case SvgStrokeLineJoin.Round: pen.LineJoin = LineJoin.Round; break; default: pen.LineJoin = LineJoin.Miter; break; } pen.MiterLimit = this.StrokeMiterLimit; switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; break; case SvgStrokeLineCap.Square: pen.StartCap = LineCap.Square; pen.EndCap = LineCap.Square; break; } renderer.DrawPath(pen, path); return true; } } } } } return false; } /// /// Sets the clipping region of the specified . /// /// The to have its clipping region set. protected internal virtual void SetClip(ISvgRenderer renderer) { if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip)) { this._previousClip = renderer.GetClip(); if (this.ClipPath != null) { SvgClipPath clipPath = this.OwnerDocument.GetElementById(this.ClipPath.ToString()); if (clipPath != null) renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect); } var clip = this.Clip; if (!string.IsNullOrEmpty(clip) && clip.StartsWith("rect(")) { clip = clip.Trim(); var offsets = (from o in clip.Substring(5, clip.Length - 6).Split(',') select float.Parse(o.Trim())).ToList(); var bounds = this.Bounds; var clipRect = new RectangleF(bounds.Left + offsets[3], bounds.Top + offsets[0], bounds.Width - (offsets[3] + offsets[1]), bounds.Height - (offsets[2] + offsets[0])); renderer.SetClip(new Region(clipRect), CombineMode.Intersect); } } } /// /// Resets the clipping region of the specified back to where it was before the method was called. /// /// The to have its clipping region reset. protected internal virtual void ResetClip(ISvgRenderer renderer) { if (this._previousClip != null) { renderer.SetClip(this._previousClip); this._previousClip = null; } } /// /// Sets the clipping region of the specified . /// /// The to have its clipping region set. void ISvgClipable.SetClip(ISvgRenderer renderer) { this.SetClip(renderer); } /// /// Resets the clipping region of the specified back to where it was before the method was called. /// /// The to have its clipping region reset. void ISvgClipable.ResetClip(ISvgRenderer renderer) { this.ResetClip(renderer); } public override SvgElement DeepCopy() { var newObj = base.DeepCopy() 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; } } }