SvgMarker.cs 13.1 KB
Newer Older
1
using System;
2
using System.Drawing;
James Welle's avatar
James Welle committed
3
4
using System.Drawing.Drawing2D;
using System.Linq;
5
using Svg.DataTypes;
6
7
8
9

namespace Svg
{
    [SvgElement("marker")]
mrbean-bremen's avatar
mrbean-bremen committed
10
    public class SvgMarker : SvgPathBasedElement, ISvgViewPort
11
    {
James Welle's avatar
James Welle committed
12
        private SvgOrient _svgOrient = new SvgOrient();
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
        private SvgVisualElement _markerElement = null;

        /// <summary>
        /// Return the child element that represent the marker
        /// </summary>
        private SvgVisualElement MarkerElement
        {
            get
            {
                if (_markerElement == null)
                {
                    _markerElement = (SvgVisualElement)this.Children.FirstOrDefault(x => x is SvgVisualElement);
                }
                return _markerElement;
            }
        }
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

        [SvgAttribute("refX")]
        public virtual SvgUnit RefX
        {
            get { return this.Attributes.GetAttribute<SvgUnit>("refX"); }
            set { this.Attributes["refX"] = value; }
        }

        [SvgAttribute("refY")]
        public virtual SvgUnit RefY
        {
            get { return this.Attributes.GetAttribute<SvgUnit>("refY"); }
            set { this.Attributes["refY"] = value; }
        }


James Welle's avatar
James Welle committed
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
        [SvgAttribute("orient")]
        public virtual SvgOrient Orient
        {
            get { return _svgOrient; }
            set { _svgOrient = value; }
        }


        [SvgAttribute("overflow")]
        public virtual SvgOverflow Overflow
        {
            get { return this.Attributes.GetAttribute<SvgOverflow>("overflow"); }
            set { this.Attributes["overflow"] = value; }
        }


        [SvgAttribute("viewBox")]
        public virtual SvgViewBox ViewBox
        {
            get { return this.Attributes.GetAttribute<SvgViewBox>("viewBox"); }
            set { this.Attributes["viewBox"] = value; }
        }


        [SvgAttribute("preserveAspectRatio")]
        public virtual SvgAspectRatio AspectRatio
        {
            get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
            set { this.Attributes["preserveAspectRatio"] = value; }
        }


        [SvgAttribute("markerWidth")]
        public virtual SvgUnit MarkerWidth
        {
            get { return this.Attributes.GetAttribute<SvgUnit>("markerWidth"); }
            set { this.Attributes["markerWidth"] = value; }
        }

        [SvgAttribute("markerHeight")]
        public virtual SvgUnit MarkerHeight
        {
            get { return this.Attributes.GetAttribute<SvgUnit>("markerHeight"); }
            set { this.Attributes["markerHeight"] = value; }
        }

        [SvgAttribute("markerUnits")]
        public virtual SvgMarkerUnits MarkerUnits
        {
            get { return this.Attributes.GetAttribute<SvgMarkerUnits>("markerUnits"); }
            set { this.Attributes["markerUnits"] = value; }
        }

mrbean-bremen's avatar
mrbean-bremen committed
98
99
100
101
102
103
104
        /// <summary>
        /// If not set set in the marker, consider the attribute in the drawing element.
        /// </summary>
        public override SvgPaintServer Fill
        {
            get
            {
105
106
                if (MarkerElement != null)
                    return MarkerElement.Fill;
mrbean-bremen's avatar
mrbean-bremen committed
107
108
109
110
111
112
113
114
115
                return base.Fill;
            }
        }

        /// <summary>
        /// If not set set in the marker, consider the attribute in the drawing element.
        /// </summary>
        public override SvgPaintServer Stroke
        {
116
117
118
119
            get
            {
                if (MarkerElement != null)
                    return MarkerElement.Stroke;
mrbean-bremen's avatar
mrbean-bremen committed
120
121
122
123
                return base.Stroke;
            }
        }

James Welle's avatar
James Welle committed
124
125
        public SvgMarker()
        {
126
            MarkerUnits = SvgMarkerUnits.StrokeWidth;
James Welle's avatar
James Welle committed
127
128
            MarkerHeight = 3;
            MarkerWidth = 3;
129
            Overflow = SvgOverflow.Hidden;
James Welle's avatar
James Welle committed
130
131
        }

Eric Domke's avatar
Eric Domke committed
132
        public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer)
133
        {
134
135
            if (MarkerElement != null)
                return MarkerElement.Path(renderer);
136
            return null;
137
138
        }

James Welle's avatar
James Welle committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
        public override SvgElement DeepCopy()
        {
            return DeepCopy<SvgMarker>();
        }

