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(" 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();
}
}
}