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