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