SvgDocument.cs 18.3 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;
11
using System.Linq;
Eric Domke's avatar
Eric Domke committed
12
13
using ExCSS;
using Svg.Css;
davescriven's avatar
davescriven committed
14
15
16
17

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

25
26
27
        /// <summary>
        /// Initializes a new instance of the <see cref="SvgDocument"/> class.
        /// </summary>
28
        public SvgDocument()
davescriven's avatar
davescriven committed
29
        {
James Welle's avatar
James Welle committed
30
            Ppi = PointsPerInch;
davescriven's avatar
davescriven committed
31
32
        }

33
34
        public Uri BaseUri { get; set; }

davescriven's avatar
davescriven committed
35
36
37
38
39
40
41
        /// <summary>
        /// Gets an <see cref="SvgElementIdManager"/> for this document.
        /// </summary>
        protected internal virtual SvgElementIdManager IdManager
        {
            get
            {
42
                if (_idManager == null)
43
                {
44
                    _idManager = new SvgElementIdManager(this);
45
                }
davescriven's avatar
davescriven committed
46

47
                return _idManager;
davescriven's avatar
davescriven committed
48
49
            }
        }
Eric Domke's avatar
Eric Domke committed
50

51
52
53
54
55
56
57
58
        /// <summary>
        /// Overwrites the current IdManager with a custom implementation. 
        /// Be careful with this: If elements have been inserted into the document before,
        /// you have to take care that the new IdManager also knows of them.
        /// </summary>
        /// <param name="manager"></param>
        public void OverwriteIdManager(SvgElementIdManager manager)
        {
James Welle's avatar
James Welle committed
59
            _idManager = manager;
60
        }
davescriven's avatar
davescriven committed
61

62
63
64
        /// <summary>
        /// Gets or sets the Pixels Per Inch of the rendered image.
        /// </summary>
65
66
67
68
69
        public int Ppi { get; set; }

        #region ITypeDescriptorContext Members

        IContainer ITypeDescriptorContext.Container
davescriven's avatar
davescriven committed
70
        {
71
            get { throw new NotImplementedException(); }
davescriven's avatar
davescriven committed
72
73
        }

74
        object ITypeDescriptorContext.Instance
davescriven's avatar
davescriven committed
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
101
102
103
104
105
106
107
108
            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
109
110
        }

111
112
113
114
115
116
117
118
119
        /// <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);
        }
Eric Domke's avatar
Eric Domke committed
120
121

        /// <summary>
122
123
124
125
126
127
128
129
130
        /// Opens the document at the specified path and loads the SVG contents.
        /// </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)
        {
            return Open<SvgDocument>(path, null);
        }
131

davescriven's avatar
davescriven committed
132
        /// <summary>
133
        /// Opens the document at the specified path and loads the SVG contents.
davescriven's avatar
davescriven committed
134
135
136
137
        /// </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>
138
        public static T Open<T>(string path) where T : SvgDocument, new()
139
        {
140
            return Open<T>(path, null);
141
142
        }

143
        /// <summary>
144
        /// Opens the document at the specified path and loads the SVG contents.
145
146
147
148
        /// </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>
149
        /// <exception cref="FileNotFoundException">The document at the specified <paramref name="path"/> cannot be found.</exception>
150
        public static T Open<T>(string path, Dictionary<string, string> entities) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
151
        {
152
153
154
155
156
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

davescriven's avatar
davescriven committed
157
            if (!File.Exists(path))
158
            {
davescriven's avatar
davescriven committed
159
                throw new FileNotFoundException("The specified document cannot be found.", path);
160
            }
davescriven's avatar
davescriven committed
161

162
163
164
            var doc = Open<T>(File.OpenRead(path), entities);
            doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path));
            return doc;
davescriven's avatar
davescriven committed
165
166
        }

167
168
169
170
        /// <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>
171
        public static T Open<T>(Stream stream) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
172
        {
173
            return Open<T>(stream, null);
davescriven's avatar
davescriven committed
174
175
        }

Eric Domke's avatar
Eric Domke committed
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196

        /// <summary>
        /// Attempts to create an SVG document from the specified string data.
        /// </summary>
        /// <param name="svg">The SVG data.</param>
        public static T FromSvg<T>(string svg) where T : SvgDocument, new()
        {
            if (string.IsNullOrEmpty(svg))
            {
                throw new ArgumentNullException("svg");
            }

            using (var strReader = new System.IO.StringReader(svg))
            {
                var reader = new SvgTextReader(strReader, null);
                reader.XmlResolver = new SvgDtdResolver();
                reader.WhitespaceHandling = WhitespaceHandling.None;
                return Open<T>(reader);
            }
        }

