1. /*
  2. * @(#)BasicStroke.java 1.37 01/02/09
  3. *
  4. * Copyright 1997-2001 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;
  11. import java.awt.geom.GeneralPath;
  12. import java.awt.geom.PathIterator;
  13. import sun.dc.path.FastPathProducer;
  14. import sun.dc.path.PathConsumer;
  15. import sun.dc.path.PathException;
  16. import sun.dc.pr.PathStroker;
  17. import sun.dc.pr.PathDasher;
  18. import sun.dc.pr.Rasterizer;
  19. /**
  20. * The <code>BasicStroke</code> class defines a basic set of rendering
  21. * attributes for the outlines of graphics primitives, which are rendered
  22. * with a {@link Graphics2D} object that has its Stroke attribute set to
  23. * this <code>BasicStroke</code>.
  24. * The rendering attributes defined by <code>BasicStroke</code> describe
  25. * the shape of the mark made by a pen drawn along the outline of a
  26. * {@link Shape} and the decorations applied at the ends and joins of
  27. * path segments of the <code>Shape</code>.
  28. * These rendering attributes include:
  29. * <dl compact>
  30. * <dt><i>width</i>
  31. * <dd>The pen width, measured perpendicularly to the pen trajectory.
  32. * <dt><i>end caps</i>
  33. * <dd>The decoration applied to the ends of unclosed subpaths and
  34. * dash segments. Subpaths that start and end on the same point are
  35. * still considered unclosed if they do not have a CLOSE segment.
  36. * See {@link java.awt.geom.PathIterator#SEG_CLOSE SEG_CLOSE}
  37. * for more information on the CLOSE segment.
  38. * The three different decorations are: {@link #CAP_BUTT},
  39. * {@link #CAP_ROUND}, and {@link #CAP_SQUARE}.
  40. * <dt><i>line joins</i>
  41. * <dd>The decoration applied at the intersection of two path segments
  42. * and at the intersection of the endpoints of a subpath that is closed
  43. * using {@link java.awt.geom.PathIterator#SEG_CLOSE SEG_CLOSE}.
  44. * The three different decorations are: {@link #JOIN_BEVEL},
  45. * {@link #JOIN_MITER}, and {@link #JOIN_ROUND}.
  46. * <dt><i>miter limit</i>
  47. * <dd>The limit to trim a line join that has a JOIN_MITER decoration.
  48. * A line join is trimmed when the ratio of miter length to stroke
  49. * width is greater than the miterlimit value. The miter length is
  50. * the diagonal length of the miter, which is the distance between
  51. * the inside corner and the outside corner of the intersection.
  52. * The smaller the angle formed by two line segments, the longer
  53. * the miter length and the sharper the angle of intersection. The
  54. * default miterlimit value of 10.0f causes all angles less than
  55. * 11 degrees to be trimmed. Trimming miters converts
  56. * the decoration of the line join to bevel.
  57. * <dt><i>dash attributes</i>
  58. * <dd>The definition of how to make a dash pattern by alternating
  59. * between opaque and transparent sections.
  60. * </dl>
  61. * All attributes that specify measurements and distances controlling
  62. * the shape of the returned outline are measured in the same
  63. * coordinate system as the original unstroked <code>Shape</code>
  64. * argument. When a <code>Graphics2D</code> object uses a
  65. * <code>Stroke</code> object to redefine a path during the execution
  66. * of one of its <code>draw</code> methods, the geometry is supplied
  67. * in its original form before the <code>Graphics2D</code> transform
  68. * attribute is applied. Therefore, attributes such as the pen width
  69. * are interpreted in the user space coordinate system of the
  70. * <code>Graphics2D</code> object and are subject to the scaling and
  71. * shearing effects of the user-space-to-device-space transform in that
  72. * particular <code>Graphics2D</code>.
  73. * For example, the width of a rendered shape's outline is determined
  74. * not only by the width attribute of this <code>BasicStroke</code>,
  75. * but also by the transform attribute of the
  76. * <code>Graphics2D</code> object. Consider this code:
  77. * <blockquote><tt>
  78. * // sets the Graphics2D object's Tranform attribute
  79. * g2d.scale(10, 10);
  80. * // sets the Graphics2D object's Stroke attribute
  81. * g2d.setStroke(new BasicStroke(1.5f));
  82. * </tt></blockquote>
  83. * Assuming there are no other scaling transforms added to the
  84. * <code>Graphics2D</code> object, the resulting line
  85. * will be approximately 15 pixels wide.
  86. * As the example code demonstrates, a floating-point line
  87. * offers better precision, especially when large transforms are
  88. * used with a <code>Graphics2D</code> object.
  89. * When a line is diagonal, the exact width depends on how the
  90. * rendering pipeline chooses which pixels to fill as it traces the
  91. * theoretical widened outline. The choice of which pixels to turn
  92. * on is affected by the antialiasing attribute because the
  93. * antialiasing rendering pipeline can choose to color
  94. * partially-covered pixels.
  95. * <p>
  96. * For more information on the user space coordinate system and the
  97. * rendering process, see the <code>Graphics2D</code> class comments.
  98. * @see Graphics2D
  99. * @version 1.37, 02/09/01
  100. * @author Jim Graham
  101. */
  102. public class BasicStroke implements Stroke {
  103. /**
  104. * Joins path segments by extending their outside edges until
  105. * they meet.
  106. */
  107. public final static int JOIN_MITER = 0;
  108. /**
  109. * Joins path segments by rounding off the corner at a radius
  110. * of half the line width.
  111. */
  112. public final static int JOIN_ROUND = 1;
  113. /**
  114. * Joins path segments by connecting the outer corners of their
  115. * wide outlines with a straight segment.
  116. */
  117. public final static int JOIN_BEVEL = 2;
  118. /**
  119. * Ends unclosed subpaths and dash segments with no added
  120. * decoration.
  121. */
  122. public final static int CAP_BUTT = 0;
  123. /**
  124. * Ends unclosed subpaths and dash segments with a round
  125. * decoration that has a radius equal to half of the width
  126. * of the pen.
  127. */
  128. public final static int CAP_ROUND = 1;
  129. /**
  130. * Ends unclosed subpaths and dash segments with a square
  131. * projection that extends beyond the end of the segment
  132. * to a distance equal to half of the line width.
  133. */
  134. public final static int CAP_SQUARE = 2;
  135. float width;
  136. int join;
  137. int cap;
  138. float miterlimit;
  139. float dash[];
  140. float dash_phase;
  141. /**
  142. * Constructs a new <code>BasicStroke</code> with the specified
  143. * attributes.
  144. * @param width the width of this <code>BasicStroke</code>. The
  145. * width must be greater than or equal to 0.0f. If width is
  146. * set to 0.0f, the stroke is rendered as the thinnest
  147. * possible line for the target device and the antialias
  148. * hint setting.
  149. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  150. * @param join the decoration applied where path segments meet
  151. * @param miterlimit the limit to trim the miter join. The miterlimit
  152. * must be greater than or equal to 1.0f.
  153. * @param dash the array representing the dashing pattern
  154. * @param dash_phase the offset to start the dashing pattern
  155. * @throws IllegalArgumentException if <code>width</code> is negative
  156. * @throws IllegalArgumentException if <code>cap</code> is not either
  157. * CAP_BUTT, CAP_ROUND or CAP_SQUARE
  158. * @throws IllegalArgumentException if <code>miterlimit</code> is less
  159. * than 1 and <code>join</code> is JOIN_MITER
  160. * @throws IllegalArgumentException if <code>join</code> is not
  161. * either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER
  162. * @throws IllegalArgumentException if <code>dash_phase</code>
  163. * is negative and <code>dash</code> is not <code>null</code>
  164. * @throws IllegalArgumentException if the length of
  165. * <code>dash</code> is zero
  166. * @throws IllegalArgumentException if dash lengths are all zero.
  167. */
  168. public BasicStroke(float width, int cap, int join, float miterlimit,
  169. float dash[], float dash_phase) {
  170. if (width < 0.0f) {
  171. throw new IllegalArgumentException("negative width");
  172. }
  173. if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
  174. throw new IllegalArgumentException("illegal end cap value");
  175. }
  176. if (join == JOIN_MITER) {
  177. if (miterlimit < 1.0f) {
  178. throw new IllegalArgumentException("miter limit < 1");
  179. }
  180. } else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
  181. throw new IllegalArgumentException("illegal line join value");
  182. }
  183. if (dash != null) {
  184. if (dash_phase < 0.0f) {
  185. throw new IllegalArgumentException("negative dash phase");
  186. }
  187. boolean allzero = true;
  188. for (int i = 0; i < dash.length; i++) {
  189. float d = dash[i];
  190. if (d > 0.0) {
  191. allzero = false;
  192. } else if (d < 0.0) {
  193. throw new IllegalArgumentException("negative dash length");
  194. }
  195. }
  196. if (allzero) {
  197. throw new IllegalArgumentException("dash lengths all zero");
  198. }
  199. }
  200. this.width = width;
  201. this.cap = cap;
  202. this.join = join;
  203. this.miterlimit = miterlimit;
  204. if (dash != null) {
  205. this.dash = (float []) dash.clone();
  206. }
  207. this.dash_phase = dash_phase;
  208. }
  209. /**
  210. * Constructs a solid <code>BasicStroke</code> with the specified
  211. * attributes.
  212. * @param width the width of the <code>BasicStroke</code>
  213. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  214. * @param join the decoration applied where path segments meet
  215. * @param miterlimit the limit to trim the miter join
  216. * @throws IllegalArgumentException if <code>width</code> is negative
  217. * @throws IllegalArgumentException if <code>cap</code> is not either
  218. * CAP_BUTT, CAP_ROUND or CAP_SQUARE
  219. * @throws IllegalArgumentException if <code>miterlimit</code> is less
  220. * than 1 and <code>join</code> is JOIN_MITER
  221. * @throws IllegalArgumentException if <code>join</code> is not
  222. * either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER
  223. */
  224. public BasicStroke(float width, int cap, int join, float miterlimit) {
  225. this(width, cap, join, miterlimit, null, 0.0f);
  226. }
  227. /**
  228. * Constructs a solid <code>BasicStroke</code> with the specified
  229. * attributes. The <code>miterlimit</code> parameter is
  230. * unnecessary in cases where the default is allowable or the
  231. * line joins are not specified as JOIN_MITER.
  232. * @param width the width of the <code>BasicStroke</code>
  233. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  234. * @param join the decoration applied where path segments meet
  235. * @throws IllegalArgumentException if <code>width</code> is negative
  236. * @throws IllegalArgumentException if <code>cap</code> is not either
  237. * CAP_BUTT, CAP_ROUND or CAP_SQUARE
  238. * @throws IllegalArgumentException if <code>join</code> is not
  239. * either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER
  240. */
  241. public BasicStroke(float width, int cap, int join) {
  242. this(width, cap, join, 10.0f, null, 0.0f);
  243. }
  244. /**
  245. * Constructs a solid <code>BasicStroke</code> with the specified
  246. * line width and with default values for the cap and join
  247. * styles.
  248. * @param width the width of the <code>BasicStroke</code>
  249. * @throws IllegalArgumentException if <code>width</code> is negative
  250. */
  251. public BasicStroke(float width) {
  252. this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
  253. }
  254. /**
  255. * Constructs a new <code>BasicStroke</code> with defaults for all
  256. * attributes.
  257. * The default attributes are a solid line of width 1.0, CAP_SQUARE,
  258. * JOIN_MITER, a miter limit of 10.0.
  259. */
  260. public BasicStroke() {
  261. this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
  262. }
  263. /**
  264. * Returns a <code>Shape</code> whose interior defines the
  265. * stroked outline of a specified <code>Shape</code>.
  266. * @param s the <code>Shape</code> boundary be stroked
  267. * @return the <code>Shape</code> of the stroked outline.
  268. */
  269. public Shape createStrokedShape(Shape s) {
  270. FillAdapter filler = new FillAdapter();
  271. PathStroker stroker = new PathStroker(filler);
  272. PathConsumer consumer;
  273. stroker.setPenDiameter(width);
  274. stroker.setPenT4(null);
  275. stroker.setCaps(RasterizerCaps[cap]);
  276. stroker.setCorners(RasterizerCorners[join], miterlimit);
  277. if (dash != null) {
  278. PathDasher dasher = new PathDasher(stroker);
  279. dasher.setDash(dash, dash_phase);
  280. dasher.setDashT4(null);
  281. consumer = dasher;
  282. } else {
  283. consumer = stroker;
  284. }
  285. PathIterator pi = s.getPathIterator(null);
  286. try {
  287. consumer.beginPath();
  288. boolean pathClosed = false;
  289. float mx = 0.0f;
  290. float my = 0.0f;
  291. float point[] = new float[6];
  292. while (!pi.isDone()) {
  293. int type = pi.currentSegment(point);
  294. if (pathClosed == true) {
  295. pathClosed = false;
  296. if (type != PathIterator.SEG_MOVETO) {
  297. // Force current point back to last moveto point
  298. consumer.beginSubpath(mx, my);
  299. }
  300. }
  301. switch (type) {
  302. case PathIterator.SEG_MOVETO:
  303. mx = point[0];
  304. my = point[1];
  305. consumer.beginSubpath(point[0], point[1]);
  306. break;
  307. case PathIterator.SEG_LINETO:
  308. consumer.appendLine(point[0], point[1]);
  309. break;
  310. case PathIterator.SEG_QUADTO:
  311. // Quadratic curves take two points
  312. consumer.appendQuadratic(point[0], point[1],
  313. point[2], point[3]);
  314. break;
  315. case PathIterator.SEG_CUBICTO:
  316. // Cubic curves take three points
  317. consumer.appendCubic(point[0], point[1],
  318. point[2], point[3],
  319. point[4], point[5]);
  320. break;
  321. case PathIterator.SEG_CLOSE:
  322. consumer.closedSubpath();
  323. pathClosed = true;
  324. break;
  325. }
  326. pi.next();
  327. }
  328. consumer.endPath();
  329. } catch (PathException e) {
  330. throw new InternalError("Unable to Stroke shape ("+
  331. e.getMessage()+")");
  332. }
  333. return filler.getShape();
  334. }
  335. /**
  336. * Returns the line width. Line width is represented in user space,
  337. * which is the default-coordinate space used by Java 2D. See the
  338. * <code>Graphics2D</code> class comments for more information on
  339. * the user space coordinate system.
  340. * @return the line width of this <code>BasicStroke</code>.
  341. * @see Graphics2D
  342. */
  343. public float getLineWidth() {
  344. return width;
  345. }
  346. /**
  347. * Returns the end cap style.
  348. * @return the end cap style of this <code>BasicStroke</code> as one
  349. * of the static <code>int</code> values that define possible end cap
  350. * styles.
  351. */
  352. public int getEndCap() {
  353. return cap;
  354. }
  355. /**
  356. * Returns the line join style.
  357. * @return the line join style of the <code>BasicStroke</code> as one
  358. * of the static <code>int</code> values that define possible line
  359. * join styles.
  360. */
  361. public int getLineJoin() {
  362. return join;
  363. }
  364. /**
  365. * Returns the limit of miter joins.
  366. * @return the limit of miter joins of the <code>BasicStroke</code>.
  367. */
  368. public float getMiterLimit() {
  369. return miterlimit;
  370. }
  371. /**
  372. * Returns the array representing the lengths of the dash segments.
  373. * Alternate entries in the array represent the user space lengths
  374. * of the opaque and transparent segments of the dashes.
  375. * As the pen moves along the outline of the <code>Shape</code>
  376. * to be stroked, the user space
  377. * distance that the pen travels is accumulated. The distance
  378. * value is used to index into the dash array.
  379. * The pen is opaque when its current cumulative distance maps
  380. * to an even element of the dash array and transparent otherwise.
  381. * @return the dash array.
  382. */
  383. public float[] getDashArray() {
  384. if (dash == null) {
  385. return null;
  386. }
  387. return (float[]) dash.clone();
  388. }
  389. /**
  390. * Returns the current dash phase.
  391. * The dash phase is a distance specified in user coordinates that
  392. * represents an offset into the dashing pattern. In other words, the dash
  393. * phase defines the point in the dashing pattern that will correspond to
  394. * the beginning of the stroke.
  395. * @return the dash phase as a <code>float</code> value.
  396. */
  397. public float getDashPhase() {
  398. return dash_phase;
  399. }
  400. /**
  401. * Returns the hashcode for this stroke.
  402. * @return a hash code for this stroke.
  403. */
  404. public int hashCode() {
  405. int hash = Float.floatToIntBits(width);
  406. hash = hash * 31 + join;
  407. hash = hash * 31 + cap;
  408. hash = hash * 31 + Float.floatToIntBits(miterlimit);
  409. if (dash != null) {
  410. hash = hash * 31 + Float.floatToIntBits(dash_phase);
  411. for (int i = 0; i < dash.length; i++) {
  412. hash = hash * 31 + Float.floatToIntBits(dash[i]);
  413. }
  414. }
  415. return hash;
  416. }
  417. /**
  418. * Returns true if this BasicStroke represents the same
  419. * stroking operation as the given argument.
  420. */
  421. /**
  422. * Tests if a specified object is equal to this <code>BasicStroke</code>
  423. * by first testing if it is a <code>BasicStroke</code> and then comparing
  424. * its width, join, cap, miter limit, dash, and dash phase attributes with
  425. * those of this <code>BasicStroke</code>.
  426. * @param obj the specified object to compare to this
  427. * <code>BasicStroke</code>
  428. * @return <code>true</code> if the width, join, cap, miter limit, dash, and
  429. * dash phase are the same for both objects;
  430. * <code>false</code> otherwise.
  431. */
  432. public boolean equals(Object obj) {
  433. if (!(obj instanceof BasicStroke)) {
  434. return false;
  435. }
  436. BasicStroke bs = (BasicStroke) obj;
  437. if (width != bs.width) {
  438. return false;
  439. }
  440. if (join != bs.join) {
  441. return false;
  442. }
  443. if (cap != bs.cap) {
  444. return false;
  445. }
  446. if (miterlimit != bs.miterlimit) {
  447. return false;
  448. }
  449. if (dash != null) {
  450. if (dash_phase != bs.dash_phase) {
  451. return false;
  452. }
  453. if (!java.util.Arrays.equals(dash, bs.dash)) {
  454. return false;
  455. }
  456. }
  457. else if (bs.dash != null) {
  458. return false;
  459. }
  460. return true;
  461. }
  462. private static final int RasterizerCaps[] = {
  463. Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE
  464. };
  465. private static final int RasterizerCorners[] = {
  466. Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL
  467. };
  468. private class FillAdapter implements PathConsumer {
  469. boolean closed;
  470. GeneralPath path;
  471. public FillAdapter() {
  472. path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
  473. }
  474. public Shape getShape() {
  475. return path;
  476. }
  477. public void beginPath() {}
  478. public void beginSubpath(float x0, float y0) {
  479. if (closed) {
  480. path.closePath();
  481. closed = false;
  482. }
  483. path.moveTo(x0, y0);
  484. }
  485. public void appendLine(float x1, float y1) {
  486. path.lineTo(x1, y1);
  487. }
  488. public void appendQuadratic(float xm, float ym, float x1, float y1) {
  489. path.quadTo(xm, ym, x1, y1);
  490. }
  491. public void appendCubic(float xm, float ym,
  492. float xn, float yn,
  493. float x1, float y1) {
  494. path.curveTo(xm, ym, xn, yn, x1, y1);
  495. }
  496. public void closedSubpath() {
  497. closed = true;
  498. }
  499. public void endPath() {
  500. if (closed) {
  501. path.closePath();
  502. closed = false;
  503. }
  504. }
  505. public void useProxy(FastPathProducer proxy)
  506. throws PathException
  507. {
  508. proxy.sendTo(this);
  509. }
  510. public long getCPathConsumer() {
  511. return 0;
  512. }
  513. }
  514. }