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