1. /*
  2. * @(#)GeneralPath.java 1.59 03/12/19
  3. *
  4. * Copyright 2004 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 1.59, 12/19/03
  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. * @see #setWindingRule
  329. */
  330. public synchronized int getWindingRule() {
  331. return windingRule;
  332. }
  333. /**
  334. * Sets the winding rule for this path to the specified value.
  335. * @param rule an integer representing the specified
  336. * winding rule
  337. * @exception <code>IllegalArgumentException</code> if
  338. * <code>rule</code> is not either
  339. * <code>WIND_EVEN_ODD</code> or
  340. * <code>WIND_NON_ZERO</code>
  341. * @see #WIND_EVEN_ODD
  342. * @see #WIND_NON_ZERO
  343. * @see #getWindingRule
  344. */
  345. public void setWindingRule(int rule) {
  346. if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
  347. throw new IllegalArgumentException("winding rule must be "+
  348. "WIND_EVEN_ODD or "+
  349. "WIND_NON_ZERO");
  350. }
  351. windingRule = rule;
  352. }
  353. /**
  354. * Returns the coordinates most recently added to the end of the path
  355. * as a {@link Point2D} object.
  356. * @return a <code>Point2D</code> object containing the ending
  357. * coordinates of the path or <code>null</code> if there are no points
  358. * in the path.
  359. */
  360. public synchronized Point2D getCurrentPoint() {
  361. if (numTypes < 1 || numCoords < 2) {
  362. return null;
  363. }
  364. int index = numCoords;
  365. if (pointTypes[numTypes - 1] == SEG_CLOSE) {
  366. loop:
  367. for (int i = numTypes - 2; i > 0; i--) {
  368. switch (pointTypes[i]) {
  369. case SEG_MOVETO:
  370. break loop;
  371. case SEG_LINETO:
  372. index -= 2;
  373. break;
  374. case SEG_QUADTO:
  375. index -= 4;
  376. break;
  377. case SEG_CUBICTO:
  378. index -= 6;
  379. break;
  380. case SEG_CLOSE:
  381. break;
  382. }
  383. }
  384. }
  385. return new Point2D.Float(pointCoords[index - 2],
  386. pointCoords[index - 1]);
  387. }
  388. /**
  389. * Resets the path to empty. The append position is set back to the
  390. * beginning of the path and all coordinates and point types are
  391. * forgotten.
  392. */
  393. public synchronized void reset() {
  394. numTypes = numCoords = 0;
  395. }
  396. /**
  397. * Transforms the geometry of this path using the specified
  398. * {@link AffineTransform}.
  399. * The geometry is transformed in place, which permanently changes the
  400. * boundary defined by this object.
  401. * @param at the <code>AffineTransform</code> used to transform the area
  402. */
  403. public void transform(AffineTransform at) {
  404. at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
  405. }
  406. /**
  407. * Returns a new transformed <code>Shape</code>.
  408. * @param at the <code>AffineTransform</code> used to transform a
  409. * new <code>Shape</code>.
  410. * @return a new <code>Shape</code>, transformed with the specified
  411. * <code>AffineTransform</code>.
  412. */
  413. public synchronized Shape createTransformedShape(AffineTransform at) {
  414. GeneralPath gp = (GeneralPath) clone();
  415. if (at != null) {
  416. gp.transform(at);
  417. }
  418. return gp;
  419. }
  420. /**
  421. * Return the bounding box of the path.
  422. * @return a {@link java.awt.Rectangle} object that
  423. * bounds the current path.
  424. */
  425. public java.awt.Rectangle getBounds() {
  426. return getBounds2D().getBounds();
  427. }
  428. /**
  429. * Returns the bounding box of the path.
  430. * @return a {@link Rectangle2D} object that
  431. * bounds the current path.
  432. */
  433. public synchronized Rectangle2D getBounds2D() {
  434. float x1, y1, x2, y2;
  435. int i = numCoords;
  436. if (i > 0) {
  437. y1 = y2 = pointCoords[--i];
  438. x1 = x2 = pointCoords[--i];
  439. while (i > 0) {
  440. float y = pointCoords[--i];
  441. float x = pointCoords[--i];
  442. if (x < x1) x1 = x;
  443. if (y < y1) y1 = y;
  444. if (x > x2) x2 = x;
  445. if (y > y2) y2 = y;
  446. }
  447. } else {
  448. x1 = y1 = x2 = y2 = 0.0f;
  449. }
  450. return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
  451. }
  452. /**
  453. * Tests if the specified coordinates are inside the boundary of
  454. * this <code>Shape</code>.
  455. * @param x, y the specified coordinates
  456. * @return <code>true</code> if the specified coordinates are inside this
  457. * <code>Shape</code> <code>false</code> otherwise
  458. */
  459. public boolean contains(double x, double y) {
  460. if (numTypes < 2) {
  461. return false;
  462. }
  463. int cross = Curve.crossingsForPath(getPathIterator(null), x, y);
  464. if (windingRule == WIND_NON_ZERO) {
  465. return (cross != 0);
  466. } else {
  467. return ((cross & 1) != 0);
  468. }
  469. }
  470. /**
  471. * Tests if the specified <code>Point2D</code> is inside the boundary
  472. * of this <code>Shape</code>.
  473. * @param p the specified <code>Point2D</code>
  474. * @return <code>true</code> if this <code>Shape</code> contains the
  475. * specified <code>Point2D</code>, <code>false</code> otherwise.
  476. */
  477. public boolean contains(Point2D p) {
  478. return contains(p.getX(), p.getY());
  479. }
  480. /**
  481. * Tests if the specified rectangular area is inside the boundary of
  482. * this <code>Shape</code>.
  483. * @param x, y the specified coordinates
  484. * @param w the width of the specified rectangular area
  485. * @param h the height of the specified rectangular area
  486. * @return <code>true</code> if this <code>Shape</code> contains
  487. * the specified rectangluar area; <code>false</code> otherwise.
  488. */
  489. public boolean contains(double x, double y, double w, double h) {
  490. Crossings c = Crossings.findCrossings(getPathIterator(null),
  491. x, y, x+w, y+h);
  492. return (c != null && c.covers(y, y+h));
  493. }
  494. /**
  495. * Tests if the specified <code>Rectangle2D</code>
  496. * is inside the boundary of this <code>Shape</code>.
  497. * @param r a specified <code>Rectangle2D</code>
  498. * @return <code>true</code> if this <code>Shape</code> bounds the
  499. * specified <code>Rectangle2D</code> <code>false</code> otherwise.
  500. */
  501. public boolean contains(Rectangle2D r) {
  502. return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  503. }
  504. /**
  505. * Tests if the interior of this <code>Shape</code> intersects the
  506. * interior of a specified set of rectangular coordinates.
  507. * @param x, y the specified coordinates
  508. * @param w the width of the specified rectangular coordinates
  509. * @param h the height of the specified rectangular coordinates
  510. * @return <code>true</code> if this <code>Shape</code> and the
  511. * interior of the specified set of rectangular coordinates intersect
  512. * each other; <code>false</code> otherwise.
  513. */
  514. public boolean intersects(double x, double y, double w, double h) {
  515. Crossings c = Crossings.findCrossings(getPathIterator(null),
  516. x, y, x+w, y+h);
  517. return (c == null || !c.isEmpty());
  518. }
  519. /**
  520. * Tests if the interior of this <code>Shape</code> intersects the
  521. * interior of a specified <code>Rectangle2D</code>.
  522. * @param r the specified <code>Rectangle2D</code>
  523. * @return <code>true</code> if this <code>Shape</code> and the interior
  524. * of the specified <code>Rectangle2D</code> intersect each
  525. * other; <code>false</code> otherwise.
  526. */
  527. public boolean intersects(Rectangle2D r) {
  528. return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  529. }
  530. /**
  531. * Returns a <code>PathIterator</code> object that iterates along the
  532. * boundary of this <code>Shape</code> and provides access to the
  533. * geometry of the outline of this <code>Shape</code>.
  534. * The iterator for this class is not multi-threaded safe,
  535. * which means that this <code>GeneralPath</code> class does not
  536. * guarantee that modifications to the geometry of this
  537. * <code>GeneralPath</code> object do not affect any iterations of
  538. * that geometry that are already in process.
  539. * @param at an <code>AffineTransform</code>
  540. * @return a new <code>PathIterator</code> that iterates along the
  541. * boundary of this <code>Shape</code> and provides access to the
  542. * geometry of this <code>Shape</code>'s outline
  543. */
  544. public PathIterator getPathIterator(AffineTransform at) {
  545. return new GeneralPathIterator(this, at);
  546. }
  547. /**
  548. * Returns a <code>PathIterator</code> object that iterates along the
  549. * boundary of the flattened <code>Shape</code> and provides access to the
  550. * geometry of the outline of the <code>Shape</code>.
  551. * The iterator for this class is not multi-threaded safe,
  552. * which means that this <code>GeneralPath</code> class does not
  553. * guarantee that modifications to the geometry of this
  554. * <code>GeneralPath</code> object do not affect any iterations of
  555. * that geometry that are already in process.
  556. * @param at an <code>AffineTransform</code>
  557. * @param flatness the maximum distance that the line segments used to
  558. * approximate the curved segments are allowed to deviate
  559. * from any point on the original curve
  560. * @return a new <code>PathIterator</code> that iterates along the flattened
  561. * <code>Shape</code> boundary.
  562. */
  563. public PathIterator getPathIterator(AffineTransform at, double flatness) {
  564. return new FlatteningPathIterator(getPathIterator(at), flatness);
  565. }
  566. /**
  567. * Creates a new object of the same class as this object.
  568. *
  569. * @return a clone of this instance.
  570. * @exception OutOfMemoryError if there is not enough memory.
  571. * @see java.lang.Cloneable
  572. * @since 1.2
  573. */
  574. public Object clone() {
  575. try {
  576. GeneralPath copy = (GeneralPath) super.clone();
  577. copy.pointTypes = (byte[]) pointTypes.clone();
  578. copy.pointCoords = (float[]) pointCoords.clone();
  579. return copy;
  580. } catch (CloneNotSupportedException e) {
  581. // this shouldn't happen, since we are Cloneable
  582. throw new InternalError();
  583. }
  584. }
  585. GeneralPath(int windingRule,
  586. byte[] pointTypes,
  587. int numTypes,
  588. float[] pointCoords,
  589. int numCoords) {
  590. // used to construct from native
  591. this.windingRule = windingRule;
  592. this.pointTypes = pointTypes;
  593. this.numTypes = numTypes;
  594. this.pointCoords = pointCoords;
  595. this.numCoords = numCoords;
  596. }
  597. }