22 Commits cb7a849633 ... 521251cd30

Author SHA1 Message Date
  Rumei 521251cd30 changed back to trackable 5 years ago
  Rumei ace626d450 minor changes 5 years ago
  Rumei 215efb66b7 comments added and rearanged code 5 years ago
  Rumei e64e93eae7 small changes 5 years ago
  Rumei 2bc3ff794a merged 5 years ago
  Rumei e39adf3cee small changes for expertenevaluation 5 years ago
  Rumei 1f1e0efe2d testing pInvoke 5 years ago
  Martin Edlund bdc87c57f5 Merge, cleanup and optimisation: 5 years ago
  Martin Edlund a674035906 Re-added debug options, 5 years ago
  Rumei 93d1767f7f removed RightCanvas.Cursor = Cursors.AppStarting 5 years ago
  Rumei 51dc6f5485 minor fix 5 years ago
  Rumei 10785de5e4 ready for testing of setCurPos 5 years ago
  Rumei 3f5b003c53 merged 5 years ago
  Rumei 05365b087e minor changes 5 years ago
  videowall-pc-user 7bad5a23c7 updated parameter 5 years ago
  Rumei 48ab2050cc added Armband dummy 5 years ago
  videowall-pc-user 90b8aa80a0 new ttp file 5 years ago
  Rumei ba9d740392 ready for testing 5 years ago
  Rumei 6a772e547c minor changes 5 years ago
  Rumei 382ac72383 functions in Model extended 5 years ago
  videowall-pc-user c39a2397a0 optitrack working 5 years ago
  Rumei 66e0fbec4b testing 5 years ago
38 changed files with 3333 additions and 376 deletions
  1. 25 6
      .appveyor.yml
  2. 13 0
      Finished Userstories/userstory15.md
  3. 13 0
      Finished Userstories/userstory16.md
  4. 13 0
      Finished Userstories/userstory18.md
  5. 13 0
      Finished Userstories/userstory21.md
  6. 2 2
      Finished Userstories/userstory6.md
  7. 4 1
      README.md
  8. 13 0
      SketchAssistant/GenerateCoverageReport.bat
  9. 15 0
      SketchAssistant/SketchAssistant.sln
  10. BIN
      SketchAssistant/SketchAssistant/optitrack_setup.ttp
  11. 70 0
      SketchAssistant/SketchAssistantWPF/Armband.cs
  12. 0 0
      SketchAssistant/SketchAssistantWPF/DebugData.cs
  13. 1550 7
      SketchAssistant/SketchAssistantWPF/FileImporter.cs
  14. 2 2
      SketchAssistant/SketchAssistantWPF/Frame.cs
  15. 0 48
      SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs
  16. 11 16
      SketchAssistant/SketchAssistantWPF/InternalLine.cs
  17. 245 43
      SketchAssistant/SketchAssistantWPF/MVP_Model.cs
  18. 149 100
      SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs
  19. 9 8
      SketchAssistant/SketchAssistantWPF/MVP_View.cs
  20. 73 26
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml
  21. 137 82
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml.cs
  22. 2 2
      SketchAssistant/SketchAssistantWPF/OptiTrackConnector.cs
  23. 32 0
      SketchAssistant/SketchAssistantWPF/SketchAssistantWPF.csproj
  24. 6 0
      SketchAssistant/SketchAssistantWPF/packages.config
  25. 585 32
      SketchAssistant/WhiteTests/UITest.cs
  26. 47 1
      SketchAssistant/WhiteTests/WhiteTests.csproj
  27. 10 0
      SketchAssistant/WhiteTests/test.runsettings
  28. 30 0
      SketchAssistant/WhiteTests/test_input_files/blacklisted/all_no_size_def_in_header.svg
  29. 29 0
      SketchAssistant/WhiteTests/test_input_files/blacklisted/all_unclosed.svg
  30. 30 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/all.svg
  31. 14 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/circle_ellipse.svg
  32. 10 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/line.svg
  33. 21 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/path.svg
  34. 10 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/polygon.svg
  35. 11 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/polyline.svg
  36. 13 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/rect.svg
  37. BIN
      SketchAssistant/optitrack_setup.ttp
  38. 126 0
      screenres.ps1

+ 25 - 6
.appveyor.yml

@@ -1,8 +1,27 @@
 image:
- - Visual Studio 2017
- 
+    - Visual Studio 2017
+
 install:
- - "nuget install TestStack.White"
- - "nuget install MSTest.TestAdapter"
- - "nuget install MSTest.TestFramework"
- - "nuget restore SketchAssistant/SketchAssistant.sln"
+    - "nuget install TestStack.White"
+    - "nuget install MSTest.TestAdapter"
+    - "nuget install MSTest.TestFramework"
+    - "nuget restore SketchAssistant/SketchAssistant.sln"
+
+before_build:
+    - ps: .\screenres.ps1
+
+artifacts:
+    - path: \SketchAssistant\GeneratedReports\
+#  - path: SketchAssistant/WhiteTests/test_input_files/whitelisted/*.svg
+#    name: whitelisted svg files for testing
+#  - path: SketchAssistant/WhiteTests/test_input_files/blacklisted/*.svg
+#    name: blacklisted svg files for testing
+
+before_test:
+    - ps: "SketchAssistant/GenerateCoverageReport.bat"
+
+test:
+    #don't run tests depending on [DeploymentItem] and filesystem access
+    categories:
+        except:
+            - FileIO

+ 13 - 0
Finished Userstories/userstory15.md

@@ -0,0 +1,13 @@
+# Userstory 15  
+ 
+|**ID**|15|  
+|-|-|
+|**Name**|Schöne Linien|
+|**Beschreibung**|Als Auftraggeber wünsche ich, dass die Linien die gezeichnet werden, an den Enden dünner und in der Mitte dicker sind, um einem echten Stift zu ähneln.|
+|**Akzeptanzkriterium**|Die Userstory wird akzeptiert, wenn die vom Anwender gezeichnete Line am Anfang schmaler ist, im Verlauf etwas breiter wird und am Ende wieder so schmal wie am Anfang ist.|
+|Geschätzter Aufwand (Story Points)|10|
+|Entwickler|Tim|
+|Umgesetzt in Iteration|9|
+|Tatsächlicher Aufwand (Std.)|keine|
+|Velocity (Std./Story Point)|keine|
+|Bemerkungen|Wurde durch Userstory 16 umgesetzt|

+ 13 - 0
Finished Userstories/userstory16.md

@@ -0,0 +1,13 @@
+# Userstory 16  
+ 
+|**ID**|16|  
+|-|-|
+|**Name**|Änderung der Nutzeroberfläche & wpf|
+|**Beschreibung**|Die Nutzeroberfläche wird an die neuen Anforderungen des Auftraggebers angepasst.|
+|**Akzeptanzkriterium**|Die Userstory ist akzeptiert, wenn die UI eine Zeichenfläche enthält und eine Fläche zum Anzeigen von Grafiken, sowie alle vorherig verfügbare Knöpfe. Zusätzlich ist erforderlich, dass die UI statt auf WinForms auf WPF basiert. Auch sollen die Knöpfe in der Toolbar durch Symbole, statt durch Text markiert sein, und es einen Edit Button gibt in welchem zusätzlich die Funktionen vorhanden sind.|
+|Geschätzter Aufwand (Story Points)|10|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|9|
+|Tatsächlicher Aufwand (Std.)|30|
+|Velocity (Std./Story Point)|3|
+|Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory18.md

@@ -0,0 +1,13 @@
+# Userstory 18  
+ 
+|**ID**|18|  
+|-|-|
+|**Name**|Antialiasing|
+|**Beschreibung**|Als Auftraggeber möchte ich eine möglichst saubere Linie auf dem Bildschrim sehen, das heißt, dass die Kanten der Pixel geglättet werden.|
+|**Akzeptanzkriterium**|Die Breite der Linie wird an die Größe des Bildes in der rechten Picturebox angepasst. Zudem soll Antialiasing auf die Linien angewendet werden, damit diese glatte Kanten aufweisen.|
+|Geschätzter Aufwand (Story Points)|keine|
+|Entwickler|keiner|
+|Umgesetzt in Iteration|9|
+|Tatsächlicher Aufwand (Std.)|keine|
+|Velocity (Std./Story Point)|keine|
+|Bemerkungen|Wurde durch Userstory 16 gelöst|

+ 13 - 0
Finished Userstories/userstory21.md

@@ -0,0 +1,13 @@
+# Userstory 21  
+ 
+|**ID**|21|  
+|-|-|
+|**Name**|InkCanvas|
+|**Beschreibung**|Bei den manuellen Tests haben sich beim zeichnen auserhalb der vorgesehnen Zeichenoberfläche Fehler ergeben. Als Programmierer benötige ich eine Oberfläche, die resistenter gegen solche Fehler ist.|
+|**Akzeptanzkriterium**|InkCanvas soll verwendet werden, um die aktuelle Linie zu zeichnen und anzuzeigen.|
+|Geschätzter Aufwand (Story Points)|5|
+|Entwickler|Tim Reischl|
+|Umgesetzt in Iteration|13|
+|Tatsächlicher Aufwand (Std.)|30|
+|Velocity (Std./Story Point)|6|
+|Bemerkungen|Keine|

+ 2 - 2
Finished Userstories/userstory6.md

@@ -4,10 +4,10 @@
 |-|-|
 |**Name**|Löschfunktionalität|
 |**Beschreibung**|Man kann einen Knopf in der UI drücken um in den Löschmodus zu kommen, und um wieder aus dem Löschmodus rauszukommen. Wenn man im Löschmodus ist, kann man auf einer Leinwand im rechten Bereich gezeichnete Elemente löschen indem man auf sie klickt.|
-|**Akzeptanzkriterium**|Der Knopf für den Löschmodus kann nur gedrückt werden, wenn im rechten Bereich eine Zeichennleinwand vorhanden ist. Wenn man den Knopf drückt ohne im Löschmodus zu sein, verändert sich der Knopf erkennbar und man kommt in den Löschmodus. Drückt man den Knopf, während man im Löschmodus ist, wird der Löschmodus beendet und der Knopf bekommt wieder sein urspüngliches Aussehen. Während man im Löschmodus ist, kann man auf der Leinwand im rechten Bereich Elemente löschen, wenn der Mauszeiger sie berührt während man die Maustaste gedrückt hält. Die Elemente die man löscht verschwinden. Drückt man den Löschknopf während man in einem anderen Modus ist (z.B. Zeichenmodus) wird der andere Modus beendet und man ist im Löschmodus.|
+|**Akzeptanzkriterium**|Der Knopf für den Löschmodus kann nur gedrückt werden, wenn im rechten Bereich eine Zeichennleinwand vorhanden ist. Wenn man den Knopf drückt ohne im Löschmodus zu sein, verändert sich der Knopf erkennbar und man kommt in den Löschmodus. Drückt man den Knopf, während man im Löschmodus ist, bleibt der Knopf gleich und man ist immer noch im Löschmodus. Während man im Löschmodus ist, kann man auf der Leinwand im rechten Bereich Elemente löschen, wenn der Mauszeiger sie berührt während man die Maustaste gedrückt hält. Die Elemente die man löscht verschwinden. Drückt man den Löschknopf während man in einem anderen Modus ist (z.B. Zeichenmodus) wird der andere Modus beendet und man ist im Löschmodus.|
 |Geschätzter Aufwand (Story Points)|16|
 |Entwickler|Martin Edlund, Tim Reischl|
 |Umgesetzt in Iteration|3|
 |Tatsächlicher Aufwand (Std.)|14,5|
 |Velocity (Std./Story Point)|0,90625|
-|Bemerkungen|Keine|
+|Bemerkungen|Keine|

+ 4 - 1
README.md

