using System.Collections.Generic; using System.IO; using System.Collections; using System.Drawing.Imaging; using FastReport.Utils; using System.Drawing; namespace FastReport.Export.Svg { /// /// Represents the SVG export filter. /// public partial class SVGExport : ExportBase { private XmlDocument doc; #region Private Fields private string path; private string fileNameWOext; private string extension; private string pageFileName; private SvgGraphics graphics; private int currentPage; private bool pictures; private bool embedPictures; private bool useWidthAndHeight; private bool useViewBox; private AspectRatio preserveAspectRatio; private string prefixStyle; private float pageWidth; private float pageHeight; #endregion #region Properties /// /// Enable or disable the pictures in SVG export /// public bool Pictures { get { return pictures; } set { pictures = value; } } /// /// Gets or sets the image format used when exporting. /// public SVGImageFormat ImageFormat { get; set; } /// /// Embed images into svg /// public bool EmbedPictures { get { return embedPictures; } set { embedPictures = value; } } /// /// Gets or sets value indicating whether or not should to force uniform scaling of SVG document /// public AspectRatio PreserveAspectRatio { get { return preserveAspectRatio; } set { preserveAspectRatio = value; } } /// /// Gets or sets value indicating whether or not should be added 'viewBox' attribute to the svg tag /// public bool UseViewBox { get { return useViewBox; } set { useViewBox = value; } } /// /// Gets or sets value indicating whether or not should be added 'width' and 'height' attributes to the svg tag /// public bool UseWidthAndHeight { get { return useWidthAndHeight; } set { useWidthAndHeight = value; } } /// /// Gets or sets the prefix for style classes and object ids /// public string PrefixStyle { get { return prefixStyle; } set { prefixStyle = value; } } #endregion #region Private XML Methods private void Save(string filename) { doc.Save(filename); } private void Save(Stream stream) { doc.Save(stream); } private void CreateDoc(string imagePathAndPrefix, int pageNo = 0) { doc = new XmlDocument(); doc.AutoIndent = true; if (prefixStyle == null) prefixStyle = string.Empty; graphics = new SvgGraphics(doc); graphics.PrefixStyle = prefixStyle + pageNo + "_"; graphics.SvgImageFormat = this.ImageFormat; graphics.EmbeddedImages = this.EmbedPictures; if (!EmbedPictures) graphics.ImageFilePrefix = imagePathAndPrefix; } private void UpdateSize(float width, float height) { if (useWidthAndHeight) graphics.Size = new SizeF(width, height); if (useViewBox) graphics.ViewBox = new RectangleF(0, 0, width, height); if (preserveAspectRatio != null) { FastString ratioVal = new FastString(preserveAspectRatio.Align.ToString()); if (preserveAspectRatio.MeetOrSlice != null) { ratioVal.Append(" ").Append(preserveAspectRatio.MeetOrSlice.ToString()); } doc.Root.SetProp("preserveAspectRatio", ratioVal.ToString()); } } private void AddImageWatermark(ReportPage page) { page.Watermark.DrawImage(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache), new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, page.WidthInPixels, page.HeightInPixels), page.Report, false); } private void AddTextWatermark(ReportPage page) { if (string.IsNullOrEmpty(page.Watermark.Text)) return; page.Watermark.DrawText(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache), new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, page.WidthInPixels, page.HeightInPixels), page.Report, false); } #endregion #region Protected Methods /// protected override void Start() { base.Start(); GeneratedStreams = new List(); currentPage = 0; pageWidth = 0; pageHeight = 0; if (FileName != "" && FileName != null) { path = Path.GetDirectoryName(FileName); fileNameWOext = Path.GetFileNameWithoutExtension(FileName); extension = Path.GetExtension(FileName); } else { path = ""; fileNameWOext = "svgreport"; } if (!HasMultipleFiles) CreateDoc(Path.Combine(path, fileNameWOext)); } /// /// Begin exporting of page /// /// protected override void ExportPageBegin(ReportPage page) { base.ExportPageBegin(page); if (path != null && path != "") pageFileName = Path.Combine(path, fileNameWOext + currentPage.ToString() + extension); else pageFileName = null; if (HasMultipleFiles) { CreateDoc(Path.Combine(path, fileNameWOext + currentPage.ToString()), currentPage); UpdateSize(page.WidthInPixels, page.HeightInPixels); } graphics.TranslateTransform(page.LeftMargin * Units.Millimeters, page.TopMargin * Units.Millimeters); // export bottom watermark if (page.Watermark.Enabled && !page.Watermark.ShowImageOnTop) AddImageWatermark(page); if (page.Watermark.Enabled && !page.Watermark.ShowTextOnTop) AddTextWatermark(page); // create a bookmark as a hyperlink target with kind is page number XmlItem xml = graphics.OpenContainer("a"); xml.SetProp("id", $"page{currentPage + 1}"); graphics.CloseContainer(); } /// protected override void ExportBand(BandBase band) { List spanCoords = new List(); base.ExportBand(band); ExportObj(band); foreach (Base c in band.AllObjects) { if (c is Table.TableCell) { Table.TableCell cell = (c as Table.TableCell); if (spanCoords.Contains(cell.Address)) continue; for (int i = 1; i < cell.ColSpan; i++) spanCoords.Add(new Point(cell.Address.X + i, cell.Address.Y)); for (int i = 1; i < cell.RowSpan; i++) spanCoords.Add(new Point(cell.Address.X, cell.Address.Y + i)); } else ExportObj(c); } } private void ExportObj(Base obj) { if (obj is ReportComponentBase reportComponent && reportComponent.Exportable) { if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) || !string.IsNullOrEmpty(reportComponent.Bookmark)) { XmlItem xml = graphics.OpenContainer(!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) ? "a" : "g"); if (!string.IsNullOrEmpty(reportComponent.Bookmark)) xml.SetProp("id", reportComponent.Bookmark); if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value)) { switch (reportComponent.Hyperlink.Kind) { case HyperlinkKind.URL: xml.SetProp("href", reportComponent.Hyperlink.Value.ToString()); break; case HyperlinkKind.PageNumber: xml.SetProp("href", $"#page{reportComponent.Hyperlink.Value}"); break; case HyperlinkKind.DetailPage: case HyperlinkKind.DetailReport: break; case HyperlinkKind.Bookmark: xml.SetProp("href", $"#{reportComponent.Hyperlink.Value}"); break; default: xml.SetProp("href", reportComponent.Hyperlink.Value.ToString()); break; } if (reportComponent.Hyperlink.OpenLinkInNewTab) xml.SetProp("target", "_blank"); } } reportComponent.Draw(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache)); if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) || !string.IsNullOrEmpty(reportComponent.Bookmark)) graphics.CloseContainer(); } } /// /// End exporting /// /// protected override void ExportPageEnd(ReportPage page) { base.ExportPageEnd(page); // export top watermark if (page.Watermark.Enabled && page.Watermark.ShowImageOnTop) AddImageWatermark(page); if (page.Watermark.Enabled && page.Watermark.ShowTextOnTop) AddTextWatermark(page); if (HasMultipleFiles) { if (Directory.Exists(path) && !string.IsNullOrEmpty(FileName)) { // desktop mode if (currentPage == 0) { // save first page in parent Stream Save(Stream); Stream.Position = 0; GeneratedStreams.Add(Stream); GeneratedFiles.Add(FileName); } else { // save all pages after first in files Save(pageFileName); GeneratedFiles.Add(pageFileName); } } else if (string.IsNullOrEmpty(path)) { // server mode, save in internal stream collection if (currentPage == 0) { // save first page in parent Stream Save(Stream); Stream.Position = 0; GeneratedStreams.Add(Stream); GeneratedFiles.Add(FileName); } else { MemoryStream pageStream = new MemoryStream(); Save(pageStream); pageStream.Position = 0; GeneratedStreams.Add(pageStream); GeneratedFiles.Add(pageFileName); } } } else { graphics.TranslateTransform(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters + page.HeightInPixels); if (pageWidth < page.WidthInPixels) pageWidth = page.WidthInPixels; pageHeight += page.HeightInPixels; } // increment page number currentPage++; } /// protected override void Finish() { if (!HasMultipleFiles) { UpdateSize(pageWidth, pageHeight); Save(Stream); Stream.Position = 0; GeneratedFiles.Add(FileName); } graphics.Dispose(); graphics = null; doc.Dispose(); doc = null; GeneratedFiles.Clear(); GeneratedStreams.Clear(); } /// protected override string GetFileFilter() { return new MyRes("FileFilters").Get("SVGFile"); } #endregion /// public override void Serialize(FRWriter writer) { base.Serialize(writer); writer.WriteValue("ImageFormat", ImageFormat); writer.WriteBool("HasMultipleFiles", HasMultipleFiles); writer.WriteValue("EmbedPictures", EmbedPictures); } /// /// Initializes a new instance of the class. /// public SVGExport() { HasMultipleFiles = false; pictures = true; ImageFormat = SVGImageFormat.Png; preserveAspectRatio = null; useWidthAndHeight = true; useViewBox = true; prefixStyle = "p"; } } }