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