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 66 public static Path_Delegate getDelegate(long nPath) { 67 return sManager.getDelegate(nPath); 68 } 69 70 public Shape getJavaShape() { 71 return mPath; 72 } 73 74 public void setJavaShape(Shape shape) { 75 mPath.reset(); 76 mPath.append(shape, false /*connect*/); 77 } 78 79 public void reset() { 80 mPath.reset(); 81 } 82 83 public void setPathIterator(PathIterator iterator) { 84 mPath.reset(); 85 mPath.append(iterator, false /*connect*/); 86 } 87 88 // ---- native methods ---- 89 90 @LayoutlibDelegate 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 409 /*package*/ static void native_addPath(long nPath, long src) { 410 addPath(nPath, src, null /*transform*/); 411 } 412 413 @LayoutlibDelegate 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 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 437 /*package*/ static void native_offset(long nPath, float dx, float dy) { 438 native_offset(nPath, dx, dy, 0); 439 } 440 441 @LayoutlibDelegate 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 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 472 /*package*/ static void native_transform(long nPath, long matrix) { 473 native_transform(nPath, matrix, 0); 474 } 475 476 @LayoutlibDelegate 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 483 /*package*/ static void finalizer(long nPath) { 484 sManager.removeJavaReferenceFor(nPath); 485 } 486 487 @LayoutlibDelegate 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 495 private void set(Path_Delegate delegate) { 496 mPath.reset(); 497 setFillType(delegate.mFillType); 498 mPath.append(delegate.mPath, false /*connect*/); 499 } 500 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 741 private void close() { 742 mPath.closePath(); 743 } 744 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 */ 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 */ 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 */ 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