1. /*
  2. * @(#)BasicStroke.java 1.29 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;
  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.
  19. * These attributes describe the shape of the mark made by a pen drawn along
  20. * the outline of a {@link Shape} object and the decorations applied at
  21. * the ends and joins of path segments of the <code>Shape</code> object.
  22. * These attributes include:
  23. * <dl compact>
  24. * <dt><i>width</i>
  25. * <dd>The pen width, measured perpendicularly to the pen trajectory.
  26. * <dt><i>end caps</i>
  27. * <dd>The decoration applied to the ends of unclosed subpaths or dash
  28. * segments.
  29. * <dt><i>line joins</i>
  30. * <dd>The decoration applied where two path segments are joined.
  31. * <dt><i>dash attributes</i>
  32. * <dd>The definition of how to make a dash pattern by alternating
  33. * between opaque and transparent sections.
  34. * </dl>
  35. *
  36. * @version 10 Feb 1997
  37. * @author Jim Graham
  38. */
  39. public class BasicStroke implements Stroke {
  40. /**
  41. * Joins path segments by extending their outside edges until
  42. * they meet.
  43. */
  44. public final static int JOIN_MITER = 0;
  45. /**
  46. * Joins path segments by rounding off the corner at a radius
  47. * of half the line width.
  48. */
  49. public final static int JOIN_ROUND = 1;
  50. /**
  51. * Joins path segments by connecting the outer corners of their
  52. * wide outlines with a straight segment.
  53. */
  54. public final static int JOIN_BEVEL = 2;
  55. /**
  56. * Ends unclosed subpaths and dash segments with no added
  57. * decoration.
  58. */
  59. public final static int CAP_BUTT = 0;
  60. /**
  61. * Ends unclosed subpaths and dash segments with a round
  62. * decoration that has a radius equal to half of the width
  63. * of the pen.
  64. */
  65. public final static int CAP_ROUND = 1;
  66. /**
  67. * Ends unclosed subpaths and dash segments with a square
  68. * projection that extends beyond the end of the segment
  69. * to a distance equal to half of the line width.
  70. */
  71. public final static int CAP_SQUARE = 2;
  72. float width;
  73. int join;
  74. int cap;
  75. float miterlimit;
  76. float dash[];
  77. float dash_phase;
  78. /**
  79. * Constructs a new <code>BasicStroke</code> with the specified
  80. * attributes.
  81. * @param width the width of the <code>BasicStroke</code>
  82. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  83. * @param join the decoration applied where path segments meet
  84. * @param miterlimit the limit to trim the miter join
  85. * @param dash the array representing the dashing pattern
  86. * @param dash_phase the offset to start the dashing pattern
  87. */
  88. public BasicStroke(float width, int cap, int join, float miterlimit,
  89. float dash[], float dash_phase) {
  90. if (width < 0.0f) {
  91. throw new IllegalArgumentException("negative width");
  92. }
  93. if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
  94. throw new IllegalArgumentException("illegal end cap value");
  95. }
  96. if (join == JOIN_MITER) {
  97. if (miterlimit < 1.0f) {
  98. throw new IllegalArgumentException("miter limit < 1");
  99. }
  100. } else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
  101. throw new IllegalArgumentException("illegal line join value");
  102. }
  103. if (dash != null) {
  104. if (dash_phase < 0.0f) {
  105. throw new IllegalArgumentException("negative dash phase");
  106. }
  107. boolean allzero = true;
  108. for (int i = 0; i < dash.length; i++) {
  109. float d = dash[i];
  110. if (d > 0.0) {
  111. allzero = false;
  112. } else if (d < 0.0) {
  113. throw new IllegalArgumentException("negative dash length");
  114. }
  115. }
  116. if (allzero) {
  117. throw new IllegalArgumentException("dash lengths all zero");
  118. }
  119. }
  120. this.width = width;
  121. this.cap = cap;
  122. this.join = join;
  123. this.miterlimit = miterlimit;
  124. if (dash != null) {
  125. this.dash = (float []) dash.clone();
  126. }
  127. this.dash_phase = dash_phase;
  128. }
  129. /**
  130. * Constructs a solid <code>BasicStroke</code> with the specified
  131. * attributes.
  132. * @param width the width of the <code>BasicStroke</code>
  133. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  134. * @param join the decoration applied where path segments meet
  135. * @param miterlimit the limit to trim the miter join
  136. */
  137. public BasicStroke(float width, int cap, int join, float miterlimit) {
  138. this(width, cap, join, miterlimit, null, 0.0f);
  139. }
  140. /**
  141. * Constructs a solid <code>BasicStroke</code> with the specified
  142. * attributes. The <code>miterlimit</code> parameter is
  143. * unnecessary in cases where the default is allowable or the
  144. * line joins are not specified as JOIN_MITER.
  145. * @param width the width of the <code>BasicStroke</code>
  146. * @param cap the decoration of the ends of a <code>BasicStroke</code>
  147. * @param join the decoration applied where path segments meet
  148. */
  149. public BasicStroke(float width, int cap, int join) {
  150. this(width, cap, join, 10.0f, null, 0.0f);
  151. }
  152. /**
  153. * Constructs a solid <code>BasicStroke</code> with the specified
  154. * line width and with default values for the cap and join
  155. * styles.
  156. * @param width the width of the <code>BasicStroke</code>
  157. */
  158. public BasicStroke(float width) {
  159. this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
  160. }
  161. /**
  162. * Constructs a new <code>BasicStroke</code> with defaults for all
  163. * attributes.
  164. * The default attributes are a solid line of width 1.0, CAP_SQUARE,
  165. * JOIN_MITER, a miter limit of 10.0.
  166. */
  167. public BasicStroke() {
  168. this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
  169. }
  170. /**
  171. * Returns a <code>Shape</code> whose interior defines the
  172. * stroked outline of a specified <code>Shape</code>.
  173. * @param s the <code>Shape</code> boundary be stroked
  174. * @return the <code>Shape</code> of the stroked outline.
  175. */
  176. public Shape createStrokedShape(Shape s) {
  177. FillAdapter filler = new FillAdapter();
  178. PathStroker stroker = new PathStroker(filler);
  179. PathConsumer consumer;
  180. stroker.setPenDiameter(width);
  181. stroker.setPenT4(null);
  182. stroker.setCaps(RasterizerCaps[cap]);
  183. stroker.setCorners(RasterizerCorners[join], miterlimit);
  184. if (dash != null) {
  185. PathDasher dasher = new PathDasher(stroker);
  186. dasher.setDash(dash, dash_phase);
  187. dasher.setDashT4(null);
  188. consumer = dasher;
  189. } else {
  190. consumer = stroker;
  191. }
  192. PathIterator pi = s.getPathIterator(null);
  193. try {
  194. consumer.beginPath();
  195. boolean pathClosed = false;
  196. float mx = 0.0f;
  197. float my = 0.0f;
  198. float point[] = new float[6];
  199. while (!pi.isDone()) {
  200. int type = pi.currentSegment(point);
  201. if (pathClosed == true) {
  202. pathClosed = false;
  203. if (type != PathIterator.SEG_MOVETO) {
  204. // Force current point back to last moveto point
  205. consumer.beginSubpath(mx, my);
  206. }
  207. }
  208. switch (type) {
  209. case PathIterator.SEG_MOVETO:
  210. mx = point[0];
  211. my = point[1];
  212. consumer.beginSubpath(point[0], point[1]);
  213. break;
  214. case PathIterator.SEG_LINETO:
  215. consumer.appendLine(point[0], point[1]);
  216. break;
  217. case PathIterator.SEG_QUADTO:
  218. // Quadratic curves take two points
  219. consumer.appendQuadratic(point[0], point[1],
  220. point[2], point[3]);
  221. break;
  222. case PathIterator.SEG_CUBICTO:
  223. // Cubic curves take three points
  224. consumer.appendCubic(point[0], point[1],
  225. point[2], point[3],
  226. point[4], point[5]);
  227. break;
  228. case PathIterator.SEG_CLOSE:
  229. consumer.closedSubpath();
  230. pathClosed = true;
  231. break;
  232. }
  233. pi.next();
  234. }
  235. consumer.endPath();
  236. } catch (PathException e) {
  237. throw new InternalError("Unable to Stroke shape ("+
  238. e.getMessage()+")");
  239. }
  240. return filler.getShape();
  241. }
  242. /**
  243. * Returns the line width. Line width is represented in user space.
  244. * @return the line width of this <code>BasicStroke</code>.
  245. */
  246. public float getLineWidth() {
  247. return width;
  248. }
  249. /**
  250. * Returns the end cap style.
  251. * @return the end cap style of this <code>BasicStroke</code> as one
  252. * of the static <code>int</code> values that define possible end cap
  253. * styles.
  254. */
  255. public int getEndCap() {
  256. return cap;
  257. }
  258. /**
  259. * Returns the line join style.
  260. * @return the line join style of the <code>BasicStroke</code> as one
  261. * of the static <code>int</code> values that define possible line
  262. * join styles.
  263. */
  264. public int getLineJoin() {
  265. return join;
  266. }
  267. /**
  268. * Returns the limit of miter joins.
  269. * @return the limit of miter joins of the <code>BasicStroke</code>.
  270. */
  271. public float getMiterLimit() {
  272. return miterlimit;
  273. }
  274. /**
  275. * Returns the array representing the lengths of the dash segments.
  276. * Alternate entries in the array represent the user space lengths
  277. * of the opaque and transparent segments of the dashes.
  278. * As the pen moves along the outline of the <code>Shape</code>
  279. * to be stroked, the user space
  280. * distance that the pen travels is accumulated. The distance
  281. * value is used to index into the dash array.
  282. * The pen is opaque when its current cumulative distance maps
  283. * to an even element of the dash array and transparent otherwise.
  284. * @return the dash array.
  285. */
  286. public float[] getDashArray() {
  287. if (dash == null) {
  288. return null;
  289. }
  290. return (float[]) dash.clone();
  291. }
  292. /**
  293. * Returns the current dash phase.
  294. * The dash phase is a distance specified in user coordinates that
  295. * represents an offset into the dashing pattern. In other words, the dash
  296. * phase defines the point in the dashing pattern that will correspond to
  297. * the beginning of the stroke.
  298. * @return the dash phase as a <code>float</code> value.
  299. */
  300. public float getDashPhase() {
  301. return dash_phase;
  302. }
  303. /**
  304. * Returns the hashcode for this stroke.
  305. * @return a hash code for this stroke.
  306. */
  307. public int hashCode() {
  308. int hash = Float.floatToIntBits(width);
  309. hash = hash * 31 + join;
  310. hash = hash * 31 + cap;
  311. hash = hash * 31 + Float.floatToIntBits(miterlimit);
  312. if (dash != null) {
  313. hash = hash * 31 + Float.floatToIntBits(dash_phase);
  314. for (int i = 0; i < dash.length; i++) {
  315. hash = hash * 31 + Float.floatToIntBits(dash[i]);
  316. }
  317. }
  318. return hash;
  319. }
  320. /**
  321. * Returns true if this BasicStroke represents the same
  322. * stroking operation as the given argument.
  323. */
  324. /**
  325. * Tests if a specified object is equal to this <code>BasicStroke</code>
  326. * by first testing if it is a <code>BasicStroke</code> and then comparing
  327. * its width, join, cap, miter limit, dash, and dash phase attributes with
  328. * those of this <code>BasicStroke</code>.
  329. * @param obj the specified object to compare to this
  330. * <code>BasicStroke</code>
  331. * @return <code>true</code> if the width, join, cap, miter limit, dash, and
  332. * dash phase are the same for both objects;
  333. * <code>false</code> otherwise.
  334. */
  335. public boolean equals(Object obj) {
  336. if (!(obj instanceof BasicStroke)) {
  337. return false;
  338. }
  339. BasicStroke bs = (BasicStroke) obj;
  340. if (width != bs.width) {
  341. return false;
  342. }
  343. if (join != bs.join) {
  344. return false;
  345. }
  346. if (cap != bs.cap) {
  347. return false;
  348. }
  349. if (miterlimit != bs.miterlimit) {
  350. return false;
  351. }
  352. if (dash != null) {
  353. if (dash_phase != bs.dash_phase) {
  354. return false;
  355. }
  356. if (!java.util.Arrays.equals(dash, bs.dash)) {
  357. return false;
  358. }
  359. }
  360. else if (bs.dash != null) {
  361. return false;
  362. }
  363. return true;
  364. }
  365. private static final int RasterizerCaps[] = {
  366. Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE
  367. };
  368. private static final int RasterizerCorners[] = {
  369. Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL
  370. };
  371. private class FillAdapter implements PathConsumer {
  372. boolean closed;
  373. GeneralPath path;
  374. public FillAdapter() {
  375. path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
  376. }
  377. public Shape getShape() {
  378. return path;
  379. }
  380. public void beginPath() {}
  381. public void beginSubpath(float x0, float y0) {
  382. if (closed) {
  383. path.closePath();
  384. closed = false;
  385. }
  386. path.moveTo(x0, y0);
  387. }
  388. public void appendLine(float x1, float y1) {
  389. path.lineTo(x1, y1);
  390. }
  391. public void appendQuadratic(float xm, float ym, float x1, float y1) {
  392. path.quadTo(xm, ym, x1, y1);
  393. }
  394. public void appendCubic(float xm, float ym,
  395. float xn, float yn,
  396. float x1, float y1) {
  397. path.curveTo(xm, ym, xn, yn, x1, y1);
  398. }
  399. public void closedSubpath() {
  400. closed = true;
  401. }
  402. public void endPath() {
  403. if (closed) {
  404. path.closePath();
  405. closed = false;
  406. }
  407. }
  408. public void useProxy(FastPathProducer proxy)
  409. throws PathException
  410. {
  411. proxy.sendTo(this);
  412. }
  413. public long getCPathConsumer() {
  414. return 0;
  415. }
  416. }
  417. }