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