@@ -1 +1,4 @@
-# BP-SketchAssistant
+# Interactive Sketch Assistant
+[![Build status](https://ci.appveyor.com/api/projects/status/yvefugnjqwc8649y?svg=true)](https://ci.appveyor.com/project/m-edlund/bp-sketchassistant)
+
+A Sketch Assistant to help you with sketching

+ 13 - 0
SketchAssistant/GenerateCoverageReport.bat

@@ -0,0 +1,13 @@
+if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"
+
+"%~dp0\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe" ^
+-register:user ^
+-target:"%~dp0\packages\Microsoft.TestPlatform.16.0.0\tools\net451\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" ^
+-targetargs:"%~dp0\WhiteTests\bin\Debug\WhiteTests.dll" ^
+-filter:"+[SketchAssistantWPF*]*" ^
+-mergebyhash ^
+-output:"%~dp0\GeneratedReports\opencovertests.xml"
+
+"%~dp0\packages\ReportGenerator.4.0.14\tools\net47\ReportGenerator.exe" ^
+-reports:"%~dp0\GeneratedReports\opencovertests.xml" ^
+-targetdir:"%~dp0\GeneratedReports\ReportGeneratorOutput"

+ 15 - 0
SketchAssistant/SketchAssistant.sln

@@ -7,20 +7,35 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchAssistantWPF", "Sketc
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhiteTests", "WhiteTests\WhiteTests.csproj", "{EB09C624-91F2-465F-825B-559BF7A7D5CB}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CoverageTools", "CoverageTools", "{A179F8A5-FBEB-4DBB-8AF4-DD1B5C714A8F}"
+	ProjectSection(SolutionItems) = preProject
+		GenerateCoverageReport.bat = GenerateCoverageReport.bat
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
+		Debug|x86 = Debug|x86
 		Release|Any CPU = Release|Any CPU
+		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Debug|x86.ActiveCfg = Debug|x86
+		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Debug|x86.Build.0 = Debug|x86
 		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Release|x86.ActiveCfg = Release|x86
+		{EE53AE79-2AA0-4F43-9638-1789B189D5C3}.Release|x86.Build.0 = Release|x86
 		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Debug|x86.Build.0 = Debug|Any CPU
 		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Release|x86.ActiveCfg = Release|Any CPU
+		{EB09C624-91F2-465F-825B-559BF7A7D5CB}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

BIN
SketchAssistant/SketchAssistant/optitrack_setup.ttp


+ 70 - 0
SketchAssistant/SketchAssistantWPF/Armband.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace SketchAssistantWPF
+{
+    internal class Armband
+    {
+        //[StructLayout(LayoutKind.Sequential)]
+        //public class BodyActuator
+        //{
+        //    enum BodyActuator_Type
+        //    {
+        //        BODYACTUATOR_TYPE_NONE,
+        //        BODYACTUATOR_TYPE_EAI,
+        //        BODYACTUATOR_TYPE_PIEZO,
+        //        BODYACTUATOR_TYPE_ERM,
+        //        BODYACTUATOR_TYPE_EMS
+        //    };
+        //    bool valid;
+        //    ushort actuatorCount;
+        //    BodyActuator_Type type;
+        //    ArduinoHub arduinoHub;
+        //    EAIHub eaiHub;
+        //}
+
+
+        //[StructLayout(LayoutKind.Sequential)]
+        //public class EAIHub
+        //{
+        //    bool valid;
+        //    pthread_mutex_t mutex; //TODO fix this
+        //    pthread_t thread;
+        //    Actuator* actuators; //TODO
+        //    uint tactorType;
+        //    uint modulation;
+        //    int deviceID;
+        //}
+
+        //[StructLayout(LayoutKind.Sequential)]
+        //public class ArduinoHub
+        //{
+        //    enum ArduinoHub_Type
+        //    {
+        //        ARDUINOHUB_TYPE_PIEZO = 'P',
+        //        ARDUINOHUB_TYPE_ERM = 'E',
+        //        ARDUINOHUB_TYPE_EMS = 'M'
+        //    }
+        //    bool valid;
+        //    pthread_mutex_t mutex; //TODO fix this
+        //    pthread_t thread;
+        //    ArduinoHub_Type arduinoType;
+        //    Serial* serial;
+        //    Actuator* actuators;
+        //}
+
+        //[DllImport("BodyActuator.dll", EntryPoint = "BodyActuator_actuate")]
+        //static extern void pushForward(ref BodyActuator self, byte tactor, double intensity, ulong duration);
+
+        internal void pushForward()
+        {
+            Console.WriteLine("FORWARD_PUSH");
+        }
+
+        internal void pushBackward()
+        {
+            Console.WriteLine("BACKWARD_PUSH");
+        }
+    }
+}

File diff suppressed because it is too large
+ 0 - 0
SketchAssistant/SketchAssistantWPF/DebugData.cs


+ 1550 - 7
SketchAssistant/SketchAssistantWPF/FileImporter.cs

@@ -1,5 +1,7 @@
 using System;
 using System.Collections.Generic;
+//using System.Drawing;
+using System.Globalization;
 using System.Windows;
 using System.Linq;
 using System.Text;
@@ -10,6 +12,32 @@ namespace SketchAssistantWPF
 {
     public class FileImporter
     {
+
+        /// <summary>
+        /// scale factor for coordinates of svg file
+        /// </summary>
+        double scale;
+
+        /// <summary>
+        /// line pointer for the current svg document
+        /// </summary>
+        int i;
+
+        /// <summary>
+        /// array containing all characters interpreted as whitespaces which seperate words/tokens in the input file
+        /// </summary>
+        readonly char[] whitespaces = new char[] { ' ', ',' };
+
+        /// <summary>
+        /// number of points to create along the outline of an ellipse, divisible by 4
+        /// </summary>
+        readonly int samplingRateEllipse = 12;
+
+        /// <summary>
+        /// number of points to create on a bezier curve, including start and end point (even number will result in "flat" bezier curves, uneven number in "pointed" ones)
+        /// </summary>
+        readonly int samplingRateBezier = 101;
+
         /// <summary>
         /// parses a drawing consisting of line objects, given as a file in the application specific .isad format
         /// </summary>
@@ -27,7 +55,6 @@ namespace SketchAssistantWPF
         /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
         private Tuple<int, int, List<InternalLine>> ParseISADInput(String[] allLines)
         {
-
             if (allLines.Length == 0)
             {
                 throw new FileImporterException("file is empty", "", -1);
@@ -40,10 +67,9 @@ namespace SketchAssistantWPF
             {
                 throw new FileImporterException("unterminated drawing definition", ".isad files have to end with the 'enddrawing' token", allLines.Length);
             }
-
             Tuple<int, int> dimensions = ParseISADHeader(allLines);
             List<InternalLine> picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2);
-            
+
             return new Tuple<int, int, List<InternalLine>>(dimensions.Item1, dimensions.Item2, picture);
         }
 
@@ -75,12 +101,9 @@ namespace SketchAssistantWPF
         /// <returns>the parsed picture as a list of lines</returns>
         private List<InternalLine> ParseISADBody(String[] allLines, int width, int height)
         {
-
             String lineStartString = "line";
             String lineEndString = "endline";
-
             List<InternalLine> drawing = new List<InternalLine>();
-
             //number of the line currently being parsed, enumeration starting at 0, body starts at the third line, therefore lin number 2
             int i = 2;
             //parse 'line' token and complete line definition
@@ -140,5 +163,1525 @@ namespace SketchAssistantWPF
             return ParseISADInput(allLines);
         }
 
+        /// <summary>
+        /// parses a svg drawing, given as a .svg file
+        /// <para />several severe restrictions to the svg standard apply:
+        /// <para /> - width and heigth values must be integers
+        /// <para /> - the supported svg elements to be drawn must be placed on top level directly inside the 'svg' tag
+        /// <para /> - except for the global 'svg' tag, no hierarchical elements (elements which contain other svg elements) may exist. in other words: after an opening element tag no other opening element tag may occur before the closing tag of this element.
+        /// <para /> - lines in front of the (single) opening and after the (single) closing global svg tag will be ignored during parsing
+        /// <para /> - unsupported svg elements on top level will be ignored during parsing
+        /// <para /> - the input file must not contain empty lines
+        /// <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
+        /// </summary>
+        /// <param name="fileName">the path of the input file</param>
+        /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
+        public Tuple<int, int, List<InternalLine>> ParseSVGInputFile(String fileName, int windowWidth, int windowHeight)
+        {
+            return ParseSVGInput(System.IO.File.ReadAllLines(fileName), windowWidth, windowHeight);
+        }
+
+        /// <summary>
+        /// parses a svg drawing, given as the content of a .svg file, seperated into lines
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
+        private Tuple<int, int, List<InternalLine>> ParseSVGInput(String[] allLines, double windowWidth, double windowHeight)
+        {
+            i = 0; //reset line pointer
+            if (allLines.Length == 0) //check for empty file
+            {
+                throw new FileImporterException("file is empty", "", -1);
+            }
+            var sizedef = ParseSVGHeader(allLines); //parse svg file header and get internal coordinate range
+            i++;
+            int width; //width of the resulting picture in pixels
+            int height; //height of the resulting picture in pixels
+            if (windowWidth != 0 && windowHeight != 0)
+            {
+                if (windowWidth / windowHeight > sizedef.Item1 / sizedef.Item2) //height dominant, width has to be smaller than drawing window to preserve xy-scale
+                {
+                    scale = windowHeight / sizedef.Item2;
+                    height = (int)Math.Round(windowHeight);
+                    width = (int)Math.Round(scale * sizedef.Item1);
+                }
+                else //width dominant, height has to be smaller than drawing window to preserve xy-scale
+                {
+                    scale = windowWidth / sizedef.Item1;
+                    width = (int)Math.Round(windowWidth);
+                    height = (int)Math.Round(scale * sizedef.Item2);
+                }
+            }
+            else
+            {
+                scale = 1;
+                width = sizedef.Item1;
+                height = sizedef.Item2;
+            }
+            for (int j = 0; j < allLines.Length; j++)
+            {
+                allLines[j] = allLines[j].Trim(whitespaces);
+            }
+            List<InternalLine> picture = ParseSVGBody(allLines); //parse whole svg drawing into list of lines
+            return new Tuple<int, int, List<InternalLine>>(width, height, picture);
+        }
+
+        /// <summary>
+        /// 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
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the internal coordinate range of this drawing</returns>
+        private Tuple<int, int> ParseSVGHeader(String[] allLines)
+        {
+            while (!allLines[i].StartsWith("<svg")) //skip non-relevant metadata at start of svg file
+            {
+                i++;
+            }
+            String[] currentLine = allLines[i].Split(' ');
+            int width = -1;
+            int height = -1;
+            for (int j = 0; j < currentLine.Length; j++)
+            {
+                if (currentLine[j].StartsWith("width"))
+                {
+                    width = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
+                }
+                else if (currentLine[j].StartsWith("height"))
+                {
+                    height = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
+                }
+            }
+            if (width == -1)
+            {
+                throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i + 1);
+            }
+            if (height == -1)
+            {
+                throw new FileImporterException("missing height definition in SVG header", "the header should contain the \"height=...\" attribute", i + 1);
+            }
+            return new Tuple<int, int>(width, height);
+        }
+
+        /// <summary>
+        /// parses all relevant svg element definitions and skips the ones not representable by the sketch assistant
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the parsed picture as a list of lines</returns>
+        private List<InternalLine> ParseSVGBody(String[] allLines)
+        {
+            List<InternalLine> picture = new List<InternalLine>();
+            while (!allLines[i].StartsWith("</svg"))
+            {
+                List<InternalLine> element = ParseSingleSVGElement(allLines);
+                if (element != null)
+                {
+                    picture.AddRange(element);
+                }
+                i++;
+                if (i > allLines.Length - 1) throw new FileImporterException("unterminated input file: missing </svg> tag", "the file must not contain empty lines", i + 1);
+            }
+            return picture;
+        }
+
+        /// <summary>
+        /// parses one toplevel svg element
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the parsed Element as a list of lines</returns>
+        private List<InternalLine> ParseSingleSVGElement(string[] allLines)
+        {
+            String[] currentElement = GetCurrentElement(allLines);
+            return ParseSingleLineSVGElement(currentElement);
+        }
+
+        /// <summary>
+        /// parses a single toplevel svg element only taking one line
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
+        private List<InternalLine> ParseSingleLineSVGElement(string[] currentElement)
+        {
+            List<Point> points = null;
+            List<InternalLine> element = null;
+            switch (currentElement[0])
+            {
+                case "<rect":
+                    points = ParseRect(currentElement);
+                    break;
+                case "<circle":
+                    points = ParseCircle(currentElement);
+                    break;
+                case "<ellipse":
+                    points = ParseEllipse(currentElement);
+                    break;
+                case "<line":
+                    points = ParseLine(currentElement);
+                    break;
+                case "<polyline":
+                    points = ParsePolyline(currentElement);
+                    break;
+                case "<polygon":
+                    points = ParsePolygon(currentElement);
+                    break;
+                case "<path":
+                    element = ParsePath(currentElement);
+                    break;
+                default: //unsupported svg element
+                    return null; //simply ignore
+            }
+            if (element == null)
+            {
+                element = new List<InternalLine>();
+                element.Add(new InternalLine(points));
+            }
+            return element;
+        }
+
+        /// <summary>
+        /// parses a rectangle definition into a List of Points representing a single line around the rectangle (in clockwise direction), ignoring rounded corners
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParseRect(string[] currentElement)
+        {
+            double x = 0;
+            double y = 0;
+            double w = 0;
+            double h = 0;
+            double rx = 0;
+            double ry = 0;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("x="))
+                {
+                    x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("y="))
+                {
+                    y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("width="))
+                {
+                    w = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("height="))
+                {
+                    h = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("rx="))
+                {
+                    rx = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("ry="))
+                {
+                    ry = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+            }
+            List<Point> rect = new List<Point>();
+            rect.Add(ScaleAndCreatePoint(x, y));
+            rect.Add(ScaleAndCreatePoint(x + w, y));
+            rect.Add(ScaleAndCreatePoint(x + w, y + h));
+            rect.Add(ScaleAndCreatePoint(x, y + h));
+            rect.Add(ScaleAndCreatePoint(x, y));
+            return rect;
+        }
+
+        /// <summary>
+        /// parses a circle definition into a List of Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParseCircle(string[] currentElement)
+        {
+            double x = 0;
+            double y = 0;
+            double r = 0;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("cx="))
+                {
+                    x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("cy="))
+                {
+                    y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("r="))
+                {
+                    r = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+            }
+            return SampleEllipse(x, y, r, r);
+        }
+
+        /// <summary>
+        /// parses a ellipse definition into a List of Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParseEllipse(string[] currentElement)
+        {
+            double x = 0;
+            double y = 0;
+            double rx = 0;
+            double ry = 0;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("cx="))
+                {
+                    x = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("cy="))
+                {
+                    y = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("rx="))
+                {
+                    rx = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("ry="))
+                {
+                    ry = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+            }
+            return SampleEllipse(x, y, rx, ry);
+        }
+
+        /// <summary>
+        /// parses a line definition into a List of two Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParseLine(string[] currentElement)
+        {
+            double x1 = 0;
+            double y1 = 0;
+            double x2 = 0;
+            double y2 = 0;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("x1="))
+                {
+                    x1 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("y1="))
+                {
+                    y1 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("x2="))
+                {
+                    x2 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+                else if (currentElement[j].StartsWith("y2="))
+                {
+                    y2 = Convert.ToDouble(ParseSingleSVGAttribute(currentElement[j]), CultureInfo.InvariantCulture);
+                }
+            }
+            List<Point> line = new List<Point>();
+            line.Add(ScaleAndCreatePoint(x1, y1));
+            line.Add(ScaleAndCreatePoint(x2, y2));
+            return line;
+        }
+
+        /// <summary>
+        /// parses a polyline definition into a List of Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParsePolyline(string[] currentElement)
+        {
+            String[] points = null;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("points="))
+                {
+                    List<String> pointDefs = new List<string>();
+                    pointDefs.Add(currentElement[j].Substring(8)); //parse first point coordinates by removing 'points="'
+                    j++;
+                    while (!currentElement[j].EndsWith("\""))
+                    {
+                        pointDefs.Add(currentElement[j]); //parse intermediate point coordinates
+                        j++;
+                    }
+                    pointDefs.Add(currentElement[j].Trim('"')); //parse last point coordinates by removing '"'
+                    points = pointDefs.ToArray();
+                }
+            }
+            List<Point> polyline = new List<Point>();
+            for (int k = 0; k < points.Length - 1; k += 2)
+            {
+                polyline.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
+            }
+            return polyline;
+        }
+
+        /// <summary>
+        /// parses a polygon definition into a List of Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> ParsePolygon(string[] currentElement)
+        {
+            String[] points = null;
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("points="))
+                {
+                    List<String> pointDefs = new List<string>();
+                    pointDefs.Add(currentElement[j].Substring(8)); //parse first point coordinates by removing 'points="'
+                    j++;
+                    while (!currentElement[j].EndsWith("\""))
+                    {
+                        pointDefs.Add(currentElement[j]); //parse intermediate point coordinates
+                        j++;
+                    }
+                    pointDefs.Add(currentElement[j].Trim('"')); //parse last point coordinates by removing '"'
+                    points = pointDefs.ToArray();
+                }
+            }
+            List<Point> polygon = new List<Point>();
+            for (int k = 0; k < points.Length - 1; k += 2)
+            {
+                polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
+            }
+            polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[0], CultureInfo.InvariantCulture), Convert.ToDouble(points[1], CultureInfo.InvariantCulture))); //close polygon
+            return polygon;
+        }
+
+        /// <summary>
+        /// parses a path definition into a List of Points
+        /// </summary>
+        /// <param name="currentElement">the definition of the element as whitespace seperated String[]</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<InternalLine> ParsePath(string[] currentElement)
+        {
+            List<String> pathElements = new List<string>();
+            for (int j = 0; j < currentElement.Length; j++)
+            {
+                if (currentElement[j].StartsWith("d="))
+                {
+                    pathElements.Add(currentElement[j].Substring(3)); //parse first path element by removing 'd="'
+                    j++;
+                    while (!currentElement[j].EndsWith("\""))
+                    {
+                        pathElements.Add(currentElement[j]); //parse intermediate path element
+                        j++;
+                    }
+                    pathElements.Add(currentElement[j].Trim('"')); //parse last path element by removing '"'
+                }
+            }
+            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
+            List<InternalLine> element = new List<InternalLine>();
+            List<Point> currentLine = new List<Point>();
+            double lastBezierControlPointX = 0;
+            double lastBezierControlPointY = 0;
+            double lastPositionX;
+            double lastPositionY;
+            double initialPositionX = -1;
+            double initialPositionY = -1;
+            bool newSubpath = true;
+            Tuple<List<Point>, double, double> valuesArc; //list of points, new values for: lastPositionX, lastPositionY
+            Tuple<List<Point>, double, double, double, double> valuesBezierCurve; //list of points, new values for: lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY
+            var valuesSinglePoint = Parse_M_L(pathElements); //new point, new values for: lastPositionX, lastPositionY
+            currentLine = new List<Point>();
+            currentLine.Add(valuesSinglePoint.Item1);
+            lastPositionX = valuesSinglePoint.Item2;
+            lastPositionY = valuesSinglePoint.Item3;
+            String currentToken;
+            while (!(pathElements.Count == 0))
+            {
+                if (newSubpath)
+                {
+                    initialPositionX = lastPositionX; //update buffers for coordinates of first point of active subpath
+                    initialPositionY = lastPositionY;
+                    newSubpath = false;
+                }
+                currentToken = pathElements.First();
+                if (currentToken.Equals("M"))
+                {
+                    element.Add(new InternalLine(currentLine)); //save current line
+                    valuesSinglePoint = Parse_M_L(pathElements);
+                    currentLine = new List<Point>(); //create new empty line
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("m"))
+                {
+                    element.Add(new InternalLine(currentLine)); //save current line
+                    valuesSinglePoint = Parse_m_l(pathElements, lastPositionX, lastPositionY);
+                    currentLine = new List<Point>(); //create new empty line
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("Z") || currentToken.Equals("z"))
+                {
+                    valuesSinglePoint = Parse_Z(pathElements, initialPositionX, initialPositionY); //method call only used for uniform program structure... only real effect of method is to consume one token
+                    newSubpath = true;
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to old line
+                    element.Add(new InternalLine(currentLine)); //save current line
+                    currentLine = new List<Point>(); //create new empty line
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("L"))
+                {
+                    valuesSinglePoint = Parse_M_L(pathElements);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("l"))
+                {
+                    valuesSinglePoint = Parse_m_l(pathElements, lastPositionX, lastPositionY);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("H"))
+                {
+                    valuesSinglePoint = Parse_H(pathElements, lastPositionY);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("h"))
+                {
+                    valuesSinglePoint = Parse_h(pathElements, lastPositionX, lastPositionY);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("V"))
+                {
+                    valuesSinglePoint = Parse_V(pathElements, lastPositionX);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("v"))
+                {
+                    valuesSinglePoint = Parse_v(pathElements, lastPositionX, lastPositionY);
+                    currentLine.Add(valuesSinglePoint.Item1); //add point to new line
+                    lastPositionX = valuesSinglePoint.Item2; //save last point coordinates
+                    lastPositionY = valuesSinglePoint.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("C"))
+                {
+                    valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("c"))
+                {
+                    valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("S"))
+                {
+                    valuesBezierCurve = Parse_S(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("s"))
+                {
+                    valuesBezierCurve = Parse_s(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("Q"))
+                {
+                    valuesBezierCurve = Parse_Q(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("q"))
+                {
+                    valuesBezierCurve = Parse_q(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("T"))
+                {
+                    valuesBezierCurve = Parse_T(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("t"))
+                {
+                    valuesBezierCurve = Parse_t(pathElements, lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY);
+                    currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
+                    lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
+                    lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
+                    lastBezierControlPointX = valuesBezierCurve.Item4; //save last bezier control point coordinates
+                    lastBezierControlPointY = valuesBezierCurve.Item5; //save last bezier control point coordinates
+                }
+                else if (currentToken.Equals("A"))
+                {
+                    valuesArc = Parse_A(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesArc.Item1); //add points to new line
+                    lastPositionX = valuesArc.Item2; //save last point coordinates
+                    lastPositionY = valuesArc.Item3; //save last point coordinates
+                }
+                else if (currentToken.Equals("a"))
+                {
+                    valuesArc = Parse_a(pathElements, lastPositionX, lastPositionY);
+                    currentLine.AddRange(valuesArc.Item1); //add points to new line
+                    lastPositionX = valuesArc.Item2; //save last point coordinates
+                    lastPositionY = valuesArc.Item3; //save last point coordinates
+                }
+                else
+                {
+                    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);
+                }
+            }
+            if (currentLine.Count > 1)
+            {
+                element.Add(new InternalLine(currentLine)); //save current line
+            }
+            return element;
+        }
+
+        /// <summary>
+        /// normalizes the declaration of the data field of a path declaration by splitting coordinates still connected by a semicolon and command descriptors which are directly attached to the following coordinate into seperate tokens, also repeats omitted command descriptor tokens when the same command is repeated multiple times
+        /// </summary>
+        /// <param name="pathElements">the list of tokens to normalize, by splitting up existing tokens and adding new command descriptor tokens</param>
+        private void NormalizePathDeclaration(List<string> pathElements)
+        {
+            Char lastCommand = 'M';
+            int argumentCounter = 0;
+            for (int j = 0; j < pathElements.Count; j++)
+            {
+                String currentElement = pathElements.ElementAt(j);
+                if (currentElement.Length != 1)
+                {
+                    if (((currentElement.First() >= 'A' && currentElement.First() <= 'Z') || (currentElement.First() >= 'a' && currentElement.First() <= 'z')) && currentElement.First() != 'e') //seperate a single command descriptor / letter
+                    {
+                        pathElements.RemoveAt(j);
+                        pathElements.Insert(j, currentElement.First() + ""); //insert letter as seperate element
+                        pathElements.Insert(j + 1, currentElement.Substring(1)); //insert rest of String at next position so it will be processed again
+                        lastCommand = currentElement.First();
+                        argumentCounter = 0;
+                    }
+                    else if ((currentElement.First() >= '0' && currentElement.First() <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e') //seperate a single coordinate / number
+                    {
+                        bool repeatCommandDescriptor = false;
+                        switch (lastCommand)
+                        { //check for reaching of next command with omitted command descriptor
+                            case 'M':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'm':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'L':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'l':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'V':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'v':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'H':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'h':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'C':
+                                if (argumentCounter >= 6) repeatCommandDescriptor = true;
+                                break;
+                            case 'c':
+                                if (argumentCounter >= 6) repeatCommandDescriptor = true;
+                                break;
+                            case 'S':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 's':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'Q':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'q':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'T':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 't':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'A':
+                                if (argumentCounter >= 7) repeatCommandDescriptor = true;
+                                break;
+                            case 'a':
+                                if (argumentCounter >= 7) repeatCommandDescriptor = true;
+                                break;
+                        }
+                        if (repeatCommandDescriptor)
+                        {
+                            pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
+                            j++; //skip command descriptor (was put into active position in the list
+                            argumentCounter = 0; //reset argument counter
+                        }
+                        bool decimalPointEncountered = false;
+                        for (int k = 1; k < currentElement.Length; k++)
+                        {
+                            if (!decimalPointEncountered && currentElement.ElementAt(k) == '.') //allow up to one decimal point in numbers
+                            {
+                                decimalPointEncountered = true;
+                            }
+                            else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e'))
+                            {
+                                pathElements.RemoveAt(j);
+                                pathElements.Insert(j, currentElement.Substring(0, k - 1)); //insert number as seperate element
+                                pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
+                                break;
+                            }
+                        }
+                        argumentCounter++;
+                    }
+                    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)
+                    {
+                        for (int k = 1; k < currentElement.Length; k++)
+                        {
+                            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'))
+                            {
+                                pathElements.RemoveAt(j);
+                                pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
+                                break;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    if ((currentElement.First() >= 'A' && currentElement.First() <= 'Z') || (currentElement.First() >= 'a' && currentElement.First() <= 'z')) //update lastCommand buffer when reading single letter
+                    {
+                        lastCommand = currentElement.First();
+                        argumentCounter = 0;
+                    }
+                    else if (!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
+                    {
+                        pathElements.RemoveAt(j); //remove element
+                        j--; //decrement index pointer so next element will not be skipped (indices of all folowing elements just decreased by 1)
+                    }
+                    else //a single digit number
+                    {
+                        bool repeatCommandDescriptor = false;
+                        switch (lastCommand)
+                        { //check for reaching of next command with omitted command descriptor
+                            case 'M':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'm':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'L':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'l':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'V':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'v':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'H':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'h':
+                                if (argumentCounter >= 1) repeatCommandDescriptor = true;
+                                break;
+                            case 'C':
+                                if (argumentCounter >= 6) repeatCommandDescriptor = true;
+                                break;
+                            case 'c':
+                                if (argumentCounter >= 6) repeatCommandDescriptor = true;
+                                break;
+                            case 'S':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 's':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'Q':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'q':
+                                if (argumentCounter >= 4) repeatCommandDescriptor = true;
+                                break;
+                            case 'T':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 't':
+                                if (argumentCounter >= 2) repeatCommandDescriptor = true;
+                                break;
+                            case 'A':
+                                if (argumentCounter >= 7) repeatCommandDescriptor = true;
+                                break;
+                            case 'a':
+                                if (argumentCounter >= 7) repeatCommandDescriptor = true;
+                                break;
+                        }
+                        if (repeatCommandDescriptor)
+                        {
+                            pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
+                            j++; //skip command descriptor (was put into active position in the list
+                            argumentCounter = 0; //reset argument counter
+                        }
+                        argumentCounter++;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// parses a "closeloop" path element
+        /// </summary>
+        /// <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>
+        /// <param name="initialPositionX">absolute x coordinate of the last initial point of this subpath</param>
+        /// <param name="initialPositionY">absolute y coordinate of the last initial point of this subpath</param>
+        /// <returns></returns>
+        private Tuple<Point, double, double> Parse_Z(List<string> pathElements, double initialPositionX, double initialPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(initialPositionX, initialPositionY), initialPositionX, initialPositionY);
+        }
+
+        /// <summary>
+        /// parses a "moveto", "close loop" or "lineto" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <returns>the point at the end of the move, close loop or line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_M_L(List<string> pathElements)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, y), x, y);
+        }
+
+        /// <summary>
+        /// parses a "moveto", "close loop" or "lineto" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <returns>the point at the end of the move, close loop or line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_m_l(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            x = lastPositionX + x; //compute absolute x coordinate
+            y = lastPositionY + y; //compute absolute y coordinate
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, y), x, y);
+        }
+
+        /// <summary>
+        /// parses a "horizontal lineto" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <returns>the point at the end of the horizontal line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_H(List<string> pathElements, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, lastPositionY), x, lastPositionY);
+        }
+
+        /// <summary>
+        /// parses a "horizontal lineto" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <returns>the point at the end of the horizontal line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_h(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            x = lastPositionX + x; //compute absolute x coordinate
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(x, lastPositionY), x, lastPositionY);
+        }
+
+        /// <summary>
+        /// parses a "vertical lineto" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <returns>the point at the end of the vertical line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_V(List<string> pathElements, double lastPositionX)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(lastPositionX, y), lastPositionX, y);
+        }
+
+        /// <summary>
+        /// parses a "vertical lineto" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <returns>the point at the end of the vertical line action and its exact, unscaled coordinates</returns>
+        private Tuple<Point, double, double> Parse_v(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse relative y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            y = lastPositionY + y; //compute absolute y coordinate
+            return new Tuple<Point, double, double>(ScaleAndCreatePoint(lastPositionX, y), lastPositionX, y);
+        }
+
+        /// <summary>
+        /// parses a "cubic bezier curve" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_C(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
+        }
+
+        /// <summary>
+        /// parses a "cubic bezier curve" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_c(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse first control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            x1 = lastPositionX + x1; //compute absolute x coordinate
+            y1 = lastPositionY + y1; //compute absolute y coordinate
+            x2 = lastPositionX + x2; //compute absolute x coordinate
+            y2 = lastPositionY + y2; //compute absolute y coordinate
+            x = lastPositionX + x; //compute absolute x coordinate
+            y = lastPositionY + y; //compute absolute y coordinate
+            return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
+        }
+
+        /// <summary>
+        /// parses a "cubic bezier curve shorthand" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_S(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
+            double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
+            return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
+        }
+
+        /// <summary>
+        /// parses a "cubic bezier curve shorthand" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_s(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y2 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse second control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
+            double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
+            x2 = lastPositionX + x2; //compute absolute x coordinate
+            y2 = lastPositionY + y2; //compute absolute y coordinate
+            x = lastPositionX + x; //compute absolute x coordinate
+            y = lastPositionY + y; //compute absolute y coordinate
+            return new Tuple<List<Point>, double, double, double, double>(SampleCubicBezier(lastPositionX, lastPositionY, x1, y1, x2, y2, x, y), x, y, x2, y2);
+        }
+
+        /// <summary>
+        /// parses a "quadratic bezier curve" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_Q(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
+        }
+
+        /// <summary>
+        /// parses a "quadratic bezier curve" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_q(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y1 = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse control point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            x1 = lastPositionX + x1; //compute absolute x coordinate
+            y1 = lastPositionY + y1; //compute absolute y coordinate
+            x = lastPositionX + x; //compute absolute x coordinate
+            y = lastPositionY + y; //compute absolute y coordinate
+            return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
+        }
+
+        /// <summary>
+        /// parses a "quadratic bezier curve shorthand" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_T(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
+            double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
+            return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
+        }
+
+        /// <summary>
+        /// parses a "quadratic bezier curve shorthand" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <param name="lastBezierControlPointX">absolute x coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <param name="lastBezierControlPointY">absolute y coordinate of the last bezier control point of the previous bezier curve</param>
+        /// <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>
+        private Tuple<List<Point>, double, double, double, double> Parse_t(List<string> pathElements, double lastPositionX, double lastPositionY, double lastBezierControlPointX, double lastBezierControlPointY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            x = lastPositionX + x; //compute absolute x coordinate
+            y = lastPositionY + y; //compute absolute y coordinate
+            double x1 = lastPositionX + (lastPositionX - lastBezierControlPointX); //mirror last bezier control point at bezier start point to get first new bezier control point
+            double y1 = lastPositionY + (lastPositionY - lastBezierControlPointY); //mirror last bezier control point at bezier start point to get first new bezier control point
+            return new Tuple<List<Point>, double, double, double, double>(SampleQuadraticBezier(lastPositionX, lastPositionY, x1, y1, x, y), x, y, x1, y1);
+        }
+
+        /// <summary>
+        /// parses a "elliptical arc" path element with absolute coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double> Parse_A(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double rx = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x radius
+            pathElements.RemoveAt(0); //remove x radius token
+            double ry = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y radius
+            pathElements.RemoveAt(0); //remove y radius token
+            double thetha = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse rotation
+            pathElements.RemoveAt(0); //remove rotation token
+            bool largeArcFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse large arc flag
+            pathElements.RemoveAt(0); //remove large arc flag token
+            bool sweepFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse sweep flag
+            pathElements.RemoveAt(0); //remove sweep flag token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            x = x - lastPositionX; //compute relative x coordinate
+            y = y - lastPositionY; //compute relative y coordinate
+            return new Tuple<List<Point>, double, double>(SampleArc(lastPositionX, lastPositionY, rx, ry, x, y, thetha, largeArcFlag, sweepFlag), x, y);
+        }
+
+        /// <summary>
+        /// parses a "elliptical arc" path element with relative coordinates
+        /// </summary>
+        /// <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>
+        /// <param name="lastPositionX">absolute x coordinate of the last active point</param>
+        /// <param name="lastPositionY">absolute y coordinate of the last active point</param>
+        /// <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>
+        private Tuple<List<Point>, double, double> Parse_a(List<string> pathElements, double lastPositionX, double lastPositionY)
+        {
+            pathElements.RemoveAt(0); //remove element descriptor token
+            double rx = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse x radius
+            pathElements.RemoveAt(0); //remove x radius token
+            double ry = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse y radius
+            pathElements.RemoveAt(0); //remove y radius token
+            double thetha = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse rotation
+            pathElements.RemoveAt(0); //remove rotation token
+            bool largeArcFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse large arc flag
+            pathElements.RemoveAt(0); //remove large arc flag token
+            bool sweepFlag = Convert.ToInt16(pathElements.First()) == 1 ? true : false; //parse sweep flag
+            pathElements.RemoveAt(0); //remove sweep flag token
+            double x = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point x coordinate
+            pathElements.RemoveAt(0); //remove x coordinate token
+            double y = Convert.ToDouble(pathElements.First(), CultureInfo.InvariantCulture); //parse target point y coordinate
+            pathElements.RemoveAt(0); //remove y coordinate token
+            return new Tuple<List<Point>, double, double>(SampleArc(lastPositionX, lastPositionY, rx, ry, x, y, thetha, largeArcFlag, sweepFlag), x, y);
+        }
+
+        /// <summary>
+        /// samples an arc of an ellipse into a list of points
+        /// </summary>
+        /// <param name="lastPositionX">x coordinate of last point</param>
+        /// <param name="lastPositionY">y coordinate of last point</param>
+        /// <param name="rx">x radius of the ellipse</param>
+        /// <param name="ry">y radius of the ellipse</param>
+        /// <param name="nextPositionXRelative">x coordinate of next point</param>
+        /// <param name="nextPositionYRelative">y coordinate of next point</param>
+        /// <param name="thetha">rotation of the ellipse around the x axis</param>
+        /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
+        /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
+        /// <returns></returns>
+        private List<Point> SampleArc(double lastPositionX, double lastPositionY, double rx, double ry, double nextPositionXRelative, double nextPositionYRelative, double thetha, bool largeArcFlag, bool sweepFlag)
+        {
+            double cos = Math.Cos(thetha / 180 * Math.PI);
+            double sin = Math.Sin(thetha / 180 * Math.PI);
+            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
+            double targetYTransformed = sin * nextPositionXRelative + cos * nextPositionYRelative;
+            var values = SampleEllipticArcBiasedNoRotation(rx, ry, targetXTransformed, targetYTransformed, largeArcFlag, sweepFlag);
+            List<Point> result = new List<Point>();
+            for (int j = 0; j < values.Item1.Length; j++)
+            {
+                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
+                double yCoordinateRelative = cos * values.Item2[j] - sin * values.Item1[j];
+                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)))
+                double yCoordinateAbsolute = lastPositionY + yCoordinateRelative;
+                result.Add(ScaleAndCreatePoint(xCoordinateAbsolute, yCoordinateAbsolute));
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// samples an elliptical arc with given radii through coordinate origin and endpoint with specified properties
+        /// </summary>
+        /// <param name="rx">x radius</param>
+        /// <param name="ry">y radius</param>
+        /// <param name="targetXTransformed">x coordinate of next point</param>
+        /// <param name="targetYTransformed">y coordinate of next point</param>
+        /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
+        /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
+        /// <returns></returns>
+        private Tuple<double[], double[]> SampleEllipticArcBiasedNoRotation(double rx, double ry, double targetXTransformed, double targetYTransformed, bool largeArcFlag, bool sweepFlag)
+        {
+            double xStretchFactor = rx / ry; //get rx to ry ratio
+            var values = SampleCircleArcBiasedNoRotation(ry, targetXTransformed / xStretchFactor, targetYTransformed, largeArcFlag, sweepFlag); //get a circular arc with radius ry
+            for (int j = 0; j < values.Item1.Length; j++)
+            {
+                values.Item1[j] = values.Item1[j] * xStretchFactor; //correct x coordinates to get an elliptical arc from a circular one
+            }
+            return values;
+        }
+
+        /// <summary>
+        /// samples a circular arc with given radius through coordinate origin and endpoint with specified properties
+        /// </summary>
+        /// <param name="r">radius</param>
+        /// <param name="nextPositionXRelative">x coordinate of next point</param>
+        /// <param name="nextPositionYRelative">y coordinate of next point</param>
+        /// <param name="largeArcFlag">flag determining if the large or the small arc is to be drawn</param>
+        /// <param name="sweepFlag">flag determining in which direction the arc is to be drawn (false = ccw, true = cw)</param>
+        /// <returns></returns>
+        private Tuple<double[], double[]> SampleCircleArcBiasedNoRotation(double r, double nextPositionXRelative, double nextPositionYRelative, bool largeArcFlag, bool sweepFlag)
+        {
+            // code for center computation adapted from https://stackoverflow.com/a/36211852
+            double radsq = r * r;
+            double q = Math.Sqrt(((nextPositionXRelative) * (nextPositionXRelative)) + ((nextPositionYRelative) * (nextPositionYRelative))); //Math.Sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
+            double x3 = (nextPositionXRelative) / 2; //(x1 + x2) / 2;
+            double y3 = (nextPositionYRelative) / 2; //(y1 + y2) / 2;
+            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
+            bool yPlusFlag;
+            if (nextPositionXRelative > 0)
+            {
+                yPlusFlag = true; //left point lies above line
+            }
+            else
+            {
+                yPlusFlag = false; //left point lies below line
+            }
+            if (nextPositionYRelative > 0)
+            {
+                xPlusFlag = false; //left point lies left of line
+            }
+            else
+            {
+                xPlusFlag = true; //left point lies right of line
+            }
+            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) 
+            {
+                xPlusFlag = !xPlusFlag;
+                yPlusFlag = !yPlusFlag;
+            }
+            double xC; // coordinates of center point of circle
+            double yC;
+            if (xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
+            else xC = x3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q);
+            if (yPlusFlag) yC = y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q); //y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((x2-x1) / q);
+            else yC = y3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q);
+            var values = SampleCircleArcBiasedAroundCenter(-xC, -yC, nextPositionXRelative - xC, nextPositionYRelative - yC, r, largeArcFlag, sweepFlag);
+            for (int j = 0; j < values.Item1.Length; j++)
+            {
+                values.Item1[j] = values.Item1[j] + xC; //correct center point coordinate bias
+                values.Item2[j] = values.Item2[j] + yC;
+            }
+            return values;
+        }
+
+        /// <summary>
+        /// samples a circular arc with given radius around the center from the startpoint to the endpoint in the specified direction
+        /// </summary>
+        /// <param name="xStartPoint">x coordinate of the start point</param>
+        /// <param name="yStartPoint">y coordinate of the start point</param>
+        /// <param name="xFinalPoint">x coordinate of the final point</param>
+        /// <param name="yFinalPoint">y coordinate of the final point</param>
+        /// <param name="r">radius</param>
+        /// <param name="clockwise">direction</param>
+        /// <returns></returns>
+        private Tuple<double[], double[]> SampleCircleArcBiasedAroundCenter(double xStartPoint, double yStartPoint, double xFinalPoint, double yFinalPoint, double r, bool largeArcFlag, bool clockwise)
+        {
+            double phiEnd = Math.Atan2(yFinalPoint, xFinalPoint); // angles between points and origin and the positive x Axis
+            double phiStart = Math.Atan2(yStartPoint, xStartPoint);
+            double angle = ((double)2 * Math.PI) / (double)samplingRateEllipse; //compute angle increment (equal to the one used for ellipses)
+            double angleDifference = Math.Abs(phiStart - phiEnd);
+            if (angleDifference > 2 * Math.PI || angleDifference < 0) throw new Exception("angleDifference out of range: " + angleDifference); //TODO remove
+            if (largeArcFlag) // get larger angleDifference
+            {
+                if (angleDifference < Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was smaller angleDifference
+            }
+            else // get smaller angleDifference
+            {
+                if (angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was larger angleDifference
+            }
+            int numberOfPoints = (int)Math.Ceiling(angleDifference / angle);  //compute number of points to sample
+            double[] xValues = new double[numberOfPoints];
+            double[] yValues = new double[numberOfPoints];
+            double phiCurrent = phiStart;
+            for (int j = 0; j < numberOfPoints - 1; j++) //compute intermediate points
+            {
+                if (clockwise) phiCurrent -= angle; //get new angle
+                else phiCurrent += angle;
+                yValues[j] = Math.Sin(phiCurrent) * r; //angles are relative to positive x Axis!
+                xValues[j] = Math.Cos(phiCurrent) * r;
+            }
+            xValues[numberOfPoints - 1] = xFinalPoint; //(last segment always has an angle of less than or exactly 'angle')
+            yValues[numberOfPoints - 1] = yFinalPoint;
+            return new Tuple<double[], double[]>(xValues, yValues);
+        }
+
+        /// <summary>
+        /// samples a cubic bezier curve with a static number of steps (samplingRateBezier)
+        /// </summary>
+        /// <param name="lastPositionX">x coordinate of last point</param>
+        /// <param name="lastPositionY">y coordinate of last point</param>
+        /// <param name="controlPoint1X">x coordinate of control point 1</param>
+        /// <param name="controlPoint1Y">y coordinate of control point 1</param>
+        /// <param name="controlPoint2X">x coordinate of control point 2</param>
+        /// <param name="controlPoint2Y">y coordinate of control point 2</param>
+        /// <param name="nextPositionX">x coordinate of next point</param>
+        /// <param name="nextPositionY">y coordinate of next point</param>
+        /// <returns>a List of Points containing all sampled points</returns>
+        private List<Point> SampleCubicBezier(double lastPositionX, double lastPositionY, double controlPoint1X, double controlPoint1Y, double controlPoint2X, double controlPoint2Y, double nextPositionX, double nextPositionY)
+        {
+            var line1 = CreateDiscreteLine(lastPositionX, lastPositionY, controlPoint1X, controlPoint1Y);
+            var line2 = CreateDiscreteLine(controlPoint1X, controlPoint1Y, controlPoint2X, controlPoint2Y);
+            var line3 = CreateDiscreteLine(controlPoint2X, controlPoint2Y, nextPositionX, nextPositionY);
+            var quadraticBezier1 = ComputeBezierStep(line1.Item1, line1.Item2, line2.Item1, line2.Item2);
+            var quadraticBezier2 = ComputeBezierStep(line2.Item1, line2.Item2, line3.Item1, line3.Item2);
+            var values = ComputeBezierStep(quadraticBezier1.Item1, quadraticBezier1.Item2, quadraticBezier2.Item1, quadraticBezier2.Item2);
+            List<Point> result = new List<Point>();
+            for (int j = 0; j < samplingRateBezier; j++)
+            {
+                result.Add(ScaleAndCreatePoint(values.Item1[j], values.Item2[j]));
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// samples a quadratic bezier curve with a static number of steps (samplingRateBezier)
+        /// </summary>
+        /// <param name="lastPositionX">x coordinate of last point</param>
+        /// <param name="lastPositionY">y coordinate of last point</param>
+        /// <param name="controlPointX">x coordinate of control point</param>
+        /// <param name="controlPointY">y coordinate of control point</param>
+        /// <param name="nextPositionX">x coordinate of next point</param>
+        /// <param name="nextPositionY">y coordinate of next point</param>
+        /// <returns>a List of Points containing all sampled points</returns>
+        private List<Point> SampleQuadraticBezier(double lastPositionX, double lastPositionY, double controlPointX, double controlPointY, double nextPositionX, double nextPositionY)
+        {
+            var line1 = CreateDiscreteLine(lastPositionX, lastPositionY, controlPointX, controlPointY);
+            var line2 = CreateDiscreteLine(controlPointX, controlPointY, nextPositionX, nextPositionY);
+            var values = ComputeBezierStep(line1.Item1, line1.Item2, line2.Item1, line2.Item2);
+            List<Point> result = new List<Point>();
+            for (int j = 0; j < samplingRateBezier; j++)
+            {
+                result.Add(ScaleAndCreatePoint(values.Item1[j], values.Item2[j]));
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// create a discrete line with [samplingRateBezier] points (including start and end point) between two points
+        /// </summary>
+        /// <param name="point1X">coordinate of point 1</param>
+        /// <param name="point1Y">y coordinate of point 1</param>
+        /// <param name="point2X">x coordinate of point 2</param>
+        /// <param name="point2Y">y coordinate of point 2</param>
+        /// <returns>the discrete line as arrays of x and y coordinates</returns>
+        private Tuple<double[], double[]> CreateDiscreteLine(double point1X, double point1Y, double point2X, double point2Y)
+        {
+            double[] resultX = new double[samplingRateBezier];
+            double[] resultY = new double[samplingRateBezier];
+            for (int j = 0; j < samplingRateBezier; j++)
+            {
+                var pointResult = LinearInterpolationForBezier(point1X, point1Y, point2X, point2Y, j);
+                resultX[j] = pointResult.Item1;
+                resultY[j] = pointResult.Item2;
+            }
+            return new Tuple<double[], double[]>(resultX, resultY);
+        }
+
+        /// <summary>
+        /// computes the discrete bezier curve between two given dicrete lines/curves
+        /// </summary>
+        /// <param name="line1X">x coordinates of all points in line 1</param>
+        /// <param name="line1Y">y coordinates of all points in line 1</param>
+        /// <param name="line2X">x coordinates of all points in line 2</param>
+        /// <param name="line2Y">y coordinates of all points in line 2</param>
+        /// <returns>the discrete bezier curve</returns>
+        private Tuple<double[], double[]> ComputeBezierStep(double[] line1X, double[] line1Y, double[] line2X, double[] line2Y)
+        {
+            double[] resultX = new double[samplingRateBezier];
+            double[] resultY = new double[samplingRateBezier];
+            for (int j = 0; j < samplingRateBezier; j++)
+            {
+                var pointResult = LinearInterpolationForBezier(line1X[j], line1Y[j], line2X[j], line2Y[j], j);
+                resultX[j] = pointResult.Item1;
+                resultY[j] = pointResult.Item2;
+            }
+            return new Tuple<double[], double[]>(resultX, resultY);
+        }
+
+        /// <summary>
+        /// creates the linearly interpolated point at j/(samplingRateBezier - 1) between point 1 and point 2
+        /// </summary>
+        /// <param name="point1X">x coordinate of point 1</param>
+        /// <param name="point1Y">y coordinate of point 1</param>
+        /// <param name="point2X">x coordinate of point 2</param>
+        /// <param name="point2Y">y coordinate of point 2</param>
+        /// <param name="j">number of point to be interpolated, at a total number of [samplingRateBezier] points</param>
+        /// <returns>the linearly interpolated point</returns>
+        private Tuple<double, double> LinearInterpolationForBezier(double point1X, double point1Y, double point2X, double point2Y, int j)
+        {
+            double factor = ((double)1 / (double)(samplingRateBezier - 1)) * (double)j; //factor for linear interpolation
+            double x = point1X + ((point2X - point1X) * factor);
+            double y = point1Y + ((point2Y - point1Y) * factor);
+            return new Tuple<double, double>(x, y);
+        }
+
+        /// <summary>
+        /// parses a hierarchical svg element and all its sub-elements
+        /// </summary>
+        /// <param name="currentElement">the definition of the top level element as whitespace seperated String[]</param>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
+        private List<InternalLine> ParseMultiLineSVGElement(string[] currentElement, string[] allLines)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <summary>
+        /// removes the name of the attribute aswell as the '="' at the beginning and the '"' or '">' at the end of an attribute definition
+        /// </summary>
+        /// <param name="definition">the definition from the svg file</param>
+        /// <returns>the value of the attribute, as String (the part of definition contained between '"'s)</returns>
+        private String ParseSingleSVGAttribute(String definition)
+        {
+            return definition.Split('"')[1];
+        }
+
+        /// <summary>
+        /// 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
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the definition of the current svg element, as String[] split by whitespaces</returns>
+        private String[] GetCurrentElement(String[] allLines)
+        {
+            List<String> currentElementTemp = allLines[i].Split(whitespaces).ToList();
+            while (!currentElementTemp.Last().EndsWith(">"))
+            {
+                i++;
+                currentElementTemp.AddRange(allLines[i].Split(whitespaces).ToList());
+            }
+            return currentElementTemp.ToArray();
+        }
+
+        /// <summary>
+        /// applies the scale factor to the coordinates and creates a new Point
+        /// </summary>
+        /// <param name="x">unscaled x coordinate</param>
+        /// <param name="y">unscaled y coordinate</param>
+        /// <returns>new Point with scaled coordinates</returns>
+        private Point ScaleAndCreatePoint(double x, double y)
+        {
+            return new Point((int)Math.Round(x * scale), (int)Math.Round(y * scale));
+        }
+
+        /// <summary>
+        /// creates a representation of an ellipse as a List of Points by sampling the outline of the ellipse
+        /// </summary>
+        /// <param name="x">x coordinate of the center of the ellipse</param>
+        /// <param name="y">y coordinate of the center of the ellipse</param>
+        /// <param name="rx">x radius of the ellipse</param>
+        /// <param name="ry">y radius of the ellipse</param>
+        /// <returns>the parsed element as a List of Points</returns>
+        private List<Point> SampleEllipse(double x, double y, double rx, double ry)
+        {
+            List<Point> ellipse = new List<Point>();
+            double angle = ((double)2 * Math.PI) / (double)samplingRateEllipse;
+            double yScale = ry / rx;
+            double[] xValues = new double[samplingRateEllipse / 4];
+            double[] yValues = new double[samplingRateEllipse / 4];
+            for (int j = 0; j < samplingRateEllipse / 4; j++) //compute offset values of points for one quadrant
+            {
+                xValues[j] = Math.Sin((double)j * angle) * rx;
+                yValues[j] = Math.Cos((double)j * angle) * rx;
+            }
+            for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for first quadrant
+            {
+                int xCoord = Convert.ToInt32(Math.Round(x + xValues[j]));
+                int yCoord = Convert.ToInt32(Math.Round(y - yValues[j] * yScale));
+                ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
+            }
+            for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for second quadrant
+            {
+                int xCoord = Convert.ToInt32(Math.Round(x + yValues[j]));
+                int yCoord = Convert.ToInt32(Math.Round(y + xValues[j] * yScale));
+                ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
+            }
+            for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for third quadrant
+            {
+                int xCoord = Convert.ToInt32(Math.Round(x - xValues[j]));
+                int yCoord = Convert.ToInt32(Math.Round(y + yValues[j] * yScale));
+                ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
+            }
+            for (int j = 0; j < samplingRateEllipse / 4; j++) //create actual points for fourth quadrant
+            {
+                int xCoord = Convert.ToInt32(Math.Round(x - yValues[j]));
+                int yCoord = Convert.ToInt32(Math.Round(y - xValues[j] * yScale));
+                ellipse.Add(ScaleAndCreatePoint(xCoord, yCoord));
+            }
+            ellipse.Add(ScaleAndCreatePoint(Convert.ToInt32(Math.Round(x + 0)), Convert.ToInt32(Math.Round(y - rx * yScale)))); //close ellipse
+            return ellipse;
+        }
     }
-}
+}

+ 2 - 2
SketchAssistant/SketchAssistantWPF/Frame.cs

@@ -1,4 +1,4 @@
-namespace OptiTrack
+namespace OptiTrack
 {
     public class Frame
     {
@@ -36,4 +36,4 @@
             Z = z;
         }
     }
-}
+}

+ 0 - 48
SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs

@@ -12,54 +12,6 @@ namespace SketchAssistantWPF
     /// </summary>
     public static class GeometryCalculator
     {
-        /// <summary>
-        /// An implementation of the Bresenham Line Algorithm,
-        /// which calculates the points of a circle in a radius around a center point.
-        /// Implemented with the help of code examples on Wikipedia.
-        /// </summary>
-        /// <param name="center">The center of the circle.</param>
-        /// <param name="radius">The radius of the circle, 
-        /// when it is zero or less, only the midpoint is returned</param>
-        /// <returns>The HashSet containing all Points on the circle.</returns>
-        public static HashSet<Point> BresenhamCircleAlgorithm(Point center, int radius)
-        {
-            if (radius <= 0) { return new HashSet<Point> { center }; }
-
-            int x = radius - 1;
-            int y = 0;
-            int dx = 1;
-            int dy = 1;
-            int err = dx - (radius * 2);
-            HashSet<Point> returnSet = new HashSet<Point>();
-
-            while (x >= y)
-            {
-                returnSet.Add(new Point(center.X + x, center.Y + y));
-                returnSet.Add(new Point(center.X + y, center.Y + x));
-                returnSet.Add(new Point(center.X - y, center.Y + x));
-                returnSet.Add(new Point(center.X - x, center.Y + y));
-                returnSet.Add(new Point(center.X - x, center.Y - y));
-                returnSet.Add(new Point(center.X - y, center.Y - x));
-                returnSet.Add(new Point(center.X + y, center.Y - x));
-                returnSet.Add(new Point(center.X + x, center.Y - y));
-
-                if (err <= 0)
-                {
-                    y++;
-                    err += dy;
-                    dy += 2;
-                }
-
-                if (err > 0)
-                {
-                    x--;
-                    dx += 2;
-                    err += dx - (radius * 2);
-                }
-            }
-            return returnSet;
-        }
-
         /// <summary>
         /// A simple algorithm that returns a filled circle with a radius and a center point.
         /// </summary>

+ 11 - 16
SketchAssistant/SketchAssistantWPF/InternalLine.cs

@@ -101,12 +101,12 @@ namespace SketchAssistantWPF
                     if (currPoint.X >= 0 && currPoint.Y >= 0 &&
                         currPoint.X < boolMatrix.GetLength(0) && currPoint.Y < boolMatrix.GetLength(1))
                     {
-                        boolMatrix[(int) currPoint.X, (int) currPoint.Y] = true;
-                        if (listMatrix[(int) currPoint.X, (int) currPoint.Y] == null)
+                        boolMatrix[(int)currPoint.X, (int)currPoint.Y] = true;
+                        if (listMatrix[(int)currPoint.X, (int)currPoint.Y] == null)
                         {
-                            listMatrix[(int) currPoint.X, (int) currPoint.Y] = new HashSet<int>();
+                            listMatrix[(int)currPoint.X, (int)currPoint.Y] = new HashSet<int>();
                         }
-                        listMatrix[(int) currPoint.X, (int) currPoint.Y].Add(identifier);
+                        listMatrix[(int)currPoint.X, (int)currPoint.Y].Add(identifier);
                     }
                 }
             }
