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         // Distance that we've covered so far. Used to calculate the fraction of the path that
    512         // we've covered up to this point.
    513         float walkedDistance = .0f;
    514         for (int i = 0; i < nPoints; i++) {
    515             Point2D.Float point = points.get(i);
    516             float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
    517             walkedDistance += distance;
    518             result[i * 3] = walkedDistance / totalLength;
    519             result[i * 3 + 1] = point.x;
    520             result[i * 3 + 2] = point.y;
    521 
    522             previousPoint = point;
    523         }
    524 
    525         return result;
    526     }
    527 
    528     // ---- Private helper methods ----
    529 
    530     private void set(Path_Delegate delegate) {
    531         mPath.reset();
    532         setFillType(delegate.mFillType);
    533         mPath.append(delegate.mPath, false /*connect*/);
    534     }
    535 
    536     private void setFillType(FillType fillType) {
    537         mFillType = fillType;
    538         mPath.setWindingRule(getWindingRule(fillType));
    539     }
    540 
    541     /**
    542      * Returns the Java2D winding rules matching a given Android {@link FillType}.
    543      * @param type the android fill type
    544      * @return the matching java2d winding rule.
    545      */
    546     private static int getWindingRule(FillType type) {
    547         switch (type) {
    548             case WINDING:
    549             case INVERSE_WINDING:
    550                 return GeneralPath.WIND_NON_ZERO;
    551             case EVEN_ODD:
    552             case INVERSE_EVEN_ODD:
    553                 return GeneralPath.WIND_EVEN_ODD;
    554 
    555             default:
    556                 assert false;
    557                 return GeneralPath.WIND_NON_ZERO;
    558         }
    559     }
    560 
    561     @NonNull
    562     private static Direction getDirection(int direction) {
    563         for (Direction d : Direction.values()) {
    564             if (direction == d.nativeInt) {
    565                 return d;
    566             }
    567         }
    568 
    569         assert false;
    570         return null;
    571     }
    572 
    573     public static void addPath(long destPath, long srcPath, AffineTransform transform) {
    574         Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
    575         if (destPathDelegate == null) {
    576             return;
    577         }
    578 
    579         Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
    580         if (srcPathDelegate == null) {
    581             return;
    582         }
    583 
    584         if (transform != null) {
    585             destPathDelegate.mPath.append(
    586                     srcPathDelegate.mPath.getPathIterator(transform), false);
    587         } else {
    588             destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
    589         }
    590     }
    591 
    592 
    593     /**
    594      * Returns whether the path already contains any points.
    595      * Note that this is different to
    596      * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO},
    597      * {@link #isEmpty} will return true while hasPoints will return false.
    598      */
    599     public boolean hasPoints() {
    600         return !mPath.getPathIterator(null).isDone();
    601     }
    602 
    603     /**
    604      * Returns whether the path is empty (contains no lines or curves).
    605      * @see Path#isEmpty
    606      */
    607     public boolean isEmpty() {
    608         if (!mCachedIsEmpty) {
    609             return false;
    610         }
    611 
    612         float[] coords = new float[6];
    613         mCachedIsEmpty = Boolean.TRUE;
    614         for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
    615             int type = it.currentSegment(coords);
    616             if (type != PathIterator.SEG_MOVETO) {
    617                 // Once we know that the path is not empty, we do not need to check again unless
    618                 // Path#reset is called.
    619                 mCachedIsEmpty = false;
    620                 return false;
    621             }
    622         }
    623 
    624         return true;
    625     }
    626 
    627     /**
    628      * Fills the given {@link RectF} with the path bounds.
    629      * @param bounds the RectF to be filled.
    630      */
    631     public void fillBounds(RectF bounds) {
    632         Rectangle2D rect = mPath.getBounds2D();
    633         bounds.left = (float)rect.getMinX();
    634         bounds.right = (float)rect.getMaxX();
    635         bounds.top = (float)rect.getMinY();
    636         bounds.bottom = (float)rect.getMaxY();
    637     }
    638 
    639     /**
    640      * Set the beginning of the next contour to the point (x,y).
    641      *
    642      * @param x The x-coordinate of the start of a new contour
    643      * @param y The y-coordinate of the start of a new contour
    644      */
    645     public void moveTo(float x, float y) {
    646         mPath.moveTo(mLastX = x, mLastY = y);
    647     }
    648 
    649     /**
    650      * Set the beginning of the next contour relative to the last point on the
    651      * previous contour. If there is no previous contour, this is treated the
    652      * same as moveTo().
    653      *
    654      * @param dx The amount to add to the x-coordinate of the end of the
    655      *           previous contour, to specify the start of a new contour
    656      * @param dy The amount to add to the y-coordinate of the end of the
    657      *           previous contour, to specify the start of a new contour
    658      */
    659     public void rMoveTo(float dx, float dy) {
    660         dx += mLastX;
    661         dy += mLastY;
    662         mPath.moveTo(mLastX = dx, mLastY = dy);
    663     }
    664 
    665     /**
    666      * Add a line from the last point to the specified point (x,y).
    667      * If no moveTo() call has been made for this contour, the first point is
    668      * automatically set to (0,0).
    669      *
    670      * @param x The x-coordinate of the end of a line
    671      * @param y The y-coordinate of the end of a line
    672      */
    673     public void lineTo(float x, float y) {
    674         if (!hasPoints()) {
    675             mPath.moveTo(mLastX = 0, mLastY = 0);
    676         }
    677         mPath.lineTo(mLastX = x, mLastY = y);
    678     }
    679 
    680     /**
    681      * Same as lineTo, but the coordinates are considered relative to the last
    682      * point on this contour. If there is no previous point, then a moveTo(0,0)
    683      * is inserted automatically.
    684      *
    685      * @param dx The amount to add to the x-coordinate of the previous point on
    686      *           this contour, to specify a line
    687      * @param dy The amount to add to the y-coordinate of the previous point on
    688      *           this contour, to specify a line
    689      */
    690     public void rLineTo(float dx, float dy) {
    691         if (!hasPoints()) {
    692             mPath.moveTo(mLastX = 0, mLastY = 0);
    693         }
    694 
    695         if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
    696             // The delta is so small that this shouldn't generate a line
    697             return;
    698         }
    699 
    700         dx += mLastX;
    701         dy += mLastY;
    702         mPath.lineTo(mLastX = dx, mLastY = dy);
    703     }
    704 
    705     /**
    706      * Add a quadratic bezier from the last point, approaching control point
    707      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
    708      * this contour, the first point is automatically set to (0,0).
    709      *
    710      * @param x1 The x-coordinate of the control point on a quadratic curve
    711      * @param y1 The y-coordinate of the control point on a quadratic curve
    712      * @param x2 The x-coordinate of the end point on a quadratic curve
    713      * @param y2 The y-coordinate of the end point on a quadratic curve
    714      */
    715     public void quadTo(float x1, float y1, float x2, float y2) {
    716         mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
    717     }
    718 
    719     /**
    720      * Same as quadTo, but the coordinates are considered relative to the last
    721      * point on this contour. If there is no previous point, then a moveTo(0,0)
    722      * is inserted automatically.
    723      *
    724      * @param dx1 The amount to add to the x-coordinate of the last point on
    725      *            this contour, for the control point of a quadratic curve
    726      * @param dy1 The amount to add to the y-coordinate of the last point on
    727      *            this contour, for the control point of a quadratic curve
    728      * @param dx2 The amount to add to the x-coordinate of the last point on
    729      *            this contour, for the end point of a quadratic curve
    730      * @param dy2 The amount to add to the y-coordinate of the last point on
    731      *            this contour, for the end point of a quadratic curve
    732      */
    733     public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
    734         if (!hasPoints()) {
    735             mPath.moveTo(mLastX = 0, mLastY = 0);
    736         }
    737         dx1 += mLastX;
    738         dy1 += mLastY;
    739         dx2 += mLastX;
    740         dy2 += mLastY;
    741         mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
    742     }
    743 
    744     /**
    745      * Add a cubic bezier from the last point, approaching control points
    746      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
    747      * made for this contour, the first point is automatically set to (0,0).
    748      *
    749      * @param x1 The x-coordinate of the 1st control point on a cubic curve
    750      * @param y1 The y-coordinate of the 1st control point on a cubic curve
    751      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
    752      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
    753      * @param x3 The x-coordinate of the end point on a cubic curve
    754      * @param y3 The y-coordinate of the end point on a cubic curve
    755      */
    756     public void cubicTo(float x1, float y1, float x2, float y2,
    757                         float x3, float y3) {
    758         if (!hasPoints()) {
    759             mPath.moveTo(0, 0);
    760         }
    761         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
    762     }
    763 
    764     /**
    765      * Same as cubicTo, but the coordinates are considered relative to the
    766      * current point on this contour. If there is no previous point, then a
    767      * moveTo(0,0) is inserted automatically.
    768      */
    769     public void rCubicTo(float dx1, float dy1, float dx2, float dy2,
    770                          float dx3, float dy3) {
    771         if (!hasPoints()) {
    772             mPath.moveTo(mLastX = 0, mLastY = 0);
    773         }
    774         dx1 += mLastX;
    775         dy1 += mLastY;
    776         dx2 += mLastX;
    777         dy2 += mLastY;
    778         dx3 += mLastX;
    779         dy3 += mLastY;
    780         mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
    781     }
    782 
    783     /**
    784      * Append the specified arc to the path as a new contour. If the start of
    785      * the path is different from the path's current last point, then an
    786      * automatic lineTo() is added to connect the current contour to the
    787      * start of the arc. However, if the path is empty, then we call moveTo()
    788      * with the first point of the arc. The sweep angle is tread mod 360.
    789      *
    790      * @param left        The left of oval defining shape and size of the arc
    791      * @param top         The top of oval defining shape and size of the arc
    792      * @param right       The right of oval defining shape and size of the arc
    793      * @param bottom      The bottom of oval defining shape and size of the arc
    794      * @param startAngle  Starting angle (in degrees) where the arc begins
    795      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
    796      *                    mod 360.
    797      * @param forceMoveTo If true, always begin a new contour with the arc
    798      */
    799     public void arcTo(float left, float top, float right, float bottom, float startAngle,
    800             float sweepAngle,
    801             boolean forceMoveTo) {
    802         Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
    803                 -sweepAngle, Arc2D.OPEN);
    804         mPath.append(arc, true /*connect*/);
    805 
    806         resetLastPointFromPath();
    807     }
    808 
    809     /**
    810      * Close the current contour. If the current point is not equal to the
    811      * first point of the contour, a line segment is automatically added.
    812      */
    813     public void close() {
    814         mPath.closePath();
    815     }
    816 
    817     private void resetLastPointFromPath() {
    818         Point2D last = mPath.getCurrentPoint();
    819         mLastX = (float) last.getX();
    820         mLastY = (float) last.getY();
    821     }
    822 
    823     /**
    824      * Add a closed rectangle contour to the path
    825      *
    826      * @param left   The left side of a rectangle to add to the path
    827      * @param top    The top of a rectangle to add to the path
    828      * @param right  The right side of a rectangle to add to the path
    829      * @param bottom The bottom of a rectangle to add to the path
    830      * @param dir    The direction to wind the rectangle's contour
    831      */
    832     public void addRect(float left, float top, float right, float bottom,
    833                         int dir) {
    834         moveTo(left, top);
    835 
    836         Direction direction = getDirection(dir);
    837 
    838         switch (direction) {
    839             case CW:
    840                 lineTo(right, top);
    841                 lineTo(right, bottom);
    842                 lineTo(left, bottom);
    843                 break;
    844             case CCW:
    845                 lineTo(left, bottom);
    846                 lineTo(right, bottom);
    847                 lineTo(right, top);
    848                 break;
    849         }
    850 
    851         close();
    852 
    853         resetLastPointFromPath();
    854     }
    855 
    856     /**
    857      * Offset the path by (dx,dy), returning true on success
    858      *
    859      * @param dx  The amount in the X direction to offset the entire path
    860      * @param dy  The amount in the Y direction to offset the entire path
    861      */
    862     public void offset(float dx, float dy) {
    863         GeneralPath newPath = new GeneralPath();
    864 
    865         PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
    866 
    867         newPath.append(iterator, false /*connect*/);
    868         mPath = newPath;
    869     }
    870 
    871     /**
    872      * Transform the points in this path by matrix, and write the answer
    873      * into dst. If dst is null, then the the original path is modified.
    874      *
    875      * @param matrix The matrix to apply to the path
    876      * @param dst    The transformed path is written here. If dst is null,
    877      *               then the the original path is modified
    878      */
    879     public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
    880         if (matrix.hasPerspective()) {
    881             assert false;
    882             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
    883                     "android.graphics.Path#transform() only " +
    884                     "supports affine transformations.", null, null /*data*/);
    885         }
    886 
    887         GeneralPath newPath = new GeneralPath();
    888 
    889         PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
    890 
    891         newPath.append(iterator, false /*connect*/);
    892 
    893         if (dst != null) {
    894             dst.mPath = newPath;
    895         } else {
    896             mPath = newPath;
    897         }
    898     }
    899 }
    900