/** * Returns a path which is cappedPath at the ends, to prevent it from drawing under the end caps. */ protected BezierPath getCappedPath() { if (cappedPath == null) { cappedPath = path.clone(); if (isClosed()) { cappedPath.setClosed(true); } else { if (cappedPath.size() > 1) { if (get(START_DECORATION) != null) { BezierPath.Node p0 = cappedPath.get(0); BezierPath.Node p1 = cappedPath.get(1); Point2D.Double pp; if ((p0.getMask() & BezierPath.C2_MASK) != 0) { pp = p0.getControlPoint(2); } else if ((p1.getMask() & BezierPath.C1_MASK) != 0) { pp = p1.getControlPoint(1); } else { pp = p1.getControlPoint(0); } double radius = get(START_DECORATION).getDecorationRadius(this); double lineLength = Geom.length(p0.getControlPoint(0), pp); cappedPath.set( 0, 0, Geom.cap(pp, p0.getControlPoint(0), -Math.min(radius, lineLength))); } if (get(END_DECORATION) != null) { BezierPath.Node p0 = cappedPath.get(cappedPath.size() - 1); BezierPath.Node p1 = cappedPath.get(cappedPath.size() - 2); Point2D.Double pp; if ((p0.getMask() & BezierPath.C1_MASK) != 0) { pp = p0.getControlPoint(1); } else if ((p1.getMask() & BezierPath.C2_MASK) != 0) { pp = p1.getControlPoint(2); } else { pp = p1.getControlPoint(0); } double radius = get(END_DECORATION).getDecorationRadius(this); double lineLength = Geom.length(p0.getControlPoint(0), pp); cappedPath.set( cappedPath.size() - 1, 0, Geom.cap(pp, p0.getControlPoint(0), -Math.min(radius, lineLength))); } cappedPath.invalidatePath(); } } } return cappedPath; }
/** * Finds a control point index. Returns -1 if no control point could be found. FIXME - Move this * to BezierPath */ public int findNode(Point2D.Double p) { BezierPath tp = path; for (int i = 0; i < tp.size(); i++) { BezierPath.Node p2 = tp.get(i); if (p2.x[0] == p.x && p2.y[0] == p.y) { return i; } } return -1; }
@Override public boolean contains(Point2D.Double p) { double tolerance = Math.max(2f, AttributeKeys.getStrokeTotalWidth(this) / 2d); if (isClosed() || get(FILL_COLOR) != null && get(UNCLOSED_PATH_FILLED)) { if (path.contains(p)) { return true; } double grow = AttributeKeys.getPerpendicularHitGrowth(this) * 2d; GrowStroke gs = new GrowStroke(grow, AttributeKeys.getStrokeTotalWidth(this) * get(STROKE_MITER_LIMIT)); if (gs.createStrokedShape(path).contains(p)) { return true; } else { if (isClosed()) { return false; } } } if (!isClosed()) { if (getCappedPath().outlineContains(p, tolerance)) { return true; } if (get(START_DECORATION) != null) { BezierPath cp = getCappedPath(); Point2D.Double p1 = path.get(0, 0); Point2D.Double p2 = cp.get(0, 0); // FIXME - Check here, if caps path contains the point if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, p.x, p.y, tolerance)) { return true; } } if (get(END_DECORATION) != null) { BezierPath cp = getCappedPath(); Point2D.Double p1 = path.get(path.size() - 1, 0); Point2D.Double p2 = cp.get(path.size() - 1, 0); // FIXME - Check here, if caps path contains the point if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, p.x, p.y, tolerance)) { return true; } } } return false; }
protected void drawCaps(Graphics2D g) { if (getNodeCount() > 1) { if (get(START_DECORATION) != null) { BezierPath cp = getCappedPath(); Point2D.Double p1 = path.get(0, 0); Point2D.Double p2 = cp.get(0, 0); if (p2.equals(p1)) { p2 = path.get(1, 0); } get(START_DECORATION).draw(g, this, p1, p2); } if (get(END_DECORATION) != null) { BezierPath cp = getCappedPath(); Point2D.Double p1 = path.get(path.size() - 1, 0); Point2D.Double p2 = cp.get(path.size() - 1, 0); if (p2.equals(p1)) { p2 = path.get(path.size() - 2, 0); } get(END_DECORATION).draw(g, this, p1, p2); } } }
@Override public Collection<Handle> createHandles(int detailLevel) { LinkedList<Handle> handles = new LinkedList<Handle>(); switch (detailLevel % 2) { case -1: // Mouse hover handles handles.add(new BezierOutlineHandle(this, true)); break; case 0: handles.add(new BezierOutlineHandle(this)); for (int i = 0, n = path.size(); i < n; i++) { handles.add(new BezierNodeHandle(this, i)); } break; case 1: TransformHandleKit.addTransformHandles(this, handles); handles.add(new BezierScaleHandle(this)); break; } return handles; }
public static List<BezierPath> fromPathData(String str) throws IOException { LinkedList<BezierPath> paths = new LinkedList<BezierPath>(); BezierPath path = null; Point2D.Double p = new Point2D.Double(); Point2D.Double c1 = new Point2D.Double(); Point2D.Double c2 = new Point2D.Double(); StreamTokenizer tt = new StreamTokenizer(new StringReader(str)); tt.resetSyntax(); tt.parseNumbers(); tt.whitespaceChars(0, ' '); tt.whitespaceChars(',', ','); char nextCommand = 'M'; char command = 'M'; while (tt.nextToken() != StreamTokenizer.TT_EOF) { if (tt.ttype > 0) { command = (char) tt.ttype; } else { command = nextCommand; tt.pushBack(); } BezierPath.Node node; switch (command) { // moveto case 'M': if (path != null) { paths.add(path); } path = new BezierPath(); if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.moveTo(p.x, p.y); nextCommand = 'L'; break; case 'm': if (path != null) { paths.add(path); } path = new BezierPath(); if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.moveTo(p.x, p.y); nextCommand = 'l'; // close path break; case 'Z': case 'z': p.x = path.get(0).x[0]; p.y = path.get(0).y[0]; path.setClosed(true); // lineto break; case 'L': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.lineTo(p.x, p.y); nextCommand = 'L'; break; case 'l': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.lineTo(p.x, p.y); nextCommand = 'l'; break; case 'H': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; path.lineTo(p.x, p.y); nextCommand = 'H'; break; case 'h': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; path.lineTo(p.x, p.y); nextCommand = 'h'; break; case 'V': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.lineTo(p.x, p.y); nextCommand = 'V'; break; case 'v': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.lineTo(p.x, p.y); nextCommand = 'v'; // curveto break; case 'C': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.y = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.y = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y); nextCommand = 'C'; break; case 'c': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.x = p.x + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.y = p.y + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.x = p.x + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.y = p.y + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y); nextCommand = 'c'; break; case 'S': node = path.get(path.size() - 1); c1.x = node.x[0] * 2d - node.x[1]; c1.y = node.y[0] * 2d - node.y[1]; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.y = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y); nextCommand = 'S'; break; case 's': node = path.get(path.size() - 1); c1.x = node.x[0] * 2d - node.x[1]; c1.y = node.y[0] * 2d - node.y[1]; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.x = p.x + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c2.y = p.y + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y); nextCommand = 's'; // quadto break; case 'Q': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.y = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.quadTo(c1.x, c1.y, p.x, p.y); nextCommand = 'Q'; break; case 'q': if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.x = p.x + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); c1.y = p.y + tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.quadTo(c1.x, c1.y, p.x, p.y); nextCommand = 'q'; break; case 'T': node = path.get(path.size() - 1); c1.x = node.x[0] * 2d - node.x[1]; c1.y = node.y[0] * 2d - node.y[1]; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x = tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y = tt.nval; path.quadTo(c1.x, c1.y, p.x, p.y); nextCommand = 'T'; break; case 't': node = path.get(path.size() - 1); c1.x = node.x[0] * 2d - node.x[1]; c1.y = node.y[0] * 2d - node.y[1]; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.x += tt.nval; if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected"); p.y += tt.nval; path.quadTo(c1.x, c1.y, p.x, p.y); nextCommand = 's'; break; default: throw new IOException("Illegal command: " + command); } } if (path != null) { paths.add(path); } return paths; }
public static String toPathData(BezierPath path) { StringBuilder buf = new StringBuilder(); if (path.size() == 0) { // nothing to do } else if (path.size() == 1) { BezierPath.Node current = path.get(0); buf.append("M "); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); buf.append(" L "); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0] + 1); } else { BezierPath.Node previous; BezierPath.Node current; previous = current = path.get(0); buf.append("M "); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); for (int i = 1, n = path.size(); i < n; i++) { previous = current; current = path.get(i); if ((previous.mask & BezierPath.C2_MASK) == 0) { if ((current.mask & BezierPath.C1_MASK) == 0) { buf.append(" L "); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } else { buf.append(" Q "); buf.append(current.x[1]); buf.append(' '); buf.append(current.y[1]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } } else { if ((current.mask & BezierPath.C1_MASK) == 0) { buf.append(" Q "); buf.append(current.x[2]); buf.append(' '); buf.append(current.y[2]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } else { buf.append(" C "); buf.append(previous.x[2]); buf.append(' '); buf.append(previous.y[2]); buf.append(' '); buf.append(current.x[1]); buf.append(' '); buf.append(current.y[1]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } } } if (path.isClosed()) { if (path.size() > 1) { previous = path.get(path.size() - 1); current = path.get(0); if ((previous.mask & BezierPath.C2_MASK) == 0) { if ((current.mask & BezierPath.C1_MASK) == 0) { buf.append(" L "); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } else { buf.append(" Q "); buf.append(current.x[1]); buf.append(' '); buf.append(current.y[1]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } } else { if ((current.mask & BezierPath.C1_MASK) == 0) { buf.append(" Q "); buf.append(previous.x[2]); buf.append(' '); buf.append(previous.y[2]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } else { buf.append(" C "); buf.append(previous.x[2]); buf.append(' '); buf.append(previous.y[2]); buf.append(' '); buf.append(current.x[1]); buf.append(' '); buf.append(current.y[1]); buf.append(' '); buf.append(current.x[0]); buf.append(' '); buf.append(current.y[0]); } } } buf.append(" Z"); } } return buf.toString(); }
/** Gets the node count. */ public int getNodeCount() { return path.size(); }