1/*
2 * Copyright (c) 1998, 2014, 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
26package java.awt.geom;
27
28import java.awt.Shape;
29import java.awt.Rectangle;
30import java.util.Vector;
31import java.util.Enumeration;
32import java.util.NoSuchElementException;
33import sun.awt.geom.Curve;
34import sun.awt.geom.Crossings;
35import 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 */
99public 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
659class 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}
745