// THIS FILE IS PART OF SVG PROJECT // THE SVG PROJECT IS AN OPENSOURCE LIBRARY LICENSED UNDER THE MS-PL License. // COPYRIGHT (C) svg-net. ALL RIGHTS RESERVED. // GITHUB: https://github.com/svg-net/SVG using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Xml; namespace AntdUI.Svg { /// /// The class used to create and load SVG documents. /// public class SvgDocument : SvgFragment { private Dictionary>? _fontDefns = null; internal Dictionary> FontDefns() { _fontDefns ??= (from f in Descendants().OfType() group f by f.FontFamily into family select family).ToDictionary(f => f.Key, f => (IEnumerable)f); return _fontDefns; } public Uri? BaseUri { get; set; } private SvgElementIdManager? _idManager; /// /// Gets an for this document. /// protected internal virtual SvgElementIdManager IdManager { get { _idManager ??= new SvgElementIdManager(this); return _idManager; } } /// /// 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. /// /// public void OverwriteIdManager(SvgElementIdManager manager) { _idManager = manager; } /// /// Gets or sets the Pixels Per Inch of the rendered image. /// public static int Ppi { get => (int)(Config.Dpi * 96); } /// /// Retrieves the with the specified ID. /// /// A containing the ID of the element to find. /// An of one exists with the specified ID; otherwise false. public virtual SvgElement GetElementById(string id) { return IdManager.GetElementById(id); } /// /// Retrieves the with the specified ID. /// /// A containing the ID of the element to find. /// An of one exists with the specified ID; otherwise false. public virtual TSvgElement GetElementById(string id) where TSvgElement : SvgElement { return (TSvgElement)GetElementById(id); } /// /// Opens the document at the specified path and loads the SVG contents. /// /// A containing the path of the file to open. /// A dictionary of custom entity definitions to be used when resolving XML entities within the document. /// An with the contents loaded. /// The document at the specified cannot be found. public static T? Open(string path) where T : SvgDocument, new() { using (var stream = File.OpenRead(path)) { var doc = Open(stream); if (doc != null) doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); return doc; } } /// /// Attempts to create an SVG document from the specified string data. /// /// The SVG data. public static T? FromSvg(string svg) where T : SvgDocument, new() { if (string.IsNullOrEmpty(svg)) return null; using (var strReader = new System.IO.StringReader(svg)) { var reader = new SvgTextReader(strReader); reader.WhitespaceHandling = WhitespaceHandling.None; return Open(reader); } } /// /// Opens an SVG document from the specified and adds the specified entities. /// /// The containing the SVG document to open. /// Custom entity definitions. /// The parameter cannot be null. public static T? Open(Stream stream) where T : SvgDocument, new() { // Don't close the stream via a dispose: that is the client's job. var reader = new SvgTextReader(stream); reader.WhitespaceHandling = WhitespaceHandling.None; return Open(reader); } private static T? Open(XmlReader reader) where T : SvgDocument, new() { var elementStack = new Stack(); bool elementEmpty; SvgElement element, parent; T? svgDocument = null; var elementFactory = new SvgElementFactory(); try { while (reader.Read()) { 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) elementEmpty = reader.IsEmptyElement; // Create element if (elementStack.Count > 0) { element = elementFactory.CreateElement(reader, svgDocument); } else { svgDocument = elementFactory.CreateDocument(reader); element = svgDocument; } // Add to the parents children if (elementStack.Count > 0) { parent = elementStack.Peek(); if (parent != null && element != null) { parent.Children.Add(element); parent.Nodes.Add(element); } } // 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().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 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 { } if (svgDocument != null) FlushStyles(svgDocument); return svgDocument; } private static void FlushStyles(SvgElement elem) { elem.FlushStyles(); foreach (var child in elem.Children) { FlushStyles(child); } } private void Draw(ISvgRenderer renderer, ISvgBoundable boundable) { renderer.SetBoundable(boundable); Render(renderer); } /// /// Renders the to the specified . /// /// The to render the document with. /// The parameter cannot be null. public void Draw(ISvgRenderer renderer) { Draw(renderer, this); } /// /// Renders the to the specified . /// /// The to be rendered to. /// The parameter cannot be null. public void Draw(Graphics graphics) { Draw(graphics, null); } /// /// Renders the to the specified . /// /// The to be rendered to. /// The to render the document. If null document is rendered at the default document size. /// The parameter cannot be null. public void Draw(Graphics graphics, SizeF? size) { using (var renderer = SvgRenderer.FromGraphics(graphics)) { var boundable = size.HasValue ? (ISvgBoundable)new GenericBoundable(0, 0, size.Value.Width, size.Value.Height) : this; Draw(renderer, boundable); } } /// /// Renders the and returns the image as a . /// /// A containing the rendered document. public virtual Bitmap? Draw() { Bitmap? bitmap = null; try { try { var size = GetDimensions(); bitmap = new Bitmap((int)Math.Round(size.Width), (int)Math.Round(size.Height)); Draw(bitmap); } catch { } //bitmap.SetResolution(300, 300); } catch { bitmap?.Dispose(); bitmap = null; } return bitmap; } /// /// Renders the into a given Bitmap . /// public virtual void Draw(Bitmap bitmap) { using (var renderer = SvgRenderer.FromImage(bitmap)) { Overflow = SvgOverflow.Auto; var boundable = new GenericBoundable(0, 0, bitmap.Width, bitmap.Height); Draw(renderer, boundable); } } /// /// Renders the in given size and returns the image as a . /// If one of rasterWidth and rasterHeight is zero, the image is scaled preserving aspect ratio, /// otherwise the aspect ratio is ignored. /// /// A containing the rendered document. public virtual Bitmap? Draw(int rasterWidth, int rasterHeight) { var imageSize = GetDimensions(); var bitmapSize = imageSize; RasterizeDimensions(ref bitmapSize, rasterWidth, rasterHeight); if (bitmapSize.Width == 0 || bitmapSize.Height == 0) return null; Bitmap? bitmap = null; try { try { bitmap = new Bitmap((int)Math.Round(bitmapSize.Width), (int)Math.Round(bitmapSize.Height)); using (var renderer = SvgRenderer.FromImage(bitmap)) { renderer.ScaleTransform(bitmapSize.Width / imageSize.Width, bitmapSize.Height / imageSize.Height); var boundable = new GenericBoundable(0, 0, imageSize.Width, imageSize.Height); Draw(renderer, boundable); } } catch { } } catch { bitmap?.Dispose(); bitmap = null; } return bitmap; } /// /// If both or one of raster height and width is not given (0), calculate that missing value from original SVG size /// while keeping original SVG size ratio /// /// /// /// public virtual void RasterizeDimensions(ref SizeF size, int rasterWidth, int rasterHeight) { if (size.Width == 0) return; // Ratio of height/width of the original SVG size, to be used for scaling transformation float ratio = size.Height / size.Width; size.Width = rasterWidth > 0 ? (float)rasterWidth : size.Width; size.Height = rasterHeight > 0 ? (float)rasterHeight : size.Height; if (rasterHeight == 0 && rasterWidth > 0) size.Height = (int)(rasterWidth * ratio); else if (rasterHeight > 0 && rasterWidth == 0) size.Width = (int)(rasterHeight / ratio); } } }