SVGExport.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Collections;
  4. using System.Drawing.Imaging;
  5. using FastReport.Utils;
  6. using System.Drawing;
  7. namespace FastReport.Export.Svg
  8. {
  9. /// <summary>
  10. /// Represents the SVG export filter.
  11. /// </summary>
  12. public partial class SVGExport : ExportBase
  13. {
  14. private XmlDocument doc;
  15. #region Private Fields
  16. private string path;
  17. private string fileNameWOext;
  18. private string extension;
  19. private string pageFileName;
  20. private SvgGraphics graphics;
  21. private int currentPage;
  22. private bool pictures;
  23. private bool embedPictures;
  24. private bool useWidthAndHeight;
  25. private bool useViewBox;
  26. private AspectRatio preserveAspectRatio;
  27. private string prefixStyle;
  28. private float pageWidth;
  29. private float pageHeight;
  30. #endregion
  31. #region Properties
  32. /// <summary>
  33. /// Enable or disable the pictures in SVG export
  34. /// </summary>
  35. public bool Pictures
  36. {
  37. get { return pictures; }
  38. set { pictures = value; }
  39. }
  40. /// <summary>
  41. /// Gets or sets the image format used when exporting.
  42. /// </summary>
  43. public SVGImageFormat ImageFormat { get; set; }
  44. /// <summary>
  45. /// Embed images into svg
  46. /// </summary>
  47. public bool EmbedPictures
  48. {
  49. get { return embedPictures; }
  50. set { embedPictures = value; }
  51. }
  52. /// <summary>
  53. /// Gets or sets value indicating whether or not should to force uniform scaling of SVG document
  54. /// </summary>
  55. public AspectRatio PreserveAspectRatio
  56. {
  57. get { return preserveAspectRatio; }
  58. set { preserveAspectRatio = value; }
  59. }
  60. /// <summary>
  61. /// Gets or sets value indicating whether or not should be added 'viewBox' attribute to the svg tag
  62. /// </summary>
  63. public bool UseViewBox
  64. {
  65. get { return useViewBox; }
  66. set { useViewBox = value; }
  67. }
  68. /// <summary>
  69. /// Gets or sets value indicating whether or not should be added 'width' and 'height' attributes to the svg tag
  70. /// </summary>
  71. public bool UseWidthAndHeight
  72. {
  73. get { return useWidthAndHeight; }
  74. set { useWidthAndHeight = value; }
  75. }
  76. /// <summary>
  77. /// Gets or sets the prefix for style classes and object ids
  78. /// </summary>
  79. public string PrefixStyle
  80. {
  81. get { return prefixStyle; }
  82. set { prefixStyle = value; }
  83. }
  84. #endregion
  85. #region Private XML Methods
  86. private void Save(string filename)
  87. {
  88. doc.Save(filename);
  89. }
  90. private void Save(Stream stream)
  91. {
  92. doc.Save(stream);
  93. }
  94. private void CreateDoc(string imagePathAndPrefix, int pageNo = 0)
  95. {
  96. doc = new XmlDocument();
  97. doc.AutoIndent = true;
  98. if (prefixStyle == null)
  99. prefixStyle = string.Empty;
  100. graphics = new SvgGraphics(doc);
  101. graphics.PrefixStyle = prefixStyle + pageNo + "_";
  102. graphics.SvgImageFormat = this.ImageFormat;
  103. graphics.EmbeddedImages = this.EmbedPictures;
  104. if (!EmbedPictures)
  105. graphics.ImageFilePrefix = imagePathAndPrefix;
  106. }
  107. private void UpdateSize(float width, float height)
  108. {
  109. if (useWidthAndHeight)
  110. graphics.Size = new SizeF(width, height);
  111. if (useViewBox)
  112. graphics.ViewBox = new RectangleF(0, 0, width, height);
  113. if (preserveAspectRatio != null)
  114. {
  115. FastString ratioVal = new FastString(preserveAspectRatio.Align.ToString());
  116. if (preserveAspectRatio.MeetOrSlice != null)
  117. {
  118. ratioVal.Append(" ").Append(preserveAspectRatio.MeetOrSlice.ToString());
  119. }
  120. doc.Root.SetProp("preserveAspectRatio", ratioVal.ToString());
  121. }
  122. }
  123. private void AddImageWatermark(ReportPage page)
  124. {
  125. page.Watermark.DrawImage(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache),
  126. new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, page.WidthInPixels, page.HeightInPixels),
  127. page.Report, false);
  128. }
  129. private void AddTextWatermark(ReportPage page)
  130. {
  131. if (string.IsNullOrEmpty(page.Watermark.Text))
  132. return;
  133. page.Watermark.DrawText(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache),
  134. new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, page.WidthInPixels, page.HeightInPixels),
  135. page.Report, false);
  136. }
  137. #endregion
  138. #region Protected Methods
  139. /// <inheritdoc/>
  140. protected override void Start()
  141. {
  142. base.Start();
  143. GeneratedStreams = new List<Stream>();
  144. currentPage = 0;
  145. pageWidth = 0;
  146. pageHeight = 0;
  147. if (FileName != "" && FileName != null)
  148. {
  149. path = Path.GetDirectoryName(FileName);
  150. fileNameWOext = Path.GetFileNameWithoutExtension(FileName);
  151. extension = Path.GetExtension(FileName);
  152. }
  153. else
  154. {
  155. path = "";
  156. fileNameWOext = "svgreport";
  157. }
  158. if (!HasMultipleFiles)
  159. CreateDoc(Path.Combine(path, fileNameWOext));
  160. }
  161. /// <summary>
  162. /// Begin exporting of page
  163. /// </summary>
  164. /// <param name="page"></param>
  165. protected override void ExportPageBegin(ReportPage page)
  166. {
  167. base.ExportPageBegin(page);
  168. if (path != null && path != "")
  169. pageFileName = Path.Combine(path, fileNameWOext + currentPage.ToString() + extension);
  170. else
  171. pageFileName = null;
  172. if (HasMultipleFiles)
  173. {
  174. CreateDoc(Path.Combine(path, fileNameWOext + currentPage.ToString()), currentPage);
  175. UpdateSize(page.WidthInPixels, page.HeightInPixels);
  176. }
  177. graphics.TranslateTransform(page.LeftMargin * Units.Millimeters, page.TopMargin * Units.Millimeters);
  178. // export bottom watermark
  179. if (page.Watermark.Enabled && !page.Watermark.ShowImageOnTop)
  180. AddImageWatermark(page);
  181. if (page.Watermark.Enabled && !page.Watermark.ShowTextOnTop)
  182. AddTextWatermark(page);
  183. // create a bookmark as a hyperlink target with kind is page number
  184. XmlItem xml = graphics.OpenContainer("a");
  185. xml.SetProp("id", $"page{currentPage + 1}");
  186. graphics.CloseContainer();
  187. }
  188. /// <inheritdoc/>
  189. protected override void ExportBand(BandBase band)
  190. {
  191. List<Point> spanCoords = new List<Point>();
  192. base.ExportBand(band);
  193. ExportObj(band);
  194. foreach (Base c in band.AllObjects)
  195. {
  196. if (c is Table.TableCell)
  197. {
  198. Table.TableCell cell = (c as Table.TableCell);
  199. if (spanCoords.Contains(cell.Address))
  200. continue;
  201. for (int i = 1; i < cell.ColSpan; i++)
  202. spanCoords.Add(new Point(cell.Address.X + i, cell.Address.Y));
  203. for (int i = 1; i < cell.RowSpan; i++)
  204. spanCoords.Add(new Point(cell.Address.X, cell.Address.Y + i));
  205. }
  206. else
  207. ExportObj(c);
  208. }
  209. }
  210. private void ExportObj(Base obj)
  211. {
  212. if (obj is ReportComponentBase reportComponent && reportComponent.Exportable)
  213. {
  214. if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) || !string.IsNullOrEmpty(reportComponent.Bookmark))
  215. {
  216. XmlItem xml = graphics.OpenContainer(!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) ? "a" : "g");
  217. if (!string.IsNullOrEmpty(reportComponent.Bookmark))
  218. xml.SetProp("id", reportComponent.Bookmark);
  219. if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value))
  220. {
  221. switch (reportComponent.Hyperlink.Kind)
  222. {
  223. case HyperlinkKind.URL:
  224. xml.SetProp("href", reportComponent.Hyperlink.Value.ToString());
  225. break;
  226. case HyperlinkKind.PageNumber:
  227. xml.SetProp("href", $"#page{reportComponent.Hyperlink.Value}");
  228. break;
  229. case HyperlinkKind.DetailPage:
  230. case HyperlinkKind.DetailReport:
  231. break;
  232. case HyperlinkKind.Bookmark:
  233. xml.SetProp("href", $"#{reportComponent.Hyperlink.Value}");
  234. break;
  235. default:
  236. xml.SetProp("href", reportComponent.Hyperlink.Value.ToString());
  237. break;
  238. }
  239. if (reportComponent.Hyperlink.OpenLinkInNewTab)
  240. xml.SetProp("target", "_blank");
  241. }
  242. }
  243. reportComponent.Draw(new FRPaintEventArgs(graphics, 1, 1, Report.GraphicCache));
  244. if (!string.IsNullOrEmpty(reportComponent.Hyperlink.Value) || !string.IsNullOrEmpty(reportComponent.Bookmark))
  245. graphics.CloseContainer();
  246. }
  247. }
  248. /// <summary>
  249. /// End exporting
  250. /// </summary>
  251. /// <param name="page"></param>
  252. protected override void ExportPageEnd(ReportPage page)
  253. {
  254. base.ExportPageEnd(page);
  255. // export top watermark
  256. if (page.Watermark.Enabled && page.Watermark.ShowImageOnTop)
  257. AddImageWatermark(page);
  258. if (page.Watermark.Enabled && page.Watermark.ShowTextOnTop)
  259. AddTextWatermark(page);
  260. if (HasMultipleFiles)
  261. {
  262. if (Directory.Exists(path) && !string.IsNullOrEmpty(FileName))
  263. {
  264. // desktop mode
  265. if (currentPage == 0)
  266. {
  267. // save first page in parent Stream
  268. Save(Stream);
  269. Stream.Position = 0;
  270. GeneratedStreams.Add(Stream);
  271. GeneratedFiles.Add(FileName);
  272. }
  273. else
  274. {
  275. // save all pages after first in files
  276. Save(pageFileName);
  277. GeneratedFiles.Add(pageFileName);
  278. }
  279. }
  280. else if (string.IsNullOrEmpty(path))
  281. {
  282. // server mode, save in internal stream collection
  283. if (currentPage == 0)
  284. {
  285. // save first page in parent Stream
  286. Save(Stream);
  287. Stream.Position = 0;
  288. GeneratedStreams.Add(Stream);
  289. GeneratedFiles.Add(FileName);
  290. }
  291. else
  292. {
  293. MemoryStream pageStream = new MemoryStream();
  294. Save(pageStream);
  295. pageStream.Position = 0;
  296. GeneratedStreams.Add(pageStream);
  297. GeneratedFiles.Add(pageFileName);
  298. }
  299. }
  300. }
  301. else
  302. {
  303. graphics.TranslateTransform(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters + page.HeightInPixels);
  304. if (pageWidth < page.WidthInPixels)
  305. pageWidth = page.WidthInPixels;
  306. pageHeight += page.HeightInPixels;
  307. }
  308. // increment page number
  309. currentPage++;
  310. }
  311. /// <inheritdoc/>
  312. protected override void Finish()
  313. {
  314. if (!HasMultipleFiles)
  315. {
  316. UpdateSize(pageWidth, pageHeight);
  317. Save(Stream);
  318. Stream.Position = 0;
  319. GeneratedFiles.Add(FileName);
  320. }
  321. graphics.Dispose();
  322. graphics = null;
  323. doc.Dispose();
  324. doc = null;
  325. GeneratedFiles.Clear();
  326. GeneratedStreams.Clear();
  327. }
  328. /// <inheritdoc/>
  329. protected override string GetFileFilter()
  330. {
  331. return new MyRes("FileFilters").Get("SVGFile");
  332. }
  333. #endregion
  334. /// <inheritdoc/>
  335. public override void Serialize(FRWriter writer)
  336. {
  337. base.Serialize(writer);
  338. writer.WriteValue("ImageFormat", ImageFormat);
  339. writer.WriteBool("HasMultipleFiles", HasMultipleFiles);
  340. writer.WriteValue("EmbedPictures", EmbedPictures);
  341. }
  342. /// <summary>
  343. /// Initializes a new instance of the <see cref="SVGExport"/> class.
  344. /// </summary>
  345. public SVGExport()
  346. {
  347. HasMultipleFiles = false;
  348. pictures = true;
  349. ImageFormat = SVGImageFormat.Png;
  350. preserveAspectRatio = null;
  351. useWidthAndHeight = true;
  352. useViewBox = true;
  353. prefixStyle = "p";
  354. }
  355. }
  356. }