SvgDocument.cs 15.2 KB
Newer Older
davescriven's avatar
davescriven committed
1
2
3
4
5
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
6
7
using System.Drawing.Drawing2D;
using System.Drawing.Text;
davescriven's avatar
davescriven committed
8
9
10
using System.IO;
using System.Text;
using System.Xml;
Tebjan Halm's avatar
Tebjan Halm committed
11
12
using System.Threading;
using System.Globalization;
davescriven's avatar
davescriven committed
13
14
15
16

namespace Svg
{
    /// <summary>
17
    /// The class used to create and load SVG documents.
davescriven's avatar
davescriven committed
18
19
20
21
    /// </summary>
    public class SvgDocument : SvgFragment, ITypeDescriptorContext
    {
        public static readonly int PPI = 96;
22

davescriven's avatar
davescriven committed
23
24
25
26
27
        /// <summary>
        /// Gets a <see cref="string"/> containing the XLink namespace (http://www.w3.org/1999/xlink).
        /// </summary>
        public static readonly string XLinkNamespace = "http://www.w3.org/1999/xlink";

28
29
        private SvgElementIdManager _idManager;

30
31
32
        /// <summary>
        /// Initializes a new instance of the <see cref="SvgDocument"/> class.
        /// </summary>
33
        public SvgDocument()
davescriven's avatar
davescriven committed
34
        {
35
            Ppi = 96;
davescriven's avatar
davescriven committed
36
37
38
39
40
41
42
43
44
        }

        /// <summary>
        /// Gets an <see cref="SvgElementIdManager"/> for this document.
        /// </summary>
        protected internal virtual SvgElementIdManager IdManager
        {
            get
            {
45
                if (_idManager == null)
46
                {
47
                    _idManager = new SvgElementIdManager(this);
48
                }
davescriven's avatar
davescriven committed
49

50
                return _idManager;
davescriven's avatar
davescriven committed
51
52
53
            }
        }

54
55
56
        /// <summary>
        /// Gets or sets the Pixels Per Inch of the rendered image.
        /// </summary>
57
58
59
60
61
        public int Ppi { get; set; }

        #region ITypeDescriptorContext Members

        IContainer ITypeDescriptorContext.Container
davescriven's avatar
davescriven committed
62
        {
63
            get { throw new NotImplementedException(); }
davescriven's avatar
davescriven committed
64
65
        }

66
        object ITypeDescriptorContext.Instance
davescriven's avatar
davescriven committed
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
98
99
100
            get { return this; }
        }

        void ITypeDescriptorContext.OnComponentChanged()
        {
            throw new NotImplementedException();
        }

        bool ITypeDescriptorContext.OnComponentChanging()
        {
            throw new NotImplementedException();
        }

        PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
        {
            get { throw new NotImplementedException(); }
        }

        object IServiceProvider.GetService(Type serviceType)
        {
            throw new NotImplementedException();
        }

        #endregion

