Home | History | Annotate | Download | only in graphics
      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 
     17 package android.graphics;
     18 
     19 import com.android.ide.common.rendering.api.LayoutLog;
     20 import com.android.layoutlib.bridge.Bridge;
     21 import com.android.layoutlib.bridge.impl.DelegateManager;
     22 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     23 
     24 import android.graphics.Path.Direction;
     25 import android.graphics.Path.FillType;
     26 
     27 import java.awt.Shape;
     28 import java.awt.geom.AffineTransform;
     29 import java.awt.geom.Arc2D;
     30 import java.awt.geom.Area;
     31 import java.awt.geom.Ellipse2D;
     32 import java.awt.geom.GeneralPath;
     33 import java.awt.geom.PathIterator;
     34 import java.awt.geom.Point2D;
     35 import java.awt.geom.Rectangle2D;
     36 import 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  */
     51 public 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