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