SvgMarker.cs 12.6 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

        [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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
        [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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
        /// <summary>
        /// If not set set in the marker, consider the attribute in the drawing element.
        /// </summary>
        public override SvgPaintServer Fill
        {
            get
            {
                var path = this.Children.FirstOrDefault(x => x is SvgVisualElement);
                if (path != null)
                {
                    return path.Fill;
                }
                return base.Fill;
            }
        }

        /// <summary>
        /// If not set set in the marker, consider the attribute in the drawing element.
        /// </summary>
        public override SvgPaintServer Stroke
        {
            get {
                var path = this.Children.FirstOrDefault(x => x is SvgVisualElement);
                if (path != null)
                {
                    return path.Stroke;
                }
                return base.Stroke;
            }
        }

James Welle's avatar
James Welle committed
113
114
        public SvgMarker()
        {
115
            MarkerUnits = SvgMarkerUnits.StrokeWidth;
James Welle's avatar
James Welle committed
116
117
            MarkerHeight = 3;
            MarkerWidth = 3;
118
            Overflow = SvgOverflow.Hidden;
James Welle's avatar
James Welle committed
119
120
        }

Eric Domke's avatar
Eric Domke committed
121
        public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer)
122
        {
Eric Domke's avatar
Eric Domke committed
123
            var path = this.Children.FirstOrDefault(x => x is SvgVisualElement);
124
            if (path != null)
Eric Domke's avatar
Eric Domke committed
125
                return (path as SvgVisualElement).Path(renderer);
126
            return null;
127
128
        }

James Welle's avatar
James Welle committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
        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>
152
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
153
154
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
Eric Domke's avatar
Eric Domke committed
155
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
James Welle's avatar
James Welle committed
156
        {
157
158
159
160
161
162
163
164
            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
165
166
167
168
169
170
171
172
173

            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>
174
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
175
176
177
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
        /// <param name="pMarkerPoint3"></param>
Eric Domke's avatar
Eric Domke committed
178
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
James Welle's avatar
James Welle committed
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        {
            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
198
        private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint)
James Welle's avatar
James Welle committed
199
        {
Eric Domke's avatar
Eric Domke committed
200
            using (var pRenderPen = CreatePen(pOwner, pRenderer))
James Welle's avatar
James Welle committed
201
            {
Eric Domke's avatar
Eric Domke committed
202
203
204
205
206
207
208
209
210
211
212
                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)
                        {
213
                            case SvgMarkerUnits.StrokeWidth:
214
215
                                if (ViewBox.Width > 0 && ViewBox.Height > 0)
                                {
mrbean-bremen's avatar
mrbean-bremen committed
216
217
                                    transMatrix.Scale(MarkerWidth, MarkerHeight);
                                    var strokeWidth = pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this);
218
                                    transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) *
mrbean-bremen's avatar
mrbean-bremen committed
219
                                                            strokeWidth),
220
                                                          AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) *
mrbean-bremen's avatar
mrbean-bremen committed
221
                                                            strokeWidth));
222
223
224
225
226
227
228
229
230
231
                                }
                                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
232
                                break;
233
                            case SvgMarkerUnits.UserSpaceOnUse:
Eric Domke's avatar
Eric Domke committed
234
235
236
237
238
                                transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this),
                                                      -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this));
                                break;
                        }
                        markerPath.Transform(transMatrix);
Eric Domke's avatar
Eric Domke committed
239
                        if (pRenderPen != null) pRenderer.DrawPath(pRenderPen, markerPath);
Eric Domke's avatar
Eric Domke committed
240

Eric Domke's avatar
Eric Domke committed
241
                        SvgPaintServer pFill = this.Children.First().Fill;
Eric Domke's avatar
Eric Domke committed
242
243
244
245
246
247
248
249
250
251
252
253
                        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
254
255
256
257
258
259
260
            }
        }

        /// <summary>
        /// Create a pen that can be used to render this marker
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
261
        private Pen CreatePen(SvgVisualElement pPath, ISvgRenderer renderer)
James Welle's avatar
James Welle committed
262
        {
mrbean-bremen's avatar
mrbean-bremen committed
263
264
            if (this.Stroke == null) return null;
            Brush pBrush = this.Stroke.GetBrush(this, renderer, Opacity);
James Welle's avatar
James Welle committed
265
266
            switch (MarkerUnits)
            {
267
                case SvgMarkerUnits.StrokeWidth:
mrbean-bremen's avatar
mrbean-bremen committed
268
269
270
                    // 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)));
271
                case SvgMarkerUnits.UserSpaceOnUse:
272
                    return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
273
            }
274
            return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
275
276
277
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
278
        /// Get a clone of the current path, scaled for the stroke width
James Welle's avatar
James Welle committed
279
280
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
281
        private GraphicsPath GetClone(SvgVisualElement pPath)
James Welle's avatar
James Welle committed
282
        {
283
            GraphicsPath pRet = Path(null).Clone() as GraphicsPath;
James Welle's avatar
James Welle committed
284
285
            switch (MarkerUnits)
            {
286
                case SvgMarkerUnits.StrokeWidth:
Eric Domke's avatar
Eric Domke committed
287
288
289
290
291
                    using (var transMatrix = new Matrix())
                    {
                        transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
                        pRet.Transform(transMatrix);
                    }
James Welle's avatar
James Welle committed
292
                    break;
293
                case SvgMarkerUnits.UserSpaceOnUse:
James Welle's avatar
James Welle committed
294
295
296
297
298
299
300
301
302
303
304
305
306
                    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
307
            return (ViewBox.Width <= 0 ? 1 : fWidth / ViewBox.Width);
James Welle's avatar
James Welle committed
308
309
310
311
312
        }

        /// <summary>
        /// Adjust the given value to account for the height of the viewbox in the viewport
        /// </summary>
313
        /// <param name="fHeight"></param>
James Welle's avatar
James Welle committed
314
315
316
317
        /// <returns></returns>
        private float AdjustForViewBoxHeight(float fHeight)
        {
            //	TODO: We know this isn't correct
Eric Domke's avatar
Eric Domke committed
318
            return (ViewBox.Height <= 0 ? 1 : fHeight / ViewBox.Height);
James Welle's avatar
James Welle committed
319
320
        }
    }
321
}