Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)
Frames | No Frames

Source for java.awt.geom.Arc2D

 1:  /* Arc2D.java -- represents an arc in 2-D space
 2:  Copyright (C) 2002, 2003, 2004 Free Software Foundation
 3: 
 4: This file is part of GNU Classpath.
 5: 
 6: GNU Classpath is free software; you can redistribute it and/or modify
 7: it under the terms of the GNU General Public License as published by
 8: the Free Software Foundation; either version 2, or (at your option)
 9: any later version.
 10: 
 11: GNU Classpath is distributed in the hope that it will be useful, but
 12: WITHOUT ANY WARRANTY; without even the implied warranty of
 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 14: General Public License for more details.
 15: 
 16: You should have received a copy of the GNU General Public License
 17: along with GNU Classpath; see the file COPYING. If not, write to the
 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 19: 02110-1301 USA.
 20: 
 21: Linking this library statically or dynamically with other modules is
 22: making a combined work based on this library. Thus, the terms and
 23: conditions of the GNU General Public License cover the whole
 24: combination.
 25: 
 26: As a special exception, the copyright holders of this library give you
 27: permission to link this library with independent modules to produce an
 28: executable, regardless of the license terms of these independent
 29: modules, and to copy and distribute the resulting executable under
 30: terms of your choice, provided that you also meet, for each linked
 31: independent module, the terms and conditions of the license of that
 32: module. An independent module is a module which is not derived from
 33: or based on this library. If you modify this library, you may extend
 34: this exception to your version of the library, but you are not
 35: obligated to do so. If you do not wish to do so, delete this
 36: exception statement from your version. */
 37: 
 38:  package java.awt.geom;
 39: 
 40:  import java.util.NoSuchElementException;
 41: 
 42: 
 43:  /**
 44:  * This class represents all arcs (segments of an ellipse in 2-D space). The
 45:  * arcs are defined by starting angle and extent (arc length) in degrees, as
 46:  * opposed to radians (like the rest of Java), and can be open, chorded, or
 47:  * wedge shaped. The angles are skewed according to the ellipse, so that 45
 48:  * degrees always points to the upper right corner (positive x, negative y)
 49:  * of the bounding rectangle. A positive extent draws a counterclockwise arc,
 50:  * and while the angle can be any value, the path iterator only traverses the
 51:  * first 360 degrees. Storage is up to the subclasses.
 52:  *
 53:  * @author Eric Blake (ebb9@email.byu.edu)
 54:  * @author Sven de Marothy (sven@physto.se)
 55:  * @since 1.2
 56:  */
 57:  public abstract class Arc2D extends RectangularShape
 58: {
 59:  /**
 60:  * An open arc, with no segment connecting the endpoints. This type of
 61:  * arc still contains the same points as a chorded version.
 62:  */
 63:  public static final int OPEN = 0;
 64: 
 65:  /**
 66:  * A closed arc with a single segment connecting the endpoints (a chord).
 67:  */
 68:  public static final int CHORD = 1;
 69: 
 70:  /**
 71:  * A closed arc with two segments, one from each endpoint, meeting at the
 72:  * center of the ellipse.
 73:  */
 74:  public static final int PIE = 2;
 75: 
 76:  /** The closure type of this arc. This is package-private to avoid an
 77:  * accessor method. */
 78:  int type;
 79: 
 80:  /**
 81:  * Create a new arc, with the specified closure type.
 82:  *
 83:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}.
 84:  * @throws IllegalArgumentException if type is invalid
 85:  */
 86:  protected Arc2D(int type)
 87:  {
 88:  if (type < OPEN || type > PIE)
 89:  throw new IllegalArgumentException();
 90:  this.type = type;
 91:  }
 92: 
 93:  /**
 94:  * Get the starting angle of the arc in degrees.
 95:  *
 96:  * @return the starting angle
 97:  * @see #setAngleStart(double)
 98:  */
 99:  public abstract double getAngleStart();
 100: 
 101:  /**
 102:  * Get the extent angle of the arc in degrees.
 103:  *
 104:  * @return the extent angle
 105:  * @see #setAngleExtent(double)
 106:  */
 107:  public abstract double getAngleExtent();
 108: 
 109:  /**
 110:  * Return the closure type of the arc.
 111:  *
 112:  * @return the closure type
 113:  * @see #OPEN
 114:  * @see #CHORD
 115:  * @see #PIE
 116:  * @see #setArcType(int)
 117:  */
 118:  public int getArcType()
 119:  {
 120:  return type;
 121:  }
 122: 
 123:  /**
 124:  * Returns the starting point of the arc.
 125:  *
 126:  * @return the start point
 127:  */
 128:  public Point2D getStartPoint()
 129:  {
 130:  double angle = Math.toRadians(getAngleStart());
 131:  double rx = getWidth() / 2;
 132:  double ry = getHeight() / 2;
 133:  double x = getX() + rx + rx * Math.cos(angle);
 134:  double y = getY() + ry - ry * Math.sin(angle);
 135:  return new Point2D.Double(x, y);
 136:  }
 137: 
 138:  /**
 139:  * Returns the ending point of the arc.
 140:  *
 141:  * @return the end point
 142:  */
 143:  public Point2D getEndPoint()
 144:  {
 145:  double angle = Math.toRadians(getAngleStart() + getAngleExtent());
 146:  double rx = getWidth() / 2;
 147:  double ry = getHeight() / 2;
 148:  double x = getX() + rx + rx * Math.cos(angle);
 149:  double y = getY() + ry - ry * Math.sin(angle);
 150:  return new Point2D.Double(x, y);
 151:  }
 152: 
 153:  /**
 154:  * Set the parameters of the arc. The angles are in degrees, and a positive
 155:  * extent sweeps counterclockwise (from the positive x-axis to the negative
 156:  * y-axis).
 157:  *
 158:  * @param x the new x coordinate of the upper left of the bounding box
 159:  * @param y the new y coordinate of the upper left of the bounding box
 160:  * @param w the new width of the bounding box
 161:  * @param h the new height of the bounding box
 162:  * @param start the start angle, in degrees
 163:  * @param extent the arc extent, in degrees
 164:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
 165:  * @throws IllegalArgumentException if type is invalid
 166:  */
 167:  public abstract void setArc(double x, double y, double w, double h,
 168:  double start, double extent, int type);
 169: 
 170:  /**
 171:  * Set the parameters of the arc. The angles are in degrees, and a positive
 172:  * extent sweeps counterclockwise (from the positive x-axis to the negative
 173:  * y-axis).
 174:  *
 175:  * @param p the upper left point of the bounding box
 176:  * @param d the dimensions of the bounding box
 177:  * @param start the start angle, in degrees
 178:  * @param extent the arc extent, in degrees
 179:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
 180:  * @throws IllegalArgumentException if type is invalid
 181:  * @throws NullPointerException if p or d is null
 182:  */
 183:  public void setArc(Point2D p, Dimension2D d, double start, double extent,
 184:  int type)
 185:  {
 186:  setArc(p.getX(), p.getY(), d.getWidth(), d.getHeight(), start, extent, type);
 187:  }
 188: 
 189:  /**
 190:  * Set the parameters of the arc. The angles are in degrees, and a positive
 191:  * extent sweeps counterclockwise (from the positive x-axis to the negative
 192:  * y-axis).
 193:  *
 194:  * @param r the new bounding box
 195:  * @param start the start angle, in degrees
 196:  * @param extent the arc extent, in degrees
 197:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
 198:  * @throws IllegalArgumentException if type is invalid
 199:  * @throws NullPointerException if r is null
 200:  */
 201:  public void setArc(Rectangle2D r, double start, double extent, int type)
 202:  {
 203:  setArc(r.getX(), r.getY(), r.getWidth(), r.getHeight(), start, extent, type);
 204:  }
 205: 
 206:  /**
 207:  * Set the parameters of the arc from the given one.
 208:  *
 209:  * @param a the arc to copy
 210:  * @throws NullPointerException if a is null
 211:  */
 212:  public void setArc(Arc2D a)
 213:  {
 214:  setArc(a.getX(), a.getY(), a.getWidth(), a.getHeight(), a.getAngleStart(),
 215:  a.getAngleExtent(), a.getArcType());
 216:  }
 217: 
 218:  /**
 219:  * Set the parameters of the arc. The angles are in degrees, and a positive
 220:  * extent sweeps counterclockwise (from the positive x-axis to the negative
 221:  * y-axis). This controls the center point and radius, so the arc will be
 222:  * circular.
 223:  *
 224:  * @param x the x coordinate of the center of the circle
 225:  * @param y the y coordinate of the center of the circle
 226:  * @param r the radius of the circle
 227:  * @param start the start angle, in degrees
 228:  * @param extent the arc extent, in degrees
 229:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
 230:  * @throws IllegalArgumentException if type is invalid
 231:  */
 232:  public void setArcByCenter(double x, double y, double r, double start,
 233:  double extent, int type)
 234:  {
 235:  setArc(x - r, y - r, r + r, r + r, start, extent, type);
 236:  }
 237: 
 238:  /**
 239:  * Sets the parameters of the arc by finding the tangents of two lines, and
 240:  * using the specified radius. The arc will be circular, will begin on the
 241:  * tangent point of the line extending from p1 to p2, and will end on the
 242:  * tangent point of the line extending from p2 to p3.
 243:  *
 244:  * XXX What happens if the points are colinear, or the radius negative?
 245:  *
 246:  * @param p1 the first point
 247:  * @param p2 the tangent line intersection point
 248:  * @param p3 the third point
 249:  * @param r the radius of the arc
 250:  * @throws NullPointerException if any point is null
 251:  */
 252:  public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3, double r)
 253:  {
 254:  if ((p2.getX() - p1.getX()) * (p3.getY() - p1.getY())
 255:  - (p3.getX() - p1.getX()) * (p2.getY() - p1.getY()) > 0)
 256:  {
 257:  Point2D p = p3;
 258:  p3 = p1;
 259:  p1 = p;
 260:  }
 261: 
 262:  // normalized tangent vectors
 263:  double dx1 = (p1.getX() - p2.getX()) / p1.distance(p2);
 264:  double dy1 = (p1.getY() - p2.getY()) / p1.distance(p2);
 265:  double dx2 = (p2.getX() - p3.getX()) / p3.distance(p2);
 266:  double dy2 = (p2.getY() - p3.getY()) / p3.distance(p2);
 267:  double theta1 = Math.atan2(dx1, dy1);
 268:  double theta2 = Math.atan2(dx2, dy2);
 269: 
 270:  double dx = r * Math.cos(theta2) - r * Math.cos(theta1);
 271:  double dy = -r * Math.sin(theta2) + r * Math.sin(theta1);
 272: 
 273:  if (theta1 < 0)
 274:  theta1 += 2 * Math.PI;
 275:  if (theta2 < 0)
 276:  theta2 += 2 * Math.PI;
 277:  if (theta2 < theta1)
 278:  theta2 += 2 * Math.PI;
 279: 
 280:  // Vectors of the lines, not normalized, note we change 
 281:  // the direction of line 2.
 282:  dx1 = p1.getX() - p2.getX();
 283:  dy1 = p1.getY() - p2.getY();
 284:  dx2 = p3.getX() - p2.getX();
 285:  dy2 = p3.getY() - p2.getY();
 286: 
 287:  // Calculate the tangent point to the second line
 288:  double t2 = -(dx1 * dy - dy1 * dx) / (dx2 * dy1 - dx1 * dy2);
 289:  double x2 = t2 * (p3.getX() - p2.getX()) + p2.getX();
 290:  double y2 = t2 * (p3.getY() - p2.getY()) + p2.getY();
 291: 
 292:  // calculate the center point
 293:  double x = x2 - r * Math.cos(theta2);
 294:  double y = y2 + r * Math.sin(theta2);
 295: 
 296:  setArc(x - r, y - r, 2 * r, 2 * r, Math.toDegrees(theta1),
 297:  Math.toDegrees(theta2 - theta1), getArcType());
 298:  }
 299: 
 300:  /**
 301:  * Set the start, in degrees.
 302:  *
 303:  * @param start the new start angle
 304:  * @see #getAngleStart()
 305:  */
 306:  public abstract void setAngleStart(double start);
 307: 
 308:  /**
 309:  * Set the extent, in degrees.
 310:  *
 311:  * @param extent the new extent angle
 312:  * @see #getAngleExtent()
 313:  */
 314:  public abstract void setAngleExtent(double extent);
 315: 
 316:  /**
 317:  * Sets the starting angle to the angle of the given point relative to
 318:  * the center of the arc. The extent remains constant; in other words,
 319:  * this rotates the arc.
 320:  *
 321:  * @param p the new start point
 322:  * @throws NullPointerException if p is null
 323:  * @see #getStartPoint()
 324:  * @see #getAngleStart()
 325:  */
 326:  public void setAngleStart(Point2D p)
 327:  {
 328:  // Normalize.
 329:  double x = p.getX() - (getX() + getWidth() / 2);
 330:  double y = p.getY() - (getY() + getHeight() / 2);
 331:  setAngleStart(Math.toDegrees(Math.atan2(-y, x)));
 332:  }
 333: 
 334:  /**
 335:  * Sets the starting and extent angles to those of the given points
 336:  * relative to the center of the arc. The arc will be non-empty, and will
 337:  * extend counterclockwise.
 338:  *
 339:  * @param x1 the first x coordinate
 340:  * @param y1 the first y coordinate
 341:  * @param x2 the second x coordinate
 342:  * @param y2 the second y coordinate
 343:  * @see #setAngleStart(Point2D)
 344:  */
 345:  public void setAngles(double x1, double y1, double x2, double y2)
 346:  {
 347:  // Normalize the points.
 348:  double mx = getX();
 349:  double my = getY();
 350:  double mw = getWidth();
 351:  double mh = getHeight();
 352:  x1 = x1 - (mx + mw / 2);
 353:  y1 = y1 - (my + mh / 2);
 354:  x2 = x2 - (mx + mw / 2);
 355:  y2 = y2 - (my + mh / 2);
 356:  double start = Math.toDegrees(Math.atan2(-y1, x1));
 357:  double extent = Math.toDegrees(Math.atan2(-y2, x2)) - start;
 358:  if (extent < 0)
 359:  extent += 360;
 360:  setAngleStart(start);
 361:  setAngleExtent(extent);
 362:  }
 363: 
 364:  /**
 365:  * Sets the starting and extent angles to those of the given points
 366:  * relative to the center of the arc. The arc will be non-empty, and will
 367:  * extend counterclockwise.
 368:  *
 369:  * @param p1 the first point
 370:  * @param p2 the second point
 371:  * @throws NullPointerException if either point is null
 372:  * @see #setAngleStart(Point2D)
 373:  */
 374:  public void setAngles(Point2D p1, Point2D p2)
 375:  {
 376:  setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY());
 377:  }
 378: 
 379:  /**
 380:  * Set the closure type of this arc.
 381:  *
 382:  * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
 383:  * @throws IllegalArgumentException if type is invalid
 384:  * @see #getArcType()
 385:  */
 386:  public void setArcType(int type)
 387:  {
 388:  if (type < OPEN || type > PIE)
 389:  throw new IllegalArgumentException();
 390:  this.type = type;
 391:  }
 392: 
 393:  /**
 394:  * Sets the location and bounds of the ellipse of which this arc is a part.
 395:  *
 396:  * @param x the new x coordinate
 397:  * @param y the new y coordinate
 398:  * @param w the new width
 399:  * @param h the new height
 400:  * @see #getFrame()
 401:  */
 402:  public void setFrame(double x, double y, double w, double h)
 403:  {
 404:  setArc(x, y, w, h, getAngleStart(), getAngleExtent(), type);
 405:  }
 406: 
 407:  /**
 408:  * Gets the bounds of the arc. This is much tighter than
 409:  * <code>getBounds</code>, as it takes into consideration the start and
 410:  * end angles, and the center point of a pie wedge, rather than just the
 411:  * overall ellipse.
 412:  *
 413:  * @return the bounds of the arc
 414:  * @see #getBounds()
 415:  */
 416:  public Rectangle2D getBounds2D()
 417:  {
 418:  double extent = getAngleExtent();
 419:  if (Math.abs(extent) >= 360)
 420:  return makeBounds(getX(), getY(), getWidth(), getHeight());
 421: 
 422:  // Find the minimal bounding box. This determined by its extrema,
 423:  // which are the center, the endpoints of the arc, and any local
 424:  // maximum contained by the arc.
 425:  double rX = getWidth() / 2;
 426:  double rY = getHeight() / 2;
 427:  double centerX = getX() + rX;
 428:  double centerY = getY() + rY;
 429: 
 430:  Point2D p1 = getStartPoint();
 431:  Rectangle2D result = makeBounds(p1.getX(), p1.getY(), 0, 0);
 432:  result.add(getEndPoint());
 433: 
 434:  if (type == PIE)
 435:  result.add(centerX, centerY);
 436:  if (containsAngle(0))
 437:  result.add(centerX + rX, centerY);
 438:  if (containsAngle(90))
 439:  result.add(centerX, centerY - rY);
 440:  if (containsAngle(180))
 441:  result.add(centerX - rX, centerY);
 442:  if (containsAngle(270))
 443:  result.add(centerX, centerY + rY);
 444: 
 445:  return result;
 446:  }
 447: 
 448:  /**
 449:  * Construct a bounding box in a precision appropriate for the subclass.
 450:  *
 451:  * @param x the x coordinate
 452:  * @param y the y coordinate
 453:  * @param w the width
 454:  * @param h the height
 455:  * @return the rectangle for use in getBounds2D
 456:  */
 457:  protected abstract Rectangle2D makeBounds(double x, double y, double w,
 458:  double h);
 459: 
 460:  /**
 461:  * Tests if the given angle, in degrees, is included in the arc.
 462:  * All angles are normalized to be between 0 and 360 degrees.
 463:  *
 464:  * @param a the angle to test
 465:  * @return true if it is contained
 466:  */
 467:  public boolean containsAngle(double a)
 468:  {
 469:  double start = getAngleStart();
 470:  double extent = getAngleExtent();
 471:  double end = start + extent;
 472: 
 473:  if (extent == 0)
 474:  return false;
 475: 
 476:  if (extent >= 360 || extent <= -360)
 477:  return true;
 478: 
 479:  if (extent < 0)
 480:  {
 481:  end = start;
 482:  start += extent;
 483:  }
 484: 
 485:  start %= 360;
 486:  while (start < 0)
 487:  start += 360;
 488: 
 489:  end %= 360;
 490:  while (end < start)
 491:  end += 360;
 492: 
 493:  a %= 360;
 494:  while (a < start)
 495:  a += 360;
 496: 
 497:  return a >= start && a < end; // starting angle included, ending angle not
 498:  }
 499: 
 500:  /**
 501:  * Determines if the arc contains the given point. If the bounding box
 502:  * is empty, then this will return false.
 503:  *
 504:  * The area considered 'inside' an arc of type OPEN is the same as the
 505:  * area inside an equivalent filled CHORD-type arc. The area considered
 506:  * 'inside' a CHORD-type arc is the same as the filled area.
 507:  *
 508:  * @param x the x coordinate to test
 509:  * @param y the y coordinate to test
 510:  * @return true if the point is inside the arc
 511:  */
 512:  public boolean contains(double x, double y)
 513:  {
 514:  double w = getWidth();
 515:  double h = getHeight();
 516:  double extent = getAngleExtent();
 517:  if (w <= 0 || h <= 0 || extent == 0)
 518:  return false;
 519: 
 520:  double mx = getX() + w / 2;
 521:  double my = getY() + h / 2;
 522:  double dx = (x - mx) * 2 / w;
 523:  double dy = (y - my) * 2 / h;
 524:  if ((dx * dx + dy * dy) >= 1.0)
 525:  return false;
 526: 
 527:  double angle = Math.toDegrees(Math.atan2(-dy, dx));
 528:  if (getArcType() == PIE)
 529:  return containsAngle(angle);
 530: 
 531:  double a1 = Math.toRadians(getAngleStart());
 532:  double a2 = Math.toRadians(getAngleStart() + extent);
 533:  double x1 = mx + getWidth() * Math.cos(a1) / 2;
 534:  double y1 = my - getHeight() * Math.sin(a1) / 2;
 535:  double x2 = mx + getWidth() * Math.cos(a2) / 2;
 536:  double y2 = my - getHeight() * Math.sin(a2) / 2;
 537:  double sgn = ((x2 - x1) * (my - y1) - (mx - x1) * (y2 - y1)) * ((x2 - x1) * (y
 538:  - y1) - (x - x1) * (y2 - y1));
 539: 
 540:  if (Math.abs(extent) > 180)
 541:  {
 542:  if (containsAngle(angle))
 543:  return true;
 544:  return sgn > 0;
 545:  }
 546:  else
 547:  {
 548:  if (! containsAngle(angle))
 549:  return false;
 550:  return sgn < 0;
 551:  }
 552:  }
 553: 
 554:  /**
 555:  * Tests if a given rectangle intersects the area of the arc.
 556:  *
 557:  * For a definition of the 'inside' area, see the contains() method.
 558:  * @see #contains(double, double)
 559:  *
 560:  * @param x the x coordinate of the rectangle
 561:  * @param y the y coordinate of the rectangle
 562:  * @param w the width of the rectangle
 563:  * @param h the height of the rectangle
 564:  * @return true if the two shapes share common points
 565:  */
 566:  public boolean intersects(double x, double y, double w, double h)
 567:  {
 568:  double extent = getAngleExtent();
 569:  if (extent == 0)
 570:  return false;
 571: 
 572:  if (contains(x, y) || contains(x, y + h) || contains(x + w, y)
 573:  || contains(x + w, y + h))
 574:  return true;
 575: 
 576:  Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
 577: 
 578:  double a = getWidth() / 2.0;
 579:  double b = getHeight() / 2.0;
 580: 
 581:  double mx = getX() + a;
 582:  double my = getY() + b;
 583:  double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart()));
 584:  double y1 = my - b * Math.sin(Math.toRadians(getAngleStart()));
 585:  double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent));
 586:  double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent));
 587: 
 588:  if (getArcType() != CHORD)
 589:  {
 590:  // check intersections against the pie radii
 591:  if (rect.intersectsLine(mx, my, x1, y1))
 592:  return true;
 593:  if (rect.intersectsLine(mx, my, x2, y2))
 594:  return true;
 595:  }
 596:  else// check the chord
 597:  if (rect.intersectsLine(x1, y1, x2, y2))
 598:  return true;
 599: 
 600:  // Check the Arc segment against the four edges
 601:  double dx;
 602: 
 603:  // Check the Arc segment against the four edges
 604:  double dy;
 605:  dy = y - my;
 606:  dx = a * Math.sqrt(1 - ((dy * dy) / (b * b)));
 607:  if (! java.lang.Double.isNaN(dx))
 608:  {
 609:  if (mx + dx >= x && mx + dx <= x + w
 610:  && containsAngle(Math.toDegrees(Math.atan2(-dy, dx))))
 611:  return true;
 612:  if (mx - dx >= x && mx - dx <= x + w
 613:  && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx))))
 614:  return true;
 615:  }
 616:  dy = (y + h) - my;
 617:  dx = a * Math.sqrt(1 - ((dy * dy) / (b * b)));
 618:  if (! java.lang.Double.isNaN(dx))
 619:  {
 620:  if (mx + dx >= x && mx + dx <= x + w
 621:  && containsAngle(Math.toDegrees(Math.atan2(-dy, dx))))
 622:  return true;
 623:  if (mx - dx >= x && mx - dx <= x + w
 624:  && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx))))
 625:  return true;
 626:  }
 627:  dx = x - mx;
 628:  dy = b * Math.sqrt(1 - ((dx * dx) / (a * a)));
 629:  if (! java.lang.Double.isNaN(dy))
 630:  {
 631:  if (my + dy >= y && my + dy <= y + h
 632:  && containsAngle(Math.toDegrees(Math.atan2(-dy, dx))))
 633:  return true;
 634:  if (my - dy >= y && my - dy <= y + h
 635:  && containsAngle(Math.toDegrees(Math.atan2(dy, dx))))
 636:  return true;
 637:  }
 638: 
 639:  dx = (x + w) - mx;
 640:  dy = b * Math.sqrt(1 - ((dx * dx) / (a * a)));
 641:  if (! java.lang.Double.isNaN(dy))
 642:  {
 643:  if (my + dy >= y && my + dy <= y + h
 644:  && containsAngle(Math.toDegrees(Math.atan2(-dy, dx))))
 645:  return true;
 646:  if (my - dy >= y && my - dy <= y + h
 647:  && containsAngle(Math.toDegrees(Math.atan2(dy, dx))))
 648:  return true;
 649:  }
 650: 
 651:  // Check whether the arc is contained within the box
 652:  if (rect.contains(mx, my))
 653:  return true;
 654: 
 655:  return false;
 656:  }
 657: 
 658:  /**
 659:  * Tests if a given rectangle is contained in the area of the arc.
 660:  *
 661:  * @param x the x coordinate of the rectangle
 662:  * @param y the y coordinate of the rectangle
 663:  * @param w the width of the rectangle
 664:  * @param h the height of the rectangle
 665:  * @return true if the arc contains the rectangle
 666:  */
 667:  public boolean contains(double x, double y, double w, double h)
 668:  {
 669:  double extent = getAngleExtent();
 670:  if (extent == 0)
 671:  return false;
 672: 
 673:  if (! (contains(x, y) && contains(x, y + h) && contains(x + w, y)
 674:  && contains(x + w, y + h)))
 675:  return false;
 676: 
 677:  Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
 678: 
 679:  double a = getWidth() / 2.0;
 680:  double b = getHeight() / 2.0;
 681: 
 682:  double mx = getX() + a;
 683:  double my = getY() + b;
 684:  double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart()));
 685:  double y1 = my - b * Math.sin(Math.toRadians(getAngleStart()));
 686:  double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent));
 687:  double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent));
 688:  if (getArcType() != CHORD)
 689:  {
 690:  // check intersections against the pie radii
 691:  if (rect.intersectsLine(mx, my, x1, y1))
 692:  return false;
 693: 
 694:  if (rect.intersectsLine(mx, my, x2, y2))
 695:  return false;
 696:  }
 697:  else if (rect.intersectsLine(x1, y1, x2, y2))
 698:  return false;
 699:  return true;
 700:  }
 701: 
 702:  /**
 703:  * Tests if a given rectangle is contained in the area of the arc.
 704:  *
 705:  * @param r the rectangle
 706:  * @return true if the arc contains the rectangle
 707:  */
 708:  public boolean contains(Rectangle2D r)
 709:  {
 710:  return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
 711:  }
 712: 
 713:  /**
 714:  * Returns an iterator over this arc, with an optional transformation.
 715:  * This iterator is threadsafe, so future modifications to the arc do not
 716:  * affect the iteration.
 717:  *
 718:  * @param at the transformation, or null
 719:  * @return a path iterator
 720:  */
 721:  public PathIterator getPathIterator(AffineTransform at)
 722:  {
 723:  return new ArcIterator(this, at);
 724:  }
 725: 
 726:  /**
 727:  * This class is used to iterate over an arc. Since ellipses are a subclass
 728:  * of arcs, this is used by Ellipse2D as well.
 729:  *
 730:  * @author Eric Blake (ebb9@email.byu.edu)
 731:  */
 732:  static final class ArcIterator implements PathIterator
 733:  {
 734:  /** The current iteration. */
 735:  private int current;
 736: 
 737:  /** The last iteration. */
 738:  private final int limit;
 739: 
 740:  /** The optional transformation. */
 741:  private final AffineTransform xform;
 742: 
 743:  /** The x coordinate of the bounding box. */
 744:  private final double x;
 745: 
 746:  /** The y coordinate of the bounding box. */
 747:  private final double y;
 748: 
 749:  /** The width of the bounding box. */
 750:  private final double w;
 751: 
 752:  /** The height of the bounding box. */
 753:  private final double h;
 754: 
 755:  /** The start angle, in radians (not degrees). */
 756:  private final double start;
 757: 
 758:  /** The extent angle, in radians (not degrees). */
 759:  private final double extent;
 760: 
 761:  /** The arc closure type. */
 762:  private final int type;
 763: 
 764:  /**
 765:  * Construct a new iterator over an arc.
 766:  *
 767:  * @param a the arc
 768:  * @param xform the transform
 769:  */
 770:  public ArcIterator(Arc2D a, AffineTransform xform)
 771:  {
 772:  this.xform = xform;
 773:  x = a.getX();
 774:  y = a.getY();
 775:  w = a.getWidth();
 776:  h = a.getHeight();
 777:  double start = Math.toRadians(a.getAngleStart());
 778:  double extent = Math.toRadians(a.getAngleExtent());
 779: 
 780:  this.start = start;
 781:  this.extent = extent;
 782: 
 783:  type = a.type;
 784:  if (w < 0 || h < 0)
 785:  limit = -1;
 786:  else if (extent == 0)
 787:  limit = type;
 788:  else if (Math.abs(extent) <= Math.PI / 2.0)
 789:  limit = type + 1;
 790:  else if (Math.abs(extent) <= Math.PI)
 791:  limit = type + 2;
 792:  else if (Math.abs(extent) <= 3.0 * (Math.PI / 2.0))
 793:  limit = type + 3;
 794:  else
 795:  limit = type + 4;
 796:  }
 797: 
 798:  /**
 799:  * Construct a new iterator over an ellipse.
 800:  *
 801:  * @param e the ellipse
 802:  * @param xform the transform
 803:  */
 804:  public ArcIterator(Ellipse2D e, AffineTransform xform)
 805:  {
 806:  this.xform = xform;
 807:  x = e.getX();
 808:  y = e.getY();
 809:  w = e.getWidth();
 810:  h = e.getHeight();
 811:  start = 0;
 812:  extent = 2 * Math.PI;
 813:  type = CHORD;
 814:  limit = (w < 0 || h < 0) ? -1 : 5;
 815:  }
 816: 
 817:  /**
 818:  * Return the winding rule.
 819:  *
 820:  * @return {@link PathIterator#WIND_NON_ZERO}
 821:  */
 822:  public int getWindingRule()
 823:  {
 824:  return WIND_NON_ZERO;
 825:  }
 826: 
 827:  /**
 828:  * Test if the iteration is complete.
 829:  *
 830:  * @return true if more segments exist
 831:  */
 832:  public boolean isDone()
 833:  {
 834:  return current > limit;
 835:  }
 836: 
 837:  /**
 838:  * Advance the iterator.
 839:  */
 840:  public void next()
 841:  {
 842:  current++;
 843:  }
 844: 
 845:  /**
 846:  * Put the current segment into the array, and return the segment type.
 847:  *
 848:  * @param coords an array of 6 elements
 849:  * @return the segment type
 850:  * @throws NullPointerException if coords is null
 851:  * @throws ArrayIndexOutOfBoundsException if coords is too small
 852:  */
 853:  public int currentSegment(float[] coords)
 854:  {
 855:  double[] double_coords = new double[6];
 856:  int code = currentSegment(double_coords);
 857:  for (int i = 0; i < 6; ++i)
 858:  coords[i] = (float) double_coords[i];
 859:  return code;
 860:  }
 861: 
 862:  /**
 863:  * Put the current segment into the array, and return the segment type.
 864:  *
 865:  * @param coords an array of 6 elements
 866:  * @return the segment type
 867:  * @throws NullPointerException if coords is null
 868:  * @throws ArrayIndexOutOfBoundsException if coords is too small
 869:  */
 870:  public int currentSegment(double[] coords)
 871:  {
 872:  double rx = w / 2;
 873:  double ry = h / 2;
 874:  double xmid = x + rx;
 875:  double ymid = y + ry;
 876: 
 877:  if (current > limit)
 878:  throw new NoSuchElementException("arc iterator out of bounds");
 879: 
 880:  if (current == 0)
 881:  {
 882:  coords[0] = xmid + rx * Math.cos(start);
 883:  coords[1] = ymid - ry * Math.sin(start);
 884:  if (xform != null)
 885:  xform.transform(coords, 0, coords, 0, 1);
 886:  return SEG_MOVETO;
 887:  }
 888: 
 889:  if (type != OPEN && current == limit)
 890:  return SEG_CLOSE;
 891: 
 892:  if ((current == limit - 1) && (type == PIE))
 893:  {
 894:  coords[0] = xmid;
 895:  coords[1] = ymid;
 896:  if (xform != null)
 897:  xform.transform(coords, 0, coords, 0, 1);
 898:  return SEG_LINETO;
 899:  }
 900: 
 901:  // note that this produces a cubic approximation of the arc segment,
 902:  // not a true ellipsoid. there's no ellipsoid path segment code,
 903:  // unfortunately. the cubic approximation looks about right, though.
 904:  double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0);
 905:  double quad = (Math.PI / 2.0);
 906: 
 907:  double curr_begin;
 908:  double curr_extent;
 909:  if (extent > 0)
 910:  {
 911:  curr_begin = start + (current - 1) * quad;
 912:  curr_extent = Math.min((start + extent) - curr_begin, quad);
 913:  }
 914:  else
 915:  {
 916:  curr_begin = start - (current - 1) * quad;
 917:  curr_extent = Math.max((start + extent) - curr_begin, -quad);
 918:  }
 919:  
 920:  double portion_of_a_quadrant = Math.abs(curr_extent / quad);
 921: 
 922:  double x0 = xmid + rx * Math.cos(curr_begin);
 923:  double y0 = ymid - ry * Math.sin(curr_begin);
 924: 
 925:  double x1 = xmid + rx * Math.cos(curr_begin + curr_extent);
 926:  double y1 = ymid - ry * Math.sin(curr_begin + curr_extent);
 927: 
 928:  AffineTransform trans = new AffineTransform();
 929:  double[] cvec = new double[2];
 930:  double len = kappa * portion_of_a_quadrant;
 931:  double angle = curr_begin;
 932: 
 933:  // in a hypothetical "first quadrant" setting, our first control
 934:  // vector would be sticking up, from [1,0] to [1,kappa].
 935:  //
 936:  // let us recall however that in java2d, y coords are upside down
 937:  // from what one would consider "normal" first quadrant rules, so we
 938:  // will *subtract* the y value of this control vector from our first
 939:  // point.
 940:  cvec[0] = 0;
 941:  if (extent > 0)
 942:  cvec[1] = len;
 943:  else
 944:  cvec[1] = -len;
 945:  
 946:  trans.scale(rx, ry);
 947:  trans.rotate(angle);
 948:  trans.transform(cvec, 0, cvec, 0, 1);
 949:  coords[0] = x0 + cvec[0];
 950:  coords[1] = y0 - cvec[1];
 951: 
 952:  // control vector #2 would, ideally, be sticking out and to the
 953:  // right, in a first quadrant arc segment. again, subtraction of y.
 954:  cvec[0] = 0;
 955:  if (extent > 0)
 956:  cvec[1] = -len;
 957:  else
 958:  cvec[1] = len;
 959:  
 960:  trans.rotate(curr_extent);
 961:  trans.transform(cvec, 0, cvec, 0, 1);
 962:  coords[2] = x1 + cvec[0];
 963:  coords[3] = y1 - cvec[1];
 964: 
 965:  // end point
 966:  coords[4] = x1;
 967:  coords[5] = y1;
 968: 
 969:  if (xform != null)
 970:  xform.transform(coords, 0, coords, 0, 3);
 971: 
 972:  return SEG_CUBICTO;
 973:  }
 974:  } // class ArcIterator
 975: 
 976:  /**
 977:  * This class implements an arc in double precision.
 978:  *
 979:  * @author Eric Blake (ebb9@email.byu.edu)
 980:  * @since 1.2
 981:  */
 982:  public static class Double extends Arc2D
 983:  {
 984:  /** The x coordinate of the box bounding the ellipse of this arc. */
 985:  public double x;
 986: 
 987:  /** The y coordinate of the box bounding the ellipse of this arc. */
 988:  public double y;
 989: 
 990:  /** The width of the box bounding the ellipse of this arc. */
 991:  public double width;
 992: 
 993:  /** The height of the box bounding the ellipse of this arc. */
 994:  public double height;
 995: 
 996:  /** The start angle of this arc, in degrees. */
 997:  public double start;
 998: 
 999:  /** The extent angle of this arc, in degrees. */
1000:  public double extent;
1001: 
1002:  /**
1003:  * Create a new, open arc at (0,0) with 0 extent.
1004:  */
1005:  public Double()
1006:  {
1007:  super(OPEN);
1008:  }
1009: 
1010:  /**
1011:  * Create a new arc of the given type at (0,0) with 0 extent.
1012:  *
1013:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1014:  * @throws IllegalArgumentException if type is invalid
1015:  */
1016:  public Double(int type)
1017:  {
1018:  super(type);
1019:  }
1020: 
1021:  /**
1022:  * Create a new arc with the given dimensions.
1023:  *
1024:  * @param x the x coordinate
1025:  * @param y the y coordinate
1026:  * @param w the width
1027:  * @param h the height
1028:  * @param start the start angle, in degrees
1029:  * @param extent the extent, in degrees
1030:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1031:  * @throws IllegalArgumentException if type is invalid
1032:  */
1033:  public Double(double x, double y, double w, double h, double start,
1034:  double extent, int type)
1035:  {
1036:  super(type);
1037:  this.x = x;
1038:  this.y = y;
1039:  width = w;
1040:  height = h;
1041:  this.start = start;
1042:  this.extent = extent;
1043:  }
1044: 
1045:  /**
1046:  * Create a new arc with the given dimensions.
1047:  *
1048:  * @param r the bounding box
1049:  * @param start the start angle, in degrees
1050:  * @param extent the extent, in degrees
1051:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1052:  * @throws IllegalArgumentException if type is invalid
1053:  * @throws NullPointerException if r is null
1054:  */
1055:  public Double(Rectangle2D r, double start, double extent, int type)
1056:  {
1057:  super(type);
1058:  x = r.getX();
1059:  y = r.getY();
1060:  width = r.getWidth();
1061:  height = r.getHeight();
1062:  this.start = start;
1063:  this.extent = extent;
1064:  }
1065: 
1066:  /**
1067:  * Return the x coordinate of the bounding box.
1068:  *
1069:  * @return the value of x
1070:  */
1071:  public double getX()
1072:  {
1073:  return x;
1074:  }
1075: 
1076:  /**
1077:  * Return the y coordinate of the bounding box.
1078:  *
1079:  * @return the value of y
1080:  */
1081:  public double getY()
1082:  {
1083:  return y;
1084:  }
1085: 
1086:  /**
1087:  * Return the width of the bounding box.
1088:  *
1089:  * @return the value of width
1090:  */
1091:  public double getWidth()
1092:  {
1093:  return width;
1094:  }
1095: 
1096:  /**
1097:  * Return the height of the bounding box.
1098:  *
1099:  * @return the value of height
1100:  */
1101:  public double getHeight()
1102:  {
1103:  return height;
1104:  }
1105: 
1106:  /**
1107:  * Return the start angle of the arc, in degrees.
1108:  *
1109:  * @return the value of start
1110:  */
1111:  public double getAngleStart()
1112:  {
1113:  return start;
1114:  }
1115: 
1116:  /**
1117:  * Return the extent of the arc, in degrees.
1118:  *
1119:  * @return the value of extent
1120:  */
1121:  public double getAngleExtent()
1122:  {
1123:  return extent;
1124:  }
1125: 
1126:  /**
1127:  * Tests if the arc contains points.
1128:  *
1129:  * @return true if the arc has no interior
1130:  */
1131:  public boolean isEmpty()
1132:  {
1133:  return width <= 0 || height <= 0;
1134:  }
1135: 
1136:  /**
1137:  * Sets the arc to the given dimensions.
1138:  *
1139:  * @param x the x coordinate
1140:  * @param y the y coordinate
1141:  * @param w the width
1142:  * @param h the height
1143:  * @param start the start angle, in degrees
1144:  * @param extent the extent, in degrees
1145:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1146:  * @throws IllegalArgumentException if type is invalid
1147:  */
1148:  public void setArc(double x, double y, double w, double h, double start,
1149:  double extent, int type)
1150:  {
1151:  this.x = x;
1152:  this.y = y;
1153:  width = w;
1154:  height = h;
1155:  this.start = start;
1156:  this.extent = extent;
1157:  setArcType(type);
1158:  }
1159: 
1160:  /**
1161:  * Sets the start angle of the arc.
1162:  *
1163:  * @param start the new start angle
1164:  */
1165:  public void setAngleStart(double start)
1166:  {
1167:  this.start = start;
1168:  }
1169: 
1170:  /**
1171:  * Sets the extent angle of the arc.
1172:  *
1173:  * @param extent the new extent angle
1174:  */
1175:  public void setAngleExtent(double extent)
1176:  {
1177:  this.extent = extent;
1178:  }
1179: 
1180:  /**
1181:  * Creates a tight bounding box given dimensions that more precise than
1182:  * the bounding box of the ellipse.
1183:  *
1184:  * @param x the x coordinate
1185:  * @param y the y coordinate
1186:  * @param w the width
1187:  * @param h the height
1188:  */
1189:  protected Rectangle2D makeBounds(double x, double y, double w, double h)
1190:  {
1191:  return new Rectangle2D.Double(x, y, w, h);
1192:  }
1193:  } // class Double
1194: 
1195:  /**
1196:  * This class implements an arc in float precision.
1197:  *
1198:  * @author Eric Blake (ebb9@email.byu.edu)
1199:  * @since 1.2
1200:  */
1201:  public static class Float extends Arc2D
1202:  {
1203:  /** The x coordinate of the box bounding the ellipse of this arc. */
1204:  public float x;
1205: 
1206:  /** The y coordinate of the box bounding the ellipse of this arc. */
1207:  public float y;
1208: 
1209:  /** The width of the box bounding the ellipse of this arc. */
1210:  public float width;
1211: 
1212:  /** The height of the box bounding the ellipse of this arc. */
1213:  public float height;
1214: 
1215:  /** The start angle of this arc, in degrees. */
1216:  public float start;
1217: 
1218:  /** The extent angle of this arc, in degrees. */
1219:  public float extent;
1220: 
1221:  /**
1222:  * Create a new, open arc at (0,0) with 0 extent.
1223:  */
1224:  public Float()
1225:  {
1226:  super(OPEN);
1227:  }
1228: 
1229:  /**
1230:  * Create a new arc of the given type at (0,0) with 0 extent.
1231:  *
1232:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1233:  * @throws IllegalArgumentException if type is invalid
1234:  */
1235:  public Float(int type)
1236:  {
1237:  super(type);
1238:  }
1239: 
1240:  /**
1241:  * Create a new arc with the given dimensions.
1242:  *
1243:  * @param x the x coordinate
1244:  * @param y the y coordinate
1245:  * @param w the width
1246:  * @param h the height
1247:  * @param start the start angle, in degrees
1248:  * @param extent the extent, in degrees
1249:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1250:  * @throws IllegalArgumentException if type is invalid
1251:  */
1252:  public Float(float x, float y, float w, float h, float start,
1253:  float extent, int type)
1254:  {
1255:  super(type);
1256:  this.x = x;
1257:  this.y = y;
1258:  width = w;
1259:  height = h;
1260:  this.start = start;
1261:  this.extent = extent;
1262:  }
1263: 
1264:  /**
1265:  * Create a new arc with the given dimensions.
1266:  *
1267:  * @param r the bounding box
1268:  * @param start the start angle, in degrees
1269:  * @param extent the extent, in degrees
1270:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1271:  * @throws IllegalArgumentException if type is invalid
1272:  * @throws NullPointerException if r is null
1273:  */
1274:  public Float(Rectangle2D r, float start, float extent, int type)
1275:  {
1276:  super(type);
1277:  x = (float) r.getX();
1278:  y = (float) r.getY();
1279:  width = (float) r.getWidth();
1280:  height = (float) r.getHeight();
1281:  this.start = start;
1282:  this.extent = (float) extent;
1283:  }
1284: 
1285:  /**
1286:  * Return the x coordinate of the bounding box.
1287:  *
1288:  * @return the value of x
1289:  */
1290:  public double getX()
1291:  {
1292:  return x;
1293:  }
1294: 
1295:  /**
1296:  * Return the y coordinate of the bounding box.
1297:  *
1298:  * @return the value of y
1299:  */
1300:  public double getY()
1301:  {
1302:  return y;
1303:  }
1304: 
1305:  /**
1306:  * Return the width of the bounding box.
1307:  *
1308:  * @return the value of width
1309:  */
1310:  public double getWidth()
1311:  {
1312:  return width;
1313:  }
1314: 
1315:  /**
1316:  * Return the height of the bounding box.
1317:  *
1318:  * @return the value of height
1319:  */
1320:  public double getHeight()
1321:  {
1322:  return height;
1323:  }
1324: 
1325:  /**
1326:  * Return the start angle of the arc, in degrees.
1327:  *
1328:  * @return the value of start
1329:  */
1330:  public double getAngleStart()
1331:  {
1332:  return start;
1333:  }
1334: 
1335:  /**
1336:  * Return the extent of the arc, in degrees.
1337:  *
1338:  * @return the value of extent
1339:  */
1340:  public double getAngleExtent()
1341:  {
1342:  return extent;
1343:  }
1344: 
1345:  /**
1346:  * Tests if the arc contains points.
1347:  *
1348:  * @return true if the arc has no interior
1349:  */
1350:  public boolean isEmpty()
1351:  {
1352:  return width <= 0 || height <= 0;
1353:  }
1354: 
1355:  /**
1356:  * Sets the arc to the given dimensions.
1357:  *
1358:  * @param x the x coordinate
1359:  * @param y the y coordinate
1360:  * @param w the width
1361:  * @param h the height
1362:  * @param start the start angle, in degrees
1363:  * @param extent the extent, in degrees
1364:  * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1365:  * @throws IllegalArgumentException if type is invalid
1366:  */
1367:  public void setArc(double x, double y, double w, double h, double start,
1368:  double extent, int type)
1369:  {
1370:  this.x = (float) x;
1371:  this.y = (float) y;
1372:  width = (float) w;
1373:  height = (float) h;
1374:  this.start = (float) start;
1375:  this.extent = (float) extent;
1376:  setArcType(type);
1377:  }
1378: 
1379:  /**
1380:  * Sets the start angle of the arc.
1381:  *
1382:  * @param start the new start angle
1383:  */
1384:  public void setAngleStart(double start)
1385:  {
1386:  this.start = (float) start;
1387:  }
1388: 
1389:  /**
1390:  * Sets the extent angle of the arc.
1391:  *
1392:  * @param extent the new extent angle
1393:  */
1394:  public void setAngleExtent(double extent)
1395:  {
1396:  this.extent = (float) extent;
1397:  }
1398: 
1399:  /**
1400:  * Creates a tight bounding box given dimensions that more precise than
1401:  * the bounding box of the ellipse.
1402:  *
1403:  * @param x the x coordinate
1404:  * @param y the y coordinate
1405:  * @param w the width
1406:  * @param h the height
1407:  */
1408:  protected Rectangle2D makeBounds(double x, double y, double w, double h)
1409:  {
1410:  return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h);
1411:  }
1412:  } // class Float
1413: } // class Arc2D
Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)

AltStyle によって変換されたページ (->オリジナル) /