diff --git a/svgreader/.classpath b/svgreader/.classpath new file mode 100644 index 000000000..6afd8691b --- /dev/null +++ b/svgreader/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/svgreader/.project b/svgreader/.project new file mode 100644 index 000000000..e7a9079ab --- /dev/null +++ b/svgreader/.project @@ -0,0 +1,17 @@ + + + svgreader + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/svgreader/src/processing/svg/reader/SVG.java b/svgreader/src/processing/svg/reader/SVG.java new file mode 100755 index 000000000..7fed415b7 --- /dev/null +++ b/svgreader/src/processing/svg/reader/SVG.java @@ -0,0 +1,575 @@ +package processing.svg.reader; +//package candy; +import java.util.Vector; +import processing.core.PApplet; +import processing.core.PConstants; +import proxml.InvalidDocumentException; +import proxml.XMLElement; +import proxml.XMLInOut; + +//CANDY +//Processing SVG import for the common man +//author: Flux +//with contributions by: Christian Riekoff +//email: flux.blackcat@gmail.com +// http://www.ghost-hack.com +//Built for Processing 118 + +//Using Christian Riekoff's handy proXML parser +// http://www.texone.org/proxml +// Hawt. + +//Last 100% working for Processing 0115 +//Please send bugs and annoyances to above email +//Refer to readme.txt that was part of this package for legal jargon + +public class SVG{ + + public String filename; + + protected static PApplet p; + + private static XMLInOut xmlio; + + private int svgWidth = 0; + + private int svgHeight = 0; + + private Vector draw = new Vector(); + + private XMLElement svgData; + + //For some reason Processing was using -1 for #FFFFFFFF + //thus we will use our own null value + private static int transValue = -255; + + private boolean styleOverride = false; + + /** + * Initializes a new SVG Object with the given filename. + * @param filename, String: filename of the object + * @param parent, PApplet: instance of the parent application + */ + public SVG(String filename, PApplet parent){ + //Hook into PApplet + if(p == null){ + p = parent; + xmlio = new XMLInOut(p); + } + + this.filename = filename; + + //Load into XML parser + XMLElement svgDocument; + try{ + svgDocument = xmlio.loadElementFrom(filename); + }catch (InvalidDocumentException ide){ + PApplet.println("XML: File does not exist"); + return; + } + + //Get the xml child node we need + XMLElement doc = svgDocument.getChild(0); + XMLElement entSVG = doc.getChild(0); + XMLElement svg = entSVG.getChild(1); + //While we're doing that, save the width and height too + // svgWidth = svg.getIntAttribute("width"); + //svgHeight = svg.getIntAttribute("height"); + + //Catch exception when SVG doesn't have a tag + XMLElement graphics; + String nameOfFirstChild = svg.getChild(1).toString(); + if(nameOfFirstChild.equals("")) + graphics = svg.getChild(1); + else + graphics = svg; + + this.svgData = svg; + + //Print SVG on construction + //Use this for debugging + //svg.printElementTree(" ."); + + //Store vector graphics into our draw-routine + XMLElement elements[] = graphics.getChildren(); + + for (int i = 0; i < elements.length; i++){ + String name = elements[i].getElement(); + XMLElement element = elements[i]; + if (name.equals("line")) + draw.add(new Line(element)); + else if (name.equals("circle")) + draw.add(new Circle(element)); + else if (name.equals("ellipse")) + draw.add(new Ellipse(element)); + else if (name.equals("rect")) + draw.add(new Rectangle(element)); + else if (name.equals("polygon")) + draw.add(new Polygon(element)); + else if (name.equals("path")) + draw.add(new Path(element)); + } + } + + /** + * Draws the SVG document. + * + */ + public void draw(){ + //Maybe it would be smart to save all changes that have to made to processing modes here + //like + int saveEllipseMode = p.g.ellipseMode; + + boolean stroke = p.g.stroke; + int strokeColor = p.g.strokeColor; + + boolean fill = p.g.fill; + int fillColor = p.g.fillColor; + + for (int i = 0; i < draw.size(); i++){ + VectorObject vo = (VectorObject) draw.get(i); + vo.basicDraw(); + } + + //set back the changed modes + p.g.ellipseMode = saveEllipseMode; + p.g.stroke = stroke; + p.g.fill = fill; + + p.g.strokeColor = strokeColor; + p.g.fillColor = fillColor; + } + + /** + * Overrides SVG-set styles and uses PGraphics styles and colors. + */ + public void customStyle(){ + styleOverride = true; + } + + /** + * Uses SVG's styles and colors. + */ + public void SVGStyle(){ + styleOverride = false; + } + + /** + * Prints out the SVG document usefull for parsing + */ + public void printSVG(){ + svgData.printElementTree(" ."); + } + + //Converts a string to a float + private float valueOf(String s){ + return Float.valueOf(s).floatValue(); + } + + //Default vector graphics class from which all others will polymorph + protected abstract class VectorObject{ + + int strokeColor = transValue; + int fillColor = transValue; + float strokeWeight = 1; + + + boolean hasTransform = false; + float transformation[] = null; + + //Should we keep these here even when we don't have transforms? + float rotation = 0; + float translateX = 0; + float translateY = 0; + + public VectorObject(XMLElement properties){ + getColors(properties); + getTransformation(properties); + } + + + private void getTransformation(XMLElement properties){ + String transform = ""; + if (properties.hasAttribute("transform")){ + this.hasTransform = true; + transform = properties.getAttribute("transform"); + transform = transform.substring(7, transform.length() - 2); + String tf[] = PApplet.split(transform); + + this.transformation = new float[tf.length]; + for (int i = 0; i < transformation.length; i++) + this.transformation[i] = Float.valueOf(tf[i]).floatValue(); + } + //Hacky code to get rotation working + //Done through the powers of trial and error + if (this.hasTransform){ + float t[] = this.transformation; + if (t[0] < 0 && t[1] < 0 && t[2] > 0 && t[3] < 0) + this.rotation = -p.acos(this.transformation[3]); + if (t[0] > 0 && t[1] < 0 && t[2] > 0 && t[3] > 0) + this.rotation = p.asin(this.transformation[1]); + if (t[0] < 0 && t[1] > 0 && t[2] < 0 && t[3] < 0) + this.rotation = p.acos(this.transformation[0]); + if (t[0] > 0 && t[1] > 0 && t[2] < 0 && t[3] > 0) + this.rotation = p.acos(this.transformation[0]); + this.translateX = this.transformation[4]; + this.translateY = this.transformation[5]; + } + } + + private int colorFromString(String color, String opacity){ + if (!color.equals("none")){ + color = color.substring(1, 7); + color = opacity + color; + return PApplet.unhex(color); + }else{ + return transValue; + } + } + + //We'll need color information like stroke, fill, opacity, stroke-weight + protected void getColors(XMLElement properties){ + //Yes. This is hacky. + String opacity = "FF"; + + if (properties.hasAttribute("opacity")){ + int o = (int) (properties.getFloatAttribute("opacity") * 255); + opacity = PApplet.hex(o); + opacity = opacity.substring(6, 8); + } + //A value of -1 = noStroke() or noFill() + if (properties.hasAttribute("stroke")){ + strokeColor = colorFromString(properties.getAttribute("stroke"),opacity); + } + + if (properties.hasAttribute("fill")){ + fillColor = colorFromString(properties.getAttribute("fill"),opacity); + } + + if (properties.hasAttribute("stroke-width")){ + strokeWeight = properties.getFloatAttribute("stroke-width"); + } + } + + protected void setColors(){ + p.colorMode(PConstants.RGB, 255, 255, 255, 255); + if (strokeColor != transValue) + p.stroke(strokeColor); + else + p.noStroke(); + + if (fillColor != transValue) + p.fill(fillColor); + else + p.noFill(); + p.strokeWeight(strokeWeight); + } + + protected abstract void draw(); + + //might be more efficient for all subclasses + protected void basicDraw(){ + if(!styleOverride) + setColors(); + if (hasTransform){ + p.pushMatrix(); + p.translate(translateX, translateY); + p.rotate(rotation); + } + + draw(); + + if (hasTransform){ + p.popMatrix(); + } + } + } + + private class Line extends VectorObject{ + + float x1; + + float y1; + + float x2; + + float y2; + + public Line(XMLElement properties){ + super(properties); + this.x1 = properties.getFloatAttribute("x1"); + this.y1 = properties.getFloatAttribute("y1"); + this.x2 = properties.getFloatAttribute("x2"); + this.y2 = properties.getFloatAttribute("y2"); + } + + protected void draw(){ + p.line(x1, y1, x2, y2); + } + } + + private class Circle extends VectorObject{ + + float x; + + float y; + + float radius; + + public Circle(XMLElement properties){ + super(properties); + this.x = properties.getFloatAttribute("cx"); + this.y = properties.getFloatAttribute("cy"); + this.radius = properties.getFloatAttribute("r") * 2; + } + + protected void draw(){ + p.ellipseMode(PConstants.CENTER); + p.ellipse(x, y, radius, radius); + } + } + + private class Ellipse extends VectorObject{ + + float x; + + float y; + + float rx; + + float ry; + + + public Ellipse(XMLElement properties){ + super(properties); + this.x = properties.getFloatAttribute("cx"); + this.y = properties.getFloatAttribute("cy"); + this.rx = properties.getFloatAttribute("rx") * 2; + this.ry = properties.getFloatAttribute("ry") * 2; + } + + protected void draw(){ + p.ellipseMode(PConstants.CENTER); + p.ellipse(x, y, rx, ry); + } + } + + private class Rectangle extends VectorObject{ + + float x; + + float y; + + float w; + + float h; + + public Rectangle(XMLElement properties){ + super(properties); + this.x = properties.getFloatAttribute("x"); + this.y = properties.getFloatAttribute("y"); + this.w = properties.getFloatAttribute("width"); + this.h = properties.getFloatAttribute("height"); + } + + protected void draw(){ + p.rectMode(PConstants.CORNER); + p.rect(x, y, w, h); + } + } + + private class Polygon extends VectorObject{ + + float points[][] = null; + + public Polygon(XMLElement properties){ + super(properties); + String pointsBuffer[] = null; + + //Flux: if any spaces exist in the points list string after the last point + // we may have a problem + + if (properties.hasAttribute("points")) + pointsBuffer = PApplet.split(properties.getAttribute("points"), ' '); + points = new float[pointsBuffer.length][2]; + for (int i = 0; i < points.length; i++){ + String pb[] = PApplet.split(pointsBuffer[i], ','); + points[i][0] = Float.valueOf(pb[0]).floatValue(); + points[i][1] = Float.valueOf(pb[1]).floatValue(); + } + } + + protected void draw(){ + if (points != null) + if (points.length > 0){ + p.beginShape(); + for (int i = 0; i < points.length; i++){ + p.vertex(points[i][0], points[i][1]); + } + p.endShape(PConstants.CLOSE); + } + } + } + + //Hang on! This is going to be meaty. + //Big and nasty constructor coming up.... + private class Path extends VectorObject{ + + Vector points = new Vector(); + + boolean closed = false; + + public Path(XMLElement properties){ + super(properties); + String pathDataBuffer = ""; + + if (!properties.hasAttribute("d")) + return; + + pathDataBuffer = properties.getAttribute("d"); + StringBuffer pathChars = new StringBuffer(); + + boolean lastSeperate = false; + + for (int i = 0; i < pathDataBuffer.length(); i++){ + char c = pathDataBuffer.charAt(i); + boolean seperate = false; + + if ( + c == 'M' || c == 'm' || + c == 'L' || c == 'l' || + c == 'H' || c == 'h' || + c == 'V' || c == 'v' || + c == 'C' || c == 'c' || + c == 'S' || c == 's' || + c == ',' || + c == 'Z' || c == 'z' + ){ + seperate = true; + if (i != 0) + pathChars.append("|"); + } + if (c == 'Z' || c == 'z') + seperate = false; + if (c == '-' && !lastSeperate){ + pathChars.append("|"); + } + if (c != ',') + pathChars.append("" + pathDataBuffer.charAt(i)); + if (seperate && c != ',' && c != '-') + pathChars.append("|"); + lastSeperate = seperate; + } + + pathDataBuffer = pathChars.toString(); + + String pathDataKeys[] = PApplet.split(pathDataBuffer, '|'); + + float cp[] = {0, 0}; + + for (int i = 0; i < pathDataKeys.length; i++){ + char c = pathDataKeys[i].charAt(0); + switch (c){ + //M - move to (absolute) + case 'M': { + cp[0] = valueOf(pathDataKeys[i + 1]); + cp[1] = valueOf(pathDataKeys[i + 2]); + float s[] = {cp[0], cp[1]}; + i += 2; + points.add(s); + } + break; + //m - move to (relative) + case 'm': { + cp[0] = cp[0] + valueOf(pathDataKeys[i + 1]); + cp[1] = cp[1] + valueOf(pathDataKeys[i + 2]); + float s[] = {cp[0], cp[1]}; + i += 2; + points.add(s); + } + //C - curve to (absolute) + case 'C': { + float curvePA[] = {valueOf(pathDataKeys[i + 1]), valueOf(pathDataKeys[i + 2])}; + float curvePB[] = {valueOf(pathDataKeys[i + 3]), valueOf(pathDataKeys[i + 4])}; + float endP[] = {valueOf(pathDataKeys[i + 5]), valueOf(pathDataKeys[i + 6])}; + cp[0] = endP[0]; + cp[1] = endP[1]; + i += 6; + points.add(curvePA); + points.add(curvePB); + points.add(endP); + } + break; + //c - curve to (relative) + case 'c': { + float curvePA[] = {cp[0] + valueOf(pathDataKeys[i + 1]), cp[1] + valueOf(pathDataKeys[i + 2])}; + float curvePB[] = {cp[0] + valueOf(pathDataKeys[i + 3]), cp[1] + valueOf(pathDataKeys[i + 4])}; + float endP[] = {cp[0] + valueOf(pathDataKeys[i + 5]), cp[1] + valueOf(pathDataKeys[i + 6])}; + cp[0] = endP[0]; + cp[1] = endP[1]; + i += 6; + points.add(curvePA); + points.add(curvePB); + points.add(endP); + } + break; + //S - curve to shorthand (absolute) + case 'S': { + float lastPoint[] = (float[]) points.get(points.size() - 1); + float lastLastPoint[] = (float[]) points.get(points.size() - 2); + float curvePA[] = {cp[0] + (lastPoint[0] - lastLastPoint[0]), cp[1] + (lastPoint[1] - lastLastPoint[1])}; + float curvePB[] = {valueOf(pathDataKeys[i + 1]), valueOf(pathDataKeys[i + 2])}; + float e[] = {valueOf(pathDataKeys[i + 3]), valueOf(pathDataKeys[i + 4])}; + cp[0] = e[0]; + cp[1] = e[1]; + points.add(curvePA); + points.add(curvePB); + points.add(e); + i += 4; + } + break; + //s - curve to shorthand (relative) + case 's': { + float lastPoint[] = (float[]) points.get(points.size() - 1); + float lastLastPoint[] = (float[]) points.get(points.size() - 2); + float curvePA[] = {cp[0] + (lastPoint[0] - lastLastPoint[0]), cp[1] + (lastPoint[1] - lastLastPoint[1])}; + float curvePB[] = {cp[0] + valueOf(pathDataKeys[i + 1]), cp[1] + valueOf(pathDataKeys[i + 2])}; + float e[] = {cp[0] + valueOf(pathDataKeys[i + 3]), cp[1] + valueOf(pathDataKeys[i + 4])}; + cp[0] = e[0]; + cp[1] = e[1]; + points.add(curvePA); + points.add(curvePB); + points.add(e); + i += 4; + } + break; + case 'Z': + closed = true; + break; + case 'z': + closed = true; + break; + } + } + } + + protected void draw(){ + p.beginShape(); + float start[] = (float[]) points.get(0); + p.vertex(start[0], start[1]); + for (int i = 1; i < points.size(); i += 3){ + float a[] = (float[]) points.get(i); + float b[] = (float[]) points.get(i + 1); + float e[] = (float[]) points.get(i + 2); + p.bezierVertex(a[0], a[1], b[0], b[1], e[0], e[1]); + } + if (closed) + p.endShape(PConstants.CLOSE); + else + p.beginShape(); + // p.endShape(); + } + + } + //--------------------end of class +}