197
        /// <summary>
198
        /// Opens an SVG document from the specified <see cref="Stream"/> and adds the specified entities.
199
200
201
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the SVG document to open.</param>
        /// <param name="entities">Custom entity definitions.</param>
202
        /// <exception cref="ArgumentNullException">The <paramref name="stream"/> parameter cannot be <c>null</c>.</exception>
203
        public static T Open<T>(Stream stream, Dictionary<string, string> entities) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
204
        {
205
206
207
208
209
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

Eric Domke's avatar
Eric Domke committed
210
211
212
213
214
215
            // Don't close the stream via a dispose: that is the client's job.
            var reader = new SvgTextReader(stream, entities);
            reader.XmlResolver = new SvgDtdResolver();
            reader.WhitespaceHandling = WhitespaceHandling.None;
            return Open<T>(reader);
        }
davescriven's avatar
davescriven committed
216

Eric Domke's avatar
Eric Domke committed
217
218
219
220
221
222
223
224
225
226
227
        private static T Open<T>(XmlReader reader) where T : SvgDocument, new()
        {
            var elementStack = new Stack<SvgElement>();
            bool elementEmpty;
            SvgElement element = null;
            SvgElement parent;
            T svgDocument = null;
            
            var styles = new List<ISvgNode>();

            while (reader.Read())
davescriven's avatar
davescriven committed
228
            {
Eric Domke's avatar
Eric Domke committed
229
                try
davescriven's avatar
davescriven committed
230
                {
Eric Domke's avatar
Eric Domke committed
231
                    switch (reader.NodeType)
davescriven's avatar
davescriven committed
232
                    {
Eric Domke's avatar
Eric Domke committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
                        case XmlNodeType.Element:
                            // Does this element have a value or children
                            // (Must do this check here before we progress to another node)
                            elementEmpty = reader.IsEmptyElement;
                            // Create element
                            if (elementStack.Count > 0)
                            {
                                element = SvgElementFactory.CreateElement(reader, svgDocument);
                            }
                            else
                            {
                                svgDocument = SvgElementFactory.CreateDocument<T>(reader);
                                element = svgDocument;
                            }

                            // Add to the parents children
                            if (elementStack.Count > 0)
                            {
                                parent = elementStack.Peek();
                                if (parent != null && element != null)
davescriven's avatar
davescriven committed
253
                                {
Eric Domke's avatar
Eric Domke committed
254
255
                                    parent.Children.Add(element);
                                    parent.Nodes.Add(element);
davescriven's avatar
davescriven committed
256
                                }
Eric Domke's avatar
Eric Domke committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
                            }

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

                            // Need to process if the element is empty
                            if (elementEmpty)
                            {
                                goto case XmlNodeType.EndElement;
                            }

                            break;
                        case XmlNodeType.EndElement:

                            // Pop the element out of the stack
                            element = elementStack.Pop();

                            if (element.Nodes.OfType<SvgContentNode>().Any())
                            {
                                element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
                            }
                            else
                            {
                                element.Nodes.Clear(); // No sense wasting the space where it isn't needed
                            }

                            var unknown = element as SvgUnknownElement;
                            if (unknown != null && unknown.ElementName == "style")
                            {
                                styles.Add(unknown);
                            }
                            break;
                        case XmlNodeType.CDATA:
                        case XmlNodeType.Text:
                            element = elementStack.Peek();
                            element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
                            break;
                        case XmlNodeType.EntityReference:
                            reader.ResolveEntity();
                            element = elementStack.Peek();
                            element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
                            break;
                    }
                }
                catch (Exception exc)
                {
                    Trace.TraceError(exc.Message);
                }
            }
davescriven's avatar
davescriven committed
306

Eric Domke's avatar
Eric Domke committed
307
308
309
310
311
            if (styles.Any())
            {
                var cssTotal = styles.Select((s) => s.Content).Aggregate((p, c) => p + Environment.NewLine + c);
                var cssParser = new Parser();
                var sheet = cssParser.Parse(cssTotal);
312
313
                AggregateSelectorList aggList;
                IEnumerable<BaseSelector> selectors;
Eric Domke's avatar
Eric Domke committed
314
                IEnumerable<SvgElement> elemsToStyle;
davescriven's avatar
davescriven committed
315

Eric Domke's avatar
Eric Domke committed
316
317
                foreach (var rule in sheet.StyleRules)
                {
318
319
320
321
322
323
324
325
326
327
328
                    aggList = rule.Selector as AggregateSelectorList;
                    if (aggList != null && aggList.Delimiter == ",")
                    {
                        selectors = aggList;
                    }
                    else
                    {
                        selectors = Enumerable.Repeat(rule.Selector, 1);
                    }

                    foreach (var selector in selectors)
davescriven's avatar
davescriven committed
329
                    {
330
331
                        elemsToStyle = svgDocument.QuerySelectorAll(rule.Selector.ToString());
                        foreach (var elem in elemsToStyle)
Eric Domke's avatar
Eric Domke committed
332
                        {
333
334
335
336
                            foreach (var decl in rule.Declarations)
                            {
                                elem.AddStyle(decl.Name, decl.Term.ToString(), rule.Selector.GetSpecificity());
                            }
Eric Domke's avatar
Eric Domke committed
337
                        }
davescriven's avatar
davescriven committed
338
339
                    }
                }
Eric Domke's avatar
Eric Domke committed
340
341
            }

342
            if (svgDocument != null) FlushStyles(svgDocument);
Eric Domke's avatar
Eric Domke committed
343
344
            return svgDocument;
        }
