1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Denis M. Kishenko
19 * @version $Revision$
20 */
21
22package java.awt.geom;
23
24import java.awt.Rectangle;
25import java.awt.Shape;
26import java.util.NoSuchElementException;
27
28import org.apache.harmony.awt.gl.Crossing;
29import org.apache.harmony.awt.internal.nls.Messages;
30
31/**
32 * The class GeneralPath represents a shape whose outline is given by different
33 * types of curved and straight segments.
34 *
35 * @since Android 1.0
36 */
37public final class GeneralPath implements Shape, Cloneable {
38
39    /**
40     * The Constant WIND_EVEN_ODD see {@link PathIterator#WIND_EVEN_ODD}.
41     */
42    public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
43
44    /**
45     * The Constant WIND_NON_ZERO see {@link PathIterator#WIND_NON_ZERO}.
46     */
47    public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
48
49    /**
50     * The buffers size.
51     */
52    private static final int BUFFER_SIZE = 10;
53
54    /**
55     * The buffers capacity.
56     */
57    private static final int BUFFER_CAPACITY = 10;
58
59    /**
60     * The point's types buffer.
61     */
62    byte[] types;
63
64    /**
65     * The points buffer.
66     */
67    float[] points;
68
69    /**
70     * The point's type buffer size.
71     */
72    int typeSize;
73
74    /**
75     * The points buffer size.
76     */
77    int pointSize;
78
79    /**
80     * The path rule.
81     */
82    int rule;
83
84    /**
85     * The space amount in points buffer for different segmenet's types.
86     */
87    static int pointShift[] = {
88            2, // MOVETO
89            2, // LINETO
90            4, // QUADTO
91            6, // CUBICTO
92            0
93    }; // CLOSE
94
95    /*
96     * GeneralPath path iterator
97     */
98    /**
99     * The Class Iterator is the subclass of Iterator for traversing the outline
100     * of a GeneralPath.
101     */
102    class Iterator implements PathIterator {
103
104        /**
105         * The current cursor position in types buffer.
106         */
107        int typeIndex;
108
109        /**
110         * The current cursor position in points buffer.
111         */
112        int pointIndex;
113
114        /**
115         * The source GeneralPath object.
116         */
117        GeneralPath p;
118
119        /**
120         * The path iterator transformation.
121         */
122        AffineTransform t;
123
124        /**
125         * Constructs a new GeneralPath.Iterator for given general path.
126         *
127         * @param path
128         *            the source GeneralPath object.
129         */
130        Iterator(GeneralPath path) {
131            this(path, null);
132        }
133
134        /**
135         * Constructs a new GeneralPath.Iterator for given general path and
136         * transformation.
137         *
138         * @param path
139         *            the source GeneralPath object.
140         * @param at
141         *            the AffineTransform object to apply rectangle path.
142         */
143        Iterator(GeneralPath path, AffineTransform at) {
144            this.p = path;
145            this.t = at;
146        }
147
148        public int getWindingRule() {
149            return p.getWindingRule();
150        }
151
152        public boolean isDone() {
153            return typeIndex >= p.typeSize;
154        }
155
156        public void next() {
157            typeIndex++;
158        }
159
160        public int currentSegment(double[] coords) {
161            if (isDone()) {
162                // awt.4B=Iterator out of bounds
163                throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
164            }
165            int type = p.types[typeIndex];
166            int count = GeneralPath.pointShift[type];
167            for (int i = 0; i < count; i++) {
168                coords[i] = p.points[pointIndex + i];
169            }
170            if (t != null) {
171                t.transform(coords, 0, coords, 0, count / 2);
172            }
173            pointIndex += count;
174            return type;
175        }
176
177        public int currentSegment(float[] coords) {
178            if (isDone()) {
179                // awt.4B=Iterator out of bounds
180                throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
181            }
182            int type = p.types[typeIndex];
183            int count = GeneralPath.pointShift[type];
184            System.arraycopy(p.points, pointIndex, coords, 0, count);
185            if (t != null) {
186                t.transform(coords, 0, coords, 0, count / 2);
187            }
188            pointIndex += count;
189            return type;
190        }
191
192    }
193
194    /**
195     * Instantiates a new general path with the winding rule set to
196     * {@link PathIterator#WIND_NON_ZERO} and the initial capacity (number of
197     * segments) set to the default value 10.
198     */
199    public GeneralPath() {
200        this(WIND_NON_ZERO, BUFFER_SIZE);
201    }
202
203    /**
204     * Instantiates a new general path with the given winding rule and the
205     * initial capacity (number of segments) set to the default value 10.
206     *
207     * @param rule
208     *            the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
209     *            {@link PathIterator#WIND_NON_ZERO}.
210     */
211    public GeneralPath(int rule) {
212        this(rule, BUFFER_SIZE);
213    }
214
215    /**
216     * Instantiates a new general path with the given winding rule and initial
217     * capacity (number of segments).
218     *
219     * @param rule
220     *            the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
221     *            {@link PathIterator#WIND_NON_ZERO}.
222     * @param initialCapacity
223     *            the number of segments the path is set to hold.
224     */
225    public GeneralPath(int rule, int initialCapacity) {
226        setWindingRule(rule);
227        types = new byte[initialCapacity];
228        points = new float[initialCapacity * 2];
229    }
230
231    /**
232     * Creates a new GeneralPath from the outline of the given shape.
233     *
234     * @param shape
235     *            the shape.
236     */
237    public GeneralPath(Shape shape) {
238        this(WIND_NON_ZERO, BUFFER_SIZE);
239        PathIterator p = shape.getPathIterator(null);
240        setWindingRule(p.getWindingRule());
241        append(p, false);
242    }
243
244    /**
245     * Sets the winding rule, which determines how to decide whether a point
246     * that isn't on the path itself is inside or outside of the shape.
247     *
248     * @param rule
249     *            the new winding rule.
250     * @throws IllegalArgumentException
251     *             if the winding rule is neither
252     *             {@link PathIterator#WIND_EVEN_ODD} nor
253     *             {@link PathIterator#WIND_NON_ZERO}.
254     */
255    public void setWindingRule(int rule) {
256        if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
257            // awt.209=Invalid winding rule value
258            throw new java.lang.IllegalArgumentException(Messages.getString("awt.209")); //$NON-NLS-1$
259        }
260        this.rule = rule;
261    }
262
263    /**
264     * Gets the winding rule.
265     *
266     * @return the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or
267     *         {@link PathIterator#WIND_NON_ZERO}.
268     */
269    public int getWindingRule() {
270        return rule;
271    }
272
273    /**
274     * Checks the point data buffer sizes to see whether pointCount additional
275     * point-data elements can fit. (Note that the number of point data elements
276     * to add is more than one per point -- it depends on the type of point
277     * being added.) Reallocates the buffers to enlarge the size if necessary.
278     *
279     * @param pointCount
280     *            the number of point data elements to be added.
281     * @param checkMove
282     *            whether to check for existing points.
283     * @throws IllegalPathStateException
284     *             checkMove is true and the path is currently empty.
285     */
286    void checkBuf(int pointCount, boolean checkMove) {
287        if (checkMove && typeSize == 0) {
288            // awt.20A=First segment should be SEG_MOVETO type
289            throw new IllegalPathStateException(Messages.getString("awt.20A")); //$NON-NLS-1$
290        }
291        if (typeSize == types.length) {
292            byte tmp[] = new byte[typeSize + BUFFER_CAPACITY];
293            System.arraycopy(types, 0, tmp, 0, typeSize);
294            types = tmp;
295        }
296        if (pointSize + pointCount > points.length) {
297            float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)];
298            System.arraycopy(points, 0, tmp, 0, pointSize);
299            points = tmp;
300        }
301    }
302
303    /**
304     * Appends a new point to the end of this general path, disconnected from
305     * the existing path.
306     *
307     * @param x
308     *            the x coordinate of the next point to append.
309     * @param y
310     *            the y coordinate of the next point to append.
311     */
312    public void moveTo(float x, float y) {
313        if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) {
314            points[pointSize - 2] = x;
315            points[pointSize - 1] = y;
316        } else {
317            checkBuf(2, false);
318            types[typeSize++] = PathIterator.SEG_MOVETO;
319            points[pointSize++] = x;
320            points[pointSize++] = y;
321        }
322    }
323
324    /**
325     * Appends a new segment to the end of this general path by making a
326     * straight line segment from the current endpoint to the given new point.
327     *
328     * @param x
329     *            the x coordinate of the next point to append.
330     * @param y
331     *            the y coordinate of the next point to append.
332     */
333    public void lineTo(float x, float y) {
334        checkBuf(2, true);
335        types[typeSize++] = PathIterator.SEG_LINETO;
336        points[pointSize++] = x;
337        points[pointSize++] = y;
338    }
339
340    /**
341     * Appends a new segment to the end of this general path by making a
342     * quadratic curve from the current endpoint to the point (x2, y2) using the
343     * point (x1, y1) as the quadratic curve's control point.
344     *
345     * @param x1
346     *            the x coordinate of the quadratic curve's control point.
347     * @param y1
348     *            the y coordinate of the quadratic curve's control point.
349     * @param x2
350     *            the x coordinate of the quadratic curve's end point.
351     * @param y2
352     *            the y coordinate of the quadratic curve's end point.
353     */
354    public void quadTo(float x1, float y1, float x2, float y2) {
355        checkBuf(4, true);
356        types[typeSize++] = PathIterator.SEG_QUADTO;
357        points[pointSize++] = x1;
358        points[pointSize++] = y1;
359        points[pointSize++] = x2;
360        points[pointSize++] = y2;
361    }
362
363    /**
364     * Appends a new segment to the end of this general path by making a cubic
365     * curve from the current endpoint to the point (x3, y3) using (x1, y1) and
366     * (x2, y2) as control points.
367     *
368     * @see java.awt.geom.CubicCurve2D
369     * @param x1
370     *            the x coordinate of the new cubic segment's first control
371     *            point.
372     * @param y1
373     *            the y coordinate of the new cubic segment's first control
374     *            point.
375     * @param x2
376     *            the x coordinate of the new cubic segment's second control
377     *            point.
378     * @param y2
379     *            the y coordinate of the new cubic segment's second control
380     *            point.
381     * @param x3
382     *            the x coordinate of the new cubic segment's end point.
383     * @param y3
384     *            the y coordinate of the new cubic segment's end point.
385     */
386    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
387        checkBuf(6, true);
388        types[typeSize++] = PathIterator.SEG_CUBICTO;
389        points[pointSize++] = x1;
390        points[pointSize++] = y1;
391        points[pointSize++] = x2;
392        points[pointSize++] = y2;
393        points[pointSize++] = x3;
394        points[pointSize++] = y3;
395    }
396
397    /**
398     * Appends the type information to declare that the current endpoint closes
399     * the curve.
400     */
401    public void closePath() {
402        if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) {
403            checkBuf(0, true);
404            types[typeSize++] = PathIterator.SEG_CLOSE;
405        }
406    }
407
408    /**
409     * Appends the outline of the specified shape onto the end of this
410     * GeneralPath.
411     *
412     * @param shape
413     *            the shape whose outline is to be appended.
414     * @param connect
415     *            true to connect this path's current endpoint to the first
416     *            point of the shape's outline or false to append the shape's
417     *            outline without connecting it.
418     * @throws NullPointerException
419     *             if the shape parameter is null.
420     */
421    public void append(Shape shape, boolean connect) {
422        PathIterator p = shape.getPathIterator(null);
423        append(p, connect);
424    }
425
426    /**
427     * Appends the path defined by the specified PathIterator onto the end of
428     * this GeneralPath.
429     *
430     * @param path
431     *            the PathIterator that defines the new path to append.
432     * @param connect
433     *            true to connect this path's current endpoint to the first
434     *            point of the shape's outline or false to append the shape's
435     *            outline without connecting it.
436     */
437    public void append(PathIterator path, boolean connect) {
438        while (!path.isDone()) {
439            float coords[] = new float[6];
440            switch (path.currentSegment(coords)) {
441                case PathIterator.SEG_MOVETO:
442                    if (!connect || typeSize == 0) {
443                        moveTo(coords[0], coords[1]);
444                        break;
445                    }
446                    if (types[typeSize - 1] != PathIterator.SEG_CLOSE
447                            && points[pointSize - 2] == coords[0]
448                            && points[pointSize - 1] == coords[1]) {
449                        break;
450                    }
451                    // NO BREAK;
452                case PathIterator.SEG_LINETO:
453                    lineTo(coords[0], coords[1]);
454                    break;
455                case PathIterator.SEG_QUADTO:
456                    quadTo(coords[0], coords[1], coords[2], coords[3]);
457                    break;
458                case PathIterator.SEG_CUBICTO:
459                    curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
460                    break;
461                case PathIterator.SEG_CLOSE:
462                    closePath();
463                    break;
464            }
465            path.next();
466            connect = false;
467        }
468    }
469
470    /**
471     * Gets the current end point of the path.
472     *
473     * @return the current end point of the path.
474     */
475    public Point2D getCurrentPoint() {
476        if (typeSize == 0) {
477            return null;
478        }
479        int j = pointSize - 2;
480        if (types[typeSize - 1] == PathIterator.SEG_CLOSE) {
481
482            for (int i = typeSize - 2; i > 0; i--) {
483                int type = types[i];
484                if (type == PathIterator.SEG_MOVETO) {
485                    break;
486                }
487                j -= pointShift[type];
488            }
489        }
490        return new Point2D.Float(points[j], points[j + 1]);
491    }
492
493    /**
494     * Resets the GeneralPath to being an empty path. The underlying point and
495     * segment data is not deleted but rather the end indices of the data arrays
496     * are set to zero.
497     */
498    public void reset() {
499        typeSize = 0;
500        pointSize = 0;
501    }
502
503    /**
504     * Transform all of the coordinates of this path according to the specified
505     * AffineTransform.
506     *
507     * @param t
508     *            the AffineTransform.
509     */
510    public void transform(AffineTransform t) {
511        t.transform(points, 0, points, 0, pointSize / 2);
512    }
513
514    /**
515     * Creates a new GeneralPath whose data is given by this path's data
516     * transformed according to the specified AffineTransform.
517     *
518     * @param t
519     *            the AffineTransform.
520     * @return the new GeneralPath whose data is given by this path's data
521     *         transformed according to the specified AffineTransform.
522     */
523    public Shape createTransformedShape(AffineTransform t) {
524        GeneralPath p = (GeneralPath)clone();
525        if (t != null) {
526            p.transform(t);
527        }
528        return p;
529    }
530
531    public Rectangle2D getBounds2D() {
532        float rx1, ry1, rx2, ry2;
533        if (pointSize == 0) {
534            rx1 = ry1 = rx2 = ry2 = 0.0f;
535        } else {
536            int i = pointSize - 1;
537            ry1 = ry2 = points[i--];
538            rx1 = rx2 = points[i--];
539            while (i > 0) {
540                float y = points[i--];
541                float x = points[i--];
542                if (x < rx1) {
543                    rx1 = x;
544                } else if (x > rx2) {
545                    rx2 = x;
546                }
547                if (y < ry1) {
548                    ry1 = y;
549                } else if (y > ry2) {
550                    ry2 = y;
551                }
552            }
553        }
554        return new Rectangle2D.Float(rx1, ry1, rx2 - rx1, ry2 - ry1);
555    }
556
557    public Rectangle getBounds() {
558        return getBounds2D().getBounds();
559    }
560
561    /**
562     * Checks the cross count (number of times a ray from the point crosses the
563     * shape's boundary) to determine whether the number of crossings
564     * corresponds to a point inside the shape or not (according to the shape's
565     * path rule).
566     *
567     * @param cross
568     *            the point's cross count.
569     * @return true if the point is inside the path, or false otherwise.
570     */
571    boolean isInside(int cross) {
572        if (rule == WIND_NON_ZERO) {
573            return Crossing.isInsideNonZero(cross);
574        }
575        return Crossing.isInsideEvenOdd(cross);
576    }
577
578    public boolean contains(double px, double py) {
579        return isInside(Crossing.crossShape(this, px, py));
580    }
581
582    public boolean contains(double rx, double ry, double rw, double rh) {
583        int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
584        return cross != Crossing.CROSSING && isInside(cross);
585    }
586
587    public boolean intersects(double rx, double ry, double rw, double rh) {
588        int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
589        return cross == Crossing.CROSSING || isInside(cross);
590    }
591
592    public boolean contains(Point2D p) {
593        return contains(p.getX(), p.getY());
594    }
595
596    public boolean contains(Rectangle2D r) {
597        return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
598    }
599
600    public boolean intersects(Rectangle2D r) {
601        return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
602    }
603
604    public PathIterator getPathIterator(AffineTransform t) {
605        return new Iterator(this, t);
606    }
607
608    public PathIterator getPathIterator(AffineTransform t, double flatness) {
609        return new FlatteningPathIterator(getPathIterator(t), flatness);
610    }
611
612    @Override
613    public Object clone() {
614        try {
615            GeneralPath p = (GeneralPath)super.clone();
616            p.types = types.clone();
617            p.points = points.clone();
618            return p;
619        } catch (CloneNotSupportedException e) {
620            throw new InternalError();
621        }
622    }
623
624}
625