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