FileImporter.cs 105 KB

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