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