001/* BasicStroke.java -- 
002   Copyright (C) 2002, 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt;
040
041import gnu.java.awt.java2d.CubicSegment;
042import gnu.java.awt.java2d.LineSegment;
043import gnu.java.awt.java2d.QuadSegment;
044import gnu.java.awt.java2d.Segment;
045
046import java.awt.geom.FlatteningPathIterator;
047import java.awt.geom.GeneralPath;
048import java.awt.geom.PathIterator;
049import java.awt.geom.Point2D;
050import 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 */
065public 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}