home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-03-20 | 17.0 KB | 530 lines |
- /*
- * @(#)GeneralPath.java 1.36 98/03/18
- *
- * Copyright 1996-1998 by Sun Microsystems, Inc.,
- * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
- * All rights reserved.
- *
- * This software is the confidential and proprietary information
- * of Sun Microsystems, Inc. ("Confidential Information"). You
- * shall not disclose such Confidential Information and shall use
- * it only in accordance with the terms of the license agreement
- * you entered into with Sun.
- */
-
- package java.awt.geom;
-
- import java.awt.Shape;
-
- /**
- * This class represents a geometric path constructed from straight
- * lines, quadratic and cubic (Bezier) curves. It can contain multiple
- * subpaths.
- * <p>
- * The winding rule specifies how the interior of a path will be
- * 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. For a NON_ZERO winding rule, we start by conceptually
- * drawing a ray from a given point to infinity in any direction
- * and then by examining all of the places where the path intersects
- * the ray. We now traverse the path in one direction and keep
- * track of the number of times that the path crosses the ray from
- * left to right, and the number of times that the path crosses the
- * ray from right to left. If these numbers are equal, the point
- * is outside of the path. If they are not equal, the point is
- * inside.
- * @version 10 Feb 1997
- * @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 byte EVEN_ODD = PathIterator.WIND_EVEN_ODD;
-
- /**
- * A non-zero winding rule for determining the interior of a
- * path.
- */
- public static final byte NON_ZERO = PathIterator.WIND_NON_ZERO;
-
- // For code simplicity, copy these constants to our namespace.
- private static final byte SEG_MOVETO = PathIterator.SEG_MOVETO;
- private static final byte SEG_LINETO = PathIterator.SEG_LINETO;
- private static final byte SEG_QUADTO = PathIterator.SEG_QUADTO;
- private static final byte SEG_CUBICTO = PathIterator.SEG_CUBICTO;
- private static final byte SEG_CLOSE = PathIterator.SEG_CLOSE;
-
- byte[] pointTypes;
- float[] pointCoords;
- int numTypes;
- int numCoords;
- int windingRule;
-
- static final int INIT_SIZE = 10;
- static final int EXPAND_SIZE = 10;
-
- /**
- * Constructs a new GeneralPath object.
- * If an operation is performed on this path which requires the
- * interior of the path to be defined, then the default NON_ZERO
- * winding rule is used.
- * @see #NON_ZERO
- */
- public GeneralPath() {
- this(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 #EVEN_ODD
- * @see #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 coordinates will
- * be in the path, but the storage will be 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 coordinates
- * in the path.
- * @see #EVEN_ODD
- * @see #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 coordinates will be in the path, but the storage will
- * be expanded as needed to store whatever path segments are added
- * to this path.
- * @param rule The winding rule.
- * @param initialTypes The estimate for the type of segment.
- * @param initialCapacity The estimate for the number of coordinates
- * @see #EVEN_ODD
- * @see #NON_ZERO
- */
- GeneralPath(int rule, int initialTypes, int initialCoords) {
- setWindingRule(rule);
- pointTypes = new byte[initialTypes];
- pointCoords = new float[initialCoords];
- }
-
- /**
- * Constructs a new GeneralPath object from an arbitrary Shape object.
- * All of the initial geometry and the winding rule for this path are
- * taken from the specified Shape object.
- */
- public GeneralPath(Shape s) {
- this(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");
- }
- if (numCoords + newCoords > pointCoords.length) {
- int num = numCoords + Math.min(newCoords, EXPAND_SIZE * 2);
- float[] arr = new float[num];
- System.arraycopy(pointCoords, 0, arr, 0, numCoords);
- pointCoords = arr;
- }
- if (numTypes + newTypes > pointTypes.length) {
- int num = numTypes + Math.min(newTypes, EXPAND_SIZE);
- byte[] arr = new byte[num];
- System.arraycopy(pointTypes, 0, arr, 0, numTypes);
- pointTypes = arr;
- }
- }
-
- /**
- * Adds a point to the path by moving to 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.
- */
- public synchronized void lineTo(float x, float y) {
- needRoom(1, 2, true);
- pointTypes[numTypes++] = SEG_LINETO;
- pointCoords[numCoords++] = x;
- pointCoords[numCoords++] = y;
- }
-
- /**
- * Adds 2 points to the path by drawing a Quadratic curve from
- * the current coordinates through the second set of specified
- * coordinates, using the first set of specified coordinates as
- * a quadratic parametric control point.
- * @param x1 the X coordinate of the first quadratic control point
- * @param y1 the Y coordinate of the first quadratic control point
- * @param x2 the X coordinate of the final endpoint
- * @param y2 the Y coordinate 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 3 points to the path by drawing a Bezier curve from the
- * current coordinates through the third set of specified
- * coordinates, using the first and second sets of specified
- * coordinates as Bezier control points.
- * @param x1 the X coordinate of the first Bezier control point
- * @param y1 the Y coordinate of the first Bezier control point
- * @param x2 the X coordinate of the second Bezier control point
- * @param y2 the Y coordinate of the second Bezier control point
- * @param x3 the X coordinate of the final endpoint
- * @param y3 the Y coordinate 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 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
- * will be 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 will be omitted as superfluous.
- * The winding rule of the specified Shape will be ignored
- * and the appended geometry will be governed by the winding
- * rule specified for this path.
- * @param s the shape whose geometry will be 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 PathIterator object to the
- * path, possibly connecting the 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
- * will be 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 will be omitted as superfluous.
- * The winding rule of the specified Shape will be ignored
- * and the appended geometry will be governed by the winding
- * rule specified for this path.
- * @param s the shape whose geometry will be 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.
- */
- public synchronized int getWindingRule() {
- return windingRule;
- }
-
- /**
- * Sets the winding rule for this path to the specified value.
- */
- public void setWindingRule(int rule) {
- if (rule != EVEN_ODD && rule != NON_ZERO) {
- throw new IllegalArgumentException("winding rule must be "+
- "EVEN_ODD or NON_ZERO");
- }
- windingRule = rule;
- }
-
- /**
- * Returns the most recent point added to the end of the path.
- * @return a Point object containing the ending coordinate 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;
- }
-
- static GeneralPath doTransform(GeneralPath p, AffineTransform t) {
-
- if (t.isIdentity()) {
- return p;
- }
-
- GeneralPath np = new GeneralPath(p.windingRule,
- p.numTypes,
- p.numCoords);
- System.arraycopy(p.pointTypes, 0, np.pointTypes, 0, p.numTypes);
- np.numTypes = p.numTypes;
- t.transform(p.pointCoords, 0,
- np.pointCoords, 0,
- p.numCoords / 2);
- np.numCoords = p.numCoords;
- return np;
- }
-
- /**
- * Transforms the geometry of this Path using the specified transform.
- * The geometry is transformed in place permanently changing the
- * boundary defined by this object.
- * @param t The matrix used to transform the area.
- */
- public void transform(AffineTransform t) {
- t.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
- }
-
- /**
- * Returns a new transformed Path.
- */
- public synchronized Shape createTransformedShape(AffineTransform t) {
- return doTransform(this, (AffineTransform) t);
- }
-
- /**
- * Return the bounding box of the shape.
- */
- public java.awt.Rectangle getBounds() {
- return getBounds2D().getBounds();
- }
-
- /**
- * Returns the bounding box of the 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);
- }
-
- /**
- * Test if a given coordinate is inside the boundary of the shape.
- */
- public boolean contains(double x, double y) {
- return new Area(this).contains(x, y);
- }
-
- /**
- * Test if a given Point is inside the boundary of the shape.
- */
- public boolean contains(Point2D p) {
- return contains(p.getX(), p.getY());
- }
-
- /**
- * Test if a given coordinate is inside the boundary of the shape.
- */
- public boolean contains(double x, double y, double w, double h) {
- return new Area(this).contains(x, y, w, h);
- }
-
- /**
- * Test if a given Point is inside the boundary of the shape.
- */
- public boolean contains(Rectangle2D r) {
- return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
- }
-
- /**
- * Test if the interior of the Shape intersects the interior of a given
- * set of rectangular coordinates.
- */
- public boolean intersects(double x, double y, double w, double h) {
- return new Area(this).intersects(x, y, w, h);
- }
-
- /**
- * Test if the interior of the Shape intersects the interior of a given
- * Rectangle.
- */
- public boolean intersects(Rectangle2D r) {
- return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
- }
-
- /**
- * Return an iterator object that iterates along the boundary of
- * the shape and provides access to the geometry of the outline
- * of the shape.
- */
- public PathIterator getPathIterator(AffineTransform at) {
- return new GeneralPathIterator(this, at);
- }
-
- /**
- * Return an iterator object that iterates along the boundary of
- * the flattened shape and provides access to the geometry of the
- * outline of the shape.
- */
- 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 JDK1.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();
- }
- }
- }
-