1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23
24import android.graphics.Path.Direction;
25import android.graphics.Path.FillType;
26
27import java.awt.Shape;
28import java.awt.geom.AffineTransform;
29import java.awt.geom.Arc2D;
30import java.awt.geom.Area;
31import java.awt.geom.Ellipse2D;
32import java.awt.geom.GeneralPath;
33import java.awt.geom.PathIterator;
34import java.awt.geom.Point2D;
35import java.awt.geom.Rectangle2D;
36import java.awt.geom.RoundRectangle2D;
37
38/**
39 * Delegate implementing the native methods of android.graphics.Path
40 *
41 * Through the layoutlib_create tool, the original native methods of Path have been replaced
42 * by calls to methods of the same name in this delegate class.
43 *
44 * This class behaves like the original native implementation, but in Java, keeping previously
45 * native data into its own objects and mapping them to int that are sent back and forth between
46 * it and the original Path class.
47 *
48 * @see DelegateManager
49 *
50 */
51public final class Path_Delegate {
52
53    // ---- delegate manager ----
54    private static final DelegateManager<Path_Delegate> sManager =
55            new DelegateManager<Path_Delegate>(Path_Delegate.class);
56
57    // ---- delegate data ----
58    private FillType mFillType = FillType.WINDING;
59    private GeneralPath mPath = new GeneralPath();
60
61    private float mLastX = 0;
62    private float mLastY = 0;
63
64    // ---- Public Helper methods ----
65
66    public static Path_Delegate getDelegate(int nPath) {
67        return sManager.getDelegate(nPath);
68    }
69
70    public Shape getJavaShape() {
71        return mPath;
72    }
73
74    public void setJavaShape(Shape shape) {
75        mPath.reset();
76        mPath.append(shape, false /*connect*/);
77    }
78
79    public void reset() {
80        mPath.reset();
81    }
82
83    public void setPathIterator(PathIterator iterator) {
84        mPath.reset();
85        mPath.append(iterator, false /*connect*/);
86    }
87
88    // ---- native methods ----
89
90    @LayoutlibDelegate
91    /*package*/ static int init1() {
92        // create the delegate
93        Path_Delegate newDelegate = new Path_Delegate();
94
95        return sManager.addNewDelegate(newDelegate);
96    }
97
98    @LayoutlibDelegate
99    /*package*/ static int init2(int nPath) {
100        // create the delegate
101        Path_Delegate newDelegate = new Path_Delegate();
102
103        // get the delegate to copy, which could be null if nPath is 0
104        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
105        if (pathDelegate != null) {
106            newDelegate.set(pathDelegate);
107        }
108
109        return sManager.addNewDelegate(newDelegate);
110    }
111
112    @LayoutlibDelegate
113    /*package*/ static void native_reset(int nPath) {
114        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
115        if (pathDelegate == null) {
116            return;
117        }
118
119        pathDelegate.mPath.reset();
120    }
121
122    @LayoutlibDelegate
123    /*package*/ static void native_rewind(int nPath) {
124        // call out to reset since there's nothing to optimize in
125        // terms of data structs.
126        native_reset(nPath);
127    }
128
129    @LayoutlibDelegate
130    /*package*/ static void native_set(int native_dst, int native_src) {
131        Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
132        if (pathDstDelegate == null) {
133            return;
134        }
135
136        Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
137        if (pathSrcDelegate == null) {
138            return;
139        }
140
141        pathDstDelegate.set(pathSrcDelegate);
142    }
143
144    @LayoutlibDelegate
145    /*package*/ static int native_getFillType(int nPath) {
146        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
147        if (pathDelegate == null) {
148            return 0;
149        }
150
151        return pathDelegate.mFillType.nativeInt;
152    }
153
154    @LayoutlibDelegate
155    /*package*/ static void native_setFillType(int nPath, int ft) {
156        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
157        if (pathDelegate == null) {
158            return;
159        }
160
161        pathDelegate.mFillType = Path.sFillTypeArray[ft];
162    }
163
164    @LayoutlibDelegate
165    /*package*/ static boolean native_isEmpty(int nPath) {
166        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
167        if (pathDelegate == null) {
168            return true;
169        }
170
171        return pathDelegate.isEmpty();
172    }
173
174    @LayoutlibDelegate
175    /*package*/ static boolean native_isRect(int nPath, RectF rect) {
176        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
177        if (pathDelegate == null) {
178            return false;
179        }
180
181        // create an Area that can test if the path is a rect
182        Area area = new Area(pathDelegate.mPath);
183        if (area.isRectangular()) {
184            if (rect != null) {
185                pathDelegate.fillBounds(rect);
186            }
187
188            return true;
189        }
190
191        return false;
192    }
193
194    @LayoutlibDelegate
195    /*package*/ static void native_computeBounds(int nPath, RectF bounds) {
196        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
197        if (pathDelegate == null) {
198            return;
199        }
200
201        pathDelegate.fillBounds(bounds);
202    }
203
204    @LayoutlibDelegate
205    /*package*/ static void native_incReserve(int nPath, int extraPtCount) {
206        // since we use a java2D path, there's no way to pre-allocate new points,
207        // so we do nothing.
208    }
209
210    @LayoutlibDelegate
211    /*package*/ static void native_moveTo(int nPath, float x, float y) {
212        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
213        if (pathDelegate == null) {
214            return;
215        }
216
217        pathDelegate.moveTo(x, y);
218    }
219
220    @LayoutlibDelegate
221    /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) {
222        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
223        if (pathDelegate == null) {
224            return;
225        }
226
227        pathDelegate.rMoveTo(dx, dy);
228    }
229
230    @LayoutlibDelegate
231    /*package*/ static void native_lineTo(int nPath, float x, float y) {
232        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
233        if (pathDelegate == null) {
234            return;
235        }
236
237        pathDelegate.lineTo(x, y);
238    }
239
240    @LayoutlibDelegate
241    /*package*/ static void native_rLineTo(int nPath, float dx, float dy) {
242        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
243        if (pathDelegate == null) {
244            return;
245        }
246
247        pathDelegate.rLineTo(dx, dy);
248    }
249
250    @LayoutlibDelegate
251    /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) {
252        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
253        if (pathDelegate == null) {
254            return;
255        }
256
257        pathDelegate.quadTo(x1, y1, x2, y2);
258    }
259
260    @LayoutlibDelegate
261    /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) {
262        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
263        if (pathDelegate == null) {
264            return;
265        }
266
267        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
268    }
269
270    @LayoutlibDelegate
271    /*package*/ static void native_cubicTo(int nPath, float x1, float y1,
272            float x2, float y2, float x3, float y3) {
273        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
274        if (pathDelegate == null) {
275            return;
276        }
277
278        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
279    }
280
281    @LayoutlibDelegate
282    /*package*/ static void native_rCubicTo(int nPath, float x1, float y1,
283            float x2, float y2, float x3, float y3) {
284        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
285        if (pathDelegate == null) {
286            return;
287        }
288
289        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
290    }
291
292    @LayoutlibDelegate
293    /*package*/ static void native_arcTo(int nPath, RectF oval,
294                    float startAngle, float sweepAngle, boolean forceMoveTo) {
295        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
296        if (pathDelegate == null) {
297            return;
298        }
299
300        pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
301    }
302
303    @LayoutlibDelegate
304    /*package*/ static void native_close(int nPath) {
305        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
306        if (pathDelegate == null) {
307            return;
308        }
309
310        pathDelegate.close();
311    }
312
313    @LayoutlibDelegate
314    /*package*/ static void native_addRect(int nPath, RectF rect, int dir) {
315        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
316        if (pathDelegate == null) {
317            return;
318        }
319
320        pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
321    }
322
323    @LayoutlibDelegate
324    /*package*/ static void native_addRect(int nPath,
325            float left, float top, float right, float bottom, int dir) {
326        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
327        if (pathDelegate == null) {
328            return;
329        }
330
331        pathDelegate.addRect(left, top, right, bottom, dir);
332    }
333
334    @LayoutlibDelegate
335    /*package*/ static void native_addOval(int nPath, RectF oval, int dir) {
336        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
337        if (pathDelegate == null) {
338            return;
339        }
340
341        pathDelegate.mPath.append(new Ellipse2D.Float(
342                oval.left, oval.top, oval.width(), oval.height()), false);
343    }
344
345    @LayoutlibDelegate
346    /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) {
347        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
348        if (pathDelegate == null) {
349            return;
350        }
351
352        // because x/y is the center of the circle, need to offset this by the radius
353        pathDelegate.mPath.append(new Ellipse2D.Float(
354                x - radius, y - radius, radius * 2, radius * 2), false);
355    }
356
357    @LayoutlibDelegate
358    /*package*/ static void native_addArc(int nPath, RectF oval,
359            float startAngle, float sweepAngle) {
360        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
361        if (pathDelegate == null) {
362            return;
363        }
364
365        // because x/y is the center of the circle, need to offset this by the radius
366        pathDelegate.mPath.append(new Arc2D.Float(
367                oval.left, oval.top, oval.width(), oval.height(),
368                -startAngle, -sweepAngle, Arc2D.OPEN), false);
369    }
370
371    @LayoutlibDelegate
372    /*package*/ static void native_addRoundRect(
373            int nPath, RectF rect, float rx, float ry, int dir) {
374
375        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
376        if (pathDelegate == null) {
377            return;
378        }
379
380        pathDelegate.mPath.append(new RoundRectangle2D.Float(
381                rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false);
382    }
383
384    @LayoutlibDelegate
385    /*package*/ static void native_addRoundRect(int nPath, RectF rect, float[] radii, int dir) {
386        // Java2D doesn't support different rounded corners in each corner, so just use the
387        // first value.
388        native_addRoundRect(nPath, rect, radii[0], radii[1], dir);
389
390        // there can be a case where this API is used but with similar values for all corners, so
391        // in that case we don't warn.
392        // we only care if 2 corners are different so just compare to the next one.
393        for (int i = 0 ; i < 3 ; i++) {
394            if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) {
395                Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
396                        "Different corner sizes are not supported in Path.addRoundRect.",
397                        null, null /*data*/);
398                break;
399            }
400        }
401    }
402
403    @LayoutlibDelegate
404    /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) {
405        addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
406    }
407
408    @LayoutlibDelegate
409    /*package*/ static void native_addPath(int nPath, int src) {
410        addPath(nPath, src, null /*transform*/);
411    }
412
413    @LayoutlibDelegate
414    /*package*/ static void native_addPath(int nPath, int src, int matrix) {
415        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
416        if (matrixDelegate == null) {
417            return;
418        }
419
420        addPath(nPath, src, matrixDelegate.getAffineTransform());
421    }
422
423    @LayoutlibDelegate
424    /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) {
425        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
426        if (pathDelegate == null) {
427            return;
428        }
429
430        // could be null if the int is 0;
431        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
432
433        pathDelegate.offset(dx, dy, dstDelegate);
434    }
435
436    @LayoutlibDelegate
437    /*package*/ static void native_offset(int nPath, float dx, float dy) {
438        native_offset(nPath, dx, dy, 0);
439    }
440
441    @LayoutlibDelegate
442    /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) {
443        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
444        if (pathDelegate == null) {
445            return;
446        }
447
448        pathDelegate.mLastX = dx;
449        pathDelegate.mLastY = dy;
450    }
451
452    @LayoutlibDelegate
453    /*package*/ static void native_transform(int nPath, int matrix,
454                                                int dst_path) {
455        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
456        if (pathDelegate == null) {
457            return;
458        }
459
460        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
461        if (matrixDelegate == null) {
462            return;
463        }
464
465        // this can be null if dst_path is 0
466        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
467
468        pathDelegate.transform(matrixDelegate, dstDelegate);
469    }
470
471    @LayoutlibDelegate
472    /*package*/ static void native_transform(int nPath, int matrix) {
473        native_transform(nPath, matrix, 0);
474    }
475
476    @LayoutlibDelegate
477    /*package*/ static boolean native_op(int nPath1, int nPath2, int op, int result) {
478        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
479        return false;
480    }
481
482    @LayoutlibDelegate
483    /*package*/ static void finalizer(int nPath) {
484        sManager.removeJavaReferenceFor(nPath);
485    }
486
487
488    // ---- Private helper methods ----
489
490    private void set(Path_Delegate delegate) {
491        mPath.reset();
492        setFillType(delegate.mFillType);
493        mPath.append(delegate.mPath, false /*connect*/);
494    }
495
496    private void setFillType(FillType fillType) {
497        mFillType = fillType;
498        mPath.setWindingRule(getWindingRule(fillType));
499    }
500
501    /**
502     * Returns the Java2D winding rules matching a given Android {@link FillType}.
503     * @param type the android fill type
504     * @return the matching java2d winding rule.
505     */
506    private static int getWindingRule(FillType type) {
507        switch (type) {
508            case WINDING:
509            case INVERSE_WINDING:
510                return GeneralPath.WIND_NON_ZERO;
511            case EVEN_ODD:
512            case INVERSE_EVEN_ODD:
513                return GeneralPath.WIND_EVEN_ODD;
514        }
515
516        assert false;
517        throw new IllegalArgumentException();
518    }
519
520    private static Direction getDirection(int direction) {
521        for (Direction d : Direction.values()) {
522            if (direction == d.nativeInt) {
523                return d;
524            }
525        }
526
527        assert false;
528        return null;
529    }
530
531    private static void addPath(int destPath, int srcPath, AffineTransform transform) {
532        Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
533        if (destPathDelegate == null) {
534            return;
535        }
536
537        Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
538        if (srcPathDelegate == null) {
539            return;
540        }
541
542        if (transform != null) {
543            destPathDelegate.mPath.append(
544                    srcPathDelegate.mPath.getPathIterator(transform), false);
545        } else {
546            destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
547        }
548    }
549
550
551    /**
552     * Returns whether the path is empty.
553     * @return true if the path is empty.
554     */
555    private boolean isEmpty() {
556        return mPath.getCurrentPoint() == null;
557    }
558
559    /**
560     * Fills the given {@link RectF} with the path bounds.
561     * @param bounds the RectF to be filled.
562     */
563    private void fillBounds(RectF bounds) {
564        Rectangle2D rect = mPath.getBounds2D();
565        bounds.left = (float)rect.getMinX();
566        bounds.right = (float)rect.getMaxX();
567        bounds.top = (float)rect.getMinY();
568        bounds.bottom = (float)rect.getMaxY();
569    }
570
571    /**
572     * Set the beginning of the next contour to the point (x,y).
573     *
574     * @param x The x-coordinate of the start of a new contour
575     * @param y The y-coordinate of the start of a new contour
576     */
577    private void moveTo(float x, float y) {
578        mPath.moveTo(mLastX = x, mLastY = y);
579    }
580
581    /**
582     * Set the beginning of the next contour relative to the last point on the
583     * previous contour. If there is no previous contour, this is treated the
584     * same as moveTo().
585     *
586     * @param dx The amount to add to the x-coordinate of the end of the
587     *           previous contour, to specify the start of a new contour
588     * @param dy The amount to add to the y-coordinate of the end of the
589     *           previous contour, to specify the start of a new contour
590     */
591    private void rMoveTo(float dx, float dy) {
592        dx += mLastX;
593        dy += mLastY;
594        mPath.moveTo(mLastX = dx, mLastY = dy);
595    }
596
597    /**
598     * Add a line from the last point to the specified point (x,y).
599     * If no moveTo() call has been made for this contour, the first point is
600     * automatically set to (0,0).
601     *
602     * @param x The x-coordinate of the end of a line
603     * @param y The y-coordinate of the end of a line
604     */
605    private void lineTo(float x, float y) {
606        mPath.lineTo(mLastX = x, mLastY = y);
607    }
608
609    /**
610     * Same as lineTo, but the coordinates are considered relative to the last
611     * point on this contour. If there is no previous point, then a moveTo(0,0)
612     * is inserted automatically.
613     *
614     * @param dx The amount to add to the x-coordinate of the previous point on
615     *           this contour, to specify a line
616     * @param dy The amount to add to the y-coordinate of the previous point on
617     *           this contour, to specify a line
618     */
619    private void rLineTo(float dx, float dy) {
620        if (isEmpty()) {
621            mPath.moveTo(mLastX = 0, mLastY = 0);
622        }
623        dx += mLastX;
624        dy += mLastY;
625        mPath.lineTo(mLastX = dx, mLastY = dy);
626    }
627
628    /**
629     * Add a quadratic bezier from the last point, approaching control point
630     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
631     * this contour, the first point is automatically set to (0,0).
632     *
633     * @param x1 The x-coordinate of the control point on a quadratic curve
634     * @param y1 The y-coordinate of the control point on a quadratic curve
635     * @param x2 The x-coordinate of the end point on a quadratic curve
636     * @param y2 The y-coordinate of the end point on a quadratic curve
637     */
638    private void quadTo(float x1, float y1, float x2, float y2) {
639        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
640    }
641
642    /**
643     * Same as quadTo, but the coordinates are considered relative to the last
644     * point on this contour. If there is no previous point, then a moveTo(0,0)
645     * is inserted automatically.
646     *
647     * @param dx1 The amount to add to the x-coordinate of the last point on
648     *            this contour, for the control point of a quadratic curve
649     * @param dy1 The amount to add to the y-coordinate of the last point on
650     *            this contour, for the control point of a quadratic curve
651     * @param dx2 The amount to add to the x-coordinate of the last point on
652     *            this contour, for the end point of a quadratic curve
653     * @param dy2 The amount to add to the y-coordinate of the last point on
654     *            this contour, for the end point of a quadratic curve
655     */
656    private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
657        if (isEmpty()) {
658            mPath.moveTo(mLastX = 0, mLastY = 0);
659        }
660        dx1 += mLastX;
661        dy1 += mLastY;
662        dx2 += mLastX;
663        dy2 += mLastY;
664        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
665    }
666
667    /**
668     * Add a cubic bezier from the last point, approaching control points
669     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
670     * made for this contour, the first point is automatically set to (0,0).
671     *
672     * @param x1 The x-coordinate of the 1st control point on a cubic curve
673     * @param y1 The y-coordinate of the 1st control point on a cubic curve
674     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
675     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
676     * @param x3 The x-coordinate of the end point on a cubic curve
677     * @param y3 The y-coordinate of the end point on a cubic curve
678     */
679    private void cubicTo(float x1, float y1, float x2, float y2,
680                        float x3, float y3) {
681        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
682    }
683
684    /**
685     * Same as cubicTo, but the coordinates are considered relative to the
686     * current point on this contour. If there is no previous point, then a
687     * moveTo(0,0) is inserted automatically.
688     */
689    private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
690                         float dx3, float dy3) {
691        if (isEmpty()) {
692            mPath.moveTo(mLastX = 0, mLastY = 0);
693        }
694        dx1 += mLastX;
695        dy1 += mLastY;
696        dx2 += mLastX;
697        dy2 += mLastY;
698        dx3 += mLastX;
699        dy3 += mLastY;
700        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
701    }
702
703    /**
704     * Append the specified arc to the path as a new contour. If the start of
705     * the path is different from the path's current last point, then an
706     * automatic lineTo() is added to connect the current contour to the
707     * start of the arc. However, if the path is empty, then we call moveTo()
708     * with the first point of the arc. The sweep angle is tread mod 360.
709     *
710     * @param oval        The bounds of oval defining shape and size of the arc
711     * @param startAngle  Starting angle (in degrees) where the arc begins
712     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
713     *                    mod 360.
714     * @param forceMoveTo If true, always begin a new contour with the arc
715     */
716    private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
717        Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle,
718                -sweepAngle, Arc2D.OPEN);
719        mPath.append(arc, true /*connect*/);
720
721        resetLastPointFromPath();
722    }
723
724    /**
725     * Close the current contour. If the current point is not equal to the
726     * first point of the contour, a line segment is automatically added.
727     */
728    private void close() {
729        mPath.closePath();
730    }
731
732    private void resetLastPointFromPath() {
733        Point2D last = mPath.getCurrentPoint();
734        mLastX = (float) last.getX();
735        mLastY = (float) last.getY();
736    }
737
738    /**
739     * Add a closed rectangle contour to the path
740     *
741     * @param left   The left side of a rectangle to add to the path
742     * @param top    The top of a rectangle to add to the path
743     * @param right  The right side of a rectangle to add to the path
744     * @param bottom The bottom of a rectangle to add to the path
745     * @param dir    The direction to wind the rectangle's contour
746     */
747    private void addRect(float left, float top, float right, float bottom,
748                        int dir) {
749        moveTo(left, top);
750
751        Direction direction = getDirection(dir);
752
753        switch (direction) {
754            case CW:
755                lineTo(right, top);
756                lineTo(right, bottom);
757                lineTo(left, bottom);
758                break;
759            case CCW:
760                lineTo(left, bottom);
761                lineTo(right, bottom);
762                lineTo(right, top);
763                break;
764        }
765
766        close();
767
768        resetLastPointFromPath();
769    }
770
771    /**
772     * Offset the path by (dx,dy), returning true on success
773     *
774     * @param dx  The amount in the X direction to offset the entire path
775     * @param dy  The amount in the Y direction to offset the entire path
776     * @param dst The translated path is written here. If this is null, then
777     *            the original path is modified.
778     */
779    public void offset(float dx, float dy, Path_Delegate dst) {
780        GeneralPath newPath = new GeneralPath();
781
782        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
783
784        newPath.append(iterator, false /*connect*/);
785
786        if (dst != null) {
787            dst.mPath = newPath;
788        } else {
789            mPath = newPath;
790        }
791    }
792
793    /**
794     * Transform the points in this path by matrix, and write the answer
795     * into dst. If dst is null, then the the original path is modified.
796     *
797     * @param matrix The matrix to apply to the path
798     * @param dst    The transformed path is written here. If dst is null,
799     *               then the the original path is modified
800     */
801    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
802        if (matrix.hasPerspective()) {
803            assert false;
804            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
805                    "android.graphics.Path#transform() only " +
806                    "supports affine transformations.", null, null /*data*/);
807        }
808
809        GeneralPath newPath = new GeneralPath();
810
811        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
812
813        newPath.append(iterator, false /*connect*/);
814
815        if (dst != null) {
816            dst.mPath = newPath;
817        } else {
818            mPath = newPath;
819        }
820    }
821}
822