1 /* 2 * @(#)GeneralPath.java 1.54 00/02/02 3 * 4 * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved. 5 * 6 * This software is the proprietary information of Sun Microsystems, Inc. 7 * Use is subject to license terms. 8 * 9 */ 10 11 package math.geom2d.curve; 12 13 import math.geom2d.Point2D; 14 15 import java.awt.geom.*; 16 import java.awt.Shape; 17 18 /** 19 * The <code>GeneralPath</code> class represents a geometric path constructed 20 * from straight lines, and quadratic and cubic (Bezier) curves. It can contain 21 * multiple subpaths. 22 * <p> 23 * The winding rule specifies how the interior of a path is determined. There 24 * are two types of winding rules: EVEN_ODD and NON_ZERO. 25 * <p> 26 * An EVEN_ODD winding rule means that enclosed regions of the path alternate 27 * between interior and exterior areas as traversed from the outside of the path 28 * towards a point inside the region. 29 * <p> 30 * A NON_ZERO winding rule means that if a ray is drawn in any direction from a 31 * given point to infinity and the places where the path intersects the ray are 32 * examined, the point is inside of the path if and only if the number of times 33 * that the path crosses the ray from left to right does not equal the number of 34 * times that the path crosses the ray from right to left. 35 * 36 * @version 1.54, 02/02/00 37 * @author Jim Graham 38 */ 39 public final class GeneralPath2D implements Shape, Cloneable { 40 41 GeneralPath path; 42 43 /** 44 * An even-odd winding rule for determining the interior of a path. 45 */ 46 public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; 47 48 /** 49 * A non-zero winding rule for determining the interior of a path. 50 */ 51 public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; 52 53 /** 54 * Constructs a new <code>GeneralPath</code> object. If an operation 55 * performed on this path requires the interior of the path to be defined 56 * then the default NON_ZERO winding rule is used. 57 * 58 * @see #WIND_NON_ZERO 59 */ 60 public GeneralPath2D() { 61 path = new GeneralPath(); 62 } 63 64 /** 65 * Constructs a new <code>GeneralPath</code> object with the specified 66 * winding rule to control operations that require the interior of the path 67 * to be defined. 68 * 69 * @param rule the winding rule 70 * @see #WIND_EVEN_ODD 71 * @see #WIND_NON_ZERO 72 */ 73 public GeneralPath2D(int rule) { 74 path = new GeneralPath(rule); 75 } 76 77 /** 78 * Constructs a new <code>GeneralPath</code> object with the specified 79 * winding rule and the specified initial capacity to store path 80 * coordinates. This number is an initial guess as to how many path segments 81 * are in the path, but the storage is expanded as needed to store whatever 82 * path segments are added to this path. 83 * 84 * @param rule the winding rule 85 * @param initialCapacity the estimate for the number of path segments in 86 * the path 87 * @see #WIND_EVEN_ODD 88 * @see #WIND_NON_ZERO 89 */ 90 public GeneralPath2D(int rule, int initialCapacity) { 91 path = new GeneralPath(rule, initialCapacity); 92 } 93 94 /** 95 * Constructs a new <code>GeneralPath</code> object from an arbitrary 96 * {@link Shape} object. All of the initial geometry and the winding rule 97 * for this path are taken from the specified <code>Shape</code> object. 98 * 99 * @param s the specified <code>Shape</code> object 100 */ 101 public GeneralPath2D(Shape s) { 102 path = new GeneralPath(s); 103 } 104 105 /** 106 * Adds a point to the path by moving to the specified coordinates. 107 * 108 * @param x the x-coordinate of the destination 109 * @param y the y-coordinate of the destination 110 */ 111 public synchronized void moveTo(double x, double y) { 112 path.moveTo((float) x, (float) y); 113 } 114 115 /** 116 * Adds a point to the path by moving to the specified coordinates. 117 * 118 * @param p the specified point 119 */ 120 public synchronized void moveTo(java.awt.geom.Point2D p) { 121 path.moveTo((float) p.getX(), (float) p.getY()); 122 } 123 124 /** 125 * Adds a point to the path by drawing a straight line from the current 126 * coordinates to the new specified coordinates. 127 * 128 * @param x the x-coordinate of the destination 129 * @param y the y-coordinate of the destination 130 */ 131 public synchronized void lineTo(double x, double y) { 132 path.lineTo((float) x, (float) y); 133 } 134 135 /** 136 * Adds a point to the path by drawing a straight line from the current 137 * coordinates to the new specified coordinates. 138 * 139 * @param p the coordinate of the destionation point 140 */ 141 public synchronized void lineTo(java.awt.geom.Point2D p) { 142 path.lineTo((float) p.getX(), (float) p.getY()); 143 } 144 145 /** 146 * Adds a curved segment, defined by two new points, to the path by drawing 147 * a Quadratic curve that intersects both the current coordinates and the 148 * coordinates (x2, y2), using the specified point (x1, y1) as a 149 * quadratic parametric control point. 150 * 151 * @param x1 the x-coordinate of the control point 152 * @param y1 the y-coordinate of the control point 153 * @param x2 the x-coordinate of the end point 154 * @param y2 the y-coordinate of the end point 155 */ 156 public synchronized void quadTo(double x1, double y1, double x2, double y2) { 157 path.quadTo((float) x1, (float) y1, (float) x2, (float) y2); 158 } 159 160 /** 161 * Adds a curved segment, defined by two new points, to the path by drawing 162 * a Quadratic curve that intersects both the current coordinates and the 163 * coordinates (x2, y2), using the specified point (x1, y1) as a 164 * quadratic parametric control point. 165 * 166 * @param p1 the control point 167 * @param p2 the end point 168 */ 169 public synchronized void quadTo(java.awt.geom.Point2D p1, 170 java.awt.geom.Point2D p2) { 171 path.quadTo((float) p1.getX(), (float) p1.getY(), (float) p2.getX(), 172 (float) p2.getY()); 173 } 174 175 /** 176 * Adds a curved segment, defined by three new points, to the path by 177 * drawing a Bezier curve that intersects both the current coordinates and 178 * the coordinates (x3, y3), using the specified points (x1, y1) 179 * and (x2, y2) as Bezier control points. 180 * 181 * @param x1 the x-coordinate of the first control point 182 * @param y1 the y-coordinate of the first control point 183 * @param x2 the x-coordinate of the second control point 184 * @param y2 the y-coordinate of the second control point 185 * @param x3 the x-coordinate of the end point 186 * @param y3 the y-coordinate of the end point 187 */ 188 public synchronized void curveTo(double x1, double y1, double x2, 189 double y2, double x3, double y3) { 190 path.curveTo((float) x1, (float) y1, (float) x2, (float) y2, 191 (float) x3, (float) y3); 192 } 193 194 /** 195 * Adds a curved segment, defined by three new points, to the path by 196 * drawing a Bezier curve that intersects both the current coordinates and 197 * the coordinates (x3, y3), using the specified points (x1, y1) 198 * and (x2, y2) as Bezier control points. 199 * 200 * @param p1 the coordinates of the first control point 201 * @param p2 the coordinates of the second control point 202 * @param p3 the coordinates of the final endpoint 203 */ 204 public synchronized void curveTo(java.awt.geom.Point2D p1, 205 java.awt.geom.Point2D p2, java.awt.geom.Point2D p3) { 206 path.curveTo((float) p1.getX(), (float) p1.getY(), (float) p2.getX(), 207 (float) p2.getY(), (float) p3.getX(), (float) p3.getY()); 208 } 209 210 /** 211 * Closes the current subpath by drawing a straight line back to the 212 * coordinates of the last <code>moveTo</code>. If the path is already 213 * closed then this method has no effect. 214 */ 215 public synchronized void closePath() { 216 path.closePath(); 217 } 218 219 /** 220 * Appends the geometry of the specified <code>Shape</code> object to the 221 * path, possibly connecting the new geometry to the existing path segments 222 * with a line segment. If the <code>connect</code> parameter is 223 * <code>true</code> and the path is not empty then any initial 224 * <code>moveTo</code> in the geometry of the appended <code>Shape</code> 225 * is turned into a <code>lineTo</code> segment. If the destination 226 * coordinates of such a connecting <code>lineTo</code> segment match the 227 * ending coordinates of a currently open subpath then the segment is 228 * omitted as superfluous. The winding rule of the specified 229 * <code>Shape</code> is ignored and the appended geometry is governed by 230 * the winding rule specified for this path. 231 * 232 * @param s the <code>Shape</code> whose geometry is appended to this path 233 * @param connect a boolean to control whether or not to turn an initial 234 * <code>moveTo</code> segment into a <code>lineTo</code> 235 * segment to connect the new geometry to the existing path 236 */ 237 public void append(Shape s, boolean connect) { 238 path.append(s, connect); 239 } 240 241 /** 242 * Appends the geometry of the specified {@link PathIterator} object to the 243 * path, possibly connecting the new geometry to the existing path segments 244 * with a line segment. If the <code>connect</code> parameter is 245 * <code>true</code> and the path is not empty then any initial 246 * <code>moveTo</code> in the geometry of the appended <code>Shape</code> 247 * is turned into a <code>lineTo</code> segment. If the destination 248 * coordinates of such a connecting <code>lineTo</code> segment match the 249 * ending coordinates of a currently open subpath then the segment is 250 * omitted as superfluous. The winding rule of the specified 251 * <code>Shape</code> is ignored and the appended geometry is governed by 252 * the winding rule specified for this path. 253 * 254 * @param pi the <code>PathIterator</code> whose geometry is appended to 255 * this path 256 * @param connect a boolean to control whether or not to turn an initial 257 * <code>moveTo</code> segment into a <code>lineTo</code> 258 * segment to connect the new geometry to the existing path 259 */ 260 public void append(PathIterator pi, boolean connect) { 261 path.append(pi, connect); 262 } 263 264 /** 265 * Returns the fill style winding rule. 266 * 267 * @return an integer representing the current winding rule. 268 * @see #WIND_EVEN_ODD 269 * @see #WIND_NON_ZERO 270 */ 271 public synchronized int getWindingRule() { 272 return path.getWindingRule(); 273 } 274 275 /** 276 * Sets the winding rule for this path to the specified value. 277 * 278 * @param rule an integer representing the specified winding rule 279 * @exception <code>IllegalArgumentException</code> if <code>rule</code> 280 * is not either <code>WIND_EVEN_ODD</code> or 281 * <code>WIND_NON_ZERO</code> 282 * @see #WIND_EVEN_ODD 283 * @see #WIND_NON_ZERO 284 */ 285 public void setWindingRule(int rule) { 286 path.setWindingRule(rule); 287 } 288 289 /** 290 * Returns the coordinates most recently added to the end of the path as a 291 * {@link Point2D} object. 292 * 293 * @return a <code>Point2D</code> object containing the ending coordinates 294 * of the path or <code>null</code> if there are no points in the 295 * path. 296 */ 297 public synchronized Point2D getCurrentPoint() { 298 return new Point2D(path.getCurrentPoint()); 299 } 300 301 /** 302 * Resets the path to empty. The append position is set back to the 303 * beginning of the path and all coordinates and point types are forgotten. 304 */ 305 public synchronized void reset() { 306 path.reset(); 307 } 308 309 /** 310 * Transforms the geometry of this path using the specified 311 * {@link AffineTransform}. The geometry is transformed in place, which 312 * permanently changes the boundary defined by this object. 313 * 314 * @param at the <code>AffineTransform</code> used to transform the area 315 */ 316 public void transform(AffineTransform at) { 317 path.transform(at); 318 } 319 320 /** 321 * Returns a new transformed <code>Shape</code>. 322 * 323 * @param at the <code>AffineTransform</code> used to transform a new 324 * <code>Shape</code>. 325 * @return a new <code>Shape</code>, transformed with the specified 326 * <code>AffineTransform</code>. 327 */ 328 public synchronized Shape createTransformedShape(AffineTransform at) { 329 return path.createTransformedShape(at); 330 } 331 332 /** 333 * Return the bounding box of the path. 334 * 335 * @return a {@link java.awt.Rectangle} object that bounds the current path. 336 */ 337 public java.awt.Rectangle getBounds() { 338 return path.getBounds(); 339 } 340 341 /** 342 * Returns the bounding box of the path. 343 * 344 * @return a {@link Rectangle2D} object that bounds the current path. 345 */ 346 public synchronized java.awt.geom.Rectangle2D getBounds2D() { 347 return path.getBounds2D(); 348 } 349 350 /** 351 * Tests if the specified coordinates are inside the boundary of this 352 * <code>Shape</code>. 353 * 354 * @param x the x-coordinate of the point 355 * @param y the y-coordinate of the point 356 * @return <code>true</code> if the specified coordinates are inside this 357 * <code>Shape</code>; <code>false</code> otherwise 358 */ 359 public boolean contains(double x, double y) { 360 return path.contains(x, y); 361 } 362 363 /** 364 * Tests if the specified <code>Point2D</code> is inside the boundary of 365 * this <code>Shape</code>. 366 * 367 * @param p the specified <code>Point2D</code> 368 * @return <code>true</code> if this <code>Shape</code> contains the 369 * specified <code>Point2D</code>, <code>false</code> 370 * otherwise. 371 */ 372 public boolean contains(java.awt.geom.Point2D p) { 373 return contains(p); 374 } 375 376 /** 377 * Tests if the specified rectangular area is inside the boundary of this 378 * <code>Shape</code>. 379 * 380 * @param x the x coordinate of the rectangle 381 * @param y the y coordinate of the rectangle 382 * @param w the width of the specified rectangular area 383 * @param h the height of the specified rectangular area 384 * @return <code>true</code> if this <code>Shape</code> contains the 385 * specified rectangluar area; <code>false</code> otherwise. 386 */ 387 public boolean contains(double x, double y, double w, double h) { 388 return contains(x, y, w, h); 389 } 390 391 /** 392 * Tests if the specified <code>Rectangle2D</code> is inside the boundary 393 * of this <code>Shape</code>. 394 * 395 * @param r a specified <code>Rectangle2D</code> 396 * @return <code>true</code> if this <code>Shape</code> bounds the 397 * specified <code>Rectangle2D</code>; <code>false</code> 398 * otherwise. 399 */ 400 public boolean contains(java.awt.geom.Rectangle2D r) { 401 return path.contains(r); 402 } 403 404 /** 405 * Tests if the interior of this <code>Shape</code> intersects the 406 * interior of a specified set of rectangular coordinates. 407 * 408 * @param x the position of the left corner 409 * @param y the position of the bottom corner 410 * @param w the width of the specified rectangular coordinates 411 * @param h the height of the specified rectangular coordinates 412 * @return <code>true</code> if this <code>Shape</code> and the interior 413 * of the specified set of rectangular coordinates intersect each 414 * other; <code>false</code> otherwise. 415 */ 416 public boolean intersects(double x, double y, double w, double h) { 417 return intersects(x, y, w, h); 418 } 419 420 /** 421 * Tests if the interior of this <code>Shape</code> intersects the 422 * interior of a specified <code>Rectangle2D</code>. 423 * 424 * @param r the specified <code>Rectangle2D</code> 425 * @return <code>true</code> if this <code>Shape</code> and the interior 426 * of the specified <code>Rectangle2D</code> intersect each other; 427 * <code>false</code> otherwise. 428 */ 429 public boolean intersects(java.awt.geom.Rectangle2D r) { 430 return intersects(r); 431 } 432 433 /** 434 * Returns a <code>PathIterator</code> object that iterates along the 435 * boundary of this <code>Shape</code> and provides access to the geometry 436 * of the outline of this <code>Shape</code>. The iterator for this class 437 * is not multi-threaded safe, which means that this 438 * <code>GeneralPath</code> class does not guarantee that modifications to 439 * the geometry of this <code>GeneralPath</code> object do not affect any 440 * iterations of that geometry that are already in process. 441 * 442 * @param at an <code>AffineTransform</code> 443 * @return a new <code>PathIterator</code> that iterates along the 444 * boundary of this <code>Shape</code> and provides access to the 445 * geometry of this <code>Shape</code>'s outline 446 */ 447 public PathIterator getPathIterator(AffineTransform at) { 448 return path.getPathIterator(at); 449 } 450 451 /** 452 * Returns a <code>PathIterator</code> object that iterates along the 453 * boundary of the flattened <code>Shape</code> and provides access to the 454 * geometry of the outline of the <code>Shape</code>. The iterator for 455 * this class is not multi-threaded safe, which means that this 456 * <code>GeneralPath</code> class does not guarantee that modifications to 457 * the geometry of this <code>GeneralPath</code> object do not affect any 458 * iterations of that geometry that are already in process. 459 * 460 * @param at an <code>AffineTransform</code> 461 * @param flatness the maximum distance that the line segments used to 462 * approximate the curved segments are allowed to deviate from 463 * any point on the original curve 464 * @return a new <code>PathIterator</code> that iterates along the 465 * flattened <code>Shape</code> boundary. 466 */ 467 public PathIterator getPathIterator(AffineTransform at, double flatness) { 468 return path.getPathIterator(at, flatness); 469 } 470 471 /** 472 * Creates a new object of the same class as this object. 473 * 474 * @return a clone of this instance. 475 * @exception OutOfMemoryError if there is not enough memory. 476 * @see java.lang.Cloneable 477 * @since 1.2 478 */ 479 @Override 480 public Object clone() { 481 return path.clone(); 482 } 483 }