diff --git a/candy/src/processing/candy/BaseObject.java b/candy/src/processing/candy/BaseObject.java
index b93f801b9..0e2a2f43a 100644
--- a/candy/src/processing/candy/BaseObject.java
+++ b/candy/src/processing/candy/BaseObject.java
@@ -7,6 +7,120 @@ import processing.core.*;
import processing.xml.XMLElement;
+/**
+ * Candy is a minimal SVG import library for Processing.
+ * Candy was written by Michael Chang, and later revised and
+ * expanded for use as a Processing core library by Ben Fry.
+ *
+ * SVG stands for Scalable Vector Graphics, a portable graphics format. It is
+ * a vector format so it allows for infinite resolution and relatively small
+ * file sizes. Most modern media software can view SVG files, including Adobe
+ * products, Firefox, etc. Illustrator and Inkscape can edit SVG files.
+ *
+ * We have no intention of turning this into a full-featured SVG library.
+ * The goal of this project is a basic shape importer that is small enough
+ * to be included with applets, meaning that its download size should be
+ * in the neighborhood of 25-30k. Because of this size, it is not made part
+ * of processing.core, as it's not a feature that will be used by the majority
+ * of our audience.
+ *
+ * For more sophisticated import/export, consider the
+ * Batik
+ * library from the Apache Software Foundation. Future improvements to this
+ * library may focus on this properly supporting a specific subset of SVG,
+ * for instance the simpler SVG profiles known as
+ * SVG Tiny or Basic ,
+ * although we still would not support the interactivity options.
+ *
+ * This library was specifically tested under SVG files created with Adobe
+ * Illustrator. We can't guarantee that it will work for any SVGs created with
+ * other software. In the future we would like to improve compatibility with
+ * Open Source software such as Inkscape, however initial tests show its
+ * base implementation produces more complicated files, and this will require
+ * more time.
+ *
+ * An SVG created under Illustrator must be created in one of two ways:
+ *
+ * File → Save for Web (or control-alt-shift-s on a PC). Under
+ * settings, make sure the CSS properties is set to "Presentation Attributes".
+ * With Illustrator CS2, it is also possible to use "Save As" with "SVG"
+ * as the file setting, but the CSS properties should also be set similarly.
+ *
+ * Saving it any other way will most likely break Candy.
+ *
+ *
+ *
+ * A minimal example program using Candy:
+ * (assuming a working moo.svg is in your data folder)
+ *
+ *
+ * import processing.candy.*;
+ * import processing.xml.*;
+ *
+ * SVG moo;
+ * void setup() {
+ * size(400,400);
+ * moo = new SVG("moo.svg",this);
+ * }
+ * void draw() {
+ * moo.draw();
+ * }
+ *
+ *
+ * Note that processing.xml needs to be imported as well.
+ * This may not be required when running code within the Processing
+ * environment, but when exported it may cause a NoClassDefError.
+ * This will be fixed in later releases of Processing
+ * (Bug 518 ).
+ *
+ *
+ *
+ * August 2008 revisions by fry (Processing 0149)
+ *
+ * Major changes to rework around PShape.
+ * Now implementing more of the "transform" attribute.
+ *
+ *
+ * February 2008 revisions by fry (Processing 0136)
+ *
+ * Added support for quadratic curves in paths (Q, q, T, and t operators)
+ * Support for reading SVG font data (though not rendering it yet)
+ *
+ *
+ * Revisions for "Candy 2" November 2006 by fry
+ *
+ * Switch to the new processing.xml library
+ * Several bug fixes for parsing of shape data
+ * Support for linear and radial gradients
+ * Support for additional types of shapes
+ * Added compound shapes (shapes with interior points)
+ * Added methods to get shapes from an internal table
+ *
+ *
+ * Revision 10/31/06 by flux
+ *
+ * Now properly supports Processing 0118
+ * Fixed a bunch of things for Casey's students and general buggity.
+ * Will now properly draw #FFFFFFFF colors (were being represented as -1)
+ * SVGs without tags are now properly caught and loaded
+ * Added a method customStyle() for overriding SVG colors/styles
+ * Added a method SVGStyle() to go back to using SVG colors/styles
+ *
+ *
+ * Some SVG objects and features may not yet be supported.
+ * Here is a partial list of non-included features
+ *
+ * Rounded rectangles
+ * Drop shadow objects
+ * Typography
+ * Layers added for Candy 2
+ * Patterns
+ * Embedded images
+ *
+ *
+ * For those interested, the SVG specification can be found
+ * here .
+ */
public class BaseObject extends PShape {
XMLElement element;
@@ -21,6 +135,61 @@ public class BaseObject extends PShape {
String fillName; // id of another object
+ /**
+ * Initializes a new SVG Object with the given filename.
+ */
+ public BaseObject(PApplet parent, String filename) {
+ // this will grab the root document, starting
+ // the xml version and initial comments are ignored
+ this(new XMLElement(parent, filename));
+ }
+
+
+ /**
+ * Initializes a new SVG Object from the given XMLElement.
+ */
+ public BaseObject(XMLElement svg) {
+ this(null, svg);
+
+ if (!svg.getName().equals("svg")) {
+ throw new RuntimeException("root is not , it's <" + svg.getName() + ">");
+ }
+
+ // not proper parsing of the viewBox, but will cover us for cases where
+ // the width and height of the object is not specified
+ String viewBoxStr = svg.getStringAttribute("viewBox");
+ if (viewBoxStr != null) {
+ int[] viewBox = PApplet.parseInt(PApplet.splitTokens(viewBoxStr));
+ width = viewBox[2];
+ height = viewBox[3];
+ }
+
+ // TODO if viewbox is not same as width/height, then use it to scale
+ // the original objects. for now, viewbox only used when width/height
+ // are empty values (which by the spec means w/h of "100%"
+ String unitWidth = svg.getStringAttribute("width");
+ String unitHeight = svg.getStringAttribute("height");
+ if (unitWidth != null) {
+ width = parseUnitSize(unitWidth);
+ height = parseUnitSize(unitHeight);
+ } else {
+ if ((width == 0) || (height == 0)) {
+ //throw new RuntimeException("width/height not specified");
+ PGraphics.showWarning("The width and/or height is not " +
+ "readable in the tag of this file.");
+ // For the spec, the default is 100% and 100%. For purposes
+ // here, insert a dummy value because this is prolly just a
+ // font or something for which the w/h doesn't matter.
+ width = 1;
+ height = 1;
+ }
+ }
+
+ //root = new Group(null, svg);
+ parseChildren(svg); // ?
+ }
+
+
public BaseObject(BaseObject parent, XMLElement properties) {
//super(GROUP);
@@ -90,6 +259,483 @@ public class BaseObject extends PShape {
}
+ protected void parseChildren(XMLElement graphics) {
+ XMLElement[] elements = graphics.getChildren();
+ children = new PShape[elements.length];
+ childCount = 0;
+
+ for (XMLElement elem : elements) {
+ PShape kid = parseChild(elem);
+ if (kid != null) {
+ addChild(kid);
+ }
+ }
+ }
+
+
+ /**
+ * Parse a child XML element.
+ * Override this method to add parsing for more SVG elements.
+ */
+ protected PShape parseChild(XMLElement elem) {
+ String name = elem.getName();
+ BaseObject shape = new BaseObject(this, elem);
+
+ if (name.equals("g")) {
+ //return new BaseObject(this, elem);
+
+ } else if (name.equals("defs")) {
+ // generally this will contain gradient info, so may
+ // as well just throw it into a group element for parsing
+ //return new BaseObject(this, elem);
+
+ } else if (name.equals("line")) {
+ //return new Line(this, elem);
+ //return new BaseObject(this, elem, LINE);
+ shape.parseLine();
+
+ } else if (name.equals("circle")) {
+ //return new BaseObject(this, elem, ELLIPSE);
+ shape.parseEllipse(true);
+
+ } else if (name.equals("ellipse")) {
+ //return new BaseObject(this, elem, ELLIPSE);
+ shape.parseEllipse(false);
+
+ } else if (name.equals("rect")) {
+ //return new BaseObject(this, elem, RECT);
+ shape.parseRect();
+
+ } else if (name.equals("polygon")) {
+ //return new BaseObject(this, elem, POLYGON);
+ shape.parsePoly(true);
+
+ } else if (name.equals("polyline")) {
+ //return new BaseObject(this, elem, POLYGON);
+ shape.parsePoly(false);
+
+ } else if (name.equals("path")) {
+ //return new BaseObject(this, elem, PATH);
+ shape.parsePath();
+
+ } else if (name.equals("radialGradient")) {
+ return new RadialGradient(this, elem);
+
+ } else if (name.equals("linearGradient")) {
+ return new LinearGradient(this, elem);
+
+ } else if (name.equals("text")) {
+ PGraphics.showWarning("Text in SVG files is not currently supported, " +
+ "convert text to outlines instead.");
+
+ } else if (name.equals("filter")) {
+ PGraphics.showWarning("Filters are not supported.");
+
+ } else if (name.equals("mask")) {
+ PGraphics.showWarning("Masks are not supported.");
+
+ } else {
+ PGraphics.showWarning("Ignoring <" + name + "> tag.");
+ }
+ return null;
+ }
+
+
+ protected void parseLine() {
+ kind = LINE;
+ family = PRIMITIVE;
+ params = new float[] {
+ element.getFloatAttribute("x1"),
+ element.getFloatAttribute("y1"),
+ element.getFloatAttribute("x2"),
+ element.getFloatAttribute("y2"),
+ };
+// x = params[0];
+// y = params[1];
+// width = params[2];
+// height = params[3];
+ }
+
+
+ /**
+ * Handles parsing ellipse and circle tags.
+ * @param circle true if this is a circle and not an ellipse
+ */
+ protected void parseEllipse(boolean circle) {
+ kind = ELLIPSE;
+ family = PRIMITIVE;
+ params = new float[4];
+
+ params[0] = element.getFloatAttribute("cx");
+ params[1] = element.getFloatAttribute("cy");
+
+ float rx, ry;
+ if (circle) {
+ rx = ry = element.getFloatAttribute("r");
+ } else {
+ rx = element.getFloatAttribute("rx");
+ ry = element.getFloatAttribute("ry");
+ }
+ params[0] -= rx;
+ params[1] -= ry;
+
+ params[2] = rx*2;
+ params[3] = ry*2;
+ }
+
+
+ protected void parseRect() {
+ kind = RECT;
+ family = PRIMITIVE;
+ params = new float[] {
+ element.getFloatAttribute("x"),
+ element.getFloatAttribute("y"),
+ element.getFloatAttribute("width"),
+ element.getFloatAttribute("height"),
+ };
+ }
+
+
+ /**
+ * Parse a polyline or polygon from an SVG file.
+ * @param close true if shape is closed (polygon), false if not (polyline)
+ */
+ protected void parsePoly(boolean close) {
+ family = PATH;
+ this.close = close;
+
+ String pointsAttr = element.getStringAttribute("points");
+ if (pointsAttr != null) {
+ String[] pointsBuffer = PApplet.splitTokens(pointsAttr);
+ vertexCount = pointsBuffer.length;
+ vertices = new float[vertexCount][2];
+ for (int i = 0; i < vertexCount; i++) {
+ String pb[] = PApplet.split(pointsBuffer[i], ',');
+ vertices[i][X] = Float.valueOf(pb[0]).floatValue();
+ vertices[i][Y] = Float.valueOf(pb[1]).floatValue();
+ }
+ }
+ }
+
+
+ protected void parsePath() {
+ family = PATH;
+ kind = 0;
+
+ String pathData = element.getStringAttribute("d");
+ if (pathData == null) return;
+ char[] pathDataChars = pathData.toCharArray();
+
+ StringBuffer pathBuffer = new StringBuffer();
+ boolean lastSeparate = false;
+
+ for (int i = 0; i < pathDataChars.length; i++) {
+ char c = pathDataChars[i];
+ boolean separate = false;
+
+ if (c == 'M' || c == 'm' ||
+ c == 'L' || c == 'l' ||
+ c == 'H' || c == 'h' ||
+ c == 'V' || c == 'v' ||
+ c == 'C' || c == 'c' || // beziers
+ c == 'S' || c == 's' ||
+ c == 'Q' || c == 'q' || // quadratic beziers
+ c == 'T' || c == 't' ||
+ c == 'Z' || c == 'z' || // closepath
+ c == ',') {
+ separate = true;
+ if (i != 0) {
+ pathBuffer.append("|");
+ }
+ }
+ if (c == 'Z' || c == 'z') {
+ separate = false;
+ }
+ if (c == '-' && !lastSeparate) {
+ pathBuffer.append("|");
+ }
+ if (c != ',') {
+ pathBuffer.append(c); //"" + pathDataBuffer.charAt(i));
+ }
+ if (separate && c != ',' && c != '-') {
+ pathBuffer.append("|");
+ }
+ lastSeparate = separate;
+ }
+
+ // use whitespace constant to get rid of extra spaces and CR or LF
+ String[] pathDataKeys =
+ PApplet.splitTokens(pathBuffer.toString(), "|" + WHITESPACE);
+
+ float cx = 0;
+ float cy = 0;
+
+ int i = 0;
+ while (i < pathDataKeys.length) {
+ char c = pathDataKeys[i].charAt(0);
+ switch (c) {
+
+ case 'M': // M - move to (absolute)
+ cx = PApplet.parseFloat(pathDataKeys[i + 1]);
+ cy = PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathMoveto(cx, cy);
+ i += 3;
+ break;
+
+ case 'm': // m - move to (relative)
+ cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ cy = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathMoveto(cx, cy);
+ i += 3;
+ break;
+
+ case 'L':
+ cx = PApplet.parseFloat(pathDataKeys[i + 1]);
+ cy = PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathLineto(cx, cy);
+ i += 3;
+ break;
+
+ case 'l':
+ cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ cy = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathLineto(cx, cy);
+ i += 3;
+ break;
+
+ // horizontal lineto absolute
+ case 'H':
+ cx = PApplet.parseFloat(pathDataKeys[i + 1]);
+ parsePathLineto(cx, cy);
+ i += 2;
+ break;
+
+ // horizontal lineto relative
+ case 'h':
+ cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ parsePathLineto(cx, cy);
+ i += 2;
+ break;
+
+ case 'V':
+ cy = PApplet.parseFloat(pathDataKeys[i + 1]);
+ parsePathLineto(cx, cy);
+ i += 2;
+ break;
+
+ case 'v':
+ cy = cy + PApplet.parseFloat(pathDataKeys[i + 1]);
+ parsePathLineto(cx, cy);
+ i += 2;
+ break;
+
+ // C - curve to (absolute)
+ case 'C': {
+ float ctrlX1 = PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY1 = PApplet.parseFloat(pathDataKeys[i + 2]);
+ float ctrlX2 = PApplet.parseFloat(pathDataKeys[i + 3]);
+ float ctrlY2 = PApplet.parseFloat(pathDataKeys[i + 4]);
+ float endX = PApplet.parseFloat(pathDataKeys[i + 5]);
+ float endY = PApplet.parseFloat(pathDataKeys[i + 6]);
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 7;
+ }
+ break;
+
+ // c - curve to (relative)
+ case 'c': {
+ float ctrlX1 = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY1 = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ float ctrlX2 = cx + PApplet.parseFloat(pathDataKeys[i + 3]);
+ float ctrlY2 = cy + PApplet.parseFloat(pathDataKeys[i + 4]);
+ float endX = cx + PApplet.parseFloat(pathDataKeys[i + 5]);
+ float endY = cy + PApplet.parseFloat(pathDataKeys[i + 6]);
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 7;
+ }
+ break;
+
+ // S - curve to shorthand (absolute)
+ case 'S': {
+ float ppx = vertices[vertexCount-2][X];
+ float ppy = vertices[vertexCount-2][Y];
+ float px = vertices[vertexCount-1][X];
+ float py = vertices[vertexCount-1][Y];
+ float ctrlX1 = px + (px - ppx);
+ float ctrlY1 = py + (py - ppy);
+ float ctrlX2 = PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY2 = PApplet.parseFloat(pathDataKeys[i + 2]);
+ float endX = PApplet.parseFloat(pathDataKeys[i + 3]);
+ float endY = PApplet.parseFloat(pathDataKeys[i + 4]);
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 5;
+ }
+ break;
+
+ // s - curve to shorthand (relative)
+ case 's': {
+ float ppx = vertices[vertexCount-2][X];
+ float ppy = vertices[vertexCount-2][Y];
+ float px = vertices[vertexCount-1][X];
+ float py = vertices[vertexCount-1][Y];
+ float ctrlX1 = px + (px - ppx);
+ float ctrlY1 = py + (py - ppy);
+ float ctrlX2 = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY2 = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ float endX = cx + PApplet.parseFloat(pathDataKeys[i + 3]);
+ float endY = cy + PApplet.parseFloat(pathDataKeys[i + 4]);
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 5;
+ }
+ break;
+
+ // Q - quadratic curve to (absolute)
+ case 'Q': {
+ float ctrlX = PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY = PApplet.parseFloat(pathDataKeys[i + 2]);
+ float endX = PApplet.parseFloat(pathDataKeys[i + 3]);
+ float endY = PApplet.parseFloat(pathDataKeys[i + 4]);
+ parsePathCurveto(ctrlX, ctrlY, ctrlX, ctrlY, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 5;
+ }
+ break;
+
+ // q - quadratic curve to (relative)
+ case 'q': {
+ float ctrlX = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ float ctrlY = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ float endX = cx + PApplet.parseFloat(pathDataKeys[i + 3]);
+ float endY = cy + PApplet.parseFloat(pathDataKeys[i + 4]);
+ parsePathCurveto(ctrlX, ctrlY, ctrlX, ctrlY, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 5;
+ }
+ break;
+
+ // T - quadratic curve to shorthand (absolute)
+ // The control point is assumed to be the reflection of the
+ // control point on the previous command relative to the
+ // current point. (If there is no previous command or if the
+ // previous command was not a Q, q, T or t, assume the control
+ // point is coincident with the current point.)
+ case 'T': {
+ float ppx = vertices[vertexCount-2][X];
+ float ppy = vertices[vertexCount-2][Y];
+ float px = vertices[vertexCount-1][X];
+ float py = vertices[vertexCount-1][Y];
+ float ctrlX = px + (px - ppx);
+ float ctrlY = py + (py - ppy);
+ float endX = PApplet.parseFloat(pathDataKeys[i + 1]);
+ float endY = PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathCurveto(ctrlX, ctrlY, ctrlX, ctrlY, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 3;
+ }
+ break;
+
+ // t - quadratic curve to shorthand (relative)
+ case 't': {
+ float ppx = vertices[vertexCount-2][X];
+ float ppy = vertices[vertexCount-2][Y];
+ float px = vertices[vertexCount-1][X];
+ float py = vertices[vertexCount-1][Y];
+ float ctrlX = px + (px - ppx);
+ float ctrlY = py + (py - ppy);
+ float endX = cx + PApplet.parseFloat(pathDataKeys[i + 1]);
+ float endY = cy + PApplet.parseFloat(pathDataKeys[i + 2]);
+ parsePathCurveto(ctrlX, ctrlY, ctrlX, ctrlY, endX, endY);
+ cx = endX;
+ cy = endY;
+ i += 3;
+ }
+ break;
+
+ case 'Z':
+ case 'z':
+ close = true;
+ i++;
+ break;
+
+ default:
+ String parsed =
+ PApplet.join(PApplet.subset(pathDataKeys, 0, i), ",");
+ String unparsed =
+ PApplet.join(PApplet.subset(pathDataKeys, i), ",");
+ System.err.println("parsed: " + parsed);
+ System.err.println("unparsed: " + unparsed);
+ throw new RuntimeException("shape command not handled: " + pathDataKeys[i]);
+ }
+ }
+ }
+
+
+// private void parsePathCheck(int num) {
+// if (vertexCount + num-1 >= vertices.length) {
+// //vertices = (float[][]) PApplet.expand(vertices);
+// float[][] temp = new float[vertexCount << 1][2];
+// System.arraycopy(vertices, 0, temp, 0, vertexCount);
+// vertices = temp;
+// }
+// }
+
+ private void parsePathVertex(float x, float y) {
+ if (vertexCount == vertices.length) {
+ //vertices = (float[][]) PApplet.expand(vertices);
+ float[][] temp = new float[vertexCount << 1][2];
+ System.arraycopy(vertices, 0, temp, 0, vertexCount);
+ vertices = temp;
+ }
+ vertices[vertexCount][X] = x;
+ vertices[vertexCount][Y] = y;
+ vertexCount++;
+ }
+
+
+ private void parsePathCode(int what) {
+ if (vertexCodeCount == vertexCodes.length) {
+ vertexCodes = PApplet.expand(vertexCodes);
+ }
+ vertexCodes[vertexCodeCount++] = what;
+ }
+
+
+ private void parsePathMoveto(float px, float py) {
+ if (vertexCount > 0) {
+ parsePathCode(BREAK);
+ }
+ parsePathCode(VERTEX);
+ parsePathVertex(px, py);
+ }
+
+
+ private void parsePathLineto(float px, float py) {
+ parsePathCode(VERTEX);
+ parsePathVertex(px, py);
+ }
+
+
+ private void parsePathCurveto(float x1, float y1,
+ float x2, float y2,
+ float x3, float y3) {
+ parsePathCode(BEZIER_VERTEX);
+ parsePathVertex(x1, y1);
+ parsePathVertex(x2, y2);
+ parsePathVertex(x3, y3);
+ }
+
+
/**
* Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D
* is rotated relative to the SVG definition, so parameters are rearranged
@@ -249,56 +895,56 @@ public class BaseObject extends PShape {
static protected int parseRGB(String what) {
- int leftParen = what.indexOf('(') + 1;
- int rightParen = what.indexOf(')');
- String sub = what.substring(leftParen, rightParen);
- int[] values = PApplet.parseInt(PApplet.splitTokens(sub, ", "));
- return (values[0] << 16) | (values[1] << 8) | (values[2]);
+ int leftParen = what.indexOf('(') + 1;
+ int rightParen = what.indexOf(')');
+ String sub = what.substring(leftParen, rightParen);
+ int[] values = PApplet.parseInt(PApplet.splitTokens(sub, ", "));
+ return (values[0] << 16) | (values[1] << 8) | (values[2]);
}
- static protected HashMap parseStyleAttributes(String style) {
- HashMap table = new HashMap();
- String[] pieces = style.split(";");
- for (int i = 0; i < pieces.length; i++) {
- String[] parts = pieces[i].split(":");
- table.put(parts[0], parts[1]);
- }
- return table;
- }
+ static protected HashMap parseStyleAttributes(String style) {
+ HashMap table = new HashMap();
+ String[] pieces = style.split(";");
+ for (int i = 0; i < pieces.length; i++) {
+ String[] parts = pieces[i].split(":");
+ table.put(parts[0], parts[1]);
+ }
+ return table;
+ }
- /**
- * Parse a size that may have a suffix for its units.
- * Ignoring cases where this could also be a percentage.
- * The units spec:
- *
- * "1pt" equals "1.25px" (and therefore 1.25 user units)
- * "1pc" equals "15px" (and therefore 15 user units)
- * "1mm" would be "3.543307px" (3.543307 user units)
- * "1cm" equals "35.43307px" (and therefore 35.43307 user units)
- * "1in" equals "90px" (and therefore 90 user units)
- *
- */
- static protected float parseUnitSize(String text) {
- int len = text.length() - 2;
+ /**
+ * Parse a size that may have a suffix for its units.
+ * Ignoring cases where this could also be a percentage.
+ * The units spec:
+ *
+ * "1pt" equals "1.25px" (and therefore 1.25 user units)
+ * "1pc" equals "15px" (and therefore 15 user units)
+ * "1mm" would be "3.543307px" (3.543307 user units)
+ * "1cm" equals "35.43307px" (and therefore 35.43307 user units)
+ * "1in" equals "90px" (and therefore 90 user units)
+ *
+ */
+ static protected float parseUnitSize(String text) {
+ int len = text.length() - 2;
- if (text.endsWith("pt")) {
- return PApplet.parseFloat(text.substring(0, len)) * 1.25f;
- } else if (text.endsWith("pc")) {
- return PApplet.parseFloat(text.substring(0, len)) * 15;
- } else if (text.endsWith("mm")) {
- return PApplet.parseFloat(text.substring(0, len)) * 3.543307f;
- } else if (text.endsWith("cm")) {
- return PApplet.parseFloat(text.substring(0, len)) * 35.43307f;
- } else if (text.endsWith("in")) {
- return PApplet.parseFloat(text.substring(0, len)) * 90;
- } else if (text.endsWith("px")) {
- return PApplet.parseFloat(text.substring(0, len));
- } else {
- return PApplet.parseFloat(text);
- }
- }
+ if (text.endsWith("pt")) {
+ return PApplet.parseFloat(text.substring(0, len)) * 1.25f;
+ } else if (text.endsWith("pc")) {
+ return PApplet.parseFloat(text.substring(0, len)) * 15;
+ } else if (text.endsWith("mm")) {
+ return PApplet.parseFloat(text.substring(0, len)) * 3.543307f;
+ } else if (text.endsWith("cm")) {
+ return PApplet.parseFloat(text.substring(0, len)) * 35.43307f;
+ } else if (text.endsWith("in")) {
+ return PApplet.parseFloat(text.substring(0, len)) * 90;
+ } else if (text.endsWith("px")) {
+ return PApplet.parseFloat(text.substring(0, len));
+ } else {
+ return PApplet.parseFloat(text);
+ }
+ }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -380,88 +1026,88 @@ public class BaseObject extends PShape {
*/
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
-
protected Paint calcGradientPaint(Gradient gradient) {
- if (gradient instanceof LinearGradient) {
- LinearGradient grad = (LinearGradient) gradient;
- return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2,
- grad.offset, grad.color, grad.count,
- opacity);
+ if (gradient instanceof LinearGradient) {
+ LinearGradient grad = (LinearGradient) gradient;
+ return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2,
+ grad.offset, grad.color, grad.count,
+ opacity);
- } else if (gradient instanceof RadialGradient) {
- RadialGradient grad = (RadialGradient) gradient;
- return new RadialGradientPaint(grad.cx, grad.cy, grad.r,
- grad.offset, grad.color, grad.count,
- opacity);
- }
- return null;
+ } else if (gradient instanceof RadialGradient) {
+ RadialGradient grad = (RadialGradient) gradient;
+ return new RadialGradientPaint(grad.cx, grad.cy, grad.r,
+ grad.offset, grad.color, grad.count,
+ opacity);
+ }
+ return null;
}
protected Paint calcGradientPaint(Gradient gradient,
- float x1, float y1, float x2, float y2) {
- if (gradient instanceof LinearGradient) {
- LinearGradient grad = (LinearGradient) gradient;
- return new LinearGradientPaint(x1, y1, x2, y2,
- grad.offset, grad.color, grad.count,
- opacity);
- }
- throw new RuntimeException("Not a linear gradient.");
+ float x1, float y1, float x2, float y2) {
+ if (gradient instanceof LinearGradient) {
+ LinearGradient grad = (LinearGradient) gradient;
+ return new LinearGradientPaint(x1, y1, x2, y2,
+ grad.offset, grad.color, grad.count,
+ opacity);
+ }
+ throw new RuntimeException("Not a linear gradient.");
}
protected Paint calcGradientPaint(Gradient gradient,
- float cx, float cy, float r) {
- if (gradient instanceof RadialGradient) {
- RadialGradient grad = (RadialGradient) gradient;
- return new RadialGradientPaint(cx, cy, r,
- grad.offset, grad.color, grad.count,
- opacity);
- }
- throw new RuntimeException("Not a radial gradient.");
+ float cx, float cy, float r) {
+ if (gradient instanceof RadialGradient) {
+ RadialGradient grad = (RadialGradient) gradient;
+ return new RadialGradientPaint(cx, cy, r,
+ grad.offset, grad.color, grad.count,
+ opacity);
+ }
+ throw new RuntimeException("Not a radial gradient.");
}
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
-
protected void styles(PGraphics g) {
- super.styles(g);
+ super.styles(g);
- if (g instanceof PGraphicsJava2D) {
- PGraphicsJava2D p2d = (PGraphicsJava2D) g;
+ if (g instanceof PGraphicsJava2D) {
+ PGraphicsJava2D p2d = (PGraphicsJava2D) g;
- if (strokeGradient != null) {
- p2d.strokeGradient = true;
- p2d.strokeGradientObject = strokeGradientPaint;
- } else {
- // need to shut off, in case parent object has a gradient applied
- //p2d.strokeGradient = false;
- }
- if (fillGradient != null) {
- p2d.fillGradient = true;
- p2d.fillGradientObject = fillGradientPaint;
- } else {
- // need to shut off, in case parent object has a gradient applied
- //p2d.fillGradient = false;
- }
- }
+ if (strokeGradient != null) {
+ p2d.strokeGradient = true;
+ p2d.strokeGradientObject = strokeGradientPaint;
+ } else {
+ // need to shut off, in case parent object has a gradient applied
+ //p2d.strokeGradient = false;
+ }
+ if (fillGradient != null) {
+ p2d.fillGradient = true;
+ p2d.fillGradientObject = fillGradientPaint;
+ } else {
+ // need to shut off, in case parent object has a gradient applied
+ //p2d.fillGradient = false;
+ }
+ }
}
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- public void drawImpl(PGraphics g) {
- // do nothing
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ //public void drawImpl(PGraphics g) {
+ // do nothing
+ //}
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
/**
* Get a particular element based on its SVG ID. When editing SVG by hand,
* this is the id="" tag on any SVG element. When editing from Illustrator,
@@ -474,101 +1120,17 @@ public class BaseObject extends PShape {
*
*/
public PShape getChild(String name) {
- PShape found = super.getChild(name);
- if (found != null) return found;
- // otherwise try with underscores instead of spaces
- return super.getChild(name.replace(' ', '_'));
- }
-
-
- protected void parseChildren(XMLElement graphics) {
- XMLElement[] elements = graphics.getChildren();
- //objects = new BaseObject[elements.length];
- children = new PShape[elements.length];
- childCount = 0;
-
- for (XMLElement elem : elements) {
- PShape kid = parseChild(elem);
- if (kid != null) {
-// System.out.println("adding child " +
-// kid.getClass().getName() +
-// " to " + getClass().getName());
- addChild(kid);
- }
- }
-
- // for (int i = 0; i < elements.length; i++) {
- // String name = elements[i].getName(); //getElement();
- // XMLElement elem = elements[i];
- //
- // addChild(parseGroupChild(elem, name));
- // }
+ PShape found = super.getChild(name);
+ if (found != null) return found;
+ // otherwise try with underscores instead of spaces
+ return super.getChild(name.replace(' ', '_'));
}
/**
- * Parse a child XML element.
- * Override this method to add parsing for more SVG elements.
+ * Prints out the SVG document. Useful for parsing.
*/
- protected PShape parseChild(XMLElement elem) {
- String name = elem.getName();
-
- if (name.equals("g")) {
- return new BaseObject(this, elem);
-
- } else if (name.equals("defs")) {
- // generally this will contain gradient info, so may
- // as well just throw it into a group element for parsing
- return new BaseObject(this, elem);
-
- } else if (name.equals("line")) {
- return new Line(this, elem);
-
- } else if (name.equals("circle")) {
- return new Circle(this, elem);
-
- } else if (name.equals("ellipse")) {
- return new Ellipse(this, elem);
-
- } else if (name.equals("rect")) {
- return new Rect(this, elem);
-
- } else if (name.equals("polygon")) {
- return new Poly(this, elem, true);
-
- } else if (name.equals("polyline")) {
- return new Poly(this, elem, false);
-
- } else if (name.equals("path")) {
- return new Path(this, elem);
-
- } else if (name.equals("radialGradient")) {
- return new RadialGradient(this, elem);
-
- } else if (name.equals("linearGradient")) {
- return new LinearGradient(this, elem);
-
- } else if (name.equals("text")) {
- PGraphics.showWarning("Text is not currently handled, " +
- "convert text to outlines instead.");
-
- } else if (name.equals("filter")) {
- PApplet.println("Filters are not supported.");
-
- } else if (name.equals("mask")) {
- PApplet.println("Masks are not supported.");
-
- } else {
- System.err.println("Ignoring <" + name + "> tag.");
- }
- return null;
+ public void print() {
+ PApplet.println(element.toString());
}
-
-
- /**
- * Prints out the SVG document. Useful for parsing.
- */
- public void print() {
- PApplet.println(element.toString());
- }
}
diff --git a/candy/src/processing/candy/Path.java b/candy/src/processing/candy/Path.java
index dc36a21f0..848242d7a 100644
--- a/candy/src/processing/candy/Path.java
+++ b/candy/src/processing/candy/Path.java
@@ -38,15 +38,15 @@ public class Path extends BaseObject {
boolean separate = false;
if (c == 'M' || c == 'm' ||
- c == 'L' || c == 'l' ||
- c == 'H' || c == 'h' ||
- c == 'V' || c == 'v' ||
- c == 'C' || c == 'c' || // beziers
- c == 'S' || c == 's' ||
- c == 'Q' || c == 'q' || // quadratic beziers
- c == 'T' || c == 't' ||
- c == 'Z' || c == 'z' || // closepath
- c == ',') {
+ c == 'L' || c == 'l' ||
+ c == 'H' || c == 'h' ||
+ c == 'V' || c == 'v' ||
+ c == 'C' || c == 'c' || // beziers
+ c == 'S' || c == 's' ||
+ c == 'Q' || c == 'q' || // quadratic beziers
+ c == 'T' || c == 't' ||
+ c == 'Z' || c == 'z' || // closepath
+ c == ',') {
separate = true;
if (i != 0) {
pathChars.append("|");
diff --git a/core/src/processing/core/PShape.java b/core/src/processing/core/PShape.java
index ff1f8685f..c2ff093d7 100644
--- a/core/src/processing/core/PShape.java
+++ b/core/src/processing/core/PShape.java
@@ -77,22 +77,27 @@ abstract public class PShape implements PConstants {
/** Temporary toggle for whether styles should be honored. */
protected boolean style = true;
- //public boolean hasTransform;
- //protected float[] transformation;
+ /** For primitive shapes in particular, parms like x/y/w/h or x1/y1/x2/y2. */
+ protected float[] params;
- static final int VERTEX = 0;
- static final int BEZIER_VERTEX = 1;
- static final int CURVE_VERTEX = 2;
- int[] opcodes;
- int opcodeCount;
-
protected int vertexCount;
- protected float[][] vertices;
+ /**
+ * When drawing POLYGON shapes, the second param is an array of length
+ * VERTEX_FIELD_COUNT. When drawing PATH shapes, the second param has only
+ * two variables.
+ */
+ protected float[][] vertices;
+
+ static public final int VERTEX = 0;
+ static public final int BEZIER_VERTEX = 1;
+ static public final int CURVE_VERTEX = 2;
+ static public final int BREAK = 3;
+ /** Array of VERTEX, BEZIER_VERTEX, and CURVE_VERTEXT calls. */
+ protected int vertexCodeCount;
+ protected int[] vertexCodes;
+ /** True if this is a closed path. */
+ protected boolean close;
- // need to reorder vertex fields to make a VERTEX_SHORT_COUNT
- // that puts all the non-rendering fields into later indices
- //int dataCount;
- float[] params; // second param is the VERTEX_FIELD_COUNT
// should this be called vertices (consistent with PGraphics internals)
// or does that hurt flexibility?
@@ -431,13 +436,14 @@ abstract public class PShape implements PConstants {
}
+ /*
protected void drawPath(PGraphics g) {
g.beginShape();
for (int j = 0; j < childCount; j++) {
if (j > 0) g.breakShape();
int count = children[j].vertexCount;
float[][] vert = children[j].vertices;
- int[] code = children[j].opcodes;
+ int[] code = children[j].vertexCodes;
for (int i = 0; i < count; i++) {
if (style) {
@@ -482,6 +488,64 @@ abstract public class PShape implements PConstants {
}
g.endShape();
}
+ */
+
+
+ protected void drawPath(PGraphics g) {
+ g.beginShape();
+ int index = 0;
+
+ if (vertices[0].length == 2) {
+ for (int j = 0; j < vertexCodeCount; j++) {
+ switch (vertexCodes[j]) {
+
+ case VERTEX:
+ g.vertex(vertices[index][X], vertices[index][Y]);
+ index++;
+ break;
+
+ case BEZIER_VERTEX:
+ g.bezierVertex(vertices[index+0][X], vertices[index+0][Y],
+ vertices[index+1][X], vertices[index+1][Y],
+ vertices[index+2][X], vertices[index+2][Y]);
+ index += 3;
+ break;
+
+ case CURVE_VERTEX:
+ g.curveVertex(vertices[index][X], vertices[index][Y]);
+ index++;
+
+ case BREAK:
+ g.breakShape();
+ }
+ }
+ } else {
+ for (int j = 0; j < vertexCodeCount; j++) {
+ switch (vertexCodes[j]) {
+
+ case VERTEX:
+ g.vertex(vertices[index][X], vertices[index][Y], vertices[index][Z]);
+ index++;
+ break;
+
+ case BEZIER_VERTEX:
+ g.bezierVertex(vertices[index+0][X], vertices[index+0][Y], vertices[index+0][Z],
+ vertices[index+1][X], vertices[index+1][Y], vertices[index+1][Z],
+ vertices[index+2][X], vertices[index+2][Y], vertices[index+2][Z]);
+ index += 3;
+ break;
+
+ case CURVE_VERTEX:
+ g.curveVertex(vertices[index][X], vertices[index][Y], vertices[index][Z]);
+ index++;
+
+ case BREAK:
+ g.breakShape();
+ }
+ }
+ }
+ g.endShape(close ? CLOSE : OPEN);
+ }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .