SvgDocument.cs 15.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
11
12
13
14
using System.IO;
using System.Text;
using System.Xml;

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

davescriven's avatar
davescriven committed
22

23

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

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

44
                return _idManager;
davescriven's avatar
davescriven committed
45
46
            }
        }
47
48
49
50
51
52
53
54
55
56
57
        
        /// <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)
        {
        	_idManager = manager;
        }
davescriven's avatar
davescriven committed
58

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

        #region ITypeDescriptorContext Members

        IContainer ITypeDescriptorContext.Container
davescriven's avatar
davescriven committed
67
        {
68
            get { throw new NotImplementedException(); }
davescriven's avatar
davescriven committed
69
70
        }

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

108
109
110
111
112
113
114
115
116
        /// <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);
        }
117
118
119
120
121
122
123
124
125
126
127
        
                /// <summary>
        /// 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);
        }
128

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

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

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

159
            return Open<T>(File.OpenRead(path), entities);
davescriven's avatar
davescriven committed
160
161
        }

162
163
164
165
        /// <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>
166
        public static T Open<T>(Stream stream) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
167
        {
168
            return Open<T>(stream, null);
davescriven's avatar
davescriven committed
169
170
        }

171
        /// <summary>
172
        /// Opens an SVG document from the specified <see cref="Stream"/> and adds the specified entities.
173
174
175
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the SVG document to open.</param>
        /// <param name="entities">Custom entity definitions.</param>
176
        /// <exception cref="ArgumentNullException">The <paramref name="stream"/> parameter cannot be <c>null</c>.</exception>
177
        public static T Open<T>(Stream stream, Dictionary<string, string> entities) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
178
        {
179
180
181
182
183
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

184
            //Trace.TraceInformation("Begin Read");
davescriven's avatar
davescriven committed
185

186
            using (var reader = new SvgTextReader(stream, entities))
davescriven's avatar
davescriven committed
187
            {
188
189
                var elementStack = new Stack<SvgElement>();
                var value = new StringBuilder();
190
                bool elementEmpty;
davescriven's avatar
davescriven committed
191
                SvgElement element = null;
192
                SvgElement parent;
193
                T svgDocument = null;
davescriven's avatar
davescriven committed
194
195
196
197
198
199
200
201
202
203
204
205
                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)
206
                                elementEmpty = reader.IsEmptyElement;
davescriven's avatar
davescriven committed
207
208
                                // Create element
                                if (elementStack.Count > 0)
209
                                {
davescriven's avatar
davescriven committed
210
                                    element = SvgElementFactory.CreateElement(reader, svgDocument);
211
                                }
davescriven's avatar
davescriven committed
212
213
                                else
                                {
214
215
                                    svgDocument = SvgElementFactory.CreateDocument<T>(reader);
                                    element = svgDocument;
davescriven's avatar
davescriven committed
216
217
218
219
220
221
                                }

                                // Add to the parents children
                                if (elementStack.Count > 0)
                                {
                                    parent = elementStack.Peek();
222
223
                                    if (parent != null && element != null)
                                        parent.Children.Add(element);
davescriven's avatar
davescriven committed
224
225
226
227
228
229
                                }

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

                                // Need to process if the element is empty
230
                                if (elementEmpty)
231
                                {
davescriven's avatar
davescriven committed
232
                                    goto case XmlNodeType.EndElement;
233
                                }
davescriven's avatar
davescriven committed
234
235
236

                                break;
                            case XmlNodeType.EndElement:
tebjan's avatar
tebjan committed
237

davescriven's avatar
davescriven committed
238
239
240
                                // Pop the element out of the stack
                                element = elementStack.Pop();

tebjan's avatar
tebjan committed
241
                                if (value.Length > 0 && element != null)
davescriven's avatar
davescriven committed
242
                                {
tebjan's avatar
tebjan committed
243
244
                                    element.Content = value.ToString();
                                    
davescriven's avatar
davescriven committed
245
                                    // Reset content value for new element
246
                                    value.Clear();
davescriven's avatar
davescriven committed
247
248
249
250
251
252
                                }
                                break;
                            case XmlNodeType.CDATA:
                            case XmlNodeType.Text:
                                value.Append(reader.Value);
                                break;
253
254
255
256
                            case XmlNodeType.EntityReference:
                                reader.ResolveEntity();
                                value.Append(reader.Value);
                                break;
davescriven's avatar
davescriven committed
257
258
259
260
261
262
263
264
                        }
                    }
                    catch (Exception exc)
                    {
                        Trace.TraceError(exc.Message);
                    }
                }

265
                //Trace.TraceInformation("End Read");
davescriven's avatar
davescriven committed
266
267
268
269
                return svgDocument;
            }
        }

270
271
272
273
274
        /// <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>
275
        public static SvgDocument Open(XmlDocument document)
davescriven's avatar
davescriven committed
276
        {
277
278
279
280
281
            if (document == null)
            {
                throw new ArgumentNullException("document");
            }

282
283
            Stream stream = new MemoryStream(UTF8Encoding.Default.GetBytes(document.InnerXml));
            return Open<SvgDocument>(stream, null);
davescriven's avatar
davescriven committed
284
285
286
287
288
289
290
        }

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

291
        public static Bitmap OpenAsBitmap(XmlDocument document)
davescriven's avatar
davescriven committed
292
293
294
295
296
297
        {
            return null;
        }

        public RectangleF GetDimensions()
        {
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
        	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
313
314
        }

315
316
317
318
        /// <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>
319
        /// <exception cref="ArgumentNullException">The <paramref name="renderer"/> parameter cannot be <c>null</c>.</exception>
320
        public void Draw(SvgRenderer renderer)
davescriven's avatar
davescriven committed
321
        {
322
323
324
325
326
            if (renderer == null)
            {
                throw new ArgumentNullException("renderer");
            }

327
            this.Render(renderer);
davescriven's avatar
davescriven committed
328
329
        }

330
331
332
333
334
335
336
337
338
339
340
341
        /// <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");
            }

342
            this.Render(SvgRenderer.FromGraphics(graphics));
343
344
        }

345
346
347
348
        /// <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
349
350
        public virtual Bitmap Draw()
        {
351
            //Trace.TraceInformation("Begin Render");
davescriven's avatar
davescriven committed
352

353
            var size = GetDimensions();
354
            var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
355
       // 	bitmap.SetResolution(300, 300);
Tebjan Halm's avatar
Tebjan Halm committed
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
            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");

377
            try
davescriven's avatar
davescriven committed
378
            {
379
                using (var renderer = SvgRenderer.FromImage(bitmap))
380
                {
381
382
383
384
385
                    renderer.TextRenderingHint = TextRenderingHint.AntiAlias;
                    renderer.TextContrast = 1;
                    renderer.PixelOffsetMode = PixelOffsetMode.Half;
                    this.Render(renderer);
                    renderer.Save();
386
387
388
389
390
                }
            }
            catch
            {
                throw;
davescriven's avatar
davescriven committed
391
392
            }

393
            //Trace.TraceInformation("End Render");
davescriven's avatar
davescriven committed
394
395
396
397
        }

        public void Write(Stream stream)
        {
Tebjan Halm's avatar
Tebjan Halm committed
398
399
400

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

Tebjan Halm's avatar
Tebjan Halm committed
402
403
404
405
            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
406
            xmlWriter.Flush();
davescriven's avatar
davescriven committed
407
408
409
410
        }

        public void Write(string path)
        {
Tebjan Halm's avatar
Tebjan Halm committed
411
412
413
414
            using(var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                this.Write(fs);
            }
davescriven's avatar
davescriven committed
415
        }
416

davescriven's avatar
davescriven committed
417
    }
418
}