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