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.util.NoSuchElementException;
25
26import org.apache.harmony.awt.internal.nls.Messages;
27
28/**
29 * The Class FlatteningPathIterator takes a PathIterator for traversing a curved
30 * shape and flattens it by estimating the curve as a series of line segments.
31 * The flattening factor indicates how far the estimating line segments are
32 * allowed to be from the actual curve: the FlatteningPathIterator will keep
33 * dividing each curved segment into smaller and smaller flat segments until
34 * either the segments are within the flattening factor of the curve or until
35 * the buffer limit is reached.
36 *
37 * @since Android 1.0
38 */
39public class FlatteningPathIterator implements PathIterator {
40
41    /**
42     * The default points buffer size.
43     */
44    private static final int BUFFER_SIZE = 16;
45
46    /**
47     * The default curve subdivision limit.
48     */
49    private static final int BUFFER_LIMIT = 16;
50
51    /**
52     * The points buffer capacity.
53     */
54    private static final int BUFFER_CAPACITY = 16;
55
56    /**
57     * The type of current segment to be flat.
58     */
59    int bufType;
60
61    /**
62     * The curve subdivision limit.
63     */
64    int bufLimit;
65
66    /**
67     * The current points buffer size.
68     */
69    int bufSize;
70
71    /**
72     * The inner cursor position in points buffer.
73     */
74    int bufIndex;
75
76    /**
77     * The current subdivision count.
78     */
79    int bufSubdiv;
80
81    /**
82     * The points buffer.
83     */
84    double buf[];
85
86    /**
87     * The indicator of empty points buffer.
88     */
89    boolean bufEmpty = true;
90
91    /**
92     * The source PathIterator.
93     */
94    PathIterator p;
95
96    /**
97     * The flatness of new path.
98     */
99    double flatness;
100
101    /**
102     * The square of flatness.
103     */
104    double flatness2;
105
106    /**
107     * The x coordinate of previous path segment.
108     */
109    double px;
110
111    /**
112     * The y coordinate of previous path segment.
113     */
114    double py;
115
116    /**
117     * The temporary buffer for getting points from PathIterator.
118     */
119    double coords[] = new double[6];
120
121    /**
122     * Instantiates a new flattening path iterator given the path iterator for a
123     * (possibly) curved path and a flattening factor which indicates how close
124     * together the points on the curve should be chosen. The buffer limit
125     * defaults to 16 which means that each curve will be divided into no more
126     * than 16 segments regardless of the flattening factor.
127     *
128     * @param path
129     *            the path iterator of the original curve.
130     * @param flatness
131     *            the flattening factor that indicates how far the flat path is
132     *            allowed to be from the actual curve in order to decide when to
133     *            stop dividing the path into smaller and smaller segments.
134     * @throws IllegalArgumentException
135     *             if the flatness is less than zero.
136     * @throws NullPointerException
137     *             if the path is null.
138     */
139    public FlatteningPathIterator(PathIterator path, double flatness) {
140        this(path, flatness, BUFFER_LIMIT);
141    }
142
143    /**
144     * Instantiates a new flattening path iterator given the path iterator for a
145     * (possibly) curved path and a flattening factor and a buffer limit. The
146     * FlatteningPathIterator will keep dividing each curved segment into
147     * smaller and smaller flat segments until either the segments are within
148     * the flattening factor of the curve or until the buffer limit is reached.
149     *
150     * @param path
151     *            the path iterator of the original curve.
152     * @param flatness
153     *            the flattening factor that indicates how far the flat path is
154     *            allowed to be from the actual curve in order to decide when to
155     *            stop dividing the path into smaller and smaller segments.
156     * @param limit
157     *            the maximum number of flat segments to divide each curve into.
158     * @throws IllegalArgumentException
159     *             if the flatness or limit is less than zero.
160     * @throws NullPointerException
161     *             if the path is null.
162     */
163    public FlatteningPathIterator(PathIterator path, double flatness, int limit) {
164        if (flatness < 0.0) {
165            // awt.206=Flatness is less then zero
166            throw new IllegalArgumentException(Messages.getString("awt.206")); //$NON-NLS-1$
167        }
168        if (limit < 0) {
169            // awt.207=Limit is less then zero
170            throw new IllegalArgumentException(Messages.getString("awt.207")); //$NON-NLS-1$
171        }
172        if (path == null) {
173            // awt.208=Path is null
174            throw new NullPointerException(Messages.getString("awt.208")); //$NON-NLS-1$
175        }
176        this.p = path;
177        this.flatness = flatness;
178        this.flatness2 = flatness * flatness;
179        this.bufLimit = limit;
180        this.bufSize = Math.min(bufLimit, BUFFER_SIZE);
181        this.buf = new double[bufSize];
182        this.bufIndex = bufSize;
183    }
184
185    /**
186     * Gets the flattening factor.
187     *
188     * @return the flattening factor.
189     */
190    public double getFlatness() {
191        return flatness;
192    }
193
194    /**
195     * Gets the maximum number of subdivisions per curved segment.
196     *
197     * @return the maximum number of subdivisions per curved segment.
198     */
199    public int getRecursionLimit() {
200        return bufLimit;
201    }
202
203    public int getWindingRule() {
204        return p.getWindingRule();
205    }
206
207    public boolean isDone() {
208        return bufEmpty && p.isDone();
209    }
210
211    /**
212     * Calculates flat path points for current segment of the source shape. Line
213     * segment is flat by itself. Flatness of quad and cubic curves evaluated by
214     * getFlatnessSq() method. Curves subdivided until current flatness is
215     * bigger than user defined and subdivision limit isn't exhausted. Single
216     * source segment translated to series of buffer points. The less flatness
217     * the bigger series. Every currentSegment() call extract one point from the
218     * buffer. When series completed evaluate() takes next source shape segment.
219     */
220    void evaluate() {
221        if (bufEmpty) {
222            bufType = p.currentSegment(coords);
223        }
224
225        switch (bufType) {
226            case SEG_MOVETO:
227            case SEG_LINETO:
228                px = coords[0];
229                py = coords[1];
230                break;
231            case SEG_QUADTO:
232                if (bufEmpty) {
233                    bufIndex -= 6;
234                    buf[bufIndex + 0] = px;
235                    buf[bufIndex + 1] = py;
236                    System.arraycopy(coords, 0, buf, bufIndex + 2, 4);
237                    bufSubdiv = 0;
238                }
239
240                while (bufSubdiv < bufLimit) {
241                    if (QuadCurve2D.getFlatnessSq(buf, bufIndex) < flatness2) {
242                        break;
243                    }
244
245                    // Realloc buffer
246                    if (bufIndex <= 4) {
247                        double tmp[] = new double[bufSize + BUFFER_CAPACITY];
248                        System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize
249                                - bufIndex);
250                        buf = tmp;
251                        bufSize += BUFFER_CAPACITY;
252                        bufIndex += BUFFER_CAPACITY;
253                    }
254
255                    QuadCurve2D.subdivide(buf, bufIndex, buf, bufIndex - 4, buf, bufIndex);
256
257                    bufIndex -= 4;
258                    bufSubdiv++;
259                }
260
261                bufIndex += 4;
262                px = buf[bufIndex];
263                py = buf[bufIndex + 1];
264
265                bufEmpty = (bufIndex == bufSize - 2);
266                if (bufEmpty) {
267                    bufIndex = bufSize;
268                    bufType = SEG_LINETO;
269                } else {
270                    bufSubdiv--;
271                }
272                break;
273            case SEG_CUBICTO:
274                if (bufEmpty) {
275                    bufIndex -= 8;
276                    buf[bufIndex + 0] = px;
277                    buf[bufIndex + 1] = py;
278                    System.arraycopy(coords, 0, buf, bufIndex + 2, 6);
279                    bufSubdiv = 0;
280                }
281
282                while (bufSubdiv < bufLimit) {
283                    if (CubicCurve2D.getFlatnessSq(buf, bufIndex) < flatness2) {
284                        break;
285                    }
286
287                    // Realloc buffer
288                    if (bufIndex <= 6) {
289                        double tmp[] = new double[bufSize + BUFFER_CAPACITY];
290                        System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize
291                                - bufIndex);
292                        buf = tmp;
293                        bufSize += BUFFER_CAPACITY;
294                        bufIndex += BUFFER_CAPACITY;
295                    }
296
297                    CubicCurve2D.subdivide(buf, bufIndex, buf, bufIndex - 6, buf, bufIndex);
298
299                    bufIndex -= 6;
300                    bufSubdiv++;
301                }
302
303                bufIndex += 6;
304                px = buf[bufIndex];
305                py = buf[bufIndex + 1];
306
307                bufEmpty = (bufIndex == bufSize - 2);
308                if (bufEmpty) {
309                    bufIndex = bufSize;
310                    bufType = SEG_LINETO;
311                } else {
312                    bufSubdiv--;
313                }
314                break;
315        }
316
317    }
318
319    public void next() {
320        if (bufEmpty) {
321            p.next();
322        }
323    }
324
325    public int currentSegment(float[] coords) {
326        if (isDone()) {
327            // awt.4B=Iterator out of bounds
328            throw new NoSuchElementException(Messages.getString("awt.4Bx")); //$NON-NLS-1$
329        }
330        evaluate();
331        int type = bufType;
332        if (type != SEG_CLOSE) {
333            coords[0] = (float)px;
334            coords[1] = (float)py;
335            if (type != SEG_MOVETO) {
336                type = SEG_LINETO;
337            }
338        }
339        return type;
340    }
341
342    public int currentSegment(double[] coords) {
343        if (isDone()) {
344            // awt.4B=Iterator out of bounds
345            throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
346        }
347        evaluate();
348        int type = bufType;
349        if (type != SEG_CLOSE) {
350            coords[0] = px;
351            coords[1] = py;
352            if (type != SEG_MOVETO) {
353                type = SEG_LINETO;
354            }
355        }
356        return type;
357    }
358}
359