diff --git a/android/core/src/processing/core/PShape.java b/android/core/src/processing/core/PShape.java index 539833448..fe022364a 100644 --- a/android/core/src/processing/core/PShape.java +++ b/android/core/src/processing/core/PShape.java @@ -68,7 +68,7 @@ public class PShape implements PConstants { protected int family; /** ELLIPSE, LINE, QUAD; TRIANGLE_FAN, QUAD_STRIP; etc. */ - protected int kind; + protected int primitive; protected PMatrix matrix; @@ -80,7 +80,17 @@ public class PShape implements PConstants { //protected float y; //protected float width; //protected float height; + /** + * The width of the PShape document. + * @webref + * @brief Shape document width + */ public float width; + /** + * The width of the PShape document. + * @webref + * @brief Shape document height + */ public float height; public float depth; @@ -114,7 +124,7 @@ public class PShape implements PConstants { 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. */ + /** Array of VERTEX, BEZIER_VERTEX, and CURVE_VERTEX calls. */ protected int vertexCodeCount; protected int[] vertexCodes; /** True if this is a closed path. */ @@ -178,21 +188,39 @@ public class PShape implements PConstants { return name; } - + /** + * Returns a boolean value "true" if the image is set to be visible, "false" if not. This is modified with the setVisible() parameter. + *

The visibility of a shape is usually controlled by whatever program created the SVG file. + * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. + * + * @webref + * @brief Returns a boolean value "true" if the image is set to be visible, "false" if not + */ public boolean isVisible() { return visible; } - + /** + * Sets the shape to be visible or invisible. This is determined by the value of the visible parameter. + *

The visibility of a shape is usually controlled by whatever program created the SVG file. + * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. + * @param visible "false" makes the shape invisible and "true" makes it visible + * @webref + * @brief Sets the shape to be visible or invisible + */ public void setVisible(boolean visible) { this.visible = visible; } /** + * Disables the shape's style data and uses Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. + * =advanced * Overrides this shape's style information and uses PGraphics styles and * colors. Identical to ignoreStyles(true). Also disables styles for all * child shapes. + * @webref + * @brief Disables the shape's style data and uses Processing styles */ public void disableStyle() { style = false; @@ -204,7 +232,9 @@ public class PShape implements PConstants { /** - * Re-enables style information (fill and stroke) set in the shape. + * Enables the shape's style data and ignores Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. + * @webref + * @brief Enables the shape's style data and ignores the Processing styles */ public void enableStyle() { style = true; @@ -398,10 +428,10 @@ public class PShape implements PConstants { protected void drawPrimitive(PGraphics g) { - if (kind == POINT) { + if (primitive == POINT) { g.point(params[0], params[1]); - } else if (kind == LINE) { + } else if (primitive == LINE) { if (params.length == 4) { // 2D g.line(params[0], params[1], params[2], params[3]); @@ -410,18 +440,18 @@ public class PShape implements PConstants { params[3], params[4], params[5]); } - } else if (kind == TRIANGLE) { + } else if (primitive == TRIANGLE) { g.triangle(params[0], params[1], params[2], params[3], params[4], params[5]); - } else if (kind == QUAD) { + } else if (primitive == QUAD) { g.quad(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]); - } else if (kind == RECT) { + } else if (primitive == RECT) { if (image != null) { g.imageMode(CORNER); g.image(image, params[0], params[1], params[2], params[3]); @@ -430,29 +460,29 @@ public class PShape implements PConstants { g.rect(params[0], params[1], params[2], params[3]); } - } else if (kind == ELLIPSE) { + } else if (primitive == ELLIPSE) { g.ellipseMode(CORNER); g.ellipse(params[0], params[1], params[2], params[3]); - } else if (kind == ARC) { + } else if (primitive == ARC) { g.ellipseMode(CORNER); g.arc(params[0], params[1], params[2], params[3], params[4], params[5]); - } else if (kind == BOX) { + } else if (primitive == BOX) { if (params.length == 1) { g.box(params[0]); } else { g.box(params[0], params[1], params[2]); } - } else if (kind == SPHERE) { + } else if (primitive == SPHERE) { g.sphere(params[0]); } } protected void drawGeometry(PGraphics g) { - g.beginShape(kind); + g.beginShape(primitive); if (style) { for (int i = 0; i < vertexCount; i++) { g.vertex(vertices[i]); @@ -608,12 +638,21 @@ public class PShape implements PConstants { return childCount; } - + /** + * + * @param index the layer position of the shape to get + */ public PShape getChild(int index) { return children[index]; } - + /** + * Extracts a child shape from a parent shape. Specify the name of the shape with the target parameter. + * The shape is returned as a PShape object, or null is returned if there is an error. + * @param target the name of the shape to get + * @webref + * @brief Returns a child element of a shape as a PShape object + */ public PShape getChild(String target) { if (name != null && name.equals(target)) { return this; @@ -632,7 +671,7 @@ public class PShape implements PConstants { /** * Same as getChild(name), except that it first walks all the way up the - * hierarchy to the farthest parent, so that children can be found anywhere. + * hierarchy to the eldest grandparent, so that children can be found anywhere. */ public PShape findChild(String target) { if (parent == null) { @@ -684,6 +723,114 @@ public class PShape implements PConstants { // } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** The shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY. */ + public int getFamily() { + return family; + } + + + public int getPrimitive() { + return primitive; + } + + + public float[] getParams() { + return getParams(null); + } + + + public float[] getParams(float[] target) { + if (target == null || target.length != params.length) { + target = new float[params.length]; + } + PApplet.arrayCopy(params, target); + return target; + } + + + public float getParam(int index) { + return params[index]; + } + + + public int getVertexCount() { + return vertexCount; + } + + + public float[] getVertex(int index) { + if (index < 0 || index >= vertexCount) { + String msg = "No vertex " + index + " for this shape, " + + "only vertices 0 through " + (vertexCount-1) + "."; + throw new IllegalArgumentException(msg); + } + return vertices[index]; + } + + + public float getVertexX(int index) { + return vertices[index][X]; + } + + + public float getVertexY(int index) { + return vertices[index][Y]; + } + + + public float getVertexZ(int index) { + return vertices[index][Z]; + } + + + public int[] getVertexCodes() { + if (vertexCodes.length != vertexCodeCount) { + vertexCodes = PApplet.subset(vertexCodes, 0, vertexCodeCount); + } + return vertexCodes; + } + + + public int getVertexCodeCount() { + return vertexCodeCount; + } + + + /** + * One of VERTEX, BEZIER_VERTEX, CURVE_VERTEX, or BREAK. + */ + public int getVertexCode(int index) { + return vertexCodes[index]; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + public boolean contains(float x, float y) { + if (family == PATH) { + boolean c = false; + for (int i = 0, j = vertexCount-1; i < vertexCount; j = i++) { + if (((vertices[i][Y] > y) != (vertices[j][Y] > y)) && + (x < + (vertices[j][X]-vertices[i][X]) * + (y-vertices[i][Y]) / + (vertices[j][1]-vertices[i][Y]) + + vertices[i][X])) { + c = !c; + } + } + return c; + } else { + throw new IllegalArgumentException("The contains() method is only implemented for paths."); + } + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -692,58 +839,119 @@ public class PShape implements PConstants { // if matrix is null when one is called, // it is created and set to identity - public void translate(float tx, float ty) { checkMatrix(2); matrix.translate(tx, ty); } - + /** + * Specifies an amount to displace the shape. The x parameter specifies left/right translation, the y parameter specifies up/down translation, and the z parameter specifies translations toward/away from the screen. Subsequent calls to the method accumulates the effect. For example, calling translate(50, 0) and then translate(20, 0) is the same as translate(70, 0). This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + *

Using this method with the z parameter requires using the P3D or OPENGL parameter in combination with size. + * @webref + * @param tx left/right translation + * @param ty up/down translation + * @param tz forward/back translation + * @brief Displaces the shape + */ public void translate(float tx, float ty, float tz) { checkMatrix(3); matrix.translate(tx, ty, 0); } - - + + /** + * Rotates a shape around the x-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. + *

Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. + * Subsequent calls to the method accumulates the effect. For example, calling rotateX(HALF_PI) and then rotateX(HALF_PI) is the same as rotateX(PI). + * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + *

This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. + * @param angle angle of rotation specified in radians + * @webref + * @brief Rotates the shape around the x-axis + */ public void rotateX(float angle) { rotate(angle, 1, 0, 0); } - + /** + * Rotates a shape around the y-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. + *

Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. + * Subsequent calls to the method accumulates the effect. For example, calling rotateY(HALF_PI) and then rotateY(HALF_PI) is the same as rotateY(PI). + * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + *

This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. + * @param angle angle of rotation specified in radians + * @webref + * @brief Rotates the shape around the y-axis + */ public void rotateY(float angle) { rotate(angle, 0, 1, 0); } + /** + * Rotates a shape around the z-axis the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. + *

Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. + * Subsequent calls to the method accumulates the effect. For example, calling rotateZ(HALF_PI) and then rotateZ(HALF_PI) is the same as rotateZ(PI). + * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + *

This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the size() method as shown in the example above. + * @param angle angle of rotation specified in radians + * @webref + * @brief Rotates the shape around the z-axis + */ public void rotateZ(float angle) { rotate(angle, 0, 0, 1); } - - + + /** + * Rotates a shape the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() method. + *

Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. + * Transformations apply to everything that happens after and subsequent calls to the method accumulates the effect. + * For example, calling rotate(HALF_PI) and then rotate(HALF_PI) is the same as rotate(PI). + * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + * @param angle angle of rotation specified in radians + * @webref + * @brief Rotates the shape + */ public void rotate(float angle) { - rotate(angle, 0, 0, 1); + checkMatrix(2); // at least 2... + matrix.rotate(angle); } public void rotate(float angle, float v0, float v1, float v2) { + checkMatrix(3); + matrix.rotate(angle, v0, v1, v2); } // - - + + /** + * @param s percentage to scale the object + */ public void scale(float s) { checkMatrix(2); // at least 2... matrix.scale(s); } - public void scale(float sx, float sy) { + public void scale(float x, float y) { checkMatrix(2); - matrix.scale(sx, sy); + matrix.scale(x, y); } + /** + * Increases or decreases the size of a shape by expanding and contracting vertices. Shapes always scale from the relative origin of their bounding box. + * Scale values are specified as decimal percentages. For example, the method call scale(2.0) increases the dimension of a shape by 200%. + * Subsequent calls to the method multiply the effect. For example, calling scale(2.0) and then scale(1.5) is the same as scale(3.0). + * This transformation is applied directly to the shape, it's not refreshed each time draw() is run. + *

Using this fuction with the z parameter requires passing P3D or OPENGL into the size() parameter. + * @param x percentage to scale the object in the x-axis + * @param y percentage to scale the object in the y-axis + * @param z percentage to scale the object in the z-axis + * @webref + * @brief Increases and decreases the size of a shape + */ public void scale(float x, float y, float z) { checkMatrix(3); matrix.scale(x, y, z); diff --git a/android/core/src/processing/core/PShape3D.java b/android/core/src/processing/core/PShape3D.java index d388876b7..915458c56 100644 --- a/android/core/src/processing/core/PShape3D.java +++ b/android/core/src/processing/core/PShape3D.java @@ -341,7 +341,8 @@ public class PShape3D extends PShape implements PConstants { return numVertices; } - public PVector getVertex(int idx) { +// public PVector getVertex(int idx) { + public float[] getVertex(int idx) { if (updateElement != VERTICES) { throw new RuntimeException("PShape3D: update mode is not set to VERTICES"); } @@ -350,10 +351,11 @@ public class PShape3D extends PShape implements PConstants { float y = vertexArray[3 * idx + 1]; float z = vertexArray[3 * idx + 2]; - PVector res = new PVector(x, y, z); - return res; +// PVector res = new PVector(x, y, z); +// return res; + return new float[] { x, y, z }; } - + public float[] getVertexArray() { if (updateElement != VERTICES) { @@ -371,7 +373,7 @@ public class PShape3D extends PShape implements PConstants { PApplet.arrayCopy(vertexArray, firstUpd * 3, data, firstData * 3, length * 3); return data; } - + public ArrayList getVertexArrayList() { if (updateElement != VERTICES) { @@ -381,7 +383,10 @@ public class PShape3D extends PShape implements PConstants { ArrayList res; res = new ArrayList(); - for (int i = 0; i < numVertices; i++) res.add(getVertex(i)); + for (int i = 0; i < numVertices; i++) { + float[] v = getVertex(i); + res.add(new PVector(v[0], v[1], v[2])); + } return res; } diff --git a/android/core/src/processing/core/PShapeSVG.java b/android/core/src/processing/core/PShapeSVG.java index 84f1d651a..188cda632 100644 --- a/android/core/src/processing/core/PShapeSVG.java +++ b/android/core/src/processing/core/PShapeSVG.java @@ -207,7 +207,9 @@ public class PShapeSVG extends PShape { public PShapeSVG(PShapeSVG parent, XMLElement properties) { - //super(GROUP); + // Need to set this so that findChild() works. + // Otherwise 'parent' is null until addChild() is called later. + this.parent = parent; if (parent == null) { // set values to their defaults according to the SVG spec @@ -257,10 +259,10 @@ public class PShapeSVG extends PShape { element = properties; name = properties.getStringAttribute("id"); - // @#$(* adobe illustrator + // @#$(* adobe illustrator mangles names of objects when re-saving if (name != null) { while (true) { - String[] m = PApplet.match(name, "_x(.*)_"); + String[] m = PApplet.match(name, "_x([A-Za-z0-9]{2})_"); if (m == null) break; char repair = (char) PApplet.unhex(m[1]); name = name.replace(m[0], "" + repair); @@ -354,8 +356,9 @@ public class PShapeSVG extends PShape { } 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, " + + } else if (name.equals("text") || name.equals("font")) { + PGraphics.showWarning("Text and fonts in SVG files " + + "are not currently supported, " + "convert text to outlines instead."); } else if (name.equals("filter")) { @@ -364,9 +367,15 @@ public class PShapeSVG extends PShape { } else if (name.equals("mask")) { PGraphics.showWarning("Masks are not supported."); + } else if (name.equals("pattern")) { + PGraphics.showWarning("Patterns are not supported."); + + } else if (name.equals("stop")) { + // stop tag is handled by gradient parser, so don't warn about it + } else if (name.equals("sodipodi:namedview")) { // these are always in Inkscape files, the warnings get tedious - + } else { PGraphics.showWarning("Ignoring <" + name + "> tag."); } @@ -375,18 +384,14 @@ public class PShapeSVG extends PShape { protected void parseLine() { - kind = LINE; + primitive = LINE; family = PRIMITIVE; params = new float[] { - element.getFloatAttribute("x1"), - element.getFloatAttribute("y1"), - element.getFloatAttribute("x2"), - element.getFloatAttribute("y2"), + getFloatWithUnit(element, "x1"), + getFloatWithUnit(element, "y1"), + getFloatWithUnit(element, "x2"), + getFloatWithUnit(element, "y2") }; - // x = params[0]; - // y = params[1]; - // width = params[2]; - // height = params[3]; } @@ -395,19 +400,19 @@ public class PShapeSVG extends PShape { * @param circle true if this is a circle and not an ellipse */ protected void parseEllipse(boolean circle) { - kind = ELLIPSE; + primitive = ELLIPSE; family = PRIMITIVE; params = new float[4]; - params[0] = element.getFloatAttribute("cx"); - params[1] = element.getFloatAttribute("cy"); + params[0] = getFloatWithUnit(element, "cx"); + params[1] = getFloatWithUnit(element, "cy"); float rx, ry; if (circle) { - rx = ry = element.getFloatAttribute("r"); + rx = ry = getFloatWithUnit(element, "r"); } else { - rx = element.getFloatAttribute("rx"); - ry = element.getFloatAttribute("ry"); + rx = getFloatWithUnit(element, "rx"); + ry = getFloatWithUnit(element, "ry"); } params[0] -= rx; params[1] -= ry; @@ -418,13 +423,13 @@ public class PShapeSVG extends PShape { protected void parseRect() { - kind = RECT; + primitive = RECT; family = PRIMITIVE; params = new float[] { - element.getFloatAttribute("x"), - element.getFloatAttribute("y"), - element.getFloatAttribute("width"), - element.getFloatAttribute("height"), + getFloatWithUnit(element, "x"), + getFloatWithUnit(element, "y"), + getFloatWithUnit(element, "width"), + getFloatWithUnit(element, "height") }; } @@ -453,7 +458,7 @@ public class PShapeSVG extends PShape { protected void parsePath() { family = PATH; - kind = 0; + primitive = 0; String pathData = element.getStringAttribute("d"); if (pathData == null) return; @@ -485,7 +490,7 @@ public class PShapeSVG extends PShape { separate = false; } if (c == '-' && !lastSeparate) { - // allow for 'e' notation in numbers, e.g. 2.10e-9 + // allow for 'e' notation in numbers, e.g. 2.10e-9 // http://dev.processing.org/bugs/show_bug.cgi?id=1408 if (i == 0 || pathDataChars[i-1] != 'e') { pathBuffer.append("|"); @@ -509,6 +514,7 @@ public class PShapeSVG extends PShape { float cx = 0; float cy = 0; int i = 0; + char implicitCommand = '\0'; while (i < pathDataKeys.length) { @@ -651,7 +657,7 @@ public class PShapeSVG extends PShape { 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); + parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; i += 5; @@ -664,7 +670,7 @@ public class PShapeSVG extends PShape { 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); + parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; i += 5; @@ -686,7 +692,7 @@ public class PShapeSVG extends PShape { 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); + parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; i += 3; @@ -703,7 +709,7 @@ public class PShapeSVG extends PShape { 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); + parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); cx = endX; cy = endY; i += 3; @@ -788,6 +794,16 @@ public class PShapeSVG extends PShape { parsePathVertex(x3, y3); } + private void parsePathQuadto(float x1, float y1, + float cx, float cy, + float x2, float y2) { + parsePathCode(BEZIER_VERTEX); + // x1/y1 already covered by last moveto, lineto, or curveto + parsePathVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f)); + parsePathVertex(x2 + ((cx-x2)*2/3.0f), y2 + ((cy-y2)*2/3.0f)); + parsePathVertex(x2, y2); + } + /** * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D @@ -854,7 +870,12 @@ public class PShapeSVG extends PShape { if (properties.hasAttribute("stroke")) { String strokeText = properties.getStringAttribute("stroke"); - setStroke(strokeText); + setColor(strokeText, false); + } + + if (properties.hasAttribute("stroke-opacity")) { + String strokeOpacityText = properties.getStringAttribute("stroke-opacity"); + setStrokeOpacity(strokeOpacityText); } if (properties.hasAttribute("stroke-width")) { @@ -873,15 +894,18 @@ public class PShapeSVG extends PShape { setStrokeCap(linecap); } - // fill defaults to black (though stroke defaults to "none") // http://www.w3.org/TR/SVG/painting.html#FillProperties if (properties.hasAttribute("fill")) { String fillText = properties.getStringAttribute("fill"); - setFill(fillText); - + setColor(fillText, true); } + if (properties.hasAttribute("fill-opacity")) { + String fillOpacityText = properties.getStringAttribute("fill-opacity"); + setFillOpacity(fillOpacityText); + } + if (properties.hasAttribute("style")) { String styleText = properties.getStringAttribute("style"); String[] styleTokens = PApplet.splitTokens(styleText, ";"); @@ -894,13 +918,13 @@ public class PShapeSVG extends PShape { tokens[0] = PApplet.trim(tokens[0]); if (tokens[0].equals("fill")) { - setFill(tokens[1]); + setColor(tokens[1], true); } else if(tokens[0].equals("fill-opacity")) { setFillOpacity(tokens[1]); } else if(tokens[0].equals("stroke")) { - setStroke(tokens[1]); + setColor(tokens[1], false); } else if(tokens[0].equals("stroke-width")) { setStrokeWeight(tokens[1]); @@ -924,43 +948,24 @@ public class PShapeSVG extends PShape { } } + void setOpacity(String opacityText) { opacity = PApplet.parseFloat(opacityText); strokeColor = ((int) (opacity * 255)) << 24 | strokeColor & 0xFFFFFF; fillColor = ((int) (opacity * 255)) << 24 | fillColor & 0xFFFFFF; } + void setStrokeWeight(String lineweight) { - strokeWeight = PApplet.parseFloat(lineweight); + strokeWeight = parseUnitSize(lineweight); } + void setStrokeOpacity(String opacityText) { strokeOpacity = PApplet.parseFloat(opacityText); strokeColor = ((int) (strokeOpacity * 255)) << 24 | strokeColor & 0xFFFFFF; } - void setStroke(String strokeText) { - int opacityMask = strokeColor & 0xFF000000; - if (strokeText.equals("none")) { - stroke = false; - } else if (strokeText.startsWith("#")) { - stroke = true; - strokeColor = opacityMask | - (Integer.parseInt(strokeText.substring(1), 16)) & 0xFFFFFF; - } else if (strokeText.startsWith("rgb")) { - stroke = true; - strokeColor = opacityMask | parseRGB(strokeText); - } else if (strokeText.startsWith("url(#")) { - strokeName = strokeText.substring(5, strokeText.length() - 1); - Object strokeObject = findChild(strokeName); - if (strokeObject instanceof Gradient) { - strokeGradient = (Gradient) strokeObject; - strokeGradientPaint = calcGradientPaint(strokeGradient); //, opacity); - } else { - System.err.println("url " + strokeName + " refers to unexpected data"); - } - } - } void setStrokeJoin(String linejoin) { if (linejoin.equals("inherit")) { @@ -977,6 +982,7 @@ public class PShapeSVG extends PShape { } } + void setStrokeCap(String linecap) { if (linecap.equals("inherit")) { // do nothing, will inherit automatically @@ -991,41 +997,67 @@ public class PShapeSVG extends PShape { strokeCap = PConstants.PROJECT; } } + void setFillOpacity(String opacityText) { fillOpacity = PApplet.parseFloat(opacityText); fillColor = ((int) (fillOpacity * 255)) << 24 | fillColor & 0xFFFFFF; } + - void setFill(String fillText) { + void setColor(String colorText, boolean isFill) { int opacityMask = fillColor & 0xFF000000; - if (fillText.equals("none")) { - fill = false; - } else if (fillText.startsWith("#")) { - fill = true; - fillColor = opacityMask | - (Integer.parseInt(fillText.substring(1), 16)) & 0xFFFFFF; + boolean visible = true; + int color = 0; + String name = ""; + Gradient gradient = null; + Shader paint = null; + if (colorText.equals("none")) { + visible = false; + } else if (colorText.equals("black")) { + color = opacityMask; + } else if (colorText.equals("white")) { + color = opacityMask | 0xFFFFFF; + } else if (colorText.startsWith("#")) { + if (colorText.length() == 4) { + // Short form: #ABC, transform to long form #AABBCC + colorText = colorText.replaceAll("^#(.)(.)(.)$", "#$1$1$2$2$3$3"); + } + color = opacityMask | + (Integer.parseInt(colorText.substring(1), 16)) & 0xFFFFFF; //System.out.println("hex for fill is " + PApplet.hex(fillColor)); - } else if (fillText.startsWith("rgb")) { - fill = true; - fillColor = opacityMask | parseRGB(fillText); - } else if (fillText.startsWith("url(#")) { - fillName = fillText.substring(5, fillText.length() - 1); - //PApplet.println("looking for " + fillName); - Object fillObject = findChild(fillName); + } else if (colorText.startsWith("rgb")) { + color = opacityMask | parseRGB(colorText); + } else if (colorText.startsWith("url(#")) { + name = colorText.substring(5, colorText.length() - 1); +// PApplet.println("looking for " + name); + Object object = findChild(name); //PApplet.println("found " + fillObject); - if (fillObject instanceof Gradient) { - fill = true; - fillGradient = (Gradient) fillObject; - fillGradientPaint = calcGradientPaint(fillGradient); //, opacity); + if (object instanceof Gradient) { + gradient = (Gradient) object; + paint = calcGradientPaint(gradient); //, opacity); //PApplet.println("got filla " + fillObject); } else { - System.err.println("url " + fillName + " refers to unexpected data"); +// visible = false; + System.err.println("url " + name + " refers to unexpected data: " + object); } } + if (isFill) { + fill = visible; + fillColor = color; + fillName = name; + fillGradient = gradient; + fillGradientPaint = paint; + } else { + stroke = visible; + strokeColor = color; + strokeName = name; + strokeGradient = gradient; + strokeGradientPaint = paint; + } } - + static protected int parseRGB(String what) { int leftParen = what.indexOf('(') + 1; int rightParen = what.indexOf(')'); @@ -1046,6 +1078,19 @@ public class PShapeSVG extends PShape { } + /** + * Used in place of element.getFloatAttribute(a) because we can + * have a unit suffix (length or coordinate). + * @param element what to parse + * @param attribute name of the attribute to get + * @return unit-parsed version of the data + */ + static protected float getFloatWithUnit(XMLElement element, String attribute) { + String val = element.getStringAttribute(attribute); + return (val == null) ? 0 : parseUnitSize(val); + } + + /** * Parse a size that may have a suffix for its units. * Ignoring cases where this could also be a percentage. @@ -1079,85 +1124,6 @@ public class PShapeSVG extends PShape { } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - // these are a set of hacks so that gradients can be hacked into Java 2D. - - /* - protected Paint getGradient(String name, float cx, float cy, float r) { - BaseObject obj = (BaseObject) findChild(name); - - if (obj != null) { - if (obj.fillGradient != null) { - return obj.calcGradientPaint(obj.fillGradient, cx, cy, r); - } - } - throw new RuntimeException("No gradient found for shape " + name); - } - - - protected Paint getGradient(String name, float x1, float y1, float x2, float y2) { - BaseObject obj = (BaseObject) findChild(name); - - if (obj != null) { - if (obj.fillGradient != null) { - return obj.calcGradientPaint(obj.fillGradient, x1, y1, x2, y2); - } - } - throw new RuntimeException("No gradient found for shape " + name); - } - - - protected void strokeGradient(PGraphics g, String name, float x, float y, float r) { - Paint paint = getGradient(name, x, y, r); - - if (g instanceof PGraphicsJava2D) { - PGraphicsJava2D p2d = (PGraphicsJava2D) g; - - p2d.strokeGradient = true; - p2d.strokeGradientObject = paint; - } - } - - - protected void strokeGradient(PGraphics g, String name, float x1, float y1, float x2, float y2) { - Paint paint = getGradient(name, x1, y1, x2, y2); - - if (g instanceof PGraphicsJava2D) { - PGraphicsJava2D p2d = (PGraphicsJava2D) g; - - p2d.strokeGradient = true; - p2d.strokeGradientObject = paint; - } - } - - - protected void fillGradient(PGraphics g, String name, float x, float y, float r) { - Paint paint = getGradient(name, x, y, r); - - if (g instanceof PGraphicsJava2D) { - PGraphicsJava2D p2d = (PGraphicsJava2D) g; - - p2d.fillGradient = true; - p2d.fillGradientObject = paint; - } - } - - - protected void fillGradient(PGraphics g, String name, float x1, float y1, float x2, float y2) { - Paint paint = getGradient(name, x1, y1, x2, y2); - - if (g instanceof PGraphicsJava2D) { - PGraphicsJava2D p2d = (PGraphicsJava2D) g; - - p2d.fillGradient = true; - p2d.fillGradientObject = paint; - } - } - */ - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1180,7 +1146,13 @@ public class PShapeSVG extends PShape { XMLElement elem = elements[i]; String name = elem.getName(); if (name.equals("stop")) { - offset[count] = elem.getFloatAttribute("offset"); + String offsetAttr = elem.getStringAttribute("offset"); + float div = 1.0f; + if (offsetAttr.endsWith("%")) { + div = 100.0f; + offsetAttr = offsetAttr.substring(0, offsetAttr.length() - 1); + } + offset[count] = PApplet.parseFloat(offsetAttr) / div; String style = elem.getStringAttribute("style"); HashMap styles = parseStyleAttributes(style); @@ -1206,10 +1178,10 @@ public class PShapeSVG extends PShape { public LinearGradient(PShapeSVG parent, XMLElement properties) { super(parent, properties); - this.x1 = properties.getFloatAttribute("x1"); - this.y1 = properties.getFloatAttribute("y1"); - this.x2 = properties.getFloatAttribute("x2"); - this.y2 = properties.getFloatAttribute("y2"); + this.x1 = getFloatWithUnit(properties, "x1"); + this.y1 = getFloatWithUnit(properties, "y1"); + this.x2 = getFloatWithUnit(properties, "x2"); + this.y2 = getFloatWithUnit(properties, "y2"); String transformStr = properties.getStringAttribute("gradientTransform"); @@ -1250,9 +1222,9 @@ public class PShapeSVG extends PShape { public RadialGradient(PShapeSVG parent, XMLElement properties) { super(parent, properties); - this.cx = properties.getFloatAttribute("cx"); - this.cy = properties.getFloatAttribute("cy"); - this.r = properties.getFloatAttribute("r"); + this.cx = getFloatWithUnit(properties, "cx"); + this.cy = getFloatWithUnit(properties, "cy"); + this.r = getFloatWithUnit(properties, "r"); String transformStr = properties.getStringAttribute("gradientTransform"); @@ -1589,7 +1561,7 @@ public class PShapeSVG extends PShape { * beneath them can be used here. *
    * // This code grabs "Layer 3" and the shapes beneath it.
-   * SVG layer3 = svg.getChild("Layer 3");
+   * PShape layer3 = svg.getChild("Layer 3");
    * 
*/ public PShape getChild(String name) {