1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17/** 18 * @author Denis M. Kishenko 19 * @version $Revision$ 20 */ 21 22package java.awt.geom; 23 24import java.awt.Rectangle; 25import java.awt.Shape; 26import java.util.NoSuchElementException; 27 28import org.apache.harmony.awt.gl.Crossing; 29import org.apache.harmony.awt.internal.nls.Messages; 30 31/** 32 * The class GeneralPath represents a shape whose outline is given by different 33 * types of curved and straight segments. 34 * 35 * @since Android 1.0 36 */ 37public final class GeneralPath implements Shape, Cloneable { 38 39 /** 40 * The Constant WIND_EVEN_ODD see {@link PathIterator#WIND_EVEN_ODD}. 41 */ 42 public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; 43 44 /** 45 * The Constant WIND_NON_ZERO see {@link PathIterator#WIND_NON_ZERO}. 46 */ 47 public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; 48 49 /** 50 * The buffers size. 51 */ 52 private static final int BUFFER_SIZE = 10; 53 54 /** 55 * The buffers capacity. 56 */ 57 private static final int BUFFER_CAPACITY = 10; 58 59 /** 60 * The point's types buffer. 61 */ 62 byte[] types; 63 64 /** 65 * The points buffer. 66 */ 67 float[] points; 68 69 /** 70 * The point's type buffer size. 71 */ 72 int typeSize; 73 74 /** 75 * The points buffer size. 76 */ 77 int pointSize; 78 79 /** 80 * The path rule. 81 */ 82 int rule; 83 84 /** 85 * The space amount in points buffer for different segmenet's types. 86 */ 87 static int pointShift[] = { 88 2, // MOVETO 89 2, // LINETO 90 4, // QUADTO 91 6, // CUBICTO 92 0 93 }; // CLOSE 94 95 /* 96 * GeneralPath path iterator 97 */ 98 /** 99 * The Class Iterator is the subclass of Iterator for traversing the outline 100 * of a GeneralPath. 101 */ 102 class Iterator implements PathIterator { 103 104 /** 105 * The current cursor position in types buffer. 106 */ 107 int typeIndex; 108 109 /** 110 * The current cursor position in points buffer. 111 */ 112 int pointIndex; 113 114 /** 115 * The source GeneralPath object. 116 */ 117 GeneralPath p; 118 119 /** 120 * The path iterator transformation. 121 */ 122 AffineTransform t; 123 124 /** 125 * Constructs a new GeneralPath.Iterator for given general path. 126 * 127 * @param path 128 * the source GeneralPath object. 129 */ 130 Iterator(GeneralPath path) { 131 this(path, null); 132 } 133 134 /** 135 * Constructs a new GeneralPath.Iterator for given general path and 136 * transformation. 137 * 138 * @param path 139 * the source GeneralPath object. 140 * @param at 141 * the AffineTransform object to apply rectangle path. 142 */ 143 Iterator(GeneralPath path, AffineTransform at) { 144 this.p = path; 145 this.t = at; 146 } 147 148 public int getWindingRule() { 149 return p.getWindingRule(); 150 } 151 152 public boolean isDone() { 153 return typeIndex >= p.typeSize; 154 } 155 156 public void next() { 157 typeIndex++; 158 } 159 160 public int currentSegment(double[] coords) { 161 if (isDone()) { 162 // awt.4B=Iterator out of bounds 163 throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ 164 } 165 int type = p.types[typeIndex]; 166 int count = GeneralPath.pointShift[type]; 167 for (int i = 0; i < count; i++) { 168 coords[i] = p.points[pointIndex + i]; 169 } 170 if (t != null) { 171 t.transform(coords, 0, coords, 0, count / 2); 172 } 173 pointIndex += count; 174 return type; 175 } 176 177 public int currentSegment(float[] coords) { 178 if (isDone()) { 179 // awt.4B=Iterator out of bounds 180 throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ 181 } 182 int type = p.types[typeIndex]; 183 int count = GeneralPath.pointShift[type]; 184 System.arraycopy(p.points, pointIndex, coords, 0, count); 185 if (t != null) { 186 t.transform(coords, 0, coords, 0, count / 2); 187 } 188 pointIndex += count; 189 return type; 190 } 191 192 } 193 194 /** 195 * Instantiates a new general path with the winding rule set to 196 * {@link PathIterator#WIND_NON_ZERO} and the initial capacity (number of 197 * segments) set to the default value 10. 198 */ 199 public GeneralPath() { 200 this(WIND_NON_ZERO, BUFFER_SIZE); 201 } 202 203 /** 204 * Instantiates a new general path with the given winding rule and the 205 * initial capacity (number of segments) set to the default value 10. 206 * 207 * @param rule 208 * the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or 209 * {@link PathIterator#WIND_NON_ZERO}. 210 */ 211 public GeneralPath(int rule) { 212 this(rule, BUFFER_SIZE); 213 } 214 215 /** 216 * Instantiates a new general path with the given winding rule and initial 217 * capacity (number of segments). 218 * 219 * @param rule 220 * the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or 221 * {@link PathIterator#WIND_NON_ZERO}. 222 * @param initialCapacity 223 * the number of segments the path is set to hold. 224 */ 225 public GeneralPath(int rule, int initialCapacity) { 226 setWindingRule(rule); 227 types = new byte[initialCapacity]; 228 points = new float[initialCapacity * 2]; 229 } 230 231 /** 232 * Creates a new GeneralPath from the outline of the given shape. 233 * 234 * @param shape 235 * the shape. 236 */ 237 public GeneralPath(Shape shape) { 238 this(WIND_NON_ZERO, BUFFER_SIZE); 239 PathIterator p = shape.getPathIterator(null); 240 setWindingRule(p.getWindingRule()); 241 append(p, false); 242 } 243 244 /** 245 * Sets the winding rule, which determines how to decide whether a point 246 * that isn't on the path itself is inside or outside of the shape. 247 * 248 * @param rule 249 * the new winding rule. 250 * @throws IllegalArgumentException 251 * if the winding rule is neither 252 * {@link PathIterator#WIND_EVEN_ODD} nor 253 * {@link PathIterator#WIND_NON_ZERO}. 254 */ 255 public void setWindingRule(int rule) { 256 if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { 257 // awt.209=Invalid winding rule value 258 throw new java.lang.IllegalArgumentException(Messages.getString("awt.209")); //$NON-NLS-1$ 259 } 260 this.rule = rule; 261 } 262 263 /** 264 * Gets the winding rule. 265 * 266 * @return the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or 267 * {@link PathIterator#WIND_NON_ZERO}. 268 */ 269 public int getWindingRule() { 270 return rule; 271 } 272 273 /** 274 * Checks the point data buffer sizes to see whether pointCount additional 275 * point-data elements can fit. (Note that the number of point data elements 276 * to add is more than one per point -- it depends on the type of point 277 * being added.) Reallocates the buffers to enlarge the size if necessary. 278 * 279 * @param pointCount 280 * the number of point data elements to be added. 281 * @param checkMove 282 * whether to check for existing points. 283 * @throws IllegalPathStateException 284 * checkMove is true and the path is currently empty. 285 */ 286 void checkBuf(int pointCount, boolean checkMove) { 287 if (checkMove && typeSize == 0) { 288 // awt.20A=First segment should be SEG_MOVETO type 289 throw new IllegalPathStateException(Messages.getString("awt.20A")); //$NON-NLS-1$ 290 } 291 if (typeSize == types.length) { 292 byte tmp[] = new byte[typeSize + BUFFER_CAPACITY]; 293 System.arraycopy(types, 0, tmp, 0, typeSize); 294 types = tmp; 295 } 296 if (pointSize + pointCount > points.length) { 297 float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; 298 System.arraycopy(points, 0, tmp, 0, pointSize); 299 points = tmp; 300 } 301 } 302 303 /** 304 * Appends a new point to the end of this general path, disconnected from 305 * the existing path. 306 * 307 * @param x 308 * the x coordinate of the next point to append. 309 * @param y 310 * the y coordinate of the next point to append. 311 */ 312 public void moveTo(float x, float y) { 313 if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) { 314 points[pointSize - 2] = x; 315 points[pointSize - 1] = y; 316 } else { 317 checkBuf(2, false); 318 types[typeSize++] = PathIterator.SEG_MOVETO; 319 points[pointSize++] = x; 320 points[pointSize++] = y; 321 } 322 } 323 324 /** 325 * Appends a new segment to the end of this general path by making a 326 * straight line segment from the current endpoint to the given new point. 327 * 328 * @param x 329 * the x coordinate of the next point to append. 330 * @param y 331 * the y coordinate of the next point to append. 332 */ 333 public void lineTo(float x, float y) { 334 checkBuf(2, true); 335 types[typeSize++] = PathIterator.SEG_LINETO; 336 points[pointSize++] = x; 337 points[pointSize++] = y; 338 } 339 340 /** 341 * Appends a new segment to the end of this general path by making a 342 * quadratic curve from the current endpoint to the point (x2, y2) using the 343 * point (x1, y1) as the quadratic curve's control point. 344 * 345 * @param x1 346 * the x coordinate of the quadratic curve's control point. 347 * @param y1 348 * the y coordinate of the quadratic curve's control point. 349 * @param x2 350 * the x coordinate of the quadratic curve's end point. 351 * @param y2 352 * the y coordinate of the quadratic curve's end point. 353 */ 354 public void quadTo(float x1, float y1, float x2, float y2) { 355 checkBuf(4, true); 356 types[typeSize++] = PathIterator.SEG_QUADTO; 357 points[pointSize++] = x1; 358 points[pointSize++] = y1; 359 points[pointSize++] = x2; 360 points[pointSize++] = y2; 361 } 362 363 /** 364 * Appends a new segment to the end of this general path by making a cubic 365 * curve from the current endpoint to the point (x3, y3) using (x1, y1) and 366 * (x2, y2) as control points. 367 * 368 * @see java.awt.geom.CubicCurve2D 369 * @param x1 370 * the x coordinate of the new cubic segment's first control 371 * point. 372 * @param y1 373 * the y coordinate of the new cubic segment's first control 374 * point. 375 * @param x2 376 * the x coordinate of the new cubic segment's second control 377 * point. 378 * @param y2 379 * the y coordinate of the new cubic segment's second control 380 * point. 381 * @param x3 382 * the x coordinate of the new cubic segment's end point. 383 * @param y3 384 * the y coordinate of the new cubic segment's end point. 385 */ 386 public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) { 387 checkBuf(6, true); 388 types[typeSize++] = PathIterator.SEG_CUBICTO; 389 points[pointSize++] = x1; 390 points[pointSize++] = y1; 391 points[pointSize++] = x2; 392 points[pointSize++] = y2; 393 points[pointSize++] = x3; 394 points[pointSize++] = y3; 395 } 396 397 /** 398 * Appends the type information to declare that the current endpoint closes 399 * the curve. 400 */ 401 public void closePath() { 402 if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) { 403 checkBuf(0, true); 404 types[typeSize++] = PathIterator.SEG_CLOSE; 405 } 406 } 407 408 /** 409 * Appends the outline of the specified shape onto the end of this 410 * GeneralPath. 411 * 412 * @param shape 413 * the shape whose outline is to be appended. 414 * @param connect 415 * true to connect this path's current endpoint to the first 416 * point of the shape's outline or false to append the shape's 417 * outline without connecting it. 418 * @throws NullPointerException 419 * if the shape parameter is null. 420 */ 421 public void append(Shape shape, boolean connect) { 422 PathIterator p = shape.getPathIterator(null); 423 append(p, connect); 424 } 425 426 /** 427 * Appends the path defined by the specified PathIterator onto the end of 428 * this GeneralPath. 429 * 430 * @param path 431 * the PathIterator that defines the new path to append. 432 * @param connect 433 * true to connect this path's current endpoint to the first 434 * point of the shape's outline or false to append the shape's 435 * outline without connecting it. 436 */ 437 public void append(PathIterator path, boolean connect) { 438 while (!path.isDone()) { 439 float coords[] = new float[6]; 440 switch (path.currentSegment(coords)) { 441 case PathIterator.SEG_MOVETO: 442 if (!connect || typeSize == 0) { 443 moveTo(coords[0], coords[1]); 444 break; 445 } 446 if (types[typeSize - 1] != PathIterator.SEG_CLOSE 447 && points[pointSize - 2] == coords[0] 448 && points[pointSize - 1] == coords[1]) { 449 break; 450 } 451 // NO BREAK; 452 case PathIterator.SEG_LINETO: 453 lineTo(coords[0], coords[1]); 454 break; 455 case PathIterator.SEG_QUADTO: 456 quadTo(coords[0], coords[1], coords[2], coords[3]); 457 break; 458 case PathIterator.SEG_CUBICTO: 459 curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); 460 break; 461 case PathIterator.SEG_CLOSE: 462 closePath(); 463 break; 464 } 465 path.next(); 466 connect = false; 467 } 468 } 469 470 /** 471 * Gets the current end point of the path. 472 * 473 * @return the current end point of the path. 474 */ 475 public Point2D getCurrentPoint() { 476 if (typeSize == 0) { 477 return null; 478 } 479 int j = pointSize - 2; 480 if (types[typeSize - 1] == PathIterator.SEG_CLOSE) { 481 482 for (int i = typeSize - 2; i > 0; i--) { 483 int type = types[i]; 484 if (type == PathIterator.SEG_MOVETO) { 485 break; 486 } 487 j -= pointShift[type]; 488 } 489 } 490 return new Point2D.Float(points[j], points[j + 1]); 491 } 492 493 /** 494 * Resets the GeneralPath to being an empty path. The underlying point and 495 * segment data is not deleted but rather the end indices of the data arrays 496 * are set to zero. 497 */ 498 public void reset() { 499 typeSize = 0; 500 pointSize = 0; 501 } 502 503 /** 504 * Transform all of the coordinates of this path according to the specified 505 * AffineTransform. 506 * 507 * @param t 508 * the AffineTransform. 509 */ 510 public void transform(AffineTransform t) { 511 t.transform(points, 0, points, 0, pointSize / 2); 512 } 513 514 /** 515 * Creates a new GeneralPath whose data is given by this path's data 516 * transformed according to the specified AffineTransform. 517 * 518 * @param t 519 * the AffineTransform. 520 * @return the new GeneralPath whose data is given by this path's data 521 * transformed according to the specified AffineTransform. 522 */ 523 public Shape createTransformedShape(AffineTransform t) { 524 GeneralPath p = (GeneralPath)clone(); 525 if (t != null) { 526 p.transform(t); 527 } 528 return p; 529 } 530 531 public Rectangle2D getBounds2D() { 532 float rx1, ry1, rx2, ry2; 533 if (pointSize == 0) { 534 rx1 = ry1 = rx2 = ry2 = 0.0f; 535 } else { 536 int i = pointSize - 1; 537 ry1 = ry2 = points[i--]; 538 rx1 = rx2 = points[i--]; 539 while (i > 0) { 540 float y = points[i--]; 541 float x = points[i--]; 542 if (x < rx1) { 543 rx1 = x; 544 } else if (x > rx2) { 545 rx2 = x; 546 } 547 if (y < ry1) { 548 ry1 = y; 549 } else if (y > ry2) { 550 ry2 = y; 551 } 552 } 553 } 554 return new Rectangle2D.Float(rx1, ry1, rx2 - rx1, ry2 - ry1); 555 } 556 557 public Rectangle getBounds() { 558 return getBounds2D().getBounds(); 559 } 560 561 /** 562 * Checks the cross count (number of times a ray from the point crosses the 563 * shape's boundary) to determine whether the number of crossings 564 * corresponds to a point inside the shape or not (according to the shape's 565 * path rule). 566 * 567 * @param cross 568 * the point's cross count. 569 * @return true if the point is inside the path, or false otherwise. 570 */ 571 boolean isInside(int cross) { 572 if (rule == WIND_NON_ZERO) { 573 return Crossing.isInsideNonZero(cross); 574 } 575 return Crossing.isInsideEvenOdd(cross); 576 } 577 578 public boolean contains(double px, double py) { 579 return isInside(Crossing.crossShape(this, px, py)); 580 } 581 582 public boolean contains(double rx, double ry, double rw, double rh) { 583 int cross = Crossing.intersectShape(this, rx, ry, rw, rh); 584 return cross != Crossing.CROSSING && isInside(cross); 585 } 586 587 public boolean intersects(double rx, double ry, double rw, double rh) { 588 int cross = Crossing.intersectShape(this, rx, ry, rw, rh); 589 return cross == Crossing.CROSSING || isInside(cross); 590 } 591 592 public boolean contains(Point2D p) { 593 return contains(p.getX(), p.getY()); 594 } 595 596 public boolean contains(Rectangle2D r) { 597 return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 598 } 599 600 public boolean intersects(Rectangle2D r) { 601 return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 602 } 603 604 public PathIterator getPathIterator(AffineTransform t) { 605 return new Iterator(this, t); 606 } 607 608 public PathIterator getPathIterator(AffineTransform t, double flatness) { 609 return new FlatteningPathIterator(getPathIterator(t), flatness); 610 } 611 612 @Override 613 public Object clone() { 614 try { 615 GeneralPath p = (GeneralPath)super.clone(); 616 p.types = types.clone(); 617 p.points = points.clone(); 618 return p; 619 } catch (CloneNotSupportedException e) { 620 throw new InternalError(); 621 } 622 } 623 624} 625