SvgVisualElement.cs 15 KB
Newer Older
davescriven's avatar
davescriven committed
1
2
3
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
Eric Domke's avatar
Eric Domke committed
4
using System.Diagnostics;
Eric Domke's avatar
Eric Domke committed
5
using System.Linq;
davescriven's avatar
davescriven committed
6
7
8
9
10
11

namespace Svg
{
    /// <summary>
    /// The class that all SVG elements should derive from when they are to be rendered.
    /// </summary>
James Welle's avatar
James Welle committed
12
    public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
davescriven's avatar
davescriven committed
13
14
    {
        private bool _requiresSmoothRendering;
15
        private Region _previousClip;
davescriven's avatar
davescriven committed
16
17
18
19

        /// <summary>
        /// Gets the <see cref="GraphicsPath"/> for this element.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
20
        public abstract GraphicsPath Path(ISvgRenderer renderer);
James Welle's avatar
James Welle committed
21

davescriven's avatar
davescriven committed
22
23
24
        /// <summary>
        /// Gets the bounds of the element.
        /// </summary>
Dan Backes's avatar
Dan Backes committed
25
26
        /// <returns>The bounds.</returns>
        public abstract RectangleF CalculateBounds();
davescriven's avatar
davescriven committed
27

Eric Domke's avatar
Eric Domke committed
28
29
30
31
32
33
34
35
36
37
        /// <summary>
        /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
        /// </summary>
        [SvgAttribute("clip")]
        public virtual string Clip
        {
            get { return this.Attributes.GetInheritedAttribute<string>("clip"); }
            set { this.Attributes["clip"] = value; }
        }

38
39
40
41
42
43
        /// <summary>
        /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
        /// </summary>
        [SvgAttribute("clip-path")]
        public virtual Uri ClipPath
        {
Eric Domke's avatar
Eric Domke committed
44
            get { return this.Attributes.GetInheritedAttribute<Uri>("clip-path"); }
45
46
47
            set { this.Attributes["clip-path"] = value; }
        }

48
        /// <summary>
49
        /// Gets or sets the algorithm which is to be used to determine the clipping region.
50
51
52
53
54
55
56
57
        /// </summary>
        [SvgAttribute("clip-rule")]
        public SvgClipRule ClipRule
        {
            get { return this.Attributes.GetAttribute<SvgClipRule>("clip-rule", SvgClipRule.NonZero); }
            set { this.Attributes["clip-rule"] = value; }
        }

James Welle's avatar
James Welle committed
58
59
60
61
62
63
        /// <summary>
        /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
        /// </summary>
        [SvgAttribute("filter")]
        public virtual Uri Filter
        {
Eric Domke's avatar
Eric Domke committed
64
            get { return this.Attributes.GetInheritedAttribute<Uri>("filter"); }
James Welle's avatar
James Welle committed
65
66
            set { this.Attributes["filter"] = value; }
        }
67

davescriven's avatar
davescriven committed
68
69
70
71
72
73
74
75
76
77
78
        /// <summary>
        /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
        /// </summary>
        protected virtual bool RequiresSmoothRendering
        {
            get { return this._requiresSmoothRendering; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SvgGraphicsElement"/> class.
        /// </summary>
79
        public SvgVisualElement()
davescriven's avatar
davescriven committed
80
        {
Eric Domke's avatar
Eric Domke committed
81
            this.IsPathDirty = true;
davescriven's avatar
davescriven committed
82
83
84
            this._requiresSmoothRendering = false;
        }

Eric Domke's avatar
Eric Domke committed
85
86
        protected virtual bool Renderable { get { return true; } }

davescriven's avatar
davescriven committed
87
88
89
        /// <summary>
        /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
90
91
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected override void Render(ISvgRenderer renderer)
davescriven's avatar
davescriven committed
92
        {
Eric Domke's avatar
Eric Domke committed
93
94
95
            this.Render(renderer, true);
        }

Eric Domke's avatar
Eric Domke committed
96
        private void Render(ISvgRenderer renderer, bool renderFilter)
Eric Domke's avatar
Eric Domke committed
97
98
99
        {
            if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
                (!Renderable || this.Path(renderer) != null))
davescriven's avatar
davescriven committed
100
            {
Eric Domke's avatar
Eric Domke committed
101
                bool renderNormal = true;
davescriven's avatar
davescriven committed
102

Eric Domke's avatar
Eric Domke committed
103
                if (renderFilter && this.Filter != null)
davescriven's avatar
davescriven committed
104
                {
Eric Domke's avatar
Eric Domke committed
105
106
107
108
109
110
                    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;
Eric Domke's avatar
Eric Domke committed
111
112
113
                    if (filter != null)
                    {
                        this.PopTransforms(renderer);
Eric Domke's avatar
Eric Domke committed
114
115
116
117
118
                        try
                        {
                            filter.ApplyFilter(this, renderer, (r) => this.Render(r, false));
                        }
                        catch (Exception ex) { Debug.Print(ex.ToString()); }
Eric Domke's avatar
Eric Domke committed
119
120
                        renderNormal = false;
                    }
davescriven's avatar
davescriven committed
121
122
                }

123

Eric Domke's avatar
Eric Domke committed
124
                if (renderNormal)
davescriven's avatar
davescriven committed
125
                {
Eric Domke's avatar
Eric Domke committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
                    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);
152
153
154
155
156
157
                }

            }
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
158
        /// Renders the fill of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
159
        /// </summary>
Eric Domke's avatar
Eric Domke committed
160
161
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual void RenderFill(ISvgRenderer renderer)
162
163
164
        {
            if (this.Fill != null)
            {
Eric Domke's avatar
Eric Domke committed
165
                using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
166
167
                {
                    if (brush != null)
davescriven's avatar
davescriven committed
168
                    {
169
170
                        this.Path(renderer).FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
                        renderer.FillPath(brush, this.Path(renderer));
davescriven's avatar
davescriven committed
171
172
                    }
                }
173
174
            }
        }
davescriven's avatar
davescriven committed
175

176
        /// <summary>
Eric Domke's avatar
Eric Domke committed
177
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
178
        /// </summary>
Eric Domke's avatar
Eric Domke committed
179
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
Eric Domke's avatar
Eric Domke committed
180
        protected internal virtual bool RenderStroke(ISvgRenderer renderer)
181
        {
Eric Domke's avatar
Eric Domke committed
182
            if (this.Stroke != null && this.Stroke != SvgColourServer.None)
183
            {
184
                float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
Eric Domke's avatar
Eric Domke committed
185
                using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
davescriven's avatar
davescriven committed
186
                {
Eric Domke's avatar
Eric Domke committed
187
                    if (brush != null)
davescriven's avatar
davescriven committed
188
                    {
Eric Domke's avatar
Eric Domke committed
189
190
191
192
                        var path = this.Path(renderer);
                        var bounds = path.GetBounds();
                        if (path.PointCount < 1) return false;
                        if (bounds.Width <= 0 && bounds.Height <= 0)
Eric Domke's avatar
Eric Domke committed
193
                        {
Eric Domke's avatar
Eric Domke committed
194
                            switch (this.StrokeLineCap)
Eric Domke's avatar
Eric Domke committed
195
                            {
Eric Domke's avatar
Eric Domke committed
196
197
198
199
200
201
202
203
204
205
206
207
208
209
                                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;
Eric Domke's avatar
Eric Domke committed
210
                            }
Eric Domke's avatar
Eric Domke committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
                        }
                        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);
248

Eric Domke's avatar
Eric Domke committed
249
250
                                return true;
                            }
Eric Domke's avatar
Eric Domke committed
251
252
                        }
                    }
davescriven's avatar
davescriven committed
253
254
                }
            }
Eric Domke's avatar
Eric Domke committed
255
256

