// 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);
}
}
}