1 /* BasicStroke.java --
2    Copyright (C) 2002, 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.awt;
40 
41 import gnu.java.awt.java2d.CubicSegment;
42 import gnu.java.awt.java2d.LineSegment;
43 import gnu.java.awt.java2d.QuadSegment;
44 import gnu.java.awt.java2d.Segment;
45 
46 import java.awt.geom.FlatteningPathIterator;
47 import java.awt.geom.GeneralPath;
48 import java.awt.geom.PathIterator;
49 import java.awt.geom.Point2D;
50 import java.util.Arrays;
51 
52 /**
53  * A general purpose {@link Stroke} implementation that can represent a wide
54  * variety of line styles for use with subclasses of {@link Graphics2D}.
55  * <p>
56  * The line cap and join styles can be set using the options illustrated
57  * here:
58  * <p>
59  * <img src="doc-files/capjoin.png" width="350" height="180"
60  * alt="Illustration of line cap and join styles" />
61  * <p>
62  * A dash array can be used to specify lines with alternating opaque and
63  * transparent sections.
64  */
65 public class BasicStroke implements Stroke
66 {
67   /**
68    * Indicates a mitered line join style. See the class overview for an
69    * illustration.
70    */
71   public static final int JOIN_MITER = 0;
72 
73   /**
74    * Indicates a rounded line join style. See the class overview for an
75    * illustration.
76    */
77   public static final int JOIN_ROUND = 1;
78 
79   /**
80    * Indicates a bevelled line join style. See the class overview for an
81    * illustration.
82    */
83   public static final int JOIN_BEVEL = 2;
84 
85   /**
86    * Indicates a flat line cap style. See the class overview for an
87    * illustration.
88    */
89   public static final int CAP_BUTT = 0;
90 
91   /**
92    * Indicates a rounded line cap style. See the class overview for an
93    * illustration.
94    */
95   public static final int CAP_ROUND = 1;
96 
97   /**
98    * Indicates a square line cap style. See the class overview for an
99    * illustration.
100    */
101   public static final int CAP_SQUARE = 2;
102 
103   /** The stroke width. */
104   private final float width;
105 
106   /** The line cap style. */
107   private final int cap;
108 
109   /** The line join style. */
110   private final int join;
111 
112   /** The miter limit. */
113   private final float limit;
114 
115   /** The dash array. */
116   private final float[] dash;
117 
118   /** The dash phase. */
119   private final float phase;
120 
121   // The inner and outer paths of the stroke
122   private Segment start, end;
123 
124   /**
125    * Creates a new <code>BasicStroke</code> instance with the given attributes.
126    *
127    * @param width  the line width (>= 0.0f).
128    * @param cap  the line cap style (one of {@link #CAP_BUTT},
129    *             {@link #CAP_ROUND} or {@link #CAP_SQUARE}).
130    * @param join  the line join style (one of {@link #JOIN_ROUND},
131    *              {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}).
132    * @param miterlimit  the limit to trim the miter join. The miterlimit must be
133    * greater than or equal to 1.0f.
134    * @param dash The array representing the dashing pattern. There must be at
135    * least one non-zero entry.
136    * @param dashPhase is negative and dash is not null.
137    *
138    * @throws IllegalArgumentException If one input parameter doesn't meet
139    * its needs.
140    */
BasicStroke(float width, int cap, int join, float miterlimit, float[] dash, float dashPhase)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   /**
188    * Creates a new <code>BasicStroke</code> instance with the given attributes.
189    *
190    * @param width  the line width (>= 0.0f).
191    * @param cap  the line cap style (one of {@link #CAP_BUTT},
192    *             {@link #CAP_ROUND} or {@link #CAP_SQUARE}).
193    * @param join  the line join style (one of {@link #JOIN_ROUND},
194    *              {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}).
195    * @param miterlimit the limit to trim the miter join. The miterlimit must be
196    * greater than or equal to 1.0f.
197    *
198    * @throws IllegalArgumentException If one input parameter doesn't meet
199    * its needs.
200    */
BasicStroke(float width, int cap, int join, float miterlimit)201   public BasicStroke(float width, int cap, int join, float miterlimit)
202   {
203     this(width, cap, join, miterlimit, null, 0);
204   }
205 
206   /**
207    * Creates a new <code>BasicStroke</code> instance with the given attributes.
208    * The miter limit defaults to <code>10.0</code>.
209    *
210    * @param width  the line width (>= 0.0f).
211    * @param cap  the line cap style (one of {@link #CAP_BUTT},
212    *             {@link #CAP_ROUND} or {@link #CAP_SQUARE}).
213    * @param join  the line join style (one of {@link #JOIN_ROUND},
214    *              {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}).
215    *
216    * @throws IllegalArgumentException If one input parameter doesn't meet
217    * its needs.
218    */
BasicStroke(float width, int cap, int join)219   public BasicStroke(float width, int cap, int join)
220   {
221     this(width, cap, join, 10, null, 0);
222   }
223 
224   /**
225    * Creates a new <code>BasicStroke</code> instance with the given line
226    * width.  The default values are:
227    * <ul>
228    * <li>line cap style: {@link #CAP_SQUARE};</li>
229    * <li>line join style: {@link #JOIN_MITER};</li>
230    * <li>miter limit: <code>10.0f</code>.
231    * </ul>
232    *
233    * @param width  the line width (>= 0.0f).
234    *
235    * @throws IllegalArgumentException If <code>width</code> is negative.
236    */
BasicStroke(float width)237   public BasicStroke(float width)
238   {
239     this(width, CAP_SQUARE, JOIN_MITER, 10, null, 0);
240   }
241 
242   /**
243    * Creates a new <code>BasicStroke</code> instance.  The default values are:
244    * <ul>
245    * <li>line width: <code>1.0f</code>;</li>
246    * <li>line cap style: {@link #CAP_SQUARE};</li>
247    * <li>line join style: {@link #JOIN_MITER};</li>
248    * <li>miter limit: <code>10.0f</code>.
249    * </ul>
250    */
BasicStroke()251   public BasicStroke()
252   {
253     this(1, CAP_SQUARE, JOIN_MITER, 10, null, 0);
254   }
255 
256   /**
257    * Creates a shape representing the stroked outline of the given shape.
258    * THIS METHOD IS NOT YET IMPLEMENTED.
259    *
260    * @param s  the shape.
261    */
createStrokedShape(Shape s)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   /**
273    * Returns the line width.
274    *
275    * @return The line width.
276    */
getLineWidth()277   public float getLineWidth()
278   {
279     return width;
280   }
281 
282   /**
283    * Returns a code indicating the line cap style (one of {@link #CAP_BUTT},
284    * {@link #CAP_ROUND}, {@link #CAP_SQUARE}).
285    *
286    * @return A code indicating the line cap style.
287    */
getEndCap()288   public int getEndCap()
289   {
290     return cap;
291   }
292 
293   /**
294    * Returns a code indicating the line join style (one of {@link #JOIN_BEVEL},
295    * {@link #JOIN_MITER} or {@link #JOIN_ROUND}).
296    *
297    * @return A code indicating the line join style.
298    */
getLineJoin()299   public int getLineJoin()
300   {
301     return join;
302   }
303 
304   /**
305    * Returns the miter limit.
306    *
307    * @return The miter limit.
308    */
getMiterLimit()309   public float getMiterLimit()
310   {
311     return limit;
312   }
313 
314   /**
315    * Returns the dash array, which defines the length of alternate opaque and
316    * transparent sections in lines drawn with this stroke.  If
317    * <code>null</code>, a continuous line will be drawn.
318    *
319    * @return The dash array (possibly <code>null</code>).
320    */
getDashArray()321   public float[] getDashArray()
322   {
323     return dash;
324   }
325 
326   /**
327    * Returns the dash phase for the stroke.  This is the offset from the start
328    * of a path at which the pattern defined by {@link #getDashArray()} is
329    * rendered.
330    *
331    * @return The dash phase.
332    */
getDashPhase()333   public float getDashPhase()
334   {
335     return phase;
336   }
337 
338   /**
339    * Returns the hash code for this object. The hash is calculated by
340    * xoring the hash, cap, join, limit, dash array and phase values
341    * (converted to <code>int</code> first with
342    * <code>Float.floatToIntBits()</code> if the value is a
343    * <code>float</code>).
344    *
345    * @return The hash code.
346    */
hashCode()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   /**
364    * Compares this <code>BasicStroke</code> for equality with an arbitrary
365    * object.  This method returns <code>true</code> if and only if:
366    * <ul>
367    * <li><code>o</code> is an instanceof <code>BasicStroke</code>;</li>
368    * <li>this object has the same width, line cap style, line join style,
369    * miter limit, dash array and dash phase as <code>o</code>.</li>
370    * </ul>
371    *
372    * @param o  the object (<code>null</code> permitted).
373    *
374    * @return <code>true</code> if this stroke is equal to <code>o</code> and
375    *         <code>false</code> otherwise.
376    */
equals(Object o)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 
solidStroke(PathIterator pi)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 
dashedStroke(PathIterator pi)488   private Shape dashedStroke(PathIterator pi)
489   {
490     // The choice of (flatnessSq == width / 3) is made to be consistent with
491     // the flattening in CubicSegment.getDisplacedSegments
492     FlatteningPathIterator flat = new FlatteningPathIterator(pi,
493                                                              Math.sqrt(width / 3));
494 
495     // Holds the endpoint of the current segment (or piece of a segment)
496     double[] coords = new double[2];
497 
498     // Holds end of the last segment
499     double x, y, x0, y0;
500     x = x0 = y = y0 = 0;
501 
502     // Various useful flags
503     boolean pathOpen = false;
504     boolean dashOn = true;
505     boolean offsetting = (phase != 0);
506 
507     // How far we are into the current dash
508     double distance = 0;
509     int dashIndex = 0;
510 
511     // And variables to hold the final output
512     GeneralPath output = new GeneralPath();
513     Segment[] p;
514 
515     // Iterate over the FlatteningPathIterator
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                 // Find the total remaining length of this segment
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                 // The current segment fits entirely inside the current dash
547                 if ((offsetting && distance + segLength <= phase)
548                     || distance + segLength <= dash[dashIndex])
549                   {
550                     spanBoundary = false;
551                   }
552 
553                 // Otherwise, we need to split the segment in two, as this
554                 // segment spans a dash boundry
555                 else
556                   {
557                     segmentEnd = (double[]) coords.clone();
558 
559                     // Calculate the remaining distance in this dash,
560                     // and coordinates of the dash boundary
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                     // Dash is off, or we are in offset - treat this as a
574                     // moveTo
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                     // Dash is on - treat this as a lineTo
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                 // Update variables depending on whether we spanned a
605                 // dash boundary or not
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                     // Since the value of segmentConsumed is still false,
625                     // the next run of the while loop will complete the segment
626                   }
627               }
628             break;
629 
630           // This is a flattened path, so we don't need to deal with curves
631           }
632         flat.next();
633       }
634 
635     if (pathOpen)
636       {
637         capEnds();
638         convertPath(output, start);
639       }
640     return output;
641   }
642 
643   /**
644    * Cap the ends of the path (joining the start and end list of segments)
645    */
capEnds()646   private void capEnds()
647   {
648     Segment returnPath = end.last;
649 
650     end.reverseAll(); // reverse the path.
651     end = null;
652     capEnd(start, returnPath);
653     start.last = returnPath.last;
654     end = null;
655 
656     capEnd(start, start);
657   }
658 
659   /**
660    * Append the Segments in s to the GeneralPath p
661    */
convertPath(GeneralPath p, Segment s)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   /**
690    * Add the segments to start and end (the inner and outer edges of the stroke)
691    */
addSegments(Segment[] segments)692   private void addSegments(Segment[] segments)
693   {
694     joinSegments(segments);
695     start.add(segments[0]);
696     end.add(segments[1]);
697   }
698 
joinSegments(Segment[] segments)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         // start and segment[0] form the 'inner' part of a join,
716         // connect the overlapping segments
717         joinInnerSegments(start, segments[0], p);
718         joinOuterSegments(end, segments[1], p);
719       }
720     else
721       {
722         // end and segment[1] form the 'inner' part
723         joinInnerSegments(end, segments[1], p);
724         joinOuterSegments(start, segments[0], p);
725       }
726   }
727 
728   /**
729    * Make a cap between a and b segments,
730    * where a-->b is the direction of iteration.
731    */
capEnd(Segment a, Segment b)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   /**
780    * Returns the intersection of two lines, or null if there isn't one.
781    * @param infinite - true if the lines should be regarded as infinite, false
782    * if the intersection must be within the given segments.
783    * @return a Point2D or null.
784    */
lineIntersection(double X1, double Y1, double X2, double Y2, double X3, double Y3, double X4, double Y4, boolean infinite)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     // lines can be considered parallel.
805     if (Math.abs(determinant) < 1E-6)
806       return null;
807 
808     nom = nom / determinant;
809 
810     // check if lines are within the bounds
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   /**
818    * Join a and b segments, where a-->b is the direction of iteration.
819    *
820    * insideP is the inside intersection point of the join, needed for
821    * calculating miter lengths.
822    */
joinOuterSegments(Segment a, Segment b, Point2D insideP)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             // outside miter limit, do a bevel join.
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]; // backwards direction.
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   /**
880    * Join a and b segments, removing any overlap
881    */
joinInnerSegments(Segment a, Segment b, Point2D p)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         // Dodgy.
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       // This assumes segments a and b are single segments, which is
898       // incorrect - if they are a linked list of segments (ie, passed in
899       // from a flattening operation), this produces strange results!!
900       a.last.P2 = b.P1 = p;
901   }
902 }
903