@@ -117,26 +117,21 @@ namespace SketchAssistantWPF
         /// </summary>
         private void CleanPoints()
         {
-            if (linePoints.Count > 1)
+            if (linePoints.Any())
             {
-                //if this is a point or not
-                var localIsPoint = true;
                 //check if its a point
-                foreach(Point p in linePoints)
+                var localIsPoint = linePoints.All(o => o.X == linePoints.First().X && o.Y == linePoints.First().Y);
+                if (!localIsPoint)
                 {
-                    if (p.X != linePoints[0].X || p.Y != linePoints[0].Y)
-                        localIsPoint = false;
-                }
-                if (!localIsPoint) {
                     List<Point> newList = new List<Point>();
                     List<Point> tempList = new List<Point>();
                     //Since Point is non-nullable, we must ensure the nullPoints, 
                     //which we remove can not possibly be points of the original given line.
-                    int nullValue = (int) linePoints[0].X + 1;
+                    int nullValue = (int)linePoints[0].X + 1;
                     //Fill the gaps between points
                     for (int i = 0; i < linePoints.Count - 1; i++)
                     {
-                        nullValue += (int) linePoints[i + 1].X;
+                        nullValue += (int)linePoints[i + 1].X;
                         List<Point> partialList = GeometryCalculator.BresenhamLineAlgorithm(linePoints[i], linePoints[i + 1]);
                         tempList.AddRange(partialList);
                     }
@@ -162,8 +157,8 @@ namespace SketchAssistantWPF
                 else
                 {
                     isPoint = true;
-                    point = linePoints[0];
-                    linePoints = new List<Point>();
+                    point = linePoints.First();
+                    linePoints.Clear();
                     linePoints.Add(point);
                 }
             }

+ 245 - 43
SketchAssistant/SketchAssistantWPF/MVP_Model.cs

@@ -8,6 +8,8 @@ using System.Windows.Controls;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using OptiTrack;
+using System.Runtime.InteropServices;
+using System.IO;
 
 namespace SketchAssistantWPF
 {
@@ -21,12 +23,8 @@ namespace SketchAssistantWPF
         /// History of Actions
         /// </summary>
         ActionHistory historyOfActions;
-		/// <summary>
-		/// The assistant responsible for the redraw mode
-		/// </summary>
-		//RedrawAssistant redrawAss;
-		OptiTrackConnector connector;
-		OptiTrackConnector.OnFrameReady frameReady;
+
+        OptiTrackConnector connector;
 
         /***********************/
         /*** CLASS VARIABLES ***/
@@ -37,6 +35,10 @@ namespace SketchAssistantWPF
         /// </summary>
         bool inDrawingMode;
         /// <summary>
+        /// if the program is using OptiTrack
+        /// </summary>
+        public bool optiTrackInUse { get; private set; }
+        /// <summary>
         /// Size of deletion area
         /// </summary>
         int deletionRadius = 5;
@@ -91,22 +93,57 @@ namespace SketchAssistantWPF
         /// <summary>
         /// Indicates whether or not the canvas on the right side is active.
         /// </summary>
-        public bool canvasActive {get; set;}
+        public bool canvasActive { get; set; }
         /// <summary>
         /// Indicates if there is a graphic loaded in the left canvas.
         /// </summary>
         public bool graphicLoaded { get; set; }
+        /// <summary>
+        /// Whether or not an optitrack system is avaiable.
+        /// </summary>
+        public bool optitrackAvailable { get; private set; }
+        /// <summary>
+        /// x koordinate in real world. one unit is one meter. If standing in front of video wall facing it, moving left results in incrementation of x.
+        /// </summary>
+        public float optiTrackX;
+        /// <summary>
+        /// y koordinate in real world. one unit is one meter. If standing in front of video wall, moving up results in incrementation of y.
+        /// </summary>
+        public float optiTrackY;
+        /// <summary>
+        /// z koordinate in real world. one unit is one meter. If standing in front of video wall, moving back results in incrementation of y.
+        /// </summary>
+        public float optiTrackZ;
+        /// <summary>
+        /// keeps track of whether last tick the trackable was inside drawing zone or not.
+        /// </summary>
+        private bool optiTrackInsideDrawingZone = false;
+        /// <summary>
+        /// this is a variable used for detecting whether the tracker is in the warning zone (0 +- variable), no drawing zone (0 +- 2 * variable) or normal drawing zone
+        /// </summary>
+        private double WARNING_ZONE_BOUNDARY = 0.10; //10cm
+        /// <summary>
+        /// object of class Armband used for controlling the vibrotactil armband
+        /// </summary>
+        private Armband armband;
+        /// <summary>
+        /// deactivates all Console.WriteLine() when set to false
+        /// </summary>
+        bool testing = false;//TODO: remove after finishing userstory
+
 
         Image rightImageWithoutOverlay;
+        /// <summary>
+        /// Whether or not the mouse is pressed.
+        /// </summary>
+        private bool mouseDown;
 
         List<InternalLine> leftLineList;
-        
+
         List<Tuple<bool, InternalLine>> rightLineList;
 
         List<Point> currentLine = new List<Point>();
 
-
-
         public MVP_Model(MVP_Presenter presenter)
         {
             programPresenter = presenter;
@@ -118,9 +155,35 @@ namespace SketchAssistantWPF
             UpdateUI();
             rightImageSize = new ImageDimension(0, 0);
             leftImageSize = new ImageDimension(0, 0);
-			connector = new OptiTrackConnector();
+            connector = new OptiTrackConnector();
+            armband = new Armband();
+
+            optitrackAvailable = false;
+            if (File.Exists(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+            {
+                if (connector.Init(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+                {
+                    optitrackAvailable = true;
+                    connector.StartTracking(getOptiTrackPosition);
+                }
+            }
+        }
+
+        [DllImport("user32.dll", EntryPoint = "SetCursorPos")]
+        private static extern bool SetCursorPos(int X, int Y);
+
+        //[DllImport("user32.dll")]
+        //public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
+
+        public enum MouseEventType : int
+        {
+            LeftDown = 0x02,
+            LeftUp = 0x04,
+            RightDown = 0x08,
+            RightUp = 0x10
         }
 
+
         /**************************/
         /*** INTERNAL FUNCTIONS ***/
         /**************************/
@@ -166,7 +229,7 @@ namespace SketchAssistantWPF
         /// </summary>
         private void UpdateUI()
         {
-            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded);
+            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded, optitrackAvailable, optiTrackInUse);
         }
 
         /// <summary>
@@ -193,6 +256,51 @@ namespace SketchAssistantWPF
             return returnSet;
         }
 
+        //TODO: calibrate
+        double OPTITRACK_X_OFFSET = 0.7878;
+        double OPTITRACK_Y_OFFSET = 0.7977;
+        double OPTITRACK_CANVAS_HEIGHT = 1.29;
+        double OPTITRACK_X_SCALE = -0.254 * (((1.816/*size of canvis*/ / 0.0254) * 96) / (1.816));
+        double OPTITRACK_Y_SCALE = 0.254 * (((1.360 / 0.0254) * 96) / (1.360));
+        /// <summary>
+        /// converts given point to device-independent pixel
+        /// </summary>
+        /// <param name="p">real world coordinate</param>
+        /// <returns>The given Point converted to device-independent pixel </returns>
+        private Point ConvertTo96thsOfInch(Point p)
+        {
+            double xCoordinate = (p.X - OPTITRACK_X_OFFSET) * OPTITRACK_X_SCALE;
+            double yCoordinate = (OPTITRACK_CANVAS_HEIGHT - (p.Y - OPTITRACK_Y_OFFSET)) * OPTITRACK_Y_SCALE;
+            return new Point(xCoordinate, yCoordinate);
+        }
+
+        /// <summary>
+        /// converts given point to pixel, calibrated for video wall in a08
+        /// </summary>
+        /// <param name="p"></param>
+        /// <returns></returns>
+        private Point ConvertToPixel(Point p)
+        {//TODO: realize comments
+            double xCoordinate = (p.X - OPTITRACK_X_OFFSET) * -1 * (/*Anzahl Pixel X-Richtung*/0 / (1.816)) + 0/*1/2 * x-richtung pixel*/; //TODO
+            double yCoordinate = (((OPTITRACK_CANVAS_HEIGHT + 0/*meter von oberer Rand Leinwand zu oberer Rand Bildschirm*/) - (p.Y - OPTITRACK_Y_OFFSET))) * (/*Anzahl Pixel Y-Richtung*/0 / (1.360));
+            return new Point(xCoordinate, yCoordinate);
+        }
+
+        /// <summary>
+        /// Updates the current cursor position of the model.
+        /// </summary>
+        /// <param name="p">The new cursor position</param>
+        public void SetCurrentFingerPosition(Point p)
+        {
+            Point correctedPoint = ConvertTo96thsOfInch(p);
+            if (testing)
+            {
+                Console.WriteLine("raw coordinates: " + p.X + ";" + p.Y);
+                Console.WriteLine(correctedPoint.X + "," + correctedPoint.Y);
+            }
+            currentCursorPosition = correctedPoint;
+        }
+
         /********************************************/
         /*** FUNCTIONS TO INTERACT WITH PRESENTER ***/
         /********************************************/
@@ -204,9 +312,9 @@ namespace SketchAssistantWPF
         /// <param name="RightCanvas">The size of the right canvas.</param>
         public void ResizeEvent(ImageDimension LeftCanvas, ImageDimension RightCanvas)
         {
-            if(LeftCanvas.Height >= 0 && LeftCanvas.Width>= 0) { leftImageSize = LeftCanvas; }
-            if(RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
-          
+            if (LeftCanvas.Height >= 0 && LeftCanvas.Width >= 0) { leftImageSize = LeftCanvas; }
+            if (RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
+
             RepopulateDeletionMatrixes();
         }
 
@@ -332,73 +440,153 @@ namespace SketchAssistantWPF
             inDrawingMode = nowDrawing;
             UpdateUI();
         }
-        
+
+        /// <summary>
+        /// The function called by the Presenter to set a variable which describes if OptiTrack is in use
+        /// </summary>
+        /// <param name="usingOptiTrack"></param>
+        public void SetOptiTrack(bool usingOptiTrack)
+        {
+            optiTrackInUse = usingOptiTrack;
+            if (usingOptiTrack && optiTrackX == 0 && optiTrackY == 0 && optiTrackZ == 0)
+            {
+                programPresenter.PassMessageToView("Trackable not detected, please check if OptiTrack is activated and Trackable is recognized", "Warning");
+            }
+        }
+
         /// <summary>
         /// Updates the current cursor position of the model.
         /// </summary>
         /// <param name="p">The new cursor position</param>
         public void SetCurrentCursorPosition(Point p)
         {
-            currentCursorPosition = p;
+            if (!optiTrackInUse) currentCursorPosition = p;
+            mouseDown = programPresenter.IsMousePressed();
         }
 
         /// <summary>
         /// Start a new Line, when the Mouse is pressed down.
         /// </summary>
-        public void MouseDown()
+        public void StartNewLine()
         {
-            if (inDrawingMode)
+            if (optiTrackInUse || programPresenter.IsMousePressed())
             {
-                currentLine.Clear();
-                currentLine.Add(currentCursorPosition);
+                if (inDrawingMode)
+                {
+                    currentLine.Clear();
+                    currentLine.Add(currentCursorPosition);
+                }
             }
         }
 
         /// <summary>
         /// Finish the current Line, when the pressed Mouse is released.
         /// </summary>
-        public void MouseUp()
+        /// <param name="valid">Whether the up event is valid or not</param>
+        public void FinishCurrentLine(bool valid)
         {
-            if (inDrawingMode && currentLine.Count > 0)
+            foreach (Point p in currentLine)
+            {
+                if (testing)
+                {
+                    Console.WriteLine(p.X + ";" + p.Y);
+                }
+            }
+            if (valid)
+            {
+                if (inDrawingMode && currentLine.Count > 0)
+                {
+                    InternalLine newLine = new InternalLine(currentLine, rightLineList.Count);
+                    rightLineList.Add(new Tuple<bool, InternalLine>(true, newLine));
+                    newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
+                    programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
+                    //TODO: For the person implementing overlay: Add check if overlay needs to be added
+                    programPresenter.UpdateRightLines(rightLineList);
+                    currentLine.Clear();
+                    programPresenter.UpdateCurrentLine(currentLine);
+                }
+            }
+            else
             {
-                InternalLine newLine = new InternalLine(currentLine, rightLineList.Count);
-                rightLineList.Add(new Tuple<bool, InternalLine>(true, newLine));
-                newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
-                programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
-                //TODO: For the person implementing overlay: Add check if overlay needs to be added
-                programPresenter.UpdateRightLines(rightLineList);
                 currentLine.Clear();
-                programPresenter.UpdateCurrentLine(currentLine);
             }
             UpdateUI();
         }
 
+
+        void getOptiTrackPosition(OptiTrack.Frame frame)
+        {
+            if(frame.Trackables.Length > 1)
+            {
+                optiTrackX = frame.Trackables[0].X;
+                optiTrackY = frame.Trackables[0].Y;
+                optiTrackZ = frame.Trackables[0].Z;
+            }
+        }
+
         /// <summary>
         /// Method to be called every tick. Updates the current Line, or checks for Lines to delete, depending on the drawing mode.
         /// </summary>
         public void Tick()
         {
-			if(inDrawingMode)
-			{
-				if (connector.Init(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
-				{
-					connector.StartTracking(programPresenter.PassOptiTrackMessage);
-				}
-			}
-			
             if (cursorPositions.Count > 0) { previousCursorPosition = cursorPositions.Dequeue(); }
             else { previousCursorPosition = currentCursorPosition; }
-            cursorPositions.Enqueue(currentCursorPosition);
-            //Drawing
-            if (inDrawingMode && programPresenter.IsMousePressed())
+            if (optiTrackInUse) //drawing optiTrack
             {
-                currentLine.Add(currentCursorPosition);
-                programPresenter.UpdateCurrentLine(currentLine);
+                if (CheckInsideDrawingZone(optiTrackZ))
+                {
+                    SetCurrentFingerPosition(new Point(optiTrackX, optiTrackY));
+                    currentLine.Add(currentCursorPosition);
+                    programPresenter.UpdateCurrentLine(currentLine);
+                    if (!optiTrackInsideDrawingZone)
+                    {
+                        optiTrackInsideDrawingZone = true;
+                        StartNewLine();
+                        if (testing)
+                        {
+                            Console.WriteLine("new line begun");
+                        }
+                    }
+                    if (optiTrackZ > WARNING_ZONE_BOUNDARY)
+                    {
+                        armband.pushForward();
+                    }
+                    else if (optiTrackZ < -1 * WARNING_ZONE_BOUNDARY)
+                    {
+                        armband.pushBackward();
+                    }
+                }
+                else
+                {
+                    if (optiTrackInsideDrawingZone) //trackable was in drawing zone last tick -> finish line
+                    {
+                        optiTrackInsideDrawingZone = false;
+                        FinishCurrentLine(true);
+                        if (testing)
+                        {
+                            Console.WriteLine("line finished");
+                        }
+                    }
+                }
+                //if (optitrackAvailable) { TODO test and remove
+                projectPointOntoScreen(optiTrackX, optiTrackY);
+                //}
+                cursorPositions.Enqueue(currentCursorPosition);
+            }
+            else //drawing normal
+            {
+                cursorPositions.Enqueue(currentCursorPosition);
+                if (inDrawingMode && programPresenter.IsMousePressed())
+                {
+                    currentLine.Add(currentCursorPosition);
+                    //programPresenter.UpdateCurrentLine(currentLine);
+                }
             }
             //Deleting
             if (!inDrawingMode && programPresenter.IsMousePressed())
             {
                 List<Point> uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+
                 foreach (Point currPoint in uncheckedPoints)
                 {
                     HashSet<int> linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionRadius);
@@ -417,6 +605,19 @@ namespace SketchAssistantWPF
             }
         }
 
+        private void projectPointOntoScreen(float optiTrackX, float optiTrackY)
+        {
+            Point auxiliaryPoint = ConvertToPixel(new Point(optiTrackX, optiTrackY));
+            SetCursorPos((int)auxiliaryPoint.X, (int)auxiliaryPoint.Y);
+        }
+
+        private bool CheckInsideDrawingZone(float optiTrackZ)
+        {
+            if (Math.Abs(optiTrackZ) > WARNING_ZONE_BOUNDARY * 2) return false;
+            return true;
+        }
+
+        /*
         /// <summary>
         /// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
         /// </summary>
@@ -447,6 +648,7 @@ namespace SketchAssistantWPF
                 deletionRadius = (int)(5 * zoomFactor);
             }
         }
+        */
 
         /// <summary>
         /// If there is unsaved progress.

+ 149 - 100
SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs

@@ -27,7 +27,7 @@ namespace SketchAssistantWPF
         /// </summary>
         Dictionary<int, Shape> rightPolyLines;
 
-        ImageDimension CanvasSizeLeft = new ImageDimension(0,0);
+        ImageDimension CanvasSizeLeft = new ImageDimension(0, 0);
 
         ImageDimension CanvasSizeRight = new ImageDimension(0, 0);
 
@@ -44,6 +44,7 @@ namespace SketchAssistantWPF
             Click,
             Down,
             Up,
+            Up_Invalid,
             Move
         }
 
@@ -78,7 +79,7 @@ namespace SketchAssistantWPF
         {
             CanvasSizeLeft.ChangeDimension(leftPBS.Item1, leftPBS.Item2);
             CanvasSizeRight.ChangeDimension(rightPBS.Item1, rightPBS.Item2);
-            programModel.UpdateSizes(CanvasSizeRight);
+            //programModel.UpdateSizes(CanvasSizeRight);
             programModel.ResizeEvent(CanvasSizeLeft, CanvasSizeRight);
         }
 
@@ -98,14 +99,63 @@ namespace SketchAssistantWPF
                 if (!fileNameTup.Item1.Equals("") && !fileNameTup.Item2.Equals(""))
                 {
                     programView.SetToolStripLoadStatus(fileNameTup.Item2);
-                    Tuple<int, int, List<InternalLine>> values = fileImporter.ParseISADInputFile(fileNameTup.Item1);
-                    programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
-
-                    programModel.ResetRightImage();
-                    programModel.CanvasActivated();
-                    programModel.ChangeState(true);
-                    programView.EnableTimer();
-                    ClearRightLines();
+                    try
+                    {
+                        Tuple<int, int, List<InternalLine>> values = fileImporter.ParseISADInputFile(fileNameTup.Item1);
+                        programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
+
+                        programModel.ResetRightImage();
+                        programModel.CanvasActivated();
+                        programModel.ChangeState(true);
+                        programView.EnableTimer();
+                        ClearRightLines();
+                    }
+                    catch (FileImporterException ex)
+                    {
+                        programView.ShowInfoMessage(ex.ToString(),"");
+                    }
+                    catch (Exception ex)
+                    {
+                        programView.ShowInfoMessage("exception occured while trying to load interactive sketch-assistant drawing file:\n\n" + ex.ToString() + "\n\n" + ex.StackTrace, "");
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Display a new FileDialog to a svg drawing.
+        /// </summary>
+        public void SVGToolStripMenuItemClick()
+        {
+            var okToContinue = true;
+            if (programModel.HasUnsavedProgress())
+            {
+                okToContinue = programView.ShowWarning("You have unsaved progress. Continue?");
+            }
+            if (okToContinue)
+            {
+                var fileNameTup = programView.openNewDialog("Scalable Vector Graphics|*.svg");
+                if (!fileNameTup.Item1.Equals("") && !fileNameTup.Item2.Equals(""))
+                {
+                    programView.SetToolStripLoadStatus(fileNameTup.Item2);
+                    try
+                    {
+                        Tuple<int, int, List<InternalLine>> values = fileImporter.ParseSVGInputFile(fileNameTup.Item1, programModel.leftImageBoxWidth, programModel.leftImageBoxHeight);
+                        programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
+                        programModel.ResetRightImage();
+                        programModel.CanvasActivated();
+                        programModel.ChangeState(true);
+                        programView.EnableTimer();
+                        ClearRightLines();
+                    }
+                    catch (FileImporterException ex)
+                    {
+                        programView.ShowInfoMessage(ex.ToString(), "");
+                    }
+                    catch (Exception ex)
+                    {
+                        programView.ShowInfoMessage("exception occured while trying to parse svg file:\n\n" + ex.ToString() + "\n\n" + ex.StackTrace, "");
+                    }
                 }
             }
         }
@@ -119,6 +169,17 @@ namespace SketchAssistantWPF
             programModel.ChangeState(NowDrawing);
         }
 
+        /// <summary>
+        /// Pass-through function to change the OptiTrack-in-use state of the model
+        /// </summary>
+        public void ChangeOptiTrack(bool usingOptiTrack)
+        {
+            if (programModel.optitrackAvailable)
+            {
+                programModel.SetOptiTrack(usingOptiTrack);
+            }
+        }
+
         /// <summary>
         /// Pass-trough function to undo an action.
         /// </summary>
@@ -168,7 +229,7 @@ namespace SketchAssistantWPF
         /// Pass-trough when the mouse is moved.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
+        /// <param name="position">The position of the mouse.</param>
         public void MouseEvent(MouseAction mouseAction, Point position)
         {
             switch (mouseAction)
@@ -185,24 +246,29 @@ namespace SketchAssistantWPF
         /// Pass-trough function that calls the correct Mouse event of the model, when the mouse is clicked.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
         public void MouseEvent(MouseAction mouseAction)
         {
-            switch (mouseAction)
+            if (!programModel.optiTrackInUse)
             {
-                case MouseAction.Click:
-                    programModel.MouseDown();
-                    programModel.Tick();
-                    programModel.MouseUp();
-                    break;
-                case MouseAction.Down:
-                    programModel.MouseDown();
-                    break;
-                case MouseAction.Up:
-                    programModel.MouseUp();
-                    break;
-                default:
-                    break;
+                switch (mouseAction)
+                {
+                    case MouseAction.Click:
+                        programModel.StartNewLine();
+                        programModel.Tick();
+                        programModel.FinishCurrentLine(true);
+                        break;
+                    case MouseAction.Down:
+                        programModel.StartNewLine();
+                        break;
+                    case MouseAction.Up:
+                        programModel.FinishCurrentLine(true);
+                        break;
+                    case MouseAction.Up_Invalid:
+                        programModel.FinishCurrentLine(false);
+                        break;
+                    default:
+                        break;
+                }
             }
         }
 
@@ -245,7 +311,7 @@ namespace SketchAssistantWPF
         /// </summary>
         public void UpdateRightLines(List<Tuple<bool, InternalLine>> lines)
         {
-            foreach(Tuple<bool, InternalLine> tup in lines)
+            foreach (Tuple<bool, InternalLine> tup in lines)
             {
                 var status = tup.Item1;
                 var line = tup.Item2;
@@ -261,10 +327,8 @@ namespace SketchAssistantWPF
                     else
                     {
                         Ellipse newPoint = new Ellipse();
-                        newPoint.SetValue(Canvas.LeftProperty, line.point.X);
-                        newPoint.SetValue(Canvas.TopProperty, line.point.Y);
                         rightPolyLines.Add(line.GetID(), newPoint);
-                        programView.AddNewPointRight(newPoint);
+                        programView.AddNewPointRight(newPoint, line);
                     }
                 }
                 SetVisibility(rightPolyLines[line.GetID()], status);
@@ -297,23 +361,49 @@ namespace SketchAssistantWPF
         /// <param name="canRedo">If actions in the model can be redone</param>
         /// <param name="canvasActive">If the right canvas is active</param>
         /// <param name="graphicLoaded">If an image is loaded in the model</param>
-        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive, bool graphicLoaded)
+        /// <param name="optiTrackAvailable">If there is an optitrack system available</param>
+        /// <param name="optiTrackInUse">If the optitrack system is active</param>
+        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive,
+                bool graphicLoaded, bool optiTrackAvailable, bool optiTrackInUse)
         {
             Dictionary<String, MainWindow.ButtonState> dict = new Dictionary<String, MainWindow.ButtonState> {
                 {"canvasButton", MainWindow.ButtonState.Enabled }, {"drawButton", MainWindow.ButtonState.Disabled}, {"deleteButton",MainWindow.ButtonState.Disabled },
-                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}};
+                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}, {"drawWithOptiButton", MainWindow.ButtonState.Disabled}};
 
             if (canvasActive)
             {
                 if (inDrawingMode)
                 {
-                    dict["drawButton"] = MainWindow.ButtonState.Active;
-                    dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    if (optiTrackAvailable)
+                    {
+                        if (optiTrackInUse)
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Enabled;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Active;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                        else
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Active;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                    }
+                    else
+                    {
+                        dict["drawButton"] = MainWindow.ButtonState.Active;
+                        dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    }
+
                 }
                 else
                 {
                     dict["drawButton"] = MainWindow.ButtonState.Enabled;
                     dict["deleteButton"] = MainWindow.ButtonState.Active;
+                    if (optiTrackAvailable)
+                    {
+                        dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                    }
                 }
                 if (canUndo) { dict["undoButton"] = MainWindow.ButtonState.Enabled; }
                 if (canRedo) { dict["redoButton"] = MainWindow.ButtonState.Enabled; }
@@ -331,9 +421,19 @@ namespace SketchAssistantWPF
         /// Pass-trough function to display an info message in the view.
         /// </summary>
         /// <param name="msg">The message.</param>
-        public void PassMessageToView(String msg)
+        /// <param name="caption">The caption.</param>
+        public void PassMessageToView(String msg, String caption)
         {
-            programView.ShowInfoMessage(msg);
+            programView.ShowInfoMessage(msg, caption);
+        }
+
+        /// <summary>
+        /// Pass-through function to desplay an Warning message in the view
+        /// </summary>
+        /// <param name="msg"></param>
+        public void PassWarning(String msg)
+        {
+            programView.ShowWarning(msg);
         }
 
         /// <summary>
@@ -354,25 +454,22 @@ namespace SketchAssistantWPF
             return programView.IsMousePressed();
         }
 
-		public void PassOptiTrackMessage(OptiTrack.Frame frame)
-		{
-			float x = frame.Trackables[0].X;
-			float y = frame.Trackables[0].Y;
-			float z = frame.Trackables[0].Z;
-            MessageBox.Show(x.ToString());
+        public void PassOptiTrackMessage(String stringToPass)
+        {
+            programView.SetOptiTrackText(stringToPass);
             //programView.SetOptiTrackText("X: ");// + x + "Y: " + y + "Z: " + z);
         }
 
-		/*************************/
-		/*** HELPING FUNCTIONS ***/
-		/*************************/
+        /*************************/
+        /*** HELPING FUNCTIONS ***/
+        /*************************/
 
-		/// <summary>
-		/// Sets the visibility of a polyline.
-		/// </summary>
-		/// <param name="line">The polyline</param>
-		/// <param name="visible">Whether or not it should be visible.</param>
-		private void SetVisibility(Shape line, bool visible)
+        /// <summary>
+        /// Sets the visibility of a polyline.
+        /// </summary>
+        /// <param name="line">The polyline</param>
+        /// <param name="visible">Whether or not it should be visible.</param>
+        private void SetVisibility(Shape line, bool visible)
         {
             if (!visible)
             {
@@ -383,53 +480,5 @@ namespace SketchAssistantWPF
                 line.Opacity = 1;
             }
         }
-
-        /// <summary>
-        /// A function that calculates the coordinates of a point on a zoomed in image.
-        /// </summary>
-        /// <param name="">The position of the mouse cursor</param>
-        /// <returns>The real coordinates of the mouse cursor on the image</returns>
-        private Point ConvertCoordinates(Point cursorPosition)
-        {
-            if (!programModel.canvasActive) { return cursorPosition; }
-            if (programModel.canvasActive && !programModel.graphicLoaded) { return cursorPosition; }
-            ImageDimension rightImageDimensions = programModel.rightImageSize;
-            Point realCoordinates = new Point(0, 0);
-
-            int widthImage = rightImageDimensions.Width;
-            int heightImage = rightImageDimensions.Height;
-            int widthBox = programModel.rightImageBoxWidth;
-            int heightBox = programModel.rightImageBoxHeight;
-
-            if (heightImage == 0 && widthImage == 0)
-            {
-                return cursorPosition;
-            }
-
-            float imageRatio = (float)widthImage / (float)heightImage;
-            float containerRatio = (float)widthBox / (float)heightBox;
-
-            if (imageRatio >= containerRatio)
-            {
-                //Image is wider than it is high
-                float zoomFactor = (float)widthImage / (float)widthBox;
-                float scaledHeight = heightImage / zoomFactor;
-                float filler = (heightBox - scaledHeight) / 2;
-                realCoordinates.X = (int)(cursorPosition.X * zoomFactor);
-                realCoordinates.Y = (int)((cursorPosition.Y - filler) * zoomFactor);
-            }
-            else
-            {
-                //Image is higher than it is wide
-                float zoomFactor = (float)heightImage / (float)heightBox;
-                float scaledWidth = widthImage / zoomFactor;
-                float filler = (widthBox - scaledWidth) / 2;
-                realCoordinates.X = (int)((cursorPosition.X - filler) * zoomFactor);
-                realCoordinates.Y = (int)(cursorPosition.Y * zoomFactor);
-            }
-            return realCoordinates;
-        }
-
-		
-	}
+    }
 }

+ 9 - 8
SketchAssistant/SketchAssistantWPF/MVP_View.cs

@@ -87,7 +87,7 @@ namespace SketchAssistantWPF
         /// shows the given info message in a popup and asks the user to aknowledge it
         /// </summary>
         /// <param name="message">the message to show</param>
-        void ShowInfoMessage(String message);
+        void ShowInfoMessage(String message, String caption);
 
         /// <summary>
         /// Shows a warning box with the given message (Yes/No Buttons)and returns true if the user aknowledges it.
@@ -106,7 +106,8 @@ namespace SketchAssistantWPF
         /// Adds a point to the right canvas
         /// </summary>
         /// <param name="newPoint">The point</param>
-        void AddNewPointRight(Ellipse newPoint);
+        /// <param name="line">The original line object</param>
+        void AddNewPointRight(Ellipse newPoint, InternalLine line);
 
         /// <summary>
         /// Adds a point to the left canvas
@@ -120,10 +121,10 @@ namespace SketchAssistantWPF
         /// <returns>The cursor Position</returns>
         Point GetCursorPosition();
 
-		/// <summary>
-		/// Sets the contents of the last action taken indicator label.
-		/// </summary>
-		/// <param name="message">The new contents</param>
-		void SetOptiTrackText(String message);
-	}
+        /// <summary>
+        /// Sets the contents of the last action taken indicator label.
+        /// </summary>
+        /// <param name="message">The new contents</param>
+        void SetOptiTrackText(String message);
+    }
 }