        /// <summary>
        /// Retrieves the <see cref="SvgElement"/> with the specified ID.
        /// </summary>
        /// <param name="id">A <see cref="string"/> containing the ID of the element to find.</param>
        /// <returns>An <see cref="SvgElement"/> of one exists with the specified ID; otherwise false.</returns>
        public virtual SvgElement GetElementById(string id)
        {
            return IdManager.GetElementById(id);
davescriven's avatar
davescriven committed
101
102
        }

103
104
105
106
107
108
109
110
111
112
        /// <summary>
        /// Retrieves the <see cref="SvgElement"/> with the specified ID.
        /// </summary>
        /// <param name="id">A <see cref="string"/> containing the ID of the element to find.</param>
        /// <returns>An <see cref="SvgElement"/> of one exists with the specified ID; otherwise false.</returns>
        public virtual TSvgElement GetElementById<TSvgElement>(string id) where TSvgElement : SvgElement
        {
            return (this.GetElementById(id) as TSvgElement);
        }

davescriven's avatar
davescriven committed
113
        /// <summary>
114
        /// Opens the document at the specified path and loads the SVG contents.
davescriven's avatar
davescriven committed
115
116
117
118
119
        /// </summary>
        /// <param name="path">A <see cref="string"/> containing the path of the file to open.</param>
        /// <returns>An <see cref="SvgDocument"/> with the contents loaded.</returns>
        /// <exception cref="FileNotFoundException">The document at the specified <paramref name="path"/> cannot be found.</exception>
        public static SvgDocument Open(string path)
120
121
122
123
        {
            return Open(path, null);
        }

124
        /// <summary>
125
        /// Opens the document at the specified path and loads the SVG contents.
126
127
128
129
        /// </summary>
        /// <param name="path">A <see cref="string"/> containing the path of the file to open.</param>
        /// <param name="entities">A dictionary of custom entity definitions to be used when resolving XML entities within the document.</param>
        /// <returns>An <see cref="SvgDocument"/> with the contents loaded.</returns>
130
        /// <exception cref="FileNotFoundException">The document at the specified <paramref name="path"/> cannot be found.</exception>
131
        public static SvgDocument Open(string path, Dictionary<string, string> entities)
davescriven's avatar
davescriven committed
132
        {
133
134
135
136
137
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

davescriven's avatar
davescriven committed
138
            if (!File.Exists(path))
139
            {
davescriven's avatar
davescriven committed
140
                throw new FileNotFoundException("The specified document cannot be found.", path);
141
            }
davescriven's avatar
davescriven committed
142

143
            return Open(File.OpenRead(path), entities);
davescriven's avatar
davescriven committed
144
145
        }

146
147
148
149
        /// <summary>
        /// Attempts to open an SVG document from the specified <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the SVG document to open.</param>
davescriven's avatar
davescriven committed
150
151
152
153
154
        public static SvgDocument Open(Stream stream)
        {
            return Open(stream, null);
        }

155
        /// <summary>
156
        /// Opens an SVG document from the specified <see cref="Stream"/> and adds the specified entities.
157
158
159
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the SVG document to open.</param>
        /// <param name="entities">Custom entity definitions.</param>
160
        /// <exception cref="ArgumentNullException">The <paramref name="stream"/> parameter cannot be <c>null</c>.</exception>
161
        public static SvgDocument Open(Stream stream, Dictionary<string, string> entities)
davescriven's avatar
davescriven committed
162
        {
163
164
165
166
167
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

168
            //Trace.TraceInformation("Begin Read");
davescriven's avatar
davescriven committed
169

170
            using (var reader = new SvgTextReader(stream, entities))
davescriven's avatar
davescriven committed
171
            {
172
173
                var elementStack = new Stack<SvgElement>();
                var value = new StringBuilder();
174
                bool elementEmpty;
davescriven's avatar
davescriven committed
175
                SvgElement element = null;
176
                SvgElement parent;
davescriven's avatar
davescriven committed
177
178
179
180
181
182
183
184
185
186
187
188
189
                SvgDocument svgDocument = null;
                reader.XmlResolver = new SvgDtdResolver();
                reader.WhitespaceHandling = WhitespaceHandling.None;

                while (reader.Read())
                {
                    try
                    {
                        switch (reader.NodeType)
                        {
                            case XmlNodeType.Element:
                                // Does this element have a value or children
                                // (Must do this check here before we progress to another node)
190
                                elementEmpty = reader.IsEmptyElement;
davescriven's avatar
davescriven committed
191
192
                                // Create element
                                if (elementStack.Count > 0)
193
                                {
davescriven's avatar
davescriven committed
194
                                    element = SvgElementFactory.CreateElement(reader, svgDocument);
195
                                }
davescriven's avatar
davescriven committed
196
197
198
                                else
                                {
                                    element = SvgElementFactory.CreateDocument(reader);
199
                                    svgDocument = (SvgDocument)element;
davescriven's avatar
davescriven committed
200
201
202
                                }

                                if (element == null)
203
                                {
davescriven's avatar
davescriven committed
204
                                    continue;
205
                                }
davescriven's avatar
davescriven committed
206
207
208
209
210
211
212
213
214
215
216
217

                                // Add to the parents children
                                if (elementStack.Count > 0)
                                {
                                    parent = elementStack.Peek();
                                    parent.Children.Add(element);
                                }

                                // Push element into stack
                                elementStack.Push(element);

                                // Need to process if the element is empty
218
                                if (elementEmpty)
219
                                {
davescriven's avatar
davescriven committed
220
                                    goto case XmlNodeType.EndElement;
221
                                }
davescriven's avatar
davescriven committed
222
223
224

                                break;
                            case XmlNodeType.EndElement:
225
226
227
228
                                // Skip if no element was created and is not the closing tag for the last
                                // known element
                                if (element == null && reader.LocalName != elementStack.Peek().ElementName)
                                {
davescriven's avatar
davescriven committed
229
                                    continue;
230
                                }
davescriven's avatar
davescriven committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
                                // Pop the element out of the stack
                                element = elementStack.Pop();

                                if (value.Length > 0)
                                {
                                    element.Content = value.ToString();
                                    // Reset content value for new element
                                    value = new StringBuilder();
                                }
                                break;
                            case XmlNodeType.CDATA:
                            case XmlNodeType.Text:
                                value.Append(reader.Value);
                                break;
                        }
                    }
                    catch (Exception exc)
                    {
                        Trace.TraceError(exc.Message);
                    }
                }

253
                //Trace.TraceInformation("End Read");
davescriven's avatar
davescriven committed
254
255
256
257
                return svgDocument;
            }
        }

258
259
260
261
262
        /// <summary>
        /// Opens an SVG document from the specified <see cref="XmlDocument"/>.
        /// </summary>
        /// <param name="document">The <see cref="XmlDocument"/> containing the SVG document XML.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="document"/> parameter cannot be <c>null</c>.</exception>
263
        public static SvgDocument Open(XmlDocument document)
davescriven's avatar
davescriven committed
264
        {
265
266
267
268
269
            if (document == null)
            {
                throw new ArgumentNullException("document");
            }

270
            Stream stream = new MemoryStream(UTF8Encoding.Default.GetBytes(document.InnerXml));
271
            return Open(stream, null);
davescriven's avatar
davescriven committed
272
273
274
275
276
277
278
        }

