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