1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45:
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51:
52:
65: public class BasicStroke implements Stroke
66: {
67:
71: public static final int JOIN_MITER = 0;
72:
73:
77: public static final int JOIN_ROUND = 1;
78:
79:
83: public static final int JOIN_BEVEL = 2;
84:
85:
89: public static final int CAP_BUTT = 0;
90:
91:
95: public static final int CAP_ROUND = 1;
96:
97:
101: public static final int CAP_SQUARE = 2;
102:
103:
104: private final float width;
105:
106:
107: private final int cap;
108:
109:
110: private final int join;
111:
112:
113: private final float limit;
114:
115:
116: private final float[] dash;
117:
118:
119: private final float phase;
120:
121:
122: private Segment start, end;
123:
124:
141: public BasicStroke(float width, int cap, int join, float miterlimit,
142: float[] dash, float dashPhase)
143: {
144: if (width < 0.0f )
145: throw new IllegalArgumentException("width " + width + " < 0");
146: else if (cap < CAP_BUTT || cap > CAP_SQUARE)
147: throw new IllegalArgumentException("cap " + cap + " out of range ["
148: + CAP_BUTT + ".." + CAP_SQUARE + "]");
149: else if (miterlimit < 1.0f && join == JOIN_MITER)
150: throw new IllegalArgumentException("miterlimit " + miterlimit
151: + " < 1.0f while join == JOIN_MITER");
152: else if (join < JOIN_MITER || join > JOIN_BEVEL)
153: throw new IllegalArgumentException("join " + join + " out of range ["
154: + JOIN_MITER + ".." + JOIN_BEVEL
155: + "]");
156: else if (dashPhase < 0.0f && dash != null)
157: throw new IllegalArgumentException("dashPhase " + dashPhase
158: + " < 0.0f while dash != null");
159: else if (dash != null)
160: if (dash.length == 0)
161: throw new IllegalArgumentException("dash.length is 0");
162: else
163: {
164: boolean allZero = true;
165:
166: for ( int i = 0; i < dash.length; ++i)
167: {
168: if (dash[i] != 0.0f)
169: {
170: allZero = false;
171: break;
172: }
173: }
174:
175: if (allZero)
176: throw new IllegalArgumentException("all dashes are 0.0f");
177: }
178:
179: this.width = width;
180: this.cap = cap;
181: this.join = join;
182: limit = miterlimit;
183: this.dash = dash == null ? null : (float[]) dash.clone();
184: phase = dashPhase;
185: }
186:
187:
201: public BasicStroke(float width, int cap, int join, float miterlimit)
202: {
203: this(width, cap, join, miterlimit, null, 0);
204: }
205:
206:
219: public BasicStroke(float width, int cap, int join)
220: {
221: this(width, cap, join, 10, null, 0);
222: }
223:
224:
237: public BasicStroke(float width)
238: {
239: this(width, CAP_SQUARE, JOIN_MITER, 10, null, 0);
240: }
241:
242:
251: public BasicStroke()
252: {
253: this(1, CAP_SQUARE, JOIN_MITER, 10, null, 0);
254: }
255:
256:
262: public Shape createStrokedShape(Shape s)
263: {
264: PathIterator pi = s.getPathIterator(null);
265:
266: if( dash == null )
267: return solidStroke( pi );
268:
269: return dashedStroke( pi );
270: }
271:
272:
277: public float getLineWidth()
278: {
279: return width;
280: }
281:
282:
288: public int getEndCap()
289: {
290: return cap;
291: }
292:
293:
299: public int getLineJoin()
300: {
301: return join;
302: }
303:
304:
309: public float getMiterLimit()
310: {
311: return limit;
312: }
313:
314:
321: public float[] getDashArray()
322: {
323: return dash;
324: }
325:
326:
333: public float getDashPhase()
334: {
335: return phase;
336: }
337:
338:
347: public int hashCode()
348: {
349: int hash = Float.floatToIntBits(width);
350: hash ^= cap;
351: hash ^= join;
352: hash ^= Float.floatToIntBits(limit);
353:
354: if (dash != null)
355: for (int i = 0; i < dash.length; i++)
356: hash ^= Float.floatToIntBits(dash[i]);
357:
358: hash ^= Float.floatToIntBits(phase);
359:
360: return hash;
361: }
362:
363:
377: public boolean equals(Object o)
378: {
379: if (! (o instanceof BasicStroke))
380: return false;
381: BasicStroke s = (BasicStroke) o;
382: return width == s.width && cap == s.cap && join == s.join
383: && limit == s.limit && Arrays.equals(dash, s.dash) && phase == s.phase;
384: }
385:
386: private Shape solidStroke(PathIterator pi)
387: {
388: double[] coords = new double[6];
389: double x, y, x0, y0;
390: boolean pathOpen = false;
391: GeneralPath output = new GeneralPath( );
392: Segment[] p;
393: x = x0 = y = y0 = 0;
394:
395: while( !pi.isDone() )
396: {
397: switch( pi.currentSegment(coords) )
398: {
399: case PathIterator.SEG_MOVETO:
400: x0 = x = coords[0];
401: y0 = y = coords[1];
402: if( pathOpen )
403: {
404: capEnds();
405: convertPath(output, start);
406: start = end = null;
407: pathOpen = false;
408: }
409: break;
410:
411: case PathIterator.SEG_LINETO:
412: p = (new LineSegment(x, y, coords[0], coords[1])).
413: getDisplacedSegments(width/2.0);
414: if( !pathOpen )
415: {
416: start = p[0];
417: end = p[1];
418: pathOpen = true;
419: }
420: else
421: addSegments(p);
422:
423: x = coords[0];
424: y = coords[1];
425: break;
426:
427: case PathIterator.SEG_QUADTO:
428: p = (new QuadSegment(x, y, coords[0], coords[1], coords[2],
429: coords[3])).getDisplacedSegments(width/2.0);
430: if( !pathOpen )
431: {
432: start = p[0];
433: end = p[1];
434: pathOpen = true;
435: }
436: else
437: addSegments(p);
438:
439: x = coords[2];
440: y = coords[3];
441: break;
442:
443: case PathIterator.SEG_CUBICTO:
444: p = new CubicSegment(x, y, coords[0], coords[1],
445: coords[2], coords[3],
446: coords[4], coords[5]).getDisplacedSegments(width/2.0);
447: if( !pathOpen )
448: {
449: start = p[0];
450: end = p[1];
451: pathOpen = true;
452: }
453: else
454: addSegments(p);
455:
456: x = coords[4];
457: y = coords[5];
458: break;
459:
460: case PathIterator.SEG_CLOSE:
461: if (x == x0 && y == y0)
462: {
463: joinSegments(new Segment[] { start.first, end.first });
464: }
465: else
466: {
467: p = (new LineSegment(x, y, x0, y0)).getDisplacedSegments(width / 2.0);
468: addSegments(p);
469: }
470: convertPath(output, start);
471: convertPath(output, end);
472: start = end = null;
473: pathOpen = false;
474: output.setWindingRule(GeneralPath.WIND_EVEN_ODD);
475: break;
476: }
477: pi.next();
478: }
479:
480: if( pathOpen )
481: {
482: capEnds();
483: convertPath(output, start);
484: }
485: return output;
486: }
487:
488: private Shape dashedStroke(PathIterator pi)
489: {
490:
491:
492: FlatteningPathIterator flat = new FlatteningPathIterator(pi,
493: Math.sqrt(width / 3));
494:
495:
496: double[] coords = new double[2];
497:
498:
499: double x, y, x0, y0;
500: x = x0 = y = y0 = 0;
501:
502:
503: boolean pathOpen = false;
504: boolean dashOn = true;
505: boolean offsetting = (phase != 0);
506:
507:
508: double distance = 0;
509: int dashIndex = 0;
510:
511:
512: GeneralPath output = new GeneralPath();
513: Segment[] p;
514:
515:
516: while (! flat.isDone())
517: {
518: switch (flat.currentSegment(coords))
519: {
520: case PathIterator.SEG_MOVETO:
521: x0 = x = coords[0];
522: y0 = y = coords[1];
523:
524: if (pathOpen)
525: {
526: capEnds();
527: convertPath(output, start);
528: start = end = null;
529: pathOpen = false;
530: }
531:
532: break;
533:
534: case PathIterator.SEG_LINETO:
535: boolean segmentConsumed = false;
536:
537: while (! segmentConsumed)
538: {
539:
540: double segLength = Math.sqrt((x - coords[0]) * (x - coords[0])
541: + (y - coords[1])
542: * (y - coords[1]));
543: boolean spanBoundary = true;
544: double[] segmentEnd = null;
545:
546:
547: if ((offsetting && distance + segLength <= phase)
548: || distance + segLength <= dash[dashIndex])
549: {
550: spanBoundary = false;
551: }
552:
553:
554:
555: else
556: {
557: segmentEnd = (double[]) coords.clone();
558:
559:
560:
561: double reqLength;
562: if (offsetting)
563: reqLength = phase - distance;
564: else
565: reqLength = dash[dashIndex] - distance;
566:
567: coords[0] = x + ((coords[0] - x) * reqLength / segLength);
568: coords[1] = y + ((coords[1] - y) * reqLength / segLength);
569: }
570:
571: if (offsetting || ! dashOn)
572: {
573:
574:
575: x0 = x = coords[0];
576: y0 = y = coords[1];
577:
578: if (pathOpen)
579: {
580: capEnds();
581: convertPath(output, start);
582: start = end = null;
583: pathOpen = false;
584: }
585: }
586: else
587: {
588:
589: p = (new LineSegment(x, y, coords[0], coords[1])).getDisplacedSegments(width / 2.0);
590:
591: if (! pathOpen)
592: {
593: start = p[0];
594: end = p[1];
595: pathOpen = true;
596: }
597: else
598: addSegments(p);
599:
600: x = coords[0];
601: y = coords[1];
602: }
603:
604:
605:
606: if (! spanBoundary)
607: {
608: distance += segLength;
609: segmentConsumed = true;
610: }
611: else
612: {
613: if (offsetting)
614: offsetting = false;
615: dashOn = ! dashOn;
616: distance = 0;
617: coords = segmentEnd;
618:
619: if (dashIndex + 1 == dash.length)
620: dashIndex = 0;
621: else
622: dashIndex++;
623:
624:
625:
626: }
627: }
628: break;
629:
630:
631: }
632: flat.next();
633: }
634:
635: if (pathOpen)
636: {
637: capEnds();
638: convertPath(output, start);
639: }
640: return output;
641: }
642:
643:
646: private void capEnds()
647: {
648: Segment returnPath = end.last;
649:
650: end.reverseAll();
651: end = null;
652: capEnd(start, returnPath);
653: start.last = returnPath.last;
654: end = null;
655:
656: capEnd(start, start);
657: }
658:
659:
662: private void convertPath(GeneralPath p, Segment s)
663: {
664: Segment v = s;
665: p.moveTo((float)s.P1.getX(), (float)s.P1.getY());
666:
667: do
668: {
669: if(v instanceof LineSegment)
670: p.lineTo((float)v.P2.getX(), (float)v.P2.getY());
671: else if(v instanceof QuadSegment)
672: p.quadTo((float)((QuadSegment)v).cp.getX(),
673: (float)((QuadSegment)v).cp.getY(),
674: (float)v.P2.getX(),
675: (float)v.P2.getY());
676: else if(v instanceof CubicSegment)
677: p.curveTo((float)((CubicSegment)v).cp1.getX(),
678: (float)((CubicSegment)v).cp1.getY(),
679: (float)((CubicSegment)v).cp2.getX(),
680: (float)((CubicSegment)v).cp2.getY(),
681: (float)v.P2.getX(),
682: (float)v.P2.getY());
683: v = v.next;
684: } while(v != s && v != null);
685:
686: p.closePath();
687: }
688:
689:
692: private void addSegments(Segment[] segments)
693: {
694: joinSegments(segments);
695: start.add(segments[0]);
696: end.add(segments[1]);
697: }
698:
699: private void joinSegments(Segment[] segments)
700: {
701: double[] p0 = start.last.cp2();
702: double[] p1 = new double[]{start.last.P2.getX(), start.last.P2.getY()};
703: double[] p2 = new double[]{segments[0].first.P1.getX(), segments[0].first.P1.getY()};
704: double[] p3 = segments[0].cp1();
705: Point2D p;
706:
707: p = lineIntersection(p0[0],p0[1],p1[0],p1[1],
708: p2[0],p2[1],p3[0],p3[1], false);
709:
710: double det = (p1[0] - p0[0])*(p3[1] - p2[1]) -
711: (p3[0] - p2[0])*(p1[1] - p0[1]);
712:
713: if( det > 0 )
714: {
715:
716:
717: joinInnerSegments(start, segments[0], p);
718: joinOuterSegments(end, segments[1], p);
719: }
720: else
721: {
722:
723: joinInnerSegments(end, segments[1], p);
724: joinOuterSegments(start, segments[0], p);
725: }
726: }
727:
728:
732: private void capEnd(Segment a, Segment b)
733: {
734: double[] p0, p1;
735: double dx, dy, l;
736: Point2D c1,c2;
737:
738: switch( cap )
739: {
740: case CAP_BUTT:
741: a.add(new LineSegment(a.last.P2, b.P1));
742: break;
743:
744: case CAP_SQUARE:
745: p0 = a.last.cp2();
746: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
747: dx = p1[0] - p0[0];
748: dy = p1[1] - p0[1];
749: l = Math.sqrt(dx * dx + dy * dy);
750: dx = 0.5*width*dx/l;
751: dy = 0.5*width*dy/l;
752: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
753: c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy);
754: a.add(new LineSegment(a.last.P2, c1));
755: a.add(new LineSegment(c1, c2));
756: a.add(new LineSegment(c2, b.P1));
757: break;
758:
759: case CAP_ROUND:
760: p0 = a.last.cp2();
761: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
762: dx = p1[0] - p0[0];
763: dy = p1[1] - p0[1];
764: if (dx != 0 && dy != 0)
765: {
766: l = Math.sqrt(dx * dx + dy * dy);
767: dx = (2.0/3.0)*width*dx/l;
768: dy = (2.0/3.0)*width*dy/l;
769: }
770:
771: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
772: c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy);
773: a.add(new CubicSegment(a.last.P2, c1, c2, b.P1));
774: break;
775: }
776: a.add(b);
777: }
778:
779:
785: private Point2D lineIntersection(double X1, double Y1,
786: double X2, double Y2,
787: double X3, double Y3,
788: double X4, double Y4,
789: boolean infinite)
790: {
791: double x1 = X1;
792: double y1 = Y1;
793: double rx = X2 - x1;
794: double ry = Y2 - y1;
795:
796: double x2 = X3;
797: double y2 = Y3;
798: double sx = X4 - x2;
799: double sy = Y4 - y2;
800:
801: double determinant = sx * ry - sy * rx;
802: double nom = (sx * (y2 - y1) + sy * (x1 - x2));
803:
804:
805: if (Math.abs(determinant) < 1E-6)
806: return null;
807:
808: nom = nom / determinant;
809:
810:
811: if(!infinite && (nom > 1.0 || nom < 0.0))
812: return null;
813:
814: return new Point2D.Double(x1 + nom * rx, y1 + nom * ry);
815: }
816:
817:
823: private void joinOuterSegments(Segment a, Segment b, Point2D insideP)
824: {
825: double[] p0, p1;
826: double dx, dy, l;
827: Point2D c1,c2;
828:
829: switch( join )
830: {
831: case JOIN_MITER:
832: p0 = a.last.cp2();
833: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
834: double[] p2 = new double[]{b.P1.getX(), b.P1.getY()};
835: double[] p3 = b.cp1();
836: Point2D p = lineIntersection(p0[0],p0[1],p1[0],p1[1],p2[0],p2[1],p3[0],p3[1], true);
837: if( p == null || insideP == null )
838: a.add(new LineSegment(a.last.P2, b.P1));
839: else if((p.distance(insideP)/width) < limit)
840: {
841: a.add(new LineSegment(a.last.P2, p));
842: a.add(new LineSegment(p, b.P1));
843: }
844: else
845: {
846:
847: a.add(new LineSegment(a.last.P2, b.P1));
848: }
849: break;
850:
851: case JOIN_ROUND:
852: p0 = a.last.cp2();
853: p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
854: dx = p1[0] - p0[0];
855: dy = p1[1] - p0[1];
856: l = Math.sqrt(dx * dx + dy * dy);
857: dx = 0.5*width*dx/l;
858: dy = 0.5*width*dy/l;
859: c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
860:
861: p0 = new double[]{b.P1.getX(), b.P1.getY()};
862: p1 = b.cp1();
863:
864: dx = p0[0] - p1[0];
865: dy = p0[1] - p1[1];
866: l = Math.sqrt(dx * dx + dy * dy);
867: dx = 0.5*width*dx/l;
868: dy = 0.5*width*dy/l;
869: c2 = new Point2D.Double(p0[0] + dx, p0[1] + dy);
870: a.add(new CubicSegment(a.last.P2, c1, c2, b.P1));
871: break;
872:
873: case JOIN_BEVEL:
874: a.add(new LineSegment(a.last.P2, b.P1));
875: break;
876: }
877: }
878:
879:
882: private void joinInnerSegments(Segment a, Segment b, Point2D p)
883: {
884: double[] p0 = a.last.cp2();
885: double[] p1 = new double[] { a.last.P2.getX(), a.last.P2.getY() };
886: double[] p2 = new double[] { b.P1.getX(), b.P1.getY() };
887: double[] p3 = b.cp1();
888:
889: if (p == null)
890: {
891:
892: a.add(new LineSegment(a.last.P2, b.P1));
893: p = new Point2D.Double((b.P1.getX() + a.last.P2.getX()) / 2.0,
894: (b.P1.getY() + a.last.P2.getY()) / 2.0);
895: }
896: else
897:
898:
899:
900: a.last.P2 = b.P1 = p;
901: }
902: }