1 /* 2 * Copyright (c) 2006, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.geom; 27 28 import com.sun.javafx.geom.transform.BaseTransform; 29 import java.util.Arrays; 30 31 /** 32 * The {@code Path2D} class provides a simple, yet flexible 33 * shape which represents an arbitrary geometric path. 34 * It can fully represent any path which can be iterated by the 35 * {@link PathIterator} interface including all of its segment 36 * types and winding rules and it implements all of the 37 * basic hit testing methods of the {@link Shape} interface. 38 * <p> 39 * Use {@link Path2D} when dealing with data that can be represented 40 * and used with floating point precision. 41 * <p> 42 * {@code Path2D} provides exactly those facilities required for 43 * basic construction and management of a geometric path and 44 * implementation of the above interfaces with little added 45 * interpretation. 46 * If it is useful to manipulate the interiors of closed 47 * geometric shapes beyond simple hit testing then the 48 * {@link Area} class provides additional capabilities 49 * specifically targeted at closed figures. 50 * While both classes nominally implement the {@code Shape} 51 * interface, they differ in purpose and together they provide 52 * two useful views of a geometric shape where {@code Path2D} 53 * deals primarily with a trajectory formed by path segments 54 * and {@code Area} deals more with interpretation and manipulation 55 * of enclosed regions of 2D geometric space. 56 * <p> 57 * The {@link PathIterator} interface has more detailed descriptions 58 * of the types of segments that make up a path and the winding rules 59 * that control how to determine which regions are inside or outside 60 * the path. 61 * 62 * @version 1.10, 05/05/07 63 */ 64 public class Path2D extends Shape implements PathConsumer2D { 65 66 static final int curvecoords[] = {2, 2, 4, 6, 0}; 67 68 public enum CornerPrefix { 69 CORNER_ONLY, 70 MOVE_THEN_CORNER, 71 LINE_THEN_CORNER 72 } 73 74 /** 75 * An even-odd winding rule for determining the interior of 76 * a path. 77 * 78 * @see PathIterator#WIND_EVEN_ODD 79 */ 80 public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; 81 82 /** 83 * A non-zero winding rule for determining the interior of a 84 * path. 85 * 86 * @see PathIterator#WIND_NON_ZERO 87 */ 88 public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; 89 90 // For code simplicity, copy these constants to our namespace 91 // and cast them to byte constants for easy storage. 92 private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO; 93 private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO; 94 private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO; 95 private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO; 96 private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE; 97 98 byte[] pointTypes; 99 int numTypes; 100 int numCoords; 101 int windingRule; 102 103 static final int INIT_SIZE = 20; 104 static final int EXPAND_MAX = 500; 105 static final int EXPAND_MAX_COORDS = EXPAND_MAX * 2; 106 107 float floatCoords[]; 108 float moveX, moveY; 109 float prevX, prevY; 110 float currX, currY; 111 112 /** 113 * Constructs a new empty single precision {@code Path2D} object 114 * with a default winding rule of {@link #WIND_NON_ZERO}. 115 */ 116 public Path2D() { 117 this(WIND_NON_ZERO, INIT_SIZE); 118 } 119 120 /** 121 * Constructs a new empty single precision {@code Path2D} object 122 * with the specified winding rule to control operations that 123 * require the interior of the path to be defined. 124 * 125 * @param rule the winding rule 126 * @see #WIND_EVEN_ODD 127 * @see #WIND_NON_ZERO 128 */ 129 public Path2D(int rule) { 130 this(rule, INIT_SIZE); 131 } 132 133 /** 134 * Constructs a new empty single precision {@code Path2D} object 135 * with the specified winding rule and the specified initial 136 * capacity to store path segments. 137 * This number is an initial guess as to how many path segments 138 * will be added to the path, but the storage is expanded as 139 * needed to store whatever path segments are added. 140 * 141 * @param rule the winding rule 142 * @param initialCapacity the estimate for the number of path segments 143 * in the path 144 * @see #WIND_EVEN_ODD 145 * @see #WIND_NON_ZERO 146 */ 147 public Path2D(int rule, int initialCapacity) { 148 setWindingRule(rule); 149 this.pointTypes = new byte[initialCapacity]; 150 floatCoords = new float[initialCapacity * 2]; 151 } 152 153 /** 154 * Constructs a new single precision {@code Path2D} object 155 * from an arbitrary {@link Shape} object. 156 * All of the initial geometry and the winding rule for this path are 157 * taken from the specified {@code Shape} object. 158 * 159 * @param s the specified {@code Shape} object 160 */ 161 public Path2D(Shape s) { 162 this(s, null); 163 } 164 165 /** 166 * Constructs a new single precision {@code Path2D} object 167 * from an arbitrary {@link Shape} object, transformed by an 168 * {@link BaseTransform} object. 169 * All of the initial geometry and the winding rule for this path are 170 * taken from the specified {@code Shape} object and transformed 171 * by the specified {@code BaseTransform} object. 172 * 173 * @param s the specified {@code Shape} object 174 * @param tx the specified {@code BaseTransform} object 175 */ 176 public Path2D(Shape s, BaseTransform tx) { 177 if (s instanceof Path2D) { 178 Path2D p2d = (Path2D) s; 179 setWindingRule(p2d.windingRule); 180 this.numTypes = p2d.numTypes; 181 this.pointTypes = Arrays.copyOf(p2d.pointTypes, numTypes); 182 this.numCoords = p2d.numCoords; 183 if (tx == null || tx.isIdentity()) { 184 this.floatCoords = Arrays.copyOf(p2d.floatCoords, numCoords); 185 this.moveX = p2d.moveX; 186 this.moveY = p2d.moveY; 187 this.prevX = p2d.prevX; 188 this.prevY = p2d.prevY; 189 this.currX = p2d.currX; 190 this.currY = p2d.currY; 191 } else { 192 this.floatCoords = new float[numCoords + 6]; 193 tx.transform(p2d.floatCoords, 0, this.floatCoords, 0, numCoords / 2); 194 floatCoords[numCoords + 0] = moveX; 195 floatCoords[numCoords + 1] = moveY; 196 floatCoords[numCoords + 2] = prevX; 197 floatCoords[numCoords + 3] = prevY; 198 floatCoords[numCoords + 4] = currX; 199 floatCoords[numCoords + 5] = currY; 200 tx.transform(this.floatCoords, numCoords, this.floatCoords, numCoords, 3); 201 moveX = floatCoords[numCoords + 0]; 202 moveY = floatCoords[numCoords + 1]; 203 prevX = floatCoords[numCoords + 2]; 204 prevY = floatCoords[numCoords + 3]; 205 currX = floatCoords[numCoords + 4]; 206 currY = floatCoords[numCoords + 5]; 207 } 208 } else { 209 PathIterator pi = s.getPathIterator(tx); 210 setWindingRule(pi.getWindingRule()); 211 this.pointTypes = new byte[INIT_SIZE]; 212 this.floatCoords = new float[INIT_SIZE * 2]; 213 append(pi, false); 214 } 215 } 216 217 218 /** 219 * Construct a Path2D from pre-composed data. 220 * Used by internal font code which has obtained the path data 221 * for a glyph outline, and which promises not to 222 * mess with the arrays, dropping all other references, 223 so there's no need to clone them here. 224 */ 225 public Path2D(int windingRule, 226 byte[] pointTypes, 227 int numTypes, 228 float[] pointCoords, 229 int numCoords) 230 { 231 this.windingRule = windingRule; 232 this.pointTypes = pointTypes; 233 this.numTypes = numTypes; 234 this.floatCoords = pointCoords; 235 this.numCoords = numCoords; 236 } 237 238 Point2D getPoint(int coordindex) { 239 return new Point2D(floatCoords[coordindex], 240 floatCoords[coordindex+1]); 241 } 242 243 private boolean close(int ix, float fx, float tolerance) { 244 return (Math.abs(ix - fx) <= tolerance); 245 } 246 247 /** 248 * Check and return if the fillable interior of the path is a simple 249 * rectangle on nearly integer bounds and initialize the indicated 250 * {@link Rectangle} with the integer representation of the rectangle 251 * if it is. 252 * The method will return false if the path is not rectangular, or if 253 * the horizontal and linear segments are not within the indicated 254 * tolerance of an integer coordinate, or if the resulting rectangle 255 * cannot be safely represented by the integer attributes of the 256 * {@code Rectangle} object. 257 * 258 * @param retrect the {@code Rectangle} to return the rectangular area, 259 * or null 260 * @param tolerance the maximum difference from an integer allowed 261 * for any edge of the rectangle 262 * @return true iff the path is a simple rectangle 263 */ 264 public boolean checkAndGetIntRect(Rectangle retrect, float tolerance) { 265 // Valid rectangular paths are: 266 // 4 segs: MOVE, LINE, LINE, LINE (implicit CLOSE) 267 // 5 segs: MOVE, LINE, LINE, LINE, LINE 268 // 5 segs: MOVE, LINE, LINE, LINE, CLOSE 269 // 6 segs: MOVE, LINE, LINE, LINE, LINE, CLOSE 270 if (numTypes == 5) { 271 // points[4] can be LINETO or CLOSE 272 if (pointTypes[4] != SEG_LINETO && pointTypes[4] != SEG_CLOSE) { 273 return false; 274 } 275 } else if (numTypes == 6) { 276 // points[4] must be LINETO and 277 // points[5] must be CLOSE 278 if (pointTypes[4] != SEG_LINETO) return false; 279 if (pointTypes[5] != SEG_CLOSE) return false; 280 } else if (numTypes != 4) { 281 return false; 282 } 283 if (pointTypes[0] != SEG_MOVETO) return false; 284 if (pointTypes[1] != SEG_LINETO) return false; 285 if (pointTypes[2] != SEG_LINETO) return false; 286 if (pointTypes[3] != SEG_LINETO) return false; 287 288 int x0 = (int) (floatCoords[0] + 0.5f); 289 int y0 = (int) (floatCoords[1] + 0.5f); 290 if (!close(x0, floatCoords[0], tolerance)) return false; 291 if (!close(y0, floatCoords[1], tolerance)) return false; 292 293 int x1 = (int) (floatCoords[2] + 0.5f); 294 int y1 = (int) (floatCoords[3] + 0.5f); 295 if (!close(x1, floatCoords[2], tolerance)) return false; 296 if (!close(y1, floatCoords[3], tolerance)) return false; 297 298 int x2 = (int) (floatCoords[4] + 0.5f); 299 int y2 = (int) (floatCoords[5] + 0.5f); 300 if (!close(x2, floatCoords[4], tolerance)) return false; 301 if (!close(y2, floatCoords[5], tolerance)) return false; 302 303 int x3 = (int) (floatCoords[6] + 0.5f); 304 int y3 = (int) (floatCoords[7] + 0.5f); 305 if (!close(x3, floatCoords[6], tolerance)) return false; 306 if (!close(y3, floatCoords[7], tolerance)) return false; 307 308 if (numTypes > 4 && pointTypes[4] == SEG_LINETO) { 309 if (!close(x0, floatCoords[8], tolerance)) return false; 310 if (!close(y0, floatCoords[9], tolerance)) return false; 311 } 312 313 if ((x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) || 314 (y0 == y1 && y2 == y3 && x0 == x3 && x1 == x2)) 315 { 316 // We can use either diagonal to calculate the rectangle: 317 // (x0, y0) -> (x2, y2) 318 // (x1, y1) -> (x3, y3) 319 // We also need to deal with upside down and/or backwards rectangles 320 int x, y, w, h; 321 if (x2 < x0) { x = x2; w = x0 - x2; } 322 else { x = x0; w = x2 - x0; } 323 if (y2 < y0) { y = y2; h = y0 - y2; } 324 else { y = y0; h = y2 - y0; } 325 // Overflow protection... 326 if (w < 0) return false; 327 if (h < 0) return false; 328 329 if (retrect != null) { 330 retrect.setBounds(x, y, w, h); 331 } 332 return true; 333 } 334 return false; 335 } 336 337 void needRoom(boolean needMove, int newCoords) { 338 if (needMove && (numTypes == 0)) { 339 throw new IllegalPathStateException("missing initial moveto "+ 340 "in path definition"); 341 } 342 int size = pointTypes.length; 343 if (size == 0) { 344 pointTypes = new byte[2]; 345 } else if (numTypes >= size) { 346 pointTypes = expandPointTypes(pointTypes, 1); 347 } 348 size = floatCoords.length; 349 if (numCoords > (floatCoords.length - newCoords)) { 350 floatCoords = expandCoords(floatCoords, newCoords); 351 } 352 } 353 354 static byte[] expandPointTypes(byte[] oldPointTypes, int needed) { 355 final int oldSize = oldPointTypes.length; 356 final int newSizeMin = oldSize + needed; 357 if (newSizeMin < oldSize) { 358 // hard overflow failure - we can't even accommodate 359 // new items without overflowing 360 throw new ArrayIndexOutOfBoundsException( 361 "pointTypes exceeds maximum capacity !"); 362 } 363 // growth algorithm computation 364 int grow = oldSize; 365 if (grow > EXPAND_MAX) { 366 grow = Math.max(EXPAND_MAX, oldSize >> 3); // 1/8th min 367 } else if (grow < INIT_SIZE) { 368 grow = INIT_SIZE; // ensure > 6 (cubics) 369 } 370 assert grow > 0; 371 372 int newSize = oldSize + grow; 373 if (newSize < newSizeMin) { 374 // overflow in growth algorithm computation 375 newSize = Integer.MAX_VALUE; 376 } 377 378 while (true) { 379 try { 380 // try allocating the larger array 381 return Arrays.copyOf(oldPointTypes, newSize); 382 } catch (OutOfMemoryError oome) { 383 if (newSize == newSizeMin) { 384 throw oome; 385 } 386 } 387 newSize = newSizeMin + (newSize - newSizeMin) / 2; 388 } 389 } 390 391 static float[] expandCoords(float[] oldCoords, int needed) { 392 final int oldSize = oldCoords.length; 393 final int newSizeMin = oldSize + needed; 394 if (newSizeMin < oldSize) { 395 // hard overflow failure - we can't even accommodate 396 // new items without overflowing 397 throw new ArrayIndexOutOfBoundsException( 398 "coords exceeds maximum capacity !"); 399 } 400 // growth algorithm computation 401 int grow = oldSize; 402 if (grow > EXPAND_MAX_COORDS) { 403 grow = Math.max(EXPAND_MAX_COORDS, oldSize >> 3); // 1/8th min 404 } else if (grow < INIT_SIZE) { 405 grow = INIT_SIZE; // ensure > 6 (cubics) 406 } 407 assert grow > needed; 408 409 int newSize = oldSize + grow; 410 if (newSize < newSizeMin) { 411 // overflow in growth algorithm computation 412 newSize = Integer.MAX_VALUE; 413 } 414 while (true) { 415 try { 416 // try allocating the larger array 417 return Arrays.copyOf(oldCoords, newSize); 418 } catch (OutOfMemoryError oome) { 419 if (newSize == newSizeMin) { 420 throw oome; 421 } 422 } 423 newSize = newSizeMin + (newSize - newSizeMin) / 2; 424 } 425 } 426 427 /** 428 * Adds a point to the path by moving to the specified 429 * coordinates specified in float precision. 430 * 431 * @param x the specified X coordinate 432 * @param y the specified Y coordinate 433 */ 434 public final void moveTo(float x, float y) { 435 if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) { 436 floatCoords[numCoords-2] = moveX = prevX = currX = x; 437 floatCoords[numCoords-1] = moveY = prevY = currY = y; 438 } else { 439 needRoom(false, 2); 440 pointTypes[numTypes++] = SEG_MOVETO; 441 floatCoords[numCoords++] = moveX = prevX = currX = x; 442 floatCoords[numCoords++] = moveY = prevY = currY = y; 443 } 444 } 445 446 /** 447 * Adds a point to the path by moving to the specified coordinates 448 * relative to the current point, specified in float precision. 449 * 450 * @param relx the specified relative X coordinate 451 * @param rely the specified relative Y coordinate 452 * @see Path2D#moveTo 453 */ 454 public final void moveToRel(float relx, float rely) { 455 if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) { 456 floatCoords[numCoords-2] = moveX = prevX = (currX += relx); 457 floatCoords[numCoords-1] = moveY = prevY = (currY += rely); 458 } else { 459 needRoom(true, 2); 460 pointTypes[numTypes++] = SEG_MOVETO; 461 floatCoords[numCoords++] = moveX = prevX = (currX += relx); 462 floatCoords[numCoords++] = moveY = prevY = (currY += rely); 463 } 464 } 465 466 /** 467 * Adds a point to the path by drawing a straight line from the 468 * current coordinates to the new coordinates. 469 * 470 * @param x the specified X coordinate 471 * @param y the specified Y coordinate 472 */ 473 public final void lineTo(float x, float y) { 474 needRoom(true, 2); 475 pointTypes[numTypes++] = SEG_LINETO; 476 floatCoords[numCoords++] = prevX = currX = x; 477 floatCoords[numCoords++] = prevY = currY = y; 478 } 479 480 /** 481 * Adds a point to the path by drawing a straight line from the 482 * current coordinates to the new coordinates relative to the 483 * current point. 484 * 485 * @param relx the specified relative X coordinate 486 * @param rely the specified relative Y coordinate 487 * @see Path2D#lineTo 488 */ 489 public final void lineToRel(float relx, float rely) { 490 needRoom(true, 2); 491 pointTypes[numTypes++] = SEG_LINETO; 492 floatCoords[numCoords++] = prevX = (currX += relx); 493 floatCoords[numCoords++] = prevY = (currY += rely); 494 } 495 496 /** 497 * Adds a curved segment to the path, defined by two new points, by 498 * drawing a Quadratic curve that intersects both the current 499 * coordinates and the specified coordinates {@code (x2,y2)}, 500 * using the specified point {@code (x1,y1)} as a quadratic 501 * parametric control point. 502 * 503 * @param x1 the X coordinate of the quadratic control point 504 * @param y1 the Y coordinate of the quadratic control point 505 * @param x2 the X coordinate of the final end point 506 * @param y2 the Y coordinate of the final end point 507 */ 508 public final void quadTo(float x1, float y1, 509 float x2, float y2) 510 { 511 needRoom(true, 4); 512 pointTypes[numTypes++] = SEG_QUADTO; 513 floatCoords[numCoords++] = prevX = x1; 514 floatCoords[numCoords++] = prevY = y1; 515 floatCoords[numCoords++] = currX = x2; 516 floatCoords[numCoords++] = currY = y2; 517 } 518 519 /** 520 * Adds a curved segment to the path, defined by two new points 521 * relative to the current point, by 522 * drawing a Quadratic curve that intersects both the current 523 * coordinates and the specified relative coordinates {@code (rx2,ry2)}, 524 * using the specified relative point {@code (rx1,ry1)} as a quadratic 525 * parametric control point. 526 * This is equivalent to: 527 * <pre> 528 * quadTo(getCurrentX() + rx1, getCurrentY() + ry1, 529 * getCurrentX() + rx2, getCurrentY() + ry2); 530 * </pre> 531 * 532 * @param relx1 the relative X coordinate of the quadratic control point 533 * @param rely1 the relative Y coordinate of the quadratic control point 534 * @param relx2 the relative X coordinate of the final end point 535 * @param rely2 the relative Y coordinate of the final end point 536 * @see Path2D#quadTo 537 */ 538 public final void quadToRel(float relx1, float rely1, 539 float relx2, float rely2) 540 { 541 needRoom(true, 4); 542 pointTypes[numTypes++] = SEG_QUADTO; 543 floatCoords[numCoords++] = prevX = currX + relx1; 544 floatCoords[numCoords++] = prevY = currY + rely1; 545 floatCoords[numCoords++] = (currX += relx2); 546 floatCoords[numCoords++] = (currY += rely2); 547 } 548 549 /** 550 * Adds a curved segment to the path, defined by a new point, by 551 * drawing a Quadratic curve that intersects both the current 552 * coordinates and the specified coordinates {@code (x,y)}, 553 * using a quadratic parametric control point that is positioned 554 * symmetrically across the current point from the previous curve 555 * control point. 556 * If the previous path segment is not a curve, then the control 557 * point will be positioned at the current point. This is 558 * equivalent to: 559 * <pre> 560 * quadTo(getCurrentX() * 2 - <previousControlX>, 561 * getCurrentY() * 2 - <previousControlY>, 562 * x, y); 563 * </pre> 564 * 565 * @param x2 the X coordinate of the final end point 566 * @param y2 the Y coordinate of the final end point 567 * @see Path2D#quadTo 568 */ 569 public final void quadToSmooth(float x2, float y2) { 570 needRoom(true, 4); 571 pointTypes[numTypes++] = SEG_QUADTO; 572 floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX); 573 floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY); 574 floatCoords[numCoords++] = currX = x2; 575 floatCoords[numCoords++] = currY = y2; 576 } 577 578 /** 579 * Adds a curved segment to the path, defined by a new point 580 * relative to the current point, by 581 * drawing a Quadratic curve that intersects both the current 582 * coordinates and the specified relative coordinates {@code (x,y)}, 583 * using a quadratic parametric control point that is positioned 584 * symmetrically across the current point from the previous curve 585 * control point. 586 * If the previous path segment is not a curve, then the control 587 * point will be positioned at the current point. This is 588 * equivalent to: 589 * <pre> 590 * quadTo(getCurrentX() * 2 - <previousControlX>, 591 * getCurrentY() * 2 - <previousControlY>, 592 * getCurrentX() + x, getCurrentY() + y); 593 * </pre> 594 * 595 * @param relx2 the relative X coordinate of the final end point 596 * @param rely2 the relative Y coordinate of the final end point 597 * @see Path2D#quadTo 598 */ 599 public final void quadToSmoothRel(float relx2, float rely2) { 600 needRoom(true, 4); 601 pointTypes[numTypes++] = SEG_QUADTO; 602 floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX); 603 floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY); 604 floatCoords[numCoords++] = (currX += relx2); 605 floatCoords[numCoords++] = (currY += rely2); 606 } 607 608 /** 609 * Adds a curved segment to the path, defined by three new points, by 610 * drawing a Bézier curve that intersects both the current 611 * coordinates and the specified coordinates {@code (x3,y3)}, 612 * using the specified points {@code (x1,y1)} and {@code (x2,y2)} as 613 * Bézier control points. 614 * 615 * @param x1 the X coordinate of the first Bézier control point 616 * @param y1 the Y coordinate of the first Bézier control point 617 * @param x2 the X coordinate of the second Bézier control point 618 * @param y2 the Y coordinate of the second Bézier control point 619 * @param x3 the X coordinate of the final end point 620 * @param y3 the Y coordinate of the final end point 621 * @see Path2D#curveTo 622 */ 623 public final void curveTo(float x1, float y1, 624 float x2, float y2, 625 float x3, float y3) 626 { 627 needRoom(true, 6); 628 pointTypes[numTypes++] = SEG_CUBICTO; 629 floatCoords[numCoords++] = x1; 630 floatCoords[numCoords++] = y1; 631 floatCoords[numCoords++] = prevX = x2; 632 floatCoords[numCoords++] = prevY = y2; 633 floatCoords[numCoords++] = currX = x3; 634 floatCoords[numCoords++] = currY = y3; 635 } 636 637 /** 638 * Adds a curved segment to the path, defined by three new points 639 * relative to the current point, by 640 * drawing a Bézier curve that intersects both the current 641 * coordinates and the specified coordinates {@code (x3,y3)}, 642 * using the specified points {@code (x1,y1)} and {@code (x2,y2)} as 643 * Bézier control points. 644 * This is equivalent to: 645 * <pre> 646 * curveTo(getCurrentX() + rx1, getCurrentY() + ry1, 647 * getCurrentX() + rx2, getCurrentY() + ry2, 648 * getCurrentX() + rx3, getCurrentY() + ry3) 649 * </pre> 650 * 651 * @param relx1 the relative X coordinate of the first Bézier control point 652 * @param rely1 the relative Y coordinate of the first Bézier control point 653 * @param relx2 the relative X coordinate of the second Bézier control point 654 * @param rely2 the relative Y coordinate of the second Bézier control point 655 * @param relx3 the relative X coordinate of the final end point 656 * @param rely3 the relative Y coordinate of the final end point 657 * @see Path2D#curveTo 658 */ 659 public final void curveToRel(float relx1, float rely1, 660 float relx2, float rely2, 661 float relx3, float rely3) 662 { 663 needRoom(true, 6); 664 pointTypes[numTypes++] = SEG_CUBICTO; 665 floatCoords[numCoords++] = currX + relx1; 666 floatCoords[numCoords++] = currY + rely1; 667 floatCoords[numCoords++] = prevX = currX + relx2; 668 floatCoords[numCoords++] = prevY = currY + rely2; 669 floatCoords[numCoords++] = (currX += relx3); 670 floatCoords[numCoords++] = (currY += rely3); 671 } 672 673 /** 674 * Adds a curved segment to the path, defined by two new points and 675 * a third point inferred from the previous curve, by 676 * drawing a Bézier curve that intersects both the current 677 * coordinates and the specified coordinates {@code (x3,y3)}, 678 * using the specified point {@code (x2,y2)} as the second 679 * Bézier control point and a first Bézier control 680 * point that is positioned 681 * symmetrically across the current point from the previous curve 682 * control point. 683 * This is equivalent to: 684 * <pre> 685 * curveTo(getCurrentX() * 2.0f - <previousControlX>, 686 * getCurrentY() * 2.0f - <previousControlY>, 687 * x2, y2, x3, y3); 688 * </pre> 689 * 690 * @param x2 the X coordinate of the second Bézier control point 691 * @param y2 the Y coordinate of the second Bézier control point 692 * @param x3 the X coordinate of the final end point 693 * @param y3 the Y coordinate of the final end point 694 * @see Path2D#curveTo 695 */ 696 public final void curveToSmooth(float x2, float y2, 697 float x3, float y3) 698 { 699 needRoom(true, 6); 700 pointTypes[numTypes++] = SEG_CUBICTO; 701 floatCoords[numCoords++] = currX * 2.0f - prevX; 702 floatCoords[numCoords++] = currY * 2.0f - prevY; 703 floatCoords[numCoords++] = prevX = x2; 704 floatCoords[numCoords++] = prevY = y2; 705 floatCoords[numCoords++] = currX = x3; 706 floatCoords[numCoords++] = currY = y3; 707 } 708 709 /** 710 * Adds a curved segment to the path, defined by two new points relative 711 * to the current point and 712 * a third point inferred from the previous curve, by 713 * drawing a Bézier curve that intersects both the current 714 * coordinates and the specified relative coordinates {@code (rx3,ry3)}, 715 * using the specified relative point {@code (rx2,ry2)} as the second 716 * Bézier control point and a first Bézier control 717 * point that is positioned 718 * symmetrically across the current point from the previous curve 719 * control point. 720 * This is equivalent to: 721 * <pre> 722 * curveTo(getCurrentX() * 2.0f - <previousControlX>, 723 * getCurrentY() * 2.0f - <previousControlY>, 724 * getCurrentX() + x2, getCurrentY() + y2, 725 * getCurrentX() + x3, getCurrentY() + y3); 726 * </pre> 727 * 728 * @param relx2 the relative X coordinate of the second Bézier control point 729 * @param rely2 the relative Y coordinate of the second Bézier control point 730 * @param relx3 the relative X coordinate of the final end point 731 * @param rely3 the relative Y coordinate of the final end point 732 * @see Path2D#curveTo 733 */ 734 public final void curveToSmoothRel(float relx2, float rely2, 735 float relx3, float rely3) 736 { 737 needRoom(true, 6); 738 pointTypes[numTypes++] = SEG_CUBICTO; 739 floatCoords[numCoords++] = currX * 2.0f - prevX; 740 floatCoords[numCoords++] = currY * 2.0f - prevY; 741 floatCoords[numCoords++] = prevX = currX + relx2; 742 floatCoords[numCoords++] = prevY = currY + rely2; 743 floatCoords[numCoords++] = (currX += relx3); 744 floatCoords[numCoords++] = (currY += rely3); 745 } 746 747 /** 748 * Append a section of a quadrant of an oval to the current path, 749 * relative to the current point. 750 * See {@link appendOvalQuadrant} for a precise definition of the 751 * path segments to be added, considering that this method uses the 752 * current point of the path as the first pair of coordinates and 753 * a hard-coded prefix of {@link CornerPrefix.CORNER_ONLY CORNER_ONLY}. 754 * This method is equivalent to (and only slightly faster than): 755 * <pre> 756 * appendOvalQuadrant(getCurrentX(), getCurrentY(), 757 * cx, cy, ex, ey, tfrom, tto, 758 * CornerPrefix.CORNER_ONLY); 759 * </pre> 760 * Note that you could define a circle inscribed in the rectangular 761 * bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the 762 * following 4 calls to this method: 763 * <pre> 764 * Path2D path = new Path2D(); 765 * float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom 766 * float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right 767 * path.moveTo(cx, y0); 768 * path.ovalQuadrantTo(x1, y0, x1, cy, 0f, 1f); 769 * path.ovalQuadrantTo(x1, y1, cx, y1, 0f, 1f); 770 * path.ovalQuadrantTo(x0, y1, x0, cy, 0f, 1f); 771 * path.ovalQuadrantTo(x0, y0, cx, y0, 0f, 1f); 772 * path.closePath(); 773 * </pre> 774 * You could also define a rounded rectangle inscribed in the rectangular 775 * bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner 776 * arc radius {@code r} less than half the width and the height with the 777 * following 4 calls to this method: 778 * <pre> 779 * Path2D path = new Path2D(); 780 * float lx = x0 + r; 781 * float rx = x1 - r; 782 * float ty = y0 + r; 783 * float by = y1 - r; 784 * path.moveTo(rx, y0); 785 * path.ovalQuadrantTo(x1, y0, x1, ty, 0f, 1f); 786 * path.lineTo(x1, by); 787 * path.ovalQuadrantTo(x1, y1, rx, y1, 0f, 1f); 788 * path.lineTo(lx, y1); 789 * path.ovalQuadrantTo(x0, y1, x0, by, 0f, 1f); 790 * path.lineTo(x0, by); 791 * path.ovalQuadrantTo(x0, y0, lx, y0, 0f, 1f); 792 * path.closePath(); 793 * </pre> 794 * 795 * @param cx the X coordinate of the corner 796 * @param cy the Y coordinate of the corner 797 * @param ex the X coordinate of the midpoint of the trailing edge 798 * interpolated by the oval 799 * @param ey the Y coordinate of the midpoint of the trailing edge 800 * interpolated by the oval 801 * @param tfrom the fraction of the oval section where the curve should start 802 * @param tto the fraction of the oval section where the curve should end 803 * @throws IllegalPathStateException 804 * if there is no current point in the path 805 * @throws IllegalArgumentException 806 * if the {@code tfrom} and {@code tto} values do not satisfy the 807 * required relationship {@code (0 <= tfrom <= tto <= 1). 808 */ 809 public final void ovalQuadrantTo(float cx, float cy, 810 float ex, float ey, 811 float tfrom, float tto) 812 { 813 if (numTypes < 1) { 814 throw new IllegalPathStateException("missing initial moveto "+ 815 "in path definition"); 816 } 817 appendOvalQuadrant(currX, currY, 818 cx, cy, ex, ey, tfrom, tto, CornerPrefix.CORNER_ONLY); 819 } 820 821 /** 822 * Append a section of a quadrant of an oval to the current path. 823 * The oval from which a quadrant is taken is the oval that would be 824 * inscribed in a parallelogram defined by 3 points, 825 * {@code (sx, sy)} which is considered to be the midpoint of the edge 826 * leading into the corner of the oval where the oval grazes it, 827 * {@code (cx, cy)} which is considered to be the location of the 828 * corner of the parallelogram in which the oval is inscribed, 829 * and {@code (ex, ey)} which is considered to be the midpoint of the 830 * edge leading away from the corner of the oval where the oval grazes it. 831 * A typical case involves the two segments being equal in length and 832 * at right angles to each other in which case the oval is a quarter of 833 * a circle. 834 * <p> 835 * Only the portion of the oval from {@code tfrom} to {@code tto} 836 * will be included where {@code 0f} represents the point where the 837 * oval grazes the leading edge, {@code 1f} represents the point where 838 * the oval grazes the trailing edge, and {@code 0.5f} represents the 839 * point on the oval closest to the corner (i.e. the "45 degree" point). 840 * The two values must satisfy the relation 841 * {@code (0 <= tfrom <= tto <= 1)}. 842 * If {@code tfrom} is not {@code 0f} then the caller would most likely 843 * want to use one of the {@code prefix} values that inserts a segment 844 * leading to the initial point (see below). 845 * <p> 846 * An initial {@link moveTo} or {@link lineTo} can be added to direct 847 * the path to the starting point of the oval section if 848 * {@link CornerPrefix.MOVE_THEN_CORNER MOVE_THEN_CORNER} or 849 * {@link CornerPrefix.LINE_THEN_CORNER LINE_THEN_CORNER} are 850 * specified by the prefix argument. 851 * The {@code lineTo} path segment will only be added if the current point 852 * is not already at the indicated location to avoid spurious empty line 853 * segments. 854 * The prefix can be specified as 855 * {@link CornerPrefix.CORNER_ONLY CORNER_ONLY} if the current point 856 * on the path is known to be at the starting point of the oval section, 857 * but could otherwise produce odd results if the current point is not 858 * appropriate. 859 * <p> 860 * Note that you could define a circle inscribed in the rectangular 861 * bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the 862 * following 4 calls to this method: 863 * <pre> 864 * Path2D path = new Path2D(); 865 * float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom 866 * float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right 867 * path.appendOvalQuadrant(cx, y0, x1, y0, x1, cy, 0f, 1f, MOVE_THEN_CORNER); 868 * path.appendOvalQuadrant(x1, cy, x1, y1, cx, y1, 0f, 1f, CORNER_ONLY); 869 * path.appendOvalQuadrant(cx, y1, x0, y1, x0, cy, 0f, 1f, CORNER_ONLY); 870 * path.appendOvalQuadrant(x0, cy, x0, y0, cx, y0, 0f, 1f, CORNER_ONLY); 871 * path.closePath(); 872 * </pre> 873 * You could also define a rounded rectangle inscribed in the rectangular 874 * bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner 875 * arc radius {@code r} less than half the width and the height with the 876 * following 4 calls to this method: 877 * <pre> 878 * Path2D path = new Path2D(); 879 * float lx = x0 + r; 880 * float rx = x1 - r; 881 * float ty = y0 + r; 882 * float by = y1 - r; 883 * path.appendOvalQuadrant(rx, y0, x1, y0, x1, ty, 0f, 1f, MOVE_THEN_CORNER); 884 * path.appendOvalQuadrant(x1, by, x1, y1, rx, y1, 0f, 1f, LINE_THEN_CORNER); 885 * path.appendOvalQuadrant(lx, y1, x0, y1, x0, by, 0f, 1f, LINE_THEN_CORNER); 886 * path.appendOvalQuadrant(x0, by, x0, y0, lx, y0, 0f, 1f, LINE_THEN_CORNER); 887 * path.closePath(); 888 * </pre> 889 * 890 * @param sx the X coordinate of the midpoint of the leading edge 891 * interpolated by the oval 892 * @param sy the Y coordinate of the midpoint of the leading edge 893 * interpolated by the oval 894 * @param cx the X coordinate of the corner 895 * @param cy the Y coordinate of the corner 896 * @param ex the X coordinate of the midpoint of the trailing edge 897 * interpolated by the oval 898 * @param ey the Y coordinate of the midpoint of the trailing edge 899 * interpolated by the oval 900 * @param tfrom the fraction of the oval section where the curve should start 901 * @param tto the fraction of the oval section where the curve should end 902 * @param prefix the specification of what additional path segments should 903 * be appended to lead the current path to the starting point 904 * @throws IllegalPathStateException 905 * if there is no current point in the path and the prefix is 906 * not {@code CornerPrevix.MOVE_THEN_CORNER MOVE_THEN_CORNER}. 907 * @throws IllegalArgumentException 908 * if the {@code tfrom} and {@code tto} values do not satisfy the 909 * required relationship {@code (0 <= tfrom <= tto <= 1). 910 */ 911 public final void appendOvalQuadrant(float sx, float sy, 912 float cx, float cy, 913 float ex, float ey, 914 float tfrom, float tto, 915 CornerPrefix prefix) 916 { 917 if (!(tfrom >= 0f && tfrom <= tto && tto <= 1f)) { 918 throw new IllegalArgumentException("0 <= tfrom <= tto <= 1 required"); 919 } 920 float cx0 = (float) (sx + (cx - sx) * EllipseIterator.CtrlVal); 921 float cy0 = (float) (sy + (cy - sy) * EllipseIterator.CtrlVal); 922 float cx1 = (float) (ex + (cx - ex) * EllipseIterator.CtrlVal); 923 float cy1 = (float) (ey + (cy - ey) * EllipseIterator.CtrlVal); 924 if (tto < 1f) { 925 float t = 1f - tto; 926 ex += (cx1 - ex) * t; 927 ey += (cy1 - ey) * t; 928 cx1 += (cx0 - cx1) * t; 929 cy1 += (cy0 - cy1) * t; 930 cx0 += (sx - cx0) * t; 931 cy0 += (sy - cy0) * t; 932 ex += (cx1 - ex) * t; 933 ey += (cy1 - ey) * t; 934 cx1 += (cx0 - cx1) * t; 935 cy1 += (cy0 - cy1) * t; 936 ex += (cx1 - ex) * t; 937 ey += (cy1 - ey) * t; 938 } 939 if (tfrom > 0f) { 940 if (tto < 1f) { 941 tfrom = tfrom / tto; 942 } 943 sx += (cx0 - sx) * tfrom; 944 sy += (cy0 - sy) * tfrom; 945 cx0 += (cx1 - cx0) * tfrom; 946 cy0 += (cy1 - cy0) * tfrom; 947 cx1 += (ex - cx1) * tfrom; 948 cy1 += (ey - cy1) * tfrom; 949 sx += (cx0 - sx) * tfrom; 950 sy += (cy0 - sy) * tfrom; 951 cx0 += (cx1 - cx0) * tfrom; 952 cy0 += (cy1 - cy0) * tfrom; 953 sx += (cx0 - sx) * tfrom; 954 sy += (cy0 - sy) * tfrom; 955 } 956 if (prefix == CornerPrefix.MOVE_THEN_CORNER) { 957 // Always execute moveTo so we break the path... 958 moveTo(sx, sy); 959 } else if (prefix == CornerPrefix.LINE_THEN_CORNER) { 960 if (numTypes == 1 || 961 sx != currX || 962 sy != currY) 963 { 964 lineTo(sx, sy); 965 } 966 } 967 if (tfrom == tto || 968 (sx == cx0 && cx0 == cx1 && cx1 == ex && 969 sy == cy0 && cy0 == cy1 && cy1 == ey)) 970 { 971 if (prefix != CornerPrefix.LINE_THEN_CORNER) { 972 lineTo(ex, ey); 973 } 974 } else { 975 curveTo(cx0, cy0, cx1, cy1, ex, ey); 976 } 977 } 978 979 /** 980 * Append a portion of an ellipse to the path. 981 * The ellipse from which the portions are extracted follows the rules: 982 * <ul> 983 * <li>The ellipse will have its X axis tilted from horizontal by the 984 * angle {@code xAxisRotation} specified in radians. 985 * <li>The ellipse will have the X and Y radii (viewed from its tilted 986 * coordinate system) specified by {@code radiusx} and {@code radiusy} 987 * unless that ellipse is too small to bridge the gap from the current 988 * point to the specified destination point in which case a larger 989 * ellipse with the same ratio of dimensions will be substituted instead. 990 * <li>The ellipse may slide perpendicular to the direction from the 991 * current point to the specified destination point so that it just 992 * touches the two points. 993 * The direction it slides (to the "left" or to the "right") will be 994 * chosen to meet the criteria specified by the two boolean flags as 995 * described below. 996 * Only one direction will allow the method to meet both criteria. 997 * <li>If the {@code largeArcFlag} is true, then the ellipse will sweep 998 * the longer way around the ellipse that meets these criteria. 999 * <li>If the {@code sweepFlag} is true, then the ellipse will sweep 1000 * clockwise around the ellipse that meets these criteria. 1001 * </ul> 1002 * The method will do nothing if the destination point is the same as 1003 * the current point. 1004 * The method will draw a simple line segment to the destination point 1005 * if either of the two radii are zero. 1006 * <p> 1007 * Note: This method adheres to the definition of an elliptical arc path 1008 * segment from the SVG spec: 1009 * <pre> 1010 * http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands 1011 * </pre> 1012 * 1013 * @param radiusx the X radius of the tilted ellipse 1014 * @param radiusy the Y radius of the tilted ellipse 1015 * @param xAxisRotation the angle of tilt of the ellipse 1016 * @param largeArcFlag true iff the path will sweep the long way around 1017 * the ellipse 1018 * @param sweepFlag true iff the path will sweep clockwise around 1019 * the ellipse 1020 * @param x the destination X coordinate 1021 * @param y the destination Y coordinate 1022 * @throws IllegalPathStateException 1023 * if there is no current point in the path 1024 */ 1025 public void arcTo(float radiusx, float radiusy, float xAxisRotation, 1026 boolean largeArcFlag, boolean sweepFlag, 1027 float x, float y) 1028 { 1029 // First ensure preceding moveto 1030 if (numTypes < 1) { 1031 throw new IllegalPathStateException("missing initial moveto "+ 1032 "in path definition"); 1033 } 1034 // Reference equations are provided for implementation assistance: 1035 // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 1036 // We use the following modifications: 1037 // They use a secondary coordinate system which is based on 1038 // - translating to the midpoint between the endpoints 1039 // - rotating so that the xAxis is "horizontal" 1040 // You can see that most of their math then has their secondary 1041 // coordinates being divided by rx and ry everywhere so we scale 1042 // by 1/rx and 1/ry so that we are working on a unit circle: 1043 // [ x' ] [ +cos/rx +sin/rx ] [ x - mx ] 1044 // [ ] = [ ] * [ ] 1045 // [ y' ] [ -sin/ry +cos/ry ] [ y - my ] 1046 // and reversing back to user space coordinates: 1047 // [ x ] [ +cos -sin ] [ x' * rx ] [ mx ] 1048 // [ ] = [ ] * [ ] + [ ] 1049 // [ y ] [ +sin +cos ] [ y' * ry ] [ my ] 1050 double rx = Math.abs(radiusx); 1051 double ry = Math.abs(radiusy); 1052 if (rx == 0 || ry == 0) { 1053 lineTo(x, y); 1054 return; 1055 } 1056 double x1 = currX; 1057 double y1 = currY; 1058 double x2 = x; 1059 double y2 = y; 1060 if (x1 == x2 && y1 == y2) { 1061 return; 1062 } 1063 double cosphi, sinphi; 1064 if (xAxisRotation == 0.0) { 1065 cosphi = 1.0; 1066 sinphi = 0.0; 1067 } else { 1068 cosphi = Math.cos(xAxisRotation); 1069 sinphi = Math.sin(xAxisRotation); 1070 } 1071 double mx = (x1 + x2) / 2.0; 1072 double my = (y1 + y2) / 2.0; 1073 double relx1 = x1 - mx; 1074 double rely1 = y1 - my; 1075 double x1p = (cosphi * relx1 + sinphi * rely1) / rx; 1076 double y1p = (cosphi * rely1 - sinphi * relx1) / ry; 1077 // The documentation for the SVG arc operator recommends computing 1078 // a "scale" value and then scaling the radii appropriately if the 1079 // scale is greater than 1. Technically, they are computing the 1080 // ratio of the distance to the endpoints compared to the distance 1081 // across the indicated section of the ellipse centered at the midpoint. 1082 // If the ratio is greater than 1 then the endpoints are outside the 1083 // ellipse and so the ellipse is not large enough to bridge the gap 1084 // without growing. If they are inside, then we slide the ellipse 1085 // in the appropriate direction as specified by the 2 flags so that 1086 // the transformed relative points are on the edge. If they 1087 // are outside, then we note that we simply have a (distorted) half 1088 // circle to render in that case since the endpoints will be on 1089 // opposite sides of a stretched version of the unmoved ellipse. 1090 double lenpsq = x1p * x1p + y1p * y1p; 1091 if (lenpsq >= 1.0) { 1092 // Unlike the reference equations, we do not need to scale the 1093 // radii here since we will work directly from the transformed 1094 // relative vectors which already have the proper distance from 1095 // the midpoint. (They are already on the "stretched" ellipse.) 1096 1097 // Produce 2 quadrant circles from: 1098 // x1p,y1p => xqp,yqp => x2p,y2p 1099 // where x2p,y2p = -x1p,-y1p 1100 // and xqp,yqp = either y1p,-x1p or -y1p,x1p depending on sweepFlag 1101 // the corners of the quadrants are at: 1102 // x1p+xqp,y1p+yqp and x2p+xqp,y2p+yqp 1103 // or consequently at: 1104 // x1+(xq-mx),y1+(yq-my) and x2+(xq-mx),y2+(yq-my) 1105 double xqpr = y1p * rx; 1106 double yqpr = x1p * ry; 1107 if (sweepFlag) { xqpr = -xqpr; } else { yqpr = -yqpr; } 1108 double relxq = cosphi * xqpr - sinphi * yqpr; 1109 double relyq = cosphi * yqpr + sinphi * xqpr; 1110 double xq = mx + relxq; 1111 double yq = my + relyq; 1112 double xc = x1 + relxq; 1113 double yc = y1 + relyq; 1114 appendOvalQuadrant((float) x1, (float) y1, 1115 (float) xc, (float) yc, 1116 (float) xq, (float) yq, 1117 0f, 1f, CornerPrefix.CORNER_ONLY); 1118 xc = x2 + relxq; 1119 yc = y2 + relyq; 1120 appendOvalQuadrant((float) xq, (float) yq, 1121 (float) xc, (float) yc, 1122 (float) x2, (float) y2, 1123 0f, 1f, CornerPrefix.CORNER_ONLY); 1124 return; 1125 } 1126 // We now need to displace the circle perpendicularly to the line 1127 // between the end points so that the new center is at a unit distance 1128 // to either end point. One component of the new distance will be 1129 // the distance from the midpoint to either end point (the square 1130 // of which is already computed in "den" above). The other component 1131 // of the new distances will be how far we displace the center: 1132 // lenpsq + displen^2 = 1.0 1133 // displen^2 = 1.0 - lenpsq 1134 // displen = sqrt(1 - lenpsq) 1135 // The vector we displace along is the perpendicular of the x1p,y1p 1136 // vector whose length is sqrt(lenpsq) so we need to divide that vector 1137 // by that length to turn it into a unit vector: 1138 // cxp = +/-y1p / sqrt(lenpsq) * displen 1139 // cyp = +/-x1p / sqrt(lenpsq) * displen 1140 // To simplify, we combine the "/sqrt(lenpsq)" factor into displen to 1141 // share the one sqrt() calculation: 1142 // scalef = displen / sqrt(lenpsq) = sqrt((1-lenpsq)/lenpsq) 1143 double scalef = Math.sqrt((1.0 - lenpsq) / lenpsq); 1144 // cxp,cyp is displaced perpendicularly to the relative vector x1p,y1p 1145 // by the scalef value. The perpendicular is either -y1p,x1p or 1146 // y1p,-x1p depending on the values of the flags. 1147 double cxp = scalef * y1p; 1148 double cyp = scalef * x1p; 1149 // The direction of the perpendicular (which component is negated) 1150 // depends on both flags. 1151 if (largeArcFlag == sweepFlag) { cxp = -cxp; } else { cyp = -cyp; } 1152 mx += (cosphi * cxp * rx - sinphi * cyp * ry); 1153 my += (cosphi * cyp * ry + sinphi * cxp * rx); 1154 // Now we sweep by quadrants in the direction specified until we 1155 // reach the angle to the destination point and possibly perform 1156 // one last partial-quadrant arc segment. 1157 // First we need to reexpress our vectors relative to the new center. 1158 double ux = x1p - cxp; 1159 double uy = y1p - cyp; 1160 // x2p = -x1p; x2p-cxp = -x1p-cxp = -(x1p+cxp) 1161 // y2p = -y1p; y2p-cyp = -y1p-cyp = -(y1p+cyp) 1162 double vx = -(x1p + cxp); 1163 double vy = -(y1p + cyp); 1164 // px and py are the factors that produce the perpendicular for ux,uy 1165 // in the direction specified by sweepFlag. 1166 boolean done = false; // set to true when we detect "last quadrant" 1167 float quadlen = 1.0f; // 1.0 yields a full 90 degree arc at a time 1168 boolean wasclose = false; // overshoot prevention 1169 do { 1170 // Compute the next circle quadrant endpoint, cw or ccw 1171 double xqp = uy; 1172 double yqp = ux; 1173 if (sweepFlag) { xqp = -xqp; } else { yqp = -yqp; } 1174 // qp.v > 0 tells us if sweep towards v is < 180 1175 if (xqp * vx + yqp * vy > 0) { 1176 // u.v >= 0 now tells us if sweep towards v is <= 90 1177 // (It is also true for >270, but we already checked for <180) 1178 double dot = ux * vx + uy * vy; 1179 if (dot >= 0) { 1180 // u.v is the cosine of the angle we have left since both 1181 // u and v are unit vectors. We now need to express how 1182 // much we want to shorten this last arc segment in terms 1183 // of 0.0=>1.0 meaning 0=>90 degrees. 1184 quadlen = (float) (Math.acos(dot) / (Math.PI / 2.0)); 1185 done = true; 1186 } 1187 // Remember that we were once within 180 degrees so we 1188 // do not accidentally overshoot due to fp rounding error. 1189 wasclose = true; 1190 } else if (wasclose) { 1191 // At some point we were in the <180 case above, but now we 1192 // are back at the >180 case having never gone into the <90 1193 // case where done would have been set to true. This should 1194 // not happen, but since we are computing the perpendiculars 1195 // and then expecting that they will have predictable results 1196 // in the dot product equations, there is a theoretical chance 1197 // of a tiny round-off error that would cause us to overshoot 1198 // from just barely >90 left to suddenly past the 0 point. 1199 // If that ever happens, we will end up in here and we can just 1200 // break out of the loop since that last quadrant we rendered 1201 // should have landed us right on top of the vx,vy location. 1202 break; 1203 } 1204 double relxq = (cosphi * xqp * rx - sinphi * yqp * ry); 1205 double relyq = (cosphi * yqp * ry + sinphi * xqp * rx); 1206 double xq = mx + relxq; 1207 double yq = my + relyq; 1208 double xc = x1 + relxq; 1209 double yc = y1 + relyq; 1210 appendOvalQuadrant((float) x1, (float) y1, 1211 (float) xc, (float) yc, 1212 (float) xq, (float) yq, 1213 0f, quadlen, CornerPrefix.CORNER_ONLY); 1214 x1 = xq; 1215 y1 = yq; 1216 ux = xqp; 1217 uy = yqp; 1218 } while (!done); 1219 } 1220 1221 /** 1222 * Append a portion of an ellipse to the path using relative coordinates. 1223 * This method is identical to calling: 1224 * <pre> 1225 * arcTo(radiusX, radiusY, xAxisRotation, 1226 * largeArcFlag, sweepFlag, 1227 * getCurrentX() + rx, getCurrentY() + ry); 1228 * </pre> 1229 * 1230 * @param radiusx the X radius of the tilted ellipse 1231 * @param radiusy the Y radius of the tilted ellipse 1232 * @param xAxisRotation the angle of tilt of the ellipse 1233 * @param largeArcFlag true iff the path will sweep the long way around 1234 * the ellipse 1235 * @param sweepFlag true iff the path will sweep clockwise around 1236 * the ellipse 1237 * @param relx the relative destination relative X coordinate 1238 * @param rely the relative destination relative Y coordinate 1239 * @throws IllegalPathStateException 1240 * if there is no current point in the path 1241 * @see Path2D#arcTo 1242 */ 1243 public void arcToRel(float radiusx, float radiusy, float xAxisRotation, 1244 boolean largeArcFlag, boolean sweepFlag, 1245 float relx, float rely) 1246 { 1247 arcTo(radiusx, radiusy, xAxisRotation, 1248 largeArcFlag, sweepFlag, 1249 currX + relx, currY + rely); 1250 } 1251 1252 int pointCrossings(float px, float py) { 1253 float movx, movy, curx, cury, endx, endy; 1254 float coords[] = floatCoords; 1255 curx = movx = coords[0]; 1256 cury = movy = coords[1]; 1257 int crossings = 0; 1258 int ci = 2; 1259 for (int i = 1; i < numTypes; i++) { 1260 switch (pointTypes[i]) { 1261 case PathIterator.SEG_MOVETO: 1262 if (cury != movy) { 1263 crossings += 1264 Shape.pointCrossingsForLine(px, py, 1265 curx, cury, 1266 movx, movy); 1267 } 1268 movx = curx = coords[ci++]; 1269 movy = cury = coords[ci++]; 1270 break; 1271 case PathIterator.SEG_LINETO: 1272 crossings += 1273 Shape.pointCrossingsForLine(px, py, 1274 curx, cury, 1275 endx = coords[ci++], 1276 endy = coords[ci++]); 1277 curx = endx; 1278 cury = endy; 1279 break; 1280 case PathIterator.SEG_QUADTO: 1281 crossings += 1282 Shape.pointCrossingsForQuad(px, py, 1283 curx, cury, 1284 coords[ci++], 1285 coords[ci++], 1286 endx = coords[ci++], 1287 endy = coords[ci++], 1288 0); 1289 curx = endx; 1290 cury = endy; 1291 break; 1292 case PathIterator.SEG_CUBICTO: 1293 crossings += 1294 Shape.pointCrossingsForCubic(px, py, 1295 curx, cury, 1296 coords[ci++], 1297 coords[ci++], 1298 coords[ci++], 1299 coords[ci++], 1300 endx = coords[ci++], 1301 endy = coords[ci++], 1302 0); 1303 curx = endx; 1304 cury = endy; 1305 break; 1306 case PathIterator.SEG_CLOSE: 1307 if (cury != movy) { 1308 crossings += 1309 Shape.pointCrossingsForLine(px, py, 1310 curx, cury, 1311 movx, movy); 1312 } 1313 curx = movx; 1314 cury = movy; 1315 break; 1316 } 1317 } 1318 if (cury != movy) { 1319 crossings += 1320 Shape.pointCrossingsForLine(px, py, 1321 curx, cury, 1322 movx, movy); 1323 } 1324 return crossings; 1325 } 1326 1327 int rectCrossings(float rxmin, float rymin, 1328 float rxmax, float rymax) 1329 { 1330 float coords[] = floatCoords; 1331 float curx, cury, movx, movy, endx, endy; 1332 curx = movx = coords[0]; 1333 cury = movy = coords[1]; 1334 int crossings = 0; 1335 int ci = 2; 1336 for (int i = 1; 1337 crossings != Shape.RECT_INTERSECTS && i < numTypes; 1338 i++) 1339 { 1340 switch (pointTypes[i]) { 1341 case PathIterator.SEG_MOVETO: 1342 if (curx != movx || cury != movy) { 1343 crossings = 1344 Shape.rectCrossingsForLine(crossings, 1345 rxmin, rymin, 1346 rxmax, rymax, 1347 curx, cury, 1348 movx, movy); 1349 } 1350 // Count should always be a multiple of 2 here. 1351 // assert((crossings & 1) != 0); 1352 movx = curx = coords[ci++]; 1353 movy = cury = coords[ci++]; 1354 break; 1355 case PathIterator.SEG_LINETO: 1356 crossings = 1357 Shape.rectCrossingsForLine(crossings, 1358 rxmin, rymin, 1359 rxmax, rymax, 1360 curx, cury, 1361 endx = coords[ci++], 1362 endy = coords[ci++]); 1363 curx = endx; 1364 cury = endy; 1365 break; 1366 case PathIterator.SEG_QUADTO: 1367 crossings = 1368 Shape.rectCrossingsForQuad(crossings, 1369 rxmin, rymin, 1370 rxmax, rymax, 1371 curx, cury, 1372 coords[ci++], 1373 coords[ci++], 1374 endx = coords[ci++], 1375 endy = coords[ci++], 1376 0); 1377 curx = endx; 1378 cury = endy; 1379 break; 1380 case PathIterator.SEG_CUBICTO: 1381 crossings = 1382 Shape.rectCrossingsForCubic(crossings, 1383 rxmin, rymin, 1384 rxmax, rymax, 1385 curx, cury, 1386 coords[ci++], 1387 coords[ci++], 1388 coords[ci++], 1389 coords[ci++], 1390 endx = coords[ci++], 1391 endy = coords[ci++], 1392 0); 1393 curx = endx; 1394 cury = endy; 1395 break; 1396 case PathIterator.SEG_CLOSE: 1397 if (curx != movx || cury != movy) { 1398 crossings = 1399 Shape.rectCrossingsForLine(crossings, 1400 rxmin, rymin, 1401 rxmax, rymax, 1402 curx, cury, 1403 movx, movy); 1404 } 1405 curx = movx; 1406 cury = movy; 1407 // Count should always be a multiple of 2 here. 1408 // assert((crossings & 1) != 0); 1409 break; 1410 } 1411 } 1412 if (crossings != Shape.RECT_INTERSECTS && 1413 (curx != movx || cury != movy)) 1414 { 1415 crossings = 1416 Shape.rectCrossingsForLine(crossings, 1417 rxmin, rymin, 1418 rxmax, rymax, 1419 curx, cury, 1420 movx, movy); 1421 } 1422 // Count should always be a multiple of 2 here. 1423 // assert((crossings & 1) != 0); 1424 return crossings; 1425 } 1426 1427 /** 1428 * {@inheritDoc} 1429 */ 1430 public final void append(PathIterator pi, boolean connect) { 1431 float coords[] = new float[6]; 1432 while (!pi.isDone()) { 1433 switch (pi.currentSegment(coords)) { 1434 case SEG_MOVETO: 1435 if (!connect || numTypes < 1 || numCoords < 1) { 1436 moveTo(coords[0], coords[1]); 1437 break; 1438 } 1439 if (pointTypes[numTypes - 1] != SEG_CLOSE && 1440 floatCoords[numCoords-2] == coords[0] && 1441 floatCoords[numCoords-1] == coords[1]) 1442 { 1443 // Collapse out initial moveto/lineto 1444 break; 1445 } 1446 // NO BREAK; 1447 case SEG_LINETO: 1448 lineTo(coords[0], coords[1]); 1449 break; 1450 case SEG_QUADTO: 1451 quadTo(coords[0], coords[1], 1452 coords[2], coords[3]); 1453 break; 1454 case SEG_CUBICTO: 1455 curveTo(coords[0], coords[1], 1456 coords[2], coords[3], 1457 coords[4], coords[5]); 1458 break; 1459 case SEG_CLOSE: 1460 closePath(); 1461 break; 1462 } 1463 pi.next(); 1464 connect = false; 1465 } 1466 } 1467 1468 /** 1469 * {@inheritDoc} 1470 */ 1471 public final void transform(BaseTransform tx) { 1472 if (numCoords == 0) return; 1473 needRoom(false, 6); 1474 floatCoords[numCoords + 0] = moveX; 1475 floatCoords[numCoords + 1] = moveY; 1476 floatCoords[numCoords + 2] = prevX; 1477 floatCoords[numCoords + 3] = prevY; 1478 floatCoords[numCoords + 4] = currX; 1479 floatCoords[numCoords + 5] = currY; 1480 tx.transform(floatCoords, 0, floatCoords, 0, numCoords / 2 + 3); 1481 moveX = floatCoords[numCoords + 0]; 1482 moveY = floatCoords[numCoords + 1]; 1483 prevX = floatCoords[numCoords + 2]; 1484 prevY = floatCoords[numCoords + 3]; 1485 currX = floatCoords[numCoords + 4]; 1486 currY = floatCoords[numCoords + 5]; 1487 } 1488 1489 /** 1490 * {@inheritDoc} 1491 */ 1492 public final RectBounds getBounds() { 1493 float x1, y1, x2, y2; 1494 int i = numCoords; 1495 if (i > 0) { 1496 y1 = y2 = floatCoords[--i]; 1497 x1 = x2 = floatCoords[--i]; 1498 while (i > 0) { 1499 float y = floatCoords[--i]; 1500 float x = floatCoords[--i]; 1501 if (x < x1) x1 = x; 1502 if (y < y1) y1 = y; 1503 if (x > x2) x2 = x; 1504 if (y > y2) y2 = y; 1505 } 1506 } else { 1507 x1 = y1 = x2 = y2 = 0.0f; 1508 } 1509 return new RectBounds(x1, y1, x2, y2); 1510 } 1511 1512 // The following three methods are used only by Prism to access 1513 // internal structures; not intended for general use! 1514 public final int getNumCommands() { 1515 return numTypes; 1516 } 1517 public final byte[] getCommandsNoClone() { 1518 return pointTypes; 1519 } 1520 public final float[] getFloatCoordsNoClone() { 1521 return floatCoords; 1522 } 1523 1524 /** 1525 * {@inheritDoc} 1526 * <p> 1527 * The iterator for this class is not multi-threaded safe, 1528 * which means that the {@code Path2D} class does not 1529 * guarantee that modifications to the geometry of this 1530 * {@code Path2D} object do not affect any iterations of 1531 * that geometry that are already in process. 1532 */ 1533 public PathIterator getPathIterator(BaseTransform tx) { 1534 if (tx == null) { 1535 return new CopyIterator(this); 1536 } else { 1537 return new TxIterator(this, tx); 1538 } 1539 } 1540 1541 static class CopyIterator extends Path2D.Iterator { 1542 float floatCoords[]; 1543 1544 CopyIterator(Path2D p2df) { 1545 super(p2df); 1546 this.floatCoords = p2df.floatCoords; 1547 } 1548 1549 public int currentSegment(float[] coords) { 1550 int type = path.pointTypes[typeIdx]; 1551 int numCoords = curvecoords[type]; 1552 if (numCoords > 0) { 1553 System.arraycopy(floatCoords, pointIdx, 1554 coords, 0, numCoords); 1555 } 1556 return type; 1557 } 1558 1559 public int currentSegment(double[] coords) { 1560 int type = path.pointTypes[typeIdx]; 1561 int numCoords = curvecoords[type]; 1562 if (numCoords > 0) { 1563 for (int i = 0; i < numCoords; i++) { 1564 coords[i] = floatCoords[pointIdx + i]; 1565 } 1566 } 1567 return type; 1568 } 1569 } 1570 1571 static class TxIterator extends Path2D.Iterator { 1572 float floatCoords[]; 1573 BaseTransform transform; 1574 1575 TxIterator(Path2D p2df, BaseTransform tx) { 1576 super(p2df); 1577 this.floatCoords = p2df.floatCoords; 1578 this.transform = tx; 1579 } 1580 1581 public int currentSegment(float[] coords) { 1582 int type = path.pointTypes[typeIdx]; 1583 int numCoords = curvecoords[type]; 1584 if (numCoords > 0) { 1585 transform.transform(floatCoords, pointIdx, 1586 coords, 0, numCoords / 2); 1587 } 1588 return type; 1589 } 1590 1591 public int currentSegment(double[] coords) { 1592 int type = path.pointTypes[typeIdx]; 1593 int numCoords = curvecoords[type]; 1594 if (numCoords > 0) { 1595 transform.transform(floatCoords, pointIdx, 1596 coords, 0, numCoords / 2); 1597 } 1598 return type; 1599 } 1600 } 1601 1602 /** 1603 * Closes the current subpath by drawing a straight line back to 1604 * the coordinates of the last {@code moveTo}. If the path is already 1605 * closed then this method has no effect. 1606 */ 1607 public final void closePath() { 1608 if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) { 1609 needRoom(true, 0); 1610 pointTypes[numTypes++] = SEG_CLOSE; 1611 prevX = currX = moveX; 1612 prevY = currY = moveY; 1613 } 1614 } 1615 1616 public void pathDone() { 1617 } 1618 1619 /** 1620 * Appends the geometry of the specified {@code Shape} object to the 1621 * path, possibly connecting the new geometry to the existing path 1622 * segments with a line segment. 1623 * If the {@code connect} parameter is {@code true} and the 1624 * path is not empty then any initial {@code moveTo} in the 1625 * geometry of the appended {@code Shape} 1626 * is turned into a {@code lineTo} segment. 1627 * If the destination coordinates of such a connecting {@code lineTo} 1628 * segment match the ending coordinates of a currently open 1629 * subpath then the segment is omitted as superfluous. 1630 * The winding rule of the specified {@code Shape} is ignored 1631 * and the appended geometry is governed by the winding 1632 * rule specified for this path. 1633 * 1634 * @param s the {@code Shape} whose geometry is appended 1635 * to this path 1636 * @param connect a boolean to control whether or not to turn an initial 1637 * {@code moveTo} segment into a {@code lineTo} segment 1638 * to connect the new geometry to the existing path 1639 */ 1640 public final void append(Shape s, boolean connect) { 1641 append(s.getPathIterator(null), connect); 1642 } 1643 1644 static class SVGParser { 1645 final String svgpath; 1646 final int len; 1647 int pos; 1648 boolean allowcomma; 1649 1650 public SVGParser(String svgpath) { 1651 this.svgpath = svgpath; 1652 this.len = svgpath.length(); 1653 } 1654 1655 public boolean isDone() { 1656 return (toNextNonWsp() >= len); 1657 } 1658 1659 public char getChar() { 1660 return svgpath.charAt(pos++); 1661 } 1662 1663 public boolean nextIsNumber() { 1664 if (toNextNonWsp() < len) { 1665 switch (svgpath.charAt(pos)) { 1666 case '-': 1667 case '+': 1668 case '0': case '1': case '2': case '3': case '4': 1669 case '5': case '6': case '7': case '8': case '9': 1670 case '.': 1671 return true; 1672 } 1673 } 1674 return false; 1675 } 1676 1677 public float f() { 1678 return getFloat(); 1679 } 1680 1681 public float a() { 1682 return (float) Math.toRadians(getFloat()); 1683 } 1684 1685 public float getFloat() { 1686 int start = toNextNonWsp(); 1687 this.allowcomma = true; 1688 int end = toNumberEnd(); 1689 if (start < end) { 1690 String flstr = svgpath.substring(start, end); 1691 try { 1692 return Float.parseFloat(flstr); 1693 } catch (NumberFormatException e) { 1694 } 1695 throw new IllegalArgumentException("invalid float ("+flstr+ 1696 ") in path at pos="+start); 1697 } 1698 throw new IllegalArgumentException("end of path looking for float"); 1699 } 1700 1701 public boolean b() { 1702 toNextNonWsp(); 1703 this.allowcomma = true; 1704 if (pos < len) { 1705 char flag = svgpath.charAt(pos); 1706 switch (flag) { 1707 case '0': pos++; return false; 1708 case '1': pos++; return true; 1709 } 1710 throw new IllegalArgumentException("invalid boolean flag ("+flag+ 1711 ") in path at pos="+pos); 1712 } 1713 throw new IllegalArgumentException("end of path looking for boolean"); 1714 } 1715 1716 private int toNextNonWsp() { 1717 boolean canbecomma = this.allowcomma; 1718 while (pos < len) { 1719 switch (svgpath.charAt(pos)) { 1720 case ',': 1721 if (!canbecomma) { 1722 return pos; 1723 } 1724 canbecomma = false; 1725 break; 1726 case ' ': 1727 case '\t': 1728 case '\r': 1729 case '\n': 1730 break; 1731 default: 1732 return pos; 1733 } 1734 pos++; 1735 } 1736 return pos; 1737 } 1738 1739 private int toNumberEnd() { 1740 boolean allowsign = true; 1741 boolean hasexp = false; 1742 boolean hasdecimal = false; 1743 while (pos < len) { 1744 switch (svgpath.charAt(pos)) { 1745 case '-': 1746 case '+': 1747 if (!allowsign) return pos; 1748 allowsign = false; 1749 break; 1750 case '0': case '1': case '2': case '3': case '4': 1751 case '5': case '6': case '7': case '8': case '9': 1752 allowsign = false; 1753 break; 1754 case 'E': case 'e': 1755 if (hasexp) return pos; 1756 hasexp = allowsign = true; 1757 break; 1758 case '.': 1759 if (hasexp || hasdecimal) return pos; 1760 hasdecimal = true; 1761 allowsign = false; 1762 break; 1763 default: 1764 return pos; 1765 } 1766 pos++; 1767 } 1768 return pos; 1769 } 1770 } 1771 1772 /** 1773 * Appends the geometry of the path in the specified {@code String} 1774 * argument in the format of an SVG path. 1775 * The specification of the grammar of the language for an SVG path 1776 * is specified on the W3C web page: 1777 * <pre> 1778 * http://www.w3.org/TR/SVG/paths.html#PathDataBNF 1779 * </pre> 1780 * and the interpretation of the various elements in the format is 1781 * specified on the W3C web page: 1782 * <pre> 1783 * http://www.w3.org/TR/SVG/paths.html#PathData 1784 * </pre> 1785 * 1786 * @param svgpath the {@code String} object containing the SVG style 1787 * definition of the geometry to be apppended 1788 * @throws IllegalArgumentException 1789 * if {@code svgpath} does not match the indicated SVG path grammar 1790 * @throws IllegalPathStateException 1791 * if there is no current point in the path 1792 */ 1793 public final void appendSVGPath(String svgpath) { 1794 SVGParser p = new SVGParser(svgpath); 1795 p.allowcomma = false; 1796 while (!p.isDone()) { 1797 p.allowcomma = false; 1798 char cmd = p.getChar(); 1799 switch (cmd) { 1800 case 'M': 1801 moveTo(p.f(), p.f()); 1802 while (p.nextIsNumber()) { 1803 lineTo(p.f(), p.f()); 1804 } 1805 break; 1806 case 'm': 1807 if (numTypes > 0) { 1808 moveToRel(p.f(), p.f()); 1809 } else { 1810 moveTo(p.f(), p.f()); 1811 } 1812 while (p.nextIsNumber()) { 1813 lineToRel(p.f(), p.f()); 1814 } 1815 break; 1816 case 'L': 1817 do { 1818 lineTo(p.f(), p.f()); 1819 } while (p.nextIsNumber()); 1820 break; 1821 case 'l': 1822 do { 1823 lineToRel(p.f(), p.f()); 1824 } while (p.nextIsNumber()); 1825 break; 1826 case 'H': 1827 do { 1828 lineTo(p.f(), currY); 1829 } while (p.nextIsNumber()); 1830 break; 1831 case 'h': 1832 do { 1833 lineToRel(p.f(), 0); 1834 } while (p.nextIsNumber()); 1835 break; 1836 case 'V': 1837 do { 1838 lineTo(currX, p.f()); 1839 } while (p.nextIsNumber()); 1840 break; 1841 case 'v': 1842 do { 1843 lineToRel(0, p.f()); 1844 } while (p.nextIsNumber()); 1845 break; 1846 case 'Q': 1847 do { 1848 quadTo(p.f(), p.f(), p.f(), p.f()); 1849 } while (p.nextIsNumber()); 1850 break; 1851 case 'q': 1852 do { 1853 quadToRel(p.f(), p.f(), p.f(), p.f()); 1854 } while (p.nextIsNumber()); 1855 break; 1856 case 'T': 1857 do { 1858 quadToSmooth(p.f(), p.f()); 1859 } while (p.nextIsNumber()); 1860 break; 1861 case 't': 1862 do { 1863 quadToSmoothRel(p.f(), p.f()); 1864 } while (p.nextIsNumber()); 1865 break; 1866 case 'C': 1867 do { 1868 curveTo(p.f(), p.f(), p.f(), p.f(), p.f(), p.f()); 1869 } while (p.nextIsNumber()); 1870 break; 1871 case 'c': 1872 do { 1873 curveToRel(p.f(), p.f(), p.f(), p.f(), p.f(), p.f()); 1874 } while (p.nextIsNumber()); 1875 break; 1876 case 'S': 1877 do { 1878 curveToSmooth(p.f(), p.f(), p.f(), p.f()); 1879 } while (p.nextIsNumber()); 1880 break; 1881 case 's': 1882 do { 1883 curveToSmoothRel(p.f(), p.f(), p.f(), p.f()); 1884 } while (p.nextIsNumber()); 1885 break; 1886 case 'A': 1887 do { 1888 arcTo(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f()); 1889 } while (p.nextIsNumber()); 1890 break; 1891 case 'a': 1892 do { 1893 arcToRel(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f()); 1894 } while (p.nextIsNumber()); 1895 break; 1896 case 'Z': case 'z': closePath(); break; 1897 default: 1898 throw new IllegalArgumentException("invalid command ("+cmd+ 1899 ") in SVG path at pos="+p.pos); 1900 } 1901 p.allowcomma = false; 1902 } 1903 } 1904 1905 /** 1906 * Returns the fill style winding rule. 1907 * 1908 * @return an integer representing the current winding rule. 1909 * @see #WIND_EVEN_ODD 1910 * @see #WIND_NON_ZERO 1911 * @see #setWindingRule 1912 */ 1913 public final int getWindingRule() { 1914 return windingRule; 1915 } 1916 1917 /** 1918 * Sets the winding rule for this path to the specified value. 1919 * 1920 * @param rule an integer representing the specified 1921 * winding rule 1922 * @exception IllegalArgumentException if 1923 * {@code rule} is not either 1924 * {@link #WIND_EVEN_ODD} or 1925 * {@link #WIND_NON_ZERO} 1926 * @see #getWindingRule 1927 */ 1928 public final void setWindingRule(int rule) { 1929 if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { 1930 throw new IllegalArgumentException("winding rule must be "+ 1931 "WIND_EVEN_ODD or "+ 1932 "WIND_NON_ZERO"); 1933 } 1934 windingRule = rule; 1935 } 1936 1937 /** 1938 * Returns the coordinates most recently added to the end of the path 1939 * as a {@link Point2D} object. 1940 * 1941 * @return a {@code Point2D} object containing the ending coordinates of 1942 * the path or {@code null} if there are no points in the path. 1943 */ 1944 public final Point2D getCurrentPoint() { 1945 if (numTypes < 1) { 1946 return null; 1947 } 1948 return new Point2D(currX, currY); 1949 } 1950 1951 public final float getCurrentX() { 1952 if (numTypes < 1) { 1953 throw new IllegalPathStateException("no current point in empty path"); 1954 } 1955 return currX; 1956 } 1957 1958 public final float getCurrentY() { 1959 if (numTypes < 1) { 1960 throw new IllegalPathStateException("no current point in empty path"); 1961 } 1962 return currY; 1963 } 1964 1965 /** 1966 * Resets the path to empty. The append position is set back to the 1967 * beginning of the path and all coordinates and point types are 1968 * forgotten. 1969 */ 1970 public final void reset() { 1971 numTypes = numCoords = 0; 1972 moveX = moveY = prevX = prevY = currX = currY = 0; 1973 } 1974 1975 /** 1976 * Returns a new {@code Shape} representing a transformed version 1977 * of this {@code Path2D}. 1978 * Note that the exact type and coordinate precision of the return 1979 * value is not specified for this method. 1980 * The method will return a Shape that contains no less precision 1981 * for the transformed geometry than this {@code Path2D} currently 1982 * maintains, but it may contain no more precision either. 1983 * If the tradeoff of precision vs. storage size in the result is 1984 * important then the convenience constructors in the 1985 * {@link Path2D(Shape, BaseTransform) Path2D} 1986 * 1987 * @param tx the {@code BaseTransform} used to transform a 1988 * new {@code Shape}. 1989 * @return a new {@code Shape}, transformed with the specified 1990 * {@code BaseTransform}. 1991 */ 1992 public final Shape createTransformedShape(BaseTransform tx) { 1993 return new Path2D(this, tx); 1994 } 1995 1996 @Override 1997 public Path2D copy() { 1998 return new Path2D(this); 1999 } 2000 2001 /** 2002 * {@inheritDoc} 2003 * 2004 * Note that this method may return false when the geometry of the 2005 * given {@code Path2D} is identical to the geometry of this object 2006 * but is expressed in a different way. This method will only return 2007 * true when the internal representation of this object is exactly the 2008 * same as that of the given object. 2009 */ 2010 @Override 2011 public boolean equals(Object obj) { 2012 if (obj == this) { 2013 return true; 2014 } 2015 if (obj instanceof Path2D) { 2016 Path2D p = (Path2D)obj; 2017 if (p.numTypes == this.numTypes && 2018 p.numCoords == this.numCoords && 2019 p.windingRule == this.windingRule) 2020 { 2021 for (int i = 0; i < numTypes; i++) { 2022 if (p.pointTypes[i] != this.pointTypes[i]) { 2023 return false; 2024 } 2025 } 2026 for (int i = 0; i < numCoords; i++) { 2027 if (p.floatCoords[i] != this.floatCoords[i]) { 2028 return false; 2029 } 2030 } 2031 return true; 2032 } 2033 } 2034 return false; 2035 } 2036 2037 @Override 2038 public int hashCode() { 2039 int hash = 7; 2040 hash = 11 * hash + numTypes; 2041 hash = 11 * hash + numCoords; 2042 hash = 11 * hash + windingRule; 2043 for (int i = 0; i < numTypes; i++) { 2044 hash = 11 * hash + pointTypes[i]; 2045 } 2046 for (int i = 0; i < numCoords; i++) { 2047 hash = 11 * hash + Float.floatToIntBits(floatCoords[i]); 2048 } 2049 return hash; 2050 } 2051 2052 /** 2053 * Tests if the specified coordinates are inside the closed 2054 * boundary of the specified {@link PathIterator}. 2055 * <p> 2056 * This method provides a basic facility for implementors of 2057 * the {@link Shape} interface to implement support for the 2058 * {@link Shape#contains(double, double)} method. 2059 * 2060 * @param pi the specified {@code PathIterator} 2061 * @param x the specified X coordinate 2062 * @param y the specified Y coordinate 2063 * @return {@code true} if the specified coordinates are inside the 2064 * specified {@code PathIterator}; {@code false} otherwise 2065 */ 2066 public static boolean contains(PathIterator pi, float x, float y) { 2067 if (x * 0f + y * 0f == 0f) { 2068 /* N * 0.0 is 0.0 only if N is finite. 2069 * Here we know that both x and y are finite. 2070 */ 2071 int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 1); 2072 int cross = Shape.pointCrossingsForPath(pi, x, y); 2073 return ((cross & mask) != 0); 2074 } else { 2075 /* Either x or y was infinite or NaN. 2076 * A NaN always produces a negative response to any test 2077 * and Infinity values cannot be "inside" any path so 2078 * they should return false as well. 2079 */ 2080 return false; 2081 } 2082 } 2083 2084 /** 2085 * Tests if the specified {@link Point2D} is inside the closed 2086 * boundary of the specified {@link PathIterator}. 2087 * <p> 2088 * This method provides a basic facility for implementors of 2089 * the {@link Shape} interface to implement support for the 2090 * {@link Shape#contains(Point2D)} method. 2091 * 2092 * @param pi the specified {@code PathIterator} 2093 * @param p the specified {@code Point2D} 2094 * @return {@code true} if the specified coordinates are inside the 2095 * specified {@code PathIterator}; {@code false} otherwise 2096 */ 2097 public static boolean contains(PathIterator pi, Point2D p) { 2098 return contains(pi, p.x, p.y); 2099 } 2100 2101 /** 2102 * {@inheritDoc} 2103 */ 2104 public final boolean contains(float x, float y) { 2105 if (x * 0f + y * 0f == 0f) { 2106 /* N * 0.0 is 0.0 only if N is finite. 2107 * Here we know that both x and y are finite. 2108 */ 2109 if (numTypes < 2) { 2110 return false; 2111 } 2112 int mask = (windingRule == WIND_NON_ZERO ? -1 : 1); 2113 return ((pointCrossings(x, y) & mask) != 0); 2114 } else { 2115 /* Either x or y was infinite or NaN. 2116 * A NaN always produces a negative response to any test 2117 * and Infinity values cannot be "inside" any path so 2118 * they should return false as well. 2119 */ 2120 return false; 2121 } 2122 } 2123 2124 /** 2125 * {@inheritDoc} 2126 */ 2127 @Override 2128 public final boolean contains(Point2D p) { 2129 return contains(p.x, p.y); 2130 } 2131 2132 /** 2133 * Tests if the specified rectangular area is entirely inside the 2134 * closed boundary of the specified {@link PathIterator}. 2135 * <p> 2136 * This method provides a basic facility for implementors of 2137 * the {@link Shape} interface to implement support for the 2138 * {@link Shape#contains(double, double, double, double)} method. 2139 * <p> 2140 * This method object may conservatively return false in 2141 * cases where the specified rectangular area intersects a 2142 * segment of the path, but that segment does not represent a 2143 * boundary between the interior and exterior of the path. 2144 * Such segments could lie entirely within the interior of the 2145 * path if they are part of a path with a {@link #WIND_NON_ZERO} 2146 * winding rule or if the segments are retraced in the reverse 2147 * direction such that the two sets of segments cancel each 2148 * other out without any exterior area falling between them. 2149 * To determine whether segments represent true boundaries of 2150 * the interior of the path would require extensive calculations 2151 * involving all of the segments of the path and the winding 2152 * rule and are thus beyond the scope of this implementation. 2153 * 2154 * @param pi the specified {@code PathIterator} 2155 * @param x the specified X coordinate 2156 * @param y the specified Y coordinate 2157 * @param w the width of the specified rectangular area 2158 * @param h the height of the specified rectangular area 2159 * @return {@code true} if the specified {@code PathIterator} contains 2160 * the specified rectangluar area; {@code false} otherwise. 2161 */ 2162 public static boolean contains(PathIterator pi, 2163 float x, float y, float w, float h) 2164 { 2165 if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) { 2166 /* [xy]+[wh] is NaN if any of those values are NaN, 2167 * or if adding the two together would produce NaN 2168 * by virtue of adding opposing Infinte values. 2169 * Since we need to add them below, their sum must 2170 * not be NaN. 2171 * We return false because NaN always produces a 2172 * negative response to tests 2173 */ 2174 return false; 2175 } 2176 if (w <= 0 || h <= 0) { 2177 return false; 2178 } 2179 int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2); 2180 int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h); 2181 return (crossings != Shape.RECT_INTERSECTS && 2182 (crossings & mask) != 0); 2183 } 2184 2185 /** 2186 * {@inheritDoc} 2187 * <p> 2188 * This method object may conservatively return false in 2189 * cases where the specified rectangular area intersects a 2190 * segment of the path, but that segment does not represent a 2191 * boundary between the interior and exterior of the path. 2192 * Such segments could lie entirely within the interior of the 2193 * path if they are part of a path with a {@link #WIND_NON_ZERO} 2194 * winding rule or if the segments are retraced in the reverse 2195 * direction such that the two sets of segments cancel each 2196 * other out without any exterior area falling between them. 2197 * To determine whether segments represent true boundaries of 2198 * the interior of the path would require extensive calculations 2199 * involving all of the segments of the path and the winding 2200 * rule and are thus beyond the scope of this implementation. 2201 */ 2202 public final boolean contains(float x, float y, float w, float h) { 2203 if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) { 2204 /* [xy]+[wh] is NaN if any of those values are NaN, 2205 * or if adding the two together would produce NaN 2206 * by virtue of adding opposing Infinte values. 2207 * Since we need to add them below, their sum must 2208 * not be NaN. 2209 * We return false because NaN always produces a 2210 * negative response to tests 2211 */ 2212 return false; 2213 } 2214 if (w <= 0 || h <= 0) { 2215 return false; 2216 } 2217 int mask = (windingRule == WIND_NON_ZERO ? -1 : 2); 2218 int crossings = rectCrossings(x, y, x+w, y+h); 2219 return (crossings != Shape.RECT_INTERSECTS && 2220 (crossings & mask) != 0); 2221 } 2222 2223 /** 2224 * Tests if the interior of the specified {@link PathIterator} 2225 * intersects the interior of a specified set of rectangular 2226 * coordinates. 2227 * <p> 2228 * This method provides a basic facility for implementors of 2229 * the {@link Shape} interface to implement support for the 2230 * {@link Shape#intersects(double, double, double, double)} method. 2231 * <p> 2232 * This method object may conservatively return true in 2233 * cases where the specified rectangular area intersects a 2234 * segment of the path, but that segment does not represent a 2235 * boundary between the interior and exterior of the path. 2236 * Such a case may occur if some set of segments of the 2237 * path are retraced in the reverse direction such that the 2238 * two sets of segments cancel each other out without any 2239 * interior area between them. 2240 * To determine whether segments represent true boundaries of 2241 * the interior of the path would require extensive calculations 2242 * involving all of the segments of the path and the winding 2243 * rule and are thus beyond the scope of this implementation. 2244 * 2245 * @param pi the specified {@code PathIterator} 2246 * @param x the specified X coordinate 2247 * @param y the specified Y coordinate 2248 * @param w the width of the specified rectangular coordinates 2249 * @param h the height of the specified rectangular coordinates 2250 * @return {@code true} if the specified {@code PathIterator} and 2251 * the interior of the specified set of rectangular 2252 * coordinates intersect each other; {@code false} otherwise. 2253 */ 2254 public static boolean intersects(PathIterator pi, 2255 float x, float y, float w, float h) 2256 { 2257 if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) { 2258 /* [xy]+[wh] is NaN if any of those values are NaN, 2259 * or if adding the two together would produce NaN 2260 * by virtue of adding opposing Infinte values. 2261 * Since we need to add them below, their sum must 2262 * not be NaN. 2263 * We return false because NaN always produces a 2264 * negative response to tests 2265 */ 2266 return false; 2267 } 2268 if (w <= 0 || h <= 0) { 2269 return false; 2270 } 2271 int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2); 2272 int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h); 2273 return (crossings == Shape.RECT_INTERSECTS || 2274 (crossings & mask) != 0); 2275 } 2276 2277 /** 2278 * {@inheritDoc} 2279 * <p> 2280 * This method object may conservatively return true in 2281 * cases where the specified rectangular area intersects a 2282 * segment of the path, but that segment does not represent a 2283 * boundary between the interior and exterior of the path. 2284 * Such a case may occur if some set of segments of the 2285 * path are retraced in the reverse direction such that the 2286 * two sets of segments cancel each other out without any 2287 * interior area between them. 2288 * To determine whether segments represent true boundaries of 2289 * the interior of the path would require extensive calculations 2290 * involving all of the segments of the path and the winding 2291 * rule and are thus beyond the scope of this implementation. 2292 */ 2293 public final boolean intersects(float x, float y, float w, float h) { 2294 if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) { 2295 /* [xy]+[wh] is NaN if any of those values are NaN, 2296 * or if adding the two together would produce NaN 2297 * by virtue of adding opposing Infinte values. 2298 * Since we need to add them below, their sum must 2299 * not be NaN. 2300 * We return false because NaN always produces a 2301 * negative response to tests 2302 */ 2303 return false; 2304 } 2305 if (w <= 0 || h <= 0) { 2306 return false; 2307 } 2308 int mask = (windingRule == WIND_NON_ZERO ? -1 : 2); 2309 int crossings = rectCrossings(x, y, x+w, y+h); 2310 return (crossings == Shape.RECT_INTERSECTS || 2311 (crossings & mask) != 0); 2312 } 2313 2314 /** 2315 * {@inheritDoc} 2316 * <p> 2317 * The iterator for this class is not multi-threaded safe, 2318 * which means that this {@code Path2D} class does not 2319 * guarantee that modifications to the geometry of this 2320 * {@code Path2D} object do not affect any iterations of 2321 * that geometry that are already in process. 2322 */ 2323 public PathIterator getPathIterator(BaseTransform tx, 2324 float flatness) 2325 { 2326 return new FlatteningPathIterator(getPathIterator(tx), flatness); 2327 } 2328 2329 static abstract class Iterator implements PathIterator { 2330 int typeIdx; 2331 int pointIdx; 2332 Path2D path; 2333 2334 Iterator(Path2D path) { 2335 this.path = path; 2336 } 2337 2338 public int getWindingRule() { 2339 return path.getWindingRule(); 2340 } 2341 2342 public boolean isDone() { 2343 return (typeIdx >= path.numTypes); 2344 } 2345 2346 public void next() { 2347 int type = path.pointTypes[typeIdx++]; 2348 pointIdx += curvecoords[type]; 2349 } 2350 } 2351 2352 public void setTo(Path2D otherPath) { 2353 numTypes = otherPath.numTypes; 2354 numCoords = otherPath.numCoords; 2355 if (numTypes > pointTypes.length) { 2356 pointTypes = new byte[numTypes]; 2357 } 2358 System.arraycopy(otherPath.pointTypes, 0, pointTypes, 0, numTypes); 2359 if (numCoords > floatCoords.length) { 2360 floatCoords = new float[numCoords]; 2361 } 2362 System.arraycopy(otherPath.floatCoords, 0, floatCoords, 0, numCoords); 2363 windingRule = otherPath.windingRule; 2364 moveX = otherPath.moveX; 2365 moveY = otherPath.moveY; 2366 prevX = otherPath.prevX; 2367 prevY = otherPath.prevY; 2368 currX = otherPath.currX; 2369 currY = otherPath.currY; 2370 } 2371 }