+ 73 - 26
SketchAssistant/SketchAssistantWPF/MainWindow.xaml

@@ -1,20 +1,19 @@
-<Window x:Class="SketchAssistantWPF.MainWindow"
-        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:local="clr-namespace:SketchAssistantWPF"
-        mc:Ignorable="d"
-        Title="Sketch Assistant" Height="612" Width="914" SizeChanged="Window_SizeChanged">
+<Window x:Class="SketchAssistantWPF.MainWindow" 
+    xmlns:local="clr-namespace:SketchAssistantWPF" 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="Sketch Assistant" Height="612" Width="914" SizeChanged="Window_SizeChanged">
     <Grid>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="auto"/>
             <ColumnDefinition Width="auto"/>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="170*"/>
+            <ColumnDefinition Width="55*"/>
+            <ColumnDefinition Width="225*"/>
             <ColumnDefinition Width="5"/>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="226*"/>
+            <ColumnDefinition Width="225*"/>
             <ColumnDefinition Width="auto"/>
             <ColumnDefinition Width="auto"/>
         </Grid.ColumnDefinitions>
@@ -23,20 +22,27 @@
             <RowDefinition Height="*"/>
             <RowDefinition Height="auto"/>
         </Grid.RowDefinitions>
-        <ToolBar x:Name="MenuToolbar" Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="0" Background="LightGray">
+        <ToolBar x:Name="MenuToolbar" Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0" Background="LightGray">
             <Menu Background="LightGray">
                 <MenuItem x:Name="LoadMenuButton" Header="Load">
