001/*
002 * @(#)Area.java        1.21 06/02/24
003 *
004 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
005 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
006 */
007package sec.sun.awt.geom;
008
009import armyc2.c2sd.graphics2d.*;
010import sec.geo.ShapeObject;
011
012/**
013 * An <code>Area</code> object stores and manipulates a resolution-independent
014 * description of an enclosed area of 2-dimensional space. <code>Area</code>
015 * objects can be transformed and can perform various Constructive Area Geometry
016 * (CAG) operations when combined with other <code>Area</code> objects. The CAG
017 * operations include area  {@link #add addition}, {@link #subtract subtraction},
018 * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}. See
019 * the linked method documentation for examples of the various operations.
020 * <p>
021 * The <code>Area</code> class implements the <code>Shape</code> interface and
022 * provides full support for all of its hit-testing and path iteration
023 * facilities, but an <code>Area</code> is more specific than a generalized path
024 * in a number of ways:
025 * <ul>
026 * <li>Only closed paths and sub-paths are stored. <code>Area</code> objects
027 * constructed from unclosed paths are implicitly closed during construction as
028 * if those paths had been filled by the <code>Graphics2D.fill</code> method.
029 * <li>The interiors of the individual stored sub-paths are all non-empty and
030 * non-overlapping. Paths are decomposed during construction into separate
031 * component non-overlapping parts, empty pieces of the path are discarded, and
032 * then these non-empty and non-overlapping properties are maintained through
033 * all subsequent CAG operations. Outlines of different component sub-paths may
034 * touch each other, as long as they do not cross so that their enclosed areas
035 * overlap.
036 * <li>The geometry of the path describing the outline of the <code>Area</code>
037 * resembles the path from which it was constructed only in that it describes
038 * the same enclosed 2-dimensional area, but may use entirely different types
039 * and ordering of the path segments to do so.
040 * </ul>
041 * Interesting issues which are not always obvious when using the
042 * <code>Area</code> include:
043 * <ul>
044 * <li>Creating an <code>Area</code> from an unclosed (open) <code>Shape</code>
045 * results in a closed outline in the <code>Area</code> object.
046 * <li>Creating an <code>Area</code> from a <code>Shape</code> which encloses no
047 * area (even when "closed") produces an empty <code>Area</code>. A common
048 * example of this issue is that producing an <code>Area</code> from a line will
049 * be empty since the line encloses no area. An empty <code>Area</code> will
050 * iterate no geometry in its <code>PathIterator</code> objects.
051 * <li>A self-intersecting <code>Shape</code> may be split into two (or more)
052 * sub-paths each enclosing one of the non-intersecting portions of the original
053 * path.
054 * <li>An <code>Area</code> may take more path segments to describe the same
055 * geometry even when the original outline is simple and obvious. The analysis
056 * that the <code>Area</code> class must perform on the path may not reflect the
057 * same concepts of "simple and obvious" as a human being perceives.
058 * </ul>
059 *
060 * @since 1.2
061 */
062public class Area /* implements Shape, Cloneable */ {
063
064    private static Vector EmptyCurves = new Vector();
065    private static final boolean normalizeGeoPoints = true;
066    private Vector curves;
067
068    /**
069     * Default constructor which creates an empty area.
070     *
071     * @since 1.2
072     */
073    public Area() {
074        curves = EmptyCurves;
075    }
076
077    /**
078     * The <code>Area</code> class creates an area geometry from the specified
079     * {@link Shape} object. The geometry is explicitly closed, if the
080     * <code>Shape</code> is not already closed. The fill rule (even-odd or
081     * winding) specified by the geometry of the <code>Shape</code> is used to
082     * determine the resulting enclosed area.
083     *
084     * @param s the <code>Shape</code> from which the area is constructed
085     * @throws NullPointerException if <code>s</code> is null
086     * @since 1.2
087     */
088    public Area(ShapeObject s) {
089//      if (s instanceof Area) {
090//          curves = ((Area) s).curves;
091//      } 
092//        else 
093//        {
094        curves = pathToCurves(s.getPathIterator(null));
095//        }
096    }
097
098    private static Vector pathToCurves(PathIterator pi) {
099        Vector curves = new Vector();
100        int windingRule = pi.getWindingRule();
101        // coords array is big enough for holding:
102        //     coordinates returned from currentSegment (6)
103        //     OR
104        //         two subdivided quadratic curves (2+4+4=10)
105        //         AND
106        //             0-1 horizontal splitting parameters
107        //             OR
108        //             2 parametric equation derivative coefficients
109        //     OR
110        //         three subdivided cubic curves (2+6+6+6=20)
111        //         AND
112        //             0-2 horizontal splitting parameters
113        //             OR
114        //             3 parametric equation derivative coefficients
115        double coords[] = new double[23];
116        double movx = 0, movy = 0;
117        double curx = 0, cury = 0;
118        double newx, newy;
119        while (!pi.isDone()) {
120            switch (pi.currentSegment(coords)) {
121                case PathIterator.SEG_MOVETO:
122                    if (normalizeGeoPoints == true) {
123                        if (movx > 0) {
124                            movx -= 360;
125                        }
126                        if (curx > 0) {
127                            curx -= 360;
128                        }
129                    }
130                    Curve.insertLine(curves, curx, cury, movx, movy);
131                    curx = movx = coords[0];
132                    cury = movy = coords[1];
133                    if (normalizeGeoPoints == true) {
134                        if (movx > 0) {
135                            movx -= 360;
136                        }
137                    }
138                    Curve.insertMove(curves, movx, movy);
139                    break;
140                case PathIterator.SEG_LINETO:
141                    newx = coords[0];
142                    newy = coords[1];
143                    if (normalizeGeoPoints == true) {
144                        if (newx > 0) {
145                            newx -= 360;
146                        }
147                        if (curx > 0) {
148                            curx -= 360;
149                        }
150                    }
151                    Curve.insertLine(curves, curx, cury, newx, newy);
152                    curx = newx;
153                    cury = newy;
154                    break;
155                case PathIterator.SEG_QUADTO:
156                    newx = coords[2];
157                    newy = coords[3];
158                    if (normalizeGeoPoints == true) {
159                        if (curx > 0) {
160                            curx -= 360;
161                        }
162                    }
163                    Curve.insertQuad(curves, curx, cury, coords);
164                    curx = newx;
165                    cury = newy;
166                    break;
167                case PathIterator.SEG_CUBICTO:
168                    newx = coords[4];
169                    newy = coords[5];
170                    if (normalizeGeoPoints == true) {
171                        if (curx > 0) {
172                            curx -= 360;
173                        }
174                    }
175                    Curve.insertCubic(curves, curx, cury, coords);
176                    curx = newx;
177                    cury = newy;
178                    break;
179                case PathIterator.SEG_CLOSE:
180                    if (normalizeGeoPoints == true) {
181                        if (movx > 0) {
182                            movx -= 360;
183                        }
184                        if (curx > 0) {
185                            curx -= 360;
186                        }
187                    }
188                    Curve.insertLine(curves, curx, cury, movx, movy);
189                    curx = movx;
190                    cury = movy;
191                    break;
192            }
193            pi.next();
194        }
195        if (normalizeGeoPoints == true) {
196            if (movx > 0) {
197                movx -= 360;
198            }
199            if (curx > 0) {
200                curx -= 360;
201            }
202        }
203        Curve.insertLine(curves, curx, cury, movx, movy);
204        AreaOp2 operator2 = null;
205        if (windingRule == PathIterator.WIND_EVEN_ODD) {
206            operator2 = new AreaOp2(AreaOp2.EOWINDOP);
207        } else {
208            operator2 = new AreaOp2(AreaOp2.NZWINDOP);
209        }
210        return operator2.calculate(curves, EmptyCurves);
211    }
212
213    /**
214     * Adds the shape of the specified <code>Area</code> to the shape of this
215     * <code>Area</code>. The resulting shape of this <code>Area</code> will
216     * include the union of both shapes, or all areas that were contained in
217     * either this or the specified <code>Area</code>.
218     * <pre>
219     *     // Example:
220     *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
221     *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
222     *     a1.add(a2);
223     *
224     *        a1(before)     +         a2         =     a1(after)
225     *
226     *     ################     ################     ################
227     *     ##############         ##############     ################
228     *     ############             ############     ################
229     *     ##########                 ##########     ################
230     *     ########                     ########     ################
231     *     ######                         ######     ######    ######
232     *     ####                             ####     ####        ####
233     *     ##                                 ##     ##            ##
234     * </pre>
235     *
236     * @param rhs the <code>Area</code> to be added to the current shape
237     * @throws NullPointerException if <code>rhs</code> is null
238     * @since 1.2
239     */
240    public void add(Area rhs) {
241        //curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
242        //curves = new AddOp().calculate(this.curves, rhs.curves);
243        curves = new SomeOp(SomeOp.ADDOP).calculate(this.curves, rhs.curves);
244        invalidateBounds();
245    }
246
247    /**
248     * Subtracts the shape of the specified <code>Area</code> from the shape of
249     * this <code>Area</code>. The resulting shape of this <code>Area</code>
250     * will include areas that were contained only in this <code>Area</code> and
251     * not in the specified <code>Area</code>.
252     * <pre>
253     *     // Example:
254     *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
255     *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
256     *     a1.subtract(a2);
257     *
258     *        a1(before)     -         a2         =     a1(after)
259     *
260     *     ################     ################
261     *     ##############         ##############     ##
262     *     ############             ############     ####
263     *     ##########                 ##########     ######
264     *     ########                     ########     ########
265     *     ######                         ######     ######
266     *     ####                             ####     ####
267     *     ##                                 ##     ##
268     * </pre>
269     *
270     * @param rhs the <code>Area</code> to be subtracted from the current shape
271     * @throws NullPointerException if <code>rhs</code> is null
272     * @since 1.2
273     */
274    public void subtract(Area rhs) {
275        //curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
276        curves = new SomeOp(SomeOp.SUBOP).calculate(this.curves, rhs.curves);
277        invalidateBounds();
278    }
279
280    /**
281     * Sets the shape of this <code>Area</code> to the intersection of its
282     * current shape and the shape of the specified <code>Area</code>. The
283     * resulting shape of this <code>Area</code> will include only areas that
284     * were contained in both this <code>Area</code> and also in the specified
285     * <code>Area</code>.
286     * <pre>
287     *     // Example:
288     *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
289     *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
290     *     a1.intersect(a2);
291     *
292     *      a1(before)   intersect     a2         =     a1(after)
293     *
294     *     ################     ################     ################
295     *     ##############         ##############       ############
296     *     ############             ############         ########
297     *     ##########                 ##########           ####
298     *     ########                     ########
299     *     ######                         ######
300     *     ####                             ####
301     *     ##                                 ##
302     * </pre>
303     *
304     * @param rhs the <code>Area</code> to be intersected with this
305     * <code>Area</code>
306     * @throws NullPointerException if <code>rhs</code> is null
307     * @since 1.2
308     */
309    public void intersect(Area rhs) {
310        //curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
311        curves = new SomeOp(SomeOp.INTOP).calculate(this.curves, rhs.curves);
312        invalidateBounds();
313    }
314
315    /**
316     * Sets the shape of this <code>Area</code> to be the combined area of its
317     * current shape and the shape of the specified <code>Area</code>, minus
318     * their intersection. The resulting shape of this <code>Area</code> will
319     * include only areas that were contained in either this <code>Area</code>
320     * or in the specified <code>Area</code>, but not in both.
321     * <pre>
322     *     // Example:
323     *     Area a1 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 0,8]);
324     *     Area a2 = new Area([triangle 0,0 =&gt; 8,0 =&gt; 8,8]);
325     *     a1.exclusiveOr(a2);
326     *
327     *        a1(before)    xor        a2         =     a1(after)
328     *
329     *     ################     ################
330     *     ##############         ##############     ##            ##
331     *     ############             ############     ####        ####
332     *     ##########                 ##########     ######    ######
333     *     ########                     ########     ################
334     *     ######                         ######     ######    ######
335     *     ####                             ####     ####        ####
336     *     ##                                 ##     ##            ##
337     * </pre>
338     *
339     * @param rhs the <code>Area</code> to be exclusive ORed with this
340     * <code>Area</code>.
341     * @throws NullPointerException if <code>rhs</code> is null
342     * @since 1.2
343     */
344    public void exclusiveOr(Area rhs) {
345        //curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
346        curves = new SomeOp(SomeOp.XOROP).calculate(this.curves, rhs.curves);
347        invalidateBounds();
348    }
349
350    /**
351     * Removes all of the geometry from this <code>Area</code> and restores it
352     * to an empty area.
353     *
354     * @since 1.2
355     */
356    public void reset() {
357        curves = new Vector();
358        invalidateBounds();
359    }
360
361    /**
362     * Tests whether this <code>Area</code> object encloses any area.
363     *
364     * @return    <code>true</code> if this <code>Area</code> object represents an
365     * empty area; <code>false</code> otherwise.
366     * @since 1.2
367     */
368    public boolean isEmpty() {
369        //return (curves.size() == 0);
370        return (curves.isEmpty());
371    }
372
373    /**
374     * Tests whether this <code>Area</code> consists entirely of straight edged
375     * polygonal geometry.
376     *
377     * @return    <code>true</code> if the geometry of this <code>Area</code>
378     * consists entirely of line segments; <code>false</code> otherwise.
379     * @since 1.2
380     */
381    public boolean isPolygonal() {
382        Enumeration enum_ = curves.elements();
383        while (enum_.hasMoreElements()) {
384            if (((CurveObject) enum_.nextElement()).getOrder() > 1) {
385                return false;
386            }
387        }
388        return true;
389    }
390
391    /**
392     * Tests whether this <code>Area</code> is rectangular in shape.
393     *
394     * @return    <code>true</code> if the geometry of this <code>Area</code> is
395     * rectangular in shape; <code>false</code> otherwise.
396     * @since 1.2
397     */
398    public boolean isRectangular() {
399        int size = curves.size();
400        if (size == 0) {
401            return true;
402        }
403        if (size > 3) {
404            return false;
405        }
406        CurveObject c1 = (CurveObject) curves.get(1);
407        CurveObject c2 = (CurveObject) curves.get(2);
408        if (c1.getOrder() != 1 || c2.getOrder() != 1) {
409            return false;
410        }
411        if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
412            return false;
413        }
414        if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
415            // One might be able to prove that this is impossible...
416            return false;
417        }
418        return true;
419    }
420
421    /**
422     * Tests whether this <code>Area</code> is comprised of a single closed
423     * subpath. This method returns <code>true</code> if the path contains 0 or
424     * 1 subpaths, or <code>false</code> if the path contains more than 1
425     * subpath. The subpaths are counted by the number of
426     * {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments that appear in the
427     * path.
428     *
429     * @return    <code>true</code> if the <code>Area</code> is comprised of a
430     * single basic geometry; <code>false</code> otherwise.
431     * @since 1.2
432     */
433    public boolean isSingular() {
434        if (curves.size() < 3) {
435            return true;
436        }
437        Enumeration enum_ = curves.elements();
438        enum_.nextElement(); // First Order0 "moveto"
439        while (enum_.hasMoreElements()) {
440            if (((CurveObject) enum_.nextElement()).getOrder() == 0) {
441                return false;
442            }
443        }
444        return true;
445    }
446
447    private Rectangle2D cachedBounds;
448
449    private void invalidateBounds() {
450        cachedBounds = null;
451    }
452
453    /**
454     * Tests whether the geometries of the two <code>Area</code> objects are
455     * equal. This method will return false if the argument is null.
456     *
457     * @param other the <code>Area</code> to be compared to this
458     * <code>Area</code>
459     * @return  <code>true</code> if the two geometries are equal;
460     * <code>false</code> otherwise.
461     * @since 1.2
462     */
463    public boolean equals(Area other) {
464        // REMIND: A *much* simpler operation should be possible...
465        // Should be able to do a curve-wise comparison since all Areas
466        // should evaluate their curves in the same top-down order.
467        if (other == this) {
468            return true;
469        }
470        if (other == null) {
471            return false;
472        }
473        //Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves);
474        Vector c = new SomeOp(SomeOp.XOROP).calculate(this.curves, other.curves);
475        return c.isEmpty();
476    }
477
478    /**
479     * Creates a {@link PathIterator} for the outline of this <code>Area</code>
480     * object. This <code>Area</code> object is unchanged.
481     *
482     * @param at an optional <code>AffineTransform</code> to be applied to the
483     * coordinates as they are returned in the iteration, or <code>null</code>
484     * if untransformed coordinates are desired
485     * @return the <code>PathIterator</code> object that returns the geometry of
486     * the outline of this <code>Area</code>, one segment at a time.
487     * @since 1.2
488     */
489    public AreaIterator getPathIterator(AffineTransform at) {   //did return PathIterator
490        return new AreaIterator(curves, at);
491    }
492
493}