            return false;
davescriven's avatar
davescriven committed
257
        }
258

259
        /// <summary>
Eric Domke's avatar
Eric Domke committed
260
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
261
        /// </summary>
Eric Domke's avatar
Eric Domke committed
262
263
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        protected internal virtual void SetClip(ISvgRenderer renderer)
264
        {
Eric Domke's avatar
Eric Domke committed
265
            if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
266
            {
Eric Domke's avatar
Eric Domke committed
267
                this._previousClip = renderer.GetClip();
268

Eric Domke's avatar
Eric Domke committed
269
270
271
272
273
274
275
276
                if (this.ClipPath != null)
                {
                    SvgClipPath clipPath = this.OwnerDocument.GetElementById<SvgClipPath>(this.ClipPath.ToString());
                    if (clipPath != null) renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect);
                }

                var clip = this.Clip;
                if (!string.IsNullOrEmpty(clip) && clip.StartsWith("rect("))
277
                {
Eric Domke's avatar
Eric Domke committed
278
279
280
281
282
283
284
285
                    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);
286
                }
287
288
289
            }
        }

290
        /// <summary>
Eric Domke's avatar
Eric Domke committed
291
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
292
        /// </summary>
Eric Domke's avatar
Eric Domke committed
293
294
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        protected internal virtual void ResetClip(ISvgRenderer renderer)
295
        {
296
            if (this._previousClip != null)
297
            {
Eric Domke's avatar
Eric Domke committed
298
                renderer.SetClip(this._previousClip);
299
300
301
302
                this._previousClip = null;
            }
        }

303
        /// <summary>
Eric Domke's avatar
Eric Domke committed
304
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
305
        /// </summary>
Eric Domke's avatar
Eric Domke committed
306
307
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
308
309
310
311
        {
            this.SetClip(renderer);
        }

312
        /// <summary>
Eric Domke's avatar
Eric Domke committed
313
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
314
        /// </summary>
Eric Domke's avatar
Eric Domke committed
315
316
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        void ISvgClipable.ResetClip(ISvgRenderer renderer)
317
318
319
        {
            this.ResetClip(renderer);
        }
320

James Welle's avatar
James Welle committed
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
        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;
        }
346

davescriven's avatar
davescriven committed
347
    }
348
}