        public override SvgElement DeepCopy<T>()
        {
            var newObj = base.DeepCopy<T>() as SvgMarker;
            newObj.RefX = this.RefX;
            newObj.RefY = this.RefY;
            newObj.Orient = this.Orient;
            newObj.ViewBox = this.ViewBox;
            newObj.Overflow = this.Overflow;
            newObj.AspectRatio = this.AspectRatio;

            return newObj;
        }

        /// <summary>
        /// Render this marker using the slope of the given line segment
        /// </summary>
        /// <param name="pRenderer"></param>
        /// <param name="pOwner"></param>
162
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
163
164
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
Eric Domke's avatar
Eric Domke committed
165
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
James Welle's avatar
James Welle committed
166
        {
167
168
169
170
171
172
173
174
            float fAngle1 = 0f;
            if (Orient.IsAuto)
            {
                // Only calculate this if needed.
                float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
                float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
                fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
            }
James Welle's avatar
James Welle committed
175
176
177
178
179
180
181
182
183

            RenderPart2(fAngle1, pRenderer, pOwner, pRefPoint);
        }

        /// <summary>
        /// Render this marker using the average of the slopes of the two given line segments
        /// </summary>
        /// <param name="pRenderer"></param>
        /// <param name="pOwner"></param>
184
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
185
186
187
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
        /// <param name="pMarkerPoint3"></param>
Eric Domke's avatar
Eric Domke committed
188
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
James Welle's avatar
James Welle committed
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
        {
            float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
            float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
            float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);

            xDiff = pMarkerPoint3.X - pMarkerPoint2.X;
            yDiff = pMarkerPoint3.Y - pMarkerPoint2.Y;
            float fAngle2 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);

            RenderPart2((fAngle1 + fAngle2) / 2, pRenderer, pOwner, pRefPoint);
        }

        /// <summary>
        /// Common code for rendering a marker once the orientation angle has been calculated
        /// </summary>
        /// <param name="fAngle"></param>
        /// <param name="pRenderer"></param>
        /// <param name="pOwner"></param>
        /// <param name="pMarkerPoint"></param>
Eric Domke's avatar
Eric Domke committed
208
        private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint)
James Welle's avatar
James Welle committed
209
        {
Eric Domke's avatar
Eric Domke committed
210
            using (var pRenderPen = CreatePen(pOwner, pRenderer))
James Welle's avatar
James Welle committed
211
            {
Eric Domke's avatar
Eric Domke committed
212
213
214
215
216
217
218
219
220
221
222
                using (var markerPath = GetClone(pOwner))
                {
                    using (var transMatrix = new Matrix())
                    {
                        transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y);
                        if (Orient.IsAuto)
                            transMatrix.Rotate(fAngle);
                        else
                            transMatrix.Rotate(Orient.Angle);
                        switch (MarkerUnits)
                        {
223
                            case SvgMarkerUnits.StrokeWidth:
224
225
                                if (ViewBox.Width > 0 && ViewBox.Height > 0)
                                {
mrbean-bremen's avatar
mrbean-bremen committed
226
227
                                    transMatrix.Scale(MarkerWidth, MarkerHeight);
                                    var strokeWidth = pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this);
228
                                    transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) *
mrbean-bremen's avatar
mrbean-bremen committed
229
                                                            strokeWidth),
230
                                                          AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) *
mrbean-bremen's avatar
mrbean-bremen committed
231
                                                            strokeWidth));
232
233
234
235
236
237
238
239
240
241
                                }
                                else
                                {
                                    // SvgMarkerUnits.UserSpaceOnUse
                                    //	TODO: We know this isn't correct.
                                    //        But use this until the TODOs from AdjustForViewBoxWidth and AdjustForViewBoxHeight are done.
                                    //  MORE see Unit Test "MakerEndTest.TestArrowCodeCreation()"
                                    transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this),
                                                         -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this));
                                }
Eric Domke's avatar
Eric Domke committed
242
                                break;
243
                            case SvgMarkerUnits.UserSpaceOnUse:
