1. /*
  2. * @(#)GeneralPath.java 1.51 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.geom;
  8. import java.awt.Shape;
  9. import sun.awt.geom.Curve;
  10. import sun.awt.geom.Crossings;
  11. /**
  12. * The <code>GeneralPath</code> class represents a geometric path
  13. * constructed from straight lines, and quadratic and cubic
  14. * (Bézier) curves. It can contain multiple subpaths.
  15. * <p>
  16. * The winding rule specifies how the interior of a path is
  17. * determined. There are two types of winding rules:
  18. * EVEN_ODD and NON_ZERO.
  19. * <p>
  20. * An EVEN_ODD winding rule means that enclosed regions
  21. * of the path alternate between interior and exterior areas as
  22. * traversed from the outside of the path towards a point inside
  23. * the region.
  24. * <p>
  25. * A NON_ZERO winding rule means that if a ray is
  26. * drawn in any direction from a given point to infinity
  27. * and the places where the path intersects
  28. * the ray are examined, the point is inside of the path if and only if
  29. * the number of times that the path crosses the ray from
  30. * left to right does not equal the number of times that the path crosses
  31. * the ray from right to left.
  32. * @version 10 Feb 1997
  33. * @author Jim Graham
  34. */
  35. public final class GeneralPath implements Shape, Cloneable {
  36. /**
  37. * An even-odd winding rule for determining the interior of
  38. * a path.
  39. */
  40. public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
  41. /**
  42. * A non-zero winding rule for determining the interior of a
  43. * path.
  44. */
  45. public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
  46. // For code simplicity, copy these constants to our namespace
  47. // and cast them to byte constants for easy storage.
  48. private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO;
  49. private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO;
  50. private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO;
  51. private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO;
  52. private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE;
  53. byte[] pointTypes;
  54. float[] pointCoords;
  55. int numTypes;
  56. int numCoords;
  57. int windingRule;
  58. static final int INIT_SIZE = 20;
  59. static final int EXPAND_MAX = 500;
  60. /**
  61. * Constructs a new <code>GeneralPath</code> object.
  62. * If an operation performed on this path requires the
  63. * interior of the path to be defined then the default NON_ZERO
  64. * winding rule is used.
  65. * @see #WIND_NON_ZERO
  66. */
  67. public GeneralPath() {
  68. this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
  69. }
  70. /**
  71. * Constructs a new <code>GeneralPath</code> object with the specified
  72. * winding rule to control operations that require the interior of the
  73. * path to be defined.
  74. * @param rule the winding rule
  75. * @see #WIND_EVEN_ODD
  76. * @see #WIND_NON_ZERO
  77. */
  78. public GeneralPath(int rule) {
  79. this(rule, INIT_SIZE, INIT_SIZE);
  80. }
  81. /**
  82. * Constructs a new <code>GeneralPath</code> object with the specified
  83. * winding rule and the specified initial capacity to store path
  84. * coordinates. This number is an initial guess as to how many path
  85. * segments are in the path, but the storage is expanded
  86. * as needed to store whatever path segments are added to this path.
  87. * @param rule the winding rule
  88. * @param initialCapacity the estimate for the number of path segments
  89. * in the path
  90. * @see #WIND_EVEN_ODD
  91. * @see #WIND_NON_ZERO
  92. */
  93. public GeneralPath(int rule, int initialCapacity) {
  94. this(rule, initialCapacity, initialCapacity);
  95. }
  96. /**
  97. * Constructs a new <code>GeneralPath</code> object with the specified
  98. * winding rule and the specified initial capacities to store point types
  99. * and coordinates.
  100. * These numbers are an initial guess as to how many path segments
  101. * and how many points are to be in the path, but the
  102. * storage is expanded as needed to store whatever path segments are
  103. * added to this path.
  104. * @param rule the winding rule
  105. * @param initialTypes the estimate for the number of path segments
  106. * in the path
  107. * @param initialCapacity the estimate for the number of points
  108. * @see #WIND_EVEN_ODD
  109. * @see #WIND_NON_ZERO
  110. */
  111. GeneralPath(int rule, int initialTypes, int initialCoords) {
  112. setWindingRule(rule);
  113. pointTypes = new byte[initialTypes];
  114. pointCoords = new float[initialCoords * 2];
  115. }
  116. /**
  117. * Constructs a new <code>GeneralPath</code> object from an arbitrary
  118. * {@link Shape} object.
  119. * All of the initial geometry and the winding rule for this path are
  120. * taken from the specified <code>Shape</code> object.
  121. * @param s the specified <code>Shape</code> object
  122. */
  123. public GeneralPath(Shape s) {
  124. this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
  125. PathIterator pi = s.getPathIterator(null);
  126. setWindingRule(pi.getWindingRule());
  127. append(pi, false);
  128. }
  129. private void needRoom(int newTypes, int newCoords, boolean needMove) {
  130. if (needMove && numTypes == 0) {
  131. throw new IllegalPathStateException("missing initial moveto "+
  132. "in path definition");
  133. }
  134. int size = pointCoords.length;
  135. if (numCoords + newCoords > size) {
  136. int grow = size;
  137. if (grow > EXPAND_MAX * 2) {
  138. grow = EXPAND_MAX * 2;
  139. }
  140. if (grow < newCoords) {
  141. grow = newCoords;
  142. }
  143. float[] arr = new float[size + grow];
  144. System.arraycopy(pointCoords, 0, arr, 0, numCoords);
  145. pointCoords = arr;
  146. }
  147. size = pointTypes.length;
  148. if (numTypes + newTypes > size) {
  149. int grow = size;
  150. if (grow > EXPAND_MAX) {
  151. grow = EXPAND_MAX;
  152. }
  153. if (grow < newTypes) {
  154. grow = newTypes;
  155. }
  156. byte[] arr = new byte[size + grow];
  157. System.arraycopy(pointTypes, 0, arr, 0, numTypes);
  158. pointTypes = arr;
  159. }
  160. }
  161. /**
  162. * Adds a point to the path by moving to the specified
  163. * coordinates.
  164. * @param x, y the specified coordinates
  165. */
  166. public synchronized void moveTo(float x, float y) {
  167. if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
  168. pointCoords[numCoords - 2] = x;
  169. pointCoords[numCoords - 1] = y;
  170. } else {
  171. needRoom(1, 2, false);
  172. pointTypes[numTypes++] = SEG_MOVETO;
  173. pointCoords[numCoords++] = x;
  174. pointCoords[numCoords++] = y;
  175. }
  176. }
  177. /**
  178. * Adds a point to the path by drawing a straight line from the
  179. * current coordinates to the new specified coordinates.
  180. * @param x, y the specified coordinates
  181. */
  182. public synchronized void lineTo(float x, float y) {
  183. needRoom(1, 2, true);
  184. pointTypes[numTypes++] = SEG_LINETO;
  185. pointCoords[numCoords++] = x;
  186. pointCoords[numCoords++] = y;
  187. }
  188. /**
  189. * Adds a curved segment, defined by two new points, to the path by
  190. * drawing a Quadratic curve that intersects both the current
  191. * coordinates and the coordinates (x2, y2), using the
  192. * specified point (x1, y1) as a quadratic parametric control
  193. * point.
  194. * @param x1, y1 the coordinates of the first quadratic control
  195. * point
  196. * @param x2, y2 the coordinates of the final endpoint
  197. */
  198. public synchronized void quadTo(float x1, float y1, float x2, float y2) {
  199. needRoom(1, 4, true);
  200. pointTypes[numTypes++] = SEG_QUADTO;
  201. pointCoords[numCoords++] = x1;
  202. pointCoords[numCoords++] = y1;
  203. pointCoords[numCoords++] = x2;
  204. pointCoords[numCoords++] = y2;
  205. }
  206. /**
  207. * Adds a curved segment, defined by three new points, to the path by
  208. * drawing a Bézier curve that intersects both the current
  209. * coordinates and the coordinates (x3, y3), using the
  210. * specified points (x1, y1) and (x2, y2) as
  211. * Bézier control points.
  212. * @param x1, y1 the coordinates of the first Béezier
  213. * control point
  214. * @param x2, y2 the coordinates of the second Bézier
  215. * control point
  216. * @param x3, y3 the coordinates of the final endpoint
  217. */
  218. public synchronized void curveTo(float x1, float y1,
  219. float x2, float y2,
  220. float x3, float y3) {
  221. needRoom(1, 6, true);
  222. pointTypes[numTypes++] = SEG_CUBICTO;
  223. pointCoords[numCoords++] = x1;
  224. pointCoords[numCoords++] = y1;
  225. pointCoords[numCoords++] = x2;
  226. pointCoords[numCoords++] = y2;
  227. pointCoords[numCoords++] = x3;
  228. pointCoords[numCoords++] = y3;
  229. }
  230. /**
  231. * Closes the current subpath by drawing a straight line back to
  232. * the coordinates of the last <code>moveTo</code>. If the path is already
  233. * closed then this method has no effect.
  234. */
  235. public synchronized void closePath() {
  236. if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
  237. needRoom(1, 0, true);
  238. pointTypes[numTypes++] = SEG_CLOSE;
  239. }
  240. }
  241. /**
  242. * Appends the geometry of the specified <code>Shape</code> object to the
  243. * path, possibly connecting the new geometry to the existing path
  244. * segments with a line segment.
  245. * If the <code>connect</code> parameter is <code>true</code> and the
  246. * path is not empty then any initial <code>moveTo</code> in the
  247. * geometry of the appended <code>Shape</code>
  248. * is turned into a <code>lineTo</code> segment.
  249. * If the destination coordinates of such a connecting <code>lineTo</code>
  250. * segment match the ending coordinates of a currently open
  251. * subpath then the segment is omitted as superfluous.
  252. * The winding rule of the specified <code>Shape</code> is ignored
  253. * and the appended geometry is governed by the winding
  254. * rule specified for this path.
  255. * @param s the <code>Shape</code> whose geometry is appended
  256. * to this path
  257. * @param connect a boolean to control whether or not to turn an
  258. * initial <code>moveTo</code> segment into a <code>lineTo</code>
  259. * segment to connect the new geometry to the existing path
  260. */
  261. public void append(Shape s, boolean connect) {
  262. PathIterator pi = s.getPathIterator(null);
  263. append(pi,connect);
  264. }
  265. /**
  266. * Appends the geometry of the specified
  267. * {@link PathIterator} object
  268. * to the path, possibly connecting the new geometry to the existing
  269. * path segments with a line segment.
  270. * If the <code>connect</code> parameter is <code>true</code> and the
  271. * path is not empty then any initial <code>moveTo</code> in the
  272. * geometry of the appended <code>Shape</code> is turned into a
  273. * <code>lineTo</code> segment.
  274. * If the destination coordinates of such a connecting <code>lineTo</code>
  275. * segment match the ending coordinates of a currently open
  276. * subpath then the segment is omitted as superfluous.
  277. * The winding rule of the specified <code>Shape</code> is ignored
  278. * and the appended geometry is governed by the winding
  279. * rule specified for this path.
  280. * @param pi the <code>PathIterator</code> whose geometry is appended to
  281. * this path
  282. * @param connect a boolean to control whether or not to turn an
  283. * initial <code>moveTo</code> segment into a <code>lineTo</code> segment
  284. * to connect the new geometry to the existing path
  285. */
  286. public void append(PathIterator pi, boolean connect) {
  287. float coords[] = new float[6];
  288. while (!pi.isDone()) {
  289. switch (pi.currentSegment(coords)) {
  290. case SEG_MOVETO:
  291. if (!connect || numTypes < 1 || numCoords < 2) {
  292. moveTo(coords[0], coords[1]);
  293. break;
  294. }
  295. if (pointTypes[numTypes - 1] != SEG_CLOSE &&
  296. pointCoords[numCoords - 2] == coords[0] &&
  297. pointCoords[numCoords - 1] == coords[1])
  298. {
  299. // Collapse out initial moveto/lineto
  300. break;
  301. }
  302. // NO BREAK;
  303. case SEG_LINETO:
  304. lineTo(coords[0], coords[1]);
  305. break;
  306. case SEG_QUADTO:
  307. quadTo(coords[0], coords[1],
  308. coords[2], coords[3]);
  309. break;
  310. case SEG_CUBICTO:
  311. curveTo(coords[0], coords[1],
  312. coords[2], coords[3],
  313. coords[4], coords[5]);
  314. break;
  315. case SEG_CLOSE:
  316. closePath();
  317. break;
  318. }
  319. pi.next();
  320. connect = false;
  321. }
  322. }
  323. /**
  324. * Returns the fill style winding rule.
  325. * @return an integer representing the current winding rule.
  326. * @see #WIND_EVEN_ODD
  327. * @see #WIND_NON_ZERO
  328. */
  329. public synchronized int getWindingRule() {
  330. return windingRule;
  331. }
  332. /**
  333. * Sets the winding rule for this path to the specified value.
  334. * @param rule an integer representing the specified
  335. * winding rule
  336. * @exception <code>IllegalArgumentException</code> if
  337. * <code>rule</code> is not either
  338. * <code>WIND_EVEN_ODD</code> or
  339. * <code>WIND_NON_ZERO</code>
  340. * @see #WIND_EVEN_ODD
  341. * @see #WIND_NON_ZERO
  342. */
  343. public void setWindingRule(int rule) {
  344. if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
  345. throw new IllegalArgumentException("winding rule must be "+
  346. "WIND_EVEN_ODD or "+
  347. "WIND_NON_ZERO");
  348. }
  349. windingRule = rule;
  350. }
  351. /**
  352. * Returns the coordinates most recently added to the end of the path
  353. * as a {@link Point2D} object.
  354. * @return a <code>Point2D</code> object containing the ending
  355. * coordinates of the path or <code>null</code> if there are no points
  356. * in the path.
  357. */
  358. public synchronized Point2D getCurrentPoint() {
  359. if (numTypes < 1 || numCoords < 2) {
  360. return null;
  361. }
  362. int index = numCoords;
  363. if (pointTypes[numTypes - 1] == SEG_CLOSE) {
  364. loop:
  365. for (int i = numTypes - 2; i > 0; i--) {
  366. switch (pointTypes[i]) {
  367. case SEG_MOVETO:
  368. break loop;
  369. case SEG_LINETO:
  370. index -= 2;
  371. break;
  372. case SEG_QUADTO:
  373. index -= 4;
  374. break;
  375. case SEG_CUBICTO:
  376. index -= 6;
  377. break;
  378. case SEG_CLOSE:
  379. break;
  380. }
  381. }
  382. }
  383. return new Point2D.Float(pointCoords[index - 2],
  384. pointCoords[index - 1]);
  385. }
  386. /**
  387. * Resets the path to empty. The append position is set back to the
  388. * beginning of the path and all coordinates and point types are
  389. * forgotten.
  390. */
  391. public synchronized void reset() {
  392. numTypes = numCoords = 0;
  393. }
  394. /**
  395. * Transforms the geometry of this path using the specified
  396. * {@link AffineTransform}.
  397. * The geometry is transformed in place, which permanently changes the
  398. * boundary defined by this object.
  399. * @param at the <code>AffineTransform</code> used to transform the area
  400. */
  401. public void transform(AffineTransform at) {
  402. at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
  403. }
  404. /**
  405. * Returns a new transformed <code>Shape</code>.
  406. * @param at the <code>AffineTransform</code> used to transform a
  407. * new <code>Shape</code>.
  408. * @return a new <code>Shape</code>, transformed with the specified
  409. * <code>AffineTransform</code>.
  410. */
  411. public synchronized Shape createTransformedShape(AffineTransform at) {
  412. GeneralPath gp = (GeneralPath) clone();
  413. if (at != null) {
  414. gp.transform(at);
  415. }
  416. return gp;
  417. }
  418. /**
  419. * Return the bounding box of the path.
  420. * @return a {@link java.awt.Rectangle} object that
  421. * bounds the current path.
  422. */
  423. public java.awt.Rectangle getBounds() {
  424. return getBounds2D().getBounds();
  425. }
  426. /**
  427. * Returns the bounding box of the path.
  428. * @return a {@link Rectangle2D} object that
  429. * bounds the current path.
  430. */
  431. public synchronized Rectangle2D getBounds2D() {
  432. float x1, y1, x2, y2;
  433. int i = numCoords;
  434. if (i > 0) {
  435. y1 = y2 = pointCoords[--i];
  436. x1 = x2 = pointCoords[--i];
  437. while (i > 0) {
  438. float y = pointCoords[--i];
  439. float x = pointCoords[--i];
  440. if (x < x1) x1 = x;
  441. if (y < y1) y1 = y;
  442. if (x > x2) x2 = x;
  443. if (y > y2) y2 = y;
  444. }
  445. } else {
  446. x1 = y1 = x2 = y2 = 0.0f;
  447. }
  448. return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
  449. }
  450. /**
  451. * Tests if the specified coordinates are inside the boundary of
  452. * this <code>Shape</code>.
  453. * @param x, y the specified coordinates
  454. * @return <code>true</code> if the specified coordinates are inside this
  455. * <code>Shape</code> <code>false</code> otherwise
  456. */
  457. public boolean contains(double x, double y) {
  458. if (numTypes < 2) {
  459. return false;
  460. }
  461. int cross = Curve.crossingsForPath(getPathIterator(null), x, y);
  462. if (windingRule == WIND_NON_ZERO) {
  463. return (cross != 0);
  464. } else {
  465. return ((cross & 1) != 0);
  466. }
  467. }
  468. /**
  469. * Tests if the specified <code>Point2D</code> is inside the boundary
  470. * of this <code>Shape</code>.
  471. * @param p the specified <code>Point2D</code>
  472. * @return <code>true</code> if this <code>Shape</code> contains the
  473. * specified <code>Point2D</code>, <code>false</code> otherwise.
  474. */
  475. public boolean contains(Point2D p) {
  476. return contains(p.getX(), p.getY());
  477. }
  478. /**
  479. * Tests if the specified rectangular area is inside the boundary of
  480. * this <code>Shape</code>.
  481. * @param x, y the specified coordinates
  482. * @param w the width of the specified rectangular area
  483. * @param h the height of the specified rectangular area
  484. * @return <code>true</code> if this <code>Shape</code> contains
  485. * the specified rectangluar area; <code>false</code> otherwise.
  486. */
  487. public boolean contains(double x, double y, double w, double h) {
  488. Crossings c = Crossings.findCrossings(getPathIterator(null),
  489. x, y, x+w, y+h);
  490. return (c != null && c.covers(y, y+h));
  491. }
  492. /**
  493. * Tests if the specified <code>Rectangle2D</code>
  494. * is inside the boundary of this <code>Shape</code>.
  495. * @param r a specified <code>Rectangle2D</code>
  496. * @return <code>true</code> if this <code>Shape</code> bounds the
  497. * specified <code>Rectangle2D</code> <code>false</code> otherwise.
  498. */
  499. public boolean contains(Rectangle2D r) {
  500. return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  501. }
  502. /**
  503. * Tests if the interior of this <code>Shape</code> intersects the
  504. * interior of a specified set of rectangular coordinates.
  505. * @param x, y the specified coordinates
  506. * @param w the width of the specified rectangular coordinates
  507. * @param h the height of the specified rectangular coordinates
  508. * @return <code>true</code> if this <code>Shape</code> and the
  509. * interior of the specified set of rectangular coordinates intersect
  510. * each other; <code>false</code> otherwise.
  511. */
  512. public boolean intersects(double x, double y, double w, double h) {
  513. Crossings c = Crossings.findCrossings(getPathIterator(null),
  514. x, y, x+w, y+h);
  515. return (c == null || !c.isEmpty());
  516. }
  517. /**
  518. * Tests if the interior of this <code>Shape</code> intersects the
  519. * interior of a specified <code>Rectangle2D</code>.
  520. * @param r the specified <code>Rectangle2D</code>
  521. * @return <code>true</code> if this <code>Shape</code> and the interior
  522. * of the specified <code>Rectangle2D</code> intersect each
  523. * other; <code>false</code> otherwise.
  524. */
  525. public boolean intersects(Rectangle2D r) {
  526. return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  527. }
  528. /**
  529. * Returns a <code>PathIterator</code> object that iterates along the
  530. * boundary of this <code>Shape</code> and provides access to the
  531. * geometry of the outline of this <code>Shape</code>.
  532. * The iterator for this class is not multi-threaded safe,
  533. * which means that this <code>GeneralPath</code> class does not
  534. * guarantee that modifications to the geometry of this
  535. * <code>GeneralPath</code> object do not affect any iterations of
  536. * that geometry that are already in process.
  537. * @param at an <code>AffineTransform</code>
  538. * @return a new <code>PathIterator</code> that iterates along the
  539. * boundary of this <code>Shape</code> and provides access to the
  540. * geometry of this <code>Shape</code>'s outline
  541. */
  542. public PathIterator getPathIterator(AffineTransform at) {
  543. return new GeneralPathIterator(this, at);
  544. }
  545. /**
  546. * Returns a <code>PathIterator</code> object that iterates along the
  547. * boundary of the flattened <code>Shape</code> and provides access to the
  548. * geometry of the outline of the <code>Shape</code>.
  549. * The iterator for this class is not multi-threaded safe,
  550. * which means that this <code>GeneralPath</code> class does not
  551. * guarantee that modifications to the geometry of this
  552. * <code>GeneralPath</code> object do not affect any iterations of
  553. * that geometry that are already in process.
  554. * @param at an <code>AffineTransform</code>
  555. * @param flatness the maximum distance that the line segments used to
  556. * approximate the curved segments are allowed to deviate
  557. * from any point on the original curve
  558. * @return a new <code>PathIterator</code> that iterates along the flattened
  559. * <code>Shape</code> boundary.
  560. */
  561. public PathIterator getPathIterator(AffineTransform at, double flatness) {
  562. return new FlatteningPathIterator(getPathIterator(at), flatness);
  563. }
  564. /**
  565. * Creates a new object of the same class as this object.
  566. *
  567. * @return a clone of this instance.
  568. * @exception OutOfMemoryError if there is not enough memory.
  569. * @see java.lang.Cloneable
  570. * @since JDK1.2
  571. */
  572. public Object clone() {
  573. try {
  574. GeneralPath copy = (GeneralPath) super.clone();
  575. copy.pointTypes = (byte[]) pointTypes.clone();
  576. copy.pointCoords = (float[]) pointCoords.clone();
  577. return copy;
  578. } catch (CloneNotSupportedException e) {
  579. // this shouldn't happen, since we are Cloneable
  580. throw new InternalError();
  581. }
  582. }
  583. }