SvgDocument.cs 14.7 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
            return new RectangleF(0, 0, Width.ToDeviceValue(), Height.ToDeviceValue());
davescriven's avatar
davescriven committed
287
288
        }

289
290
291
292
        /// <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>
293
        /// <exception cref="ArgumentNullException">The <paramref name="renderer"/> parameter cannot be <c>null</c>.</exception>
294
        public void Draw(SvgRenderer renderer)
davescriven's avatar
davescriven committed
295
        {
296
297
298
299
300
            if (renderer == null)
            {
                throw new ArgumentNullException("renderer");
            }

301
            this.Render(renderer);
davescriven's avatar
davescriven committed
302
303
        }

304
305
306
307
308
309
310
311
312
313
314
315
        /// <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");
            }

316
            this.Render(SvgRenderer.FromGraphics(graphics));
317
318
        }

319
320
321
322
        /// <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
323
324
        public virtual Bitmap Draw()
        {
325
            //Trace.TraceInformation("Begin Render");
davescriven's avatar
davescriven committed
326

327
            var size = GetDimensions();
328
            var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
davescriven's avatar
davescriven committed
329

Tebjan Halm's avatar
Tebjan Halm committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
            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");

351
            try
davescriven's avatar
davescriven committed
352
            {
353
                using (var renderer = SvgRenderer.FromImage(bitmap))
354
                {
355
356
357
358
359
                    renderer.TextRenderingHint = TextRenderingHint.AntiAlias;
                    renderer.TextContrast = 1;
                    renderer.PixelOffsetMode = PixelOffsetMode.Half;
                    this.Render(renderer);
                    renderer.Save();
360
361
362
363
364
                }
            }
            catch
            {
                throw;
davescriven's avatar
davescriven committed
365
366
            }

367
            //Trace.TraceInformation("End Render");
davescriven's avatar
davescriven committed
368
369
370
371
        }

        public void Write(Stream stream)
        {
Tebjan Halm's avatar
Tebjan Halm committed
372
373
374
375
376
377
            //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;
378

Tebjan Halm's avatar
Tebjan Halm committed
379
380
381
382
383
384
385
            xmlWriter.WriteDocType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null);

            this.WriteElement(xmlWriter);

            xmlWriter.Close();

            Thread.CurrentThread.CurrentCulture = previousCulture;
davescriven's avatar
davescriven committed
386
387
388
389
        }

        public void Write(string path)
        {
Tebjan Halm's avatar
Tebjan Halm committed
390
391
392
393
            using(var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                this.Write(fs);
            }
davescriven's avatar
davescriven committed
394
395
396
        }
    }
}