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 void finalizer(int nPath) {
    478         sManager.removeJavaReferenceFor(nPath);
    479     }
    480 
    481 
    482     // ---- Private helper methods ----
    483 
    484     private void set(Path_Delegate delegate) {
    485         mPath.reset();
    486         setFillType(delegate.mFillType);
    487         mPath.append(delegate.mPath, false /*connect*/);
    488     }
    489 
    490     private void setFillType(FillType fillType) {
    491         mFillType = fillType;
    492         mPath.setWindingRule(getWindingRule(fillType));
    493     }
    494 
    495     /**
    496      * Returns the Java2D winding rules matching a given Android {@link FillType}.
    497      * @param type the android fill type
    498      * @return the matching java2d winding rule.
    499      */
    500     private static int getWindingRule(FillType type) {
    501         switch (type) {
    502             case WINDING:
    503             case INVERSE_WINDING:
    504                 return GeneralPath.WIND_NON_ZERO;
    505             case EVEN_ODD:
    506             case INVERSE_EVEN_ODD:
    507                 return GeneralPath.WIND_EVEN_ODD;
    508         }
    509 
    510         assert false;
    511         throw new IllegalArgumentException();
    512     }
    513 
    514     private static Direction getDirection(int direction) {
    515         for (Direction d : Direction.values()) {
    516             if (direction == d.nativeInt) {
    517                 return d;
    518             }
    519         }
    520 
    521         assert false;
    522         return null;
    523     }
    524 
    525     private static void addPath(int destPath, int srcPath, AffineTransform transform) {
    526         Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
    527         if (destPathDelegate == null) {
    528             return;
    529         }
    530 
    531         Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
    532         if (srcPathDelegate == null) {
    533             return;
    534         }
    535 
    536         if (transform != null) {
    537             destPathDelegate.mPath.append(
    538                     srcPathDelegate.mPath.getPathIterator(transform), false);
    539         } else {
    540             destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
    541         }
    542     }
    543 
    544 
    545     /**
    546      * Returns whether the path is empty.
    547      * @return true if the path is empty.
    548      */
    549     private boolean isEmpty() {
    550         return mPath.getCurrentPoint() == null;
    551     }
    552 
    553     /**
    554      * Fills the given {@link RectF} with the path bounds.
    555      * @param bounds the RectF to be filled.
    556      */
    557     private void fillBounds(RectF bounds) {
    558         Rectangle2D rect = mPath.getBounds2D();
    559         bounds.left = (float)rect.getMinX();
    560         bounds.right = (float)rect.getMaxX();
    561         bounds.top = (float)rect.getMinY();
    562         bounds.bottom = (float)rect.getMaxY();
    563     }
    564 
    565     /**
    566      * Set the beginning of the next contour to the point (x,y).
    567      *
    568      * @param x The x-coordinate of the start of a new contour
    569      * @param y The y-coordinate of the start of a new contour
    570      */
    571     private void moveTo(float x, float y) {
    572         mPath.moveTo(mLastX = x, mLastY = y);
    573     }
    574 
    575     /**
    576      * Set the beginning of the next contour relative to the last point on the
    577      * previous contour. If there is no previous contour, this is treated the
    578      * same as moveTo().
    579      *
    580      * @param dx The amount to add to the x-coordinate of the end of the
    581      *           previous contour, to specify the start of a new contour
    582      * @param dy The amount to add to the y-coordinate of the end of the
    583      *           previous contour, to specify the start of a new contour
    584      */
    585     private void rMoveTo(float dx, float dy) {
    586         dx += mLastX;
    587         dy += mLastY;
    588         mPath.moveTo(mLastX = dx, mLastY = dy);
    589     }
    590 
    591     /**
    592      * Add a line from the last point to the specified point (x,y).
    593      * If no moveTo() call has been made for this contour, the first point is
    594      * automatically set to (0,0).
    595      *
    596      * @param x The x-coordinate of the end of a line
    597      * @param y The y-coordinate of the end of a line
    598      */
    599     private void lineTo(float x, float y) {
    600         mPath.lineTo(mLastX = x, mLastY = y);
    601     }
    602 
    603     /**
    604      * Same as lineTo, but the coordinates are considered relative to the last
    605      * point on this contour. If there is no previous point, then a moveTo(0,0)
    606      * is inserted automatically.
    607      *
    608      * @param dx The amount to add to the x-coordinate of the previous point on
    609      *           this contour, to specify a line
    610      * @param dy The amount to add to the y-coordinate of the previous point on
    611      *           this contour, to specify a line
    612      */
    613     private void rLineTo(float dx, float dy) {
    614         if (isEmpty()) {
    615             mPath.moveTo(mLastX = 0, mLastY = 0);
    616         }
    617         dx += mLastX;
    618         dy += mLastY;
    619         mPath.lineTo(mLastX = dx, mLastY = dy);
    620     }
    621 
    622     /**
    623      * Add a quadratic bezier from the last point, approaching control point
    624      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
    625      * this contour, the first point is automatically set to (0,0).
    626      *
    627      * @param x1 The x-coordinate of the control point on a quadratic curve
    628      * @param y1 The y-coordinate of the control point on a quadratic curve
    629      * @param x2 The x-coordinate of the end point on a quadratic curve
    630      * @param y2 The y-coordinate of the end point on a quadratic curve
    631      */
    632     private void quadTo(float x1, float y1, float x2, float y2) {
    633         mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
    634     }
    635 
    636     /**
    637      * Same as quadTo, but the coordinates are considered relative to the last
    638      * point on this contour. If there is no previous point, then a moveTo(0,0)
    639      * is inserted automatically.
    640      *
    641      * @param dx1 The amount to add to the x-coordinate of the last point on
    642      *            this contour, for the control point of a quadratic curve
    643      * @param dy1 The amount to add to the y-coordinate of the last point on
    644      *            this contour, for the control point of a quadratic curve
    645      * @param dx2 The amount to add to the x-coordinate of the last point on
    646      *            this contour, for the end point of a quadratic curve
    647      * @param dy2 The amount to add to the y-coordinate of the last point on
    648      *            this contour, for the end point of a quadratic curve
    649      */
    650     private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
    651         if (isEmpty()) {
    652             mPath.moveTo(mLastX = 0, mLastY = 0);
    653         }
    654         dx1 += mLastX;
    655         dy1 += mLastY;
    656         dx2 += mLastX;
    657         dy2 += mLastY;
    658         mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
    659     }
    660 
    661     /**
    662      * Add a cubic bezier from the last point, approaching control points
    663      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
    664      * made for this contour, the first point is automatically set to (0,0).
    665      *
    666      * @param x1 The x-coordinate of the 1st control point on a cubic curve
    667      * @param y1 The y-coordinate of the 1st control point on a cubic curve
    668      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
    669      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
    670      * @param x3 The x-coordinate of the end point on a cubic curve
    671      * @param y3 The y-coordinate of the end point on a cubic curve
    672      */
    673     private void cubicTo(float x1, float y1, float x2, float y2,
    674                         float x3, float y3) {
    675         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
    676     }
    677 
    678     /**
    679      * Same as cubicTo, but the coordinates are considered relative to the
    680      * current point on this contour. If there is no previous point, then a
    681      * moveTo(0,0) is inserted automatically.
    682      */
    683     private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
    684                          float dx3, float dy3) {
    685         if (isEmpty()) {
    686             mPath.moveTo(mLastX = 0, mLastY = 0);
    687         }
    688         dx1 += mLastX;
    689         dy1 += mLastY;
    690         dx2 += mLastX;
    691         dy2 += mLastY;
    692         dx3 += mLastX;
    693         dy3 += mLastY;
    694         mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
    695     }
    696 
    697     /**
    698      * Append the specified arc to the path as a new contour. If the start of
    699      * the path is different from the path's current last point, then an
    700      * automatic lineTo() is added to connect the current contour to the
    701      * start of the arc. However, if the path is empty, then we call moveTo()
    702      * with the first point of the arc. The sweep angle is tread mod 360.
    703      *
    704      * @param oval        The bounds of oval defining shape and size of the arc
    705      * @param startAngle  Starting angle (in degrees) where the arc begins
    706      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
    707      *                    mod 360.
    708      * @param forceMoveTo If true, always begin a new contour with the arc
    709      */
    710     private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
    711         Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle,
    712                 -sweepAngle, Arc2D.OPEN);
    713         mPath.append(arc, true /*connect*/);
    714 
    715         resetLastPointFromPath();
    716     }
    717 
    718     /**
    719      * Close the current contour. If the current point is not equal to the
    720      * first point of the contour, a line segment is automatically added.
    721      */
    722     private void close() {
    723         mPath.closePath();
    724     }
    725 
    726     private void resetLastPointFromPath() {
    727         Point2D last = mPath.getCurrentPoint();
    728         mLastX = (float) last.getX();
    729         mLastY = (float) last.getY();
    730     }
    731 
    732     /**
    733      * Add a closed rectangle contour to the path
    734      *
    735      * @param left   The left side of a rectangle to add to the path
    736      * @param top    The top of a rectangle to add to the path
    737      * @param right  The right side of a rectangle to add to the path
    738      * @param bottom The bottom of a rectangle to add to the path
    739      * @param dir    The direction to wind the rectangle's contour
    740      */
    741     private void addRect(float left, float top, float right, float bottom,
    742                         int dir) {
    743         moveTo(left, top);
    744 
    745         Direction direction = getDirection(dir);
    746 
    747         switch (direction) {
    748             case CW:
    749                 lineTo(right, top);
    750                 lineTo(right, bottom);
    751                 lineTo(left, bottom);
    752                 break;
    753             case CCW:
    754                 lineTo(left, bottom);
    755                 lineTo(right, bottom);
    756                 lineTo(right, top);
    757                 break;
    758         }
    759 
    760         close();
    761 
    762         resetLastPointFromPath();
    763     }
    764 
    765     /**
    766      * Offset the path by (dx,dy), returning true on success
    767      *
    768      * @param dx  The amount in the X direction to offset the entire path
    769      * @param dy  The amount in the Y direction to offset the entire path
    770      * @param dst The translated path is written here. If this is null, then
    771      *            the original path is modified.
    772      */
    773     public void offset(float dx, float dy, Path_Delegate dst) {
    774         GeneralPath newPath = new GeneralPath();
    775 
    776         PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
    777 
    778         newPath.append(iterator, false /*connect*/);
    779 
    780         if (dst != null) {
    781             dst.mPath = newPath;
    782         } else {
    783             mPath = newPath;
    784         }
    785     }
    786 
    787     /**
    788      * Transform the points in this path by matrix, and write the answer
    789      * into dst. If dst is null, then the the original path is modified.
    790      *
    791      * @param matrix The matrix to apply to the path
    792      * @param dst    The transformed path is written here. If dst is null,
    793      *               then the the original path is modified
    794      */
    795     public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
    796         if (matrix.hasPerspective()) {
    797             assert false;
    798             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
    799                     "android.graphics.Path#transform() only " +
    800                     "supports affine transformations.", null, null /*data*/);
    801         }
    802 
    803         GeneralPath newPath = new GeneralPath();
    804 
    805         PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
    806 
    807         newPath.append(iterator, false /*connect*/);
    808 
    809         if (dst != null) {
    810             dst.mPath = newPath;
    811         } else {
    812             mPath = newPath;
    813         }
    814     }
    815 }
    816