davescriven's avatar
davescriven committed
345

Eric Domke's avatar
Eric Domke committed
346
347
348
349
350
351
        private static void FlushStyles(SvgElement elem)
        {
            elem.FlushStyles();
            foreach (var child in elem.Children)
            {
                FlushStyles(child);
davescriven's avatar
davescriven committed
352
353
354
            }
        }

355
356
357
358
359
        /// <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>
360
        public static SvgDocument Open(XmlDocument document)
davescriven's avatar
davescriven committed
361
        {
362
363
364
365
366
            if (document == null)
            {
                throw new ArgumentNullException("document");
            }

367
368
            var reader = new SvgNodeReader(document.DocumentElement, null);
            return Open<SvgDocument>(reader);
davescriven's avatar
davescriven committed
369
370
371
372
373
374
375
        }

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

376
        public static Bitmap OpenAsBitmap(XmlDocument document)
davescriven's avatar
davescriven committed
377
378
379
380
        {
            return null;
        }

381
382
383
384
        /// <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>
385
        /// <exception cref="ArgumentNullException">The <paramref name="renderer"/> parameter cannot be <c>null</c>.</exception>
386
        public void Draw(SvgRenderer renderer)
davescriven's avatar
davescriven committed
387
        {
388
389
390
391
392
            if (renderer == null)
            {
                throw new ArgumentNullException("renderer");
            }

393
            renderer.Boundable(this);
394
            this.Render(renderer);
davescriven's avatar
davescriven committed
395
396
        }

397
398
399
400
401
402
403
404
405
406
407
408
        /// <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");
            }

409
410
411
            var renderer = SvgRenderer.FromGraphics(graphics);
            renderer.Boundable(this);
            this.Render(renderer);
412
413
        }

414
415
416
417
        /// <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
418
419
        public virtual Bitmap Draw()
        {
420
            //Trace.TraceInformation("Begin Render");
davescriven's avatar
davescriven committed
421

422
            var size = GetDimensions();
423
            var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
Eric Domke's avatar
Eric Domke committed
424
            // 	bitmap.SetResolution(300, 300);
Tebjan Halm's avatar
Tebjan Halm committed
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
            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");

446
            try
davescriven's avatar
davescriven committed
447
            {
448
                using (var renderer = SvgRenderer.FromImage(bitmap))
449
                {
Eric Domke's avatar
Eric Domke committed
450
                    renderer.Boundable(new GenericBoundable(0, 0, bitmap.Width, bitmap.Height));
451
452
453
454
455
                    renderer.TextRenderingHint = TextRenderingHint.AntiAlias;
                    renderer.TextContrast = 1;
                    renderer.PixelOffsetMode = PixelOffsetMode.Half;
                    this.Render(renderer);
                    renderer.Save();
456
457
458
459
460
                }
            }
            catch
            {
                throw;
davescriven's avatar
davescriven committed
461
462
            }

463
            //Trace.TraceInformation("End Render");
davescriven's avatar
davescriven committed
464
465
466
467
        }

        public void Write(Stream stream)
        {
Tebjan Halm's avatar
Tebjan Halm committed
468
469
470

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

Tebjan Halm's avatar
Tebjan Halm committed
472
473
474
475
            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
476
            xmlWriter.Flush();
davescriven's avatar
davescriven committed
477
478
479
480
        }

        public void Write(string path)
        {
Eric Domke's avatar
Eric Domke committed
481
            using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
Tebjan Halm's avatar
Tebjan Halm committed
482
483
484
            {
                this.Write(fs);
            }
davescriven's avatar
davescriven committed
485
486
        }
    }
Eric Domke's avatar
Eric Domke committed
487
}