1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45:
46: import ;
47: import ;
48: import ;
49: import ;
50:
51:
64: public class BasicStroke implements Stroke
65: {
66:
70: public static final int JOIN_MITER = 0;
71:
72:
76: public static final int JOIN_ROUND = 1;
77:
78:
82: public static final int JOIN_BEVEL = 2;
83:
84:
88: public static final int CAP_BUTT = 0;
89:
90:
94: public static final int CAP_ROUND = 1;
95:
96:
100: public static final int CAP_SQUARE = 2;
101:
102:
103: private final float width;
104:
105:
106: private final int cap;
107:
108:
109: private final int join;
110:
111:
112: private final float limit;
113:
114:
115: private final float[] dash;
116:
117:
118: private final float phase;
119:
120:
121: private Segment start, end;
122:
123:
140: public BasicStroke(float width, int cap, int join, float miterlimit,
141: float[] dash, float dashPhase)
142: {
143: if (width < 0.0f )
144: throw new IllegalArgumentException("width " + width + " < 0");
145: else if (cap < CAP_BUTT || cap > CAP_SQUARE)
146: throw new IllegalArgumentException("cap " + cap + " out of range ["
147: + CAP_BUTT + ".." + CAP_SQUARE + "]");
148: else if (miterlimit < 1.0f && join == JOIN_MITER)
149: throw new IllegalArgumentException("miterlimit " + miterlimit
150: + " < 1.0f while join == JOIN_MITER");
151: else if (join < JOIN_MITER || join > JOIN_BEVEL)
152: throw new IllegalArgumentException("join " + join + " out of range ["
153: + JOIN_MITER + ".." + JOIN_BEVEL
154: + "]");
155: else if (dashPhase < 0.0f && dash != null)
156: throw new IllegalArgumentException("dashPhase " + dashPhase
157: + " < 0.0f while dash != null");
158: else if (dash != null)
159: if (dash.length == 0)
160: throw new IllegalArgumentException("dash.length is 0");
161: else
162: {
163: boolean allZero = true;
164:
165: for ( int i = 0; i < dash.length; ++i)
166: {
167: if (dash[i] != 0.0f)
168: {
169: allZero = false;
170: break;
171: }
172: }
173:
174: if (allZero)
175: throw new IllegalArgumentException("all dashes are 0.0f");
176: }
177:
178: this.width = width;
179: this.cap = cap;
180: this.join = join;
181: limit = miterlimit;
182: this.dash = dash == null ? null : (float[]) dash.clone();
183: phase = dashPhase;
184: }
185:
186:
200: public BasicStroke(float width, int cap, int join, float miterlimit)
201: {
202: this(width, cap, join, miterlimit, null, 0);
203: }
204:
205:
218: public BasicStroke(float width, int cap, int join)
219: {
220: this(width, cap, join, 10, null, 0);
221: }
222:
223:
236: public BasicStroke(float width)
237: {
238: this(width, CAP_SQUARE, JOIN_MITER, 10, null, 0);
239: }
240:
241:
250: public BasicStroke()
251: {
252: this(1, CAP_SQUARE, JOIN_MITER, 10, null, 0);
253: }
254:
255:
261: public Shape createStrokedShape(Shape s)
262: {
263: PathIterator pi = s.getPathIterator(null);
264:
265: if( dash == null )
266: return solidStroke( pi );
267:
268: return dashedStroke( pi );
269: }
270:
271:
276: public float getLineWidth()
277: {
278: return width;
279: }
280:
281:
287: public int getEndCap()
288: {
289: return cap;
290: }
291:
292:
298: public int getLineJoin()
299: {
300: return join;
301: }
302:
303:
308: public float getMiterLimit()
309: {
310: return limit;
311: }
312:
313:
320: public float[] getDashArray()
321: {
322: return dash;
323: }
324:
325:
332: public float getDashPhase()
333: {
334: return phase;
335: }
336:
337:
346: public int hashCode()
347: {
348: int hash = Float.floatToIntBits(width);
349: hash ^= cap;
350: hash ^= join;
351: hash ^= Float.floatToIntBits(limit);
352:
353: if (dash != null)
354: for (int i = 0; i < dash.length; i++)
355: hash ^= Float.floatToIntBits(dash[i]);
356:
357: hash ^= Float.floatToIntBits(phase);
358:
359: return hash;
360: }
361:
362:
376: public boolean equals(Object o)
377: {
378: if (! (o instanceof BasicStroke))
379: return false;
380: BasicStroke s = (BasicStroke) o;
381: return width == s.width && cap == s.cap && join == s.join
382: && limit == s.limit && Arrays.equals(dash, s.dash) && phase == s.phase;
383: }
384:
385: private Shape solidStroke(PathIterator pi)
386: {
387: double[] coords = new double[6];
388: double x, y, x0, y0;
389: boolean pathOpen = false;
390: GeneralPath output = new GeneralPath( );
391: Segment[] p;
392: x = x0 = y = y0 = 0;
393:
394: while( !pi.isDone() )
395: {
396: switch( pi.currentSegment(coords) )
397: {
398: case PathIterator.SEG_MOVETO:
399: x0 = x = coords[0];
400: y0 = y = coords[1];
401: if( pathOpen )
402: {
403: capEnds();
404: convertPath(output, start);
405: start = end = null;
406: pathOpen = false;
407: }
408: break;
409:
410: case PathIterator.SEG_LINETO:
411: p = (new LineSegment(x, y, coords[0], coords[1])).
412: getDisplacedSegments(width/2.0);
413: if( !pathOpen )
414: {
415: start = p[0];
416: end = p[1];
417: pathOpen = true;
418: }
419: else
420: addSegments(p);
421:
422: x = coords[0];
423: y = coords[1];
424: break;
425:
426: case PathIterator.SEG_QUADTO:
427: p = (new QuadSegment(x, y, coords[0], coords[1], coords[2],
428: coords[3])).getDisplacedSegments(width/2.0);
429: if( !pathOpen )
430: {
431: start = p[0];
432: end = p[1];
433: pathOpen = true;
434: }
435: else
436: addSegments(p);
437:
438: x = coords[2];
439: y = coords[3];
440: break;
441:
442: case PathIterator.SEG_CUBICTO:
443: p = new CubicSegment(x, y, coords[0], coords[1],
444: coords[2], coords[3],
445: coords[4], coords[5]).getDisplacedSegments(width/2.0);
446: if( !pathOpen )
447: {
448: start = p[0];
449: end = p[1];
450: pathOpen = true;
451: }
452: else
453: addSegments(p);
454:
455: x = coords[4];
456: y = coords[5];
457: break;
458:
459: case PathIterator.SEG_CLOSE:
460: if (x == x0 && y == y0)
461: {
462: joinSegments(new Segment[] { start.first, end.first });
463: }
464: else
465: {
466: p = (new LineSegment(x, y, x0, y0)).getDisplacedSegments(width / 2.0);
467: addSegments(p);
468: }
469: convertPath(output, start);
470: convertPath(output, end);
471: start = end = null;
472: pathOpen = false;
473: output.setWindingRule(GeneralPath.WIND_EVEN_ODD);
474: break;
475: }
476: pi.next();
477: }
478:
479: if( pathOpen )
480: {
481: capEnds();
482: convertPath(output, start);
483: }
484: return output;
485: }
486:
487: private Shape dashedStroke(PathIterator pi)
488: {
489: GeneralPath out = new GeneralPath();
490: return out;
491: }
492:
493:
496: private void capEnds()
497: {
498: Segment returnPath = end.last;
499:
500: end.reverseAll();
501: end = null;
502: capEnd(start, returnPath);
503: start.last = returnPath.last;
504: end = null;
505:
506: capEnd(start, start);
507: }
508:
509:
512: private void convertPath(GeneralPath p, Segment s)
513: {
514: Segment v = s;
515: p.moveTo((float)s.P1.getX(), (float)s.P1.getY());
516:
517: do
518: {
519: if(v instanceof LineSegment)
520: p.lineTo((float)v.P2.getX(), (float)v.P2.getY());
521: else if(v instanceof QuadSegment)
522: p.quadTo((float)((QuadSegment)v).cp.getX(),
523: (float)((QuadSegment)v).cp.getY(),
524: (float)v.P2.getX(),
525: (float)v.P2.getY());
526: else if(v instanceof CubicSegment)
527: p.curveTo((float)((CubicSegment)v).cp1.getX(),
528: (float)((CubicSegment)v).cp1.getY(),
529: (float)((CubicSegment)v).cp2.getX(),
530: (float)((CubicSegment)v).cp2.getY(),
531: (float)v.P2.getX(),
532: (float)v.P2.getY());
533: v = v.next;
534: } while(v != s && v != null);
535:
536: p.closePath();
537: }
538:
539:
542: private void addSegments(Segment[] segments)
543: {
544: joinSegments(segments);
545: start.add(segments[0]);
546: end.add(segments[1]);
547: }
548:
549: private void joinSegments(Segment[] segments)
550: {
551: double[] p0 = start.last.cp2();
552: double[] p1 = new double[]{start.last.P2.getX(), start.last.P2.getY()};
553: double[] p2 = new double[]{segments[0].first.P1.getX(), segments[0].first.P1.getY()};
554: double[] p3 = segments[0].cp1();
555: Point2D p;
556:
557: p = lineIntersection(p0[0],p0[1],p1[0],p1[1],
558: p2[0],p2[1],p3[0],p3[1], false);
559:
560: double det = (p1[0] - p0[0])*(p3[1] - p2[1]) -
561: (p3[0] - p2[0])*(p1[1] - p0[1]);
562:
563: if( det > 0 )
564: {
565:
566:
567: joinInnerSegments(start, segments[0], p);
568: joinOuterSegments(end, segments[1], p);
569: }
570: else
571: {
572:
573: joinInnerSegments(end, segments[1], p);
574: joinOuterSegments(start, segments[0], p);
575: }
576: }
577:
578:
582: private void capEnd(Segment a, Segment b)
583: {
584: double[] p0, p1;
585: double dx, dy, l;
586: Point2D c1,c2;
587:
588: switch( cap )
589: {
590: case CAP_BUTT:
591: a.add(new LineSegment(a.last.P2, b.P1));
592: break;
593:
594: case CAP_SQUARE:
595: p0 = a.last.cp2();
596: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
597: dx = p1[0] - p0[0];
598: dy = p1[1] - p0[1];
599: l = Math.sqrt(dx * dx + dy * dy);
600: dx = 0.5*width*dx/l;
601: dy = 0.5*width*dy/l;
602: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
603: c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy);
604: a.add(new LineSegment(a.last.P2, c1));
605: a.add(new LineSegment(c1, c2));
606: a.add(new LineSegment(c2, b.P1));
607: break;
608:
609: case CAP_ROUND:
610: p0 = a.last.cp2();
611: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
612: dx = p1[0] - p0[0];
613: dy = p1[1] - p0[1];
614: l = Math.sqrt(dx * dx + dy * dy);
615: dx = (2.0/3.0)*width*dx/l;
616: dy = (2.0/3.0)*width*dy/l;
617: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
618: c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy);
619: a.add(new CubicSegment(a.last.P2, c1, c2, b.P1));
620: break;
621: }
622: a.add(b);
623: }
624:
625:
631: private Point2D lineIntersection(double X1, double Y1,
632: double X2, double Y2,
633: double X3, double Y3,
634: double X4, double Y4,
635: boolean infinite)
636: {
637: double x1 = X1;
638: double y1 = Y1;
639: double rx = X2 - x1;
640: double ry = Y2 - y1;
641:
642: double x2 = X3;
643: double y2 = Y3;
644: double sx = X4 - x2;
645: double sy = Y4 - y2;
646:
647: double determinant = sx * ry - sy * rx;
648: double nom = (sx * (y2 - y1) + sy * (x1 - x2));
649:
650:
651: if (Math.abs(determinant) < 1E-6)
652: return null;
653:
654: nom = nom / determinant;
655:
656:
657: if(!infinite && (nom > 1.0 || nom < 0.0))
658: return null;
659:
660: return new Point2D.Double(x1 + nom * rx, y1 + nom * ry);
661: }
662:
663:
669: private void joinOuterSegments(Segment a, Segment b, Point2D insideP)
670: {
671: double[] p0, p1;
672: double dx, dy, l;
673: Point2D c1,c2;
674:
675: switch( join )
676: {
677: case JOIN_MITER:
678: p0 = a.last.cp2();
679: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
680: double[] p2 = new double[]{b.P1.getX(), b.P1.getY()};
681: double[] p3 = b.cp1();
682: Point2D p = lineIntersection(p0[0],p0[1],p1[0],p1[1],p2[0],p2[1],p3[0],p3[1], true);
683: if( p == null || insideP == null )
684: a.add(new LineSegment(a.last.P2, b.P1));
685: else if((p.distance(insideP)/width) < limit)
686: {
687: a.add(new LineSegment(a.last.P2, p));
688: a.add(new LineSegment(p, b.P1));
689: }
690: else
691: {
692:
693: a.add(new LineSegment(a.last.P2, b.P1));
694: }
695: break;
696:
697: case JOIN_ROUND:
698: p0 = a.last.cp2();
699: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
700: dx = p1[0] - p0[0];
701: dy = p1[1] - p0[1];
702: l = Math.sqrt(dx * dx + dy * dy);
703: dx = 0.5*width*dx/l;
704: dy = 0.5*width*dy/l;
705: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
706:
707: p0 = new double[]{b.P1.getX(), b.P1.getY()};
708: p1 = b.cp1();
709:
710: dx = p0[0] - p1[0];
711: dy = p0[1] - p1[1];
712: l = Math.sqrt(dx * dx + dy * dy);
713: dx = 0.5*width*dx/l;
714: dy = 0.5*width*dy/l;
715: c2 = new Point2D.Double(p0[0] + dx, p0[1] + dy);
716: a.add(new CubicSegment(a.last.P2, c1, c2, b.P1));
717: break;
718:
719: case JOIN_BEVEL:
720: a.add(new LineSegment(a.last.P2, b.P1));
721: break;
722: }
723: }
724:
725:
728: private void joinInnerSegments(Segment a, Segment b, Point2D p)
729: {
730: double[] p0 = a.last.cp2();
731: double[] p1 = new double[] { a.last.P2.getX(), a.last.P2.getY() };
732: double[] p2 = new double[] { b.P1.getX(), b.P1.getY() };
733: double[] p3 = b.cp1();
734:
735: if (p == null)
736: {
737:
738: a.add(new LineSegment(a.last.P2, b.P1));
739: p = new Point2D.Double((b.P1.getX() + a.last.P2.getX()) / 2.0,
740: (b.P1.getY() + a.last.P2.getY()) / 2.0);
741: }
742: else
743:
744:
745:
746: a.last.P2 = b.P1 = p;
747: }
748: }