• 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(long nPath)66     public static Path_Delegate getDelegate(long 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 long init1() {
92         // create the delegate
93         Path_Delegate newDelegate = new Path_Delegate();
94 
95         return sManager.addNewDelegate(newDelegate);
96     }
97 
98     @LayoutlibDelegate
init2(long nPath)99     /*package*/ static long init2(long 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(long nPath)113     /*package*/ static void native_reset(long 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(long nPath)123     /*package*/ static void native_rewind(long 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(long native_dst, long native_src)130     /*package*/ static void native_set(long native_dst, long 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_isConvex(long nPath)145     /*package*/ static boolean native_isConvex(long nPath) {
146         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
147                 "Path.isConvex is not supported.", null, null);
148         return true;
149     }
150 
151     @LayoutlibDelegate
native_getFillType(long nPath)152     /*package*/ static int native_getFillType(long nPath) {
153         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
154         if (pathDelegate == null) {
155             return 0;
156         }
157 
158         return pathDelegate.mFillType.nativeInt;
159     }
160 
161     @LayoutlibDelegate
native_setFillType(long nPath, int ft)162     /*package*/ static void native_setFillType(long nPath, int ft) {
163         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
164         if (pathDelegate == null) {
165             return;
166         }
167 
168         pathDelegate.mFillType = Path.sFillTypeArray[ft];
169     }
170 
171     @LayoutlibDelegate
native_isEmpty(long nPath)172     /*package*/ static boolean native_isEmpty(long nPath) {
173         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
174         if (pathDelegate == null) {
175             return true;
176         }
177 
178         return pathDelegate.isEmpty();
179     }
180 
181     @LayoutlibDelegate
native_isRect(long nPath, RectF rect)182     /*package*/ static boolean native_isRect(long nPath, RectF rect) {
183         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
184         if (pathDelegate == null) {
185             return false;
186         }
187 
188         // create an Area that can test if the path is a rect
189         Area area = new Area(pathDelegate.mPath);
190         if (area.isRectangular()) {
191             if (rect != null) {
192                 pathDelegate.fillBounds(rect);
193             }
194 
195             return true;
196         }
197 
198         return false;
199     }
200 
201     @LayoutlibDelegate
native_computeBounds(long nPath, RectF bounds)202     /*package*/ static void native_computeBounds(long nPath, RectF bounds) {
203         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
204         if (pathDelegate == null) {
205             return;
206         }
207 
208         pathDelegate.fillBounds(bounds);
209     }
210 
211     @LayoutlibDelegate
native_incReserve(long nPath, int extraPtCount)212     /*package*/ static void native_incReserve(long nPath, int extraPtCount) {
213         // since we use a java2D path, there's no way to pre-allocate new points,
214         // so we do nothing.
215     }
216 
217     @LayoutlibDelegate
native_moveTo(long nPath, float x, float y)218     /*package*/ static void native_moveTo(long nPath, float x, float y) {
219         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
220         if (pathDelegate == null) {
221             return;
222         }
223 
224         pathDelegate.moveTo(x, y);
225     }
226 
227     @LayoutlibDelegate
native_rMoveTo(long nPath, float dx, float dy)228     /*package*/ static void native_rMoveTo(long nPath, float dx, float dy) {
229         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
230         if (pathDelegate == null) {
231             return;
232         }
233 
234         pathDelegate.rMoveTo(dx, dy);
235     }
236 
237     @LayoutlibDelegate
native_lineTo(long nPath, float x, float y)238     /*package*/ static void native_lineTo(long nPath, float x, float y) {
239         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
240         if (pathDelegate == null) {
241             return;
242         }
243 
244         pathDelegate.lineTo(x, y);
245     }
246 
247     @LayoutlibDelegate
native_rLineTo(long nPath, float dx, float dy)248     /*package*/ static void native_rLineTo(long nPath, float dx, float dy) {
249         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
250         if (pathDelegate == null) {
251             return;
252         }
253 
254         pathDelegate.rLineTo(dx, dy);
255     }
256 
257     @LayoutlibDelegate
native_quadTo(long nPath, float x1, float y1, float x2, float y2)258     /*package*/ static void native_quadTo(long nPath, float x1, float y1, float x2, float y2) {
259         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
260         if (pathDelegate == null) {
261             return;
262         }
263 
264         pathDelegate.quadTo(x1, y1, x2, y2);
265     }
266 
267     @LayoutlibDelegate
native_rQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2)268     /*package*/ static void native_rQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
269         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
270         if (pathDelegate == null) {
271             return;
272         }
273 
274         pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
275     }
276 
277     @LayoutlibDelegate
native_cubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)278     /*package*/ static void native_cubicTo(long nPath, float x1, float y1,
279             float x2, float y2, float x3, float y3) {
280         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
281         if (pathDelegate == null) {
282             return;
283         }
284 
285         pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
286     }
287 
288     @LayoutlibDelegate
native_rCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)289     /*package*/ static void native_rCubicTo(long nPath, float x1, float y1,
290             float x2, float y2, float x3, float y3) {
291         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
292         if (pathDelegate == null) {
293             return;
294         }
295 
296         pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
297     }
298 
299     @LayoutlibDelegate
native_arcTo(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)300     /*package*/ static void native_arcTo(long nPath, float left, float top, float right,
301             float bottom,
302                     float startAngle, float sweepAngle, boolean forceMoveTo) {
303         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
304         if (pathDelegate == null) {
305             return;
306         }
307 
308         pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
309     }
310 
311     @LayoutlibDelegate
native_close(long nPath)312     /*package*/ static void native_close(long nPath) {
313         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
314         if (pathDelegate == null) {
315             return;
316         }
317 
318         pathDelegate.close();
319     }
320 
321     @LayoutlibDelegate
native_addRect(long nPath, float left, float top, float right, float bottom, int dir)322     /*package*/ static void native_addRect(long nPath,
323             float left, float top, float right, float bottom, int dir) {
324         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
325         if (pathDelegate == null) {
326             return;
327         }
328 
329         pathDelegate.addRect(left, top, right, bottom, dir);
330     }
331 
332     @LayoutlibDelegate
native_addOval(long nPath, float left, float top, float right, float bottom, int dir)333     /*package*/ static void native_addOval(long nPath, float left, float top, float right,
334             float bottom, int dir) {
335         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
336         if (pathDelegate == null) {
337             return;
338         }
339 
340         pathDelegate.mPath.append(new Ellipse2D.Float(
341                 left, top, right - left, bottom - top), false);
342     }
343 
344     @LayoutlibDelegate
native_addCircle(long nPath, float x, float y, float radius, int dir)345     /*package*/ static void native_addCircle(long nPath, float x, float y, float radius, int dir) {
346         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
347         if (pathDelegate == null) {
348             return;
349         }
350 
351         // because x/y is the center of the circle, need to offset this by the radius
352         pathDelegate.mPath.append(new Ellipse2D.Float(
353                 x - radius, y - radius, radius * 2, radius * 2), false);
354     }
355 
356     @LayoutlibDelegate
native_addArc(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle)357     /*package*/ static void native_addArc(long nPath, float left, float top, float right,
358             float bottom, float startAngle, float sweepAngle) {
359         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
360         if (pathDelegate == null) {
361             return;
362         }
363 
364         // because x/y is the center of the circle, need to offset this by the radius
365         pathDelegate.mPath.append(new Arc2D.Float(
366                 left, top, right - left, bottom - top,
367                 -startAngle, -sweepAngle, Arc2D.OPEN), false);
368     }
369 
370     @LayoutlibDelegate
native_addRoundRect(long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir)371     /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right,
372             float bottom, float rx, float ry, int dir) {
373 
374         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
375         if (pathDelegate == null) {
376             return;
377         }
378 
379         pathDelegate.mPath.append(new RoundRectangle2D.Float(
380                 left, top, right - left, bottom - top, rx * 2, ry * 2), false);
381     }
382 
383     @LayoutlibDelegate
native_addRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir)384     /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right,
385             float bottom, 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, left, top, right, bottom, 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(long nPath, long src, float dx, float dy)404     /*package*/ static void native_addPath(long nPath, long src, float dx, float dy) {
405         addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
406     }
407 
408     @LayoutlibDelegate
native_addPath(long nPath, long src)409     /*package*/ static void native_addPath(long nPath, long src) {
410         addPath(nPath, src, null /*transform*/);
411     }
412 
413     @LayoutlibDelegate
native_addPath(long nPath, long src, long matrix)414     /*package*/ static void native_addPath(long nPath, long src, long 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(long nPath, float dx, float dy, long dst_path)424     /*package*/ static void native_offset(long nPath, float dx, float dy, long 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(long nPath, float dx, float dy)437     /*package*/ static void native_offset(long nPath, float dx, float dy) {
438         native_offset(nPath, dx, dy, 0);
439     }
440 
441     @LayoutlibDelegate
native_setLastPoint(long nPath, float dx, float dy)442     /*package*/ static void native_setLastPoint(long 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(long nPath, long matrix, long dst_path)453     /*package*/ static void native_transform(long nPath, long matrix,
454                                                 long 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(long nPath, long matrix)472     /*package*/ static void native_transform(long nPath, long matrix) {
473         native_transform(nPath, matrix, 0);
474     }
475 
476     @LayoutlibDelegate
native_op(long nPath1, long nPath2, int op, long result)477     /*package*/ static boolean native_op(long nPath1, long nPath2, int op, long result) {
478         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
479         return false;
480     }
481 
482     @LayoutlibDelegate
finalizer(long nPath)483     /*package*/ static void finalizer(long nPath) {
484         sManager.removeJavaReferenceFor(nPath);
485     }
486 
487     @LayoutlibDelegate
native_approximate(long nPath, float error)488     /*package*/ static float[] native_approximate(long nPath, float error) {
489         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not supported", null);
490         return new float[0];
491     }
492 
493     // ---- Private helper methods ----
494 
set(Path_Delegate delegate)495     private void set(Path_Delegate delegate) {
496         mPath.reset();
497         setFillType(delegate.mFillType);
498         mPath.append(delegate.mPath, false /*connect*/);
499     }
500 
setFillType(FillType fillType)501     private void setFillType(FillType fillType) {
502         mFillType = fillType;
503         mPath.setWindingRule(getWindingRule(fillType));
504     }
505 
506     /**
507      * Returns the Java2D winding rules matching a given Android {@link FillType}.
508      * @param type the android fill type
509      * @return the matching java2d winding rule.
510      */
getWindingRule(FillType type)511     private static int getWindingRule(FillType type) {
512         switch (type) {
513             case WINDING:
514             case INVERSE_WINDING:
515                 return GeneralPath.WIND_NON_ZERO;
516             case EVEN_ODD:
517             case INVERSE_EVEN_ODD:
518                 return GeneralPath.WIND_EVEN_ODD;
519         }
520 
521         assert false;
522         throw new IllegalArgumentException();
523     }
524 
getDirection(int direction)525     private static Direction getDirection(int direction) {
526         for (Direction d : Direction.values()) {
527             if (direction == d.nativeInt) {
528                 return d;
529             }
530         }
531 
532         assert false;
533         return null;
534     }
535 
addPath(long destPath, long srcPath, AffineTransform transform)536     private static void addPath(long destPath, long srcPath, AffineTransform transform) {
537         Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
538         if (destPathDelegate == null) {
539             return;
540         }
541 
542         Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
543         if (srcPathDelegate == null) {
544             return;
545         }
546 
547         if (transform != null) {
548             destPathDelegate.mPath.append(
549                     srcPathDelegate.mPath.getPathIterator(transform), false);
550         } else {
551             destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
552         }
553     }
554 
555 
556     /**
557      * Returns whether the path is empty.
558      * @return true if the path is empty.
559      */
isEmpty()560     private boolean isEmpty() {
561         return mPath.getCurrentPoint() == null;
562     }
563 
564     /**
565      * Fills the given {@link RectF} with the path bounds.
566      * @param bounds the RectF to be filled.
567      */
fillBounds(RectF bounds)568     private void fillBounds(RectF bounds) {
569         Rectangle2D rect = mPath.getBounds2D();
570         bounds.left = (float)rect.getMinX();
571         bounds.right = (float)rect.getMaxX();
572         bounds.top = (float)rect.getMinY();
573         bounds.bottom = (float)rect.getMaxY();
574     }
575 
576     /**
577      * Set the beginning of the next contour to the point (x,y).
578      *
579      * @param x The x-coordinate of the start of a new contour
580      * @param y The y-coordinate of the start of a new contour
581      */
moveTo(float x, float y)582     private void moveTo(float x, float y) {
583         mPath.moveTo(mLastX = x, mLastY = y);
584     }
585 
586     /**
587      * Set the beginning of the next contour relative to the last point on the
588      * previous contour. If there is no previous contour, this is treated the
589      * same as moveTo().
590      *
591      * @param dx The amount to add to the x-coordinate of the end of the
592      *           previous contour, to specify the start of a new contour
593      * @param dy The amount to add to the y-coordinate of the end of the
594      *           previous contour, to specify the start of a new contour
595      */
rMoveTo(float dx, float dy)596     private void rMoveTo(float dx, float dy) {
597         dx += mLastX;
598         dy += mLastY;
599         mPath.moveTo(mLastX = dx, mLastY = dy);
600     }
601 
602     /**
603      * Add a line from the last point to the specified point (x,y).
604      * If no moveTo() call has been made for this contour, the first point is
605      * automatically set to (0,0).
606      *
607      * @param x The x-coordinate of the end of a line
608      * @param y The y-coordinate of the end of a line
609      */
lineTo(float x, float y)610     private void lineTo(float x, float y) {
611         if (isEmpty()) {
612             mPath.moveTo(mLastX = 0, mLastY = 0);
613         }
614         mPath.lineTo(mLastX = x, mLastY = y);
615     }
616 
617     /**
618      * Same as lineTo, but the coordinates are considered relative to the last
619      * point on this contour. If there is no previous point, then a moveTo(0,0)
620      * is inserted automatically.
621      *
622      * @param dx The amount to add to the x-coordinate of the previous point on
623      *           this contour, to specify a line
624      * @param dy The amount to add to the y-coordinate of the previous point on
625      *           this contour, to specify a line
626      */
rLineTo(float dx, float dy)627     private void rLineTo(float dx, float dy) {
628         if (isEmpty()) {
629             mPath.moveTo(mLastX = 0, mLastY = 0);
630         }
631         dx += mLastX;
632         dy += mLastY;
633         mPath.lineTo(mLastX = dx, mLastY = dy);
634     }
635 
636     /**
637      * Add a quadratic bezier from the last point, approaching control point
638      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
639      * this contour, the first point is automatically set to (0,0).
640      *
641      * @param x1 The x-coordinate of the control point on a quadratic curve
642      * @param y1 The y-coordinate of the control point on a quadratic curve
643      * @param x2 The x-coordinate of the end point on a quadratic curve
644      * @param y2 The y-coordinate of the end point on a quadratic curve
645      */
quadTo(float x1, float y1, float x2, float y2)646     private void quadTo(float x1, float y1, float x2, float y2) {
647         mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
648     }
649 
650     /**
651      * Same as quadTo, but the coordinates are considered relative to the last
652      * point on this contour. If there is no previous point, then a moveTo(0,0)
653      * is inserted automatically.
654      *
655      * @param dx1 The amount to add to the x-coordinate of the last point on
656      *            this contour, for the control point of a quadratic curve
657      * @param dy1 The amount to add to the y-coordinate of the last point on
658      *            this contour, for the control point of a quadratic curve
659      * @param dx2 The amount to add to the x-coordinate of the last point on
660      *            this contour, for the end point of a quadratic curve
661      * @param dy2 The amount to add to the y-coordinate of the last point on
662      *            this contour, for the end point of a quadratic curve
663      */
rQuadTo(float dx1, float dy1, float dx2, float dy2)664     private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
665         if (isEmpty()) {
666             mPath.moveTo(mLastX = 0, mLastY = 0);
667         }
668         dx1 += mLastX;
669         dy1 += mLastY;
670         dx2 += mLastX;
671         dy2 += mLastY;
672         mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
673     }
674 
675     /**
676      * Add a cubic bezier from the last point, approaching control points
677      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
678      * made for this contour, the first point is automatically set to (0,0).
679      *
680      * @param x1 The x-coordinate of the 1st control point on a cubic curve
681      * @param y1 The y-coordinate of the 1st control point on a cubic curve
682      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
683      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
684      * @param x3 The x-coordinate of the end point on a cubic curve
685      * @param y3 The y-coordinate of the end point on a cubic curve
686      */
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)687     private void cubicTo(float x1, float y1, float x2, float y2,
688                         float x3, float y3) {
689         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
690     }
691 
692     /**
693      * Same as cubicTo, but the coordinates are considered relative to the
694      * current point on this contour. If there is no previous point, then a
695      * moveTo(0,0) is inserted automatically.
696      */
rCubicTo(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)697     private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
698                          float dx3, float dy3) {
699         if (isEmpty()) {
700             mPath.moveTo(mLastX = 0, mLastY = 0);
701         }
702         dx1 += mLastX;
703         dy1 += mLastY;
704         dx2 += mLastX;
705         dy2 += mLastY;
706         dx3 += mLastX;
707         dy3 += mLastY;
708         mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
709     }
710 
711     /**
712      * Append the specified arc to the path as a new contour. If the start of
713      * the path is different from the path's current last point, then an
714      * automatic lineTo() is added to connect the current contour to the
715      * start of the arc. However, if the path is empty, then we call moveTo()
716      * with the first point of the arc. The sweep angle is tread mod 360.
717      *
718      * @param left        The left of oval defining shape and size of the arc
719      * @param top         The top of oval defining shape and size of the arc
720      * @param right       The right of oval defining shape and size of the arc
721      * @param bottom      The bottom of oval defining shape and size of the arc
722      * @param startAngle  Starting angle (in degrees) where the arc begins
723      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
724      *                    mod 360.
725      * @param forceMoveTo If true, always begin a new contour with the arc
726      */
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)727     private void arcTo(float left, float top, float right, float bottom, float startAngle,
728             float sweepAngle,
729             boolean forceMoveTo) {
730         Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
731                 -sweepAngle, Arc2D.OPEN);
732         mPath.append(arc, true /*connect*/);
733 
734         resetLastPointFromPath();
735     }
736 
737     /**
738      * Close the current contour. If the current point is not equal to the
739      * first point of the contour, a line segment is automatically added.
740      */
close()741     private void close() {
742         mPath.closePath();
743     }
744 
resetLastPointFromPath()745     private void resetLastPointFromPath() {
746         Point2D last = mPath.getCurrentPoint();
747         mLastX = (float) last.getX();
748         mLastY = (float) last.getY();
749     }
750 
751     /**
752      * Add a closed rectangle contour to the path
753      *
754      * @param left   The left side of a rectangle to add to the path
755      * @param top    The top of a rectangle to add to the path
756      * @param right  The right side of a rectangle to add to the path
757      * @param bottom The bottom of a rectangle to add to the path
758      * @param dir    The direction to wind the rectangle's contour
759      */
addRect(float left, float top, float right, float bottom, int dir)760     private void addRect(float left, float top, float right, float bottom,
761                         int dir) {
762         moveTo(left, top);
763 
764         Direction direction = getDirection(dir);
765 
766         switch (direction) {
767             case CW:
768                 lineTo(right, top);
769                 lineTo(right, bottom);
770                 lineTo(left, bottom);
771                 break;
772             case CCW:
773                 lineTo(left, bottom);
774                 lineTo(right, bottom);
775                 lineTo(right, top);
776                 break;
777         }
778 
779         close();
780 
781         resetLastPointFromPath();
782     }
783 
784     /**
785      * Offset the path by (dx,dy), returning true on success
786      *
787      * @param dx  The amount in the X direction to offset the entire path
788      * @param dy  The amount in the Y direction to offset the entire path
789      * @param dst The translated path is written here. If this is null, then
790      *            the original path is modified.
791      */
offset(float dx, float dy, Path_Delegate dst)792     public void offset(float dx, float dy, Path_Delegate dst) {
793         GeneralPath newPath = new GeneralPath();
794 
795         PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
796 
797         newPath.append(iterator, false /*connect*/);
798 
799         if (dst != null) {
800             dst.mPath = newPath;
801         } else {
802             mPath = newPath;
803         }
804     }
805 
806     /**
807      * Transform the points in this path by matrix, and write the answer
808      * into dst. If dst is null, then the the original path is modified.
809      *
810      * @param matrix The matrix to apply to the path
811      * @param dst    The transformed path is written here. If dst is null,
812      *               then the the original path is modified
813      */
transform(Matrix_Delegate matrix, Path_Delegate dst)814     public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
815         if (matrix.hasPerspective()) {
816             assert false;
817             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
818                     "android.graphics.Path#transform() only " +
819                     "supports affine transformations.", null, null /*data*/);
820         }
821 
822         GeneralPath newPath = new GeneralPath();
823 
824         PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
825 
826         newPath.append(iterator, false /*connect*/);
827 
828         if (dst != null) {
829             dst.mPath = newPath;
830         } else {
831             mPath = newPath;
832         }
833     }
834 }
835