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 }