SvgVisualElement.cs 15.3 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

Tebjan Halm's avatar
Tebjan Halm committed
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
        PointF ISvgBoundable.Location
        {
            get
            {
                return Bounds.Location;
            }
        }

        SizeF ISvgBoundable.Size
        {
            get
            {
                return Bounds.Size;
            }
        }

davescriven's avatar
davescriven committed
38
39
40
        /// <summary>
        /// Gets the bounds of the element.
        /// </summary>
Tebjan Halm's avatar
Tebjan Halm committed
41
42
        /// <value>The bounds.</value>
        public abstract RectangleF Bounds { get; }
davescriven's avatar
davescriven committed
43

Eric Domke's avatar
Eric Domke committed
44
45
46
47
48
49
50
51
52
53
        /// <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; }
        }

54
55
56
57
58
59
        /// <summary>
        /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
        /// </summary>
        [SvgAttribute("clip-path")]
        public virtual Uri ClipPath
        {
60
            get { return this.Attributes.GetAttribute<Uri>("clip-path"); }
61
62
63
            set { this.Attributes["clip-path"] = value; }
        }

64
        /// <summary>
65
        /// Gets or sets the algorithm which is to be used to determine the clipping region.
66
67
68
69
70
71
72
73
        /// </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
74
75
76
77
78
79
        /// <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
80
            get { return this.Attributes.GetInheritedAttribute<Uri>("filter"); }
James Welle's avatar
James Welle committed
81
82
            set { this.Attributes["filter"] = value; }
        }
83

davescriven's avatar
davescriven committed
84
85
86
87
88
89
90
91
92
93
94
        /// <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>
95
        public SvgVisualElement()
davescriven's avatar
davescriven committed
96
        {
Eric Domke's avatar
Eric Domke committed
97
            this.IsPathDirty = true;
davescriven's avatar
davescriven committed
98
99
100
            this._requiresSmoothRendering = false;
        }

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

davescriven's avatar
davescriven committed
103
104
105
        /// <summary>
        /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
106
107
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected override void Render(ISvgRenderer renderer)
davescriven's avatar
davescriven committed
108
        {
Eric Domke's avatar
Eric Domke committed
109
110
111
            this.Render(renderer, true);
        }

Eric Domke's avatar
Eric Domke committed
112
        private void Render(ISvgRenderer renderer, bool renderFilter)
Eric Domke's avatar
Eric Domke committed
113
114
115
        {
            if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
                (!Renderable || this.Path(renderer) != null))
davescriven's avatar
davescriven committed
116
            {
Eric Domke's avatar
Eric Domke committed
117
                bool renderNormal = true;
davescriven's avatar
davescriven committed
118

Eric Domke's avatar
Eric Domke committed
119
                if (renderFilter && this.Filter != null)
davescriven's avatar
davescriven committed
120
                {
Eric Domke's avatar
Eric Domke committed
121
122
123
124
125
126
                    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
127
128
129
                    if (filter != null)
                    {
                        this.PopTransforms(renderer);
Eric Domke's avatar
Eric Domke committed
130
131
132
133
134
                        try
                        {
                            filter.ApplyFilter(this, renderer, (r) => this.Render(r, false));
                        }
                        catch (Exception ex) { Debug.Print(ex.ToString()); }
Eric Domke's avatar
Eric Domke committed
135
136
                        renderNormal = false;
                    }
davescriven's avatar
davescriven committed
137
138
                }

139

Eric Domke's avatar
Eric Domke committed
140
                if (renderNormal)
davescriven's avatar
davescriven committed
141
                {
Eric Domke's avatar
Eric Domke committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
                    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);
168
169
170
171
172
173
                }

            }
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
174
        /// Renders the fill of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
175
        /// </summary>
Eric Domke's avatar
Eric Domke committed
176
177
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual void RenderFill(ISvgRenderer renderer)
178
179
180
        {
            if (this.Fill != null)
            {
Eric Domke's avatar
Eric Domke committed
181
                using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
182
183
                {
                    if (brush != null)
davescriven's avatar
davescriven committed
184
                    {
185
186
                        this.Path(renderer).FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
                        renderer.FillPath(brush, this.Path(renderer));
davescriven's avatar
davescriven committed
187
188
                    }
                }
189
190
            }
        }
davescriven's avatar
davescriven committed
191

192
        /// <summary>
Eric Domke's avatar
Eric Domke committed
193
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
194
        /// </summary>
Eric Domke's avatar
Eric Domke committed
195
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
Eric Domke's avatar
Eric Domke committed
196
        protected internal virtual bool RenderStroke(ISvgRenderer renderer)
197
        {
Eric Domke's avatar
Eric Domke committed
198
            if (this.Stroke != null && this.Stroke != SvgColourServer.None)
199
            {
200
                float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
Eric Domke's avatar
Eric Domke committed
201
                using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
davescriven's avatar
davescriven committed
202
                {
Eric Domke's avatar
Eric Domke committed
203
                    if (brush != null)
davescriven's avatar
davescriven committed
204
                    {
Eric Domke's avatar
Eric Domke committed
205
206
207
208
                        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
209
                        {
Eric Domke's avatar
Eric Domke committed
210
                            switch (this.StrokeLineCap)
Eric Domke's avatar
Eric Domke committed
211
                            {
Eric Domke's avatar
Eric Domke committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
                                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
226
                            }
Eric Domke's avatar
Eric Domke committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
                        }
                        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);
264

Eric Domke's avatar
Eric Domke committed
265
266
                                return true;
                            }
Eric Domke's avatar
Eric Domke committed
267
268
                        }
                    }
davescriven's avatar
davescriven committed
269
270
                }
            }
Eric Domke's avatar
Eric Domke committed
271
272

            return false;
davescriven's avatar
davescriven committed
273
        }
274

275
        /// <summary>
Eric Domke's avatar
Eric Domke committed
276
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
277
        /// </summary>
Eric Domke's avatar
Eric Domke committed
278
279
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        protected internal virtual void SetClip(ISvgRenderer renderer)
280
        {
Eric Domke's avatar
Eric Domke committed
281
            if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
282
            {
Eric Domke's avatar
Eric Domke committed
283
                this._previousClip = renderer.GetClip();
284

Eric Domke's avatar
Eric Domke committed
285
286
287
288
289
290
291
292
                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("))
293
                {
Eric Domke's avatar
Eric Domke committed
294
295
296
297
298
299
300
301
                    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);
302
                }
303
304
305
            }
        }

306
        /// <summary>
Eric Domke's avatar
Eric Domke committed
307
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
308
        /// </summary>
Eric Domke's avatar
Eric Domke committed
309
310
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        protected internal virtual void ResetClip(ISvgRenderer renderer)
311
        {
312
            if (this._previousClip != null)
313
            {
Eric Domke's avatar
Eric Domke committed
314
                renderer.SetClip(this._previousClip);
315
316
317
318
                this._previousClip = null;
            }
        }

319
        /// <summary>
Eric Domke's avatar
Eric Domke committed
320
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
321
        /// </summary>
Eric Domke's avatar
Eric Domke committed
322
323
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
324
325
326
327
        {
            this.SetClip(renderer);
        }

328
        /// <summary>
Eric Domke's avatar
Eric Domke committed
329
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
330
        /// </summary>
Eric Domke's avatar
Eric Domke committed
331
332
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        void ISvgClipable.ResetClip(ISvgRenderer renderer)
333
334
335
        {
            this.ResetClip(renderer);
        }
336

James Welle's avatar
James Welle committed
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        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;
        }
362

davescriven's avatar
davescriven committed
363
    }
364
}