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 
getDelegate(long nPath)78     public static Path_Delegate getDelegate(long nPath) {
79         return sManager.getDelegate(nPath);
80     }
81 
getJavaShape()82     public Path2D getJavaShape() {
83         return mPath;
84     }
85 
setJavaShape(Shape shape)86     public void setJavaShape(Shape shape) {
87         reset();
88         mPath.append(shape, false /*connect*/);
89     }
90 
reset()91     public void reset() {
92         mPath.reset();
93         mLastX = 0;
94         mLastY = 0;
95     }
96 
setPathIterator(PathIterator iterator)97     public void setPathIterator(PathIterator iterator) {
98         reset();
99         mPath.append(iterator, false /*connect*/);
100     }
101 
102     // ---- native methods ----
103 
104     @LayoutlibDelegate
nInit()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
nInit(long nPath)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
nReset(long nPath)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
nRewind(long nPath)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
nSet(long native_dst, long nSrc)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
nIsConvex(long nPath)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
nGetFillType(long nPath)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
nSetFillType(long nPath, int ft)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
nIsEmpty(long nPath)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
nIsRect(long nPath, RectF rect)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
nComputeBounds(long nPath, RectF bounds)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
nIncReserve(long nPath, int extraPtCount)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
nMoveTo(long nPath, float x, float y)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
nRMoveTo(long nPath, float dx, float dy)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
nLineTo(long nPath, float x, float y)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
nRLineTo(long nPath, float dx, float dy)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
nQuadTo(long nPath, float x1, float y1, float x2, float y2)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
nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2)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
nCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)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
nRCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)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
nArcTo(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)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
nClose(long nPath)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
nAddRect(long nPath, float left, float top, float right, float bottom, int dir)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
nAddOval(long nPath, float left, float top, float right, float bottom, int dir)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
nAddCircle(long nPath, float x, float y, float radius, int dir)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
nAddArc(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle)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
nAddRoundRect(long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir)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
nAddRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir)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
nAddPath(long nPath, long src, float dx, float dy)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
nAddPath(long nPath, long src)417     /*package*/ static void nAddPath(long nPath, long src) {
418         addPath(nPath, src, null /*transform*/);
419     }
420 
421     @LayoutlibDelegate
nAddPath(long nPath, long src, long matrix)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
nOffset(long nPath, float dx, float dy)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
nSetLastPoint(long nPath, float dx, float dy)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
nTransform(long nPath, long matrix, long dst_path)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
nTransform(long nPath, long matrix)472     /*package*/ static void nTransform(long nPath, long matrix) {
473         nTransform(nPath, matrix, 0);
474     }
475 
476     @LayoutlibDelegate
nOp(long nPath1, long nPath2, int op, long result)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
nGetFinalizer()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
nApproximate(long nPath, float error)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 
set(Path_Delegate delegate)540     private void set(Path_Delegate delegate) {
541         mPath.reset();
542         setFillType(delegate.mFillType);
543         mPath.append(delegate.mPath, false /*connect*/);
544     }
545 
setFillType(FillType fillType)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      */
getWindingRule(FillType type)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
getDirection(int direction)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 
addPath(long destPath, long srcPath, AffineTransform transform)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      */
hasPoints()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      */
isEmpty()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      */
fillBounds(RectF bounds)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      */
moveTo(float x, float y)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      */
rMoveTo(float dx, float dy)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      */
lineTo(float x, float y)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      */
rLineTo(float dx, float dy)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      */
quadTo(float x1, float y1, float x2, float y2)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      */
rQuadTo(float dx1, float dy1, float dx2, float dy2)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      */
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)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      */
rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)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      */
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)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      */
close()823     public void close() {
824         mPath.closePath();
825     }
826 
resetLastPointFromPath()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      */
addRect(float left, float top, float right, float bottom, int dir)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      */
offset(float dx, float dy)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      */
transform(Matrix_Delegate matrix, Path_Delegate dst)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