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