        public static Bitmap OpenAsBitmap(string path)
        {
            return null;
        }

279
        public static Bitmap OpenAsBitmap(XmlDocument document)
davescriven's avatar
davescriven committed
280
281
282
283
284
285
        {
            return null;
        }

        public RectangleF GetDimensions()
        {
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
        	var w = Width.ToDeviceValue();
        	var h = Height.ToDeviceValue();
        	
        	RectangleF bounds = new RectangleF();
        	var isWidthperc = Width.Type == SvgUnitType.Percentage;
        	var isHeightperc = Height.Type == SvgUnitType.Percentage;

        	if(isWidthperc || isHeightperc)
        	{
        		bounds = this.Bounds; //do just one call to the recursive bounds property
        		if(isWidthperc) w = (bounds.Width + bounds.X) * (w * 0.01f);
        		if(isHeightperc) h = (bounds.Height + bounds.Y) * (h * 0.01f);
        	}
        	
            return new RectangleF(0, 0, w, h);
davescriven's avatar
davescriven committed
301
302
        }

303
304
305
306
        /// <summary>
        /// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>.
        /// </summary>
        /// <param name="renderer">The <see cref="SvgRenderer"/> to render the document with.</param>
307
        /// <exception cref="ArgumentNullException">The <paramref name="renderer"/> parameter cannot be <c>null</c>.</exception>
308
        public void Draw(SvgRenderer renderer)
davescriven's avatar
davescriven committed
309
        {
310
311
312
313
314
            if (renderer == null)
            {
                throw new ArgumentNullException("renderer");
            }

315
            this.Render(renderer);
davescriven's avatar
davescriven committed
316
317
        }

318
319
320
321
322
323
324
325
326
327
328
329
        /// <summary>
        /// Renders the <see cref="SvgDocument"/> to the specified <see cref="Graphics"/>.
        /// </summary>
        /// <param name="graphics">The <see cref="Graphics"/> to be rendered to.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="graphics"/> parameter cannot be <c>null</c>.</exception>
        public void Draw(Graphics graphics)
        {
            if (graphics == null)
            {
                throw new ArgumentNullException("graphics");
            }

330
            this.Render(SvgRenderer.FromGraphics(graphics));
331
332
        }

333
334
335
336
        /// <summary>
        /// Renders the <see cref="SvgDocument"/> and returns the image as a <see cref="Bitmap"/>.
        /// </summary>
        /// <returns>A <see cref="Bitmap"/> containing the rendered document.</returns>
davescriven's avatar
davescriven committed
337
338
        public virtual Bitmap Draw()
        {
339
            //Trace.TraceInformation("Begin Render");
davescriven's avatar
davescriven committed
340

341
            var size = GetDimensions();
342
            var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
davescriven's avatar
davescriven committed
343

Tebjan Halm's avatar
Tebjan Halm committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
            try
            {
                Draw(bitmap);
            }
            catch
            {
                bitmap.Dispose();
                throw;
            }

            //Trace.TraceInformation("End Render");
            return bitmap;
        }

        /// <summary>
        /// Renders the <see cref="SvgDocument"/> into a given Bitmap <see cref="Bitmap"/>.
        /// </summary>
        public virtual void Draw(Bitmap bitmap)
        {
            //Trace.TraceInformation("Begin Render");

365
            try
davescriven's avatar
davescriven committed
366
            {
367
                using (var renderer = SvgRenderer.FromImage(bitmap))
368
                {
369
370
371
372
373
                    renderer.TextRenderingHint = TextRenderingHint.AntiAlias;
                    renderer.TextContrast = 1;
                    renderer.PixelOffsetMode = PixelOffsetMode.Half;
                    this.Render(renderer);
                    renderer.Save();
374
375
376
377
378
                }
            }
            catch
            {
                throw;
davescriven's avatar
davescriven committed
379
380
            }

381
            //Trace.TraceInformation("End Render");
davescriven's avatar
davescriven committed
382
383
384
385
        }

        public void Write(Stream stream)
        {
Tebjan Halm's avatar
Tebjan Halm committed
386
387
388
389
390
391
            //Save previous culture and switch to invariant for writing
            var previousCulture = Thread.CurrentThread.CurrentCulture;
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            var xmlWriter = new XmlTextWriter(stream, Encoding.UTF8);
            xmlWriter.Formatting = Formatting.Indented;
392

Tebjan Halm's avatar
Tebjan Halm committed
393
394
395
396
            xmlWriter.WriteDocType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null);

            this.WriteElement(xmlWriter);

Tebjan Halm's avatar
Tebjan Halm committed
397
            xmlWriter.Flush();
Tebjan Halm's avatar
Tebjan Halm committed
398
399

            Thread.CurrentThread.CurrentCulture = previousCulture;
davescriven's avatar
davescriven committed
400
401
402
403
        }

        public void Write(string path)
        {
Tebjan Halm's avatar
Tebjan Halm committed
404
405
406
407
            using(var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                this.Write(fs);
            }
davescriven's avatar
davescriven committed
408
409
410
        }
    }
}