Path_Delegate.java revision 103d40960daa0420995aa12881bef33892a0a2ab
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(long 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 long 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 long init2(long 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(long 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(long 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(long native_dst, long 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 boolean native_isConvex(long nPath) {
146        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
147                "Path.isConvex is not supported.", null, null);
148        return true;
149    }
150
151    @LayoutlibDelegate
152    /*package*/ static int native_getFillType(long nPath) {
153        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
154        if (pathDelegate == null) {
155            return 0;
156        }
157
158        return pathDelegate.mFillType.nativeInt;
159    }
160
161    @LayoutlibDelegate
162    /*package*/ static void native_setFillType(long nPath, int ft) {
163        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
164        if (pathDelegate == null) {
165            return;
166        }
167
168        pathDelegate.mFillType = Path.sFillTypeArray[ft];
169    }
170
171    @LayoutlibDelegate
172    /*package*/ static boolean native_isEmpty(long nPath) {
173        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
174        if (pathDelegate == null) {
175            return true;
176        }
177
178        return pathDelegate.isEmpty();
179    }
180
181    @LayoutlibDelegate
182    /*package*/ static boolean native_isRect(long nPath, RectF rect) {
183        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
184        if (pathDelegate == null) {
185            return false;
186        }
187
188        // create an Area that can test if the path is a rect
189        Area area = new Area(pathDelegate.mPath);
190        if (area.isRectangular()) {
191            if (rect != null) {
192                pathDelegate.fillBounds(rect);
193            }
194
195            return true;
196        }
197
198        return false;
199    }
200
201    @LayoutlibDelegate
202    /*package*/ static void native_computeBounds(long nPath, RectF bounds) {
203        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
204        if (pathDelegate == null) {
205            return;
206        }
207
208        pathDelegate.fillBounds(bounds);
209    }
210
211    @LayoutlibDelegate
212    /*package*/ static void native_incReserve(long nPath, int extraPtCount) {
213        // since we use a java2D path, there's no way to pre-allocate new points,
214        // so we do nothing.
215    }
216
217    @LayoutlibDelegate
218    /*package*/ static void native_moveTo(long nPath, float x, float y) {
219        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
220        if (pathDelegate == null) {
221            return;
222        }
223
224        pathDelegate.moveTo(x, y);
225    }
226
227    @LayoutlibDelegate
228    /*package*/ static void native_rMoveTo(long nPath, float dx, float dy) {
229        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
230        if (pathDelegate == null) {
231            return;
232        }
233
234        pathDelegate.rMoveTo(dx, dy);
235    }
236
237    @LayoutlibDelegate
238    /*package*/ static void native_lineTo(long nPath, float x, float y) {
239        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
240        if (pathDelegate == null) {
241            return;
242        }
243
244        pathDelegate.lineTo(x, y);
245    }
246
247    @LayoutlibDelegate
248    /*package*/ static void native_rLineTo(long nPath, float dx, float dy) {
249        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
250        if (pathDelegate == null) {
251            return;
252        }
253
254        pathDelegate.rLineTo(dx, dy);
255    }
256
257    @LayoutlibDelegate
258    /*package*/ static void native_quadTo(long nPath, float x1, float y1, float x2, float y2) {
259        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
260        if (pathDelegate == null) {
261            return;
262        }
263
264        pathDelegate.quadTo(x1, y1, x2, y2);
265    }
266
267    @LayoutlibDelegate
268    /*package*/ static void native_rQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
269        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
270        if (pathDelegate == null) {
271            return;
272        }
273
274        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
275    }
276
277    @LayoutlibDelegate
278    /*package*/ static void native_cubicTo(long nPath, float x1, float y1,
279            float x2, float y2, float x3, float y3) {
280        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
281        if (pathDelegate == null) {
282            return;
283        }
284
285        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
286    }
287
288    @LayoutlibDelegate
289    /*package*/ static void native_rCubicTo(long nPath, float x1, float y1,
290            float x2, float y2, float x3, float y3) {
291        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
292        if (pathDelegate == null) {
293            return;
294        }
295
296        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
297    }
298
299    @LayoutlibDelegate
300    /*package*/ static void native_arcTo(long nPath, RectF oval,
301                    float startAngle, float sweepAngle, boolean forceMoveTo) {
302        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
303        if (pathDelegate == null) {
304            return;
305        }
306
307        pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
308    }
309
310    @LayoutlibDelegate
311    /*package*/ static void native_close(long nPath) {
312        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
313        if (pathDelegate == null) {
314            return;
315        }
316
317        pathDelegate.close();
318    }
319
320    @LayoutlibDelegate
321    /*package*/ static void native_addRect(long nPath, RectF rect, int dir) {
322        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
323        if (pathDelegate == null) {
324            return;
325        }
326
327        pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
328    }
329
330    @LayoutlibDelegate
331    /*package*/ static void native_addRect(long nPath,
332            float left, float top, float right, float bottom, int dir) {
333        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
334        if (pathDelegate == null) {
335            return;
336        }
337
338        pathDelegate.addRect(left, top, right, bottom, dir);
339    }
340
341    @LayoutlibDelegate
342    /*package*/ static void native_addOval(long nPath, RectF oval, int dir) {
343        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
344        if (pathDelegate == null) {
345            return;
346        }
347
348        pathDelegate.mPath.append(new Ellipse2D.Float(
349                oval.left, oval.top, oval.width(), oval.height()), false);
350    }
351
352    @LayoutlibDelegate
353    /*package*/ static void native_addCircle(long nPath, float x, float y, float radius, int dir) {
354        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
355        if (pathDelegate == null) {
356            return;
357        }
358
359        // because x/y is the center of the circle, need to offset this by the radius
360        pathDelegate.mPath.append(new Ellipse2D.Float(
361                x - radius, y - radius, radius * 2, radius * 2), false);
362    }
363
364    @LayoutlibDelegate
365    /*package*/ static void native_addArc(long nPath, RectF oval,
366            float startAngle, float sweepAngle) {
367        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
368        if (pathDelegate == null) {
369            return;
370        }
371
372        // because x/y is the center of the circle, need to offset this by the radius
373        pathDelegate.mPath.append(new Arc2D.Float(
374                oval.left, oval.top, oval.width(), oval.height(),
375                -startAngle, -sweepAngle, Arc2D.OPEN), false);
376    }
377
378    @LayoutlibDelegate
379    /*package*/ static void native_addRoundRect(
380            long nPath, RectF rect, float rx, float ry, int dir) {
381
382        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
383        if (pathDelegate == null) {
384            return;
385        }
386
387        pathDelegate.mPath.append(new RoundRectangle2D.Float(
388                rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false);
389    }
390
391    @LayoutlibDelegate
392    /*package*/ static void native_addRoundRect(long nPath, RectF rect, float[] radii, int dir) {
393        // Java2D doesn't support different rounded corners in each corner, so just use the
394        // first value.
395        native_addRoundRect(nPath, rect, radii[0], radii[1], dir);
396
397        // there can be a case where this API is used but with similar values for all corners, so
398        // in that case we don't warn.
399        // we only care if 2 corners are different so just compare to the next one.
400        for (int i = 0 ; i < 3 ; i++) {
401            if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) {
402                Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
403                        "Different corner sizes are not supported in Path.addRoundRect.",
404                        null, null /*data*/);
405                break;
406            }
407        }
408    }
409
410    @LayoutlibDelegate
411    /*package*/ static void native_addPath(long nPath, long src, float dx, float dy) {
412        addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
413    }
414
415    @LayoutlibDelegate
416    /*package*/ static void native_addPath(long nPath, long src) {
417        addPath(nPath, src, null /*transform*/);
418    }
419
420    @LayoutlibDelegate
421    /*package*/ static void native_addPath(long nPath, long src, long matrix) {
422        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
423        if (matrixDelegate == null) {
424            return;
425        }
426
427        addPath(nPath, src, matrixDelegate.getAffineTransform());
428    }
429
430    @LayoutlibDelegate
431    /*package*/ static void native_offset(long nPath, float dx, float dy, long dst_path) {
432        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
433        if (pathDelegate == null) {
434            return;
435        }
436
437        // could be null if the int is 0;
438        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
439
440        pathDelegate.offset(dx, dy, dstDelegate);
441    }
442
443    @LayoutlibDelegate
444    /*package*/ static void native_offset(long nPath, float dx, float dy) {
445        native_offset(nPath, dx, dy, 0);
446    }
447
448    @LayoutlibDelegate
449    /*package*/ static void native_setLastPoint(long nPath, float dx, float dy) {
450        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
451        if (pathDelegate == null) {
452            return;
453        }
454
455        pathDelegate.mLastX = dx;
456        pathDelegate.mLastY = dy;
457    }
458
459    @LayoutlibDelegate
460    /*package*/ static void native_transform(long nPath, long matrix,
461                                                long dst_path) {
462        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
463        if (pathDelegate == null) {
464            return;
465        }
466
467        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
468        if (matrixDelegate == null) {
469            return;
470        }
471
472        // this can be null if dst_path is 0
473        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
474
475        pathDelegate.transform(matrixDelegate, dstDelegate);
476    }
477
478    @LayoutlibDelegate
479    /*package*/ static void native_transform(long nPath, long matrix) {
480        native_transform(nPath, matrix, 0);
481    }
482
483    @LayoutlibDelegate
484    /*package*/ static boolean native_op(long nPath1, long nPath2, int op, long result) {
485        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
486        return false;
487    }
488
489    @LayoutlibDelegate
490    /*package*/ static void finalizer(long nPath) {
491        sManager.removeJavaReferenceFor(nPath);
492    }
493
494    @LayoutlibDelegate
495    /*package*/ static float[] native_approximate(long nPath, float error) {
496        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not supported", null);
497        return new float[0];
498    }
499
500    // ---- Private helper methods ----
501
502    private void set(Path_Delegate delegate) {
503        mPath.reset();
504        setFillType(delegate.mFillType);
505        mPath.append(delegate.mPath, false /*connect*/);
506    }
507
508    private void setFillType(FillType fillType) {
509        mFillType = fillType;
510        mPath.setWindingRule(getWindingRule(fillType));
511    }
512
513    /**
514     * Returns the Java2D winding rules matching a given Android {@link FillType}.
515     * @param type the android fill type
516     * @return the matching java2d winding rule.
517     */
518    private static int getWindingRule(FillType type) {
519        switch (type) {
520            case WINDING:
521            case INVERSE_WINDING:
522                return GeneralPath.WIND_NON_ZERO;
523            case EVEN_ODD:
524            case INVERSE_EVEN_ODD:
525                return GeneralPath.WIND_EVEN_ODD;
526        }
527
528        assert false;
529        throw new IllegalArgumentException();
530    }
531
532    private static Direction getDirection(int direction) {
533        for (Direction d : Direction.values()) {
534            if (direction == d.nativeInt) {
535                return d;
536            }
537        }
538
539        assert false;
540        return null;
541    }
542
543    private static void addPath(long destPath, long srcPath, AffineTransform transform) {
544        Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
545        if (destPathDelegate == null) {
546            return;
547        }
548
549        Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
550        if (srcPathDelegate == null) {
551            return;
552        }
553
554        if (transform != null) {
555            destPathDelegate.mPath.append(
556                    srcPathDelegate.mPath.getPathIterator(transform), false);
557        } else {
558            destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
559        }
560    }
561
562
563    /**
564     * Returns whether the path is empty.
565     * @return true if the path is empty.
566     */
567    private boolean isEmpty() {
568        return mPath.getCurrentPoint() == null;
569    }
570
571    /**
572     * Fills the given {@link RectF} with the path bounds.
573     * @param bounds the RectF to be filled.
574     */
575    private void fillBounds(RectF bounds) {
576        Rectangle2D rect = mPath.getBounds2D();
577        bounds.left = (float)rect.getMinX();
578        bounds.right = (float)rect.getMaxX();
579        bounds.top = (float)rect.getMinY();
580        bounds.bottom = (float)rect.getMaxY();
581    }
582
583    /**
584     * Set the beginning of the next contour to the point (x,y).
585     *
586     * @param x The x-coordinate of the start of a new contour
587     * @param y The y-coordinate of the start of a new contour
588     */
589    private void moveTo(float x, float y) {
590        mPath.moveTo(mLastX = x, mLastY = y);
591    }
592
593    /**
594     * Set the beginning of the next contour relative to the last point on the
595     * previous contour. If there is no previous contour, this is treated the
596     * same as moveTo().
597     *
598     * @param dx The amount to add to the x-coordinate of the end of the
599     *           previous contour, to specify the start of a new contour
600     * @param dy The amount to add to the y-coordinate of the end of the
601     *           previous contour, to specify the start of a new contour
602     */
603    private void rMoveTo(float dx, float dy) {
604        dx += mLastX;
605        dy += mLastY;
606        mPath.moveTo(mLastX = dx, mLastY = dy);
607    }
608
609    /**
610     * Add a line from the last point to the specified point (x,y).
611     * If no moveTo() call has been made for this contour, the first point is
612     * automatically set to (0,0).
613     *
614     * @param x The x-coordinate of the end of a line
615     * @param y The y-coordinate of the end of a line
616     */
617    private void lineTo(float x, float y) {
618        mPath.lineTo(mLastX = x, mLastY = y);
619    }
620
621    /**
622     * Same as lineTo, but the coordinates are considered relative to the last
623     * point on this contour. If there is no previous point, then a moveTo(0,0)
624     * is inserted automatically.
625     *
626     * @param dx The amount to add to the x-coordinate of the previous point on
627     *           this contour, to specify a line
628     * @param dy The amount to add to the y-coordinate of the previous point on
629     *           this contour, to specify a line
630     */
631    private void rLineTo(float dx, float dy) {
632        if (isEmpty()) {
633            mPath.moveTo(mLastX = 0, mLastY = 0);
634        }
635        dx += mLastX;
636        dy += mLastY;
637        mPath.lineTo(mLastX = dx, mLastY = dy);
638    }
639
640    /**
641     * Add a quadratic bezier from the last point, approaching control point
642     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
643     * this contour, the first point is automatically set to (0,0).
644     *
645     * @param x1 The x-coordinate of the control point on a quadratic curve
646     * @param y1 The y-coordinate of the control point on a quadratic curve
647     * @param x2 The x-coordinate of the end point on a quadratic curve
648     * @param y2 The y-coordinate of the end point on a quadratic curve
649     */
650    private void quadTo(float x1, float y1, float x2, float y2) {
651        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
652    }
653
654    /**
655     * Same as quadTo, but the coordinates are considered relative to the last
656     * point on this contour. If there is no previous point, then a moveTo(0,0)
657     * is inserted automatically.
658     *
659     * @param dx1 The amount to add to the x-coordinate of the last point on
660     *            this contour, for the control point of a quadratic curve
661     * @param dy1 The amount to add to the y-coordinate of the last point on
662     *            this contour, for the control point of a quadratic curve
663     * @param dx2 The amount to add to the x-coordinate of the last point on
664     *            this contour, for the end point of a quadratic curve
665     * @param dy2 The amount to add to the y-coordinate of the last point on
666     *            this contour, for the end point of a quadratic curve
667     */
668    private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
669        if (isEmpty()) {
670            mPath.moveTo(mLastX = 0, mLastY = 0);
671        }
672        dx1 += mLastX;
673        dy1 += mLastY;
674        dx2 += mLastX;
675        dy2 += mLastY;
676        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
677    }
678
679    /**
680     * Add a cubic bezier from the last point, approaching control points
681     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
682     * made for this contour, the first point is automatically set to (0,0).
683     *
684     * @param x1 The x-coordinate of the 1st control point on a cubic curve
685     * @param y1 The y-coordinate of the 1st control point on a cubic curve
686     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
687     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
688     * @param x3 The x-coordinate of the end point on a cubic curve
689     * @param y3 The y-coordinate of the end point on a cubic curve
690     */
691    private void cubicTo(float x1, float y1, float x2, float y2,
692                        float x3, float y3) {
693        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
694    }
695
696    /**
697     * Same as cubicTo, but the coordinates are considered relative to the
698     * current point on this contour. If there is no previous point, then a
699     * moveTo(0,0) is inserted automatically.
700     */
701    private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
702                         float dx3, float dy3) {
703        if (isEmpty()) {
704            mPath.moveTo(mLastX = 0, mLastY = 0);
705        }
706        dx1 += mLastX;
707        dy1 += mLastY;
708        dx2 += mLastX;
709        dy2 += mLastY;
710        dx3 += mLastX;
711        dy3 += mLastY;
712        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
713    }
714
715    /**
716     * Append the specified arc to the path as a new contour. If the start of
717     * the path is different from the path's current last point, then an
718     * automatic lineTo() is added to connect the current contour to the
719     * start of the arc. However, if the path is empty, then we call moveTo()
720     * with the first point of the arc. The sweep angle is tread mod 360.
721     *
722     * @param oval        The bounds of oval defining shape and size of the arc
723     * @param startAngle  Starting angle (in degrees) where the arc begins
724     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
725     *                    mod 360.
726     * @param forceMoveTo If true, always begin a new contour with the arc
727     */
728    private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
729        Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle,
730                -sweepAngle, Arc2D.OPEN);
731        mPath.append(arc, true /*connect*/);
732
733        resetLastPointFromPath();
734    }
735
736    /**
737     * Close the current contour. If the current point is not equal to the
738     * first point of the contour, a line segment is automatically added.
739     */
740    private void close() {
741        mPath.closePath();
742    }
743
744    private void resetLastPointFromPath() {
745        Point2D last = mPath.getCurrentPoint();
746        mLastX = (float) last.getX();
747        mLastY = (float) last.getY();
748    }
749
750    /**
751     * Add a closed rectangle contour to the path
752     *
753     * @param left   The left side of a rectangle to add to the path
754     * @param top    The top of a rectangle to add to the path
755     * @param right  The right side of a rectangle to add to the path
756     * @param bottom The bottom of a rectangle to add to the path
757     * @param dir    The direction to wind the rectangle's contour
758     */
759    private void addRect(float left, float top, float right, float bottom,
760                        int dir) {
761        moveTo(left, top);
762
763        Direction direction = getDirection(dir);
764
765        switch (direction) {
766            case CW:
767                lineTo(right, top);
768                lineTo(right, bottom);
769                lineTo(left, bottom);
770                break;
771            case CCW:
772                lineTo(left, bottom);
773                lineTo(right, bottom);
774                lineTo(right, top);
775                break;
776        }
777
778        close();
779
780        resetLastPointFromPath();
781    }
782
783    /**
784     * Offset the path by (dx,dy), returning true on success
785     *
786     * @param dx  The amount in the X direction to offset the entire path
787     * @param dy  The amount in the Y direction to offset the entire path
788     * @param dst The translated path is written here. If this is null, then
789     *            the original path is modified.
790     */
791    public void offset(float dx, float dy, Path_Delegate dst) {
792        GeneralPath newPath = new GeneralPath();
793
794        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
795
796        newPath.append(iterator, false /*connect*/);
797
798        if (dst != null) {
799            dst.mPath = newPath;
800        } else {
801            mPath = newPath;
802        }
803    }
804
805    /**
806     * Transform the points in this path by matrix, and write the answer
807     * into dst. If dst is null, then the the original path is modified.
808     *
809     * @param matrix The matrix to apply to the path
810     * @param dst    The transformed path is written here. If dst is null,
811     *               then the the original path is modified
812     */
813    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
814        if (matrix.hasPerspective()) {
815            assert false;
816            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
817                    "android.graphics.Path#transform() only " +
818                    "supports affine transformations.", null, null /*data*/);
819        }
820
821        GeneralPath newPath = new GeneralPath();
822
823        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
824
825        newPath.append(iterator, false /*connect*/);
826
827        if (dst != null) {
828            dst.mPath = newPath;
829        } else {
830            mPath = newPath;
831        }
832    }
833}
834