Eric Domke's avatar
Eric Domke committed
244
245
246
247
                                transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this),
                                                      -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this));
                                break;
                        }
248
249
250
251
252
253
254
255

                        if (MarkerElement != null && MarkerElement.Transforms != null)
                        {
                            foreach (var transformation in MarkerElement.Transforms)
                            {
                                transMatrix.Multiply(transformation.Matrix);
                            }
                        }
Eric Domke's avatar
Eric Domke committed
256
                        markerPath.Transform(transMatrix);
Eric Domke's avatar
Eric Domke committed
257
                        if (pRenderPen != null) pRenderer.DrawPath(pRenderPen, markerPath);
Eric Domke's avatar
Eric Domke committed
258

Eric Domke's avatar
Eric Domke committed
259
                        SvgPaintServer pFill = this.Children.First().Fill;
Eric Domke's avatar
Eric Domke committed
260
261
262
263
264
265
266
267
268
269
270
271
                        SvgFillRule pFillRule = FillRule;								// TODO: What do we use the fill rule for?
                        float fOpacity = FillOpacity;

                        if (pFill != null)
                        {
                            using (var pBrush = pFill.GetBrush(this, pRenderer, fOpacity))
                            {
                                pRenderer.FillPath(pBrush, markerPath);
                            }
                        }
                    }
                }
James Welle's avatar
James Welle committed
272
273
274
275
276
277
278
            }
        }

        /// <summary>
        /// Create a pen that can be used to render this marker
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
279
        private Pen CreatePen(SvgVisualElement pPath, ISvgRenderer renderer)
James Welle's avatar
James Welle committed
280
        {
mrbean-bremen's avatar
mrbean-bremen committed
281
282
            if (this.Stroke == null) return null;
            Brush pBrush = this.Stroke.GetBrush(this, renderer, Opacity);
James Welle's avatar
James Welle committed
283
284
            switch (MarkerUnits)
            {
285
                case SvgMarkerUnits.StrokeWidth:
mrbean-bremen's avatar
mrbean-bremen committed
286
287
288
                    // TODO: have to multiply with marker stroke width if it is not inherted from the
                    // same ancestor as owner path stroke width
                    return (new Pen(pBrush, pPath.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
289
                case SvgMarkerUnits.UserSpaceOnUse:
290
                    return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
291
            }
292
            return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
293
294
295
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
296
        /// Get a clone of the current path, scaled for the stroke width
James Welle's avatar
James Welle committed
297
298
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
299
        private GraphicsPath GetClone(SvgVisualElement pPath)
James Welle's avatar
James Welle committed
300
        {
301
            GraphicsPath pRet = Path(null).Clone() as GraphicsPath;
James Welle's avatar
James Welle committed
302
303
            switch (MarkerUnits)
            {
304
                case SvgMarkerUnits.StrokeWidth:
Eric Domke's avatar
Eric Domke committed
305
306
307
308
309
                    using (var transMatrix = new Matrix())
                    {
                        transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
                        pRet.Transform(transMatrix);
                    }
James Welle's avatar
James Welle committed
310
                    break;
311
                case SvgMarkerUnits.UserSpaceOnUse:
James Welle's avatar
James Welle committed
312
313
314
315
316
317
318
319
320
321
322
323
324
                    break;
            }
            return (pRet);
        }

        /// <summary>
        /// Adjust the given value to account for the width of the viewbox in the viewport
        /// </summary>
        /// <param name="fWidth"></param>
        /// <returns></returns>
        private float AdjustForViewBoxWidth(float fWidth)
        {
            //	TODO: We know this isn't correct
Eric Domke's avatar
Eric Domke committed
325
            return (ViewBox.Width <= 0 ? 1 : fWidth / ViewBox.Width);
James Welle's avatar
James Welle committed
326
327
328
329
330
        }

        /// <summary>
        /// Adjust the given value to account for the height of the viewbox in the viewport
        /// </summary>
331
        /// <param name="fHeight"></param>
James Welle's avatar
James Welle committed
332
333
334
335
        /// <returns></returns>
        private float AdjustForViewBoxHeight(float fHeight)
        {
            //	TODO: We know this isn't correct
Eric Domke's avatar
Eric Domke committed
336
            return (ViewBox.Height <= 0 ? 1 : fHeight / ViewBox.Height);
James Welle's avatar
James Welle committed
337
338
        }
    }
339
}