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