SvgMarker.cs 12 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
10
11

namespace Svg
{
    [SvgElement("marker")]
    public class SvgMarker : SvgVisualElement, ISvgViewPort
    {
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
82
83
        [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; }
        }

        public SvgMarker()
        {
84
            MarkerUnits = SvgMarkerUnits.StrokeWidth;
James Welle's avatar
James Welle committed
85
86
            MarkerHeight = 3;
            MarkerWidth = 3;
87
            Overflow = SvgOverflow.Hidden;
James Welle's avatar
James Welle committed
88
89
        }

Eric Domke's avatar
Eric Domke committed
90
        public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer)
91
        {
Eric Domke's avatar
Eric Domke committed
92
            var path = this.Children.FirstOrDefault(x => x is SvgVisualElement);
93
            if (path != null)
Eric Domke's avatar
Eric Domke committed
94
                return (path as SvgVisualElement).Path(renderer);
95
            return null;
96
97
        }

Tebjan Halm's avatar
Tebjan Halm committed
98
        public override System.Drawing.RectangleF Bounds
99
        {
Tebjan Halm's avatar
Tebjan Halm committed
100
101
102
103
            get
            {
                var path = this.Path(null);
                if (path != null)
104
105
106
107
108
                {
                    if (Transforms != null && Transforms.Count > 0)
                    {
                        path.Transform(Transforms.GetMatrix());
                    }
Tebjan Halm's avatar
Tebjan Halm committed
109
                    return path.GetBounds();
110
111
                }
                return new RectangleF();
Tebjan Halm's avatar
Tebjan Halm committed
112
            }
113
114
        }

James Welle's avatar
James Welle committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
        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>
138
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
139
140
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
Eric Domke's avatar
Eric Domke committed
141
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
James Welle's avatar
James Welle committed
142
        {
143
144
145
146
147
148
149
150
            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
151
152
153
154
155
156
157
158
159

            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>
160
        /// <param name="pRefPoint"></param>
James Welle's avatar
James Welle committed
161
162
163
        /// <param name="pMarkerPoint1"></param>
        /// <param name="pMarkerPoint2"></param>
        /// <param name="pMarkerPoint3"></param>
Eric Domke's avatar
Eric Domke committed
164
        public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
James Welle's avatar
James Welle committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
        {
            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
184
        private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint)
James Welle's avatar
James Welle committed
185
        {
Eric Domke's avatar
Eric Domke committed
186
            using (var pRenderPen = CreatePen(pOwner, pRenderer))
James Welle's avatar
James Welle committed
187
            {
Eric Domke's avatar
Eric Domke committed
188
189
190
191
192
193
194
195
196
197
198
                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)
                        {
199
                            case SvgMarkerUnits.StrokeWidth:
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
                                if (ViewBox.Width > 0 && ViewBox.Height > 0)
                                {
                                    transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) *
                                                            pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this)),
                                                          AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) *
                                                            pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this)));
                                }
                                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
216
                                break;
217
                            case SvgMarkerUnits.UserSpaceOnUse:
Eric Domke's avatar
Eric Domke committed
218
219
220
221
222
                                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
223
                        if (pRenderPen != null) pRenderer.DrawPath(pRenderPen, markerPath);
Eric Domke's avatar
Eric Domke committed
224

Eric Domke's avatar
Eric Domke committed
225
                        SvgPaintServer pFill = this.Children.First().Fill;
Eric Domke's avatar
Eric Domke committed
226
227
228
229
230
231
232
233
234
235
236
237
                        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
238
239
240
241
242
243
244
            }
        }

        /// <summary>
        /// Create a pen that can be used to render this marker
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
245
        private Pen CreatePen(SvgVisualElement pPath, ISvgRenderer renderer)
James Welle's avatar
James Welle committed
246
        {
Eric Domke's avatar
Eric Domke committed
247
            if (pPath.Stroke == null) return null;
248
            Brush pBrush = pPath.Stroke.GetBrush(this, renderer, Opacity);
James Welle's avatar
James Welle committed
249
250
            switch (MarkerUnits)
            {
251
                case SvgMarkerUnits.StrokeWidth:
252
253
                    return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this) * 
                                            pPath.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
254
                case SvgMarkerUnits.UserSpaceOnUse:
255
                    return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
256
            }
257
            return (new Pen(pBrush, StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this)));
James Welle's avatar
James Welle committed
258
259
260
        }

        /// <summary>
Eric Domke's avatar
Eric Domke committed
261
        /// Get a clone of the current path, scaled for the stroke width
James Welle's avatar
James Welle committed
262
263
        /// </summary>
        /// <returns></returns>
Eric Domke's avatar
Eric Domke committed
264
        private GraphicsPath GetClone(SvgVisualElement pPath)
James Welle's avatar
James Welle committed
265
        {
266
            GraphicsPath pRet = Path(null).Clone() as GraphicsPath;
James Welle's avatar
James Welle committed
267
268
            switch (MarkerUnits)
            {
269
                case SvgMarkerUnits.StrokeWidth:
Eric Domke's avatar
Eric Domke committed
270
271
272
273
274
                    using (var transMatrix = new Matrix())
                    {
                        transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
                        pRet.Transform(transMatrix);
                    }
James Welle's avatar
James Welle committed
275
                    break;
276
                case SvgMarkerUnits.UserSpaceOnUse:
James Welle's avatar
James Welle committed
277
278
279
280
281
282
283
284
285
286
287
288
289
                    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
290
            return (ViewBox.Width <= 0 ? 1 : fWidth / ViewBox.Width);
James Welle's avatar
James Welle committed
291
292
293
294
295
        }

        /// <summary>
        /// Adjust the given value to account for the height of the viewbox in the viewport
        /// </summary>
296
        /// <param name="fHeight"></param>
James Welle's avatar
James Welle committed
297
298
299
300
        /// <returns></returns>
        private float AdjustForViewBoxHeight(float fHeight)
        {
            //	TODO: We know this isn't correct
Eric Domke's avatar
Eric Domke committed
301
            return (ViewBox.Height <= 0 ? 1 : fHeight / ViewBox.Height);
James Welle's avatar
James Welle committed
302
303
        }
    }
304
}