SvgVisualElement.cs 11.2 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;
davescriven's avatar
davescriven committed
5
6
7
8
9
10

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
11
    public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
davescriven's avatar
davescriven committed
12
13
14
    {
        private bool _dirty;
        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
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
        /// <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; }
        }

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

66
        /// <summary>
67
        /// Gets or sets the algorithm which is to be used to determine the clipping region.
68
69
70
71
72
73
74
75
        /// </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
76
77
78
79
80
81
82
83
84
        /// <summary>
        /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
        /// </summary>
        [SvgAttribute("filter")]
        public virtual Uri Filter
        {
            get { return this.Attributes.GetAttribute<Uri>("filter"); }
            set { this.Attributes["filter"] = value; }
        }
85

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

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

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

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

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

141

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

            }
        }

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

194
        /// <summary>
Eric Domke's avatar
Eric Domke committed
195
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
196
        /// </summary>
Eric Domke's avatar
Eric Domke committed
197
198
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual void RenderStroke(ISvgRenderer renderer)
199
        {
Eric Domke's avatar
Eric Domke committed
200
            if (this.Stroke != null && this.Stroke != SvgColourServer.None)
201
            {
202
203
                float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
                using (var pen = new Pen(this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1)), strokeWidth))
davescriven's avatar
davescriven committed
204
                {
205
                    if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0)
davescriven's avatar
davescriven committed
206
                    {
207
                        /* divide by stroke width - GDI behaviour that I don't quite understand yet.*/
208
                        pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.Value <= 0) ? 1 : u.Value) / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
209
                    }
210

211
                    renderer.DrawPath(pen, this.Path(renderer));
davescriven's avatar
davescriven committed
212
213
214
                }
            }
        }
215

216
        /// <summary>
Eric Domke's avatar
Eric Domke committed
217
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
218
        /// </summary>
Eric Domke's avatar
Eric Domke committed
219
220
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        protected internal virtual void SetClip(ISvgRenderer renderer)
221
222
223
224
        {
            if (this.ClipPath != null)
            {
                SvgClipPath clipPath = this.OwnerDocument.GetElementById<SvgClipPath>(this.ClipPath.ToString());
Eric Domke's avatar
Eric Domke committed
225
                this._previousClip = renderer.GetClip();
226
227
228

                if (clipPath != null)
                {
Eric Domke's avatar
Eric Domke committed
229
                    renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect);
230
                }
231
232
233
            }
        }

234
        /// <summary>
Eric Domke's avatar
Eric Domke committed
235
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
236
        /// </summary>
Eric Domke's avatar
Eric Domke committed
237
238
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        protected internal virtual void ResetClip(ISvgRenderer renderer)
239
        {
240
            if (this._previousClip != null)
241
            {
Eric Domke's avatar
Eric Domke committed
242
                renderer.SetClip(this._previousClip);
243
244
245
246
                this._previousClip = null;
            }
        }

247
        /// <summary>
Eric Domke's avatar
Eric Domke committed
248
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
249
        /// </summary>
Eric Domke's avatar
Eric Domke committed
250
251
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
252
253
254
255
        {
            this.SetClip(renderer);
        }

256
        /// <summary>
Eric Domke's avatar
Eric Domke committed
257
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
258
        /// </summary>
Eric Domke's avatar
Eric Domke committed
259
260
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        void ISvgClipable.ResetClip(ISvgRenderer renderer)
261
262
263
        {
            this.ResetClip(renderer);
        }
264

James Welle's avatar
James Welle committed
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
        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;
        }
290

davescriven's avatar
davescriven committed
291
    }
292
}