FileImporter.cs 105 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687
  1. using System;
  2. using System.Collections.Generic;
  3. //using System.Drawing;
  4. using System.Globalization;
  5. using System.Windows;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading.Tasks;
  10. namespace SketchAssistantWPF
  11. {
  12. public class FileImporter
  13. {
  14. /// <summary>
  15. /// scale factor for coordinates of svg file
  16. /// </summary>
  17. double scale;
  18. /// <summary>
  19. /// line pointer for the current svg document
  20. /// </summary>
  21. int i;
  22. /// <summary>
  23. /// array containing all characters interpreted as whitespaces which seperate words/tokens in the input file
  24. /// </summary>
  25. readonly char[] whitespaces = new char[] { ' ', ',' };
  26. /// <summary>
  27. /// number of points to create along the outline of an ellipse, divisible by 4
  28. /// </summary>
  29. readonly int samplingRateEllipse = 12;
  30. /// <summary>
  31. /// number of points to create on a bezier curve, including start and end point (even number will result in "flat" bezier curves, uneven number in "pointed" ones)
  32. /// </summary>
  33. readonly int samplingRateBezier = 101;
  34. /// <summary>
  35. /// parses a drawing consisting of line objects, given as a file in the application specific .isad format
  36. /// </summary>
  37. /// <param name="fileName">the path of the input file</param>
  38. /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
  39. public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
  40. {
  41. return ParseISADInput(System.IO.File.ReadAllLines(fileName));
  42. }
  43. /// <summary>
  44. /// parses a drawing consisting of line objects, given as the content of a .isad file, seperated into lines
  45. /// </summary>
  46. /// <param name="allLines">an array holding all lines of the input file</param>
  47. /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
  48. private Tuple<int, int, List<InternalLine>> ParseISADInput(String[] allLines)
  49. {
  50. if (allLines.Length == 0)
  51. {
  52. throw new FileImporterException("file is empty", "", -1);
  53. }
  54. if (!"drawing".Equals(allLines[0]))
  55. {
  56. throw new FileImporterException("file is not an interactive sketch assistant drawing", ".isad files have to start with the 'drawing' token", 1);
  57. }
  58. if (!"enddrawing".Equals(allLines[allLines.Length - 1]))
  59. {
  60. throw new FileImporterException("unterminated drawing definition", ".isad files have to end with the 'enddrawing' token", allLines.Length);
  61. }
  62. Tuple<int, int> dimensions = ParseISADHeader(allLines);
  63. List<InternalLine> picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2);
  64. return new Tuple<int, int, List<InternalLine>>(dimensions.Item1, dimensions.Item2, picture);
  65. }
  66. /// <summary>
  67. /// parses the first two lines of an input file in .isad format
  68. /// </summary>
  69. /// <param name="allLines">the input file as an array of lines</param>
  70. /// <returns>the width and height of the left canvas</returns>
  71. private Tuple<int, int> ParseISADHeader(String[] allLines)
  72. {
  73. int width;
  74. int height;
  75. if (!(allLines.Length > 1) || !Regex.Match(allLines[1], @"^\d+x?\d+$", RegexOptions.None).Success)
  76. {
  77. throw new FileImporterException("invalid or missing canvas size definition", "format: [width]x[heigth]", 2);
  78. }
  79. String[] size = allLines[1].Split('x');
  80. width = Convert.ToInt32(size[0]);
  81. height = Convert.ToInt32(size[1]);
  82. return new Tuple<int, int>(width, height);
  83. }
  84. /// <summary>
  85. /// parses all line entries of an input file in .isad format
  86. /// </summary>
  87. /// <param name="allLines">the input file as an array of lines</param>
  88. /// <returns>the parsed picture as a list of lines</returns>
  89. private List<InternalLine> ParseISADBody(String[] allLines, int width, int height)
  90. {
  91. String lineStartString = "line";
  92. String lineEndString = "endline";
  93. List<InternalLine> drawing = new List<InternalLine>();
  94. //number of the line currently being parsed, enumeration starting at 0, body starts at the third line, therefore lin number 2
  95. int i = 2;
  96. //parse 'line' token and complete line definition
  97. int lineStartPointer = i;
  98. //holds the line number of the next expected beginning of a line definition, or of the enddrawing token
  99. while (lineStartString.Equals(allLines[i]))
  100. {
  101. //start parsing next line
  102. i++;
  103. List<Point> newLine = new List<Point>();
  104. while (!lineEndString.Equals(allLines[i]))
  105. {
  106. if (i == allLines.Length)
  107. {
  108. throw new FileImporterException("unterminated line definition", null, (i + 1));
  109. }
  110. //parse single point definition
  111. if (!Regex.Match(allLines[i], @"^\d+;\d+$", RegexOptions.None).Success)
  112. {
  113. throw new FileImporterException("invalid Point definition: wrong format", "format: [xCoordinate];[yCoordinate]", (i + 1));
  114. }
  115. String[] coordinates = allLines[i].Split(';');
  116. //no errors possible, convertability to int already checked above
  117. int xCoordinate = Convert.ToInt32(coordinates[0]);
  118. int yCoordinate = Convert.ToInt32(coordinates[1]);
  119. if (xCoordinate < 0 || yCoordinate < 0 || xCoordinate > width - 1 || yCoordinate > height - 1)
  120. {
  121. throw new FileImporterException("invalid Point definition: point out of bounds", null, (i + 1));
  122. }
  123. newLine.Add(new Point(xCoordinate, yCoordinate));
  124. //start parsing next line
  125. i++;
  126. }
  127. //"parse" 'endline' token, syntax already checked at the beginning, and start parsing next line
  128. i++;
  129. //add line to drawing
  130. drawing.Add(new InternalLine(newLine));
  131. //update lineStartPointer to the presumable start of the next line
  132. lineStartPointer = i;
  133. }
  134. //check if end of body is reached after there are no more line definitions
  135. if (i != allLines.Length - 1)
  136. {
  137. throw new FileImporterException("missing or invalid line definition token", "line definitions start with the 'line' token", (i + 1));
  138. }
  139. //return parsed picture
  140. return drawing;
  141. }
  142. /// <summary>
  143. /// connection point for testing use only: calls ParseISADInput(String[] allLines) and directly passes the given argument (effectively bypassing the File Input functionality)
  144. /// </summary>
  145. /// <param name="allLines">an array holding all lines of the input file</param>
  146. /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
  147. public Tuple<int, int, List<InternalLine>> ParseISADInputForTesting(String[] allLines)
  148. {
  149. return ParseISADInput(allLines);
  150. }
  151. /// <summary>
  152. /// parses a svg drawing, given as a .svg file
  153. /// <para />several severe restrictions to the svg standard apply:
  154. /// <para /> - width and heigth values must be integers
  155. /// <para /> - the supported svg elements to be drawn must be placed on top level directly inside the 'svg' tag
  156. /// <para /> - except for the global 'svg' tag, no hierarchical elements (elements which contain other svg elements) may exist. in other words: after an opening element tag no other opening element tag may occur before the closing tag of this element.
  157. /// <para /> - lines in front of the (single) opening and after the (single) closing global svg tag will be ignored during parsing
  158. /// <para /> - unsupported svg elements on top level will be ignored during parsing
  159. /// <para /> - the input file must not contain empty lines
  160. /// <para /> - all input files have to be manually tested and approved for use with this program by a developer or otherwise entitled personnel, otherwise no guarantee about correct and error-free parsing will be given
  161. /// </summary>
  162. /// <param name="fileName">the path of the input file</param>
  163. /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
  164. public Tuple<int, int, List<InternalLine>> ParseSVGInputFile(String fileName, int windowWidth, int windowHeight)
  165. {
  166. return ParseSVGInput(System.IO.File.ReadAllLines(fileName), windowWidth, windowHeight);
  167. }
  168. /// <summary>
  169. /// parses a svg drawing, given as the content of a .svg file, seperated into lines
  170. /// </summary>
  171. /// <param name="allLines">an array holding all lines of the input file</param>
  172. /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
  173. private Tuple<int, int, List<InternalLine>> ParseSVGInput(String[] allLines, double windowWidth, double windowHeight)
  174. {
  175. i = 0; //reset line pointer
  176. if (allLines.Length == 0) //check for empty file
  177. {
  178. throw new FileImporterException("file is empty", "", -1);
  179. }
  180. var sizedef = ParseSVGHeader(allLines); //parse svg file header and get internal coordinate range
  181. i++;
  182. int width; //width of the resulting picture in pixels
  183. int height; //height of the resulting picture in pixels
  184. if (windowWidth != 0 && windowHeight != 0)
  185. {
  186. if (windowWidth / windowHeight > sizedef.Item1 / sizedef.Item2) //height dominant, width has to be smaller than drawing window to preserve xy-scale
  187. {
  188. scale = windowHeight / sizedef.Item2;
  189. height = (int)Math.Round(windowHeight);
  190. width = (int)Math.Round(scale * sizedef.Item1);
  191. }
  192. else //width dominant, height has to be smaller than drawing window to preserve xy-scale
  193. {
  194. scale = windowWidth / sizedef.Item1;
  195. width = (int)Math.Round(windowWidth);
  196. height = (int)Math.Round(scale * sizedef.Item2);
  197. }
  198. }
  199. else
  200. {
  201. scale = 1;
  202. width = sizedef.Item1;
  203. height = sizedef.Item2;
  204. }
  205. for (int j = 0; j < allLines.Length; j++)
  206. {
  207. allLines[j] = allLines[j].Trim(whitespaces);
  208. }
  209. List<InternalLine> picture = ParseSVGBody(allLines); //parse whole svg drawing into list of lines
  210. return new Tuple<int, int, List<InternalLine>>(width, height, picture);
  211. }
  212. /// <summary>
  213. /// parses the svg file header and returns the internal coordinate range of this drawing, and iterates i to point to the start of svg element definitions
  214. /// </summary>
  215. /// <param name="allLines">an array holding all lines of the input file</param>
  216. /// <returns>the internal coordinate range of this drawing</returns>
  217. private Tuple<int, int> ParseSVGHeader(String[] allLines)
  218. {
  219. while (!allLines[i].StartsWith("<svg")) //skip non-relevant metadata at start of svg file
  220. {
  221. i++;
  222. }
  223. String[] currentLine = allLines[i].Split(' ');
  224. int width = -1;
  225. int height = -1;
  226. for (int j = 0; j < currentLine.Length; j++)
  227. {
  228. if (currentLine[j].StartsWith("width"))
  229. {
  230. width = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
  231. }
  232. else if (currentLine[j].StartsWith("height"))
  233. {
  234. height = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
  235. }
  236. }
  237. if (width == -1)
  238. {
  239. throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i + 1);
  240. }
  241. if (height == -1)
  242. {
  243. throw new FileImporterException("missing height definition in SVG header", "the header should contain the \"height=...\" attribute", i + 1);
  244. }
  245. return new Tuple<int, int>(width, height);
  246. }
  247. /// <summary>
  248. /// parses all relevant svg element definitions and skips the ones not representable by the sketch assistant
  249. /// </summary>
  250. /// <param name="allLines">an array holding all lines of the input file</param>
  251. /// <returns>the parsed picture as a list of lines</returns>
  252. private List<InternalLine> ParseSVGBody(String[] allLines)
  253. {
  254. List<InternalLine> picture = new List<InternalLine>();
  255. while (!allLines[i].StartsWith("</svg"))
  256. {
  257. List<InternalLine> element = ParseSingleSVGElement(allLines);
  258. if (element != null)
  259. {
  260. picture.AddRange(element);
  261. }
  262. i++;
  263. if (i > allLines.Length - 1) throw new FileImporterException("unterminated input file: missing </svg> tag", "the file must not contain empty lines", i + 1);
  264. }
  265. return picture;
  266. }
  267. /// <summary>
  268. /// parses one toplevel svg element
  269. /// </summary>
  270. /// <param name="allLines">an array holding all lines of the input file</param>
  271. /// <returns>the parsed Element as a list of lines</returns>
  272. private List<InternalLine> ParseSingleSVGElement(string[] allLines)
  273. {
  274. String[] currentElement = GetCurrentElement(allLines);
  275. return ParseSingleLineSVGElement(currentElement);
  276. }
  277. /// <summary>
  278. /// parses a single toplevel svg element only taking one line
  279. /// </summary>
  280. /// <param name="allLines">an array holding all lines of the input file</param>
  281. /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
  282. private List<InternalLine> ParseSingleLineSVGElement(string[] currentElement)
  283. {
  284. List<Point> points = null;
  285. List<InternalLine> element = null;
  286. switch (currentElement[0])
  287. {
  288. case "<rect":
  289. points = ParseRect(currentElement);
  290. break;
  291. case "<circle":
  292. points = ParseCircle(currentElement);
  293. break;
  294. case "<ellipse":
  295. points = ParseEllipse(currentElement);
  296. break;
  297. case "<line":
  298. points = ParseLine(currentElement);
  299. break;
  300. case "<polyline":
  301. points = ParsePolyline(currentElement);
  302. break;
  303. case "<polygon":
  304. points = ParsePolygon(currentElement);
  305. break;
  306. case "<path":
  307. element = ParsePath(currentElement);
  308. break;
  309. default: //unsupported svg element
  310. return null; //simply ignore
  311. }
  312. if (element == null)
  313. {
  314. element = new List<InternalLine>();
  315. element.Add(new InternalLine(points));
  316. }
  317. return element;
  318. }
  319. /// <summary>
  320. /// parses a rectangle definition into a List of Points representing a single line around the rectangle (in clockwise direction), ignoring rounded corners
  321. /// </summary>
  322. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  323. /// <returns>the parsed element as a List of Points</returns>
  324. private List<Point> ParseRect(string[] currentElement)
  325. {
  326. double x = 0;
  327. double y = 0;
  328. double w = 0;
  329. double h = 0;
  330. double rx = 0;
  331. double ry = 0;
  332. for (int j = 0; j < currentElement.Length; j++)
  333. {
  334. if (currentElement[j].StartsWith("x="))
  335. {
  336. x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  337. }
  338. else if (currentElement[j].StartsWith("y="))
  339. {
  340. y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  341. }
  342. else if (currentElement[j].StartsWith("width="))
  343. {
  344. w = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  345. }
  346. else if (currentElement[j].StartsWith("height="))
  347. {
  348. h = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  349. }
  350. else if (currentElement[j].StartsWith("rx="))
  351. {
  352. rx = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  353. }
  354. else if (currentElement[j].StartsWith("ry="))
  355. {
  356. ry = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  357. }
  358. }
  359. List<Point> rect = new List<Point>();
  360. rect.Add(ScaleAndCreatePoint(x, y));
  361. rect.Add(ScaleAndCreatePoint(x + w, y));
  362. rect.Add(ScaleAndCreatePoint(x + w, y + h));
  363. rect.Add(ScaleAndCreatePoint(x, y + h));
  364. rect.Add(ScaleAndCreatePoint(x, y));
  365. return rect;
  366. }
  367. /// <summary>
  368. /// parses a circle definition into a List of Points
  369. /// </summary>
  370. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  371. /// <returns>the parsed element as a List of Points</returns>
  372. private List<Point> ParseCircle(string[] currentElement)
  373. {
  374. double x = 0;
  375. double y = 0;
  376. double r = 0;
  377. for (int j = 0; j < currentElement.Length; j++)
  378. {
  379. if (currentElement[j].StartsWith("cx="))
  380. {
  381. x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  382. }
  383. else if (currentElement[j].StartsWith("cy="))
  384. {
  385. y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  386. }
  387. else if (currentElement[j].StartsWith("r="))
  388. {
  389. r = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  390. }
  391. }
  392. return SampleEllipse(x, y, r, r);
  393. }
  394. /// <summary>
  395. /// parses a ellipse definition into a List of Points
  396. /// </summary>
  397. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  398. /// <returns>the parsed element as a List of Points</returns>
  399. private List<Point> ParseEllipse(string[] currentElement)
  400. {
  401. double x = 0;
  402. double y = 0;
  403. double rx = 0;
  404. double ry = 0;
  405. for (int j = 0; j < currentElement.Length; j++)
  406. {
  407. if (currentElement[j].StartsWith("cx="))
  408. {
  409. x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  410. }
  411. else if (currentElement[j].StartsWith("cy="))
  412. {
  413. y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  414. }
  415. else if (currentElement[j].StartsWith("rx="))
  416. {
  417. rx = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  418. }
  419. else if (currentElement[j].StartsWith("ry="))
  420. {
  421. ry = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  422. }
  423. }
  424. return SampleEllipse(x, y, rx, ry);
  425. }
  426. /// <summary>
  427. /// parses a line definition into a List of two Points
  428. /// </summary>
  429. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  430. /// <returns>the parsed element as a List of Points</returns>
  431. private List<Point> ParseLine(string[] currentElement)
  432. {
  433. double x1 = 0;
  434. double y1 = 0;
  435. double x2 = 0;
  436. double y2 = 0;
  437. for (int j = 0; j < currentElement.Length; j++)
  438. {
  439. if (currentElement[j].StartsWith("x1="))
  440. {
  441. x1 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  442. }
  443. else if (currentElement[j].StartsWith("y1="))
  444. {
  445. y1 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  446. }
  447. else if (currentElement[j].StartsWith("x2="))
  448. {
  449. x2 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  450. }
  451. else if (currentElement[j].StartsWith("y2="))
  452. {
  453. y2 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
  454. }
  455. }
  456. List<Point> line = new List<Point>();
  457. line.Add(ScaleAndCreatePoint(x1, y1));
  458. line.Add(ScaleAndCreatePoint(x2, y2));
  459. return line;
  460. }
  461. /// <summary>
  462. /// parses a polyline definition into a List of Points
  463. /// </summary>
  464. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  465. /// <returns>the parsed element as a List of Points</returns>
  466. private List<Point> ParsePolyline(string[] currentElement)
  467. {
  468. String[] points = null;
  469. for (int j = 0; j < currentElement.Length; j++)
  470. {
  471. if (currentElement[j].StartsWith("points="))
  472. {
  473. List<String> pointDefs = new List<string>();
  474. pointDefs.Add(currentElement[j].Substring(8)); //parse first point coordinates by removing 'points="'
  475. j++;
  476. while (!currentElement[j].EndsWith("\""))
  477. {
  478. pointDefs.Add(currentElement[j]); //parse intermediate point coordinates
  479. j++;
  480. }
  481. pointDefs.Add(currentElement[j].Trim('"')); //parse last point coordinates by removing '"'
  482. points = pointDefs.ToArray();
  483. }
  484. }
  485. List<Point> polyline = new List<Point>();
  486. for (int k = 0; k < points.Length - 1; k += 2)
  487. {
  488. polyline.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
  489. }
  490. return polyline;
  491. }
  492. /// <summary>
  493. /// parses a polygon definition into a List of Points
  494. /// </summary>
  495. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  496. /// <returns>the parsed element as a List of Points</returns>
  497. private List<Point> ParsePolygon(string[] currentElement)
  498. {
  499. String[] points = null;
  500. for (int j = 0; j < currentElement.Length; j++)
  501. {
  502. if (currentElement[j].StartsWith("points="))
  503. {
  504. List<String> pointDefs = new List<string>();
  505. pointDefs.Add(currentElement[j].Substring(8)); //parse first point coordinates by removing 'points="'
  506. j++;
  507. while (!currentElement[j].EndsWith("\""))
  508. {
  509. pointDefs.Add(currentElement[j]); //parse intermediate point coordinates
  510. j++;
  511. }
  512. pointDefs.Add(currentElement[j].Trim('"')); //parse last point coordinates by removing '"'
  513. points = pointDefs.ToArray();
  514. }
  515. }
  516. List<Point> polygon = new List<Point>();
  517. for (int k = 0; k < points.Length - 1; k += 2)
  518. {
  519. polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
  520. }
  521. polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[0], CultureInfo.InvariantCulture), Convert.ToDouble(points[1], CultureInfo.InvariantCulture))); //close polygon
  522. return polygon;
  523. }
  524. /// <summary>
  525. /// parses a path definition into a List of Points
  526. /// </summary>
  527. /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
  528. /// <returns>the parsed element as a List of Points</returns>
  529. private List<InternalLine> ParsePath(string[] currentElement)
  530. {
  531. List<String> pathElements = new List<string>();
  532. for (int j = 0; j < currentElement.Length; j++)
  533. {
  534. if (currentElement[j].StartsWith("d="))
  535. {
  536. pathElements.Add(currentElement[j].Substring(3)); //parse first path element by removing 'd="'
  537. j++;
  538. while (!currentElement[j].EndsWith("\""))
  539. {
  540. pathElements.Add(currentElement[j]); //parse intermediate path element
  541. j++;
  542. }
  543. pathElements.Add(currentElement[j].Trim('"')); //parse last path element by removing '"'
  544. }
  545. }
  546. NormalizePathDeclaration(pathElements); //expand path data to always explicitly have the command descriptor in front of the appropriate number of arguments and to seperate command descriptors, coordinates and other tokens always into seperate list elements (equivalent to seperation with spaces in the input file, but svg allows also for comma as a seperator, and for omitting seperators where possible without losing information (refer to svg grammer) to reduce file size
  547. List<InternalLine> element = new List<InternalLine>();
  548. List<Point> currentLine = new List<Point>();
  549. double lastBezierControlPointX = 0;
  550. double lastBezierControlPointY = 0;
  551. double lastPositionX;
  552. double lastPositionY;
  553. double initialPositionX = -1;
  554. double initialPositionY = -1;
  555. bool newSubpath = true;
  556. Tuple<List<Point>, double, double> valuesArc; //list of points, new values for: lastPositionX, lastPositionY
  557. Tuple<List<Point>, double, double, double, double> valuesBezierCurve; //list of points, new values for: lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY
  558. var valuesSinglePoint = Parse_M_L(pathElements); //new point, new values for: lastPositionX, lastPositionY
  559. currentLine = new List<Point>();
  560. currentLine.Add(valuesSinglePoint.Item1);
  561. lastPositionX = valuesSinglePoint.Item2;
  562. lastPositionY = valuesSinglePoint.Item3;
  563. String currentToken;
  564. while (!(pathElements.Count == 0))
  565. {
  566. if (newSubpath)
  567. {
  568. initialPositionX = lastPositionX; //update buffers for coordinates of first point of active subpath
  569. initialPositionY = lastPositionY;
  570. newSubpath = false;
  571. }
  572. currentToken = pathElements.First();
  573. if (currentToken.Equals("M"))
  574. {
  575. element.Add(new InternalLine(currentLine)); //save current line
  576. valuesSinglePoint = Parse_M_L(pathElements);
  577. currentLine = new List<Point>(); //create new empty line
  578. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  579. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  580. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  581. }
  582. else if (currentToken.Equals("m"))
  583. {
  584. element.Add(new InternalLine(currentLine)); //save current line
  585. valuesSinglePoint = Parse_m_l(pathElements, lastPositionX, lastPositionY);
  586. currentLine = new List<Point>(); //create new empty line
  587. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  588. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  589. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  590. }
  591. else if (currentToken.Equals("Z") || currentToken.Equals("z"))
  592. {
  593. valuesSinglePoint = Parse_Z(pathElements, initialPositionX, initialPositionY); //method call only used for uniform program structure... only real effect of method is to consume one token
  594. newSubpath = true;
  595. currentLine.Add(valuesSinglePoint.Item1); //add point to old line
  596. element.Add(new InternalLine(currentLine)); //save current line
  597. currentLine = new List<Point>(); //create new empty line
  598. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  599. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  600. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  601. }
  602. else if (currentToken.Equals("L"))
  603. {
  604. valuesSinglePoint = Parse_M_L(pathElements);
  605. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  606. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  607. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  608. }
  609. else if (currentToken.Equals("l"))
  610. {
  611. valuesSinglePoint = Parse_m_l(pathElements, lastPositionX, lastPositionY);
  612. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  613. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  614. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  615. }
  616. else if (currentToken.Equals("H"))
  617. {
  618. valuesSinglePoint = Parse_H(pathElements, lastPositionY);
  619. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  620. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  621. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  622. }
  623. else if (currentToken.Equals("h"))
  624. {
  625. valuesSinglePoint = Parse_h(pathElements, lastPositionX, lastPositionY);
  626. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  627. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  628. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  629. }
  630. else if (currentToken.Equals("V"))
  631. {
  632. valuesSinglePoint = Parse_V(pathElements, lastPositionX);
  633. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  634. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  635. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  636. }
  637. else if (currentToken.Equals("v"))
  638. {
  639. valuesSinglePoint = Parse_v(pathElements, lastPositionX, lastPositionY);
  640. currentLine.Add(valuesSinglePoint.Item1); //add point to new line
  641. lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
  642. lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
  643. }
  644. else if (currentToken.Equals("C"))
  645. {
  646. valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
  647. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  648. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  649. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  650. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  651. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  652. }
  653. else if (currentToken.Equals("c"))
  654. {
  655. valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
  656. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  657. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  658. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  659. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  660. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  661. }
  662. else if (currentToken.Equals("S"))
  663. {
  664. valuesBezierCurve = Parse_S(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
  665. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  666. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  667. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  668. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  669. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  670. }
  671. else if (currentToken.Equals("s"))
  672. {
  673. valuesBezierCurve = Parse_s(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
  674. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  675. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  676. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  677. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  678. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  679. }
  680. else if (currentToken.Equals("Q"))
  681. {
  682. valuesBezierCurve = Parse_Q(pathElements, lastPositionX, lastPositionY);
  683. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  684. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  685. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  686. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  687. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  688. }
  689. else if (currentToken.Equals("q"))
  690. {
  691. valuesBezierCurve = Parse_q(pathElements, lastPositionX, lastPositionY);
  692. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  693. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  694. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  695. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  696. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  697. }
  698. else if (currentToken.Equals("T"))
  699. {
  700. valuesBezierCurve = Parse_T(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
  701. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  702. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  703. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  704. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  705. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  706. }
  707. else if (currentToken.Equals("t"))
  708. {
  709. valuesBezierCurve = Parse_t(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
  710. currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
  711. lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
  712. lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
  713. lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
  714. lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
  715. }
  716. else if (currentToken.Equals("A"))
  717. {
  718. valuesArc = Parse_A(pathElements, lastPositionX, lastPositionY);
  719. currentLine.AddRange(valuesArc.Item1); //add points to new line
  720. lastPositionX = valuesArc.Item2; //save last point coordinates
  721. lastPositionY = valuesArc.Item3; //save last point coordinates
  722. }
  723. else if (currentToken.Equals("a"))
  724. {
  725. valuesArc = Parse_a(pathElements, lastPositionX, lastPositionY);
  726. currentLine.AddRange(valuesArc.Item1); //add points to new line
  727. lastPositionX = valuesArc.Item2; //save last point coordinates
  728. lastPositionY = valuesArc.Item3; //save last point coordinates
  729. }
  730. else
  731. {
  732. throw new FileImporterException("invalid path argument or path data formatting: read argument " + pathElements.First(), "valid path arguments are: {M,Z,L,H,V,C,S,Q,T,A} in upper and lower case", i + 1);
  733. }
  734. }
  735. if (currentLine.Count > 1)
  736. {
  737. element.Add(new InternalLine(currentLine)); //save current line
  738. }
  739. return element;
  740. }
  741. /// <summary>
  742. /// normalizes the declaration of the data field of a path declaration by splitting coordinates still connected by a semicolon and command descriptors which are directly attached to the following coordinate into seperate tokens, also repeats omitted command descriptor tokens when the same command is repeated multiple times
  743. /// </summary>
  744. /// <param name="pathElements">the list of tokens to normalize, by splitting up existing tokens and adding new command descriptor tokens</param>
  745. private void NormalizePathDeclaration(List<string> pathElements)
  746. {
  747. Char lastCommand = 'M';
  748. int argumentCounter = 0;
  749. for (int j = 0; j < pathElements.Count; j++)
  750. {
  751. String currentElement = pathElements.ElementAt(j);
  752. if (currentElement.Length != 1)
  753. {
  754. if (((currentElement.First() >= 'A' && currentElement.First() <= 'Z') || (currentElement.First() >= 'a' && currentElement.First() <= 'z')) && currentElement.First() != 'e') //seperate a single command descriptor / letter
  755. {
  756. pathElements.RemoveAt(j);
  757. pathElements.Insert(j, currentElement.First() + ""); //insert letter as seperate element
  758. pathElements.Insert(j + 1, currentElement.Substring(1)); //insert rest of String at next position so it will be processed again
  759. lastCommand = currentElement.First();
  760. argumentCounter = 0;
  761. }
  762. else if ((currentElement.First() >= '0' && currentElement.First() <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e') //seperate a single coordinate / number
  763. {
  764. bool repeatCommandDescriptor = false;
  765. switch (lastCommand)
  766. { //check for reaching of next command with omitted command descriptor
  767. case 'M':
  768. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  769. break;
  770. case 'm':
  771. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  772. break;
  773. case 'L':
  774. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  775. break;
  776. case 'l':
  777. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  778. break;
  779. case 'V':
  780. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  781. break;
  782. case 'v':
  783. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  784. break;
  785. case 'H':
  786. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  787. break;
  788. case 'h':
  789. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  790. break;
  791. case 'C':
  792. if (argumentCounter >= 6) repeatCommandDescriptor = true;
  793. break;
  794. case 'c':
  795. if (argumentCounter >= 6) repeatCommandDescriptor = true;
  796. break;
  797. case 'S':
  798. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  799. break;
  800. case 's':
  801. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  802. break;
  803. case 'Q':
  804. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  805. break;
  806. case 'q':
  807. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  808. break;
  809. case 'T':
  810. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  811. break;
  812. case 't':
  813. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  814. break;
  815. case 'A':
  816. if (argumentCounter >= 7) repeatCommandDescriptor = true;
  817. break;
  818. case 'a':
  819. if (argumentCounter >= 7) repeatCommandDescriptor = true;
  820. break;
  821. }
  822. if (repeatCommandDescriptor)
  823. {
  824. pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
  825. j++; //skip command descriptor (was put into active position in the list
  826. argumentCounter = 0; //reset argument counter
  827. }
  828. bool decimalPointEncountered = false;
  829. for (int k = 1; k < currentElement.Length; k++)
  830. {
  831. if (!decimalPointEncountered && currentElement.ElementAt(k) == '.') //allow up to one decimal point in numbers
  832. {
  833. decimalPointEncountered = true;
  834. }
  835. else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e'))
  836. {
  837. pathElements.RemoveAt(j);
  838. pathElements.Insert(j, currentElement.Substring(0, k - 1)); //insert number as seperate element
  839. pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
  840. break;
  841. }
  842. }
  843. argumentCounter++;
  844. }
  845. else //parse non-space seperators and skip other unsupported characters (the only other valid ones per svg standard would be weird tokens looking like format descriptors (e.g. '#xC'), these are unsopported and will likely cause an error or other inconsitencies during parsing)
  846. {
  847. for (int k = 1; k < currentElement.Length; k++)
  848. {
  849. if (((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9')) || currentElement.ElementAt(k) == '-' || currentElement.ElementAt(k) == '+' || (currentElement.ElementAt(k) >= 'A' && currentElement.ElementAt(k) <= 'Z') || (currentElement.ElementAt(k) >= 'a' && currentElement.ElementAt(k) <= 'z'))
  850. {
  851. pathElements.RemoveAt(j);
  852. pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
  853. break;
  854. }
  855. }
  856. }
  857. }
  858. else
  859. {
  860. if ((currentElement.First() >= 'A' && currentElement.First() <= 'Z') || (currentElement.First() >= 'a' && currentElement.First() <= 'z')) //update lastCommand buffer when reading single letter
  861. {
  862. lastCommand = currentElement.First();
  863. argumentCounter = 0;
  864. }
  865. else if (!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
  866. {
  867. pathElements.RemoveAt(j); //remove element
  868. j--; //decrement index pointer so next element will not be skipped (indices of all folowing elements just decreased by 1)
  869. }
  870. else //a single digit number
  871. {
  872. bool repeatCommandDescriptor = false;
  873. switch (lastCommand)
  874. { //check for reaching of next command with omitted command descriptor
  875. case 'M':
  876. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  877. break;
  878. case 'm':
  879. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  880. break;
  881. case 'L':
  882. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  883. break;
  884. case 'l':
  885. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  886. break;
  887. case 'V':
  888. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  889. break;
  890. case 'v':
  891. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  892. break;
  893. case 'H':
  894. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  895. break;
  896. case 'h':
  897. if (argumentCounter >= 1) repeatCommandDescriptor = true;
  898. break;
  899. case 'C':
  900. if (argumentCounter >= 6) repeatCommandDescriptor = true;
  901. break;
  902. case 'c':
  903. if (argumentCounter >= 6) repeatCommandDescriptor = true;
  904. break;
  905. case 'S':
  906. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  907. break;
  908. case 's':
  909. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  910. break;
  911. case 'Q':
  912. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  913. break;
  914. case 'q':
  915. if (argumentCounter >= 4) repeatCommandDescriptor = true;
  916. break;
  917. case 'T':
  918. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  919. break;
  920. case 't':
  921. if (argumentCounter >= 2) repeatCommandDescriptor = true;
  922. break;
  923. case 'A':
  924. if (argumentCounter >= 7) repeatCommandDescriptor = true;
  925. break;
  926. case 'a':
  927. if (argumentCounter >= 7) repeatCommandDescriptor = true;
  928. break;
  929. }
  930. if (repeatCommandDescriptor)
  931. {
  932. pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
  933. j++; //skip command descriptor (was put into active position in the list
  934. argumentCounter = 0; //reset argument counter
  935. }
  936. argumentCounter++;
  937. }
  938. }
  939. }
  940. }
  941. /// <summary>
  942. /// parses a "closeloop" path element
  943. /// </summary>
  944. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  945. /// <param name="initialPositionX">absolute x coordinate of the last initial point of this subpath</param>
  946. /// <param name="initialPositionY">absolute y coordinate of the last initial point of this subpath</param>
  947. /// <returns></returns>
  948. private Tuple<Point, double, double> Parse_Z(List<string> pathElements, double initialPositionX, double initialPositionY)
  949. {
  950. pathElements.RemoveAt(0); //remove element descriptor token
  951. return new Tuple<Point, double, double>(ScaleAndCreatePoint(initialPositionX, initialPositionY), initialPositionX, initialPositionY);
  952. }
  953. /// <summary>
  954. /// parses a "moveto", "close loop" or "lineto" path element with absolute coordinates
  955. /// </summary>
  956. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  957. /// <returns>the point at the end of the move, close loop or line action and its exact, unscaled coordinates</returns>
  958. private Tuple<Point, double, double> Parse_M_L(List<string> pathElements)
  959. {
  960. pathElements.RemoveAt(0); //remove element descriptor token
  961. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x coordinate
  962. pathElements.RemoveAt(0); //remove x coordinate token
  963. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y coordinate
  964. pathElements.RemoveAt(0); //remove y coordinate token
  965. return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, y), x, y);
  966. }
  967. /// <summary>
  968. /// parses a "moveto", "close loop" or "lineto" path element with relative coordinates
  969. /// </summary>
  970. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  971. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  972. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  973. /// <returns>the point at the end of the move, close loop or line action and its exact, unscaled coordinates</returns>
  974. private Tuple<Point, double, double> Parse_m_l(List<string> pathElements, double lastPositionX, double lastPositionY)
  975. {
  976. pathElements.RemoveAt(0); //remove element descriptor token
  977. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative x coordinate
  978. pathElements.RemoveAt(0); //remove x coordinate token
  979. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative y coordinate
  980. pathElements.RemoveAt(0); //remove y coordinate token
  981. x = lastPositionX + x; //compute absolute x coordinate
  982. y = lastPositionY + y; //compute absolute y coordinate
  983. return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, y), x, y);
  984. }
  985. /// <summary>
  986. /// parses a "horizontal lineto" path element with absolute coordinates
  987. /// </summary>
  988. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  989. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  990. /// <returns>the point at the end of the horizontal line action and its exact, unscaled coordinates</returns>
  991. private Tuple<Point, double, double> Parse_H(List<string> pathElements, double lastPositionY)
  992. {
  993. pathElements.RemoveAt(0); //remove element descriptor token
  994. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x coordinate
  995. pathElements.RemoveAt(0); //remove x coordinate token
  996. return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, lastPositionY), x, lastPositionY);
  997. }
  998. /// <summary>
  999. /// parses a "horizontal lineto" path element with relative coordinates
  1000. /// </summary>
  1001. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1002. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1003. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1004. /// <returns>the point at the end of the horizontal line action and its exact, unscaled coordinates</returns>
  1005. private Tuple<Point, double, double> Parse_h(List<string> pathElements, double lastPositionX, double lastPositionY)
  1006. {
  1007. pathElements.RemoveAt(0); //remove element descriptor token
  1008. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative x coordinate
  1009. pathElements.RemoveAt(0); //remove x coordinate token
  1010. x = lastPositionX + x; //compute absolute x coordinate
  1011. return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, lastPositionY), x, lastPositionY);
  1012. }
  1013. /// <summary>
  1014. /// parses a "vertical lineto" path element with absolute coordinates
  1015. /// </summary>
  1016. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1017. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1018. /// <returns>the point at the end of the vertical line action and its exact, unscaled coordinates</returns>
  1019. private Tuple<Point, double, double> Parse_V(List<string> pathElements, double lastPositionX)
  1020. {
  1021. pathElements.RemoveAt(0); //remove element descriptor token
  1022. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y coordinate
  1023. pathElements.RemoveAt(0); //remove y coordinate token
  1024. return new Tuple<Point, double, double>(ScaleAndCreatePoint(lastPositionX, y), lastPositionX, y);
  1025. }
  1026. /// <summary>
  1027. /// parses a "vertical lineto" path element with relative coordinates
  1028. /// </summary>
  1029. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1030. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1031. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1032. /// <returns>the point at the end of the vertical line action and its exact, unscaled coordinates</returns>
  1033. private Tuple<Point, double, double> Parse_v(List<string> pathElements, double lastPositionX, double lastPositionY)
  1034. {
  1035. pathElements.RemoveAt(0); //remove element descriptor token
  1036. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative y coordinate
  1037. pathElements.RemoveAt(0); //remove y coordinate token
  1038. y = lastPositionY + y; //compute absolute y coordinate
  1039. return new Tuple<Point, double, double>(ScaleAndCreatePoint(lastPositionX, y), lastPositionX, y);
  1040. }
  1041. /// <summary>
  1042. /// parses a "cubic bezier curve" path element with absolute coordinates
  1043. /// </summary>
  1044. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1045. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1046. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1047. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the second bezier control point</returns>
  1048. private Tuple<List<Point>, double, double, double, double> Parse_C(List<string> pathElements, double lastPositionX, double lastPositionY)
  1049. {
  1050. pathElements.RemoveAt(0); //remove element descriptor token
  1051. double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point x coordinate
  1052. pathElements.RemoveAt(0); //remove x coordinate token
  1053. double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point y coordinate
  1054. pathElements.RemoveAt(0); //remove y coordinate token
  1055. double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
  1056. pathElements.RemoveAt(0); //remove x coordinate token
  1057. double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
  1058. pathElements.RemoveAt(0); //remove y coordinate token
  1059. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1060. pathElements.RemoveAt(0); //remove x coordinate token
  1061. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1062. pathElements.RemoveAt(0); //remove y coordinate token
  1063. return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
  1064. }
  1065. /// <summary>
  1066. /// parses a "cubic bezier curve" path element with relative coordinates
  1067. /// </summary>
  1068. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1069. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1070. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1071. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the second bezier control point</returns>
  1072. private Tuple<List<Point>, double, double, double, double> Parse_c(List<string> pathElements, double lastPositionX, double lastPositionY)
  1073. {
  1074. pathElements.RemoveAt(0); //remove element descriptor token
  1075. double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point x coordinate
  1076. pathElements.RemoveAt(0); //remove x coordinate token
  1077. double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point y coordinate
  1078. pathElements.RemoveAt(0); //remove y coordinate token
  1079. double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
  1080. pathElements.RemoveAt(0); //remove x coordinate token
  1081. double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
  1082. pathElements.RemoveAt(0); //remove y coordinate token
  1083. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1084. pathElements.RemoveAt(0); //remove x coordinate token
  1085. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1086. pathElements.RemoveAt(0); //remove y coordinate token
  1087. x1 = lastPositionX + x1; //compute absolute x coordinate
  1088. y1 = lastPositionY + y1; //compute absolute y coordinate
  1089. x2 = lastPositionX + x2; //compute absolute x coordinate
  1090. y2 = lastPositionY + y2; //compute absolute y coordinate
  1091. x = lastPositionX + x; //compute absolute x coordinate
  1092. y = lastPositionY + y; //compute absolute y coordinate
  1093. return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
  1094. }
  1095. /// <summary>
  1096. /// parses a "cubic bezier curve shorthand" path element with absolute coordinates
  1097. /// </summary>
  1098. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1099. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1100. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1101. /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
  1102. /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
  1103. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the second bezier control point</returns>
  1104. private Tuple<List<Point>, double, double, double, double> Parse_S(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
  1105. {
  1106. pathElements.RemoveAt(0); //remove element descriptor token
  1107. double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
  1108. pathElements.RemoveAt(0); //remove x coordinate token
  1109. double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
  1110. pathElements.RemoveAt(0); //remove y coordinate token
  1111. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1112. pathElements.RemoveAt(0); //remove x coordinate token
  1113. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1114. pathElements.RemoveAt(0); //remove y coordinate token
  1115. double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
  1116. double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
  1117. return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
  1118. }
  1119. /// <summary>
  1120. /// parses a "cubic bezier curve shorthand" path element with relative coordinates
  1121. /// </summary>
  1122. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1123. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1124. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1125. /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
  1126. /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
  1127. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the second bezier control point</returns>
  1128. private Tuple<List<Point>, double, double, double, double> Parse_s(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
  1129. {
  1130. pathElements.RemoveAt(0); //remove element descriptor token
  1131. double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
  1132. pathElements.RemoveAt(0); //remove x coordinate token
  1133. double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
  1134. pathElements.RemoveAt(0); //remove y coordinate token
  1135. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1136. pathElements.RemoveAt(0); //remove x coordinate token
  1137. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1138. pathElements.RemoveAt(0); //remove y coordinate token
  1139. double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
  1140. double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
  1141. x2 = lastPositionX + x2; //compute absolute x coordinate
  1142. y2 = lastPositionY + y2; //compute absolute y coordinate
  1143. x = lastPositionX + x; //compute absolute x coordinate
  1144. y = lastPositionY + y; //compute absolute y coordinate
  1145. return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
  1146. }
  1147. /// <summary>
  1148. /// parses a "quadratic bezier curve" path element with absolute coordinates
  1149. /// </summary>
  1150. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1151. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1152. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1153. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the bezier control point</returns>
  1154. private Tuple<List<Point>, double, double, double, double> Parse_Q(List<string> pathElements, double lastPositionX, double lastPositionY)
  1155. {
  1156. pathElements.RemoveAt(0); //remove element descriptor token
  1157. double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point x coordinate
  1158. pathElements.RemoveAt(0); //remove x coordinate token
  1159. double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point y coordinate
  1160. pathElements.RemoveAt(0); //remove y coordinate token
  1161. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1162. pathElements.RemoveAt(0); //remove x coordinate token
  1163. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1164. pathElements.RemoveAt(0); //remove y coordinate token
  1165. return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
  1166. }
  1167. /// <summary>
  1168. /// parses a "quadratic bezier curve" path element with relative coordinates
  1169. /// </summary>
  1170. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1171. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1172. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1173. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the bezier control point</returns>
  1174. private Tuple<List<Point>, double, double, double, double> Parse_q(List<string> pathElements, double lastPositionX, double lastPositionY)
  1175. {
  1176. pathElements.RemoveAt(0); //remove element descriptor token
  1177. double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point x coordinate
  1178. pathElements.RemoveAt(0); //remove x coordinate token
  1179. double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point y coordinate
  1180. pathElements.RemoveAt(0); //remove y coordinate token
  1181. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1182. pathElements.RemoveAt(0); //remove x coordinate token
  1183. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1184. pathElements.RemoveAt(0); //remove y coordinate token
  1185. x1 = lastPositionX + x1; //compute absolute x coordinate
  1186. y1 = lastPositionY + y1; //compute absolute y coordinate
  1187. x = lastPositionX + x; //compute absolute x coordinate
  1188. y = lastPositionY + y; //compute absolute y coordinate
  1189. return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
  1190. }
  1191. /// <summary>
  1192. /// parses a "quadratic bezier curve shorthand" path element with absolute coordinates
  1193. /// </summary>
  1194. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1195. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1196. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1197. /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
  1198. /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
  1199. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the bezier control point</returns>
  1200. private Tuple<List<Point>, double, double, double, double> Parse_T(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
  1201. {
  1202. pathElements.RemoveAt(0); //remove element descriptor token
  1203. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1204. pathElements.RemoveAt(0); //remove x coordinate token
  1205. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1206. pathElements.RemoveAt(0); //remove y coordinate token
  1207. double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
  1208. double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
  1209. return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
  1210. }
  1211. /// <summary>
  1212. /// parses a "quadratic bezier curve shorthand" path element with relative coordinates
  1213. /// </summary>
  1214. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1215. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1216. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1217. /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
  1218. /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
  1219. /// <returns>a List of Points containing all sampled points on the bezier curve, aswell as the unscaled x and y coordinates of the last point of the curve and of the bezier control point</returns>
  1220. private Tuple<List<Point>, double, double, double, double> Parse_t(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
  1221. {
  1222. pathElements.RemoveAt(0); //remove element descriptor token
  1223. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1224. pathElements.RemoveAt(0); //remove x coordinate token
  1225. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1226. pathElements.RemoveAt(0); //remove y coordinate token
  1227. x = lastPositionX + x; //compute absolute x coordinate
  1228. y = lastPositionY + y; //compute absolute y coordinate
  1229. double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
  1230. double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
  1231. return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
  1232. }
  1233. /// <summary>
  1234. /// parses a "elliptical arc" path element with absolute coordinates
  1235. /// </summary>
  1236. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1237. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1238. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1239. /// <returns>a List of Points containing all sampled points on the elliptic arc, aswell as the unscaled x and y coordinates of the last point of the arc<returns>
  1240. private Tuple<List<Point>, double, double> Parse_A(List<string> pathElements, double lastPositionX, double lastPositionY)
  1241. {
  1242. pathElements.RemoveAt(0); //remove element descriptor token
  1243. double rx = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x radius
  1244. pathElements.RemoveAt(0); //remove x radius token
  1245. double ry = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y radius
  1246. pathElements.RemoveAt(0); //remove y radius token
  1247. double thetha = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse rotation
  1248. pathElements.RemoveAt(0); //remove rotation token
  1249. bool largeArcFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse large arc flag
  1250. pathElements.RemoveAt(0); //remove large arc flag token
  1251. bool sweepFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse sweep flag
  1252. pathElements.RemoveAt(0); //remove sweep flag token
  1253. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1254. pathElements.RemoveAt(0); //remove x coordinate token
  1255. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1256. pathElements.RemoveAt(0); //remove y coordinate token
  1257. x = x - lastPositionX; //compute relative x coordinate
  1258. y = y - lastPositionY; //compute relative y coordinate
  1259. return new Tuple<List<Point>, double, double>(SampleArc(lastPositionX, lastPositionY, rx, ry, x, y, thetha, largeArcFlag, sweepFlag), x, y);
  1260. }
  1261. /// <summary>
  1262. /// parses a "elliptical arc" path element with relative coordinates
  1263. /// </summary>
  1264. /// <param name="pathElements">a list of all not yet parsed path element tokens and values in correct order, starting with the element to be parsed</param>
  1265. /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
  1266. /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
  1267. /// <returns>a List of Points containing all sampled points on the elliptic arc, aswell as the unscaled x and y coordinates of the last point of the arc</returns>
  1268. private Tuple<List<Point>, double, double> Parse_a(List<string> pathElements, double lastPositionX, double lastPositionY)
  1269. {
  1270. pathElements.RemoveAt(0); //remove element descriptor token
  1271. double rx = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x radius
  1272. pathElements.RemoveAt(0); //remove x radius token
  1273. double ry = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y radius
  1274. pathElements.RemoveAt(0); //remove y radius token
  1275. double thetha = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse rotation
  1276. pathElements.RemoveAt(0); //remove rotation token
  1277. bool largeArcFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse large arc flag
  1278. pathElements.RemoveAt(0); //remove large arc flag token
  1279. bool sweepFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse sweep flag
  1280. pathElements.RemoveAt(0); //remove sweep flag token
  1281. double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
  1282. pathElements.RemoveAt(0); //remove x coordinate token
  1283. double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
  1284. pathElements.RemoveAt(0); //remove y coordinate token
  1285. return new Tuple<List<Point>, double, double>(SampleArc(lastPositionX, lastPositionY, rx, ry, x, y, thetha, largeArcFlag, sweepFlag), x, y);
  1286. }
  1287. /// <summary>
  1288. /// samples an arc of an ellipse into a list of points
  1289. /// </summary>
  1290. /// <param name="lastPositionX">x coordinate of last point</param>
  1291. /// <param name="lastPositionY">y coordinate of last point</param>
  1292. /// <param name="rx">x radius of the ellipse</param>
  1293. /// <param name="ry">y radius of the ellipse</param>
  1294. /// <param name="nextPositionXRelative">x coordinate of next point</param>
  1295. /// <param name="nextPositionYRelative">y coordinate of next point</param>
  1296. /// <param name="thetha">rotation of the ellipse around the x axis</param>
  1297. /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
  1298. /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
  1299. /// <returns></returns>
  1300. private List<Point> SampleArc(double lastPositionX, double lastPositionY, double rx, double ry, double nextPositionXRelative, double nextPositionYRelative, double thetha, bool largeArcFlag, bool sweepFlag)
  1301. {
  1302. double cos = Math.Cos(thetha / 180 * Math.PI);
  1303. double sin = Math.Sin(thetha / 180 * Math.PI);
  1304. double targetXTransformed = cos * nextPositionXRelative - sin * nextPositionYRelative; //rotate target point counterclockwise around the start point by [thetha] degrees, thereby practically rotating an intermediate coordinate system, which has its origin in the start point, clockwise by the same amount
  1305. double targetYTransformed = sin * nextPositionXRelative + cos * nextPositionYRelative;
  1306. var values = SampleEllipticArcBiasedNoRotation(rx, ry, targetXTransformed, targetYTransformed, largeArcFlag, sweepFlag);
  1307. List<Point> result = new List<Point>();
  1308. for (int j = 0; j < values.Item1.Length; j++)
  1309. {
  1310. double xCoordinateRelative = cos * values.Item1[j] + sin * values.Item2[j]; //rotate backwards so intermediate coordinate system and "real" coordinate system have the same rotation again
  1311. double yCoordinateRelative = cos * values.Item2[j] - sin * values.Item1[j];
  1312. double xCoordinateAbsolute = lastPositionX + xCoordinateRelative; //translate relative to absolute coordinates (intermediate coordinate system is now again aligned with the "real" one (the virtual pane on which all vectorgraphic elements are placed) (note that this "real" coordinate system is still not the same as the one actually representing pixels for drawing, as it still has to be scaled appropriately (done inside the ScaleAndCreatePoint method)))
  1313. double yCoordinateAbsolute = lastPositionY + yCoordinateRelative;
  1314. result.Add(ScaleAndCreatePoint(xCoordinateAbsolute, yCoordinateAbsolute));
  1315. }
  1316. return result;
  1317. }
  1318. /// <summary>
  1319. /// samples an elliptical arc with given radii through coordinate origin and endpoint with specified properties
  1320. /// </summary>
  1321. /// <param name="rx">x radius</param>
  1322. /// <param name="ry">y radius</param>
  1323. /// <param name="targetXTransformed">x coordinate of next point</param>
  1324. /// <param name="targetYTransformed">y coordinate of next point</param>
  1325. /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
  1326. /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
  1327. /// <returns></returns>
  1328. private Tuple<double[], double[]> SampleEllipticArcBiasedNoRotation(double rx, double ry, double targetXTransformed, double targetYTransformed, bool largeArcFlag, bool sweepFlag)
  1329. {
  1330. double xStretchFactor = rx / ry; //get rx to ry ratio
  1331. var values = SampleCircleArcBiasedNoRotation(ry, targetXTransformed / xStretchFactor, targetYTransformed, largeArcFlag, sweepFlag); //get a circular arc with radius ry
  1332. for (int j = 0; j < values.Item1.Length; j++)
  1333. {
  1334. values.Item1[j] = values.Item1[j] * xStretchFactor; //correct x coordinates to get an elliptical arc from a circular one
  1335. }
  1336. return values;
  1337. }
  1338. /// <summary>
  1339. /// samples a circular arc with given radius through coordinate origin and endpoint with specified properties
  1340. /// </summary>
  1341. /// <param name="r">radius</param>
  1342. /// <param name="nextPositionXRelative">x coordinate of next point</param>
  1343. /// <param name="nextPositionYRelative">y coordinate of next point</param>
  1344. /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
  1345. /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
  1346. /// <returns></returns>
  1347. private Tuple<double[], double[]> SampleCircleArcBiasedNoRotation(double r, double nextPositionXRelative, double nextPositionYRelative, bool largeArcFlag, bool sweepFlag)
  1348. {
  1349. // code for center computation adapted from https://stackoverflow.com/a/36211852
  1350. double radsq = r * r;
  1351. double q = Math.Sqrt(((nextPositionXRelative) * (nextPositionXRelative)) + ((nextPositionYRelative) * (nextPositionYRelative))); //Math.Sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
  1352. double x3 = (nextPositionXRelative) / 2; //(x1 + x2) / 2;
  1353. double y3 = (nextPositionYRelative) / 2; //(y1 + y2) / 2;
  1354. bool xPlusFlag; //flags needed to select center point "left" of the line between origin and the endpoint (will be used to select correct one ("left" or "right" one) later together with flags passed as arguments
  1355. bool yPlusFlag;
  1356. if (nextPositionXRelative > 0)
  1357. {
  1358. yPlusFlag = true; //left point lies above line
  1359. }
  1360. else
  1361. {
  1362. yPlusFlag = false; //left point lies below line
  1363. }
  1364. if (nextPositionYRelative > 0)
  1365. {
  1366. xPlusFlag = false; //left point lies left of line
  1367. }
  1368. else
  1369. {
  1370. xPlusFlag = true; //left point lies right of line
  1371. }
  1372. if (sweepFlag != largeArcFlag) //need "right" center point, not "left" one (refer to svg specification, sweepFlag means going around the circle in "clockwise" direction, largeArcFlag means tracing the larger of the two possible arcs in the selected direction)
  1373. {
  1374. xPlusFlag = !xPlusFlag;
  1375. yPlusFlag = !yPlusFlag;
  1376. }
  1377. double xC; // coordinates of center point of circle
  1378. double yC;
  1379. if (xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
  1380. else xC = x3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q);
  1381. if (yPlusFlag) yC = y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q); //y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((x2-x1) / q);
  1382. else yC = y3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q);
  1383. var values = SampleCircleArcBiasedAroundCenter(-xC, -yC, nextPositionXRelative - xC, nextPositionYRelative - yC, r, largeArcFlag, sweepFlag);
  1384. for (int j = 0; j < values.Item1.Length; j++)
  1385. {
  1386. values.Item1[j] = values.Item1[j] + xC; //correct center point coordinate bias
  1387. values.Item2[j] = values.Item2[j] + yC;
  1388. }
  1389. return values;
  1390. }
  1391. /// <summary>
  1392. /// samples a circular arc with given radius around the center from the startpoint to the endpoint in the specified direction
  1393. /// </summary>
  1394. /// <param name="xStartPoint">x coordinate of the start point</param>
  1395. /// <param name="yStartPoint">y coordinate of the start point</param>
  1396. /// <param name="xFinalPoint">x coordinate of the final point</param>
  1397. /// <param name="yFinalPoint">y coordinate of the final point</param>
  1398. /// <param name="r">radius</param>
  1399. /// <param name="clockwise">direction</param>
  1400. /// <returns></returns>
  1401. private Tuple<double[], double[]> SampleCircleArcBiasedAroundCenter(double xStartPoint, double yStartPoint, double xFinalPoint, double yFinalPoint, double r, bool largeArcFlag, bool clockwise)
  1402. {
  1403. double phiEnd = Math.Atan2(yFinalPoint, xFinalPoint); // angles between points and origin and the positive x Axis
  1404. double phiStart = Math.Atan2(yStartPoint, xStartPoint);
  1405. double angle = ((double)2 * Math.PI) / (double)samplingRateEllipse; //compute angle increment (equal to the one used for ellipses)
  1406. double angleDifference = Math.Abs(phiStart - phiEnd);
  1407. if (angleDifference > 2 * Math.PI || angleDifference < 0) throw new Exception("angleDifference out of range: " + angleDifference); //TODO remove
  1408. if (largeArcFlag) // get larger angleDifference
  1409. {
  1410. if (angleDifference < Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference; // was smaller angleDifference
  1411. }
  1412. else // get smaller angleDifference
  1413. {
  1414. if (angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference; // was larger angleDifference
  1415. }
  1416. int numberOfPoints = (int)Math.Ceiling(angleDifference / angle); //compute number of points to sample
  1417. double[] xValues = new double[numberOfPoints];
  1418. double[] yValues = new double[numberOfPoints];
  1419. double phiCurrent = phiStart;
  1420. for (int j = 0; j < numberOfPoints - 1; j++) //compute intermediate points
  1421. {
  1422. if (clockwise) phiCurrent -= angle; //get new angle
  1423. else phiCurrent += angle;
  1424. yValues[j] = Math.Sin(phiCurrent) * r; //angles are relative to positive x Axis!
  1425. xValues[j] = Math.Cos(phiCurrent) * r;
  1426. }
  1427. xValues[numberOfPoints - 1] = xFinalPoint; //(last segment always has an angle of less than or exactly 'angle')
  1428. yValues[numberOfPoints - 1] = yFinalPoint;
  1429. return new Tuple<double[], double[]>(xValues, yValues);
  1430. }
  1431. /// <summary>
  1432. /// samples a cubic bezier curve with a static number of steps (samplingRateBezier)
  1433. /// </summary>
  1434. /// <param name="lastPositionX">x coordinate of last point</param>
  1435. /// <param name="lastPositionY">y coordinate of last point</param>
  1436. /// <param name="controlPoint1X">x coordinate of control point 1</param>
  1437. /// <param name="controlPoint1Y">y coordinate of control point 1</param>
  1438. /// <param name="controlPoint2X">x coordinate of control point 2</param>
  1439. /// <param name="controlPoint2Y">y coordinate of control point 2</param>
  1440. /// <param name="nextPositionX">x coordinate of next point</param>
  1441. /// <param name="nextPositionY">y coordinate of next point</param>
  1442. /// <returns>a List of Points containing all sampled points</returns>
  1443. private List<Point> SampleCubicBezier(double lastPositionX, double lastPositionY, double controlPoint1X, double controlPoint1Y, double controlPoint2X, double controlPoint2Y, double nextPositionX, double nextPositionY)
  1444. {
  1445. var line1 = CreateDiscreteLine(lastPositionX, lastPositionY, controlPoint1X, controlPoint1Y);
  1446. var line2 = CreateDiscreteLine(controlPoint1X, controlPoint1Y, controlPoint2X, controlPoint2Y);
  1447. var line3 = CreateDiscreteLine(controlPoint2X, controlPoint2Y, nextPositionX, nextPositionY);
  1448. var quadraticBezier1 = ComputeBezierStep(line1.Item1, line1.Item2, line2.Item1, line2.Item2);
  1449. var quadraticBezier2 = ComputeBezierStep(line2.Item1, line2.Item2, line3.Item1, line3.Item2);
  1450. var values = ComputeBezierStep(quadraticBezier1.Item1, quadraticBezier1.Item2, quadraticBezier2.Item1, quadraticBezier2.Item2);
  1451. List<Point> result = new List<Point>();
  1452. for (int j = 0; j < samplingRateBezier; j++)
  1453. {
  1454. result.Add(ScaleAndCreatePoint(values.Item1[j], values.Item2[j]));
  1455. }
  1456. return result;
  1457. }
  1458. /// <summary>
  1459. /// samples a quadratic bezier curve with a static number of steps (samplingRateBezier)
  1460. /// </summary>
  1461. /// <param name="lastPositionX">x coordinate of last point</param>
  1462. /// <param name="lastPositionY">y coordinate of last point</param>
  1463. /// <param name="controlPointX">x coordinate of control point</param>
  1464. /// <param name="controlPointY">y coordinate of control point</param>
  1465. /// <param name="nextPositionX">x coordinate of next point</param>
  1466. /// <param name="nextPositionY">y coordinate of next point</param>
  1467. /// <returns>a List of Points containing all sampled points</returns>
  1468. private List<Point> SampleQuadraticBezier(double lastPositionX, double lastPositionY, double controlPointX, double controlPointY, double nextPositionX, double nextPositionY)
  1469. {
  1470. var line1 = CreateDiscreteLine(lastPositionX, lastPositionY, controlPointX, controlPointY);
  1471. var line2 = CreateDiscreteLine(controlPointX, controlPointY, nextPositionX, nextPositionY);
  1472. var values = ComputeBezierStep(line1.Item1, line1.Item2, line2.Item1, line2.Item2);
  1473. List<Point> result = new List<Point>();
  1474. for (int j = 0; j < samplingRateBezier; j++)
  1475. {
  1476. result.Add(ScaleAndCreatePoint(values.Item1[j], values.Item2[j]));
  1477. }
  1478. return result;
  1479. }
  1480. /// <summary>
  1481. /// create a discrete line with [samplingRateBezier] points (including start and end point) between two points
  1482. /// </summary>
  1483. /// <param name="point1X">coordinate of point 1</param>
  1484. /// <param name="point1Y">y coordinate of point 1</param>
  1485. /// <param name="point2X">x coordinate of point 2</param>
  1486. /// <param name="point2Y">y coordinate of point 2</param>
  1487. /// <returns>the discrete line as arrays of x and y coordinates</returns>
  1488. private Tuple<double[], double[]> CreateDiscreteLine(double point1X, double point1Y, double point2X, double point2Y)
  1489. {
  1490. double[] resultX = new double[samplingRateBezier];
  1491. double[] resultY = new double[samplingRateBezier];
  1492. for (int j = 0; j < samplingRateBezier; j++)
  1493. {
  1494. var pointResult = LinearInterpolationForBezier(point1X, point1Y, point2X, point2Y, j);
  1495. resultX[j] = pointResult.Item1;
  1496. resultY[j] = pointResult.Item2;
  1497. }
  1498. return new Tuple<double[], double[]>(resultX, resultY);
  1499. }
  1500. /// <summary>
  1501. /// computes the discrete bezier curve between two given dicrete lines/curves
  1502. /// </summary>
  1503. /// <param name="line1X">x coordinates of all points in line 1</param>
  1504. /// <param name="line1Y">y coordinates of all points in line 1</param>
  1505. /// <param name="line2X">x coordinates of all points in line 2</param>
  1506. /// <param name="line2Y">y coordinates of all points in line 2</param>
  1507. /// <returns>the discrete bezier curve</returns>
  1508. private Tuple<double[], double[]> ComputeBezierStep(double[] line1X, double[] line1Y, double[] line2X, double[] line2Y)
  1509. {
  1510. double[] resultX = new double[samplingRateBezier];
  1511. double[] resultY = new double[samplingRateBezier];
  1512. for (int j = 0; j < samplingRateBezier; j++)
  1513. {
  1514. var pointResult = LinearInterpolationForBezier(line1X[j], line1Y[j], line2X[j], line2Y[j], j);
  1515. resultX[j] = pointResult.Item1;
  1516. resultY[j] = pointResult.Item2;
  1517. }
  1518. return new Tuple<double[], double[]>(resultX, resultY);
  1519. }
  1520. /// <summary>
  1521. /// creates the linearly interpolated point at j/(samplingRateBezier - 1) between point 1 and point 2
  1522. /// </summary>
  1523. /// <param name="point1X">x coordinate of point 1</param>
  1524. /// <param name="point1Y">y coordinate of point 1</param>
  1525. /// <param name="point2X">x coordinate of point 2</param>
  1526. /// <param name="point2Y">y coordinate of point 2</param>
  1527. /// <param name="j">number of point to be interpolated, at a total number of [samplingRateBezier] points</param>
  1528. /// <returns>the linearly interpolated point</returns>
  1529. private Tuple<double, double> LinearInterpolationForBezier(double point1X, double point1Y, double point2X, double point2Y, int j)
  1530. {
  1531. double factor = ((double)1 / (double)(samplingRateBezier - 1)) * (double)j; //factor for linear interpolation
  1532. double x = point1X + ((point2X - point1X) * factor);
  1533. double y = point1Y + ((point2Y - point1Y) * factor);
  1534. return new Tuple<double, double>(x, y);
  1535. }
  1536. /// <summary>
  1537. /// parses a hierarchical svg element and all its sub-elements
  1538. /// </summary>
  1539. /// <param name="currentElement">the definition of the top level element as whitespace seperated String[]</param>
  1540. /// <param name="allLines">an array holding all lines of the input file</param>
  1541. /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
  1542. private List<InternalLine> ParseMultiLineSVGElement(string[] currentElement, string[] allLines)
  1543. {
  1544. throw new NotImplementedException();
  1545. }
  1546. /// <summary>
  1547. /// removes the name of the attribute aswell as the '="' at the beginning and the '"' or '">' at the end of an attribute definition
  1548. /// </summary>
  1549. /// <param name="definition">the definition from the svg file</param>
  1550. /// <returns>the value of the attribute, as String (the part of definition contained between '"'s)</returns>
  1551. private String ParseSingleSVGAttribute(String definition)
  1552. {
  1553. return definition.Split('"')[1];
  1554. }
  1555. /// <summary>
  1556. /// fetches a single svg element definition that may extend ovr several lines of the input file, iterates i to point to the last line of the element definition
  1557. /// </summary>
  1558. /// <param name="allLines">an array holding all lines of the input file</param>
  1559. /// <returns>the definition of the current svg element, as String[] split by whitespaces</returns>
  1560. private String[] GetCurrentElement(String[] allLines)
  1561. {
  1562. List<String> currentElementTemp = allLines[i].Split(whitespaces).ToList();
  1563. while (!currentElementTemp.Last().EndsWith(">"))
  1564. {
  1565. i++;
  1566. currentElementTemp.AddRange(allLines[i].Split(whitespaces).ToList());
  1567. }
  1568. return currentElementTemp.ToArray();
  1569. }
  1570. /// <summary>
  1571. /// applies the scale factor to the coordinates and creates a new Point
  1572. /// </summary>
  1573. /// <param name="x">unscaled x coordinate</param>
  1574. /// <param name="y">unscaled y coordinate</param>
  1575. /// <returns>new Point with scaled coordinates</returns>
  1576. private Point ScaleAndCreatePoint(double x, double y)
  1577. {
  1578. return new Point((int)Math.Round(x * scale), (int)Math.Round(y * scale));
  1579. }
  1580. /// <summary>
  1581. /// creates a representation of an ellipse as a List of Points by sampling the outline of the ellipse
  1582. /// </summary>
  1583. /// <param name="x">x coordinate of the center of the ellipse</param>
  1584. /// <param name="y">y coordinate of the center of the ellipse</param>
  1585. /// <param name="rx">x radius of the ellipse</param>
  1586. /// <param name="ry">y radius of the ellipse</param>
  1587. /// <returns>the parsed element as a List of Points</returns>
  1588. private List<Point> SampleEllipse(double x, double y, double rx, double ry)
  1589. {
  1590. List<Point> ellipse = new List<Point>();
  1591. double angle = ((double)2 * Math.PI) / (double)samplingRateEllipse;
  1592. double yScale = ry / rx;
  1593. double[] xValues = new double[samplingRateEllipse / 4];
  1594. double[] yValues = new double[samplingRateEllipse / 4];
  1595. for (int j = 0; j < samplingRateEllipse / 4; j++) //compute offset values of points for one quadrant
  1596. {
  1597. xValues[j] = Math.Sin((double)j * angle) * rx;
  1598. yValues[j] = Math.Cos((double)j * angle) * rx;
  1599. }
  1600. for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for first quadrant
  1601. {
  1602. int xCoord = Convert.ToInt32(Math.Round(x + xValues[j]));
  1603. int yCoord = Convert.ToInt32(Math.Round(y - yValues[j] * yScale));
  1604. ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
  1605. }
  1606. for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for second quadrant
  1607. {
  1608. int xCoord = Convert.ToInt32(Math.Round(x + yValues[j]));
  1609. int yCoord = Convert.ToInt32(Math.Round(y + xValues[j] * yScale));
  1610. ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
  1611. }
  1612. for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for third quadrant
  1613. {
  1614. int xCoord = Convert.ToInt32(Math.Round(x - xValues[j]));
  1615. int yCoord = Convert.ToInt32(Math.Round(y + yValues[j] * yScale));
  1616. ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
  1617. }
  1618. for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for fourth quadrant
  1619. {
  1620. int xCoord = Convert.ToInt32(Math.Round(x - yValues[j]));
  1621. int yCoord = Convert.ToInt32(Math.Round(y - xValues[j] * yScale));
  1622. ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
  1623. }
  1624. ellipse.Add(ScaleAndCreatePoint(Convert.ToInt32(Math.Round(x + 0)), Convert.ToInt32(Math.Round(y - rx * yScale)))); //close ellipse
  1625. return ellipse;
  1626. }
  1627. }
  1628. }