using FastReport.Utils; using Svg; using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.IO; using System.Net; using System.Text; using System.Windows.Forms; #pragma warning disable namespace FastReport.SVG { /// /// SVG object /// public partial class SVGObject : PictureObjectBase, ICloneable { private readonly string svgTagPr = " /// Gets or sets svg document /// [Browsable(false)] public SvgDocument SvgDocument { get { return FSvgDocument; } } /// /// Gets or sets ViewBox value /// [Browsable(false)] public SvgViewBox ViewBox { get { return FViewBox; } set { SetViewBox(value); } } /// /// Gets or sets AspectRatio value /// [Browsable(false)] public SvgAspectRatio AspectRatio { get { return FAspectRatio; } set { SetAspectRatio(value); } } /// [DefaultValue(PictureBoxSizeMode.Zoom)] [Category("Behavior")] public override PictureBoxSizeMode SizeMode { get { return base.SizeMode; } set { base.SizeMode = value; if (SvgDocument != null) // if not PictureObject -> Assign(); { if (value == PictureBoxSizeMode.StretchImage || value == PictureBoxSizeMode.AutoSize) AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.none); else AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid); } } } /// /// Gets or sets grayscale svg document /// [Browsable(false)] public SvgDocument SVGGrayscale { get { if (FSVGGrayscale == null) FSVGGrayscale = GetSVGGrayscale(); return FSVGGrayscale; } set { FSVGGrayscale = value; } } /// /// Gets or sets a value indicating that the image should be displayed in grayscale mode. /// [DefaultValue(false)] [Category("Appearance")] public override bool Grayscale { get { return base.Grayscale; } set { base.Grayscale = value; if (!Grayscale && FSVGGrayscale != null) { FSVGGrayscale = null; } if (value == true && (SvgDocument != null)) // if not PictureObject -> Assign(); { FSVGGrayscale = GetSVGGrayscale(); } } } /// /// Returns SVG string /// [EditorAttribute("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string SVGString { get { return FSVGString; } set { if (!string.IsNullOrEmpty(value)) { FSVGString = value; FSvgDocument = SvgDocument.FromSvg(FSVGString); if (Grayscale) { SVGGrayscale = GetSVGGrayscale(); } if (FSvgDocument != null) { originalWidth = this.Width; originalHeight = this.Height; FLastImgHeight = int.MinValue; FLastImgWidth = int.MinValue; if (FSvgDocument.Width.Type != SvgUnitType.Percentage) { originalWidth = FSvgDocument.Width; FSvgDocument.Width = new SvgUnit(SvgUnitType.Percentage, 100); } if (FSvgDocument.Height.Type != SvgUnitType.Percentage) { originalHeight = FSvgDocument.Height; FSvgDocument.Height = new SvgUnit(SvgUnitType.Percentage, 100); } if (FSvgDocument.ViewBox.Width == 0 || FSvgDocument.ViewBox.Height == 0) { FSvgDocument.ViewBox = new SvgViewBox(FSvgDocument.ViewBox.MinX, FSvgDocument.ViewBox.MinY, FSvgDocument.ViewBox.Width == 0 ? originalWidth : FSvgDocument.ViewBox.Width, FSvgDocument.ViewBox.Height == 0 ? originalHeight : FSvgDocument.ViewBox.Height); } } } } } protected override float ImageHeight { get { if (SvgDocument == null) return 0; if (SizeMode == PictureBoxSizeMode.AutoSize) return originalHeight; return SvgDocument.GetDimensions().Height; } } protected override float ImageWidth { get { if (SvgDocument == null) return 0; if (SizeMode == PictureBoxSizeMode.AutoSize) return originalWidth; return SvgDocument.GetDimensions().Width; } } #endregion #region Private Methods private Color GetGrayscaleColor(Color color) { int grayscale = (int)((color.R * 0.299f) + (color.G * 0.587f) + (color.B * 0.114f)); return Color.FromArgb(color.A, grayscale, grayscale, grayscale); } private void MakeElementGrayScale(SvgElement element) { if (element.Fill != null) { if (element.Fill is SvgGradientServer) foreach (SvgGradientStop stop in (element.Fill as SvgGradientServer).Stops) { (stop.StopColor as SvgColourServer).Colour = GetGrayscaleColor((stop.StopColor as SvgColourServer).Colour); } else ((element).Fill as SvgColourServer).Colour = GetGrayscaleColor(((element).Fill as SvgColourServer).Colour); } if ((element).Stroke != null) ((element).Stroke as SvgColourServer).Colour = GetGrayscaleColor(((element).Stroke as SvgColourServer).Colour); if (element.Children.Count > 0) { foreach (var item in element.Children) { MakeElementGrayScale(item); } } } //private byte[] SvgToImgByteArray(SvgDocument svg) //{ // using (MemoryStream pictStr = new MemoryStream()) // { // Image img = svg.Draw(); // img.Save(pictStr, ImageFormat.Png); // return pictStr.ToArray(); // } //} private static string LoadURL(string url) { if (!String.IsNullOrEmpty(url)) { System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); using (WebClient web = new WebClient()) { return Encoding.UTF8.GetString(web.DownloadData(url)); } } return null; } public Image GetImage() { if (SvgDocument == null) return null; FLastDrawTime = DateTime.MinValue; Size size = SvgDocument.GetDimensions().ToSize(); return GetImage(Grayscale, size.Width, size.Height); } private Image GetImage(bool isGrayscale, int imgWidth, int imgHeight) { Image image; float scaleY = imgHeight / FLastImgHeight; float scaleX = imgWidth / FLastImgWidth; const float minScale = 0.71f; const float maxScale = 1.41f; if (SystemFake.DateTime.Now - FLastDrawTime > FSpan || scaleY < minScale || scaleY > maxScale || scaleX < minScale || scaleX > maxScale || isGrayscale && FCachedGrayscaleImage == null || !isGrayscale && FCachedImage == null) { image = isGrayscale ? SVGGrayscale.Draw(imgWidth, imgHeight) : SvgDocument.Draw(imgWidth, imgHeight); FLastDrawTime = SystemFake.DateTime.Now; if (isGrayscale) { if (FCachedGrayscaleImage != null) FCachedGrayscaleImage.Dispose(); FCachedGrayscaleImage = image; } else { if (FCachedImage != null) FCachedImage.Dispose(); FCachedImage = image; } FLastImgHeight = imgHeight; FLastImgWidth = imgWidth; } else { if (isGrayscale) image = FCachedGrayscaleImage; else image = FCachedImage; } return image; } private string GetSvgString() { using (StringWriter sw = new StringWriter()) { using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(sw)) SvgDocument.Write(writer); string svgStr = sw.ToString(); sw.Flush(); return svgStr; } } private string GetViewBoxString(out int startVb, out int endVb) { if (string.IsNullOrEmpty(SVGString)) throw new ArgumentNullException("SvgString is null"); startVb = SVGString.IndexOf("viewBox"); endVb = -1; if (startVb != -1) { startVb = SVGString.IndexOf("\"", startVb) + 1; endVb = SVGString.IndexOf("\"", startVb); string vb = SVGString.Substring(startVb, endVb - startVb); return vb; } else return null; } private SvgViewBox GetViewBox() { if (string.IsNullOrEmpty(SVGString)) throw new ArgumentNullException("SvgString is null"); SvgViewBox viewBox = new SvgViewBox(); int startVb, endVb; string vb = GetViewBoxString(out startVb, out endVb); if (!string.IsNullOrEmpty(vb)) { string[] vbArray = vb.Split(' ', ','); float minX; float minY; float width; float height; if (float.TryParse(vbArray[0], out minX) && float.TryParse(vbArray[1], out minY) && float.TryParse(vbArray[2], out width) && float.TryParse(vbArray[3], out height)) { viewBox.MinX = minX; viewBox.MinY = minY; viewBox.Width = width; viewBox.Height = height; } } return viewBox; } private void SetViewBox(SvgViewBox viewBox) { int startVb, endVb; string oldVb = GetViewBoxString(out startVb, out endVb); string newVb = viewBox.MinX + " " + viewBox.MinY + " " + viewBox.Width + " " + viewBox.Height; if (!string.IsNullOrEmpty(oldVb)) { SVGString = SVGString.Remove(startVb, endVb - startVb); SVGString = SVGString.Insert(startVb, newVb); } else { startVb = SVGString.IndexOf(svgTagPr) + svgTagPr.Length; SVGString = SVGString.Insert(startVb, " " + "viewBox=\"" + newVb + "\""); } FViewBox = viewBox; } private string GetAspectRatioString(out int startAr, out int endAr) { if (string.IsNullOrEmpty(SVGString)) throw new ArgumentNullException("SvgString is null"); startAr = SVGString.IndexOf("preserveAspectRatio"); endAr = -1; if (startAr != -1) { startAr = SVGString.IndexOf("\"", startAr) + 1; endAr = SVGString.IndexOf("\"", startAr); string ar = SVGString.Substring(startAr, endAr - startAr); return ar; } else return null; } private SvgAspectRatio GetAspectRatio() { if (string.IsNullOrEmpty(SVGString)) throw new ArgumentNullException("SvgString is null"); SvgAspectRatio aspectRatio = new SvgAspectRatio(); int startAr, endAr; string ar = GetAspectRatioString(out startAr, out endAr); if (!string.IsNullOrEmpty(ar)) { SvgPreserveAspectRatio align; Enum.TryParse(ar, out align); aspectRatio.Align = align; } return aspectRatio; } private void SetAspectRatio(SvgAspectRatio aspectRatio) { int startAr, endAr; string oldAr = GetAspectRatioString(out startAr, out endAr); string newAr = aspectRatio.Align.ToString(); if (!string.IsNullOrEmpty(oldAr)) { SVGString = SVGString.Remove(startAr, endAr - startAr); SVGString = SVGString.Insert(startAr, newAr); } else { startAr = SVGString.IndexOf(svgTagPr) + svgTagPr.Length; SVGString = SVGString.Insert(startAr, " " + "preserveAspectRatio=\"" + newAr + "\""); } FAspectRatio = aspectRatio; } //private Stream StreamFromString(string s) //{ // var stream = new MemoryStream(); // var writer = new StreamWriter(stream, Encoding.UTF8); // writer.Write(s); // writer.Flush(); // stream.Position = 0; // return stream; //} #endregion #region Internal Methods internal static SvgDocument GetSvgDocument(string filePath) { SvgDocument document = SvgDocument.Open(filePath); return document; } internal void MakeGrayScale(SvgDocument svg) { foreach (var element in svg.Children) { MakeElementGrayScale(element); if (element.Children.Count > 0) { foreach (var item in element.Children) { MakeElementGrayScale(item); } } } } internal SvgDocument GetSVGGrayscale() { FSVGGrayscale = SvgDocument.FromSvg(FSVGString); MakeGrayScale(FSVGGrayscale); //GrayscaleHash = FSVGGrayscale.GetHashCode(); return FSVGGrayscale; } #endregion #region Public Methods /// public override void Serialize(FRWriter writer) { SVGObject c = writer.DiffObject as SVGObject; base.Serialize(writer); if (writer.SerializeTo == SerializeTo.SourcePages || writer.SerializeTo == SerializeTo.Preview || (String.IsNullOrEmpty(ImageLocation) && String.IsNullOrEmpty(DataColumn))) { if (!string.IsNullOrEmpty(SVGString) && SVGString != c.SVGString) { writer.WriteValue("SvgData", Convert.ToBase64String(Encoding.UTF8.GetBytes(SVGString))); } } } /// public override void Deserialize(FRReader reader) { base.Deserialize(reader); if (reader.HasProperty("SvgData")) { SetSVGByContent(Encoding.UTF8.GetString(Convert.FromBase64String(reader.ReadStr("SvgData")))); } } /// public override void Assign(Base source) { base.Assign(source); SVGObject src = source as SVGObject; if (src != null) { if (src.SVGString != null) SetSVGByContent(src.SVGString); } } /// public override void LoadImage() { if (!String.IsNullOrEmpty(ImageLocation)) { try { Uri uri = CalculateUri(); if (uri.IsFile) SetSVGByPath(uri.LocalPath); else SetSVGByContent(LoadURL(uri.ToString())); } catch { SetSVGByContent(""); } } } /// public override void GetData() { base.GetData(); if (!String.IsNullOrEmpty(DataColumn)) { object data = Report.GetColumnValueNullable(DataColumn); if (data is byte[]) { SetSVGByContent(Encoding.UTF8.GetString((byte[])data)); } if (data is string) { try { SetSVGByContent(data.ToString()); } catch { ImageLocation = data.ToString(); } } } } /// public override void DrawImage(FRPaintEventArgs e) { IGraphics g = e.Graphics; if (SvgDocument == null) { DrawErrorImage(g, e); return; } float drawLeft = (AbsLeft + Padding.Left) * e.ScaleX; float drawTop = (AbsTop + Padding.Top) * e.ScaleY; float drawWidth = (Width - Padding.Horizontal) * e.ScaleX; float drawHeight = (Height - Padding.Vertical) * e.ScaleY; RectangleF drawRect = new RectangleF( drawLeft, drawTop, drawWidth, drawHeight); IGraphicsState state = g.Save(); try { g.SetClip(drawRect); Report report = Report; if (report != null && report.SmoothGraphics) { g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.AntiAlias; } DrawImageInternal(e, drawRect); } finally { g.Restore(state); } } protected override void DrawImageInternal2(IGraphics graphics, PointF upperLeft, PointF upperRight, PointF lowerLeft) { int imgWidth = (int)Math.Sqrt((upperLeft.X - upperRight.X) * (upperLeft.X - upperRight.X) + (upperLeft.Y - upperRight.Y) * (upperLeft.Y - upperRight.Y)); int imgHeight = (int)Math.Sqrt((upperLeft.X - lowerLeft.X) * (upperLeft.X - lowerLeft.X) + (upperLeft.Y - lowerLeft.Y) * (upperLeft.Y - lowerLeft.Y)); Image image = GetImage(Grayscale, imgWidth, imgHeight); if (image != null) graphics.DrawImage(image, new PointF[] { upperLeft, upperRight, lowerLeft }); } ///// //internal override void DrawImageInternal(FRPaintEventArgs e, RectangleF drawRect) //{ // if (Image == null || SvgDocument == null) // return; // bool rotate = Angle == 90 || Angle == 270; // float imageWidth = Image.Width;//rotate ? Image.Height : Image.Width; // float imageHeight = Image.Height;//rotate ? Image.Width : Image.Height; // PointF upperLeft; // PointF upperRight; // PointF lowerLeft; // System.Drawing.Drawing2D.Matrix matrix = e.Graphics.Transform; // GetImageAngleTransform(drawRect, imageWidth, imageHeight, e.ScaleX, e.ScaleY, matrix.OffsetX, matrix.OffsetY, out upperLeft, out upperRight, out lowerLeft); // int imgWidth = (int)Math.Sqrt((upperLeft.X - upperRight.X) * (upperLeft.X - upperRight.X) + (upperLeft.Y - upperRight.Y) * (upperLeft.Y - upperRight.Y)); // int imgHeight = (int)Math.Sqrt((upperLeft.X - lowerLeft.X) * (upperLeft.X - lowerLeft.X) + (upperLeft.Y - lowerLeft.Y) * (upperLeft.Y - lowerLeft.Y)); // Image image; // if (Grayscale) // { // if (FSVGGrayscale == null || GrayscaleHash != FSVGGrayscale.GetHashCode()) // { // FSVGGrayscale = GetSVGGrayscale(); // } // image = GetImage(true, imgWidth, imgHeight); // } // else // image = GetImage(false, imgWidth, imgHeight); // e.Graphics.DrawImage(image, new PointF[] { upperLeft, upperRight, lowerLeft }); //} /// /// Returns clone of this object /// /// public object Clone() { SVGObject clone = new SVGObject(); clone.Assign(this); return clone; } /// /// Sets svg object by SvgDocument /// /// SVG document public void SetSVG(SvgDocument svg) { SetSVGByContent(svg.GetXML()); } /// /// Sets svg object from specified path /// /// path to SVG file public void SetSVGByPath(string path) { SetSVGByContent(File.ReadAllText(path)); } /// /// Sets svg object from svg string /// /// SVG string public void SetSVGByContent(string content) { SVGString = content; } protected override void ResetImageIndex() { } #endregion protected override void Dispose(bool disposing) { if (disposing) { if (FCachedImage != null) { FCachedImage.Dispose(); FCachedImage = null; } if (FCachedGrayscaleImage != null) { FCachedGrayscaleImage.Dispose(); FCachedGrayscaleImage = null; } } base.Dispose(disposing); } /// /// Initializes a new instance of the class with default settings. /// public SVGObject() : base() { FSpan = new TimeSpan(0, 0, 3); SetFlags(Flags.HasSmartTag, true); FLastDrawTime = DateTime.MinValue; } } } #pragma warning restore