SvgVisualElement.cs 16.5 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
                        }
                        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();
257
258
259
260
261
262

                                    if (this.StrokeDashOffset != null && this.StrokeDashOffset.Value != 0)
                                    {
                                        pen.DashOffset = ((this.StrokeDashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : this.StrokeDashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this)) /
                                            ((strokeWidth <= 0) ? 1 : strokeWidth);
                                    }
Eric Domke's avatar
Eric Domke committed
263
264
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
                                }
                                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);
290

Eric Domke's avatar
Eric Domke committed
291
292
                                return true;
                            }
Eric Domke's avatar
Eric Domke committed
293
294
                        }
                    }
davescriven's avatar
davescriven committed
295
296
                }
            }
Eric Domke's avatar
Eric Domke committed
297
298

            return false;
davescriven's avatar
davescriven committed
299
        }
300

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

Eric Domke's avatar
Eric Domke committed
311
312
313
314
315
316
317
318
                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("))
319
                {
Eric Domke's avatar
Eric Domke committed
320
321
322
323
324
325
326
327
                    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);
328
                }
329
330
331
            }
        }

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

345
        /// <summary>
Eric Domke's avatar
Eric Domke committed
346
        /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
347
        /// </summary>
Eric Domke's avatar
Eric Domke committed
348
349
        /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
        void ISvgClipable.SetClip(ISvgRenderer renderer)
350
351
352
353
        {
            this.SetClip(renderer);
        }

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

James Welle's avatar
James Welle committed
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
        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;
        }
388

davescriven's avatar
davescriven committed
389
    }
390
}