blob: 0669bc77b00645258ba29bae7daaf311a4bb5685 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Denis M. Kishenko
* @version $Revision$
*/
package java.awt.geom;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.NoSuchElementException;
import org.apache.harmony.awt.gl.Crossing;
import org.apache.harmony.awt.internal.nls.Messages;
/**
* The class GeneralPath represents a shape whose outline is given by different
* types of curved and straight segments.
*
* @since Android 1.0
*/
public final class GeneralPath implements Shape, Cloneable {
/**
* The Constant WIND_EVEN_ODD see {@link PathIterator#WIND_EVEN_ODD}.
*/
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
/**
* The Constant WIND_NON_ZERO see {@link PathIterator#WIND_NON_ZERO}.
*/
public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
/**
* The buffers size.
*/
private static final int BUFFER_SIZE = 10;
/**
* The buffers capacity.
*/
private static final int BUFFER_CAPACITY = 10;
/**
* The point's types buffer.
*/
byte[] types;
/**
* The points buffer.
*/
float[] points;
/**
* The point's type buffer size.
*/
int typeSize;
/**
* The points buffer size.
*/
int pointSize;
/**
* The path rule.
*/
int rule;
/**
* The space amount in points buffer for different segmenet's types.
*/
static int pointShift[] = {
2, // MOVETO
2, // LINETO
4, // QUADTO
6, // CUBICTO
0
}; // CLOSE
/*
* GeneralPath path iterator
*/
/**
* The Class Iterator is the subclass of Iterator for traversing the outline
* of a GeneralPath.
*/
class Iterator implements PathIterator {
/**
* The current cursor position in types buffer.
*/
int typeIndex;
/**
* The current cursor position in points buffer.
*/
int pointIndex;
/**
* The source GeneralPath object.
*/
GeneralPath p;
/**
* The path iterator transformation.
*/
AffineTransform t;
/**
* Constructs a new GeneralPath.Iterator for given general path.
*
* @param path
* the source GeneralPath object.
*/
Iterator(GeneralPath path) {
this(path, null);
}
/**
* Constructs a new GeneralPath.Iterator for given general path and
* transformation.
*
* @param path
* the source GeneralPath object.
* @param at
* the AffineTransform object to apply rectangle path.
*/
Iterator(GeneralPath path, AffineTransform at) {
this.p = path;
this.t = at;
}
public int getWindingRule() {
return p.getWindingRule();
}
public boolean isDone() {
return typeIndex >= p.typeSize;
}
public void next() {
typeIndex++;
}
public int currentSegment(double[] coords) {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
}
int type = p.types[typeIndex];
int count = GeneralPath.pointShift[type];
for (int i = 0; i < count; i++) {
coords[i] = p.points[pointIndex + i];
}
if (t != null) {
t.transform(coords, 0, coords, 0, count / 2);
}
pointIndex += count;
return type;
}
public int currentSegment(float[] coords) {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
}
int type = p.types[typeIndex];
int count = GeneralPath.pointShift[type];
System.arraycopy(p.points, pointIndex, coords, 0, count);
if (t != null) {
t.transform(coords, 0, coords, 0, count / 2);
}
pointIndex += count;
return type;
}
}
/**
* Instantiates a new general path with the winding rule set to
* {@link PathIterator#WIND_NON_ZERO} and the initial capacity (number of
* segments) set to the default value 10.
*/
public GeneralPath() {
this(WIND_NON_ZERO, BUFFER_SIZE);
}
/**
* Instantiates a new general path with the given winding rule and the
* initial capacity (number of segments) set to the default value 10.
*
* @param rule
* the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
* {@link PathIterator#WIND_NON_ZERO}.
*/
public GeneralPath(int rule) {
this(rule, BUFFER_SIZE);
}
/**
* Instantiates a new general path with the given winding rule and initial
* capacity (number of segments).
*
* @param rule
* the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
* {@link PathIterator#WIND_NON_ZERO}.
* @param initialCapacity
* the number of segments the path is set to hold.
*/
public GeneralPath(int rule, int initialCapacity) {
setWindingRule(rule);
types = new byte[initialCapacity];
points = new float[initialCapacity * 2];
}
/**
* Creates a new GeneralPath from the outline of the given shape.
*
* @param shape
* the shape.
*/
public GeneralPath(Shape shape) {
this(WIND_NON_ZERO, BUFFER_SIZE);
PathIterator p = shape.getPathIterator(null);
setWindingRule(p.getWindingRule());
append(p, false);
}
/**
* Sets the winding rule, which determines how to decide whether a point
* that isn't on the path itself is inside or outside of the shape.
*
* @param rule
* the new winding rule.
* @throws IllegalArgumentException
* if the winding rule is neither
* {@link PathIterator#WIND_EVEN_ODD} nor
* {@link PathIterator#WIND_NON_ZERO}.
*/
public void setWindingRule(int rule) {
if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
// awt.209=Invalid winding rule value
throw new java.lang.IllegalArgumentException(Messages.getString("awt.209")); //$NON-NLS-1$
}
this.rule = rule;
}
/**
* Gets the winding rule.
*
* @return the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
* {@link PathIterator#WIND_NON_ZERO}.
*/
public int getWindingRule() {
return rule;
}
/**
* Checks the point data buffer sizes to see whether pointCount additional
* point-data elements can fit. (Note that the number of point data elements
* to add is more than one per point -- it depends on the type of point
* being added.) Reallocates the buffers to enlarge the size if necessary.
*
* @param pointCount
* the number of point data elements to be added.
* @param checkMove
* whether to check for existing points.
* @throws IllegalPathStateException
* checkMove is true and the path is currently empty.
*/
void checkBuf(int pointCount, boolean checkMove) {
if (checkMove && typeSize == 0) {
// awt.20A=First segment should be SEG_MOVETO type
throw new IllegalPathStateException(Messages.getString("awt.20A")); //$NON-NLS-1$
}
if (typeSize == types.length) {
byte tmp[] = new byte[typeSize + BUFFER_CAPACITY];
System.arraycopy(types, 0, tmp, 0, typeSize);
types = tmp;
}
if (pointSize + pointCount > points.length) {
float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)];
System.arraycopy(points, 0, tmp, 0, pointSize);
points = tmp;
}
}
/**
* Appends a new point to the end of this general path, disconnected from
* the existing path.
*
* @param x
* the x coordinate of the next point to append.
* @param y
* the y coordinate of the next point to append.
*/
public void moveTo(float x, float y) {
if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) {
points[pointSize - 2] = x;
points[pointSize - 1] = y;
} else {
checkBuf(2, false);
types[typeSize++] = PathIterator.SEG_MOVETO;
points[pointSize++] = x;
points[pointSize++] = y;
}
}
/**
* Appends a new segment to the end of this general path by making a
* straight line segment from the current endpoint to the given new point.
*
* @param x
* the x coordinate of the next point to append.
* @param y
* the y coordinate of the next point to append.
*/
public void lineTo(float x, float y) {
checkBuf(2, true);
types[typeSize++] = PathIterator.SEG_LINETO;
points[pointSize++] = x;
points[pointSize++] = y;
}
/**
* Appends a new segment to the end of this general path by making a
* quadratic curve from the current endpoint to the point (x2, y2) using the
* point (x1, y1) as the quadratic curve's control point.
*
* @param x1
* the x coordinate of the quadratic curve's control point.
* @param y1
* the y coordinate of the quadratic curve's control point.
* @param x2
* the x coordinate of the quadratic curve's end point.
* @param y2
* the y coordinate of the quadratic curve's end point.
*/
public void quadTo(float x1, float y1, float x2, float y2) {
checkBuf(4, true);
types[typeSize++] = PathIterator.SEG_QUADTO;
points[pointSize++] = x1;
points[pointSize++] = y1;
points[pointSize++] = x2;
points[pointSize++] = y2;
}
/**
* Appends a new segment to the end of this general path by making a cubic
* curve from the current endpoint to the point (x3, y3) using (x1, y1) and
* (x2, y2) as control points.
*
* @see java.awt.geom.CubicCurve2D
* @param x1
* the x coordinate of the new cubic segment's first control
* point.
* @param y1
* the y coordinate of the new cubic segment's first control
* point.
* @param x2
* the x coordinate of the new cubic segment's second control
* point.
* @param y2
* the y coordinate of the new cubic segment's second control
* point.
* @param x3
* the x coordinate of the new cubic segment's end point.
* @param y3
* the y coordinate of the new cubic segment's end point.
*/
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
checkBuf(6, true);
types[typeSize++] = PathIterator.SEG_CUBICTO;
points[pointSize++] = x1;
points[pointSize++] = y1;
points[pointSize++] = x2;
points[pointSize++] = y2;
points[pointSize++] = x3;
points[pointSize++] = y3;
}
/**
* Appends the type information to declare that the current endpoint closes
* the curve.
*/
public void closePath() {
if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) {
checkBuf(0, true);
types[typeSize++] = PathIterator.SEG_CLOSE;
}
}
/**
* Appends the outline of the specified shape onto the end of this
* GeneralPath.
*
* @param shape
* the shape whose outline is to be appended.
* @param connect
* true to connect this path's current endpoint to the first
* point of the shape's outline or false to append the shape's
* outline without connecting it.
* @throws NullPointerException
* if the shape parameter is null.
*/
public void append(Shape shape, boolean connect) {
PathIterator p = shape.getPathIterator(null);
append(p, connect);
}
/**
* Appends the path defined by the specified PathIterator onto the end of
* this GeneralPath.
*
* @param path
* the PathIterator that defines the new path to append.
* @param connect
* true to connect this path's current endpoint to the first
* point of the shape's outline or false to append the shape's
* outline without connecting it.
*/
public void append(PathIterator path, boolean connect) {
while (!path.isDone()) {
float coords[] = new float[6];
switch (path.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (!connect || typeSize == 0) {
moveTo(coords[0], coords[1]);
break;
}
if (types[typeSize - 1] != PathIterator.SEG_CLOSE
&& points[pointSize - 2] == coords[0]
&& points[pointSize - 1] == coords[1]) {
break;
}
// NO BREAK;
case PathIterator.SEG_LINETO:
lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
quadTo(coords[0], coords[1], coords[2], coords[3]);
break;
case PathIterator.SEG_CUBICTO:
curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
break;
case PathIterator.SEG_CLOSE:
closePath();
break;
}
path.next();
connect = false;
}
}
/**
* Gets the current end point of the path.
*
* @return the current end point of the path.
*/
public Point2D getCurrentPoint() {
if (typeSize == 0) {
return null;
}
int j = pointSize - 2;
if (types[typeSize - 1] == PathIterator.SEG_CLOSE) {
for (int i = typeSize - 2; i > 0; i--) {
int type = types[i];
if (type == PathIterator.SEG_MOVETO) {
break;
}
j -= pointShift[type];
}
}
return new Point2D.Float(points[j], points[j + 1]);
}
/**
* Resets the GeneralPath to being an empty path. The underlying point and
* segment data is not deleted but rather the end indices of the data arrays
* are set to zero.
*/
public void reset() {
typeSize = 0;
pointSize = 0;
}
/**
* Transform all of the coordinates of this path according to the specified
* AffineTransform.
*
* @param t
* the AffineTransform.
*/
public void transform(AffineTransform t) {
t.transform(points, 0, points, 0, pointSize / 2);
}
/**
* Creates a new GeneralPath whose data is given by this path's data
* transformed according to the specified AffineTransform.
*
* @param t
* the AffineTransform.
* @return the new GeneralPath whose data is given by this path's data
* transformed according to the specified AffineTransform.
*/
public Shape createTransformedShape(AffineTransform t) {
GeneralPath p = (GeneralPath)clone();
if (t != null) {
p.transform(t);
}
return p;
}
public Rectangle2D getBounds2D() {
float rx1, ry1, rx2, ry2;
if (pointSize == 0) {
rx1 = ry1 = rx2 = ry2 = 0.0f;
} else {
int i = pointSize - 1;
ry1 = ry2 = points[i--];
rx1 = rx2 = points[i--];
while (i > 0) {
float y = points[i--];
float x = points[i--];
if (x < rx1) {
rx1 = x;
} else if (x > rx2) {
rx2 = x;
}
if (y < ry1) {
ry1 = y;
} else if (y > ry2) {
ry2 = y;
}
}
}
return new Rectangle2D.Float(rx1, ry1, rx2 - rx1, ry2 - ry1);
}
public Rectangle getBounds() {
return getBounds2D().getBounds();
}
/**
* Checks the cross count (number of times a ray from the point crosses the
* shape's boundary) to determine whether the number of crossings
* corresponds to a point inside the shape or not (according to the shape's
* path rule).
*
* @param cross
* the point's cross count.
* @return true if the point is inside the path, or false otherwise.
*/
boolean isInside(int cross) {
if (rule == WIND_NON_ZERO) {
return Crossing.isInsideNonZero(cross);
}
return Crossing.isInsideEvenOdd(cross);
}
public boolean contains(double px, double py) {
return isInside(Crossing.crossShape(this, px, py));
}
public boolean contains(double rx, double ry, double rw, double rh) {
int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
return cross != Crossing.CROSSING && isInside(cross);
}
public boolean intersects(double rx, double ry, double rw, double rh) {
int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
return cross == Crossing.CROSSING || isInside(cross);
}
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public boolean intersects(Rectangle2D r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public PathIterator getPathIterator(AffineTransform t) {
return new Iterator(this, t);
}
public PathIterator getPathIterator(AffineTransform t, double flatness) {
return new FlatteningPathIterator(getPathIterator(t), flatness);
}
@Override
public Object clone() {
try {
GeneralPath p = (GeneralPath)super.clone();
p.types = types.clone();
p.points = points.clone();
return p;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}