View Javadoc
1   /*
2    * Copyright (c) 1998, 2006, 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</code> object stores and manipulates a
39   * resolution-independent description of an enclosed area of
40   * 2-dimensional space.
41   * <code>Area</code> objects can be transformed and can perform
42   * various Constructive Area Geometry (CAG) operations when combined
43   * with other <code>Area</code> 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</code> class implements the <code>Shape</code>
51   * interface and provides full support for all of its hit-testing
52   * and path iteration facilities, but an <code>Area</code> 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</code> objects constructed from unclosed paths
57   *     are implicitly closed during construction as if those paths
58   *     had been filled by the <code>Graphics2D.fill</code> 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</code> 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</code> include:
75   * <ul>
76   * <li>Creating an <code>Area</code> from an unclosed (open)
77   *     <code>Shape</code> results in a closed outline in the
78   *     <code>Area</code> object.
79   * <li>Creating an <code>Area</code> from a <code>Shape</code>
80   *     which encloses no area (even when "closed") produces an
81   *     empty <code>Area</code>.  A common example of this issue
82   *     is that producing an <code>Area</code> from a line will
83   *     be empty since the line encloses no area.  An empty
84   *     <code>Area</code> will iterate no geometry in its
85   *     <code>PathIterator</code> objects.
86   * <li>A self-intersecting <code>Shape</code> 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</code> 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</code> 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 EmptyCurves = new Vector();
101 
102     private Vector 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</code> class creates an area geometry from the
114      * specified {@link Shape} object.  The geometry is explicitly
115      * closed, if the <code>Shape</code> is not already closed.  The
116      * fill rule (even-odd or winding) specified by the geometry of the
117      * <code>Shape</code> is used to determine the resulting enclosed area.
118      * @param s  the <code>Shape</code> from which the area is constructed
119      * @throws NullPointerException if <code>s</code> 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 pathToCurves(PathIterator pi) {
131         Vector 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</code> to the
200      * shape of this <code>Area</code>.
201      * The resulting shape of this <code>Area</code> will include
202      * the union of both shapes, or all areas that were contained
203      * in either this or the specified <code>Area</code>.
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</code> to be added to the
222      *          current shape
223      * @throws NullPointerException if <code>rhs</code> 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</code> from the
233      * shape of this <code>Area</code>.
234      * The resulting shape of this <code>Area</code> will include
235      * areas that were contained only in this <code>Area</code>
236      * and not in the specified <code>Area</code>.
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</code> to be subtracted from the
255      *          current shape
256      * @throws NullPointerException if <code>rhs</code> 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</code> to the intersection of
266      * its current shape and the shape of the specified <code>Area</code>.
267      * The resulting shape of this <code>Area</code> will include
268      * only areas that were contained in both this <code>Area</code>
269      * and also in the specified <code>Area</code>.
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</code> to be intersected with this
288      *          <code>Area</code>
289      * @throws NullPointerException if <code>rhs</code> 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</code> to be the combined area
299      * of its current shape and the shape of the specified <code>Area</code>,
300      * minus their intersection.
301      * The resulting shape of this <code>Area</code> will include
302      * only areas that were contained in either this <code>Area</code>
303      * or in the specified <code>Area</code>, 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</code> to be exclusive ORed with this
322      *          <code>Area</code>.
323      * @throws NullPointerException if <code>rhs</code> 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</code> 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</code> object encloses any area.
343      * @return    <code>true</code> if this <code>Area</code> object
344      * represents an empty area; <code>false</code> otherwise.
345      * @since 1.2
346      */
347     public boolean isEmpty() {
348         return (curves.size() == 0);
349     }
350 
351     /**
352      * Tests whether this <code>Area</code> consists entirely of
353      * straight edged polygonal geometry.
354      * @return    <code>true</code> if the geometry of this
355      * <code>Area</code> consists entirely of line segments;
356      * <code>false</code> otherwise.
357      * @since 1.2
358      */
359     public boolean isPolygonal() {
360         Enumeration enum_ = curves.elements();
361         while (enum_.hasMoreElements()) {
362             if (((Curve) enum_.nextElement()).getOrder() > 1) {
363                 return false;
364             }
365         }
366         return true;
367     }
368 
369     /**
370      * Tests whether this <code>Area</code> is rectangular in shape.
371      * @return    <code>true</code> if the geometry of this
372      * <code>Area</code> is rectangular in shape; <code>false</code>
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 = (Curve) curves.get(1);
385         Curve c2 = (Curve) 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</code> is comprised of a single
401      * closed subpath.  This method returns <code>true</code> if the
402      * path contains 0 or 1 subpaths, or <code>false</code> 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</code> if the <code>Area</code> is comprised
407      * of a single basic geometry; <code>false</code> otherwise.
408      * @since 1.2
409      */
410     public boolean isSingular() {
411         if (curves.size() < 3) {
412             return true;
413         }
414         Enumeration enum_ = curves.elements();
415         enum_.nextElement(); // First Order0 "moveto"
416         while (enum_.hasMoreElements()) {
417             if (((Curve) 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 = (Curve) 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                 ((Curve) 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</code>.
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</code> for the
454      * <code>Area</code>.
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</code>.
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</code> for the
474      * <code>Area</code>.
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</code> 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</code> objects
492      * are equal.
493      * This method will return false if the argument is null.
494      * @param   other  the <code>Area</code> to be compared to this
495      *          <code>Area</code>
496      * @return  <code>true</code> if the two geometries are equal;
497      *          <code>false</code> 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 c = new AreaOp.XorOp().calculate(this.curves, other.curves);
511         return c.isEmpty();
512     }
513 
514     /**
515      * Transforms the geometry of this <code>Area</code> 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</code> 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</code> object that contains the same
534      * geometry as this <code>Area</code> transformed by the specified
535      * <code>AffineTransform</code>.  This <code>Area</code> object
536      * is unchanged.
537      * @param t  the specified <code>AffineTransform</code> used to transform
538      *           the new <code>Area</code>
539      * @throws NullPointerException if <code>t</code> is null
540      * @return   a new <code>Area</code> 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 enum_ = curves.elements();
559         int crossings = 0;
560         while (enum_.hasMoreElements()) {
561             Curve c = (Curve) 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</code> object.  This <code>Area</code> object is unchanged.
624      * @param at an optional <code>AffineTransform</code> to be applied to
625      * the coordinates as they are returned in the iteration, or
626      * <code>null</code> if untransformed coordinates are desired
627      * @return    the <code>PathIterator</code> object that returns the
628      *          geometry of the outline of this <code>Area</code>, 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</code> for the flattened outline of
638      * this <code>Area</code> 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</code>
641      * object is unchanged.
642      * @param at an optional <code>AffineTransform</code> to be
643      * applied to the coordinates as they are returned in the
644      * iteration, or <code>null</code> 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</code> object that returns the
650      * geometry of the outline of this <code>Area</code>, 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 curves;
662     private int index;
663     private Curve prevcurve;
664     private Curve thiscurve;
665 
666     public AreaIterator(Vector curves, AffineTransform at) {
667         this.curves = curves;
668         this.transform = at;
669         if (curves.size() >= 1) {
670             thiscurve = (Curve) 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 = (Curve) 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 }