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