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