using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace SketchAssistant { public class FileImporter { /// /// pointer to the running instance of main program /// Form1 program; /// /// scale factor for coordinates of svg file /// double scale; /// /// line pointer for the current svg document /// int i; /// /// array containing all characters interpreted as whitespaces which seperate words/tokens in the input file /// readonly char[] whitespaces = new char[] { ' ' , ',' }; public FileImporter(Form1 newProgram) { program = newProgram; } /// /// parses a drawing consisting of line objects, given as a file in the application specific .isad format /// /// the path of the input file /// the width and height of the left canvas and the parsed picture as a list of lines public (int, int, List) ParseISADInputFile(String fileName) { return ParseISADInput(System.IO.File.ReadAllLines(fileName)); } /// /// parses a drawing consisting of line objects, given as the content of a .isad file, seperated into lines /// /// an array holding all lines of the input file /// the width and height of the left canvas and the parsed picture as a list of lines private (int, int, List) ParseISADInput(String[] allLines) { if (allLines.Length == 0) { throw new FileImporterException("file is empty", "", -1); } if (!"drawing".Equals(allLines[0])) { throw new FileImporterException("file is not an interactive sketch assistant drawing", ".isad files have to start with the 'drawing' token", 1); } if (!"enddrawing".Equals(allLines[allLines.Length - 1])) { throw new FileImporterException("unterminated drawing definition", ".isad files have to end with the 'enddrawing' token", allLines.Length); } (int, int) dimensions = ParseISADHeader(allLines); List picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2); return (dimensions.Item1, dimensions.Item2, picture); } /// /// parses the first two lines of an input file in .isad format /// /// the input file as an array of lines /// the width and height of the left canvas private (int, int) ParseISADHeader(String[] allLines) { int width; int height; if (!(allLines.Length > 1) || !Regex.Match(allLines[1], @"^\d+x?\d+$", RegexOptions.None).Success) { throw new FileImporterException("invalid or missing canvas size definition", "format: [width]x[heigth]", 2); } String[] size = allLines[1].Split('x'); width = Convert.ToInt32(size[0]); height = Convert.ToInt32(size[1]); return (width, height); } /// /// parses all line entries of an input file in .isad format /// /// the input file as an array of lines /// the parsed picture as a list of lines private List ParseISADBody(String[] allLines, int width, int height) { String lineStartString = "line"; String lineEndString = "endline"; List drawing = new List(); //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 int lineStartPointer = i; //holds the line number of the next expected beginning of a line definition, or of the enddrawing token while (lineStartString.Equals(allLines[i])) { //start parsing next line i++; List newLine = new List(); while (!lineEndString.Equals(allLines[i])) { if (i == allLines.Length) { throw new FileImporterException("unterminated line definition", null, (i + 1)); } //parse single point definition if (!Regex.Match(allLines[i], @"^\d+;\d+$", RegexOptions.None).Success) { throw new FileImporterException("invalid Point definition: wrong format", "format: [xCoordinate];[yCoordinate]", (i + 1) ); } String[] coordinates = allLines[i].Split(';'); //no errors possible, convertability to int already checked above int xCoordinate = Convert.ToInt32(coordinates[0]); int yCoordinate = Convert.ToInt32(coordinates[1]); if (xCoordinate < 0 || yCoordinate < 0 || xCoordinate > width - 1 || yCoordinate > height - 1) { throw new FileImporterException("invalid Point definition: point out of bounds", null, (i + 1) ); } newLine.Add(new Point(xCoordinate, yCoordinate)); //start parsing next line i++; } //"parse" 'endline' token, syntax already checked at the beginning, and start parsing next line i++; //add line to drawing drawing.Add(new Line(newLine)); //update lineStartPointer to the presumable start of the next line lineStartPointer = i; } //check if end of body is reached after there are no more line definitions if(i != allLines.Length - 1) { throw new FileImporterException("missing or invalid line definition token", "line definitions start with the 'line' token", (i + 1)); } //return parsed picture return drawing; } /// /// connection point for testing use only: calls ParseISADInput(String[] allLines) and directly passes the given argument (effectively bypassing the File Input functionality) /// /// an array holding all lines of the input file /// the width and height of the left canvas and the parsed picture as a list of lines public (int, int, List) ParseISADInputForTesting(String[] allLines) { return ParseISADInput(allLines); } /// /// parses a svg drawing, given as a .svg file /// /// the path of the input file /// the width and height of the left canvas and the parsed picture as a list of lines public (int, int, List) ParseSVGInputFile(String fileName, int windowWidth, int windowHeight) { return ParseSVGInput(System.IO.File.ReadAllLines(fileName), windowWidth, windowHeight); } /// /// parses a svg drawing, given as the content of a .svg file, seperated into lines /// /// an array holding all lines of the input file /// the width and height of the left canvas and the parsed picture as a list of lines private (int, int, List) 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); } (int, int) 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 / windowHeight > sizedef.Item1 / sizedef.Item2) //height dominant, width has to be smaller than drawing window to preserve xy-scale { scale = windowHeight / sizedef.Item2; Console.WriteLine("scale: (heights) " + windowHeight + "/" + sizedef.Item2); Console.WriteLine("widths: " + windowWidth + "/" + sizedef.Item1); height = (int)Math.Round(windowHeight); width = (int) Math.Round(scale * sizedef.Item1); Console.WriteLine(width + "x" + height + " (" + scale + ")"); } else //width dominant, height has to be smaller than drawing window to preserve xy-scale { scale = windowWidth / sizedef.Item1; Console.WriteLine("scale: (widths) " + windowWidth + "/" + sizedef.Item1); Console.WriteLine("heights: " + windowHeight + "/" + sizedef.Item2); width = (int)Math.Round(windowWidth); height = (int)Math.Round(scale * sizedef.Item2); Console.WriteLine(width + "x" + height + " (" + scale + ")"); } for(int j=0; j < allLines.Length; j++) { allLines[j] = allLines[j].Trim(whitespaces); } List picture = ParseSVGBody(allLines); //parse whole svg drawing into list of lines return (width, height, picture); } /// /// 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 /// /// an array holding all lines of the input file /// the internal coordinate range of this drawing private (int, int) ParseSVGHeader(String[] allLines) { while (!allLines[i].StartsWith(" /// parses all relevant svg element definitions and skips the ones not representable by the sketch assistant /// /// an array holding all lines of the input file /// the parsed picture as a list of lines private List ParseSVGBody(String[] allLines) { List picture = new List(); while (!allLines[i].StartsWith(" element = ParseSingleSVGElement(allLines); if (element != null) { picture.AddRange(element); } i++; } return picture; } /// /// parses one toplevel svg element /// /// an array holding all lines of the input file /// the parsed Element as a list of lines private List ParseSingleSVGElement(string[] allLines) { String[] currentElement = GetCurrentElement(allLines); if (currentElement[currentElement.Length - 1].EndsWith("/>")) //single line element { return ParseSingleLineSVGElement(currentElement); } else //element containing sub-elements { return ParseMultiLineSVGElement(currentElement, allLines); } } /// /// parses a single toplevel svg element only taking one line /// /// an array holding all lines of the input file /// the parsed element as a Line object, or null if the element is not supported private List ParseSingleLineSVGElement(string[] currentElement) { List points= null; List element = null; switch (currentElement[0]) { case "(); element.Add(new Line(points)); } return element; } /// /// parses a rectangle definition into a List of Points representing a single line around the rectangle (in clockwise direction) /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List 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 rect = new List(); 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)); Console.WriteLine("parsed point: " + x + ";" + y); return rect; } /// /// parses a circle definition into a List of Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List 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); } /// /// parses a ellipse definition into a List of Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List 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); } /// /// parses a line definition into a List of two Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List 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 line = new List(); line.Add(ScaleAndCreatePoint(x1, y1)); line.Add(ScaleAndCreatePoint(x2, y2)); return line; } /// /// parses a polyline definition into a List of Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List parsePolyline(string[] currentElement) { String[] points = null; for (int j = 0; j < currentElement.Length; j++) { if (currentElement[j].StartsWith("points=")) { List pointDefs = new List(); 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 polyline = new List(); 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; } /// /// parses a polygon definition into a List of Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List parsePolygon(string[] currentElement) { String[] points = null; for (int j = 0; j < currentElement.Length; j++) { if (currentElement[j].StartsWith("points=")) { List pointDefs = new List(); 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 polygon = new List(); 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))); Console.WriteLine("parsed point: " + points[k] + ";" + points[k + 1]); } polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[0], CultureInfo.InvariantCulture), Convert.ToDouble(points[1], CultureInfo.InvariantCulture))); //close polygon Console.WriteLine("parsed point: " + points[0] + ";" + points[1]); return polygon; } /// /// parses a path definition into a List of Points /// /// the definition of the element as whitespace seperated String[] /// the parsed element as a List of Points private List parsePath(string[] currentElement) { List pathElements = new List(); 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 '"' } } List element = new List(); List currentLine = new List(); Point mirroredBezierPoint; pathElements = PreparePathElements(pathElements); //split pathElement list objects until every object is atomar (single character or single number (coordinate)) //int k = 0; //index of active element in pathElements is always 0 currentLine = parse_M(pathElements); while(!(pathElements.Count == 0){ if (pathElements.First().Equals("M")) { element.Add(new Line(currentLine)); currentLine = parse_M(pathElements); } else if (pathElements.First().Equals("")) { } } return element; } /// /// parses a hierarchical svg element and all its sub-elements /// /// the definition of the top level element as whitespace seperated String[] /// an array holding all lines of the input file /// the parsed element as a Line object, or null if the element is not supported private List ParseMultiLineSVGElement(string[] currentElement, string[] allLines) { throw new NotImplementedException(); } /// /// removes the name of the attribute aswell as the '="' at the beginning and the '"' or '">' at the end of an attribute definition /// /// the definition from the svg file /// the value of the attribute, as String (the part of definition contained between '"'s) private String ParseSingleSVGAttribute(String definition) { return definition.Split('"')[1]; } /// /// 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 /// /// an array holding all lines of the input file /// the definition of the current svg element, as String[] split by whitespaces private String[] GetCurrentElement(String[] allLines) { List currentElementTemp = allLines[i].Split(whitespaces).ToList(); while (!currentElementTemp.Last().EndsWith(">")) { i++; currentElementTemp.AddRange(allLines[i].Split(whitespaces).ToList()); } return currentElementTemp.ToArray(); } /// /// applies the scale factor to the coordinates and creates a new Point /// /// unscaled x coordinate /// unscaled y coordinate /// new Point with scaled coordinates private Point ScaleAndCreatePoint(double x, double y) { return new Point((int)Math.Round(x * scale), (int)Math.Round(y * scale)); } /// /// creates a representation of an ellipse as a List of Points by sampling the outline of the ellipse /// /// x coordinate of the center of the ellipse /// y coordinate of the center of the ellipse /// x radius of the ellipse /// y radius of the ellipse /// the parsed element as a List of Points private List SampleEllipse(double x, double y, double rx, double ry) { throw new NotImplementedException(); } } }