-                    <MenuItem Header="Load SVG File" Click="MenuItem_Click"/>
+                    <MenuItem x:Name="ISADMenuButton" Header="Load Template (.isad)" Click="ISADMenuItem_Click"/>
+                    <MenuItem x:Name="SVGMenuButton" Header="Load Template (.svg)" Click="SVGMenuItem_Click"/>
                 </MenuItem>
                 <MenuItem x:Name="EditMenuButton" Header="Edit">
-                    <MenuItem Header="New Canvas" Click="CanvasButton_Click"/>
-                    <MenuItem Header="Undo" Click="UndoButton_Click"/>
-                    <MenuItem Header="Redo" Click="RedoButton_Click"/>
+                    <MenuItem x:Name="CanvasMenuButton" Header="New Canvas" Click="CanvasButton_Click"/>
+                    <MenuItem x:Name="UndoMenuButton" Header="Undo" Click="UndoButton_Click"/>
+                    <MenuItem x:Name="RedoMenuButton" Header="Redo" Click="RedoButton_Click"/>
+                    <MenuItem x:Name="DebugMode" Header="Debug Input">
+                        <MenuItem x:Name="DebugOne" Header="Debug 1" Click="DebugOne_Click"/>
+                        <MenuItem x:Name="DebugTwo" Header="Debug 2" Click="DebugTwo_Click"/>
+                        <MenuItem x:Name="DebugThree" Header="Debug 3" Click="DebugThree_Click"/>
+                        <MenuItem x:Name="DebugFour" Header="Debug 4" Click="DebugFour_Click"/>
+                    </MenuItem>
                 </MenuItem>
             </Menu>
         </ToolBar>
         <!-- All Icons in the Toolbar taken from openclipart.org -->
