SvgVisualElement.cs 12.8 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
15
    {
        private bool _dirty;
        private bool _requiresSmoothRendering;
16
        private Region _previousClip;
davescriven's avatar
davescriven committed
17
18
19
20

        /// <summary>
        /// Gets the <see cref="GraphicsPath"/> for this element.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
21
        public abstract GraphicsPath Path(ISvgRenderer renderer);
James Welle's avatar
James Welle committed
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

        PointF ISvgBoundable.Location
        {
            get
            {
                return Bounds.Location;
            }
        }

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

davescriven's avatar
davescriven committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        /// <summary>
        /// Gets the bounds of the element.
        /// </summary>
        /// <value>The bounds.</value>
        public abstract RectangleF Bounds { get; }

        /// <summary>
        /// Gets or sets a value indicating whether this element's <see cref="Path"/> is dirty.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the path is dirty; otherwise, <c>false</c>.
        /// </value>
        protected virtual bool IsPathDirty
        {
            get { return this._dirty; }
            set { this._dirty = value; }
        }

Eric Domke's avatar
Eric Domke committed
57
58
59
60
61
62
63
64
65
66
        /// <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; }
        }

67
68
69
70
71
72
        /// <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
73
            get { return this.Attributes.GetInheritedAttribute<Uri>("clip-path"); }
74
75
76
            set { this.Attributes["clip-path"] = value; }
        }

77
        /// <summary>
78
        /// Gets or sets the algorithm which is to be used to determine the clipping region.
79
80
81
82
83
84
85
86
        /// </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
87
88
89
90
91
92
        /// <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
93
            get { return this.Attributes.GetInheritedAttribute<Uri>("filter"); }
James Welle's avatar
James Welle committed
94
95
            set { this.Attributes["filter"] = value; }
        }
96

davescriven's avatar
davescriven committed
97
98
99
100
101
102
103
104
105
106
107
        /// <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>
108
        public SvgVisualElement()
davescriven's avatar
davescriven committed
109
110
111
112
113
        {
            this._dirty = true;
            this._requiresSmoothRendering = false;
        }

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

davescriven's avatar
davescriven committed
116
117
118
        /// <summary>
        /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
119
120
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected override void Render(ISvgRenderer renderer)
davescriven's avatar
davescriven committed
121
        {
Eric Domke's avatar
Eric Domke committed
122
123
124
            this.Render(renderer, true);
        }

Eric Domke's avatar
Eric Domke committed
125
        private void Render(ISvgRenderer renderer, bool renderFilter)
Eric Domke's avatar
Eric Domke committed
126
127
128
        {
            if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
                (!Renderable || this.Path(renderer) != null))
davescriven's avatar
davescriven committed
129
            {
Eric Domke's avatar
Eric Domke committed
130
                bool renderNormal = true;
davescriven's avatar
davescriven committed
131

Eric Domke's avatar
Eric Domke committed
132
                if (renderFilter && this.Filter != null)
davescriven's avatar
davescriven committed
133
                {
Eric Domke's avatar
Eric Domke committed
134
135
136
137
138
139
                    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
140
141
142
                    if (filter != null)
                    {
                        this.PopTransforms(renderer);
Eric Domke's avatar
Eric Domke committed
143
144
145
146
147
                        try
                        {
                            filter.ApplyFilter(this, renderer, (r) => this.Render(r, false));
                        }
                        catch (Exception ex) { Debug.Print(ex.ToString()); }
Eric Domke's avatar
Eric Domke committed
148
149
                        renderNormal = false;
                    }
davescriven's avatar
davescriven committed
150
151
                }

152

Eric Domke's avatar
Eric Domke committed
153
                if (renderNormal)
davescriven's avatar
davescriven committed
154
                {
Eric Domke's avatar
Eric Domke committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
                    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);
181
182
183
184
185
186
                }

            }
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
187
        /// Renders the fill of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
188
        /// </summary>
Eric Domke's avatar
Eric Domke committed
189
190
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual void RenderFill(ISvgRenderer renderer)
191
192
193
        {
            if (this.Fill != null)
            {
Eric Domke's avatar
Eric Domke committed
194
                using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
195
196
                {
                    if (brush != null)
davescriven's avatar
davescriven committed
197
                    {
198
199
                        this.Path(renderer).FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
                        renderer.FillPath(brush, this.Path(renderer));
davescriven's avatar
davescriven committed
200
201
                    }
                }
202
203
            }
        }
davescriven's avatar
davescriven committed
204

205
        /// <summary>
Eric Domke's avatar
Eric Domke committed
206
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
207
        /// </summary>
Eric Domke's avatar
Eric Domke committed
208
209
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual void RenderStroke(ISvgRenderer renderer)
210
        {
Eric Domke's avatar
Eric Domke committed
211
            if (this.Stroke != null && this.Stroke != SvgColourServer.None)
212
            {
213
                float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
Eric Domke's avatar
Eric Domke committed
214
                using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
davescriven's avatar
davescriven committed
215
                {
Eric Domke's avatar
Eric Domke committed
216
                    if (brush != null)
davescriven's avatar
davescriven committed
217
                    {
Eric Domke's avatar
Eric Domke committed
218
219
220
221
222
223
224
225
                        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();
                            }
226

Eric Domke's avatar
Eric Domke committed
227
228
229
                            renderer.DrawPath(pen, this.Path(renderer));
                        }
                    }
davescriven's avatar
davescriven committed
230
231
232
                }
            }
        }
233

234
        /// <summary>
Eric Domke's avatar
Eric Domke committed
235
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
236
        /// </summary>
Eric Domke's avatar
Eric Domke committed
237
238
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        protected internal virtual void SetClip(ISvgRenderer renderer)
239
        {
Eric Domke's avatar
Eric Domke committed
240
            if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
241
            {
Eric Domke's avatar
Eric Domke committed
242
                this._previousClip = renderer.GetClip();
243

Eric Domke's avatar
Eric Domke committed
244
245
246
247
248
249
250
251
                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("))
252
                {
Eric Domke's avatar
Eric Domke committed
253
254
255
256
257
258
259
260
                    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);
261
                }
262
263
264
            }
        }

265
        /// <summary>
Eric Domke's avatar
Eric Domke committed
266
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
267
        /// </summary>
Eric Domke's avatar
Eric Domke committed
268
269
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        protected internal virtual void ResetClip(ISvgRenderer renderer)
270
        {
271
            if (this._previousClip != null)
272
            {
Eric Domke's avatar
Eric Domke committed
273
                renderer.SetClip(this._previousClip);
274
275
276
277
                this._previousClip = null;
            }
        }

278
        /// <summary>
Eric Domke's avatar
Eric Domke committed
279
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
280
        /// </summary>
Eric Domke's avatar
Eric Domke committed
281
282
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
283
284
285
286
        {
            this.SetClip(renderer);
        }

287
        /// <summary>
Eric Domke's avatar
Eric Domke committed
288
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
289
        /// </summary>
Eric Domke's avatar
Eric Domke committed
290
291
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        void ISvgClipable.ResetClip(ISvgRenderer renderer)
292
293
294
        {
            this.ResetClip(renderer);
        }
295

James Welle's avatar
James Welle committed
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
        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;
        }
321

davescriven's avatar
davescriven committed
322
    }
323
}