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