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