FileImporter.cs 103 KB

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