/** * 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 <T> void set(AttributeKey<T> key, T newValue) { if (key == PATH_CLOSED) { path.setClosed((Boolean) newValue); } else if (key == WINDING_RULE) { path.setWindingRule( newValue == AttributeKeys.WindingRule.EVEN_ODD ? Path2D.Double.WIND_EVEN_ODD : Path2D.Double.WIND_NON_ZERO); } super.set(key, newValue); invalidate(); }
public Point2D.Double chop(Point2D.Double p) { if (isClosed()) { double grow = AttributeKeys.getPerpendicularHitGrowth(this); if (grow == 0d) { return path.chop(p); } else { GrowStroke gs = new GrowStroke(grow, AttributeKeys.getStrokeTotalWidth(this) * get(STROKE_MITER_LIMIT)); return Geom.chop(gs.createStrokedShape(path), p); } } else { return path.chop(p); } }
/** * 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; }
/** Sets the point coordinate of control point 0 at the specified node. */ public void setPoint(int index, Point2D.Double p) { BezierPath.Node node = path.get(index); double dx = p.x - node.x[0]; double dy = p.y - node.y[0]; for (int i = 0; i < node.x.length; i++) { node.x[i] += dx; node.y[i] += dy; } invalidate(); }
@Override public Rectangle2D.Double getDrawingArea() { if (cachedDrawingArea == null) { if (get(TRANSFORM) == null) { cachedDrawingArea = path.getBounds2D(); } else { BezierPath p2 = (BezierPath) path.clone(); p2.transform(get(TRANSFORM)); cachedDrawingArea = p2.getBounds2D(); } double strokeTotalWidth = AttributeKeys.getStrokeTotalWidth(this); double width = strokeTotalWidth / 2d; if (get(STROKE_JOIN) == BasicStroke.JOIN_MITER) { width *= get(STROKE_MITER_LIMIT); } else if (get(STROKE_CAP) != BasicStroke.CAP_BUTT) { width += strokeTotalWidth * 2; } Geom.grow(cachedDrawingArea, width, width); } return (Rectangle2D.Double) cachedDrawingArea.clone(); }
@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 readPoints(DOMInput in) throws IOException { path.clear(); in.openElement("points"); setClosed(in.getAttribute("closed", false)); for (int i = 0, n = in.getElementCount("p"); i < n; i++) { in.openElement("p", i); BezierPath.Node node = new BezierPath.Node( in.getAttribute("mask", 0), in.getAttribute("x", 0d), in.getAttribute("y", 0d), in.getAttribute("c1x", in.getAttribute("x", 0d)), in.getAttribute("c1y", in.getAttribute("y", 0d)), in.getAttribute("c2x", in.getAttribute("x", 0d)), in.getAttribute("c2y", in.getAttribute("y", 0d))); node.keepColinear = in.getAttribute("colinear", true); path.add(node); path.invalidatePath(); in.closeElement(); } in.closeElement(); }
@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 Point2D.Double getPointOnPath(float relative, double flatness) { return path.getPointOnPath(relative, flatness); }
@Override public Object getTransformRestoreData() { return path.clone(); }
@Override public void restoreTransformTo(Object geometry) { path.setTo((BezierPath) geometry); }
/** Adds a node to the list of points. */ public void addNode(final int index, BezierPath.Node p) { path.add(index, p); invalidate(); }
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(); }
/** * Joins two segments into one if the given Point2D.Double hits a node of the polyline. * * @return true if the two segments were joined. */ public int joinSegments(Point2D.Double join, float tolerance) { return path.joinSegments(join, tolerance); }
/** Sets the point coordinate of a control point. */ public void setPoint(int index, int coord, Point2D.Double p) { BezierPath.Node cp = new BezierPath.Node(path.get(index)); cp.setControlPoint(coord, p); setNode(index, cp); }
@Override public void transform(AffineTransform tx) { path.transform(tx); invalidate(); }
/** Gets the point coordinate of a control point. */ public Point2D.Double getPoint(int index, int coord) { return path.get(index).getControlPoint(coord); }
/** * Convenience method for getting the point coordinate of the first control point of the specified * node. */ public Point2D.Double getPoint(int index) { return path.get(index).getControlPoint(0); }
/** Gets a control point. */ public BezierPath.Node getNode(int index) { return (BezierPath.Node) path.get(index).clone(); }
/** Sets a control point. */ public void setNode(int index, BezierPath.Node p) { path.set(index, p); invalidate(); }
public Point2D.Double getCenter() { return path.getCenter(); }
public Point2D.Double getOutermostPoint() { return path.get(path.indexOfOutermostNode()).getControlPoint(0); }
/** Removes the Node at the specified index. */ public BezierPath.Node removeNode(int index) { return path.remove(index); }
/** * Splits the segment at the given Point2D.Double if a segment was hit. * * @return the index of the segment or -1 if no segment was hit. */ public int splitSegment(Point2D.Double split, float tolerance) { return path.splitSegment(split, tolerance); }
/** Removes the Point2D.Double at the specified index. */ protected void removeAllNodes() { path.clear(); }
public void setBezierPath(BezierPath newValue) { path = newValue.clone(); this.setClosed(newValue.isClosed()); }
/** Gets the node count. */ public int getNodeCount() { return path.size(); }
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; }
@Override public void invalidate() { super.invalidate(); path.invalidatePath(); cappedPath = null; }