You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
745 lines
26 KiB
745 lines
26 KiB
/*
|
|
* Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package java.awt.geom;
|
|
|
|
import java.awt.Shape;
|
|
import java.awt.Rectangle;
|
|
import java.util.Vector;
|
|
import java.util.Enumeration;
|
|
import java.util.NoSuchElementException;
|
|
import sun.awt.geom.Curve;
|
|
import sun.awt.geom.Crossings;
|
|
import sun.awt.geom.AreaOp;
|
|
|
|
/**
|
|
* An <code>Area</code> object stores and manipulates a
|
|
* resolution-independent description of an enclosed area of
|
|
* 2-dimensional space.
|
|
* <code>Area</code> objects can be transformed and can perform
|
|
* various Constructive Area Geometry (CAG) operations when combined
|
|
* with other <code>Area</code> objects.
|
|
* The CAG operations include area
|
|
* {@link #add addition}, {@link #subtract subtraction},
|
|
* {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}.
|
|
* See the linked method documentation for examples of the various
|
|
* operations.
|
|
* <p>
|
|
* The <code>Area</code> class implements the <code>Shape</code>
|
|
* interface and provides full support for all of its hit-testing
|
|
* and path iteration facilities, but an <code>Area</code> is more
|
|
* specific than a generalized path in a number of ways:
|
|
* <ul>
|
|
* <li>Only closed paths and sub-paths are stored.
|
|
* <code>Area</code> objects constructed from unclosed paths
|
|
* are implicitly closed during construction as if those paths
|
|
* had been filled by the <code>Graphics2D.fill</code> method.
|
|
* <li>The interiors of the individual stored sub-paths are all
|
|
* non-empty and non-overlapping. Paths are decomposed during
|
|
* construction into separate component non-overlapping parts,
|
|
* empty pieces of the path are discarded, and then these
|
|
* non-empty and non-overlapping properties are maintained
|
|
* through all subsequent CAG operations. Outlines of different
|
|
* component sub-paths may touch each other, as long as they
|
|
* do not cross so that their enclosed areas overlap.
|
|
* <li>The geometry of the path describing the outline of the
|
|
* <code>Area</code> resembles the path from which it was
|
|
* constructed only in that it describes the same enclosed
|
|
* 2-dimensional area, but may use entirely different types
|
|
* and ordering of the path segments to do so.
|
|
* </ul>
|
|
* Interesting issues which are not always obvious when using
|
|
* the <code>Area</code> include:
|
|
* <ul>
|
|
* <li>Creating an <code>Area</code> from an unclosed (open)
|
|
* <code>Shape</code> results in a closed outline in the
|
|
* <code>Area</code> object.
|
|
* <li>Creating an <code>Area</code> from a <code>Shape</code>
|
|
* which encloses no area (even when "closed") produces an
|
|
* empty <code>Area</code>. A common example of this issue
|
|
* is that producing an <code>Area</code> from a line will
|
|
* be empty since the line encloses no area. An empty
|
|
* <code>Area</code> will iterate no geometry in its
|
|
* <code>PathIterator</code> objects.
|
|
* <li>A self-intersecting <code>Shape</code> may be split into
|
|
* two (or more) sub-paths each enclosing one of the
|
|
* non-intersecting portions of the original path.
|
|
* <li>An <code>Area</code> may take more path segments to
|
|
* describe the same geometry even when the original
|
|
* outline is simple and obvious. The analysis that the
|
|
* <code>Area</code> class must perform on the path may
|
|
* not reflect the same concepts of "simple and obvious"
|
|
* as a human being perceives.
|
|
* </ul>
|
|
*
|
|
* @since 1.2
|
|
*/
|
|
public class Area implements Shape, Cloneable {
|
|
private static Vector EmptyCurves = new Vector();
|
|
|
|
private Vector curves;
|
|
|
|
/**
|
|
* Default constructor which creates an empty area.
|
|
* @since 1.2
|
|
*/
|
|
public Area() {
|
|
curves = EmptyCurves;
|
|
}
|
|
|
|
/**
|
|
* The <code>Area</code> class creates an area geometry from the
|
|
* specified {@link Shape} object. The geometry is explicitly
|
|
* closed, if the <code>Shape</code> is not already closed. The
|
|
* fill rule (even-odd or winding) specified by the geometry of the
|
|
* <code>Shape</code> is used to determine the resulting enclosed area.
|
|
* @param s the <code>Shape</code> from which the area is constructed
|
|
* @throws NullPointerException if <code>s</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public Area(Shape s) {
|
|
if (s instanceof Area) {
|
|
curves = ((Area) s).curves;
|
|
} else {
|
|
curves = pathToCurves(s.getPathIterator(null));
|
|
}
|
|
}
|
|
|
|
private static Vector pathToCurves(PathIterator pi) {
|
|
Vector curves = new Vector();
|
|
int windingRule = pi.getWindingRule();
|
|
// coords array is big enough for holding:
|
|
// coordinates returned from currentSegment (6)
|
|
// OR
|
|
// two subdivided quadratic curves (2+4+4=10)
|
|
// AND
|
|
// 0-1 horizontal splitting parameters
|
|
// OR
|
|
// 2 parametric equation derivative coefficients
|
|
// OR
|
|
// three subdivided cubic curves (2+6+6+6=20)
|
|
// AND
|
|
// 0-2 horizontal splitting parameters
|
|
// OR
|
|
// 3 parametric equation derivative coefficients
|
|
double coords[] = new double[23];
|
|
double movx = 0, movy = 0;
|
|
double curx = 0, cury = 0;
|
|
double newx, newy;
|
|
while (!pi.isDone()) {
|
|
switch (pi.currentSegment(coords)) {
|
|
case PathIterator.SEG_MOVETO:
|
|
Curve.insertLine(curves, curx, cury, movx, movy);
|
|
curx = movx = coords[0];
|
|
cury = movy = coords[1];
|
|
Curve.insertMove(curves, movx, movy);
|
|
break;
|
|
case PathIterator.SEG_LINETO:
|
|
newx = coords[0];
|
|
newy = coords[1];
|
|
Curve.insertLine(curves, curx, cury, newx, newy);
|
|
curx = newx;
|
|
cury = newy;
|
|
break;
|
|
case PathIterator.SEG_QUADTO:
|
|
newx = coords[2];
|
|
newy = coords[3];
|
|
Curve.insertQuad(curves, curx, cury, coords);
|
|
curx = newx;
|
|
cury = newy;
|
|
break;
|
|
case PathIterator.SEG_CUBICTO:
|
|
newx = coords[4];
|
|
newy = coords[5];
|
|
Curve.insertCubic(curves, curx, cury, coords);
|
|
curx = newx;
|
|
cury = newy;
|
|
break;
|
|
case PathIterator.SEG_CLOSE:
|
|
Curve.insertLine(curves, curx, cury, movx, movy);
|
|
curx = movx;
|
|
cury = movy;
|
|
break;
|
|
}
|
|
pi.next();
|
|
}
|
|
Curve.insertLine(curves, curx, cury, movx, movy);
|
|
AreaOp operator;
|
|
if (windingRule == PathIterator.WIND_EVEN_ODD) {
|
|
operator = new AreaOp.EOWindOp();
|
|
} else {
|
|
operator = new AreaOp.NZWindOp();
|
|
}
|
|
return operator.calculate(curves, EmptyCurves);
|
|
}
|
|
|
|
/**
|
|
* Adds the shape of the specified <code>Area</code> to the
|
|
* shape of this <code>Area</code>.
|
|
* The resulting shape of this <code>Area</code> will include
|
|
* the union of both shapes, or all areas that were contained
|
|
* in either this or the specified <code>Area</code>.
|
|
* <pre>
|
|
* // Example:
|
|
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
|
|
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
|
|
* a1.add(a2);
|
|
*
|
|
* a1(before) + a2 = a1(after)
|
|
*
|
|
* ################ ################ ################
|
|
* ############## ############## ################
|
|
* ############ ############ ################
|
|
* ########## ########## ################
|
|
* ######## ######## ################
|
|
* ###### ###### ###### ######
|
|
* #### #### #### ####
|
|
* ## ## ## ##
|
|
* </pre>
|
|
* @param rhs the <code>Area</code> to be added to the
|
|
* current shape
|
|
* @throws NullPointerException if <code>rhs</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public void add(Area rhs) {
|
|
curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Subtracts the shape of the specified <code>Area</code> from the
|
|
* shape of this <code>Area</code>.
|
|
* The resulting shape of this <code>Area</code> will include
|
|
* areas that were contained only in this <code>Area</code>
|
|
* and not in the specified <code>Area</code>.
|
|
* <pre>
|
|
* // Example:
|
|
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
|
|
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
|
|
* a1.subtract(a2);
|
|
*
|
|
* a1(before) - a2 = a1(after)
|
|
*
|
|
* ################ ################
|
|
* ############## ############## ##
|
|
* ############ ############ ####
|
|
* ########## ########## ######
|
|
* ######## ######## ########
|
|
* ###### ###### ######
|
|
* #### #### ####
|
|
* ## ## ##
|
|
* </pre>
|
|
* @param rhs the <code>Area</code> to be subtracted from the
|
|
* current shape
|
|
* @throws NullPointerException if <code>rhs</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public void subtract(Area rhs) {
|
|
curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Sets the shape of this <code>Area</code> to the intersection of
|
|
* its current shape and the shape of the specified <code>Area</code>.
|
|
* The resulting shape of this <code>Area</code> will include
|
|
* only areas that were contained in both this <code>Area</code>
|
|
* and also in the specified <code>Area</code>.
|
|
* <pre>
|
|
* // Example:
|
|
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
|
|
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
|
|
* a1.intersect(a2);
|
|
*
|
|
* a1(before) intersect a2 = a1(after)
|
|
*
|
|
* ################ ################ ################
|
|
* ############## ############## ############
|
|
* ############ ############ ########
|
|
* ########## ########## ####
|
|
* ######## ########
|
|
* ###### ######
|
|
* #### ####
|
|
* ## ##
|
|
* </pre>
|
|
* @param rhs the <code>Area</code> to be intersected with this
|
|
* <code>Area</code>
|
|
* @throws NullPointerException if <code>rhs</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public void intersect(Area rhs) {
|
|
curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Sets the shape of this <code>Area</code> to be the combined area
|
|
* of its current shape and the shape of the specified <code>Area</code>,
|
|
* minus their intersection.
|
|
* The resulting shape of this <code>Area</code> will include
|
|
* only areas that were contained in either this <code>Area</code>
|
|
* or in the specified <code>Area</code>, but not in both.
|
|
* <pre>
|
|
* // Example:
|
|
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
|
|
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
|
|
* a1.exclusiveOr(a2);
|
|
*
|
|
* a1(before) xor a2 = a1(after)
|
|
*
|
|
* ################ ################
|
|
* ############## ############## ## ##
|
|
* ############ ############ #### ####
|
|
* ########## ########## ###### ######
|
|
* ######## ######## ################
|
|
* ###### ###### ###### ######
|
|
* #### #### #### ####
|
|
* ## ## ## ##
|
|
* </pre>
|
|
* @param rhs the <code>Area</code> to be exclusive ORed with this
|
|
* <code>Area</code>.
|
|
* @throws NullPointerException if <code>rhs</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public void exclusiveOr(Area rhs) {
|
|
curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Removes all of the geometry from this <code>Area</code> and
|
|
* restores it to an empty area.
|
|
* @since 1.2
|
|
*/
|
|
public void reset() {
|
|
curves = new Vector();
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Tests whether this <code>Area</code> object encloses any area.
|
|
* @return <code>true</code> if this <code>Area</code> object
|
|
* represents an empty area; <code>false</code> otherwise.
|
|
* @since 1.2
|
|
*/
|
|
public boolean isEmpty() {
|
|
return (curves.size() == 0);
|
|
}
|
|
|
|
/**
|
|
* Tests whether this <code>Area</code> consists entirely of
|
|
* straight edged polygonal geometry.
|
|
* @return <code>true</code> if the geometry of this
|
|
* <code>Area</code> consists entirely of line segments;
|
|
* <code>false</code> otherwise.
|
|
* @since 1.2
|
|
*/
|
|
public boolean isPolygonal() {
|
|
Enumeration enum_ = curves.elements();
|
|
while (enum_.hasMoreElements()) {
|
|
if (((Curve) enum_.nextElement()).getOrder() > 1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Tests whether this <code>Area</code> is rectangular in shape.
|
|
* @return <code>true</code> if the geometry of this
|
|
* <code>Area</code> is rectangular in shape; <code>false</code>
|
|
* otherwise.
|
|
* @since 1.2
|
|
*/
|
|
public boolean isRectangular() {
|
|
int size = curves.size();
|
|
if (size == 0) {
|
|
return true;
|
|
}
|
|
if (size > 3) {
|
|
return false;
|
|
}
|
|
Curve c1 = (Curve) curves.get(1);
|
|
Curve c2 = (Curve) curves.get(2);
|
|
if (c1.getOrder() != 1 || c2.getOrder() != 1) {
|
|
return false;
|
|
}
|
|
if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
|
|
return false;
|
|
}
|
|
if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
|
|
// One might be able to prove that this is impossible...
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Tests whether this <code>Area</code> is comprised of a single
|
|
* closed subpath. This method returns <code>true</code> if the
|
|
* path contains 0 or 1 subpaths, or <code>false</code> if the path
|
|
* contains more than 1 subpath. The subpaths are counted by the
|
|
* number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
|
|
* that appear in the path.
|
|
* @return <code>true</code> if the <code>Area</code> is comprised
|
|
* of a single basic geometry; <code>false</code> otherwise.
|
|
* @since 1.2
|
|
*/
|
|
public boolean isSingular() {
|
|
if (curves.size() < 3) {
|
|
return true;
|
|
}
|
|
Enumeration enum_ = curves.elements();
|
|
enum_.nextElement(); // First Order0 "moveto"
|
|
while (enum_.hasMoreElements()) {
|
|
if (((Curve) enum_.nextElement()).getOrder() == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private Rectangle2D cachedBounds;
|
|
private void invalidateBounds() {
|
|
cachedBounds = null;
|
|
}
|
|
private Rectangle2D getCachedBounds() {
|
|
if (cachedBounds != null) {
|
|
return cachedBounds;
|
|
}
|
|
Rectangle2D r = new Rectangle2D.Double();
|
|
if (curves.size() > 0) {
|
|
Curve c = (Curve) curves.get(0);
|
|
// First point is always an order 0 curve (moveto)
|
|
r.setRect(c.getX0(), c.getY0(), 0, 0);
|
|
for (int i = 1; i < curves.size(); i++) {
|
|
((Curve) curves.get(i)).enlarge(r);
|
|
}
|
|
}
|
|
return (cachedBounds = r);
|
|
}
|
|
|
|
/**
|
|
* Returns a high precision bounding {@link Rectangle2D} that
|
|
* completely encloses this <code>Area</code>.
|
|
* <p>
|
|
* The Area class will attempt to return the tightest bounding
|
|
* box possible for the Shape. The bounding box will not be
|
|
* padded to include the control points of curves in the outline
|
|
* of the Shape, but should tightly fit the actual geometry of
|
|
* the outline itself.
|
|
* @return the bounding <code>Rectangle2D</code> for the
|
|
* <code>Area</code>.
|
|
* @since 1.2
|
|
*/
|
|
public Rectangle2D getBounds2D() {
|
|
return getCachedBounds().getBounds2D();
|
|
}
|
|
|
|
/**
|
|
* Returns a bounding {@link Rectangle} that completely encloses
|
|
* this <code>Area</code>.
|
|
* <p>
|
|
* The Area class will attempt to return the tightest bounding
|
|
* box possible for the Shape. The bounding box will not be
|
|
* padded to include the control points of curves in the outline
|
|
* of the Shape, but should tightly fit the actual geometry of
|
|
* the outline itself. Since the returned object represents
|
|
* the bounding box with integers, the bounding box can only be
|
|
* as tight as the nearest integer coordinates that encompass
|
|
* the geometry of the Shape.
|
|
* @return the bounding <code>Rectangle</code> for the
|
|
* <code>Area</code>.
|
|
* @since 1.2
|
|
*/
|
|
public Rectangle getBounds() {
|
|
return getCachedBounds().getBounds();
|
|
}
|
|
|
|
/**
|
|
* Returns an exact copy of this <code>Area</code> object.
|
|
* @return Created clone object
|
|
* @since 1.2
|
|
*/
|
|
public Object clone() {
|
|
return new Area(this);
|
|
}
|
|
|
|
/**
|
|
* Tests whether the geometries of the two <code>Area</code> objects
|
|
* are equal.
|
|
* This method will return false if the argument is null.
|
|
* @param other the <code>Area</code> to be compared to this
|
|
* <code>Area</code>
|
|
* @return <code>true</code> if the two geometries are equal;
|
|
* <code>false</code> otherwise.
|
|
* @since 1.2
|
|
*/
|
|
public boolean equals(Area other) {
|
|
// REMIND: A *much* simpler operation should be possible...
|
|
// Should be able to do a curve-wise comparison since all Areas
|
|
// should evaluate their curves in the same top-down order.
|
|
if (other == this) {
|
|
return true;
|
|
}
|
|
if (other == null) {
|
|
return false;
|
|
}
|
|
Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves);
|
|
return c.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Transforms the geometry of this <code>Area</code> using the specified
|
|
* {@link AffineTransform}. The geometry is transformed in place, which
|
|
* permanently changes the enclosed area defined by this object.
|
|
* @param t the transformation used to transform the area
|
|
* @throws NullPointerException if <code>t</code> is null
|
|
* @since 1.2
|
|
*/
|
|
public void transform(AffineTransform t) {
|
|
if (t == null) {
|
|
throw new NullPointerException("transform must not be null");
|
|
}
|
|
// REMIND: A simpler operation can be performed for some types
|
|
// of transform.
|
|
curves = pathToCurves(getPathIterator(t));
|
|
invalidateBounds();
|
|
}
|
|
|
|
/**
|
|
* Creates a new <code>Area</code> object that contains the same
|
|
* geometry as this <code>Area</code> transformed by the specified
|
|
* <code>AffineTransform</code>. This <code>Area</code> object
|
|
* is unchanged.
|
|
* @param t the specified <code>AffineTransform</code> used to transform
|
|
* the new <code>Area</code>
|
|
* @throws NullPointerException if <code>t</code> is null
|
|
* @return a new <code>Area</code> object representing the transformed
|
|
* geometry.
|
|
* @since 1.2
|
|
*/
|
|
public Area createTransformedArea(AffineTransform t) {
|
|
Area a = new Area(this);
|
|
a.transform(t);
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean contains(double x, double y) {
|
|
if (!getCachedBounds().contains(x, y)) {
|
|
return false;
|
|
}
|
|
Enumeration enum_ = curves.elements();
|
|
int crossings = 0;
|
|
while (enum_.hasMoreElements()) {
|
|
Curve c = (Curve) enum_.nextElement();
|
|
crossings += c.crossingsFor(x, y);
|
|
}
|
|
return ((crossings & 1) == 1);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean contains(Point2D p) {
|
|
return contains(p.getX(), p.getY());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean contains(double x, double y, double w, double h) {
|
|
if (w < 0 || h < 0) {
|
|
return false;
|
|
}
|
|
if (!getCachedBounds().contains(x, y, w, h)) {
|
|
return false;
|
|
}
|
|
Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
|
|
return (c != null && c.covers(y, y+h));
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean contains(Rectangle2D r) {
|
|
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean intersects(double x, double y, double w, double h) {
|
|
if (w < 0 || h < 0) {
|
|
return false;
|
|
}
|
|
if (!getCachedBounds().intersects(x, y, w, h)) {
|
|
return false;
|
|
}
|
|
Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
|
|
return (c == null || !c.isEmpty());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @since 1.2
|
|
*/
|
|
public boolean intersects(Rectangle2D r) {
|
|
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link PathIterator} for the outline of this
|
|
* <code>Area</code> object. This <code>Area</code> object is unchanged.
|
|
* @param at an optional <code>AffineTransform</code> to be applied to
|
|
* the coordinates as they are returned in the iteration, or
|
|
* <code>null</code> if untransformed coordinates are desired
|
|
* @return the <code>PathIterator</code> object that returns the
|
|
* geometry of the outline of this <code>Area</code>, one
|
|
* segment at a time.
|
|
* @since 1.2
|
|
*/
|
|
public PathIterator getPathIterator(AffineTransform at) {
|
|
return new AreaIterator(curves, at);
|
|
}
|
|
|
|
/**
|
|
* Creates a <code>PathIterator</code> for the flattened outline of
|
|
* this <code>Area</code> object. Only uncurved path segments
|
|
* represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
|
|
* types are returned by the iterator. This <code>Area</code>
|
|
* object is unchanged.
|
|
* @param at an optional <code>AffineTransform</code> to be
|
|
* applied to the coordinates as they are returned in the
|
|
* iteration, or <code>null</code> if untransformed coordinates
|
|
* are desired
|
|
* @param flatness the maximum amount that the control points
|
|
* for a given curve can vary from colinear before a subdivided
|
|
* curve is replaced by a straight line connecting the end points
|
|
* @return the <code>PathIterator</code> object that returns the
|
|
* geometry of the outline of this <code>Area</code>, one segment
|
|
* at a time.
|
|
* @since 1.2
|
|
*/
|
|
public PathIterator getPathIterator(AffineTransform at, double flatness) {
|
|
return new FlatteningPathIterator(getPathIterator(at), flatness);
|
|
}
|
|
}
|
|
|
|
class AreaIterator implements PathIterator {
|
|
private AffineTransform transform;
|
|
private Vector curves;
|
|
private int index;
|
|
private Curve prevcurve;
|
|
private Curve thiscurve;
|
|
|
|
public AreaIterator(Vector curves, AffineTransform at) {
|
|
this.curves = curves;
|
|
this.transform = at;
|
|
if (curves.size() >= 1) {
|
|
thiscurve = (Curve) curves.get(0);
|
|
}
|
|
}
|
|
|
|
public int getWindingRule() {
|
|
// REMIND: Which is better, EVEN_ODD or NON_ZERO?
|
|
// The paths calculated could be classified either way.
|
|
//return WIND_EVEN_ODD;
|
|
return WIND_NON_ZERO;
|
|
}
|
|
|
|
public boolean isDone() {
|
|
return (prevcurve == null && thiscurve == null);
|
|
}
|
|
|
|
public void next() {
|
|
if (prevcurve != null) {
|
|
prevcurve = null;
|
|
} else {
|
|
prevcurve = thiscurve;
|
|
index++;
|
|
if (index < curves.size()) {
|
|
thiscurve = (Curve) curves.get(index);
|
|
if (thiscurve.getOrder() != 0 &&
|
|
prevcurve.getX1() == thiscurve.getX0() &&
|
|
prevcurve.getY1() == thiscurve.getY0())
|
|
{
|
|
prevcurve = null;
|
|
}
|
|
} else {
|
|
thiscurve = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int currentSegment(float coords[]) {
|
|
double dcoords[] = new double[6];
|
|
int segtype = currentSegment(dcoords);
|
|
int numpoints = (segtype == SEG_CLOSE ? 0
|
|
: (segtype == SEG_QUADTO ? 2
|
|
: (segtype == SEG_CUBICTO ? 3
|
|
: 1)));
|
|
for (int i = 0; i < numpoints * 2; i++) {
|
|
coords[i] = (float) dcoords[i];
|
|
}
|
|
return segtype;
|
|
}
|
|
|
|
public int currentSegment(double coords[]) {
|
|
int segtype;
|
|
int numpoints;
|
|
if (prevcurve != null) {
|
|
// Need to finish off junction between curves
|
|
if (thiscurve == null || thiscurve.getOrder() == 0) {
|
|
return SEG_CLOSE;
|
|
}
|
|
coords[0] = thiscurve.getX0();
|
|
coords[1] = thiscurve.getY0();
|
|
segtype = SEG_LINETO;
|
|
numpoints = 1;
|
|
} else if (thiscurve == null) {
|
|
throw new NoSuchElementException("area iterator out of bounds");
|
|
} else {
|
|
segtype = thiscurve.getSegment(coords);
|
|
numpoints = thiscurve.getOrder();
|
|
if (numpoints == 0) {
|
|
numpoints = 1;
|
|
}
|
|
}
|
|
if (transform != null) {
|
|
transform.transform(coords, 0, coords, 0, numpoints);
|
|
}
|
|
return segtype;
|
|
}
|
|
}
|