SvgVisualElement.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Drawing2D;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. #pragma warning disable
  7. namespace Svg
  8. {
  9. /// <summary>
  10. /// The class that all SVG elements should derive from when they are to be rendered.
  11. /// </summary>
  12. public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
  13. {
  14. private bool? _requiresSmoothRendering;
  15. private Region _previousClip;
  16. /// <summary>
  17. /// Gets the <see cref="GraphicsPath"/> for this element.
  18. /// </summary>
  19. public abstract GraphicsPath Path(ISvgRenderer renderer);
  20. PointF ISvgBoundable.Location
  21. {
  22. get
  23. {
  24. return Bounds.Location;
  25. }
  26. }
  27. SizeF ISvgBoundable.Size
  28. {
  29. get
  30. {
  31. return Bounds.Size;
  32. }
  33. }
  34. /// <summary>
  35. /// Gets the bounds of the element.
  36. /// </summary>
  37. /// <value>The bounds.</value>
  38. public abstract RectangleF Bounds { get; }
  39. /// <summary>
  40. /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
  41. /// </summary>
  42. [SvgAttribute("clip")]
  43. public virtual string Clip
  44. {
  45. get { return this.Attributes.GetInheritedAttribute<string>("clip"); }
  46. set { this.Attributes["clip"] = value; }
  47. }
  48. /// <summary>
  49. /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
  50. /// </summary>
  51. [SvgAttribute("clip-path")]
  52. public virtual Uri ClipPath
  53. {
  54. get { return this.Attributes.GetAttribute<Uri>("clip-path"); }
  55. set { this.Attributes["clip-path"] = value; }
  56. }
  57. /// <summary>
  58. /// Gets or sets the algorithm which is to be used to determine the clipping region.
  59. /// </summary>
  60. [SvgAttribute("clip-rule")]
  61. public SvgClipRule ClipRule
  62. {
  63. get { return this.Attributes.GetAttribute<SvgClipRule>("clip-rule", SvgClipRule.NonZero); }
  64. set { this.Attributes["clip-rule"] = value; }
  65. }
  66. /// <summary>
  67. /// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
  68. /// </summary>
  69. [SvgAttribute("filter")]
  70. public virtual Uri Filter
  71. {
  72. get { return this.Attributes.GetInheritedAttribute<Uri>("filter"); }
  73. set { this.Attributes["filter"] = value; }
  74. }
  75. /// <summary>
  76. /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
  77. /// </summary>
  78. protected virtual bool RequiresSmoothRendering
  79. {
  80. get
  81. {
  82. if (_requiresSmoothRendering == null)
  83. _requiresSmoothRendering = ConvertShapeRendering2AntiAlias(ShapeRendering);
  84. return _requiresSmoothRendering.Value;
  85. }
  86. }
  87. private bool ConvertShapeRendering2AntiAlias(SvgShapeRendering shapeRendering)
  88. {
  89. switch (shapeRendering)
  90. {
  91. case SvgShapeRendering.OptimizeSpeed:
  92. case SvgShapeRendering.CrispEdges:
  93. case SvgShapeRendering.GeometricPrecision:
  94. return false;
  95. default:
  96. // SvgShapeRendering.Auto
  97. // SvgShapeRendering.Inherit
  98. return true;
  99. }
  100. }
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="SvgGraphicsElement"/> class.
  103. /// </summary>
  104. public SvgVisualElement()
  105. {
  106. this.IsPathDirty = true;
  107. }
  108. protected virtual bool Renderable { get { return true; } }
  109. /// <summary>
  110. /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
  111. /// </summary>
  112. /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
  113. protected override void Render(ISvgRenderer renderer)
  114. {
  115. this.Render(renderer, true);
  116. }
  117. private void Render(ISvgRenderer renderer, bool renderFilter)
  118. {
  119. if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
  120. (!Renderable || this.Path(renderer) != null))
  121. {
  122. bool renderNormal = true;
  123. if (renderFilter && this.Filter != null)
  124. {
  125. var filterPath = this.Filter;
  126. if (filterPath.ToString().StartsWith("url("))
  127. {
  128. filterPath = new Uri(filterPath.ToString().Substring(4, filterPath.ToString().Length - 5), UriKind.RelativeOrAbsolute);
  129. }
  130. var filter = this.OwnerDocument.IdManager.GetElementById(filterPath) as FilterEffects.SvgFilter;
  131. if (filter != null)
  132. {
  133. this.PopTransforms(renderer);
  134. try
  135. {
  136. filter.ApplyFilter(this, renderer, (r) => this.Render(r, false));
  137. }
  138. catch (Exception ex) { Debug.Print(ex.ToString()); }
  139. renderNormal = false;
  140. }
  141. }
  142. if (renderNormal)
  143. {
  144. this.SetClip(renderer);
  145. if (Renderable)
  146. {
  147. // If this element needs smoothing enabled turn anti-aliasing on
  148. if (this.RequiresSmoothRendering)
  149. {
  150. renderer.SmoothingMode = SmoothingMode.AntiAlias;
  151. }
  152. this.RenderFill(renderer);
  153. this.RenderStroke(renderer);
  154. // Reset the smoothing mode
  155. if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias)
  156. {
  157. renderer.SmoothingMode = SmoothingMode.Default;
  158. }
  159. }
  160. else
  161. {
  162. base.RenderChildren(renderer);
  163. }
  164. this.ResetClip(renderer);
  165. this.PopTransforms(renderer);
  166. }
  167. }
  168. }
  169. /// <summary>
  170. /// Renders the fill of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
  171. /// </summary>
  172. /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
  173. protected internal virtual void RenderFill(ISvgRenderer renderer)
  174. {
  175. if (this.Fill != null)
  176. {
  177. using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
  178. {
  179. if (brush != null)
  180. {
  181. this.Path(renderer).FillMode = this.FillRule == SvgFillRule.NonZero ? FillMode.Winding : FillMode.Alternate;
  182. renderer.FillPath(brush, this.Path(renderer));
  183. }
  184. }
  185. }
  186. }
  187. /// <summary>
  188. /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
  189. /// </summary>
  190. /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
  191. protected internal virtual bool RenderStroke(ISvgRenderer renderer)
  192. {
  193. if (this.Stroke != null && this.Stroke != SvgColourServer.None && this.StrokeWidth > 0)
  194. {
  195. float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
  196. using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
  197. {
  198. if (brush != null)
  199. {
  200. var path = this.Path(renderer);
  201. var bounds = path.GetBounds();
  202. if (path.PointCount < 1) return false;
  203. if (bounds.Width <= 0 && bounds.Height <= 0)
  204. {
  205. switch (this.StrokeLineCap)
  206. {
  207. case SvgStrokeLineCap.Round:
  208. using (var capPath = new GraphicsPath())
  209. {
  210. capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth);
  211. renderer.FillPath(brush, capPath);
  212. }
  213. break;
  214. case SvgStrokeLineCap.Square:
  215. using (var capPath = new GraphicsPath())
  216. {
  217. capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth));
  218. renderer.FillPath(brush, capPath);
  219. }
  220. break;
  221. }
  222. }
  223. else
  224. {
  225. using (var pen = new Pen(brush, strokeWidth))
  226. {
  227. if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0)
  228. {
  229. /* divide by stroke width - GDI behaviour that I don't quite understand yet.*/
  230. pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) /
  231. ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
  232. }
  233. switch (this.StrokeLineJoin)
  234. {
  235. case SvgStrokeLineJoin.Bevel:
  236. pen.LineJoin = LineJoin.Bevel;
  237. break;
  238. case SvgStrokeLineJoin.Round:
  239. pen.LineJoin = LineJoin.Round;
  240. break;
  241. default:
  242. pen.LineJoin = LineJoin.Miter;
  243. break;
  244. }
  245. pen.MiterLimit = this.StrokeMiterLimit;
  246. switch (this.StrokeLineCap)
  247. {
  248. case SvgStrokeLineCap.Round:
  249. pen.StartCap = LineCap.Round;
  250. pen.EndCap = LineCap.Round;
  251. break;
  252. case SvgStrokeLineCap.Square:
  253. pen.StartCap = LineCap.Square;
  254. pen.EndCap = LineCap.Square;
  255. break;
  256. }
  257. renderer.DrawPath(pen, path);
  258. return true;
  259. }
  260. }
  261. }
  262. }
  263. }
  264. return false;
  265. }
  266. /// <summary>
  267. /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
  268. /// </summary>
  269. /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
  270. protected internal virtual void SetClip(ISvgRenderer renderer)
  271. {
  272. if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
  273. {
  274. this._previousClip = renderer.GetClip();
  275. if (this.ClipPath != null)
  276. {
  277. SvgClipPath clipPath = this.OwnerDocument.GetElementById<SvgClipPath>(this.ClipPath.ToString());
  278. if (clipPath != null) renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect);
  279. }
  280. var clip = this.Clip;
  281. if (!string.IsNullOrEmpty(clip) && clip.StartsWith("rect("))
  282. {
  283. clip = clip.Trim();
  284. var offsets = (from o in clip.Substring(5, clip.Length - 6).Split(',')
  285. select float.Parse(o.Trim())).ToList();
  286. var bounds = this.Bounds;
  287. var clipRect = new RectangleF(bounds.Left + offsets[3], bounds.Top + offsets[0],
  288. bounds.Width - (offsets[3] + offsets[1]),
  289. bounds.Height - (offsets[2] + offsets[0]));
  290. renderer.SetClip(new Region(clipRect), CombineMode.Intersect);
  291. }
  292. }
  293. }
  294. /// <summary>
  295. /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
  296. /// </summary>
  297. /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
  298. protected internal virtual void ResetClip(ISvgRenderer renderer)
  299. {
  300. if (this._previousClip != null)
  301. {
  302. renderer.SetClip(this._previousClip);
  303. this._previousClip = null;
  304. }
  305. }
  306. /// <summary>
  307. /// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
  308. /// </summary>
  309. /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
  310. void ISvgClipable.SetClip(ISvgRenderer renderer)
  311. {
  312. this.SetClip(renderer);
  313. }
  314. /// <summary>
  315. /// Resets the clipping region of the specified <see cref="ISvgRenderer"/> back to where it was before the <see cref="SetClip"/> method was called.
  316. /// </summary>
  317. /// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region reset.</param>
  318. void ISvgClipable.ResetClip(ISvgRenderer renderer)
  319. {
  320. this.ResetClip(renderer);
  321. }
  322. public override SvgElement DeepCopy<T>()
  323. {
  324. var newObj = base.DeepCopy<T>() as SvgVisualElement;
  325. newObj.ClipPath = this.ClipPath;
  326. newObj.ClipRule = this.ClipRule;
  327. newObj.Filter = this.Filter;
  328. newObj.Visible = this.Visible;
  329. if (this.Fill != null)
  330. newObj.Fill = this.Fill;
  331. if (this.Stroke != null)
  332. newObj.Stroke = this.Stroke;
  333. newObj.FillRule = this.FillRule;
  334. newObj.FillOpacity = this.FillOpacity;
  335. newObj.StrokeWidth = this.StrokeWidth;
  336. newObj.StrokeLineCap = this.StrokeLineCap;
  337. newObj.StrokeLineJoin = this.StrokeLineJoin;
  338. newObj.StrokeMiterLimit = this.StrokeMiterLimit;
  339. newObj.StrokeDashArray = this.StrokeDashArray;
  340. newObj.StrokeDashOffset = this.StrokeDashOffset;
  341. newObj.StrokeOpacity = this.StrokeOpacity;
  342. newObj.Opacity = this.Opacity;
  343. return newObj;
  344. }
  345. }
  346. }
  347. #pragma warning restore