• 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
native_op(int nPath1, int nPath2, int op, int result)477     /*package*/ static boolean native_op(int nPath1, int nPath2, int op, int result) {
478         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
479         return false;
480     }
481 
482     @LayoutlibDelegate
finalizer(int nPath)483     /*package*/ static void finalizer(int nPath) {
484         sManager.removeJavaReferenceFor(nPath);
485     }
486 
487 
488     // ---- Private helper methods ----
489 
set(Path_Delegate delegate)490     private void set(Path_Delegate delegate) {
491         mPath.reset();
492         setFillType(delegate.mFillType);
493         mPath.append(delegate.mPath, false /*connect*/);
494     }
495 
setFillType(FillType fillType)496     private void setFillType(FillType fillType) {
497         mFillType = fillType;
498         mPath.setWindingRule(getWindingRule(fillType));
499     }
500 
501     /**
502      * Returns the Java2D winding rules matching a given Android {@link FillType}.
503      * @param type the android fill type
504      * @return the matching java2d winding rule.
505      */
getWindingRule(FillType type)506     private static int getWindingRule(FillType type) {
507         switch (type) {
508             case WINDING:
509             case INVERSE_WINDING:
510                 return GeneralPath.WIND_NON_ZERO;
511             case EVEN_ODD:
512             case INVERSE_EVEN_ODD:
513                 return GeneralPath.WIND_EVEN_ODD;
514         }
515 
516         assert false;
517         throw new IllegalArgumentException();
518     }
519 
getDirection(int direction)520     private static Direction getDirection(int direction) {
521         for (Direction d : Direction.values()) {
522             if (direction == d.nativeInt) {
523                 return d;
524             }
525         }
526 
527         assert false;
528         return null;
529     }
530 
addPath(int destPath, int srcPath, AffineTransform transform)531     private static void addPath(int destPath, int srcPath, AffineTransform transform) {
532         Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
533         if (destPathDelegate == null) {
534             return;
535         }
536 
537         Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
538         if (srcPathDelegate == null) {
539             return;
540         }
541 
542         if (transform != null) {
543             destPathDelegate.mPath.append(
544                     srcPathDelegate.mPath.getPathIterator(transform), false);
545         } else {
546             destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
547         }
548     }
549 
550 
551     /**
552      * Returns whether the path is empty.
553      * @return true if the path is empty.
554      */
isEmpty()555     private boolean isEmpty() {
556         return mPath.getCurrentPoint() == null;
557     }
558 
559     /**
560      * Fills the given {@link RectF} with the path bounds.
561      * @param bounds the RectF to be filled.
562      */
fillBounds(RectF bounds)563     private void fillBounds(RectF bounds) {
564         Rectangle2D rect = mPath.getBounds2D();
565         bounds.left = (float)rect.getMinX();
566         bounds.right = (float)rect.getMaxX();
567         bounds.top = (float)rect.getMinY();
568         bounds.bottom = (float)rect.getMaxY();
569     }
570 
571     /**
572      * Set the beginning of the next contour to the point (x,y).
573      *
574      * @param x The x-coordinate of the start of a new contour
575      * @param y The y-coordinate of the start of a new contour
576      */
moveTo(float x, float y)577     private void moveTo(float x, float y) {
578         mPath.moveTo(mLastX = x, mLastY = y);
579     }
580 
581     /**
582      * Set the beginning of the next contour relative to the last point on the
583      * previous contour. If there is no previous contour, this is treated the
584      * same as moveTo().
585      *
586      * @param dx The amount to add to the x-coordinate of the end of the
587      *           previous contour, to specify the start of a new contour
588      * @param dy The amount to add to the y-coordinate of the end of the
589      *           previous contour, to specify the start of a new contour
590      */
rMoveTo(float dx, float dy)591     private void rMoveTo(float dx, float dy) {
592         dx += mLastX;
593         dy += mLastY;
594         mPath.moveTo(mLastX = dx, mLastY = dy);
595     }
596 
597     /**
598      * Add a line from the last point to the specified point (x,y).
599      * If no moveTo() call has been made for this contour, the first point is
600      * automatically set to (0,0).
601      *
602      * @param x The x-coordinate of the end of a line
603      * @param y The y-coordinate of the end of a line
604      */
lineTo(float x, float y)605     private void lineTo(float x, float y) {
606         mPath.lineTo(mLastX = x, mLastY = y);
607     }
608 
609     /**
610      * Same as lineTo, but the coordinates are considered relative to the last
611      * point on this contour. If there is no previous point, then a moveTo(0,0)
612      * is inserted automatically.
613      *
614      * @param dx The amount to add to the x-coordinate of the previous point on
615      *           this contour, to specify a line
616      * @param dy The amount to add to the y-coordinate of the previous point on
617      *           this contour, to specify a line
618      */
rLineTo(float dx, float dy)619     private void rLineTo(float dx, float dy) {
620         if (isEmpty()) {
621             mPath.moveTo(mLastX = 0, mLastY = 0);
622         }
623         dx += mLastX;
624         dy += mLastY;
625         mPath.lineTo(mLastX = dx, mLastY = dy);
626     }
627 
628     /**
629      * Add a quadratic bezier from the last point, approaching control point
630      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
631      * this contour, the first point is automatically set to (0,0).
632      *
633      * @param x1 The x-coordinate of the control point on a quadratic curve
634      * @param y1 The y-coordinate of the control point on a quadratic curve
635      * @param x2 The x-coordinate of the end point on a quadratic curve
636      * @param y2 The y-coordinate of the end point on a quadratic curve
637      */
quadTo(float x1, float y1, float x2, float y2)638     private void quadTo(float x1, float y1, float x2, float y2) {
639         mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
640     }
641 
642     /**
643      * Same as quadTo, but the coordinates are considered relative to the last
644      * point on this contour. If there is no previous point, then a moveTo(0,0)
645      * is inserted automatically.
646      *
647      * @param dx1 The amount to add to the x-coordinate of the last point on
648      *            this contour, for the control point of a quadratic curve
649      * @param dy1 The amount to add to the y-coordinate of the last point on
650      *            this contour, for the control point of a quadratic curve
651      * @param dx2 The amount to add to the x-coordinate of the last point on
652      *            this contour, for the end point of a quadratic curve
653      * @param dy2 The amount to add to the y-coordinate of the last point on
654      *            this contour, for the end point of a quadratic curve
655      */
rQuadTo(float dx1, float dy1, float dx2, float dy2)656     private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
657         if (isEmpty()) {
658             mPath.moveTo(mLastX = 0, mLastY = 0);
659         }
660         dx1 += mLastX;
661         dy1 += mLastY;
662         dx2 += mLastX;
663         dy2 += mLastY;
664         mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
665     }
666 
667     /**
668      * Add a cubic bezier from the last point, approaching control points
669      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
670      * made for this contour, the first point is automatically set to (0,0).
671      *
672      * @param x1 The x-coordinate of the 1st control point on a cubic curve
673      * @param y1 The y-coordinate of the 1st control point on a cubic curve
674      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
675      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
676      * @param x3 The x-coordinate of the end point on a cubic curve
677      * @param y3 The y-coordinate of the end point on a cubic curve
678      */
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)679     private void cubicTo(float x1, float y1, float x2, float y2,
680                         float x3, float y3) {
681         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
682     }
683 
684     /**
685      * Same as cubicTo, but the coordinates are considered relative to the
686      * current point on this contour. If there is no previous point, then a
687      * moveTo(0,0) is inserted automatically.
688      */
rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)689     private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
690                          float dx3, float dy3) {
691         if (isEmpty()) {
692             mPath.moveTo(mLastX = 0, mLastY = 0);
693         }
694         dx1 += mLastX;
695         dy1 += mLastY;
696         dx2 += mLastX;
697         dy2 += mLastY;
698         dx3 += mLastX;
699         dy3 += mLastY;
700         mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
701     }
702 
703     /**
704      * Append the specified arc to the path as a new contour. If the start of
705      * the path is different from the path's current last point, then an
706      * automatic lineTo() is added to connect the current contour to the
707      * start of the arc. However, if the path is empty, then we call moveTo()
708      * with the first point of the arc. The sweep angle is tread mod 360.
709      *
710      * @param oval        The bounds of oval defining shape and size of the arc
711      * @param startAngle  Starting angle (in degrees) where the arc begins
712      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
713      *                    mod 360.
714      * @param forceMoveTo If true, always begin a new contour with the arc
715      */
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)716     private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
717         Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle,
718                 -sweepAngle, Arc2D.OPEN);
719         mPath.append(arc, true /*connect*/);
720 
721         resetLastPointFromPath();
722     }
723 
724     /**
725      * Close the current contour. If the current point is not equal to the
726      * first point of the contour, a line segment is automatically added.
727      */
close()728     private void close() {
729         mPath.closePath();
730     }
731 
resetLastPointFromPath()732     private void resetLastPointFromPath() {
733         Point2D last = mPath.getCurrentPoint();
734         mLastX = (float) last.getX();
735         mLastY = (float) last.getY();
736     }
737 
738     /**
739      * Add a closed rectangle contour to the path
740      *
741      * @param left   The left side of a rectangle to add to the path
742      * @param top    The top of a rectangle to add to the path
743      * @param right  The right side of a rectangle to add to the path
744      * @param bottom The bottom of a rectangle to add to the path
745      * @param dir    The direction to wind the rectangle's contour
746      */
addRect(float left, float top, float right, float bottom, int dir)747     private void addRect(float left, float top, float right, float bottom,
748                         int dir) {
749         moveTo(left, top);
750 
751         Direction direction = getDirection(dir);
752 
753         switch (direction) {
754             case CW:
755                 lineTo(right, top);
756                 lineTo(right, bottom);
757                 lineTo(left, bottom);
758                 break;
759             case CCW:
760                 lineTo(left, bottom);
761                 lineTo(right, bottom);
762                 lineTo(right, top);
763                 break;
764         }
765 
766         close();
767 
768         resetLastPointFromPath();
769     }
770 
771     /**
772      * Offset the path by (dx,dy), returning true on success
773      *
774      * @param dx  The amount in the X direction to offset the entire path
775      * @param dy  The amount in the Y direction to offset the entire path
776      * @param dst The translated path is written here. If this is null, then
777      *            the original path is modified.
778      */
offset(float dx, float dy, Path_Delegate dst)779     public void offset(float dx, float dy, Path_Delegate dst) {
780         GeneralPath newPath = new GeneralPath();
781 
782         PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
783 
784         newPath.append(iterator, false /*connect*/);
785 
786         if (dst != null) {
787             dst.mPath = newPath;
788         } else {
789             mPath = newPath;
790         }
791     }
792 
793     /**
794      * Transform the points in this path by matrix, and write the answer
795      * into dst. If dst is null, then the the original path is modified.
796      *
797      * @param matrix The matrix to apply to the path
798      * @param dst    The transformed path is written here. If dst is null,
799      *               then the the original path is modified
800      */
transform(Matrix_Delegate matrix, Path_Delegate dst)801     public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
802         if (matrix.hasPerspective()) {
803             assert false;
804             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
805                     "android.graphics.Path#transform() only " +
806                     "supports affine transformations.", null, null /*data*/);
807         }
808 
809         GeneralPath newPath = new GeneralPath();
810 
811         PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
812 
813         newPath.append(iterator, false /*connect*/);
814 
815         if (dst != null) {
816             dst.mPath = newPath;
817         } else {
818             mPath = newPath;
819         }
820     }
821 }
822