/*
* @(#)GeneralPath.java 1.59 03/12/19
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.awt.geom;
import java.awt.Shape;
import sun.awt.geom.Curve;
import sun.awt.geom.Crossings;
/**
* The GeneralPath
class represents a geometric path
* constructed from straight lines, and quadratic and cubic
* (Bézier) curves. It can contain multiple subpaths.
*
* The winding rule specifies how the interior of a path is * determined. There are two types of winding rules: * EVEN_ODD and NON_ZERO. *
* An EVEN_ODD winding rule means that enclosed regions * of the path alternate between interior and exterior areas as * traversed from the outside of the path towards a point inside * the region. *
* A NON_ZERO winding rule means that if a ray is
* drawn in any direction from a given point to infinity
* and the places where the path intersects
* the ray are examined, the point is inside of the path if and only if
* the number of times that the path crosses the ray from
* left to right does not equal the number of times that the path crosses
* the ray from right to left.
* @version 1.59, 12/19/03
* @author Jim Graham
*/
public final class GeneralPath implements Shape, Cloneable {
/**
* An even-odd winding rule for determining the interior of
* a path.
*/
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
/**
* A non-zero winding rule for determining the interior of a
* path.
*/
public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
// For code simplicity, copy these constants to our namespace
// and cast them to byte constants for easy storage.
private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO;
private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO;
private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO;
private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO;
private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE;
byte[] pointTypes;
float[] pointCoords;
int numTypes;
int numCoords;
int windingRule;
static final int INIT_SIZE = 20;
static final int EXPAND_MAX = 500;
/**
* Constructs a new GeneralPath
object.
* If an operation performed on this path requires the
* interior of the path to be defined then the default NON_ZERO
* winding rule is used.
* @see #WIND_NON_ZERO
*/
public GeneralPath() {
this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
}
/**
* Constructs a new GeneralPath
object with the specified
* winding rule to control operations that require the interior of the
* path to be defined.
* @param rule the winding rule
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
*/
public GeneralPath(int rule) {
this(rule, INIT_SIZE, INIT_SIZE);
}
/**
* Constructs a new GeneralPath
object with the specified
* winding rule and the specified initial capacity to store path
* coordinates. This number is an initial guess as to how many path
* segments are in the path, but the storage is expanded
* as needed to store whatever path segments are added to this path.
* @param rule the winding rule
* @param initialCapacity the estimate for the number of path segments
* in the path
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
*/
public GeneralPath(int rule, int initialCapacity) {
this(rule, initialCapacity, initialCapacity);
}
/**
* Constructs a new GeneralPath
object with the specified
* winding rule and the specified initial capacities to store point types
* and coordinates.
* These numbers are an initial guess as to how many path segments
* and how many points are to be in the path, but the
* storage is expanded as needed to store whatever path segments are
* added to this path.
* @param rule the winding rule
* @param initialTypes the estimate for the number of path segments
* in the path
* @param initialCapacity the estimate for the number of points
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
*/
GeneralPath(int rule, int initialTypes, int initialCoords) {
setWindingRule(rule);
pointTypes = new byte[initialTypes];
pointCoords = new float[initialCoords * 2];
}
/**
* Constructs a new GeneralPath
object from an arbitrary
* {@link Shape} object.
* All of the initial geometry and the winding rule for this path are
* taken from the specified Shape
object.
* @param s the specified Shape
object
*/
public GeneralPath(Shape s) {
this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
PathIterator pi = s.getPathIterator(null);
setWindingRule(pi.getWindingRule());
append(pi, false);
}
private void needRoom(int newTypes, int newCoords, boolean needMove) {
if (needMove && numTypes == 0) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
int size = pointCoords.length;
if (numCoords + newCoords > size) {
int grow = size;
if (grow > EXPAND_MAX * 2) {
grow = EXPAND_MAX * 2;
}
if (grow < newCoords) {
grow = newCoords;
}
float[] arr = new float[size + grow];
System.arraycopy(pointCoords, 0, arr, 0, numCoords);
pointCoords = arr;
}
size = pointTypes.length;
if (numTypes + newTypes > size) {
int grow = size;
if (grow > EXPAND_MAX) {
grow = EXPAND_MAX;
}
if (grow < newTypes) {
grow = newTypes;
}
byte[] arr = new byte[size + grow];
System.arraycopy(pointTypes, 0, arr, 0, numTypes);
pointTypes = arr;
}
}
/**
* Adds a point to the path by moving to the specified
* coordinates.
* @param x, y the specified coordinates
*/
public synchronized void moveTo(float x, float y) {
if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
pointCoords[numCoords - 2] = x;
pointCoords[numCoords - 1] = y;
} else {
needRoom(1, 2, false);
pointTypes[numTypes++] = SEG_MOVETO;
pointCoords[numCoords++] = x;
pointCoords[numCoords++] = y;
}
}
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new specified coordinates.
* @param x, y the specified coordinates
*/
public synchronized void lineTo(float x, float y) {
needRoom(1, 2, true);
pointTypes[numTypes++] = SEG_LINETO;
pointCoords[numCoords++] = x;
pointCoords[numCoords++] = y;
}
/**
* Adds a curved segment, defined by two new points, to the path by
* drawing a Quadratic curve that intersects both the current
* coordinates and the coordinates (x2, y2), using the
* specified point (x1, y1) as a quadratic parametric control
* point.
* @param x1, y1 the coordinates of the first quadratic control
* point
* @param x2, y2 the coordinates of the final endpoint
*/
public synchronized void quadTo(float x1, float y1, float x2, float y2) {
needRoom(1, 4, true);
pointTypes[numTypes++] = SEG_QUADTO;
pointCoords[numCoords++] = x1;
pointCoords[numCoords++] = y1;
pointCoords[numCoords++] = x2;
pointCoords[numCoords++] = y2;
}
/**
* Adds a curved segment, defined by three new points, to the path by
* drawing a Bézier curve that intersects both the current
* coordinates and the coordinates (x3, y3), using the
* specified points (x1, y1) and (x2, y2) as
* Bézier control points.
* @param x1, y1 the coordinates of the first Béezier
* control point
* @param x2, y2 the coordinates of the second Bézier
* control point
* @param x3, y3 the coordinates of the final endpoint
*/
public synchronized void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3) {
needRoom(1, 6, true);
pointTypes[numTypes++] = SEG_CUBICTO;
pointCoords[numCoords++] = x1;
pointCoords[numCoords++] = y1;
pointCoords[numCoords++] = x2;
pointCoords[numCoords++] = y2;
pointCoords[numCoords++] = x3;
pointCoords[numCoords++] = y3;
}
/**
* Closes the current subpath by drawing a straight line back to
* the coordinates of the last moveTo
. If the path is already
* closed then this method has no effect.
*/
public synchronized void closePath() {
if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
needRoom(1, 0, true);
pointTypes[numTypes++] = SEG_CLOSE;
}
}
/**
* Appends the geometry of the specified Shape
object to the
* path, possibly connecting the new geometry to the existing path
* segments with a line segment.
* If the connect
parameter is true
and the
* path is not empty then any initial moveTo
in the
* geometry of the appended Shape
* is turned into a lineTo
segment.
* If the destination coordinates of such a connecting lineTo
* segment match the ending coordinates of a currently open
* subpath then the segment is omitted as superfluous.
* The winding rule of the specified Shape
is ignored
* and the appended geometry is governed by the winding
* rule specified for this path.
* @param s the Shape
whose geometry is appended
* to this path
* @param connect a boolean to control whether or not to turn an
* initial moveTo
segment into a lineTo
* segment to connect the new geometry to the existing path
*/
public void append(Shape s, boolean connect) {
PathIterator pi = s.getPathIterator(null);
append(pi,connect);
}
/**
* Appends the geometry of the specified
* {@link PathIterator} object
* to the path, possibly connecting the new geometry to the existing
* path segments with a line segment.
* If the connect
parameter is true
and the
* path is not empty then any initial moveTo
in the
* geometry of the appended Shape
is turned into a
* lineTo
segment.
* If the destination coordinates of such a connecting lineTo
* segment match the ending coordinates of a currently open
* subpath then the segment is omitted as superfluous.
* The winding rule of the specified Shape
is ignored
* and the appended geometry is governed by the winding
* rule specified for this path.
* @param pi the PathIterator
whose geometry is appended to
* this path
* @param connect a boolean to control whether or not to turn an
* initial moveTo
segment into a lineTo
segment
* to connect the new geometry to the existing path
*/
public void append(PathIterator pi, boolean connect) {
float coords[] = new float[6];
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case SEG_MOVETO:
if (!connect || numTypes < 1 || numCoords < 2) {
moveTo(coords[0], coords[1]);
break;
}
if (pointTypes[numTypes - 1] != SEG_CLOSE &&
pointCoords[numCoords - 2] == coords[0] &&
pointCoords[numCoords - 1] == coords[1])
{
// Collapse out initial moveto/lineto
break;
}
// NO BREAK;
case SEG_LINETO:
lineTo(coords[0], coords[1]);
break;
case SEG_QUADTO:
quadTo(coords[0], coords[1],
coords[2], coords[3]);
break;
case SEG_CUBICTO:
curveTo(coords[0], coords[1],
coords[2], coords[3],
coords[4], coords[5]);
break;
case SEG_CLOSE:
closePath();
break;
}
pi.next();
connect = false;
}
}
/**
* Returns the fill style winding rule.
* @return an integer representing the current winding rule.
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
* @see #setWindingRule
*/
public synchronized int getWindingRule() {
return windingRule;
}
/**
* Sets the winding rule for this path to the specified value.
* @param rule an integer representing the specified
* winding rule
* @exception IllegalArgumentException
if
* rule
is not either
* WIND_EVEN_ODD
or
* WIND_NON_ZERO
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
* @see #getWindingRule
*/
public void setWindingRule(int rule) {
if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
throw new IllegalArgumentException("winding rule must be "+
"WIND_EVEN_ODD or "+
"WIND_NON_ZERO");
}
windingRule = rule;
}
/**
* Returns the coordinates most recently added to the end of the path
* as a {@link Point2D} object.
* @return a Point2D
object containing the ending
* coordinates of the path or null
if there are no points
* in the path.
*/
public synchronized Point2D getCurrentPoint() {
if (numTypes < 1 || numCoords < 2) {
return null;
}
int index = numCoords;
if (pointTypes[numTypes - 1] == SEG_CLOSE) {
loop:
for (int i = numTypes - 2; i > 0; i--) {
switch (pointTypes[i]) {
case SEG_MOVETO:
break loop;
case SEG_LINETO:
index -= 2;
break;
case SEG_QUADTO:
index -= 4;
break;
case SEG_CUBICTO:
index -= 6;
break;
case SEG_CLOSE:
break;
}
}
}
return new Point2D.Float(pointCoords[index - 2],
pointCoords[index - 1]);
}
/**
* Resets the path to empty. The append position is set back to the
* beginning of the path and all coordinates and point types are
* forgotten.
*/
public synchronized void reset() {
numTypes = numCoords = 0;
}
/**
* Transforms the geometry of this path using the specified
* {@link AffineTransform}.
* The geometry is transformed in place, which permanently changes the
* boundary defined by this object.
* @param at the AffineTransform
used to transform the area
*/
public void transform(AffineTransform at) {
at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
}
/**
* Returns a new transformed Shape
.
* @param at the AffineTransform
used to transform a
* new Shape
.
* @return a new Shape
, transformed with the specified
* AffineTransform
.
*/
public synchronized Shape createTransformedShape(AffineTransform at) {
GeneralPath gp = (GeneralPath) clone();
if (at != null) {
gp.transform(at);
}
return gp;
}
/**
* Return the bounding box of the path.
* @return a {@link java.awt.Rectangle} object that
* bounds the current path.
*/
public java.awt.Rectangle getBounds() {
return getBounds2D().getBounds();
}
/**
* Returns the bounding box of the path.
* @return a {@link Rectangle2D} object that
* bounds the current path.
*/
public synchronized Rectangle2D getBounds2D() {
float x1, y1, x2, y2;
int i = numCoords;
if (i > 0) {
y1 = y2 = pointCoords[--i];
x1 = x2 = pointCoords[--i];
while (i > 0) {
float y = pointCoords[--i];
float x = pointCoords[--i];
if (x < x1) x1 = x;
if (y < y1) y1 = y;
if (x > x2) x2 = x;
if (y > y2) y2 = y;
}
} else {
x1 = y1 = x2 = y2 = 0.0f;
}
return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
}
/**
* Tests if the specified coordinates are inside the boundary of
* this Shape
.
* @param x, y the specified coordinates
* @return true
if the specified coordinates are inside this
* Shape
; false
otherwise
*/
public boolean contains(double x, double y) {
if (numTypes < 2) {
return false;
}
int cross = Curve.crossingsForPath(getPathIterator(null), x, y);
if (windingRule == WIND_NON_ZERO) {
return (cross != 0);
} else {
return ((cross & 1) != 0);
}
}
/**
* Tests if the specified Point2D
is inside the boundary
* of this Shape
.
* @param p the specified Point2D
* @return true
if this Shape
contains the
* specified Point2D
, false
otherwise.
*/
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
/**
* Tests if the specified rectangular area is inside the boundary of
* this Shape
.
* @param x, y the specified coordinates
* @param w the width of the specified rectangular area
* @param h the height of the specified rectangular area
* @return true
if this Shape
contains
* the specified rectangluar area; false
otherwise.
*/
public boolean contains(double x, double y, double w, double h) {
Crossings c = Crossings.findCrossings(getPathIterator(null),
x, y, x+w, y+h);
return (c != null && c.covers(y, y+h));
}
/**
* Tests if the specified Rectangle2D
* is inside the boundary of this Shape
.
* @param r a specified Rectangle2D
* @return true
if this Shape
bounds the
* specified Rectangle2D
; false
otherwise.
*/
public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* Tests if the interior of this Shape
intersects the
* interior of a specified set of rectangular coordinates.
* @param x, y the specified coordinates
* @param w the width of the specified rectangular coordinates
* @param h the height of the specified rectangular coordinates
* @return true
if this Shape
and the
* interior of the specified set of rectangular coordinates intersect
* each other; false
otherwise.
*/
public boolean intersects(double x, double y, double w, double h) {
Crossings c = Crossings.findCrossings(getPathIterator(null),
x, y, x+w, y+h);
return (c == null || !c.isEmpty());
}
/**
* Tests if the interior of this Shape
intersects the
* interior of a specified Rectangle2D
.
* @param r the specified Rectangle2D
* @return true
if this Shape
and the interior
* of the specified Rectangle2D
intersect each
* other; false
otherwise.
*/
public boolean intersects(Rectangle2D r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* Returns a PathIterator
object that iterates along the
* boundary of this Shape
and provides access to the
* geometry of the outline of this Shape
.
* The iterator for this class is not multi-threaded safe,
* which means that this GeneralPath
class does not
* guarantee that modifications to the geometry of this
* GeneralPath
object do not affect any iterations of
* that geometry that are already in process.
* @param at an AffineTransform
* @return a new PathIterator
that iterates along the
* boundary of this Shape
and provides access to the
* geometry of this Shape
's outline
*/
public PathIterator getPathIterator(AffineTransform at) {
return new GeneralPathIterator(this, at);
}
/**
* Returns a PathIterator
object that iterates along the
* boundary of the flattened Shape
and provides access to the
* geometry of the outline of the Shape
.
* The iterator for this class is not multi-threaded safe,
* which means that this GeneralPath
class does not
* guarantee that modifications to the geometry of this
* GeneralPath
object do not affect any iterations of
* that geometry that are already in process.
* @param at an AffineTransform
* @param flatness the maximum distance that the line segments used to
* approximate the curved segments are allowed to deviate
* from any point on the original curve
* @return a new PathIterator
that iterates along the flattened
* Shape
boundary.
*/
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
/**
* Creates a new object of the same class as this object.
*
* @return a clone of this instance.
* @exception OutOfMemoryError if there is not enough memory.
* @see java.lang.Cloneable
* @since 1.2
*/
public Object clone() {
try {
GeneralPath copy = (GeneralPath) super.clone();
copy.pointTypes = (byte[]) pointTypes.clone();
copy.pointCoords = (float[]) pointCoords.clone();
return copy;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
GeneralPath(int windingRule,
byte[] pointTypes,
int numTypes,
float[] pointCoords,
int numCoords) {
// used to construct from native
this.windingRule = windingRule;
this.pointTypes = pointTypes;
this.numTypes = numTypes;
this.pointCoords = pointCoords;
this.numCoords = numCoords;
}
}