001    /* BasicStroke.java -- 
002       Copyright (C) 2002, 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt;
040    
041    import gnu.java.awt.java2d.CubicSegment;
042    import gnu.java.awt.java2d.LineSegment;
043    import gnu.java.awt.java2d.QuadSegment;
044    import gnu.java.awt.java2d.Segment;
045    
046    import java.awt.geom.FlatteningPathIterator;
047    import java.awt.geom.GeneralPath;
048    import java.awt.geom.PathIterator;
049    import java.awt.geom.Point2D;
050    import java.util.Arrays;
051    
052    /**
053     * A general purpose {@link Stroke} implementation that can represent a wide
054     * variety of line styles for use with subclasses of {@link Graphics2D}.
055     * <p>
056     * The line cap and join styles can be set using the options illustrated 
057     * here:
058     * <p>
059     * <img src="doc-files/capjoin.png" width="350" height="180"
060     * alt="Illustration of line cap and join styles" />
061     * <p>
062     * A dash array can be used to specify lines with alternating opaque and
063     * transparent sections.
064     */
065    public class BasicStroke implements Stroke
066    {
067      /** 
068       * Indicates a mitered line join style. See the class overview for an
069       * illustration.
070       */
071      public static final int JOIN_MITER = 0;
072      
073      /** 
074       * Indicates a rounded line join style. See the class overview for an
075       * illustration.
076       */
077      public static final int JOIN_ROUND = 1;
078      
079      /** 
080       * Indicates a bevelled line join style. See the class overview for an
081       * illustration.
082       */
083      public static final int JOIN_BEVEL = 2;
084    
085      /** 
086       * Indicates a flat line cap style. See the class overview for an
087       * illustration.
088       */
089      public static final int CAP_BUTT = 0;
090      
091      /** 
092       * Indicates a rounded line cap style. See the class overview for an
093       * illustration.
094       */
095      public static final int CAP_ROUND = 1;
096      
097      /** 
098       * Indicates a square line cap style. See the class overview for an
099       * 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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
299      public int getLineJoin()
300      {
301        return join;
302      }
303    
304      /**
305       * Returns the miter limit.
306       * 
307       * @return The miter limit.
308       */
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       */
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       */
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       */
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       */
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        // 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       */
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       */
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       */
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            // 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       */
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       */
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       */
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       */
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    }