-        <ToolBar x:Name="DrawingToolBar" Grid.Column="5" Grid.Row="0" Grid.ColumnSpan="2" Background="LightGray">
+        <ToolBar x:Name="DrawingToolBar" Grid.Column="6" Grid.Row="0" Grid.ColumnSpan="2" Background="LightGray">
             <Button x:Name="CanvasButton" ToolTip="Create a new Canvas" Click="CanvasButton_Click">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
@@ -81,7 +87,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </ToggleButton>
-            <ToggleButton x:Name="DeleteButton" ToolTip="Enter Deletion Mode" Click="DeleteButton_Click" >
+            <ToggleButton x:Name="DeleteButton" ToolTip="Enter Deletion Mode" Click="DeleteButton_Click">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -123,7 +129,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </ToggleButton>
-            <Button x:Name="UndoButton" ToolTip="Undo the last action" Click="UndoButton_Click" >
+            <Button x:Name="UndoButton" ToolTip="Undo the last action" Click="UndoButton_Click">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -169,14 +175,55 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </Button>
+            <ToggleButton x:Name="DrawWithOptiButton" ToolTip="Enter Drawing Mode (OptiTrack)" Click="DrawWithOptiButton_Click">
+                <Rectangle Width="30" Height="30">
+                    <Rectangle.Fill>
+                        <DrawingBrush>
+                            <DrawingBrush.Drawing>
+                                <DrawingGroup Transform="1,0,0,1,-30.972436,-202.26931">
+                                    <GeometryDrawing Brush="#FF000000">
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FF000000" Thickness="1.01090431" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <RectangleGeometry RadiusX="11.1847" RadiusY="11.1847" Rect="92.1615,202.7748,95.2826,53.7929" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing Brush="#FF000000">
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FF000000" Thickness="2.57952762" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <RectangleGeometry RadiusX="32.2622" RadiusY="32.2622" Rect="32.2622,219.6902,215.0813,155.1658" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing>
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FFFFFFFF" Thickness="10.62992096" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <EllipseGeometry RadiusX="48.8035" RadiusY="48.8035" Center="139.8029,297.2731" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing Brush="#FFFFFFFF">
+                                        <GeometryDrawing.Geometry>
+                                            <EllipseGeometry RadiusX="11.2372" RadiusY="11.2372" Center="66.1719,256.4501" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                </DrawingGroup>
+                            </DrawingBrush.Drawing>
+                        </DrawingBrush>
+                    </Rectangle.Fill>
+                </Rectangle>
+            </ToggleButton>
         </ToolBar>
-        <Canvas Name="LeftCanvas" Background="SlateGray" Grid.Column="2" Grid.Row="1" Height="auto" Grid.ColumnSpan="2"/>
-        <Canvas Name="CanvasSeperator" Grid.Column="4" Grid.Row="1" Background="LightGray"/>
-        <Canvas Name="RightCanvas" Background="SlateGray" Grid.Column="5" Grid.Row="1" Height="auto"
-            MouseDown="RightCanvas_MouseDown" MouseUp="RightCanvas_MouseUp" MouseMove="RightCanvas_MouseMove" Grid.ColumnSpan="2" MouseLeave="RightCanvas_MouseLeave"/>
+        <local:CustomCanvas x:Name="LeftCanvas" Background="SlateGray" Grid.Column="2" Grid.Row="1" Height="auto" Grid.ColumnSpan="3"/>
+        <Canvas Name="CanvasSeperator" Grid.Column="5" Grid.Row="1" Background="LightGray" />
+        <InkCanvas x:Name="RightCanvas" Background="SlateGray" Grid.Column="6" Grid.Row="1" Height="auto" PreviewMouseDown="RightCanvas_MouseDown" MouseUp="RightCanvas_MouseUp" MouseMove="RightCanvas_MouseMove" Grid.ColumnSpan="2" StrokeCollected="RightCanvas_StrokeCollection" EditingMode="None"/>
+
 
-        <DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="7">
-            <StatusBar DockPanel.Dock="Bottom" Name="StatusBar"  Background="LightGray">
+        <DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="8">
+            <StatusBar DockPanel.Dock="Bottom" Name="StatusBar" Background="LightGray">
                 <TextBox Name="LoadStatusBox" Text="nothing loaded" Background="LightGray"/>
                 <Separator/>
                 <TextBox Name="LastActionBox" Text="none" Background="LightGray"/>

+ 137 - 82
SketchAssistant/SketchAssistantWPF/MainWindow.xaml.cs

@@ -19,6 +19,7 @@ using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
 using System.Windows.Threading;
+using System.Windows.Ink;
 
 namespace SketchAssistantWPF
 {
@@ -39,9 +40,9 @@ namespace SketchAssistantWPF
                     InDebugMode = true;
                 }
             }
-            if(!InDebugMode)
+            if (!InDebugMode)
             {
-//DebugMode.Visibility = Visibility.Collapsed;
+                DebugMode.Visibility = Visibility.Collapsed;
             }
             ProgramPresenter = new MVP_Presenter(this);
             //  DispatcherTimer setup
