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