SvgGraphics.cs 44 KB

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