@@ -50,22 +51,9 @@ namespace SketchAssistantWPF
             dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
             ProgramPresenter.Resize(new Tuple<int, int>((int)LeftCanvas.Width, (int)LeftCanvas.Height),
                 new Tuple<int, int>((int)RightCanvas.Width, (int)RightCanvas.Height));
-            OptiTrackConnector connector = new OptiTrackConnector();
-            if (connector.Init(@"C:\Users\etri\Desktop\bppp2\optitrack_setup.ttp"))
-            {
-                connector.StartTracking(handleTrackingResult);
-            }
-
         }
 
-    private void handleTrackingResult(OptiTrack.Frame frame)
-    {
-            Console.WriteLine(frame.Markers.Length);
-    }
-
-    
-
-    public enum ButtonState
+        public enum ButtonState
         {
             Enabled,
             Disabled,
@@ -101,6 +89,10 @@ namespace SketchAssistantWPF
         /// Point collections for debugging.
         /// </summary>
         DebugData debugDat = new DebugData();
+        /// <summary>
+        /// Stores Lines drawn on RightCanvas.
+        /// </summary>
+        StrokeCollection strokeCollection = new StrokeCollection();
 
         /********************************************/
         /*** WINDOW SPECIFIC FUNCTIONS START HERE ***/
@@ -115,6 +107,15 @@ namespace SketchAssistantWPF
                 new Tuple<int, int>((int)RightCanvas.ActualWidth, (int)RightCanvas.ActualHeight));
         }
 
+        /// <summary>
+        /// Collects all Strokes on RightCanvas
+        /// </summary>
+        public void RightCanvas_StrokeCollection(object sender, InkCanvasStrokeCollectedEventArgs e)
+        {
+            strokeCollection.Add(e.Stroke);
+            System.Diagnostics.Debug.WriteLine(strokeCollection.Count);
+        }
+
         /// <summary>
         /// Redo an Action.
         /// </summary>
@@ -137,6 +138,7 @@ namespace SketchAssistantWPF
         private void DeleteButton_Click(object sender, RoutedEventArgs e)
         {
             ProgramPresenter.ChangeState(false);
+            RightCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke;
         }
 
         /// <summary>
@@ -144,7 +146,19 @@ namespace SketchAssistantWPF
         /// </summary>
         private void DrawButton_Click(object sender, RoutedEventArgs e)
         {
+            ProgramPresenter.ChangeOptiTrack(false);
             ProgramPresenter.ChangeState(true);
+            RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
+        }
+
+        /// <summary>
+        /// Changes the state of the program to drawing with OptiTrack
+        /// </summary>
+        private void DrawWithOptiButton_Click(object sender, RoutedEventArgs e)
+        {
+            ProgramPresenter.ChangeOptiTrack(true);
+            ProgramPresenter.ChangeState(true);
+            RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
         }
 
         /// <summary>
@@ -161,26 +175,20 @@ namespace SketchAssistantWPF
         /// </summary>
         private void RightCanvas_MouseUp(object sender, MouseButtonEventArgs e)
         {
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);
+            if (strokeCollection.Count == 0)
+            {
+                ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up_Invalid);
+            }
+            else
+            {
+                ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);
+                RightCanvas.Strokes.RemoveAt(0);
+                strokeCollection.RemoveAt(0);
+                System.Diagnostics.Debug.WriteLine(strokeCollection.Count);
+            }
             //System.Diagnostics.Debug.WriteLine("ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);");
         }
 
-        /// <summary>
-        /// If the mouse leaves the canvas, it is treated as if the mouse was released.
-        /// </summary>
-        private void RightCanvas_MouseLeave(object sender, MouseEventArgs e)
-        {
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);
-        }
-        
-        /// <summary>
-        /// If the finger leaves the canvas, it is treated as if the finger was released.
-        /// </summary>
-        private void RightCanvas_TouchLeave(object sender, TouchEventArgs e)
-        {
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);
-        }
-
         /// <summary>
         /// Get current Mouse positon within the right picture box.
         /// </summary>
@@ -198,33 +206,8 @@ namespace SketchAssistantWPF
         private void CanvasButton_Click(object sender, RoutedEventArgs e)
         {
             ProgramPresenter.NewCanvas();
-        }
-
-        /// <summary>
-        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
-        /// Takes 7000ms
-        /// </summary>
-        private void DebugOne_Click(object sender, RoutedEventArgs e)
-        {
-            Debug(1);
-        }
-
-        /// <summary>
-        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
-        /// Takes 24000ms
-        /// </summary>
-        private void DebugTwo_Click(object sender, RoutedEventArgs e)
-        {
-            Debug(2);
-        }
-
-        /// <summary>
-        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
-        /// Takes 7000ms
-        /// </summary>
-        private void DebugThree_Click(object sender, RoutedEventArgs e)
-        {
-            Debug(3);
+            RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
+            RightCanvas.Strokes.Clear();
         }
 
         /// <summary>
@@ -237,13 +220,21 @@ namespace SketchAssistantWPF
         }
 
         /// <summary>
-        /// Import button, will open an OpenFileDialog
+        /// Import button for .isad drawing, will open an OpenFileDialog
         /// </summary>
-        private void MenuItem_Click(object sender, RoutedEventArgs e)
+        private void ISADMenuItem_Click(object sender, RoutedEventArgs e)
         {
             ProgramPresenter.ExamplePictureToolStripMenuItemClick();
         }
 
+
+        /// <summary>
+        /// Import button for .svg file, will open an OpenFileDialog
+        /// </summary>
+        private void SVGMenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            ProgramPresenter.SVGToolStripMenuItemClick();
+        }
         /*************************/
         /*** PRESENTER -> VIEW ***/
         /*************************/
@@ -263,8 +254,10 @@ namespace SketchAssistantWPF
         /// <returns>Whether or not the mouse is pressed.</returns>
         public bool IsMousePressed()
         {
-            if (!debugRunning) {
-                return (Mouse.LeftButton.Equals(MouseButtonState.Pressed) || Mouse.RightButton.Equals(MouseButtonState.Pressed)); }
+            if (!debugRunning)
+            {
+                return (Mouse.LeftButton.Equals(MouseButtonState.Pressed) || Mouse.RightButton.Equals(MouseButtonState.Pressed));
+            }
             else return true;
         }
 
@@ -332,11 +325,12 @@ namespace SketchAssistantWPF
         /// Adds a point to the right canvas
         /// </summary>
         /// <param name="newPoint">The point</param>
-        public void AddNewPointRight(Ellipse newPoint)
+        public void AddNewPointRight(Ellipse newPoint, InternalLine line)
         {
             newPoint.Height = 3; newPoint.Width = 3;
             newPoint.Fill = Brushes.Black;
             RightCanvas.Children.Add(newPoint);
+            newPoint.Margin = new Thickness(line.point.X - 1.5, line.point.Y - 1.5, 0, 0);
         }
 
         /// <summary>
@@ -392,7 +386,7 @@ namespace SketchAssistantWPF
         /// <param name="message">The new contents</param>
         public void SetOptiTrackText(string message)
         {
-            OptiTrackBox.Text = "test";
+            OptiTrackBox.Text = message;
         }
 
         /// <summary>
@@ -423,6 +417,10 @@ namespace SketchAssistantWPF
                 case "redoButton":
                     buttonToChange = RedoButton;
                     break;
+                case "drawWithOptiButton":
+                    buttonToChange = DrawWithOptiButton;
+                    isToggleable = true;
+                    break;
                 default:
                     Console.WriteLine("Invalid Button was given to SetToolStripButton. \nMaybe you forgot to add a case?");
                     return;
@@ -478,11 +476,16 @@ namespace SketchAssistantWPF
 
         /// <summary>
         /// shows the given info message in a popup and asks the user to aknowledge it
+        /// if caption is "Warning" add MessageBoxImage.Warning to the pop up window
         /// </summary>
         /// <param name="message">the message to show</param>
-        public void ShowInfoMessage(string message)
-        {
-            MessageBox.Show(message);
+        public void ShowInfoMessage(string message, string caption)
+        {   
+            if(caption == "Warning")
+            {
+                MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Warning);
+            }
+            else MessageBox.Show(message, caption);
         }
 
         /// <summary>
@@ -503,31 +506,75 @@ namespace SketchAssistantWPF
         /// <param name="active">Whether or not the canvas is active.</param>
         public void SetCanvasState(string canvasName, bool active)
         {
-            Canvas canvas;
-            switch (canvasName){
+            switch (canvasName)
+            {
                 case ("LeftCanvas"):
-                    canvas = LeftCanvas;
+                    if (active)
+                    {
+                        LeftCanvas.Background = Brushes.White;
+                    }
+                    else
+                    {
+                        LeftCanvas.Background = Brushes.SlateGray;
+                    }
                     break;
                 case ("RightCanvas"):
-                    canvas = RightCanvas;
+                    if (active)
+                    {
+                        RightCanvas.Background = Brushes.White;
+                    }
+                    else
+                    {
+                        RightCanvas.Background = Brushes.SlateGray;
+                    }
                     break;
                 default:
                     throw new InvalidOperationException("Unknown canvas name, Check that the canvas passed is either LeftCanvas or RightCanvas");
             }
-            if (active)
-            {
-                canvas.Background = Brushes.White;
-            }
-            else
-            {
-                canvas.Background = Brushes.SlateGray;
-            }
         }
 
         /************************/
         /*** HELPING FUNCTION ***/
         /************************/
 
+
+
+        /// <summary>
+        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
+        /// Takes 7000ms
+        /// </summary>
+        private void DebugOne_Click(object sender, RoutedEventArgs e)
+        {
+            Debug(1);
+        }
+
+        /// <summary>
+        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
+        /// Takes 24000ms
+        /// </summary>
+        private void DebugTwo_Click(object sender, RoutedEventArgs e)
+        {
+            Debug(2);
+        }
+
+        /// <summary>
+        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
+        /// Takes 4000ms
+        /// </summary>
+        private void DebugThree_Click(object sender, RoutedEventArgs e)
+        {
+            Debug(3);
+        }
+
+        /// <summary>
+        /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
+        /// Takes 
+        /// </summary>
+        private void DebugFour_Click(object sender, RoutedEventArgs e)
+        {
+            Debug(4);
+        }
+
         /// <summary>
         /// A function which simulates canvas input for debugging.
         /// </summary>
@@ -535,6 +582,7 @@ namespace SketchAssistantWPF
         private async void Debug(int option)
         {
             Point[] points;
+            Point start = new Point(50, 50);
             switch (option)
             {
                 case 1:
@@ -543,13 +591,20 @@ namespace SketchAssistantWPF
                 case 2:
                     points = debugDat.debugPoints2;
                     break;
+                case 3:
+                    points = debugDat.debugPoints3;
+                    break;
+                case 4:
+                    points = debugDat.debugPoints4;
+                    start = new Point(284, 148);
+                    break;
                 default:
                     return;
             }
             dispatcherTimer.Stop();
             debugRunning = true;
             ProgramPresenter.Tick(); await Task.Delay(10);
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, new Point(50, 50));
+            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, start);
             ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down); await Task.Delay(10);
             for (int x = 0; x < points.Length; x++)
             {

+ 2 - 2
SketchAssistant/SketchAssistantWPF/OptiTrackConnector.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Runtime.InteropServices;
 using System.Threading;
 
@@ -192,4 +192,4 @@ namespace OptiTrack
         public static extern int TT_TrackableMarkerCount(int index);
 
     }
-}
+}

+ 32 - 0
SketchAssistant/SketchAssistantWPF/SketchAssistantWPF.csproj

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -51,6 +52,26 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System.Data" />
@@ -74,6 +95,7 @@
       <SubType>Designer</SubType>
     </ApplicationDefinition>
     <Compile Include="ActionHistory.cs" />
+    <Compile Include="Armband.cs" />
     <Compile Include="CustomCanvas.cs" />
     <Compile Include="DebugData.cs" />
     <Compile Include="FileImporter.cs" />
@@ -122,6 +144,7 @@
     <None Include="optitrack_setup.ttp">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
+    <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
       <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@@ -147,5 +170,14 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
+  <ItemGroup>
+    <WCFMetadata Include="Connected Services\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props'))" />
+  </Target>
 </Project>

+ 6 - 0
SketchAssistant/SketchAssistantWPF/packages.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.TestPlatform" version="16.0.0" targetFramework="net461" />
+  <package id="OpenCover" version="4.7.922" targetFramework="net461" />
+  <package id="ReportGenerator" version="4.0.14" targetFramework="net461" />
+</packages>

+ 585 - 32
SketchAssistant/WhiteTests/UITest.cs

@@ -12,25 +12,100 @@ using System.Windows;
 using System.Diagnostics;
 using TestStack.White.UIItems.WindowStripControls;
 using TestStack.White.UIItems.MenuItems;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+//using WindowsInput;
