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      */
Area()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      */
Area(Shape s)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 
pathToCurves(PathIterator pi)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      */
add(Area rhs)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      */
subtract(Area rhs)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      */
intersect(Area rhs)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      */
exclusiveOr(Area rhs)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      */
reset()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      */
isEmpty()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      */
isPolygonal()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      */
isRectangular()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      */
isSingular()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;
invalidateBounds()425     private void invalidateBounds() {
426         cachedBounds = null;
427     }
getCachedBounds()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      */
getBounds2D()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      */
getBounds()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      */
clone()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      */
equals(Area other)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      */
transform(AffineTransform t)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      */
createTransformedArea(AffineTransform t)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      */
contains(double x, double y)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      */
contains(Point2D p)571     public boolean contains(Point2D p) {
572         return contains(p.getX(), p.getY());
573     }
574 
575     /**
576      * {@inheritDoc}
577      * @since 1.2
578      */
contains(double x, double y, double w, double h)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      */
contains(Rectangle2D r)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      */
intersects(double x, double y, double w, double h)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      */
intersects(Rectangle2D r)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      */
getPathIterator(AffineTransform at)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      */
getPathIterator(AffineTransform at, double flatness)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 
AreaIterator(Vector<Curve> curves, AffineTransform at)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 
getWindingRule()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 
isDone()681     public boolean isDone() {
682         return (prevcurve == null && thiscurve == null);
683     }
684 
next()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 
currentSegment(float[] coords)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 
currentSegment(double[] coords)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 }
745