SvgGraphics.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Drawing.Drawing2D;
  5. using System.Drawing.Imaging;
  6. using System.Drawing.Text;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text;
  11. using FastReport.Utils;
  12. namespace FastReport
  13. {
  14. /// <summary>
  15. /// Specifies the image format in SVG export.
  16. /// </summary>
  17. public enum SVGImageFormat
  18. {
  19. /// <summary>
  20. /// Specifies the .png format.
  21. /// </summary>
  22. Png,
  23. /// <summary>
  24. /// Specifies the .jpg format.
  25. /// </summary>
  26. Jpeg
  27. }
  28. /// <summary>
  29. /// Drawing objects to a svg
  30. /// </summary>
  31. public class SvgGraphics : IGraphics
  32. {
  33. private XmlDocument xmlDocument;
  34. private Graphics graphics;
  35. private Bitmap internalImage;
  36. private XmlItem root;
  37. private SizeF sizeCache;
  38. private bool disposedValue = false; // To detect redundant calls
  39. private NumberFormatInfo numberFormat = CultureInfo.InvariantCulture.NumberFormat;
  40. private RectangleF viewBox;
  41. private RectangleF viewPort;
  42. private StringFormat measureFormat;
  43. private int imageId, patternId, clipId, imageFileId;
  44. private System.Drawing.Drawing2D.Matrix oldTransform;
  45. private Region oldClip;
  46. private bool needStateCheck;
  47. private Dictionary<Font, string> fontStyles;
  48. private readonly float fontDpiRatio = DrawUtils.ScreenDpi / 96f;
  49. private string prefixStyle = string.Empty;
  50. public XmlDocument XmlDocument { get { return xmlDocument; } }
  51. public SizeF Size
  52. {
  53. get { return sizeCache; }
  54. set
  55. {
  56. sizeCache = value;
  57. xmlDocument.Root.SetProp("width", GetString(value.Width));
  58. xmlDocument.Root.SetProp("height", GetString(value.Height));
  59. }
  60. }
  61. public RectangleF ViewBox
  62. {
  63. get { return viewBox; }
  64. set
  65. {
  66. viewBox = value;
  67. xmlDocument.Root.SetProp("viewBox", String.Format("{0} {1} {2} {3}",
  68. GetString(value.Left),
  69. GetString(value.Top),
  70. GetString(value.Width),
  71. GetString(value.Height)
  72. ));
  73. }
  74. }
  75. public RectangleF ViewPort
  76. {
  77. get { return viewPort; }
  78. set
  79. {
  80. viewPort = value;
  81. xmlDocument.Root.SetProp("viewPort", String.Format("{0} {1} {2} {3}",
  82. GetString(value.Left),
  83. GetString(value.Top),
  84. GetString(value.Width),
  85. GetString(value.Height)
  86. ));
  87. }
  88. }
  89. /// <summary>
  90. /// For setting namespace, clear all attributes on setting, therefore use this property before setting other svg options
  91. /// </summary>
  92. public IEnumerable<KeyValuePair<string, string>> Attributes
  93. {
  94. get
  95. {
  96. foreach (XmlProperty prop in xmlDocument.Root.Properties)
  97. {
  98. yield return new KeyValuePair<string, string>(prop.Key, prop.Value);
  99. }
  100. }
  101. set
  102. {
  103. xmlDocument.Root.Properties = null;
  104. foreach (KeyValuePair<string, string> kv in value)
  105. xmlDocument.Root.SetProp(kv.Key, kv.Value);
  106. }
  107. }
  108. public float DpiX => 96;
  109. public float DpiY => 96;
  110. // TODO: the following 4 properties
  111. public TextRenderingHint TextRenderingHint { get; set; }
  112. public InterpolationMode InterpolationMode { get; set; }
  113. public SmoothingMode SmoothingMode { get; set; }
  114. public CompositingQuality CompositingQuality { get; set; }
  115. public Graphics Graphics => graphics;
  116. public System.Drawing.Drawing2D.Matrix Transform
  117. {
  118. get
  119. {
  120. needStateCheck = true;
  121. return graphics.Transform;
  122. }
  123. set
  124. {
  125. needStateCheck = true;
  126. graphics.Transform = value;
  127. }
  128. }
  129. public GraphicsUnit PageUnit { get; set; }
  130. public bool IsClipEmpty => graphics.IsClipEmpty;
  131. public Region Clip
  132. {
  133. get
  134. {
  135. needStateCheck = true;
  136. return graphics.Clip;
  137. }
  138. set
  139. {
  140. needStateCheck = true;
  141. graphics.Clip = value;
  142. }
  143. }
  144. public bool EmbeddedImages { get; set; }
  145. public SVGImageFormat SvgImageFormat { get; set; }
  146. public string ImageFilePrefix { get; set; }
  147. /// <summary>
  148. /// Initialize a new Graphics for SVG, it's rendered to xml, layer by layer, not one image,
  149. /// set the Size of this graphics in Size property
  150. /// </summary>
  151. public SvgGraphics(XmlDocument xmlDocument)
  152. {
  153. this.xmlDocument = xmlDocument;
  154. root = this.xmlDocument.Root;
  155. root.Name = "svg";
  156. root.SetProp("xmlns", "http://www.w3.org/2000/svg");
  157. // create "style" and "defs" items at the top of document
  158. root.FindItem("style").SetProp("type", "text/css");
  159. root.FindItem("defs");
  160. this.internalImage = new Bitmap(1, 1);
  161. this.graphics = Graphics.FromImage(internalImage);
  162. this.measureFormat = StringFormat.GenericTypographic;
  163. oldTransform = new System.Drawing.Drawing2D.Matrix();
  164. oldClip = new Region();
  165. fontStyles = new Dictionary<Font, string>();
  166. needStateCheck = true;
  167. EmbeddedImages = true;
  168. ImageFilePrefix = "Image";
  169. }
  170. /// <summary>
  171. /// Sets or gets prefix for style and object ids
  172. /// </summary>
  173. public string PrefixStyle
  174. {
  175. get
  176. {
  177. return prefixStyle;
  178. }
  179. set
  180. {
  181. prefixStyle = value;
  182. }
  183. }
  184. #region IDisposable Support
  185. protected virtual void Dispose(bool disposing)
  186. {
  187. if (!disposedValue)
  188. {
  189. if (disposing)
  190. {
  191. if (graphics != null)
  192. graphics.Dispose();
  193. graphics = null;
  194. if (internalImage != null)
  195. internalImage.Dispose();
  196. internalImage = null;
  197. if (oldTransform != null)
  198. oldTransform.Dispose();
  199. oldTransform = null;
  200. if (oldClip != null)
  201. oldClip.Dispose();
  202. oldClip = null;
  203. }
  204. disposedValue = true;
  205. }
  206. }
  207. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  208. // ~SVGGraphicsRenderer() {
  209. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  210. // Dispose(false);
  211. // }
  212. // This code added to correctly implement the disposable pattern.
  213. public void Dispose()
  214. {
  215. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  216. Dispose(true);
  217. // TODO: uncomment the following line if the finalizer is overridden above.
  218. // GC.SuppressFinalize(this);
  219. }
  220. #endregion
  221. #region Internal methods
  222. private string GetString(float value)
  223. {
  224. return Math.Round(value, 2).ToString(numberFormat);
  225. }
  226. private float SpaceWidth(Font drawFont)
  227. {
  228. return graphics.MeasureString("1 2", drawFont, int.MaxValue, measureFormat).Width -
  229. graphics.MeasureString("12", drawFont, int.MaxValue, measureFormat).Width;
  230. }
  231. private string GetStringAlpha(Color color)
  232. {
  233. return GetString(color.A / 255f);
  234. }
  235. private string GetStringRGB(Color color)
  236. {
  237. return ColorTranslator.ToHtml(color);
  238. }
  239. private XmlItem AddItem(string name)
  240. {
  241. // these comparisons are quite expensive, try to avoid them with needStateCheck
  242. bool transformChanged = needStateCheck ? !oldTransform.Equals(Transform) : false;
  243. bool clipChanged = needStateCheck ? !oldClip.Equals(Clip, graphics) : false;
  244. if (transformChanged || clipChanged)
  245. {
  246. root = xmlDocument.Root.Add();
  247. root.Name = "g";
  248. // text positioning compatible with .net (commented out due to problems in some SVG readers). Replaced with top offset in AddString
  249. //root.SetProp("dominant-baseline", "text-before-edge");
  250. // transform
  251. float[] m = Transform.Elements;
  252. if (m.Length == 6 && (m[0] != 1f || m[1] != 0f || m[2] != 0f || m[3] != 1f || m[4] != 0f || m[5] != 0f))
  253. {
  254. root.SetProp("transform", String.Format("matrix({0},{1},{2},{3},{4},{5})",
  255. GetString(m[0]), GetString(m[1]), GetString(m[2]), GetString(m[3]), GetString(m[4]), GetString(m[5])));
  256. }
  257. // clip
  258. if (!Clip.IsInfinite(graphics))
  259. {
  260. root.SetProp("clip-path", "url(#" + AddDefsClip() + ")");
  261. }
  262. oldTransform.Dispose();
  263. oldTransform = Transform.Clone();
  264. oldClip.Dispose();
  265. oldClip = Clip.Clone();
  266. }
  267. needStateCheck = false;
  268. XmlItem result = root.Add();
  269. result.Name = name;
  270. return result;
  271. }
  272. private string AddDefsClip()
  273. {
  274. System.Drawing.Drawing2D.Matrix identityMatrix = new System.Drawing.Drawing2D.Matrix();
  275. RectangleF[] clipRects = Clip.GetRegionScans(identityMatrix);
  276. identityMatrix.Dispose();
  277. XmlItem defsItem = xmlDocument.Root.FindItem("defs");
  278. // search for existing clip
  279. if (clipRects.Length == 1)
  280. {
  281. string xStr = GetString(clipRects[0].X);
  282. string yStr = GetString(clipRects[0].Y);
  283. string widthStr = GetString(clipRects[0].Width);
  284. string heightStr = GetString(clipRects[0].Height);
  285. foreach (XmlItem item in defsItem.Items)
  286. {
  287. if (item.Name == "clipPath" && item.Count == 1)
  288. {
  289. if (item[0].GetProp("x") == xStr &&
  290. item[0].GetProp("y") == yStr &&
  291. item[0].GetProp("width") == widthStr &&
  292. item[0].GetProp("height") == heightStr)
  293. return item.GetProp("id");
  294. }
  295. }
  296. }
  297. clipId++;
  298. string clipIdStr = prefixStyle + "clip" + clipId.ToString();
  299. XmlItem clipItem = defsItem.Add();
  300. clipItem.Name = "clipPath";
  301. clipItem.SetProp("id", clipIdStr);
  302. foreach (RectangleF rect in clipRects)
  303. {
  304. XmlItem clipRectItem = clipItem.Add();
  305. clipRectItem.Name = "rect";
  306. clipRectItem.SetProp("x", GetString(rect.X));
  307. clipRectItem.SetProp("y", GetString(rect.Y));
  308. clipRectItem.SetProp("width", GetString(rect.Width));
  309. clipRectItem.SetProp("height", GetString(rect.Height));
  310. }
  311. return clipIdStr;
  312. }
  313. private string AddDefsImage(Image bmp, float width, float height)
  314. {
  315. string href = "";
  316. using (MemoryStream stream = new MemoryStream())
  317. {
  318. bmp.Save(stream, ImageFormat.Png);
  319. href = "data:image/png; base64," + Convert.ToBase64String(stream.ToArray());
  320. }
  321. string widthStr = GetString(width);
  322. string heightStr = GetString(height);
  323. XmlItem defsItem = xmlDocument.Root.FindItem("defs");
  324. // search for existing image
  325. foreach (XmlItem item in defsItem.Items)
  326. {
  327. if (item.Name == "image")
  328. {
  329. if (item.GetProp("width") == widthStr &&
  330. item.GetProp("height") == heightStr &&
  331. item.GetProp("href") == href)
  332. return item.GetProp("id");
  333. }
  334. }
  335. imageId++;
  336. string result = prefixStyle + "img" + imageId.ToString();
  337. XmlItem imgItem = defsItem.Add();
  338. imgItem.Name = "image";
  339. imgItem.SetProp("id", result);
  340. imgItem.SetProp("width", widthStr);
  341. imgItem.SetProp("height", heightStr);
  342. imgItem.SetProp("href", href);
  343. return result;
  344. }
  345. private string AddDefsImagePattern(Image bmp, float x, float y, float width, float height)
  346. {
  347. string xStr = GetString(x);
  348. string yStr = GetString(y);
  349. string widthStr = GetString(width);
  350. string heightStr = GetString(height);
  351. string imgId = AddDefsImage(bmp, width, height);
  352. XmlItem defsItem = xmlDocument.Root.FindItem("defs");
  353. // search for existing pattern
  354. foreach (XmlItem item in defsItem.Items)
  355. {
  356. if (item.Name == "pattern")
  357. {
  358. if (item.GetProp("x") == xStr &&
  359. item.GetProp("y") == yStr &&
  360. item.GetProp("width") == widthStr &&
  361. item.GetProp("height") == heightStr)
  362. {
  363. if (item.Count == 1 && item.Items[0].Name == "use" && item.Items[0].GetProp("href") == "#" + imgId)
  364. return item.GetProp("id");
  365. }
  366. }
  367. }
  368. patternId++;
  369. string result = prefixStyle + "p" + patternId.ToString();
  370. XmlItem pItem = defsItem.Add();
  371. pItem.Name = "pattern";
  372. pItem.SetProp("id", result);
  373. pItem.SetProp("patternUnits", "userSpaceOnUse");
  374. pItem.SetProp("x", xStr);
  375. pItem.SetProp("y", yStr);
  376. pItem.SetProp("width", widthStr);
  377. pItem.SetProp("height", heightStr);
  378. XmlItem imgItem = pItem.Add();
  379. imgItem.Name = "use";
  380. imgItem.SetProp("href", "#" + imgId);
  381. return result;
  382. }
  383. private void AddStroke(List<XmlProperty> properties, Pen pen)
  384. {
  385. if (pen != null)
  386. {
  387. if (pen.Width != 1)
  388. properties.Add(XmlProperty.Create("stroke-width", GetString(pen.Width)));
  389. properties.Add(XmlProperty.Create("stroke", GetStringRGB(pen.Color)));
  390. if (pen.Color.A < 255)
  391. properties.Add(XmlProperty.Create("stroke-opacity", GetStringAlpha(pen.Color)));
  392. if (pen.DashStyle != DashStyle.Solid)
  393. {
  394. string strokeDashArray = "";
  395. foreach (float f in pen.DashPattern)
  396. {
  397. strokeDashArray += GetString(f * pen.Width) + " ";
  398. }
  399. strokeDashArray = strokeDashArray.Trim();
  400. properties.Add(XmlProperty.Create("stroke-dasharray", strokeDashArray));
  401. }
  402. }
  403. }
  404. private void AddFill(List<XmlProperty> properties, Brush brush)
  405. {
  406. if (brush == null)
  407. {
  408. properties.Add(XmlProperty.Create("fill", "none"));
  409. }
  410. else if (brush is SolidBrush)
  411. {
  412. SolidBrush b = brush as SolidBrush;
  413. if (b.Color != Color.Black)
  414. {
  415. properties.Add(XmlProperty.Create("fill", GetStringRGB(b.Color)));
  416. if (b.Color.A < 255)
  417. properties.Add(XmlProperty.Create("fill-opacity", GetStringAlpha(b.Color)));
  418. }
  419. }
  420. else if (brush is LinearGradientBrush || brush is PathGradientBrush)
  421. {
  422. RectangleF rect = new RectangleF();
  423. WrapMode wrapMode = WrapMode.Tile;
  424. if (brush is LinearGradientBrush)
  425. {
  426. rect = (brush as LinearGradientBrush).Rectangle;
  427. wrapMode = (brush as LinearGradientBrush).WrapMode;
  428. }
  429. else if (brush is PathGradientBrush)
  430. {
  431. rect = (brush as PathGradientBrush).Rectangle;
  432. wrapMode = (brush as PathGradientBrush).WrapMode;
  433. }
  434. switch (wrapMode)
  435. {
  436. case WrapMode.TileFlipX:
  437. rect = new RectangleF(rect.X, rect.Y, rect.Width * 2, rect.Height);
  438. break;
  439. case WrapMode.TileFlipY:
  440. rect = new RectangleF(rect.X, rect.Y, rect.Width, rect.Height * 2);
  441. break;
  442. case WrapMode.TileFlipXY:
  443. rect = new RectangleF(rect.X, rect.Y, rect.Width * 2, rect.Height * 2);
  444. break;
  445. }
  446. using (Bitmap bmp = new Bitmap((int)rect.Width, (int)rect.Height))
  447. {
  448. using (Graphics g = Graphics.FromImage(bmp))
  449. {
  450. g.Clear(Color.Transparent);
  451. g.TranslateTransform(-rect.X, -rect.Y);
  452. g.FillRectangle(brush, rect);
  453. }
  454. properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(bmp, rect.X, rect.Y, rect.Width, rect.Height) + ")"));
  455. }
  456. }
  457. else if (brush is HatchBrush)
  458. {
  459. HatchBrush b = brush as HatchBrush;
  460. using (Bitmap bmp = new Bitmap(8, 8))
  461. {
  462. using (Graphics g = Graphics.FromImage(bmp))
  463. {
  464. g.Clear(Color.Transparent);
  465. g.FillRectangle(b, 0, 0, 8, 8);
  466. }
  467. properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(bmp, 0, 0, 8, 8) + ")"));
  468. }
  469. }
  470. else if (brush is TextureBrush)
  471. {
  472. // TODO wrap mode
  473. TextureBrush b = brush as TextureBrush;
  474. if (b.Image != null)
  475. properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(b.Image, 0, 0, b.Image.Width, b.Image.Height) + ")"));
  476. }
  477. }
  478. private void AddEllipse(Pen pen, Brush brush, float left, float top, float width, float height)
  479. {
  480. XmlItem obj = AddItem("ellipse");
  481. List<XmlProperty> properties = new List<XmlProperty>();
  482. properties.Add(XmlProperty.Create("cx", GetString(left + width / 2)));
  483. properties.Add(XmlProperty.Create("cy", GetString(top + height / 2)));
  484. properties.Add(XmlProperty.Create("rx", GetString(width / 2)));
  485. properties.Add(XmlProperty.Create("ry", GetString(height / 2)));
  486. AddStroke(properties, pen);
  487. AddFill(properties, brush);
  488. obj.Properties = properties.ToArray();
  489. }
  490. private void AddRectangle(Pen pen, Brush brush, float left, float top, float width, float height)
  491. {
  492. XmlItem obj = AddItem("rect");
  493. List<XmlProperty> properties = new List<XmlProperty>();
  494. properties.Add(XmlProperty.Create("x", GetString(left)));
  495. properties.Add(XmlProperty.Create("y", GetString(top)));
  496. properties.Add(XmlProperty.Create("width", GetString(width)));
  497. properties.Add(XmlProperty.Create("height", GetString(height)));
  498. AddStroke(properties, pen);
  499. AddFill(properties, brush);
  500. obj.Properties = properties.ToArray();
  501. }
  502. private void AddPolygon(Pen pen, Brush brush, PointF[] points)
  503. {
  504. if (points.Length == 0)
  505. return;
  506. XmlItem obj = AddItem("polygon");
  507. List<XmlProperty> properties = new List<XmlProperty>();
  508. StringBuilder sbPoints = new StringBuilder();
  509. foreach (PointF point in points)
  510. sbPoints.Append(GetString(point.X)).Append(" ").Append(GetString(point.Y)).Append(" ");
  511. if (sbPoints.Length > 0)
  512. sbPoints.Length--;
  513. properties.Add(XmlProperty.Create("points", sbPoints.ToString()));
  514. AddStroke(properties, pen);
  515. AddFill(properties, brush);
  516. obj.Properties = properties.ToArray();
  517. }
  518. private void AddPath(Pen pen, Brush brush, GraphicsPath path)
  519. {
  520. if (path.PointCount == 0)
  521. return;
  522. XmlItem obj = AddItem("path");
  523. List<XmlProperty> properties = new List<XmlProperty>();
  524. StringBuilder sbd = new StringBuilder();
  525. byte[] types = path.PathTypes;
  526. PointF[] points = path.PathPoints;
  527. int c_count = 0;
  528. for (int i = 0; i < points.Length; i++)
  529. {
  530. byte t = types[i];
  531. if ((t & 0x7) == 0)
  532. {
  533. // MoveTo command
  534. c_count = 0;
  535. sbd.Append("M").Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" ");
  536. }
  537. else if ((t & 0x7) == 1)
  538. {
  539. // LineTo command
  540. c_count = 0;
  541. sbd.Append("L").Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" ");
  542. }
  543. else if ((t & 0x7) == 3)
  544. {
  545. // Cubic bezier curve command has 3 points. The C symbol must be added once at the start of sequence
  546. if (c_count == 0)
  547. sbd.Append("C");
  548. c_count++;
  549. if (c_count >= 3)
  550. c_count = 0;
  551. sbd.Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" ");
  552. }
  553. if ((t & 0x80) != 0)
  554. {
  555. // Close figure flag
  556. sbd.Append("z ");
  557. }
  558. }
  559. if (sbd.Length > 0)
  560. sbd.Length--;
  561. properties.Add(XmlProperty.Create("d", sbd.ToString()));
  562. AddStroke(properties, pen);
  563. AddFill(properties, brush);
  564. obj.Properties = properties.ToArray();
  565. }
  566. private void AddFontStyle(List<XmlProperty> properties, Font font)
  567. {
  568. if (!fontStyles.ContainsKey(font))
  569. {
  570. fontStyles.Add(font, prefixStyle + "st" + fontStyles.Count.ToString());
  571. string styles = "\r\n ";
  572. foreach (KeyValuePair<Font, string> kv in fontStyles)
  573. {
  574. styles += " ." + kv.Value + "{" +
  575. "font-family:" + kv.Key.Name + ";" +
  576. "font-size:" + GetString(kv.Key.Size / 0.75f * fontDpiRatio) + "px;";
  577. if ((kv.Key.Style & FontStyle.Italic) == FontStyle.Italic)
  578. styles += "font-style:italic;";
  579. if ((kv.Key.Style & FontStyle.Bold) == FontStyle.Bold)
  580. styles += "font-weight:bold;";
  581. string decoration = "";
  582. if ((kv.Key.Style & FontStyle.Strikeout) != 0)
  583. decoration = "line-through";
  584. if ((kv.Key.Style & FontStyle.Underline) != 0)
  585. decoration += " underline";
  586. if (decoration != "")
  587. styles += "text-decoration:" + decoration + ";";
  588. styles += "}\r\n ";
  589. }
  590. xmlDocument.Root.FindItem("style").Value = styles;
  591. }
  592. properties.Add(XmlProperty.Create("class", fontStyles[font]));
  593. }
  594. private void AddString(string text, Font font, Brush brush, float left, float top, string anchor, string direction)
  595. {
  596. XmlItem obj = AddItem("text");
  597. List<XmlProperty> properties = new List<XmlProperty>();
  598. properties.Add(XmlProperty.Create("x", GetString(left + SpaceWidth(font) / 2)));
  599. properties.Add(XmlProperty.Create("y", GetString(top + font.Height * 0.75f)));
  600. if (anchor != "")
  601. properties.Add(XmlProperty.Create("text-anchor", anchor));
  602. if (direction != "")
  603. properties.Add(XmlProperty.Create("direction", direction));
  604. AddFontStyle(properties, font);
  605. AddFill(properties, brush);
  606. obj.Value = text;
  607. obj.Properties = properties.ToArray();
  608. }
  609. #endregion
  610. #region Draw and measure text
  611. public void DrawString(string text, Font font, Brush brush, float left, float top)
  612. {
  613. AddString(text, font, brush, left, top, "", "");
  614. }
  615. public void DrawString(string text, Font font, Brush brush, RectangleF rect, StringFormat format)
  616. {
  617. // TODO: trimming
  618. float lineHeight = font.GetHeight(graphics);
  619. List<string> lines = new List<string>();
  620. if (rect.Width == 0 && rect.Height == 0)
  621. {
  622. lines.Add(text);
  623. }
  624. else
  625. {
  626. if ((format.FormatFlags & StringFormatFlags.NoWrap) == 0)
  627. format.Trimming = StringTrimming.Word;
  628. format.FormatFlags |= StringFormatFlags.LineLimit;
  629. SizeF bound = new SizeF(rect.Width, lineHeight * 1.25f);
  630. string[] paragraphs = text.Split('\n');
  631. for (int i = 0; i < paragraphs.Length; i++)
  632. {
  633. string s = paragraphs[i];
  634. if (s.Length > 0 && s[s.Length - 1] == '\r')
  635. s = s.Remove(s.Length - 1);
  636. while (s.Length > 0)
  637. {
  638. int charFill;
  639. int linesFill;
  640. graphics.MeasureString(s, font, bound, format, out charFill, out linesFill);
  641. if (linesFill == 0)
  642. break;
  643. lines.Add(s.Substring(0, charFill));
  644. s = s.Substring(charFill);
  645. }
  646. }
  647. }
  648. float dx = 0;
  649. string anchor = "";
  650. if (format.Alignment == StringAlignment.Center)
  651. {
  652. dx = (rect.Width - SpaceWidth(font)) / 2;
  653. anchor = "middle";
  654. }
  655. else if (format.Alignment == StringAlignment.Far)
  656. {
  657. dx = rect.Width - SpaceWidth(font);
  658. anchor = "end";
  659. }
  660. float dy = 0;
  661. if (format.LineAlignment == StringAlignment.Center)
  662. {
  663. dy = (rect.Height - lines.Count * lineHeight) / 2;
  664. }
  665. else if (format.LineAlignment == StringAlignment.Far)
  666. {
  667. dy = rect.Height - lines.Count * lineHeight;
  668. }
  669. string direction = "";
  670. if ((format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0)
  671. {
  672. direction = "rtl";
  673. if (format.Alignment == StringAlignment.Far)
  674. {
  675. dx = 0;
  676. anchor = "end";
  677. }
  678. else if (format.Alignment == StringAlignment.Near)
  679. {
  680. dx = rect.Width - SpaceWidth(font);
  681. anchor = "";
  682. }
  683. }
  684. for (int i = 0; i < lines.Count; i++)
  685. {
  686. AddString(lines[i], font, brush, rect.Left + dx, rect.Top + dy + lineHeight * i, anchor, direction);
  687. }
  688. }
  689. public void DrawString(string text, Font font, Brush brush, RectangleF rect)
  690. {
  691. DrawString(text, font, brush, rect, StringFormat.GenericDefault);
  692. }
  693. public void DrawString(string text, Font font, Brush brush, float left, float top, StringFormat format)
  694. {
  695. DrawString(text, font, brush, new RectangleF(left, top, 0, 0), format);
  696. }
  697. public void DrawString(string text, Font font, Brush brush, PointF point, StringFormat format)
  698. {
  699. DrawString(text, font, brush, point.X, point.Y, format);
  700. }
  701. public Region[] MeasureCharacterRanges(string text, Font font, RectangleF textRect, StringFormat format)
  702. {
  703. return this.graphics.MeasureCharacterRanges(text, font, textRect, format);
  704. }
  705. public SizeF MeasureString(string text, Font font, SizeF layoutArea, StringFormat format)
  706. {
  707. return this.graphics.MeasureString(text, font, layoutArea, format);
  708. }
  709. public SizeF MeasureString(string text, Font font)
  710. {
  711. return this.graphics.MeasureString(text, font);
  712. }
  713. public SizeF MeasureString(string text, Font font, SizeF size)
  714. {
  715. return this.graphics.MeasureString(text, font, size);
  716. }
  717. public SizeF MeasureString(string text, Font font, int width, StringFormat format)
  718. {
  719. return this.graphics.MeasureString(text, font, width, format);
  720. }
  721. public void MeasureString(string text, Font font, SizeF size, StringFormat format, out int charsFit, out int linesFit)
  722. {
  723. this.graphics.MeasureString(text, font, size, format, out charsFit, out linesFit);
  724. }
  725. #endregion
  726. #region Draw images
  727. public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit srcUnit)
  728. {
  729. if (image == null || image.Width == 0 || image.Height == 0)
  730. return;
  731. if (srcRect.X != 0 || srcRect.Y != 0 || srcRect.Width != image.Width || srcRect.Height != image.Height)
  732. {
  733. // crop source image
  734. using (Bitmap newImage = new Bitmap((int)srcRect.Width, (int)srcRect.Height))
  735. {
  736. using (Graphics g = Graphics.FromImage(newImage))
  737. {
  738. g.Clear(Color.Transparent);
  739. g.DrawImageUnscaled(image, 0, 0);
  740. }
  741. DrawImage(newImage, destRect);
  742. }
  743. }
  744. else
  745. {
  746. DrawImage(image, destRect);
  747. }
  748. }
  749. public void DrawImage(Image image, RectangleF rect)
  750. {
  751. DrawImage(image, rect.X, rect.Y, rect.Width, rect.Height);
  752. }
  753. public void DrawImage(Image image, float x, float y, float width, float height)
  754. {
  755. if (image == null || image.Width == 0 || image.Height == 0)
  756. return;
  757. XmlItem obj = AddItem("image");
  758. List<XmlProperty> properties = new List<XmlProperty>();
  759. properties.Add(XmlProperty.Create("x", GetString(x)));
  760. properties.Add(XmlProperty.Create("y", GetString(y)));
  761. properties.Add(XmlProperty.Create("width", GetString(width)));
  762. properties.Add(XmlProperty.Create("height", GetString(height)));
  763. string href = "";
  764. if (EmbeddedImages)
  765. {
  766. using (MemoryStream stream = new MemoryStream())
  767. {
  768. image.Save(stream, SvgImageFormat == SVGImageFormat.Png ? ImageFormat.Png : ImageFormat.Jpeg);
  769. href = "data:image/" + (SvgImageFormat == SVGImageFormat.Png ? "png" : "jpg") + "; base64," + Convert.ToBase64String(stream.ToArray());
  770. }
  771. }
  772. else
  773. {
  774. href = ImageFilePrefix + "." + imageFileId.ToString() + "." + (SvgImageFormat == SVGImageFormat.Png ? "png" : "jpg");
  775. image.Save(href);
  776. href = Path.GetFileName(href);
  777. imageFileId++;
  778. }
  779. properties.Add(XmlProperty.Create("href", href));
  780. obj.Properties = properties.ToArray();
  781. }
  782. public void DrawImage(Image image, PointF[] points)
  783. {
  784. if (image == null || image.Width == 0 || image.Height == 0)
  785. return;
  786. if (points.Length != 3)
  787. return;
  788. PointF p0 = points[0];
  789. PointF p1 = points[1];
  790. PointF p2 = points[2];
  791. RectangleF rect = new RectangleF(0, 0, image.Width, image.Height);
  792. float m11 = (p1.X - p0.X) / rect.Width;
  793. float m12 = (p1.Y - p0.Y) / rect.Width;
  794. float m21 = (p2.X - p0.X) / rect.Height;
  795. float m22 = (p2.Y - p0.Y) / rect.Height;
  796. IGraphicsState state = Save();
  797. MultiplyTransform(new System.Drawing.Drawing2D.Matrix(m11, m12, m21, m22, p0.X, p0.Y), MatrixOrder.Prepend);
  798. DrawImage(image, rect);
  799. Restore(state);
  800. }
  801. public void DrawImage(Image image, float x, float y)
  802. {
  803. if (image == null)
  804. return;
  805. DrawImage(image, x, y, image.Width, image.Height);
  806. }
  807. public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr)
  808. {
  809. DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttr);
  810. }
  811. public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs)
  812. {
  813. using (Image newImage = new Bitmap(image.Width, image.Height))
  814. {
  815. using (Graphics g = Graphics.FromImage(newImage))
  816. {
  817. g.Clear(Color.Transparent);
  818. g.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height), 0, 0, image.Width, image.Height, srcUnit, imageAttrs);
  819. }
  820. DrawImage(newImage, destRect, new RectangleF(srcX, srcY, srcWidth, srcHeight), srcUnit);
  821. }
  822. }
  823. public void DrawImageUnscaled(Image image, Rectangle rect)
  824. {
  825. if (image == null)
  826. return;
  827. DrawImage(image, rect, 0, 0, rect.Width, rect.Height, GraphicsUnit.Pixel, null);
  828. }
  829. #endregion
  830. #region Draw geometry
  831. public void DrawArc(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle)
  832. {
  833. using (GraphicsPath path = new GraphicsPath())
  834. {
  835. path.AddArc(x, y, width, height, startAngle, sweepAngle);
  836. AddPath(pen, null, path);
  837. }
  838. }
  839. public void DrawCurve(Pen pen, PointF[] points, int offset, int numberOfSegments, float tension)
  840. {
  841. using (GraphicsPath path = new GraphicsPath())
  842. {
  843. path.AddCurve(points, offset, numberOfSegments, tension);
  844. AddPath(pen, null, path);
  845. }
  846. }
  847. public void DrawEllipse(Pen pen, float left, float top, float width, float height)
  848. {
  849. AddEllipse(pen, null, left, top, width, height);
  850. }
  851. public void DrawEllipse(Pen pen, RectangleF rect)
  852. {
  853. DrawEllipse(pen, rect.Left, rect.Top, rect.Width, rect.Height);
  854. }
  855. public void DrawLine(Pen pen, float x1, float y1, float x2, float y2)
  856. {
  857. //TODO add caps
  858. XmlItem obj = AddItem("line");
  859. List<XmlProperty> properties = new List<XmlProperty>();
  860. properties.Add(XmlProperty.Create("x1", GetString(x1)));
  861. properties.Add(XmlProperty.Create("y1", GetString(y1)));
  862. properties.Add(XmlProperty.Create("x2", GetString(x2)));
  863. properties.Add(XmlProperty.Create("y2", GetString(y2)));
  864. AddStroke(properties, pen);
  865. obj.Properties = properties.ToArray();
  866. }
  867. public void DrawLine(Pen pen, PointF p1, PointF p2)
  868. {
  869. DrawLine(pen, p1.X, p1.Y, p2.X, p2.Y);
  870. }
  871. public void DrawLines(Pen pen, PointF[] points)
  872. {
  873. if (points.Length < 2)
  874. return;
  875. for (int i = 0; i < points.Length - 1; i++)
  876. {
  877. DrawLine(pen, points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y);
  878. }
  879. }
  880. public void DrawPath(Pen outlinePen, GraphicsPath path)
  881. {
  882. AddPath(outlinePen, null, path);
  883. }
  884. public void DrawPie(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle)
  885. {
  886. using (GraphicsPath path = new GraphicsPath())
  887. {
  888. path.AddPie(x, y, width, height, startAngle, sweepAngle);
  889. AddPath(pen, null, path);
  890. }
  891. }
  892. public void DrawPolygon(Pen pen, PointF[] points)
  893. {
  894. AddPolygon(pen, null, points);
  895. }
  896. public void DrawPolygon(Pen pen, Point[] points)
  897. {
  898. if (points.Length == 0)
  899. return;
  900. PointF[] pointsF = new PointF[points.Length];
  901. for (int i = 0; i < points.Length; i++)
  902. pointsF[i] = points[i];
  903. DrawPolygon(pen, pointsF);
  904. }
  905. public void DrawRectangle(Pen pen, float left, float top, float width, float height)
  906. {
  907. AddRectangle(pen, null, left, top, width, height);
  908. }
  909. public void DrawRectangle(Pen pen, Rectangle rect)
  910. {
  911. DrawRectangle(pen, rect.Left, rect.Top, rect.Width, rect.Height);
  912. }
  913. #endregion
  914. #region Fill geometry
  915. public void FillEllipse(Brush brush, float left, float top, float width, float height)
  916. {
  917. AddEllipse(null, brush, left, top, width, height);
  918. }
  919. public void FillEllipse(Brush brush, RectangleF rect)
  920. {
  921. FillEllipse(brush, rect.Left, rect.Top, rect.Width, rect.Height);
  922. }
  923. public void FillPath(Brush brush, GraphicsPath path)
  924. {
  925. AddPath(null, brush, path);
  926. }
  927. public void FillPie(Brush brush, float x, float y, float width, float height, float startAngle, float sweepAngle)
  928. {
  929. using (GraphicsPath path = new GraphicsPath())
  930. {
  931. path.AddPie(x, y, width, height, startAngle, sweepAngle);
  932. AddPath(null, brush, path);
  933. }
  934. }
  935. public void FillPolygon(Brush brush, PointF[] points)
  936. {
  937. AddPolygon(null, brush, points);
  938. }
  939. public void FillPolygon(Brush brush, Point[] points)
  940. {
  941. if (points.Length == 0)
  942. return;
  943. PointF[] pointsF = new PointF[points.Length];
  944. for (int i = 0; i < points.Length; i++)
  945. pointsF[i] = points[i];
  946. FillPolygon(brush, pointsF);
  947. }
  948. public void FillRectangle(Brush brush, float left, float top, float width, float height)
  949. {
  950. AddRectangle(null, brush, left, top, width, height);
  951. }
  952. public void FillRectangle(Brush brush, RectangleF rect)
  953. {
  954. FillRectangle(brush, rect.Left, rect.Top, rect.Width, rect.Height);
  955. }
  956. public void FillRegion(Brush brush, Region region)
  957. {
  958. // TODO: check this case
  959. //throw new NotImplementedException();
  960. }
  961. public void FillAndDrawPath(Pen pen, Brush brush, GraphicsPath path)
  962. {
  963. AddPath(pen, brush, path);
  964. }
  965. public void FillAndDrawEllipse(Pen pen, Brush brush, RectangleF rect)
  966. {
  967. AddEllipse(pen, brush, rect.Left, rect.Top, rect.Width, rect.Height);
  968. }
  969. public void FillAndDrawEllipse(Pen pen, Brush brush, float left, float top, float width, float height)
  970. {
  971. AddEllipse(pen, brush, left, top, width, height);
  972. }
  973. public void FillAndDrawPolygon(Pen pen, Brush brush, Point[] points)
  974. {
  975. var pointsF = points.Select(point => (PointF)point).ToArray();
  976. AddPolygon(pen, brush, pointsF);
  977. }
  978. public void FillAndDrawPolygon(Pen pen, Brush brush, PointF[] points)
  979. {
  980. AddPolygon(pen, brush, points);
  981. }
  982. public void FillAndDrawRectangle(Pen pen, Brush brush, float left, float top, float width, float height)
  983. {
  984. AddRectangle(pen, brush, left, top, width, height);
  985. }
  986. #endregion
  987. #region Transform
  988. public void MultiplyTransform(System.Drawing.Drawing2D.Matrix matrix, MatrixOrder order)
  989. {
  990. needStateCheck = true;
  991. graphics.MultiplyTransform(matrix, order);
  992. }
  993. public void ScaleTransform(float scaleX, float scaleY)
  994. {
  995. needStateCheck = true;
  996. graphics.ScaleTransform(scaleX, scaleY);
  997. }
  998. public void TranslateTransform(float left, float top)
  999. {
  1000. needStateCheck = true;
  1001. graphics.TranslateTransform(left, top);
  1002. }
  1003. public void RotateTransform(float angle)
  1004. {
  1005. needStateCheck = true;
  1006. graphics.RotateTransform(angle);
  1007. }
  1008. #endregion
  1009. #region State
  1010. public IGraphicsState Save()
  1011. {
  1012. return new SvgGraphicsState(graphics.Save());
  1013. }
  1014. public void Restore(IGraphicsState state)
  1015. {
  1016. needStateCheck = true;
  1017. if (state is SvgGraphicsState)
  1018. {
  1019. graphics.Restore((state as SvgGraphicsState).State);
  1020. }
  1021. }
  1022. #endregion
  1023. #region Clip
  1024. public bool IsVisible(RectangleF rect)
  1025. {
  1026. return true;// measureGraphics.IsVisible(rect);
  1027. }
  1028. public void ResetClip()
  1029. {
  1030. needStateCheck = true;
  1031. graphics.ResetClip();
  1032. }
  1033. public void SetClip(RectangleF rect)
  1034. {
  1035. needStateCheck = true;
  1036. graphics.SetClip(rect);
  1037. }
  1038. public void SetClip(RectangleF rect, CombineMode combineMode)
  1039. {
  1040. needStateCheck = true;
  1041. graphics.SetClip(rect, combineMode);
  1042. }
  1043. public void SetClip(GraphicsPath path, CombineMode combineMode)
  1044. {
  1045. needStateCheck = true;
  1046. graphics.SetClip(path, combineMode);
  1047. }
  1048. #endregion
  1049. public class SvgGraphicsState : IGraphicsState
  1050. {
  1051. public GraphicsState State { get; }
  1052. public SvgGraphicsState(GraphicsState state)
  1053. {
  1054. State = state;
  1055. }
  1056. }
  1057. }
  1058. }