+//using WindowsInput.Native;
 
 namespace WhiteTests
 {
+
     [TestClass]
     public class UITest
     {
         private TestStack.White.Application application;
 
+        /// <summary>
+        /// The directory of the input files, saved for repeated use
+        /// </summary>
+        private String input_file_dir = null;
+        /// <summary>
+        /// instance of TestContext to be able to access deployed files
+        /// </summary>
+        private TestContext testContextInstance;
+        /// <summary>
+        ///Gets or sets the test context which provides
+        ///information about and functionality for the current test run.
+        ///</summary>
+        public TestContext TestContext
+        {
+            get
+            {
+                return testContextInstance;
+            }
+            set
+            {
+                testContextInstance = value;
+            }
+        }
+
+        /// <summary>
+        /// A function that returns the path to the test_input_files folder. 
+        /// Do with it what you want.
+        /// </summary>
+        /// <returns>the path to the test_input_files folder</returns>
+        public String getSketchAssistantDirectory()
+        {
+            Regex rx = new Regex(@"^(.*\\SketchAssistant\\)");
+            Match match = rx.Match(TestContext.DeploymentDirectory);
+            String SketchAssistDir = match.Groups[1].Value;
+            if (input_file_dir == null)
+            {
+                if (Directory.Exists(SketchAssistDir + @"WhiteTests\test_input_files\"))
+                {
+                    input_file_dir = SketchAssistDir + @"WhiteTests\test_input_files\";
+                }
+                else if (Directory.Exists(SketchAssistDir + @"WhiteTests\bin\Debug\test_input_files\"))
+                {
+                    input_file_dir = SketchAssistDir + @"WhiteTests\bin\Debug\test_input_files\";
+                }
+                else
+                {
+                    Regex rx_0 = new Regex(@"^(.*\\projects\\)");
+                    Match match_0 = rx_0.Match(TestContext.DeploymentDirectory);
+                    String ProjectsDir = match_0.Groups[1].Value;
+                    var dirs = Directory.GetDirectories(ProjectsDir, "test_input_files", SearchOption.AllDirectories);
+                    input_file_dir = dirs[0];
+                }
+            }
+            return input_file_dir;
+        }
+
         public Window setupapp()
         {
-            string outputDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
-            string editedDir = outputDir.Replace("WhiteTests", "SketchAssistantWPF");
-            string app_path = editedDir + @"\SketchAssistantWPF.exe";
-            ProcessStartInfo processStart = new ProcessStartInfo(app_path, "-debug");
+            string[] files;
+            Regex rx = new Regex(@"^(.*\\SketchAssistant\\)");
+            Match match = rx.Match(TestContext.DeploymentDirectory);
+            String SketchAssistDir = match.Groups[1].Value;
+            try
+            {
+                files = Directory.GetFiles(SketchAssistDir + @"SketchAssistantWPF\bin\", "SketchAssistantWPF.exe", SearchOption.AllDirectories);
+            }
+            catch
+            {
+                Regex rx_0 = new Regex(@"^(.*\\projects\\)");
+                Match match_0 = rx_0.Match(TestContext.DeploymentDirectory);
+                String ProjectsDir = match_0.Groups[1].Value;
+                files = Directory.GetFiles(ProjectsDir, "SketchAssistantWPF.exe", SearchOption.AllDirectories);
+            }
+
+            ProcessStartInfo processStart = new ProcessStartInfo(files[0], "-debug");
             application = Application.Launch(processStart);
             return application.GetWindow("Sketch Assistant");
         }
 
         [TestMethod]
+        [TestCategory("bla")]
         public void CreateCanvasTest()
         {
             Window mainWindow = setupapp();
@@ -43,6 +118,7 @@ namespace WhiteTests
         }
 
         [TestMethod]
+        [TestCategory("bla")]
         public void DrawLineTest()
         {
             Window mainWindow = setupapp();
@@ -61,36 +137,39 @@ namespace WhiteTests
             mainWindow.Close();
         }
 
-        /*[TestMethod]
-         public void DeleteLineTest()
-         {
-             Window mainWindow = setupapp();
-             Thread.Sleep(20);
-             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-             Thread.Sleep(20);
-             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-             Thread.Sleep(20);
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-             Thread.Sleep(20);
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
-             Thread.Sleep(7000);
-             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-             Thread.Sleep(20);
-             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
-             Thread.Sleep(20);
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-             Thread.Sleep(20);
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-             Thread.Sleep(20);
-             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
-             Thread.Sleep(24000);
-             Assert.AreEqual("Last Action: Line number 0 was deleted", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-             mainWindow.Close();
-         }*/
+        [TestMethod]
+        public void DeleteLineTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DrawButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
 
         [TestMethod]
+        [TestCategory("bla")]
         public void UndoTest()
         {
             Window mainWindow = setupapp();
@@ -114,6 +193,7 @@ namespace WhiteTests
         }
 
         [TestMethod]
+        [TestCategory("bla")]
         public void RedoTest()
         {
             Window mainWindow = setupapp();
@@ -139,5 +219,478 @@ namespace WhiteTests
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Close();
         }
+
+        [TestMethod]
+        public void DrawSeveralLines()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
+            Thread.Sleep(30000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void DeleteSeveralLines()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
+            Thread.Sleep(24000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 1 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(10000);
+            Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void UndoSeveralLines()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
+            Thread.Sleep(30000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void RedoSeveralLines()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
+            Thread.Sleep(24000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void UndoAndRedoTests()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
+            Thread.Sleep(24000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
+            Thread.Sleep(7000);
+            Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 1 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void PointDraw()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(4000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        public void NewCanvasAfterDraw()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(20);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(20);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
+            Thread.Sleep(4000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            // Click on No button in warning
+            Window messageBox0 = mainWindow.MessageBox("Warning");
+            messageBox0.Get<Button>(SearchCriteria.ByText("No")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            // close warning
+            Window messageBox1 = mainWindow.MessageBox("Warning");
+            messageBox1.Close();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(20);
+            // click yes button on warning
+            Window messageBox2 = mainWindow.MessageBox("Warning");
+            messageBox2.Get<Button>(SearchCriteria.ByText("Yes")).Click();
+            Thread.Sleep(20);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(20);
+            mainWindow.Close();
+        }
+    }
+
+    [TestClass]
+    [DeploymentItem(@"WhiteTests\test_input_files\")]
+    public class FileImporterTests
+    {
+        /// <summary>
+        /// The directory of the input files, saved for repeated use
+        /// </summary>
+        private String input_file_dir = null;
+        /// <summary>
+        /// instance of TestContext to be able to access deployed files
+        /// </summary>
+        private TestContext testContextInstance;
+        /// <summary>
+        ///Gets or sets the test context which provides
+        ///information about and functionality for the current test run.
+        ///</summary>
+        public TestContext TestContext
+        {
+            get
+            {
+                return testContextInstance;
+            }
+            set
+            {
+                testContextInstance = value;
+            }
+        }
+
+        /// <summary>
+        /// A function that returns the path to the test_input_files folder. 
+        /// Do with it what you want.
+        /// </summary>
+        /// <returns>the path to the test_input_files folder</returns>
+        public String getSketchAssistantDirectory()
+        {
+            Regex rx = new Regex(@"^(.*\\SketchAssistant\\)");
+            Match match = rx.Match(TestContext.DeploymentDirectory);
+            String SketchAssistDir = match.Groups[1].Value;
+            if (input_file_dir == null)
+            {
+                if (Directory.Exists(SketchAssistDir + @"\WhiteTests\test_input_files\"))
+                {
+                    input_file_dir = SketchAssistDir + @"\WhiteTests\test_input_files\";
+                }
+                else if (Directory.Exists(SketchAssistDir + @"\WhiteTests\bin\Debug\test_input_files\"))
+                {
+                    input_file_dir = SketchAssistDir + @"\WhiteTests\bin\Debug\test_input_files\";
+                }
+                else
+                {
+                    Regex rx_0 = new Regex(@"^(.*\\projects\\)");
+                    Match match_0 = rx_0.Match(TestContext.DeploymentDirectory);
+                    String ProjectsDir = match_0.Groups[1].Value;
+                    var dirs = Directory.GetDirectories(ProjectsDir, "test_input_files", SearchOption.AllDirectories);
+                    input_file_dir = dirs[0];
+                }
+            }
+            return input_file_dir;
+        }
+
+        /// <summary>
+        /// creates a valid .isad file from the given sets of coordinates (number divisible by 3) by creating a line for every three consecutive points, parses the file and verifies that all lines and their points have been parsed correctly
+        /// </summary>
+        /// <param name="xCoordinates">an array containing the x coordinates of the points that will be created (length divisible by 3)</param>
+        /// <param name="yCoordinates">an array containing the y coordinates of the points that will be created (length divisible by 3)</param>
+        [DataTestMethod]
+        [TestCategory("bla")]
+        [DataRow(new int[] { 54, 43, 57, 11, 145, 34, 113, 299, 0 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
+        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
+        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 54, 43, 57, 11, 145, 34, 113, 199, 0 })]
+        public void ParseISADInputSuccessfulTest(int[] xCoordinates, int[] yCoordinates)
+        {
+            FileImporter uut = new FileImporter();
+
+            List<String> file = new List<string>();
+            file.Add("drawing");
+            file.Add("300x200");
+            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
+            {
+                file.Add("line");
+                file.Add(xCoordinates[i] + ";" + yCoordinates[i]);
+                file.Add(xCoordinates[i + 1] + ";" + yCoordinates[i + 1]);
+                file.Add(xCoordinates[i + 2] + ";" + yCoordinates[i + 2]);
+                file.Add("endline");
+            }
+            file.Add("enddrawing");
+
+            Tuple<int, int, List<InternalLine>> values = uut.ParseISADInputForTesting(file.ToArray());
+
+            Assert.AreEqual(xCoordinates.Length / 3, values.Item3.Count);
+            InternalLine[] lines = values.Item3.ToArray();
+            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
+            {
+                Point[] currentLine = lines[i / 3].GetPoints().ToArray();
+                Assert.AreEqual(3, currentLine.Length);
+                for (int j = 0; j < 3; j++)
+                {
+                    Assert.IsTrue(currentLine[j].X == xCoordinates[i + j] && currentLine[j].Y == yCoordinates[i + j]);
+                }
+            }
+        }
+
+        /// <summary>
+        /// parses teh given invalid .isad files and verifies that a FileImporterException is thrown, but no other exception
+        /// </summary>
+        /// <param name="file">the input file represented as an array of lines</param>
+        [DataTestMethod]
+        [TestCategory("bla")]
+        [DataRow(new String[] { })]
+        [DataRow(new String[] { "begindrawing", "300x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300;300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "30.5x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "beginline", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "500;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50x50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50", "100", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "line", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "stopline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline", "endrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline" })]
+        public void ParseISADInputExceptionTest(String[] file)
+        {
+            bool correctExceptionThrown = false;
+
+            FileImporter uut = new FileImporter();
+
+            try
+            {
+                //try to initialize the left image with an invalid isad drawing
+                Tuple<int, int, List<InternalLine>> values1 = uut.ParseISADInputForTesting(file);
+            }
+            catch (FileImporterException)
+            {
+                //save the occurence of an exception
+                correctExceptionThrown = true;
+            }
+            catch (Exception e)
+            {
+                //don't set success flag
+            }
+            //check that an exception has been thrown
+            Assert.IsTrue(correctExceptionThrown);
+        }
+
+        /// <summary>
+        /// parses all whitelisted files and ensures no exceptions are thrown (parsing abortion, e.g. due to corrupted input files, are realized by throwing a FileImporterException)
+        /// </summary>
+        [TestMethod]
+        [TestCategory("FileIO")]
+        public void ParseSVGInputNoErrorForWhitelistedFilesTest()
+        {
+            FileImporter uut = new FileImporter();
+            string[] files = Directory.GetFiles(getSketchAssistantDirectory() + @"\whitelisted", "*.svg", SearchOption.AllDirectories);
+
+            Assert.IsTrue(files.Length > 0);
+
+            foreach (string s in files) //parse each of the whitelisted files
+            {
+                Console.WriteLine(s);
+                bool noExceptionThrown = true;
+                try
+                {
+                    uut.ParseSVGInputFile(s, 10000, 10000);
+                }
+                catch (Exception e)
+                {
+                    noExceptionThrown = false;
+                }
+                Assert.IsTrue(noExceptionThrown);
+            }
+        }
+
+        /// <summary>
+        /// parses all blacklisted files and ensures an instance of FileIporterException is thrown for each file, but no other exceptions occur
+        /// </summary>
+        [TestMethod]
+        [TestCategory("FileIO")]
+        public void ParseSVGInputNoErrorForBlacklistedFilesTest()
+        {
+            FileImporter uut = new FileImporter();
+
+            string[] files = Directory.GetFiles(getSketchAssistantDirectory() + @"\blacklisted", "*.svg", SearchOption.AllDirectories);
+            Assert.IsTrue(files.Length > 0);
+            foreach (string s in files) //parse each of the blacklisted files
+            {
+                bool correctExceptionThrown = false;
+                try
+                {
+                    uut.ParseSVGInputFile(s, 10000, 10000);
+                }
+                catch (FileImporterException e)
+                {
+                    correctExceptionThrown = true;
+                }
+                catch (Exception e)
+                {
+                }
+                Assert.IsTrue(correctExceptionThrown);
+            }
+        }
     }
 }

+ 47 - 1
SketchAssistant/WhiteTests/WhiteTests.csproj

@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="15.0" DefaultTargets="Build" 
+  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" />
   <Import Project="..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
@@ -38,6 +40,9 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject />
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
       <HintPath>..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll</HintPath>
@@ -51,10 +56,15 @@
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Windows" />
+    <Reference Include="System.Windows.Forms" />
     <Reference Include="TestStack.White, Version=0.13.0.0, Culture=neutral, PublicKeyToken=2672efbf3e161801, processorArchitecture=MSIL">
       <HintPath>..\packages\TestStack.White.0.13.3\lib\net40\TestStack.White.dll</HintPath>
     </Reference>
     <Reference Include="WindowsBase" />
+    <Reference Include="WindowsInput, Version=1.0.4.0, Culture=neutral, PublicKeyToken=9b287f7dc5073cad, processorArchitecture=MSIL">
+      <HintPath>..\packages\InputSimulator.1.0.4.0\lib\net20\WindowsInput.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="UITest.cs" />
@@ -70,6 +80,37 @@
       <Name>SketchAssistantWPF</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup />
+  <ItemGroup>
+    <Content Include="test_input_files\blacklisted\all_no_size_def_in_header.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\blacklisted\all_unclosed.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\all.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\circle_ellipse.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\line.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\path.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\polygon.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\polyline.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="test_input_files\whitelisted\rect.svg">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="test.runsettings" />
+  </ItemGroup>
   <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -78,6 +119,11 @@
     </PropertyGroup>
     <Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props'))" />
     <Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets'))" />
+    <Error Condition="!Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props'))" />
   </Target>
   <Import Project="..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets')" />
+  <PropertyGroup>
+    <PostBuildEvent>
+    </PostBuildEvent>
+  </PropertyGroup>
 </Project>

+ 10 - 0
SketchAssistant/WhiteTests/test.runsettings

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+  <!-- MSTest adapter -->
+  <MSTest>
+    <MapInconclusiveToFailed>True</MapInconclusiveToFailed>
+    <DeleteDeploymentDirectoryAfterTestRunIsComplete>False</DeleteDeploymentDirectoryAfterTestRunIsComplete>
+    <DeploymentEnabled>False</DeploymentEnabled>
+  </MSTest>
+
+</RunSettings>

+ 30 - 0
SketchAssistant/WhiteTests/test_input_files/blacklisted/all_no_size_def_in_header.svg

@@ -0,0 +1,30 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg>
+  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
+  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
+  style="fill:none;stroke:black;stroke-width:3" />
+    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
+  <rect x="50" y="20" width="150" height="150"
+  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
+  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
+  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
+  <ellipse cx="250" cy="250" rx="100" ry="50"
+  style="fill:yellow;stroke:purple;stroke-width:2" />
+  <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
+  <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
+  <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />
+  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
+  fill="none" />
+  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
+  stroke-width="5" fill="none" />
+</svg>

+ 29 - 0
SketchAssistant/WhiteTests/test_input_files/blacklisted/all_unclosed.svg

@@ -0,0 +1,29 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
+  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
+  style="fill:none;stroke:black;stroke-width:3" />
+    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
+  <rect x="50" y="20" width="150" height="150"
+  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
+  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
+  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
+  <ellipse cx="250" cy="250" rx="100" ry="50"
+  style="fill:yellow;stroke:purple;stroke-width:2" />
+  <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
+  <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
+  <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />
+  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
+  fill="none" />
+  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
+  stroke-width="5" fill="none" />

+ 30 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/all.svg

@@ -0,0 +1,30 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
+  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
+  style="fill:none;stroke:black;stroke-width:3" />
+    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
+  <rect x="50" y="20" width="150" height="150"
+  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
+  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
+  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
+  <ellipse cx="250" cy="250" rx="100" ry="50"
+  style="fill:yellow;stroke:purple;stroke-width:2" />
+  <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
+  <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
+  <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />
+  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
+  fill="none" />
+  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
+  stroke-width="5" fill="none" />
+</svg>

+ 14 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/circle_ellipse.svg

@@ -0,0 +1,14 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="5000" width="5000">
+  <ellipse cx="2500" cy="2500" rx="1000" ry="500"
+  style="fill:yellow;stroke:purple;stroke-width:2" />
+  <ellipse cx="2400" cy="1000" rx="2200" ry="300" style="fill:purple" />
+  <ellipse cx="2200" cy="700" rx="1900" ry="200" style="fill:lime" />
+  <ellipse cx="2100" cy="450" rx="1700" ry="150" style="fill:yellow" />
+</svg>

+ 10 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/line.svg

@@ -0,0 +1,10 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
+</svg>

+ 21 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/path.svg

@@ -0,0 +1,21 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="600" width="1200">
+  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path d="M 100 350 q 150 -300 300 0 z M 175 200 l 150 0" stroke="blue"
+  stroke-width="5" fill="none" />
+  <path d="M 100 100 a 60 60 0 0 0 100 0 Z"
+      fill="yellow" stroke="blue" stroke-width="5" />
+  <path d="M 100 200 a 150 120 45 1 0 100 100"
+      fill="yellow" stroke="blue" stroke-width="5" />
+  <path d="M200,300 Q400,50 600,300 T1000,300"
+        fill="none" stroke="red" stroke-width="5"  />
+</svg>

+ 10 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/polygon.svg

@@ -0,0 +1,10 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
+</svg>

+ 11 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/polyline.svg

@@ -0,0 +1,11 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
+  style="fill:none;stroke:black;stroke-width:3" />
+</svg>

+ 13 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/rect.svg

@@ -0,0 +1,13 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="500" width="500">
+  <rect x="50" y="20" width="150" height="150"
+  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
+  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
+  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
+</svg>

BIN
SketchAssistant/optitrack_setup.ttp


+ 126 - 0
screenres.ps1

@@ -0,0 +1,126 @@
+# http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/07/hey-scripting-guy-how-can-i-change-my-desktop-monitor-resolution-via-windows-powershell.aspx
+
+Function Set-ScreenResolution { 
+param ( 
+[Parameter(Mandatory=$true, 
+           Position = 0)] 
+[int] 
+$Width, 
+[Parameter(Mandatory=$true, 
+           Position = 1)] 
+[int] 
+$Height 
+) 
+$pinvokeCode = @" 
+using System; 
+using System.Runtime.InteropServices; 
+namespace Resolution 
+{ 
+    [StructLayout(LayoutKind.Sequential)] 
+    public struct DEVMODE1 
+    { 
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
+        public string dmDeviceName; 
+        public short dmSpecVersion; 
+        public short dmDriverVersion; 
+        public short dmSize; 
+        public short dmDriverExtra; 
+        public int dmFields; 
+        public short dmOrientation; 
+        public short dmPaperSize; 
+        public short dmPaperLength; 
+        public short dmPaperWidth; 
+        public short dmScale; 
+        public short dmCopies; 
+        public short dmDefaultSource; 
+        public short dmPrintQuality; 
+        public short dmColor; 
+        public short dmDuplex; 
+        public short dmYResolution; 
+        public short dmTTOption; 
+        public short dmCollate; 
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
+        public string dmFormName; 
+        public short dmLogPixels; 
+        public short dmBitsPerPel; 
+        public int dmPelsWidth; 
+        public int dmPelsHeight; 
+        public int dmDisplayFlags; 
+        public int dmDisplayFrequency; 
+        public int dmICMMethod; 
+        public int dmICMIntent; 
+        public int dmMediaType; 
+        public int dmDitherType; 
+        public int dmReserved1; 
+        public int dmReserved2; 
+        public int dmPanningWidth; 
+        public int dmPanningHeight; 
+    }; 
+    class User_32 
+    { 
+        [DllImport("user32.dll")] 
+        public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode); 
+        [DllImport("user32.dll")] 
+        public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags); 
+        public const int ENUM_CURRENT_SETTINGS = -1; 
+        public const int CDS_UPDATEREGISTRY = 0x01; 
+        public const int CDS_TEST = 0x02; 
+        public const int DISP_CHANGE_SUCCESSFUL = 0; 
+        public const int DISP_CHANGE_RESTART = 1; 
+        public const int DISP_CHANGE_FAILED = -1; 
+    } 
+    public class PrmaryScreenResolution 
+    { 
+        static public string ChangeResolution(int width, int height) 
+        { 
+            DEVMODE1 dm = GetDevMode1(); 
+            if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm)) 
+            { 
+                dm.dmPelsWidth = width; 
+                dm.dmPelsHeight = height; 
+                int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST); 
+                if (iRet == User_32.DISP_CHANGE_FAILED) 
+                { 
+                    return "Unable To Process Your Request. Sorry For This Inconvenience."; 
+                } 
+                else 
+                { 
+                    iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY); 
+                    switch (iRet) 
+                    { 
+                        case User_32.DISP_CHANGE_SUCCESSFUL: 
+                            { 
+                                return "Success"; 
+                            } 
+                        case User_32.DISP_CHANGE_RESTART: 
+                            { 
+                                return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode."; 
+                            } 
+                        default: 
+                            { 
+                                return "Failed To Change The Resolution"; 
+                            } 
+                    } 
+                } 
+            } 
+            else 
+            { 
+                return "Failed To Change The Resolution."; 
+            } 
+        } 
+        private static DEVMODE1 GetDevMode1() 
+        { 
+            DEVMODE1 dm = new DEVMODE1(); 
+            dm.dmDeviceName = new String(new char[32]); 
+            dm.dmFormName = new String(new char[32]); 
+            dm.dmSize = (short)Marshal.SizeOf(dm); 
+            return dm; 
+        } 
+    } 
+} 
+"@ 
+Add-Type $pinvokeCode -ErrorAction SilentlyContinue 
+[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height) 
+}
+
+Set-ScreenResolution 1920 1080

Some files were not shown because too many files changed in this diff