SvgVisualElement.cs 16 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
        /// <summary>
        /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
        /// </summary>
        protected virtual bool RequiresSmoothRendering
        {
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
            get
            {
                if (_requiresSmoothRendering == null)
                    _requiresSmoothRendering = ConvertShapeRendering2AntiAlias(ShapeRendering);

                return _requiresSmoothRendering.Value;
            }
        }

        private bool ConvertShapeRendering2AntiAlias(SvgShapeRendering shapeRendering)
        {
            switch (shapeRendering)
            {
                case SvgShapeRendering.OptimizeSpeed:
                case SvgShapeRendering.CrispEdges:
                case SvgShapeRendering.GeometricPrecision:
                    return false;
                default:
                    // SvgShapeRendering.Auto
                    // SvgShapeRendering.Inherit
                    return true;
            }
davescriven's avatar
davescriven committed
111
112
113
        }

        /// <summary>
114
        /// Initializes a new instance of the <see cref="SvgVisualElement"/> class.
davescriven's avatar
davescriven committed
115
        /// </summary>
116
        public SvgVisualElement()
davescriven's avatar
davescriven committed
117
        {
Eric Domke's avatar
Eric Domke committed
118
            this.IsPathDirty = true;
davescriven's avatar
davescriven committed
119
120
        }

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

davescriven's avatar
davescriven committed
123
124
125
        /// <summary>
        /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
        /// </summary>
Eric Domke's avatar
Eric Domke committed
126
127
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected override void Render(ISvgRenderer renderer)
davescriven's avatar
davescriven committed
128
        {
Eric Domke's avatar
Eric Domke committed
129
130
131
            this.Render(renderer, true);
        }

Eric Domke's avatar
Eric Domke committed
132
        private void Render(ISvgRenderer renderer, bool renderFilter)
Eric Domke's avatar
Eric Domke committed
133
134
135
        {
            if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
                (!Renderable || this.Path(renderer) != null))
davescriven's avatar
davescriven committed
136
            {
Eric Domke's avatar
Eric Domke committed
137
                bool renderNormal = true;
davescriven's avatar
davescriven committed
138

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

159

Eric Domke's avatar
Eric Domke committed
160
                if (renderNormal)
davescriven's avatar
davescriven committed
161
                {
Eric Domke's avatar
Eric Domke committed
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
                    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);
188
189
190
191
192
193
                }

            }
        }

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

212
        /// <summary>
Eric Domke's avatar
Eric Domke committed
213
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
214
        /// </summary>
Eric Domke's avatar
Eric Domke committed
215
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
Eric Domke's avatar
Eric Domke committed
216
        protected internal virtual bool RenderStroke(ISvgRenderer renderer)
217
        {
218
            if (this.Stroke != null && this.Stroke != SvgColourServer.None && this.StrokeWidth > 0)
219
            {
220
                float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
Eric Domke's avatar
Eric Domke committed
221
                using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
davescriven's avatar
davescriven committed
222
                {
Eric Domke's avatar
Eric Domke committed
223
                    if (brush != null)
davescriven's avatar
davescriven committed
224
                    {
Eric Domke's avatar
Eric Domke committed
225
226
227
228
                        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
229
                        {
Eric Domke's avatar
Eric Domke committed
230
                            switch (this.StrokeLineCap)
Eric Domke's avatar
Eric Domke committed
231
                            {
Eric Domke's avatar
Eric Domke committed
232
233
234
235
236
237
238
239
240
241
242
243
244
245
                                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
246
                            }
Eric Domke's avatar
Eric Domke committed
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
                        }
                        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);
284

Eric Domke's avatar
Eric Domke committed
285
286
                                return true;
                            }
Eric Domke's avatar
Eric Domke committed
287
288
                        }
                    }
davescriven's avatar
davescriven committed
289
290
                }
            }
Eric Domke's avatar
Eric Domke committed
291
292

            return false;
davescriven's avatar
davescriven committed
293
        }
294

295
        /// <summary>
Eric Domke's avatar
Eric Domke committed
296
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
297
        /// </summary>
Eric Domke's avatar
Eric Domke committed
298
299
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        protected internal virtual void SetClip(ISvgRenderer renderer)
300
        {
Eric Domke's avatar
Eric Domke committed
301
            if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
302
            {
Eric Domke's avatar
Eric Domke committed
303
                this._previousClip = renderer.GetClip();
304

Eric Domke's avatar
Eric Domke committed
305
306
307
308
309
310
311
312
                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("))
313
                {
Eric Domke's avatar
Eric Domke committed
314
315
316
317
318
319
320
321
                    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);
322
                }
323
324
325
            }
        }

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

339
        /// <summary>
Eric Domke's avatar
Eric Domke committed
340
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
341
        /// </summary>
Eric Domke's avatar
Eric Domke committed
342
343
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
344
345
346
347
        {
            this.SetClip(renderer);
        }

348
        /// <summary>
Eric Domke's avatar
Eric Domke committed
349
        /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
350
        /// </summary>
Eric Domke's avatar
Eric Domke committed
351
352
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
        void ISvgClipable.ResetClip(ISvgRenderer renderer)
353
354
355
        {
            this.ResetClip(renderer);
        }
356

James Welle's avatar
James Welle committed
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
        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;
        }
382

davescriven's avatar
davescriven committed
383
    }
384
}