package com.kreative.paint.material.stroke;
import java.awt.BasicStroke;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class ArrowheadShape implements Shape {
public static final ArrowheadShape GENERAL_FILLED_ARROW = new ArrowheadShape.Polygon(
new float[]{6,0,0,6,0,-6}, EndCap.SQUARE, LineJoin.MITER, 10.0f, true, true
);
public static final ArrowheadShape GENERAL_STROKED_ARROW = new ArrowheadShape.Polygon(
new float[]{6,0,0,6,0,-6}, EndCap.SQUARE, LineJoin.MITER, 10.0f, true, false
);
public static final ArrowheadShape GENERAL_FILLED_CIRCLE = new ArrowheadShape.Circle(2, 0, 4, true, true);
public static final ArrowheadShape GENERAL_STROKED_CIRCLE = new ArrowheadShape.Circle(2, 0, 4, true, false);
private final Shape awtShape;
public final boolean stroke;
public final boolean fill;
protected ArrowheadShape(Shape awtShape, boolean stroke, boolean fill) {
this.awtShape = awtShape;
this.stroke = stroke;
this.fill = fill;
}
public abstract Stroke getStroke(float lineWidth);
protected abstract boolean equalsImpl(ArrowheadShape that);
protected abstract int hashCodeImpl();
@Override
public final boolean equals(Object that) {
if (that instanceof ArrowheadShape) {
return (this.equalsImpl((ArrowheadShape)that))
&& (this.stroke == ((ArrowheadShape)that).stroke)
&& (this.fill == ((ArrowheadShape)that).fill);
} else {
return false;
}
}
@Override
public final int hashCode() {
int hashCode = hashCodeImpl();
if (stroke) hashCode ^= 0x55555555;
if (fill ) hashCode ^= 0xAAAAAAAA;
return hashCode;
}
public static class Arc extends ArrowheadShape {
public final float cx, cy, rx, ry;
public final float start, extent;
public final ArcType type;
public final EndCap endCap;
public final LineJoin lineJoin;
public final float miterLimit;
public Arc(
float cx, float cy, float rx, float ry,
float start, float extent, ArcType type,
EndCap endCap, LineJoin lineJoin, float miterLimit,
boolean stroke, boolean fill
) {
super(new Arc2D.Float(
cx-rx, cy-ry, rx+rx, ry+ry, start, extent,
((type != null) ? type.awtValue : Arc2D.OPEN)
), stroke, fill);
this.cx = cx; this.cy = cy; this.rx = rx; this.ry = ry;
this.start = start; this.extent = extent;
this.type = type;
this.endCap = endCap;
this.lineJoin = lineJoin;
this.miterLimit = miterLimit;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
((endCap != null) ? endCap.awtValue : BasicStroke.CAP_SQUARE),
((lineJoin != null) ? lineJoin.awtValue : BasicStroke.JOIN_MITER),
miterLimit
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
return (that instanceof Arc)
&& (this.cx == ((Arc)that).cx)
&& (this.cy == ((Arc)that).cy)
&& (this.rx == ((Arc)that).rx)
&& (this.ry == ((Arc)that).ry)
&& (this.start == ((Arc)that).start)
&& (this.extent == ((Arc)that).extent)
&& (this.type == ((Arc)that).type)
&& (this.endCap == ((Arc)that).endCap)
&& (this.lineJoin == ((Arc)that).lineJoin)
&& (this.miterLimit == ((Arc)that).miterLimit);
}
@Override
protected int hashCodeImpl() {
return Float.floatToIntBits(cx + cy + rx + ry + start + extent);
}
}
public static class Circle extends ArrowheadShape {
public final float cx, cy, r;
public Circle(float cx, float cy, float r, boolean stroke, boolean fill) {
super(new Ellipse2D.Float(cx-r, cy-r, r+r, r+r), stroke, fill);
this.cx = cx; this.cy = cy; this.r = r;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(lineWidth);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
return (that instanceof Circle)
&& (this.cx == ((Circle)that).cx)
&& (this.cy == ((Circle)that).cy)
&& (this.r == ((Circle)that).r);
}
@Override
protected int hashCodeImpl() {
return Float.floatToIntBits(cx + cy + r);
}
}
public static class Ellipse extends ArrowheadShape {
public final float cx, cy, rx, ry;
public Ellipse(float cx, float cy, float rx, float ry, boolean stroke, boolean fill) {
super(new Ellipse2D.Float(cx-rx, cy-ry, rx+rx, ry+ry), stroke, fill);
this.cx = cx; this.cy = cy; this.rx = rx; this.ry = ry;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(lineWidth);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
return (that instanceof Ellipse)
&& (this.cx == ((Ellipse)that).cx)
&& (this.cy == ((Ellipse)that).cy)
&& (this.rx == ((Ellipse)that).rx)
&& (this.ry == ((Ellipse)that).ry);
}
@Override
protected int hashCodeImpl() {
return Float.floatToIntBits(cx + cy + rx + ry);
}
}
public static class Line extends ArrowheadShape {
public final float x1, y1, x2, y2;
public final EndCap endCap;
public Line(float x1, float y1, float x2, float y2, EndCap endCap, boolean stroke, boolean fill) {
super(new Line2D.Float(x1, y1, x2, y2), stroke, fill);
this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2;
this.endCap = endCap;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
((endCap != null) ? endCap.awtValue : BasicStroke.CAP_SQUARE),
BasicStroke.JOIN_MITER,
10.0f
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
return (that instanceof Line)
&& (this.x1 == ((Line)that).x1)
&& (this.y1 == ((Line)that).y1)
&& (this.x2 == ((Line)that).x2)
&& (this.y2 == ((Line)that).y2)
&& (this.endCap == ((Line)that).endCap);
}
@Override
protected int hashCodeImpl() {
return Float.floatToIntBits(x1 + y1 + x2 + y2);
}
}
public static class Path extends ArrowheadShape {
public final String d;
public final EndCap endCap;
public final LineJoin lineJoin;
public final float miterLimit;
public Path(String d, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
this(parseInstructions(d), endCap, lineJoin, miterLimit, stroke, fill);
}
// Do not try this at home.
private Path(Object[] o, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
super((GeneralPath)o[1], stroke, fill);
this.d = (String)o[0];
this.endCap = endCap;
this.lineJoin = lineJoin;
this.miterLimit = miterLimit;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
((endCap != null) ? endCap.awtValue : BasicStroke.CAP_SQUARE),
((lineJoin != null) ? lineJoin.awtValue : BasicStroke.JOIN_MITER),
miterLimit
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
if (that instanceof Path) {
if (!this.d.equals(((Path)that).d)) return false;
if (this.endCap != ((Path)that).endCap) return false;
if (this.lineJoin != ((Path)that).lineJoin) return false;
if (this.miterLimit != ((Path)that).miterLimit) return false;
return true;
} else {
return false;
}
}
@Override
protected int hashCodeImpl() {
return this.d.hashCode();
}
}
public static class Polygon extends ArrowheadShape {
public final float[] points;
public final EndCap endCap;
public final LineJoin lineJoin;
public final float miterLimit;
public Polygon(float[] points, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
super(makeShape(points), stroke, fill);
this.points = points;
this.endCap = endCap;
this.lineJoin = lineJoin;
this.miterLimit = miterLimit;
}
public Polygon(String points, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
this(parseFloats(points), endCap, lineJoin, miterLimit, stroke, fill);
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
((endCap != null) ? endCap.awtValue : BasicStroke.CAP_SQUARE),
((lineJoin != null) ? lineJoin.awtValue : BasicStroke.JOIN_MITER),
miterLimit
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
if (that instanceof Polygon) {
float[] thisPoints = this.points;
float[] thatPoints = ((Polygon)that).points;
if (thisPoints.length != thatPoints.length) return false;
for (int i = 0; i < thisPoints.length; i++) {
if (thisPoints[i] != thatPoints[i]) {
return false;
}
}
if (this.endCap != ((Polygon)that).endCap) return false;
if (this.lineJoin != ((Polygon)that).lineJoin) return false;
if (this.miterLimit != ((Polygon)that).miterLimit) return false;
return true;
} else {
return false;
}
}
@Override
protected int hashCodeImpl() {
float f = 0.0f;
for (float point : points) f += point;
return Float.floatToIntBits(f);
}
private static GeneralPath makeShape(float[] points) {
GeneralPath p = new GeneralPath();
if (points.length >= 2) {
p.moveTo(points[0], points[1]);
for (int i = 2, j = 1, n = points.length / 2; j < n; j++) {
float x = points[i++];
float y = points[i++];
p.lineTo(x, y);
}
p.closePath();
}
return p;
}
}
public static class PolyLine extends ArrowheadShape {
public final float[] points;
public final EndCap endCap;
public final LineJoin lineJoin;
public final float miterLimit;
public PolyLine(float[] points, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
super(makeShape(points), stroke, fill);
this.points = points;
this.endCap = endCap;
this.lineJoin = lineJoin;
this.miterLimit = miterLimit;
}
public PolyLine(String points, EndCap endCap, LineJoin lineJoin, float miterLimit, boolean stroke, boolean fill) {
this(parseFloats(points), endCap, lineJoin, miterLimit, stroke, fill);
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
((endCap != null) ? endCap.awtValue : BasicStroke.CAP_SQUARE),
((lineJoin != null) ? lineJoin.awtValue : BasicStroke.JOIN_MITER),
miterLimit
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
if (that instanceof PolyLine) {
float[] thisPoints = this.points;
float[] thatPoints = ((PolyLine)that).points;
if (thisPoints.length != thatPoints.length) return false;
for (int i = 0; i < thisPoints.length; i++) {
if (thisPoints[i] != thatPoints[i]) {
return false;
}
}
if (this.endCap != ((PolyLine)that).endCap) return false;
if (this.lineJoin != ((PolyLine)that).lineJoin) return false;
if (this.miterLimit != ((PolyLine)that).miterLimit) return false;
return true;
} else {
return false;
}
}
@Override
protected int hashCodeImpl() {
float f = 0.0f;
for (float point : points) f += point;
return Float.floatToIntBits(f);
}
private static GeneralPath makeShape(float[] points) {
GeneralPath p = new GeneralPath();
if (points.length >= 2) {
p.moveTo(points[0], points[1]);
for (int i = 2, j = 1, n = points.length / 2; j < n; j++) {
float x = points[i++];
float y = points[i++];
p.lineTo(x, y);
}
}
return p;
}
}
public static class Rect extends ArrowheadShape {
public final float x, y, width, height, rx, ry;
public final LineJoin lineJoin;
public final float miterLimit;
public Rect(
float x, float y, float width, float height,
float rx, float ry, LineJoin lineJoin, float miterLimit,
boolean stroke, boolean fill
) {
super(
(rx == 0.0f && ry == 0.0f) ?
new Rectangle2D.Float(x, y, width, height) :
new RoundRectangle2D.Float(x, y, width, height, rx, ry),
stroke, fill
);
this.x = x; this.y = y;
this.width = width; this.height = height;
this.rx = rx; this.ry = ry;
this.lineJoin = lineJoin;
this.miterLimit = miterLimit;
}
@Override
public Stroke getStroke(float lineWidth) {
return new BasicStroke(
lineWidth,
BasicStroke.CAP_SQUARE,
((lineJoin != null) ? lineJoin.awtValue : BasicStroke.JOIN_MITER),
miterLimit
);
}
@Override
protected boolean equalsImpl(ArrowheadShape that) {
return (that instanceof Rect)
&& (this.x == ((Rect)that).x)
&& (this.y == ((Rect)that).y)
&& (this.width == ((Rect)that).width)
&& (this.height == ((Rect)that).height)
&& (this.rx == ((Rect)that).rx)
&& (this.ry == ((Rect)that).ry)
&& (this.lineJoin == ((Rect)that).lineJoin)
&& (this.miterLimit == ((Rect)that).miterLimit);
}
@Override
protected int hashCodeImpl() {
return Float.floatToIntBits(x + y + width + height + rx + ry);
}
}
@Override
public final boolean contains(Point2D p) {
return awtShape.contains(p);
}
@Override
public final boolean contains(Rectangle2D r) {
return awtShape.contains(r);
}
@Override
public final boolean contains(double x, double y) {
return awtShape.contains(x, y);
}
@Override
public final boolean contains(double x, double y, double w, double h) {
return awtShape.contains(x, y, w, h);
}
@Override
public final Rectangle getBounds() {
return awtShape.getBounds();
}
@Override
public final Rectangle2D getBounds2D() {
return awtShape.getBounds2D();
}
@Override
public final PathIterator getPathIterator(AffineTransform at) {
return awtShape.getPathIterator(at);
}
@Override
public final PathIterator getPathIterator(AffineTransform at, double flatness) {
return awtShape.getPathIterator(at, flatness);
}
@Override
public final boolean intersects(Rectangle2D r) {
return awtShape.intersects(r);
}
@Override
public final boolean intersects(double x, double y, double w, double h) {
return awtShape.intersects(x, y, w, h);
}
private static final Pattern NUMBER_PATTERN = Pattern.compile("([+-]?)([0-9]+([.][0-9]*)?|[.][0-9]+)");
private static float[] parseFloats(String s) {
List<Float> floats = new ArrayList<Float>();
Matcher m = NUMBER_PATTERN.matcher(s);
while (m.find()) {
try {
float f = Float.parseFloat(m.group());
floats.add(f);
} catch (NumberFormatException nfe) {
// ignored
}
}
int i = 0, n = floats.size();
float[] a = new float[n];
for (float f : floats) a[i++] = f;
return a;
}
private static final Pattern INSTRUCTION_PATTERN = Pattern.compile("[A-Za-z]|([+-]?)([0-9]+([.][0-9]*)?|[.][0-9]+)");
private static Object[] parseInstructions(String s) {
List<String> instructions = new ArrayList<String>();
GeneralPath p = new GeneralPath();
float lcx = 0.0f, lcy = 0.0f, lx = 0.0f, ly = 0.0f;
float ccx, ccy, arx, ary, aa;
boolean large, sweep;
float rx, ry, rw, rh, rrx, rry, ras, rae;
int rat;
Matcher m = INSTRUCTION_PATTERN.matcher(s);
while (m.find()) {
String inst = m.group();
switch (inst.charAt(0)) {
case 'M':
instructions.add("M");
instructions.add(Float.toString(lcx = lx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly = parseInstructionFloat(m)));
p.moveTo(lx, ly);
break;
case 'm':
instructions.add("M");
instructions.add(Float.toString(lcx = lx += parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly += parseInstructionFloat(m)));
p.moveTo(lx, ly);
break;
case 'H':
instructions.add("H");
instructions.add(Float.toString(lcx = lx = parseInstructionFloat(m)));
p.lineTo(lx, lcy = ly);
break;
case 'h':
instructions.add("H");
instructions.add(Float.toString(lcx = lx += parseInstructionFloat(m)));
p.lineTo(lx, lcy = ly);
break;
case 'V':
instructions.add("V");
instructions.add(Float.toString(lcy = ly = parseInstructionFloat(m)));
p.lineTo(lcx = lx, ly);
break;
case 'v':
instructions.add("V");
instructions.add(Float.toString(lcy = ly += parseInstructionFloat(m)));
p.lineTo(lcx = lx, ly);
break;
case 'L':
instructions.add("L");
instructions.add(Float.toString(lcx = lx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly = parseInstructionFloat(m)));
p.lineTo(lx, ly);
break;
case 'l':
instructions.add("L");
instructions.add(Float.toString(lcx = lx += parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly += parseInstructionFloat(m)));
p.lineTo(lx, ly);
break;
case 'Q':
instructions.add("Q");
instructions.add(Float.toString(lcx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = parseInstructionFloat(m)));
instructions.add(Float.toString(lx = parseInstructionFloat(m)));
instructions.add(Float.toString(ly = parseInstructionFloat(m)));
p.quadTo(lcx, lcy, lx, ly);
break;
case 'q':
instructions.add("Q");
instructions.add(Float.toString(lcx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(lx += parseInstructionFloat(m)));
instructions.add(Float.toString(ly += parseInstructionFloat(m)));
p.quadTo(lcx, lcy, lx, ly);
break;
case 'T':
instructions.add("T");
lcx = lx + lx - lcx;
lcy = ly + ly - lcy;
instructions.add(Float.toString(lx = parseInstructionFloat(m)));
instructions.add(Float.toString(ly = parseInstructionFloat(m)));
p.quadTo(lcx, lcy, lx, ly);
break;
case 't':
instructions.add("T");
lcx = lx + lx - lcx;
lcy = ly + ly - lcy;
instructions.add(Float.toString(lx += parseInstructionFloat(m)));
instructions.add(Float.toString(ly += parseInstructionFloat(m)));
p.quadTo(lcx, lcy, lx, ly);
break;
case 'C':
instructions.add("C");
instructions.add(Float.toString(ccx = parseInstructionFloat(m)));
instructions.add(Float.toString(ccy = parseInstructionFloat(m)));
instructions.add(Float.toString(lcx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = parseInstructionFloat(m)));
instructions.add(Float.toString(lx = parseInstructionFloat(m)));
instructions.add(Float.toString(ly = parseInstructionFloat(m)));
p.curveTo(ccx, ccy, lcx, lcy, lx, ly);
break;
case 'c':
instructions.add("C");
instructions.add(Float.toString(ccx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(ccy = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(lcx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(lx += parseInstructionFloat(m)));
instructions.add(Float.toString(ly += parseInstructionFloat(m)));
p.curveTo(ccx, ccy, lcx, lcy, lx, ly);
break;
case 'S':
instructions.add("S");
ccx = lx + lx - lcx;
ccy = ly + ly - lcy;
instructions.add(Float.toString(lcx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = parseInstructionFloat(m)));
instructions.add(Float.toString(lx = parseInstructionFloat(m)));
instructions.add(Float.toString(ly = parseInstructionFloat(m)));
p.curveTo(ccx, ccy, lcx, lcy, lx, ly);
break;
case 's':
instructions.add("S");
ccx = lx + lx - lcx;
ccy = ly + ly - lcy;
instructions.add(Float.toString(lcx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(lx += parseInstructionFloat(m)));
instructions.add(Float.toString(ly += parseInstructionFloat(m)));
p.curveTo(ccx, ccy, lcx, lcy, lx, ly);
break;
case 'A':
instructions.add("A");
instructions.add(Float.toString(arx = parseInstructionFloat(m)));
instructions.add(Float.toString(ary = parseInstructionFloat(m)));
instructions.add(Float.toString(aa = parseInstructionFloat(m)));
instructions.add((large = (parseInstructionFloat(m) != 0)) ? "1" : "0");
instructions.add((sweep = (parseInstructionFloat(m) != 0)) ? "1" : "0");
instructions.add(Float.toString(lcx = lx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly = parseInstructionFloat(m)));
arcTo(p, arx, ary, aa, large, sweep, lx, ly);
break;
case 'a':
instructions.add("A");
instructions.add(Float.toString(arx = parseInstructionFloat(m)));
instructions.add(Float.toString(ary = parseInstructionFloat(m)));
instructions.add(Float.toString(aa = parseInstructionFloat(m)));
instructions.add((large = (parseInstructionFloat(m) != 0)) ? "1" : "0");
instructions.add((sweep = (parseInstructionFloat(m) != 0)) ? "1" : "0");
instructions.add(Float.toString(lcx = lx += parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly += parseInstructionFloat(m)));
arcTo(p, arx, ary, aa, large, sweep, lx, ly);
break;
case 'Z':
case 'z':
instructions.add("Z");
p.closePath();
lcx = lx = (float)p.getCurrentPoint().getX();
lcy = ly = (float)p.getCurrentPoint().getY();
break;
case 'G':
instructions.add("G");
instructions.add(Float.toString(ccx = parseInstructionFloat(m)));
instructions.add(Float.toString(ccy = parseInstructionFloat(m)));
instructions.add(Float.toString(lcx = lx = parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly = parseInstructionFloat(m)));
coArcTo(p, ccx, ccy, lx, ly);
break;
case 'g':
instructions.add("G");
instructions.add(Float.toString(ccx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(ccy = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(lcx = lx += parseInstructionFloat(m)));
instructions.add(Float.toString(lcy = ly += parseInstructionFloat(m)));
coArcTo(p, ccx, ccy, lx, ly);
break;
case 'R':
instructions.add("R");
instructions.add(Float.toString(rx = parseInstructionFloat(m)));
instructions.add(Float.toString(ry = parseInstructionFloat(m)));
instructions.add(Float.toString(rw = parseInstructionFloat(m)));
instructions.add(Float.toString(rh = parseInstructionFloat(m)));
instructions.add(Float.toString(rrx = parseInstructionFloat(m)));
instructions.add(Float.toString(rry = parseInstructionFloat(m)));
if (rrx == 0 || rry == 0) {
p.append(new Rectangle2D.Float(rx, ry, rw, rh), false);
} else {
p.append(new RoundRectangle2D.Float(rx, ry, rw, rh, rrx, rry), false);
}
p.moveTo(lcx = lx, lcy = ly);
break;
case 'r':
instructions.add("R");
instructions.add(Float.toString(rx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(ry = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(rw = parseInstructionFloat(m)));
instructions.add(Float.toString(rh = parseInstructionFloat(m)));
instructions.add(Float.toString(rrx = parseInstructionFloat(m)));
instructions.add(Float.toString(rry = parseInstructionFloat(m)));
if (rrx == 0 || rry == 0) {
p.append(new Rectangle2D.Float(rx, ry, rw, rh), false);
} else {
p.append(new RoundRectangle2D.Float(rx, ry, rw, rh, rrx, rry), false);
}
p.moveTo(lcx = lx, lcy = ly);
break;
case 'E':
instructions.add("E");
instructions.add(Float.toString(rx = parseInstructionFloat(m)));
instructions.add(Float.toString(ry = parseInstructionFloat(m)));
instructions.add(Float.toString(rw = parseInstructionFloat(m)));
instructions.add(Float.toString(rh = parseInstructionFloat(m)));
instructions.add(Float.toString(ras = parseInstructionFloat(m)));
instructions.add(Float.toString(rae = parseInstructionFloat(m)));
instructions.add(Integer.toString(rat = (Math.abs((int)Math.round(parseInstructionFloat(m))) % 5)));
if (rae <= -360 || rae >= 360) {
p.append(new Ellipse2D.Float(rx, ry, rw, rh), false);
p.moveTo(lcx = lx, lcy = ly);
} else if (rat < 3) {
p.append(new Arc2D.Float(rx, ry, rw, rh, ras, rae, rat), false);
p.moveTo(lcx = lx, lcy = ly);
} else {
p.append(new Arc2D.Float(rx, ry, rw, rh, ras, rae, Arc2D.OPEN), rat > 3);
Point2D cp = p.getCurrentPoint();
lcx = lx = (float)cp.getX();
lcy = ly = (float)cp.getY();
}
break;
case 'e':
instructions.add("E");
instructions.add(Float.toString(rx = lx + parseInstructionFloat(m)));
instructions.add(Float.toString(ry = ly + parseInstructionFloat(m)));
instructions.add(Float.toString(rw = parseInstructionFloat(m)));
instructions.add(Float.toString(rh = parseInstructionFloat(m)));
instructions.add(Float.toString(ras = parseInstructionFloat(m)));
instructions.add(Float.toString(rae = parseInstructionFloat(m)));
instructions.add(Integer.toString(rat = (Math.abs((int)Math.round(parseInstructionFloat(m))) % 5)));
if (rae <= -360 || rae >= 360) {
p.append(new Ellipse2D.Float(rx, ry, rw, rh), false);
p.moveTo(lcx = lx, lcy = ly);
} else if (rat < 3) {
p.append(new Arc2D.Float(rx, ry, rw, rh, ras, rae, rat), false);
p.moveTo(lcx = lx, lcy = ly);
} else {
p.append(new Arc2D.Float(rx, ry, rw, rh, ras, rae, Arc2D.OPEN), rat > 3);
Point2D cp = p.getCurrentPoint();
lcx = lx = (float)cp.getX();
lcy = ly = (float)cp.getY();
}
break;
}
}
StringBuffer sb = new StringBuffer();
Iterator<String> ii = instructions.iterator();
if (ii.hasNext()) {
sb.append(ii.next());
}
while (ii.hasNext()) {
sb.append(" ");
sb.append(ii.next());
}
s = sb.toString();
return new Object[]{s,p};
}
private static float parseInstructionFloat(Matcher m) {
if (m.find()) {
try {
return Float.parseFloat(m.group());
} catch (NumberFormatException nfe) {
return 0.0f;
}
} else {
return 0.0f;
}
}
private static void arcTo(
GeneralPath p, double rx, double ry, double a,
boolean large, boolean sweep, double x, double y
) {
Point2D p0 = p.getCurrentPoint();
double x0 = p0.getX();
double y0 = p0.getY();
if (x0 == x && y0 == y) return;
if (rx == 0 || ry == 0) { p.lineTo(x, y); return; }
double dx2 = (x0 - x) / 2;
double dy2 = (y0 - y) / 2;
a = Math.toRadians(a % 360);
double ca = Math.cos(a);
double sa = Math.sin(a);
double x1 = sa * dy2 + ca * dx2;
double y1 = ca * dy2 - sa * dx2;
rx = Math.abs(rx);
ry = Math.abs(ry);
double Prx = rx * rx;
double Pry = ry * ry;
double Px1 = x1 * x1;
double Py1 = y1 * y1;
double rc = Px1/Prx + Py1/Pry;
if (rc > 1) {
rx = Math.sqrt(rc) * rx;
ry = Math.sqrt(rc) * ry;
Prx = rx * rx;
Pry = ry * ry;
}
double s = (large == sweep) ? -1 : 1;
double sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
if (sq < 0) sq = 0;
double m = s * Math.sqrt(sq);
double cx1 = m * ((rx * y1) / ry);
double cy1 = m * -((ry * x1) / rx);
double sx2 = (x0 + x) / 2;
double sy2 = (y0 + y) / 2;
double cx = sx2 + ca * cx1 - sa * cy1;
double cy = sy2 + sa * cx1 + ca * cy1;
double ux = (x1 - cx1) / rx;
double uy = (y1 - cy1) / ry;
double vx = (-x1 -cx1) / rx;
double vy = (-y1 -cy1) / ry;
double sn = Math.sqrt(ux*ux + uy*uy);
double sp = ux;
double ss = (uy < 0) ? -1 : 1;
double as = Math.toDegrees(ss * Math.acos(sp / sn));
double en = Math.sqrt((ux*ux + uy*uy) * (vx*vx + vy*vy));
double ep = ux * vx + uy * vy;
double es = (ux * vy - uy * vx < 0) ? -1 : 1;
double ae = Math.toDegrees(es * Math.acos(ep / en));
if (!sweep && ae > 0) ae -= 360;
if (sweep && ae < 0) ae += 360;
ae %= 360;
as %= 360;
Arc2D.Double arc = new Arc2D.Double();
arc.x = cx - rx;
arc.y = cy - ry;
arc.width = rx * 2;
arc.height = ry * 2;
arc.start = -as;
arc.extent = -ae;
double acx = arc.getCenterX();
double acy = arc.getCenterY();
AffineTransform t = AffineTransform.getRotateInstance(a, acx, acy);
p.append(t.createTransformedShape(arc), true);
}
private static void coArcTo(GeneralPath p, double x2, double y2, double x3, double y3) {
Point2D p1 = p.getCurrentPoint();
double x1 = p1.getX();
double y1 = p1.getY();
boolean xe = (x1 == x2 && x2 == x3);
boolean ye = (y1 == y2 && y2 == y3);
if (xe && ye) return;
if (xe || ye) { p.lineTo(x3, y3); return; }
double d = arcHK(x1, y1, x2, y2, x3, y3);
double h = arcH(x1, y1, x2, y2, x3, y3) / d;
double k = arcK(x1, y1, x2, y2, x3, y3) / d;
if (Double.isNaN(h) || Double.isInfinite(h)) { p.lineTo(x3, y3); return; }
if (Double.isNaN(k) || Double.isInfinite(k)) { p.lineTo(x3, y3); return; }
double r = Math.hypot(k - y1, x1 - h);
double a1 = Math.toDegrees(Math.atan2(k - y1, x1 - h));
double a2 = Math.toDegrees(Math.atan2(k - y2, x2 - h));
double a3 = Math.toDegrees(Math.atan2(k - y3, x3 - h));
Arc2D.Double arc = new Arc2D.Double();
arc.x = h - r;
arc.y = k - r;
arc.width = r + r;
arc.height = r + r;
arc.start = a1;
if ((a1 <= a2 && a2 <= a3) || (a3 <= a2 && a2 <= a1)) {
arc.extent = a3 - a1;
} else if (a3 <= a1) {
arc.extent = a3 - a1 + 360;
} else {
arc.extent = a3 - a1 - 360;
}
p.append(arc, true);
}
private static double arcdet(double a, double b, double c, double d, double e, double f, double g, double h, double i) {
return a*e*i + b*f*g + c*d*h - a*f*h - b*d*i - c*e*g;
}
private static double arcHK(double x1, double y1, double x2, double y2, double x3, double y3) {
return arcdet(x1, y1, 1, x2, y2, 1, x3, y3, 1) * 2;
}
private static double arcH(double x1, double y1, double x2, double y2, double x3, double y3) {
return arcdet(x1*x1 + y1*y1, y1, 1, x2*x2 + y2*y2, y2, 1, x3*x3 + y3*y3, y3, 1);
}
private static double arcK(double x1, double y1, double x2, double y2, double x3, double y3) {
return arcdet(x1, x1*x1 + y1*y1, 1, x2, x2*x2 + y2*y2, 1, x3, x3*x3 + y3*y3, 1);
}
}