1 /*
   2  * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.awt.geom;
  27 
  28 import java.awt.Shape;
  29 import java.awt.Rectangle;
  30 import java.util.Vector;
  31 import java.util.Enumeration;
  32 import java.util.NoSuchElementException;
  33 import sun.awt.geom.Curve;
  34 import sun.awt.geom.Crossings;
  35 import sun.awt.geom.AreaOp;
  36 
  37 /**
  38  * An {@code Area} object stores and manipulates a
  39  * resolution-independent description of an enclosed area of
  40  * 2-dimensional space.
  41  * {@code Area} objects can be transformed and can perform
  42  * various Constructive Area Geometry (CAG) operations when combined
  43  * with other {@code Area} objects.
  44  * The CAG operations include area
  45  * {@link #add addition}, {@link #subtract subtraction},
  46  * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}.
  47  * See the linked method documentation for examples of the various
  48  * operations.
  49  * <p>
  50  * The {@code Area} class implements the {@code Shape}
  51  * interface and provides full support for all of its hit-testing
  52  * and path iteration facilities, but an {@code Area} is more
  53  * specific than a generalized path in a number of ways:
  54  * <ul>
  55  * <li>Only closed paths and sub-paths are stored.
  56  *     {@code Area} objects constructed from unclosed paths
  57  *     are implicitly closed during construction as if those paths
  58  *     had been filled by the {@code Graphics2D.fill} method.
  59  * <li>The interiors of the individual stored sub-paths are all
  60  *     non-empty and non-overlapping.  Paths are decomposed during
  61  *     construction into separate component non-overlapping parts,
  62  *     empty pieces of the path are discarded, and then these
  63  *     non-empty and non-overlapping properties are maintained
  64  *     through all subsequent CAG operations.  Outlines of different
  65  *     component sub-paths may touch each other, as long as they
  66  *     do not cross so that their enclosed areas overlap.
  67  * <li>The geometry of the path describing the outline of the
  68  *     {@code Area} resembles the path from which it was
  69  *     constructed only in that it describes the same enclosed
  70  *     2-dimensional area, but may use entirely different types
  71  *     and ordering of the path segments to do so.
  72  * </ul>
  73  * Interesting issues which are not always obvious when using
  74  * the {@code Area} include:
  75  * <ul>
  76  * <li>Creating an {@code Area} from an unclosed (open)
  77  *     {@code Shape} results in a closed outline in the
  78  *     {@code Area} object.
  79  * <li>Creating an {@code Area} from a {@code Shape}
  80  *     which encloses no area (even when "closed") produces an
  81  *     empty {@code Area}.  A common example of this issue
  82  *     is that producing an {@code Area} from a line will
  83  *     be empty since the line encloses no area.  An empty
  84  *     {@code Area} will iterate no geometry in its
  85  *     {@code PathIterator} objects.
  86  * <li>A self-intersecting {@code Shape} may be split into
  87  *     two (or more) sub-paths each enclosing one of the
  88  *     non-intersecting portions of the original path.
  89  * <li>An {@code Area} may take more path segments to
  90  *     describe the same geometry even when the original
  91  *     outline is simple and obvious.  The analysis that the
  92  *     {@code Area} class must perform on the path may
  93  *     not reflect the same concepts of "simple and obvious"
  94  *     as a human being perceives.
  95  * </ul>
  96  *
  97  * @since 1.2
  98  */
  99 public class Area implements Shape, Cloneable {
 100     private static Vector<Curve> EmptyCurves = new Vector<>();
 101 
 102     private Vector<Curve> curves;
 103 
 104     /**
 105      * Default constructor which creates an empty area.
 106      * @since 1.2
 107      */
 108     public Area() {
 109         curves = EmptyCurves;
 110     }
 111 
 112     /**
 113      * The {@code Area} class creates an area geometry from the
 114      * specified {@link Shape} object.  The geometry is explicitly
 115      * closed, if the {@code Shape} is not already closed.  The
 116      * fill rule (even-odd or winding) specified by the geometry of the
 117      * {@code Shape} is used to determine the resulting enclosed area.
 118      * @param s  the {@code Shape} from which the area is constructed
 119      * @throws NullPointerException if {@code s} is null
 120      * @since 1.2
 121      */
 122     public Area(Shape s) {
 123         if (s instanceof Area) {
 124             curves = ((Area) s).curves;
 125         } else {
 126             curves = pathToCurves(s.getPathIterator(null));
 127         }
 128     }
 129 
 130     private static Vector<Curve> pathToCurves(PathIterator pi) {
 131         Vector<Curve> curves = new Vector<>();
 132         int windingRule = pi.getWindingRule();
 133         // coords array is big enough for holding:
 134         //     coordinates returned from currentSegment (6)
 135         //     OR
 136         //         two subdivided quadratic curves (2+4+4=10)
 137         //         AND
 138         //             0-1 horizontal splitting parameters
 139         //             OR
 140         //             2 parametric equation derivative coefficients
 141         //     OR
 142         //         three subdivided cubic curves (2+6+6+6=20)
 143         //         AND
 144         //             0-2 horizontal splitting parameters
 145         //             OR
 146         //             3 parametric equation derivative coefficients
 147         double[] coords = new double[23];
 148         double movx = 0, movy = 0;
 149         double curx = 0, cury = 0;
 150         double newx, newy;
 151         while (!pi.isDone()) {
 152             switch (pi.currentSegment(coords)) {
 153             case PathIterator.SEG_MOVETO:
 154                 Curve.insertLine(curves, curx, cury, movx, movy);
 155                 curx = movx = coords[0];
 156                 cury = movy = coords[1];
 157                 Curve.insertMove(curves, movx, movy);
 158                 break;
 159             case PathIterator.SEG_LINETO:
 160                 newx = coords[0];
 161                 newy = coords[1];
 162                 Curve.insertLine(curves, curx, cury, newx, newy);
 163                 curx = newx;
 164                 cury = newy;
 165                 break;
 166             case PathIterator.SEG_QUADTO:
 167                 newx = coords[2];
 168                 newy = coords[3];
 169                 Curve.insertQuad(curves, curx, cury, coords);
 170                 curx = newx;
 171                 cury = newy;
 172                 break;
 173             case PathIterator.SEG_CUBICTO:
 174                 newx = coords[4];
 175                 newy = coords[5];
 176                 Curve.insertCubic(curves, curx, cury, coords);
 177                 curx = newx;
 178                 cury = newy;
 179                 break;
 180             case PathIterator.SEG_CLOSE:
 181                 Curve.insertLine(curves, curx, cury, movx, movy);
 182                 curx = movx;
 183                 cury = movy;
 184                 break;
 185             }
 186             pi.next();
 187         }
 188         Curve.insertLine(curves, curx, cury, movx, movy);
 189         AreaOp operator;
 190         if (windingRule == PathIterator.WIND_EVEN_ODD) {
 191             operator = new AreaOp.EOWindOp();
 192         } else {
 193             operator = new AreaOp.NZWindOp();
 194         }
 195         return operator.calculate(curves, EmptyCurves);
 196     }
 197 
 198     /**
 199      * Adds the shape of the specified {@code Area} to the
 200      * shape of this {@code Area}.
 201      * The resulting shape of this {@code Area} will include
 202      * the union of both shapes, or all areas that were contained
 203      * in either this or the specified {@code Area}.
 204      * <pre>
 205      *     // Example:
 206      *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
 207      *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
 208      *     a1.add(a2);
 209      *
 210      *        a1(before)     +         a2         =     a1(after)
 211      *
 212      *     ################     ################     ################
 213      *     ##############         ##############     ################
 214      *     ############             ############     ################
 215      *     ##########                 ##########     ################
 216      *     ########                     ########     ################
 217      *     ######                         ######     ######    ######
 218      *     ####                             ####     ####        ####
 219      *     ##                                 ##     ##            ##
 220      * </pre>
 221      * @param   rhs  the {@code Area} to be added to the
 222      *          current shape
 223      * @throws NullPointerException if {@code rhs} is null
 224      * @since 1.2
 225      */
 226     public void add(Area rhs) {
 227         curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
 228         invalidateBounds();
 229     }
 230 
 231     /**
 232      * Subtracts the shape of the specified {@code Area} from the
 233      * shape of this {@code Area}.
 234      * The resulting shape of this {@code Area} will include
 235      * areas that were contained only in this {@code Area}
 236      * and not in the specified {@code Area}.
 237      * <pre>
 238      *     // Example:
 239      *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
 240      *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
 241      *     a1.subtract(a2);
 242      *
 243      *        a1(before)     -         a2         =     a1(after)
 244      *
 245      *     ################     ################
 246      *     ##############         ##############     ##
 247      *     ############             ############     ####
 248      *     ##########                 ##########     ######
 249      *     ########                     ########     ########
 250      *     ######                         ######     ######
 251      *     ####                             ####     ####
 252      *     ##                                 ##     ##
 253      * </pre>
 254      * @param   rhs  the {@code Area} to be subtracted from the
 255      *          current shape
 256      * @throws NullPointerException if {@code rhs} is null
 257      * @since 1.2
 258      */
 259     public void subtract(Area rhs) {
 260         curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
 261         invalidateBounds();
 262     }
 263 
 264     /**
 265      * Sets the shape of this {@code Area} to the intersection of
 266      * its current shape and the shape of the specified {@code Area}.
 267      * The resulting shape of this {@code Area} will include
 268      * only areas that were contained in both this {@code Area}
 269      * and also in the specified {@code Area}.
 270      * <pre>
 271      *     // Example:
 272      *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
 273      *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
 274      *     a1.intersect(a2);
 275      *
 276      *      a1(before)   intersect     a2         =     a1(after)
 277      *
 278      *     ################     ################     ################
 279      *     ##############         ##############       ############
 280      *     ############             ############         ########
 281      *     ##########                 ##########           ####
 282      *     ########                     ########
 283      *     ######                         ######
 284      *     ####                             ####
 285      *     ##                                 ##
 286      * </pre>
 287      * @param   rhs  the {@code Area} to be intersected with this
 288      *          {@code Area}
 289      * @throws NullPointerException if {@code rhs} is null
 290      * @since 1.2
 291      */
 292     public void intersect(Area rhs) {
 293         curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
 294         invalidateBounds();
 295     }
 296 
 297     /**
 298      * Sets the shape of this {@code Area} to be the combined area
 299      * of its current shape and the shape of the specified {@code Area},
 300      * minus their intersection.
 301      * The resulting shape of this {@code Area} will include
 302      * only areas that were contained in either this {@code Area}
 303      * or in the specified {@code Area}, but not in both.
 304      * <pre>
 305      *     // Example:
 306      *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
 307      *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
 308      *     a1.exclusiveOr(a2);
 309      *
 310      *        a1(before)    xor        a2         =     a1(after)
 311      *
 312      *     ################     ################
 313      *     ##############         ##############     ##            ##
 314      *     ############             ############     ####        ####
 315      *     ##########                 ##########     ######    ######
 316      *     ########                     ########     ################
 317      *     ######                         ######     ######    ######
 318      *     ####                             ####     ####        ####
 319      *     ##                                 ##     ##            ##
 320      * </pre>
 321      * @param   rhs  the {@code Area} to be exclusive ORed with this
 322      *          {@code Area}.
 323      * @throws NullPointerException if {@code rhs} is null
 324      * @since 1.2
 325      */
 326     public void exclusiveOr(Area rhs) {
 327         curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
 328         invalidateBounds();
 329     }
 330 
 331     /**
 332      * Removes all of the geometry from this {@code Area} and
 333      * restores it to an empty area.
 334      * @since 1.2
 335      */
 336     public void reset() {
 337         curves = new Vector<>();
 338         invalidateBounds();
 339     }
 340 
 341     /**
 342      * Tests whether this {@code Area} object encloses any area.
 343      * @return    {@code true} if this {@code Area} object
 344      * represents an empty area; {@code false} otherwise.
 345      * @since 1.2
 346      */
 347     public boolean isEmpty() {
 348         return (curves.size() == 0);
 349     }
 350 
 351     /**
 352      * Tests whether this {@code Area} consists entirely of
 353      * straight edged polygonal geometry.
 354      * @return    {@code true} if the geometry of this
 355      * {@code Area} consists entirely of line segments;
 356      * {@code false} otherwise.
 357      * @since 1.2
 358      */
 359     public boolean isPolygonal() {
 360         Enumeration<Curve> enum_ = curves.elements();
 361         while (enum_.hasMoreElements()) {
 362             if (enum_.nextElement().getOrder() > 1) {
 363                 return false;
 364             }
 365         }
 366         return true;
 367     }
 368 
 369     /**
 370      * Tests whether this {@code Area} is rectangular in shape.
 371      * @return    {@code true} if the geometry of this
 372      * {@code Area} is rectangular in shape; {@code false}
 373      * otherwise.
 374      * @since 1.2
 375      */
 376     public boolean isRectangular() {
 377         int size = curves.size();
 378         if (size == 0) {
 379             return true;
 380         }
 381         if (size > 3) {
 382             return false;
 383         }
 384         Curve c1 = curves.get(1);
 385         Curve c2 = curves.get(2);
 386         if (c1.getOrder() != 1 || c2.getOrder() != 1) {
 387             return false;
 388         }
 389         if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
 390             return false;
 391         }
 392         if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
 393             // One might be able to prove that this is impossible...
 394             return false;
 395         }
 396         return true;
 397     }
 398 
 399     /**
 400      * Tests whether this {@code Area} is comprised of a single
 401      * closed subpath.  This method returns {@code true} if the
 402      * path contains 0 or 1 subpaths, or {@code false} if the path
 403      * contains more than 1 subpath.  The subpaths are counted by the
 404      * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO}  segments
 405      * that appear in the path.
 406      * @return    {@code true} if the {@code Area} is comprised
 407      * of a single basic geometry; {@code false} otherwise.
 408      * @since 1.2
 409      */
 410     public boolean isSingular() {
 411         if (curves.size() < 3) {
 412             return true;
 413         }
 414         Enumeration<Curve> enum_ = curves.elements();
 415         enum_.nextElement(); // First Order0 "moveto"
 416         while (enum_.hasMoreElements()) {
 417             if (enum_.nextElement().getOrder() == 0) {
 418                 return false;
 419             }
 420         }
 421         return true;
 422     }
 423 
 424     private Rectangle2D cachedBounds;
 425     private void invalidateBounds() {
 426         cachedBounds = null;
 427     }
 428     private Rectangle2D getCachedBounds() {
 429         if (cachedBounds != null) {
 430             return cachedBounds;
 431         }
 432         Rectangle2D r = new Rectangle2D.Double();
 433         if (curves.size() > 0) {
 434             Curve c = curves.get(0);
 435             // First point is always an order 0 curve (moveto)
 436             r.setRect(c.getX0(), c.getY0(), 0, 0);
 437             for (int i = 1; i < curves.size(); i++) {
 438                 curves.get(i).enlarge(r);
 439             }
 440         }
 441         return (cachedBounds = r);
 442     }
 443 
 444     /**
 445      * Returns a high precision bounding {@link Rectangle2D} that
 446      * completely encloses this {@code Area}.
 447      * <p>
 448      * The Area class will attempt to return the tightest bounding
 449      * box possible for the Shape.  The bounding box will not be
 450      * padded to include the control points of curves in the outline
 451      * of the Shape, but should tightly fit the actual geometry of
 452      * the outline itself.
 453      * @return    the bounding {@code Rectangle2D} for the
 454      * {@code Area}.
 455      * @since 1.2
 456      */
 457     public Rectangle2D getBounds2D() {
 458         return getCachedBounds().getBounds2D();
 459     }
 460 
 461     /**
 462      * Returns a bounding {@link Rectangle} that completely encloses
 463      * this {@code Area}.
 464      * <p>
 465      * The Area class will attempt to return the tightest bounding
 466      * box possible for the Shape.  The bounding box will not be
 467      * padded to include the control points of curves in the outline
 468      * of the Shape, but should tightly fit the actual geometry of
 469      * the outline itself.  Since the returned object represents
 470      * the bounding box with integers, the bounding box can only be
 471      * as tight as the nearest integer coordinates that encompass
 472      * the geometry of the Shape.
 473      * @return    the bounding {@code Rectangle} for the
 474      * {@code Area}.
 475      * @since 1.2
 476      */
 477     public Rectangle getBounds() {
 478         return getCachedBounds().getBounds();
 479     }
 480 
 481     /**
 482      * Returns an exact copy of this {@code Area} object.
 483      * @return    Created clone object
 484      * @since 1.2
 485      */
 486     public Object clone() {
 487         return new Area(this);
 488     }
 489 
 490     /**
 491      * Tests whether the geometries of the two {@code Area} objects
 492      * are equal.
 493      * This method will return false if the argument is null.
 494      * @param   other  the {@code Area} to be compared to this
 495      *          {@code Area}
 496      * @return  {@code true} if the two geometries are equal;
 497      *          {@code false} otherwise.
 498      * @since 1.2
 499      */
 500     public boolean equals(Area other) {
 501         // REMIND: A *much* simpler operation should be possible...
 502         // Should be able to do a curve-wise comparison since all Areas
 503         // should evaluate their curves in the same top-down order.
 504         if (other == this) {
 505             return true;
 506         }
 507         if (other == null) {
 508             return false;
 509         }
 510         Vector<Curve> c = new AreaOp.XorOp().calculate(this.curves, other.curves);
 511         return c.isEmpty();
 512     }
 513 
 514     /**
 515      * Transforms the geometry of this {@code Area} using the specified
 516      * {@link AffineTransform}.  The geometry is transformed in place, which
 517      * permanently changes the enclosed area defined by this object.
 518      * @param t  the transformation used to transform the area
 519      * @throws NullPointerException if {@code t} is null
 520      * @since 1.2
 521      */
 522     public void transform(AffineTransform t) {
 523         if (t == null) {
 524             throw new NullPointerException("transform must not be null");
 525         }
 526         // REMIND: A simpler operation can be performed for some types
 527         // of transform.
 528         curves = pathToCurves(getPathIterator(t));
 529         invalidateBounds();
 530     }
 531 
 532     /**
 533      * Creates a new {@code Area} object that contains the same
 534      * geometry as this {@code Area} transformed by the specified
 535      * {@code AffineTransform}.  This {@code Area} object
 536      * is unchanged.
 537      * @param t  the specified {@code AffineTransform} used to transform
 538      *           the new {@code Area}
 539      * @throws NullPointerException if {@code t} is null
 540      * @return   a new {@code Area} object representing the transformed
 541      *           geometry.
 542      * @since 1.2
 543      */
 544     public Area createTransformedArea(AffineTransform t) {
 545         Area a = new Area(this);
 546         a.transform(t);
 547         return a;
 548     }
 549 
 550     /**
 551      * {@inheritDoc}
 552      * @since 1.2
 553      */
 554     public boolean contains(double x, double y) {
 555         if (!getCachedBounds().contains(x, y)) {
 556             return false;
 557         }
 558         Enumeration<Curve> enum_ = curves.elements();
 559         int crossings = 0;
 560         while (enum_.hasMoreElements()) {
 561             Curve c = enum_.nextElement();
 562             crossings += c.crossingsFor(x, y);
 563         }
 564         return ((crossings & 1) == 1);
 565     }
 566 
 567     /**
 568      * {@inheritDoc}
 569      * @since 1.2
 570      */
 571     public boolean contains(Point2D p) {
 572         return contains(p.getX(), p.getY());
 573     }
 574 
 575     /**
 576      * {@inheritDoc}
 577      * @since 1.2
 578      */
 579     public boolean contains(double x, double y, double w, double h) {
 580         if (w < 0 || h < 0) {
 581             return false;
 582         }
 583         if (!getCachedBounds().contains(x, y, w, h)) {
 584             return false;
 585         }
 586         Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
 587         return (c != null && c.covers(y, y+h));
 588     }
 589 
 590     /**
 591      * {@inheritDoc}
 592      * @since 1.2
 593      */
 594     public boolean contains(Rectangle2D r) {
 595         return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
 596     }
 597 
 598     /**
 599      * {@inheritDoc}
 600      * @since 1.2
 601      */
 602     public boolean intersects(double x, double y, double w, double h) {
 603         if (w < 0 || h < 0) {
 604             return false;
 605         }
 606         if (!getCachedBounds().intersects(x, y, w, h)) {
 607             return false;
 608         }
 609         Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
 610         return (c == null || !c.isEmpty());
 611     }
 612 
 613     /**
 614      * {@inheritDoc}
 615      * @since 1.2
 616      */
 617     public boolean intersects(Rectangle2D r) {
 618         return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
 619     }
 620 
 621     /**
 622      * Creates a {@link PathIterator} for the outline of this
 623      * {@code Area} object.  This {@code Area} object is unchanged.
 624      * @param at an optional {@code AffineTransform} to be applied to
 625      * the coordinates as they are returned in the iteration, or
 626      * {@code null} if untransformed coordinates are desired
 627      * @return    the {@code PathIterator} object that returns the
 628      *          geometry of the outline of this {@code Area}, one
 629      *          segment at a time.
 630      * @since 1.2
 631      */
 632     public PathIterator getPathIterator(AffineTransform at) {
 633         return new AreaIterator(curves, at);
 634     }
 635 
 636     /**
 637      * Creates a {@code PathIterator} for the flattened outline of
 638      * this {@code Area} object.  Only uncurved path segments
 639      * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
 640      * types are returned by the iterator.  This {@code Area}
 641      * object is unchanged.
 642      * @param at an optional {@code AffineTransform} to be
 643      * applied to the coordinates as they are returned in the
 644      * iteration, or {@code null} if untransformed coordinates
 645      * are desired
 646      * @param flatness the maximum amount that the control points
 647      * for a given curve can vary from colinear before a subdivided
 648      * curve is replaced by a straight line connecting the end points
 649      * @return    the {@code PathIterator} object that returns the
 650      * geometry of the outline of this {@code Area}, one segment
 651      * at a time.
 652      * @since 1.2
 653      */
 654     public PathIterator getPathIterator(AffineTransform at, double flatness) {
 655         return new FlatteningPathIterator(getPathIterator(at), flatness);
 656     }
 657 }
 658 
 659 class AreaIterator implements PathIterator {
 660     private AffineTransform transform;
 661     private Vector<Curve> curves;
 662     private int index;
 663     private Curve prevcurve;
 664     private Curve thiscurve;
 665 
 666     public AreaIterator(Vector<Curve> curves, AffineTransform at) {
 667         this.curves = curves;
 668         this.transform = at;
 669         if (curves.size() >= 1) {
 670             thiscurve = curves.get(0);
 671         }
 672     }
 673 
 674     public int getWindingRule() {
 675         // REMIND: Which is better, EVEN_ODD or NON_ZERO?
 676         //         The paths calculated could be classified either way.
 677         //return WIND_EVEN_ODD;
 678         return WIND_NON_ZERO;
 679     }
 680 
 681     public boolean isDone() {
 682         return (prevcurve == null && thiscurve == null);
 683     }
 684 
 685     public void next() {
 686         if (prevcurve != null) {
 687             prevcurve = null;
 688         } else {
 689             prevcurve = thiscurve;
 690             index++;
 691             if (index < curves.size()) {
 692                 thiscurve = curves.get(index);
 693                 if (thiscurve.getOrder() != 0 &&
 694                     prevcurve.getX1() == thiscurve.getX0() &&
 695                     prevcurve.getY1() == thiscurve.getY0())
 696                 {
 697                     prevcurve = null;
 698                 }
 699             } else {
 700                 thiscurve = null;
 701             }
 702         }
 703     }
 704 
 705     public int currentSegment(float[] coords) {
 706         double[] dcoords = new double[6];
 707         int segtype = currentSegment(dcoords);
 708         int numpoints = (segtype == SEG_CLOSE ? 0
 709                          : (segtype == SEG_QUADTO ? 2
 710                             : (segtype == SEG_CUBICTO ? 3
 711                                : 1)));
 712         for (int i = 0; i < numpoints * 2; i++) {
 713             coords[i] = (float) dcoords[i];
 714         }
 715         return segtype;
 716     }
 717 
 718     public int currentSegment(double[] coords) {
 719         int segtype;
 720         int numpoints;
 721         if (prevcurve != null) {
 722             // Need to finish off junction between curves
 723             if (thiscurve == null || thiscurve.getOrder() == 0) {
 724                 return SEG_CLOSE;
 725             }
 726             coords[0] = thiscurve.getX0();
 727             coords[1] = thiscurve.getY0();
 728             segtype = SEG_LINETO;
 729             numpoints = 1;
 730         } else if (thiscurve == null) {
 731             throw new NoSuchElementException("area iterator out of bounds");
 732         } else {
 733             segtype = thiscurve.getSegment(coords);
 734             numpoints = thiscurve.getOrder();
 735             if (numpoints == 0) {
 736                 numpoints = 1;
 737             }
 738         }
 739         if (transform != null) {
 740             transform.transform(coords, 0, coords, 0, numpoints);
 741         }
 742         return segtype;
 743     }
 744 }