SvgPathBuilder.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using Svg.Pathing;
  11. #pragma warning disable
  12. namespace Svg
  13. {
  14. public static class PointFExtensions
  15. {
  16. public static string ToSvgString(this PointF p)
  17. {
  18. return p.X.ToString() + " " + p.Y.ToString();
  19. }
  20. }
  21. public class SvgPathBuilder : TypeConverter
  22. {
  23. /// <summary>
  24. /// Parses the specified string into a collection of path segments.
  25. /// </summary>
  26. /// <param name="path">A <see cref="string"/> containing path data.</param>
  27. public static SvgPathSegmentList Parse(string path)
  28. {
  29. if (string.IsNullOrEmpty(path))
  30. {
  31. throw new ArgumentNullException("path");
  32. }
  33. var segments = new SvgPathSegmentList();
  34. try
  35. {
  36. char command;
  37. bool isRelative;
  38. foreach (var commandSet in SplitCommands(path.TrimEnd(null)))
  39. {
  40. command = commandSet[0];
  41. isRelative = char.IsLower(command);
  42. // http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation
  43. CreatePathSegment(command, segments, new CoordinateParser(commandSet.Trim()), isRelative);
  44. }
  45. }
  46. catch (Exception exc)
  47. {
  48. Trace.TraceError("Error parsing path \"{0}\": {1}", path, exc.Message);
  49. }
  50. return segments;
  51. }
  52. private static void CreatePathSegment(char command, SvgPathSegmentList segments, CoordinateParser parser, bool isRelative)
  53. {
  54. var coords = new float[6];
  55. switch (command)
  56. {
  57. case 'm': // relative moveto
  58. case 'M': // moveto
  59. if (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
  60. {
  61. segments.Add(new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative)));
  62. }
  63. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
  64. {
  65. segments.Add(new SvgLineSegment(segments.Last.End,
  66. ToAbsolute(coords[0], coords[1], segments, isRelative)));
  67. }
  68. break;
  69. case 'a':
  70. case 'A':
  71. bool size;
  72. bool sweep;
  73. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
  74. parser.TryGetFloat(out coords[2]) && parser.TryGetBool(out size) &&
  75. parser.TryGetBool(out sweep) && parser.TryGetFloat(out coords[3]) &&
  76. parser.TryGetFloat(out coords[4]))
  77. {
  78. // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y
  79. segments.Add(new SvgArcSegment(segments.Last.End, coords[0], coords[1], coords[2],
  80. (size ? SvgArcSize.Large : SvgArcSize.Small),
  81. (sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative),
  82. ToAbsolute(coords[3], coords[4], segments, isRelative)));
  83. }
  84. break;
  85. case 'l': // relative lineto
  86. case 'L': // lineto
  87. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
  88. {
  89. segments.Add(new SvgLineSegment(segments.Last.End,
  90. ToAbsolute(coords[0], coords[1], segments, isRelative)));
  91. }
  92. break;
  93. case 'H': // horizontal lineto
  94. case 'h': // relative horizontal lineto
  95. while (parser.TryGetFloat(out coords[0]))
  96. {
  97. segments.Add(new SvgLineSegment(segments.Last.End,
  98. ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false)));
  99. }
  100. break;
  101. case 'V': // vertical lineto
  102. case 'v': // relative vertical lineto
  103. while (parser.TryGetFloat(out coords[0]))
  104. {
  105. segments.Add(new SvgLineSegment(segments.Last.End,
  106. ToAbsolute(segments.Last.End.X, coords[0], segments, false, isRelative)));
  107. }
  108. break;
  109. case 'Q': // curveto
  110. case 'q': // relative curveto
  111. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
  112. parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]))
  113. {
  114. segments.Add(new SvgQuadraticCurveSegment(segments.Last.End,
  115. ToAbsolute(coords[0], coords[1], segments, isRelative),
  116. ToAbsolute(coords[2], coords[3], segments, isRelative)));
  117. }
  118. break;
  119. case 'T': // shorthand/smooth curveto
  120. case 't': // relative shorthand/smooth curveto
  121. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
  122. {
  123. var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment;
  124. var controlPoint = lastQuadCurve != null
  125. ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End)
  126. : segments.Last.End;
  127. segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint,
  128. ToAbsolute(coords[0], coords[1], segments, isRelative)));
  129. }
  130. break;
  131. case 'C': // curveto
  132. case 'c': // relative curveto
  133. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
  134. parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]) &&
  135. parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5]))
  136. {
  137. segments.Add(new SvgCubicCurveSegment(segments.Last.End,
  138. ToAbsolute(coords[0], coords[1], segments, isRelative),
  139. ToAbsolute(coords[2], coords[3], segments, isRelative),
  140. ToAbsolute(coords[4], coords[5], segments, isRelative)));
  141. }
  142. break;
  143. case 'S': // shorthand/smooth curveto
  144. case 's': // relative shorthand/smooth curveto
  145. while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
  146. parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]))
  147. {
  148. var lastCubicCurve = segments.Last as SvgCubicCurveSegment;
  149. var controlPoint = lastCubicCurve != null
  150. ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End)
  151. : segments.Last.End;
  152. segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint,
  153. ToAbsolute(coords[0], coords[1], segments, isRelative),
  154. ToAbsolute(coords[2], coords[3], segments, isRelative)));
  155. }
  156. break;
  157. case 'Z': // closepath
  158. case 'z': // relative closepath
  159. segments.Add(new SvgClosePathSegment());
  160. break;
  161. }
  162. }
  163. private static PointF Reflect(PointF point, PointF mirror)
  164. {
  165. float x, y, dx, dy;
  166. dx = Math.Abs(mirror.X - point.X);
  167. dy = Math.Abs(mirror.Y - point.Y);
  168. if (mirror.X >= point.X)
  169. {
  170. x = mirror.X + dx;
  171. }
  172. else
  173. {
  174. x = mirror.X - dx;
  175. }
  176. if (mirror.Y >= point.Y)
  177. {
  178. y = mirror.Y + dy;
  179. }
  180. else
  181. {
  182. y = mirror.Y - dy;
  183. }
  184. return new PointF(x, y);
  185. }
  186. /// <summary>
  187. /// Creates point with absolute coorindates.
  188. /// </summary>
  189. /// <param name="x">Raw X-coordinate value.</param>
  190. /// <param name="y">Raw Y-coordinate value.</param>
  191. /// <param name="segments">Current path segments.</param>
  192. /// <param name="isRelativeBoth"><b>true</b> if <paramref name="x"/> and <paramref name="y"/> contains relative coordinate values, otherwise <b>false</b>.</param>
  193. /// <returns><see cref="PointF"/> that contains absolute coordinates.</returns>
  194. private static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeBoth)
  195. {
  196. return ToAbsolute(x, y, segments, isRelativeBoth, isRelativeBoth);
  197. }
  198. /// <summary>
  199. /// Creates point with absolute coorindates.
  200. /// </summary>
  201. /// <param name="x">Raw X-coordinate value.</param>
  202. /// <param name="y">Raw Y-coordinate value.</param>
  203. /// <param name="segments">Current path segments.</param>
  204. /// <param name="isRelativeX"><b>true</b> if <paramref name="x"/> contains relative coordinate value, otherwise <b>false</b>.</param>
  205. /// <param name="isRelativeY"><b>true</b> if <paramref name="y"/> contains relative coordinate value, otherwise <b>false</b>.</param>
  206. /// <returns><see cref="PointF"/> that contains absolute coordinates.</returns>
  207. private static PointF ToAbsolute(float x, float y, SvgPathSegmentList segments, bool isRelativeX, bool isRelativeY)
  208. {
  209. var point = new PointF(x, y);
  210. if ((isRelativeX || isRelativeY) && segments.Count > 0)
  211. {
  212. var lastSegment = segments.Last;
  213. // if the last element is a SvgClosePathSegment the position of the previous element should be used because the position of SvgClosePathSegment is 0,0
  214. if (lastSegment is SvgClosePathSegment) lastSegment = segments.Reverse().OfType<SvgMoveToSegment>().First();
  215. if (isRelativeX)
  216. {
  217. point.X += lastSegment.End.X;
  218. }
  219. if (isRelativeY)
  220. {
  221. point.Y += lastSegment.End.Y;
  222. }
  223. }
  224. return point;
  225. }
  226. private static IEnumerable<string> SplitCommands(string path)
  227. {
  228. var commandStart = 0;
  229. for (var i = 0; i < path.Length; i++)
  230. {
  231. string command;
  232. if (char.IsLetter(path[i]) && path[i] != 'e') //e is used in scientific notiation. but not svg path
  233. {
  234. command = path.Substring(commandStart, i - commandStart).Trim();
  235. commandStart = i;
  236. if (!string.IsNullOrEmpty(command))
  237. {
  238. yield return command;
  239. }
  240. if (path.Length == i + 1)
  241. {
  242. yield return path[i].ToString();
  243. }
  244. }
  245. else if (path.Length == i + 1)
  246. {
  247. command = path.Substring(commandStart, i - commandStart + 1).Trim();
  248. if (!string.IsNullOrEmpty(command))
  249. {
  250. yield return command;
  251. }
  252. }
  253. }
  254. }
  255. //private static IEnumerable<float> ParseCoordinates(string coords)
  256. //{
  257. // if (string.IsNullOrEmpty(coords) || coords.Length < 2) yield break;
  258. // var pos = 0;
  259. // var currState = NumState.separator;
  260. // var newState = NumState.separator;
  261. // for (int i = 1; i < coords.Length; i++)
  262. // {
  263. // switch (currState)
  264. // {
  265. // case NumState.separator:
  266. // if (char.IsNumber(coords[i]))
  267. // {
  268. // newState = NumState.integer;
  269. // }
  270. // else if (IsCoordSeparator(coords[i]))
  271. // {
  272. // newState = NumState.separator;
  273. // }
  274. // else
  275. // {
  276. // switch (coords[i])
  277. // {
  278. // case '.':
  279. // newState = NumState.decPlace;
  280. // break;
  281. // case '+':
  282. // case '-':
  283. // newState = NumState.prefix;
  284. // break;
  285. // default:
  286. // newState = NumState.invalid;
  287. // break;
  288. // }
  289. // }
  290. // break;
  291. // case NumState.prefix:
  292. // if (char.IsNumber(coords[i]))
  293. // {
  294. // newState = NumState.integer;
  295. // }
  296. // else if (coords[i] == '.')
  297. // {
  298. // newState = NumState.decPlace;
  299. // }
  300. // else
  301. // {
  302. // newState = NumState.invalid;
  303. // }
  304. // break;
  305. // case NumState.integer:
  306. // if (char.IsNumber(coords[i]))
  307. // {
  308. // newState = NumState.integer;
  309. // }
  310. // else if (IsCoordSeparator(coords[i]))
  311. // {
  312. // newState = NumState.separator;
  313. // }
  314. // else
  315. // {
  316. // switch (coords[i])
  317. // {
  318. // case '.':
  319. // newState = NumState.decPlace;
  320. // break;
  321. // case 'e':
  322. // newState = NumState.exponent;
  323. // break;
  324. // case '+':
  325. // case '-':
  326. // newState = NumState.prefix;
  327. // break;
  328. // default:
  329. // newState = NumState.invalid;
  330. // break;
  331. // }
  332. // }
  333. // break;
  334. // case NumState.decPlace:
  335. // if (char.IsNumber(coords[i]))
  336. // {
  337. // newState = NumState.fraction;
  338. // }
  339. // else if (IsCoordSeparator(coords[i]))
  340. // {
  341. // newState = NumState.separator;
  342. // }
  343. // else
  344. // {
  345. // switch (coords[i])
  346. // {
  347. // case 'e':
  348. // newState = NumState.exponent;
  349. // break;
  350. // case '+':
  351. // case '-':
  352. // newState = NumState.prefix;
  353. // break;
  354. // default:
  355. // newState = NumState.invalid;
  356. // break;
  357. // }
  358. // }
  359. // break;
  360. // case NumState.fraction:
  361. // if (char.IsNumber(coords[i]))
  362. // {
  363. // newState = NumState.fraction;
  364. // }
  365. // else if (IsCoordSeparator(coords[i]))
  366. // {
  367. // newState = NumState.separator;
  368. // }
  369. // else
  370. // {
  371. // switch (coords[i])
  372. // {
  373. // case '.':
  374. // newState = NumState.decPlace;
  375. // break;
  376. // case 'e':
  377. // newState = NumState.exponent;
  378. // break;
  379. // case '+':
  380. // case '-':
  381. // newState = NumState.prefix;
  382. // break;
  383. // default:
  384. // newState = NumState.invalid;
  385. // break;
  386. // }
  387. // }
  388. // break;
  389. // case NumState.exponent:
  390. // if (char.IsNumber(coords[i]))
  391. // {
  392. // newState = NumState.expValue;
  393. // }
  394. // else if (IsCoordSeparator(coords[i]))
  395. // {
  396. // newState = NumState.invalid;
  397. // }
  398. // else
  399. // {
  400. // switch (coords[i])
  401. // {
  402. // case '+':
  403. // case '-':
  404. // newState = NumState.expPrefix;
  405. // break;
  406. // default:
  407. // newState = NumState.invalid;
  408. // break;
  409. // }
  410. // }
  411. // break;
  412. // case NumState.expPrefix:
  413. // if (char.IsNumber(coords[i]))
  414. // {
  415. // newState = NumState.expValue;
  416. // }
  417. // else
  418. // {
  419. // newState = NumState.invalid;
  420. // }
  421. // break;
  422. // case NumState.expValue:
  423. // if (char.IsNumber(coords[i]))
  424. // {
  425. // newState = NumState.expValue;
  426. // }
  427. // else if (IsCoordSeparator(coords[i]))
  428. // {
  429. // newState = NumState.separator;
  430. // }
  431. // else
  432. // {
  433. // switch (coords[i])
  434. // {
  435. // case '.':
  436. // newState = NumState.decPlace;
  437. // break;
  438. // case '+':
  439. // case '-':
  440. // newState = NumState.prefix;
  441. // break;
  442. // default:
  443. // newState = NumState.invalid;
  444. // break;
  445. // }
  446. // }
  447. // break;
  448. // }
  449. // if (newState < currState)
  450. // {
  451. // yield return float.Parse(coords.Substring(pos, i - pos), NumberStyles.Float, CultureInfo.InvariantCulture);
  452. // pos = i;
  453. // }
  454. // else if (newState != currState && currState == NumState.separator)
  455. // {
  456. // pos = i;
  457. // }
  458. // if (newState == NumState.invalid) yield break;
  459. // currState = newState;
  460. // }
  461. // if (currState != NumState.separator)
  462. // {
  463. // yield return float.Parse(coords.Substring(pos, coords.Length - pos), NumberStyles.Float, CultureInfo.InvariantCulture);
  464. // }
  465. //}
  466. public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
  467. {
  468. if (value is string)
  469. {
  470. return Parse((string)value);
  471. }
  472. return base.ConvertFrom(context, culture, value);
  473. }
  474. public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  475. {
  476. if (destinationType == typeof(string))
  477. {
  478. var paths = value as SvgPathSegmentList;
  479. if (paths != null)
  480. {
  481. var curretCulture = CultureInfo.CurrentCulture;
  482. try {
  483. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
  484. var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray());
  485. return s;
  486. }
  487. finally
  488. {
  489. // Make sure to set back the old culture even an error occurred.
  490. Thread.CurrentThread.CurrentCulture = curretCulture;
  491. }
  492. }
  493. }
  494. return base.ConvertTo(context, culture, value, destinationType);
  495. }
  496. public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
  497. {
  498. if (destinationType == typeof(string))
  499. {
  500. return true;
  501. }
  502. return base.CanConvertTo(context, destinationType);
  503. }
  504. }
  505. }
  506. #pragma warning restore