1 /* 2 * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.geom; 27 28 import java.awt.Shape; 29 import java.awt.Rectangle; 30 import java.util.Vector; 31 import java.util.Enumeration; 32 import java.util.NoSuchElementException; 33 import sun.awt.geom.Curve; 34 import sun.awt.geom.Crossings; 35 import sun.awt.geom.AreaOp; 36 37 /** 38 * An {@code Area} object stores and manipulates a 39 * resolution-independent description of an enclosed area of 40 * 2-dimensional space. 41 * {@code Area} objects can be transformed and can perform 42 * various Constructive Area Geometry (CAG) operations when combined 43 * with other {@code Area} objects. 44 * The CAG operations include area 45 * {@link #add addition}, {@link #subtract subtraction}, 46 * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}. 47 * See the linked method documentation for examples of the various 48 * operations. 49 * <p> 50 * The {@code Area} class implements the {@code Shape} 51 * interface and provides full support for all of its hit-testing 52 * and path iteration facilities, but an {@code Area} is more 53 * specific than a generalized path in a number of ways: 54 * <ul> 55 * <li>Only closed paths and sub-paths are stored. 56 * {@code Area} objects constructed from unclosed paths 57 * are implicitly closed during construction as if those paths 58 * had been filled by the {@code Graphics2D.fill} method. 59 * <li>The interiors of the individual stored sub-paths are all 60 * non-empty and non-overlapping. Paths are decomposed during 61 * construction into separate component non-overlapping parts, 62 * empty pieces of the path are discarded, and then these 63 * non-empty and non-overlapping properties are maintained 64 * through all subsequent CAG operations. Outlines of different 65 * component sub-paths may touch each other, as long as they 66 * do not cross so that their enclosed areas overlap. 67 * <li>The geometry of the path describing the outline of the 68 * {@code Area} resembles the path from which it was 69 * constructed only in that it describes the same enclosed 70 * 2-dimensional area, but may use entirely different types 71 * and ordering of the path segments to do so. 72 * </ul> 73 * Interesting issues which are not always obvious when using 74 * the {@code Area} include: 75 * <ul> 76 * <li>Creating an {@code Area} from an unclosed (open) 77 * {@code Shape} results in a closed outline in the 78 * {@code Area} object. 79 * <li>Creating an {@code Area} from a {@code Shape} 80 * which encloses no area (even when "closed") produces an 81 * empty {@code Area}. A common example of this issue 82 * is that producing an {@code Area} from a line will 83 * be empty since the line encloses no area. An empty 84 * {@code Area} will iterate no geometry in its 85 * {@code PathIterator} objects. 86 * <li>A self-intersecting {@code Shape} may be split into 87 * two (or more) sub-paths each enclosing one of the 88 * non-intersecting portions of the original path. 89 * <li>An {@code Area} may take more path segments to 90 * describe the same geometry even when the original 91 * outline is simple and obvious. The analysis that the 92 * {@code Area} class must perform on the path may 93 * not reflect the same concepts of "simple and obvious" 94 * as a human being perceives. 95 * </ul> 96 * 97 * @since 1.2 98 */ 99 public class Area implements Shape, Cloneable { 100 private static Vector<Curve> EmptyCurves = new Vector<>(); 101 102 private Vector<Curve> curves; 103 104 /** 105 * Default constructor which creates an empty area. 106 * @since 1.2 107 */ Area()108 public Area() { 109 curves = EmptyCurves; 110 } 111 112 /** 113 * The {@code Area} class creates an area geometry from the 114 * specified {@link Shape} object. The geometry is explicitly 115 * closed, if the {@code Shape} is not already closed. The 116 * fill rule (even-odd or winding) specified by the geometry of the 117 * {@code Shape} is used to determine the resulting enclosed area. 118 * @param s the {@code Shape} from which the area is constructed 119 * @throws NullPointerException if {@code s} is null 120 * @since 1.2 121 */ Area(Shape s)122 public Area(Shape s) { 123 if (s instanceof Area) { 124 curves = ((Area) s).curves; 125 } else { 126 curves = pathToCurves(s.getPathIterator(null)); 127 } 128 } 129 pathToCurves(PathIterator pi)130 private static Vector<Curve> pathToCurves(PathIterator pi) { 131 Vector<Curve> curves = new Vector<>(); 132 int windingRule = pi.getWindingRule(); 133 // coords array is big enough for holding: 134 // coordinates returned from currentSegment (6) 135 // OR 136 // two subdivided quadratic curves (2+4+4=10) 137 // AND 138 // 0-1 horizontal splitting parameters 139 // OR 140 // 2 parametric equation derivative coefficients 141 // OR 142 // three subdivided cubic curves (2+6+6+6=20) 143 // AND 144 // 0-2 horizontal splitting parameters 145 // OR 146 // 3 parametric equation derivative coefficients 147 double[] coords = new double[23]; 148 double movx = 0, movy = 0; 149 double curx = 0, cury = 0; 150 double newx, newy; 151 while (!pi.isDone()) { 152 switch (pi.currentSegment(coords)) { 153 case PathIterator.SEG_MOVETO: 154 Curve.insertLine(curves, curx, cury, movx, movy); 155 curx = movx = coords[0]; 156 cury = movy = coords[1]; 157 Curve.insertMove(curves, movx, movy); 158 break; 159 case PathIterator.SEG_LINETO: 160 newx = coords[0]; 161 newy = coords[1]; 162 Curve.insertLine(curves, curx, cury, newx, newy); 163 curx = newx; 164 cury = newy; 165 break; 166 case PathIterator.SEG_QUADTO: 167 newx = coords[2]; 168 newy = coords[3]; 169 Curve.insertQuad(curves, curx, cury, coords); 170 curx = newx; 171 cury = newy; 172 break; 173 case PathIterator.SEG_CUBICTO: 174 newx = coords[4]; 175 newy = coords[5]; 176 Curve.insertCubic(curves, curx, cury, coords); 177 curx = newx; 178 cury = newy; 179 break; 180 case PathIterator.SEG_CLOSE: 181 Curve.insertLine(curves, curx, cury, movx, movy); 182 curx = movx; 183 cury = movy; 184 break; 185 } 186 pi.next(); 187 } 188 Curve.insertLine(curves, curx, cury, movx, movy); 189 AreaOp operator; 190 if (windingRule == PathIterator.WIND_EVEN_ODD) { 191 operator = new AreaOp.EOWindOp(); 192 } else { 193 operator = new AreaOp.NZWindOp(); 194 } 195 return operator.calculate(curves, EmptyCurves); 196 } 197 198 /** 199 * Adds the shape of the specified {@code Area} to the 200 * shape of this {@code Area}. 201 * The resulting shape of this {@code Area} will include 202 * the union of both shapes, or all areas that were contained 203 * in either this or the specified {@code Area}. 204 * <pre> 205 * // Example: 206 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 207 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 208 * a1.add(a2); 209 * 210 * a1(before) + a2 = a1(after) 211 * 212 * ################ ################ ################ 213 * ############## ############## ################ 214 * ############ ############ ################ 215 * ########## ########## ################ 216 * ######## ######## ################ 217 * ###### ###### ###### ###### 218 * #### #### #### #### 219 * ## ## ## ## 220 * </pre> 221 * @param rhs the {@code Area} to be added to the 222 * current shape 223 * @throws NullPointerException if {@code rhs} is null 224 * @since 1.2 225 */ add(Area rhs)226 public void add(Area rhs) { 227 curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves); 228 invalidateBounds(); 229 } 230 231 /** 232 * Subtracts the shape of the specified {@code Area} from the 233 * shape of this {@code Area}. 234 * The resulting shape of this {@code Area} will include 235 * areas that were contained only in this {@code Area} 236 * and not in the specified {@code Area}. 237 * <pre> 238 * // Example: 239 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 240 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 241 * a1.subtract(a2); 242 * 243 * a1(before) - a2 = a1(after) 244 * 245 * ################ ################ 246 * ############## ############## ## 247 * ############ ############ #### 248 * ########## ########## ###### 249 * ######## ######## ######## 250 * ###### ###### ###### 251 * #### #### #### 252 * ## ## ## 253 * </pre> 254 * @param rhs the {@code Area} to be subtracted from the 255 * current shape 256 * @throws NullPointerException if {@code rhs} is null 257 * @since 1.2 258 */ subtract(Area rhs)259 public void subtract(Area rhs) { 260 curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves); 261 invalidateBounds(); 262 } 263 264 /** 265 * Sets the shape of this {@code Area} to the intersection of 266 * its current shape and the shape of the specified {@code Area}. 267 * The resulting shape of this {@code Area} will include 268 * only areas that were contained in both this {@code Area} 269 * and also in the specified {@code Area}. 270 * <pre> 271 * // Example: 272 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 273 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 274 * a1.intersect(a2); 275 * 276 * a1(before) intersect a2 = a1(after) 277 * 278 * ################ ################ ################ 279 * ############## ############## ############ 280 * ############ ############ ######## 281 * ########## ########## #### 282 * ######## ######## 283 * ###### ###### 284 * #### #### 285 * ## ## 286 * </pre> 287 * @param rhs the {@code Area} to be intersected with this 288 * {@code Area} 289 * @throws NullPointerException if {@code rhs} is null 290 * @since 1.2 291 */ intersect(Area rhs)292 public void intersect(Area rhs) { 293 curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves); 294 invalidateBounds(); 295 } 296 297 /** 298 * Sets the shape of this {@code Area} to be the combined area 299 * of its current shape and the shape of the specified {@code Area}, 300 * minus their intersection. 301 * The resulting shape of this {@code Area} will include 302 * only areas that were contained in either this {@code Area} 303 * or in the specified {@code Area}, but not in both. 304 * <pre> 305 * // Example: 306 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 307 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 308 * a1.exclusiveOr(a2); 309 * 310 * a1(before) xor a2 = a1(after) 311 * 312 * ################ ################ 313 * ############## ############## ## ## 314 * ############ ############ #### #### 315 * ########## ########## ###### ###### 316 * ######## ######## ################ 317 * ###### ###### ###### ###### 318 * #### #### #### #### 319 * ## ## ## ## 320 * </pre> 321 * @param rhs the {@code Area} to be exclusive ORed with this 322 * {@code Area}. 323 * @throws NullPointerException if {@code rhs} is null 324 * @since 1.2 325 */ exclusiveOr(Area rhs)326 public void exclusiveOr(Area rhs) { 327 curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves); 328 invalidateBounds(); 329 } 330 331 /** 332 * Removes all of the geometry from this {@code Area} and 333 * restores it to an empty area. 334 * @since 1.2 335 */ reset()336 public void reset() { 337 curves = new Vector<>(); 338 invalidateBounds(); 339 } 340 341 /** 342 * Tests whether this {@code Area} object encloses any area. 343 * @return {@code true} if this {@code Area} object 344 * represents an empty area; {@code false} otherwise. 345 * @since 1.2 346 */ isEmpty()347 public boolean isEmpty() { 348 return (curves.size() == 0); 349 } 350 351 /** 352 * Tests whether this {@code Area} consists entirely of 353 * straight edged polygonal geometry. 354 * @return {@code true} if the geometry of this 355 * {@code Area} consists entirely of line segments; 356 * {@code false} otherwise. 357 * @since 1.2 358 */ isPolygonal()359 public boolean isPolygonal() { 360 Enumeration<Curve> enum_ = curves.elements(); 361 while (enum_.hasMoreElements()) { 362 if (enum_.nextElement().getOrder() > 1) { 363 return false; 364 } 365 } 366 return true; 367 } 368 369 /** 370 * Tests whether this {@code Area} is rectangular in shape. 371 * @return {@code true} if the geometry of this 372 * {@code Area} is rectangular in shape; {@code false} 373 * otherwise. 374 * @since 1.2 375 */ isRectangular()376 public boolean isRectangular() { 377 int size = curves.size(); 378 if (size == 0) { 379 return true; 380 } 381 if (size > 3) { 382 return false; 383 } 384 Curve c1 = curves.get(1); 385 Curve c2 = curves.get(2); 386 if (c1.getOrder() != 1 || c2.getOrder() != 1) { 387 return false; 388 } 389 if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) { 390 return false; 391 } 392 if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) { 393 // One might be able to prove that this is impossible... 394 return false; 395 } 396 return true; 397 } 398 399 /** 400 * Tests whether this {@code Area} is comprised of a single 401 * closed subpath. This method returns {@code true} if the 402 * path contains 0 or 1 subpaths, or {@code false} if the path 403 * contains more than 1 subpath. The subpaths are counted by the 404 * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments 405 * that appear in the path. 406 * @return {@code true} if the {@code Area} is comprised 407 * of a single basic geometry; {@code false} otherwise. 408 * @since 1.2 409 */ isSingular()410 public boolean isSingular() { 411 if (curves.size() < 3) { 412 return true; 413 } 414 Enumeration<Curve> enum_ = curves.elements(); 415 enum_.nextElement(); // First Order0 "moveto" 416 while (enum_.hasMoreElements()) { 417 if (enum_.nextElement().getOrder() == 0) { 418 return false; 419 } 420 } 421 return true; 422 } 423 424 private Rectangle2D cachedBounds; invalidateBounds()425 private void invalidateBounds() { 426 cachedBounds = null; 427 } getCachedBounds()428 private Rectangle2D getCachedBounds() { 429 if (cachedBounds != null) { 430 return cachedBounds; 431 } 432 Rectangle2D r = new Rectangle2D.Double(); 433 if (curves.size() > 0) { 434 Curve c = curves.get(0); 435 // First point is always an order 0 curve (moveto) 436 r.setRect(c.getX0(), c.getY0(), 0, 0); 437 for (int i = 1; i < curves.size(); i++) { 438 curves.get(i).enlarge(r); 439 } 440 } 441 return (cachedBounds = r); 442 } 443 444 /** 445 * Returns a high precision bounding {@link Rectangle2D} that 446 * completely encloses this {@code Area}. 447 * <p> 448 * The Area class will attempt to return the tightest bounding 449 * box possible for the Shape. The bounding box will not be 450 * padded to include the control points of curves in the outline 451 * of the Shape, but should tightly fit the actual geometry of 452 * the outline itself. 453 * @return the bounding {@code Rectangle2D} for the 454 * {@code Area}. 455 * @since 1.2 456 */ getBounds2D()457 public Rectangle2D getBounds2D() { 458 return getCachedBounds().getBounds2D(); 459 } 460 461 /** 462 * Returns a bounding {@link Rectangle} that completely encloses 463 * this {@code Area}. 464 * <p> 465 * The Area class will attempt to return the tightest bounding 466 * box possible for the Shape. The bounding box will not be 467 * padded to include the control points of curves in the outline 468 * of the Shape, but should tightly fit the actual geometry of 469 * the outline itself. Since the returned object represents 470 * the bounding box with integers, the bounding box can only be 471 * as tight as the nearest integer coordinates that encompass 472 * the geometry of the Shape. 473 * @return the bounding {@code Rectangle} for the 474 * {@code Area}. 475 * @since 1.2 476 */ getBounds()477 public Rectangle getBounds() { 478 return getCachedBounds().getBounds(); 479 } 480 481 /** 482 * Returns an exact copy of this {@code Area} object. 483 * @return Created clone object 484 * @since 1.2 485 */ clone()486 public Object clone() { 487 return new Area(this); 488 } 489 490 /** 491 * Tests whether the geometries of the two {@code Area} objects 492 * are equal. 493 * This method will return false if the argument is null. 494 * @param other the {@code Area} to be compared to this 495 * {@code Area} 496 * @return {@code true} if the two geometries are equal; 497 * {@code false} otherwise. 498 * @since 1.2 499 */ equals(Area other)500 public boolean equals(Area other) { 501 // REMIND: A *much* simpler operation should be possible... 502 // Should be able to do a curve-wise comparison since all Areas 503 // should evaluate their curves in the same top-down order. 504 if (other == this) { 505 return true; 506 } 507 if (other == null) { 508 return false; 509 } 510 Vector<Curve> c = new AreaOp.XorOp().calculate(this.curves, other.curves); 511 return c.isEmpty(); 512 } 513 514 /** 515 * Transforms the geometry of this {@code Area} using the specified 516 * {@link AffineTransform}. The geometry is transformed in place, which 517 * permanently changes the enclosed area defined by this object. 518 * @param t the transformation used to transform the area 519 * @throws NullPointerException if {@code t} is null 520 * @since 1.2 521 */ transform(AffineTransform t)522 public void transform(AffineTransform t) { 523 if (t == null) { 524 throw new NullPointerException("transform must not be null"); 525 } 526 // REMIND: A simpler operation can be performed for some types 527 // of transform. 528 curves = pathToCurves(getPathIterator(t)); 529 invalidateBounds(); 530 } 531 532 /** 533 * Creates a new {@code Area} object that contains the same 534 * geometry as this {@code Area} transformed by the specified 535 * {@code AffineTransform}. This {@code Area} object 536 * is unchanged. 537 * @param t the specified {@code AffineTransform} used to transform 538 * the new {@code Area} 539 * @throws NullPointerException if {@code t} is null 540 * @return a new {@code Area} object representing the transformed 541 * geometry. 542 * @since 1.2 543 */ createTransformedArea(AffineTransform t)544 public Area createTransformedArea(AffineTransform t) { 545 Area a = new Area(this); 546 a.transform(t); 547 return a; 548 } 549 550 /** 551 * {@inheritDoc} 552 * @since 1.2 553 */ contains(double x, double y)554 public boolean contains(double x, double y) { 555 if (!getCachedBounds().contains(x, y)) { 556 return false; 557 } 558 Enumeration<Curve> enum_ = curves.elements(); 559 int crossings = 0; 560 while (enum_.hasMoreElements()) { 561 Curve c = enum_.nextElement(); 562 crossings += c.crossingsFor(x, y); 563 } 564 return ((crossings & 1) == 1); 565 } 566 567 /** 568 * {@inheritDoc} 569 * @since 1.2 570 */ contains(Point2D p)571 public boolean contains(Point2D p) { 572 return contains(p.getX(), p.getY()); 573 } 574 575 /** 576 * {@inheritDoc} 577 * @since 1.2 578 */ contains(double x, double y, double w, double h)579 public boolean contains(double x, double y, double w, double h) { 580 if (w < 0 || h < 0) { 581 return false; 582 } 583 if (!getCachedBounds().contains(x, y, w, h)) { 584 return false; 585 } 586 Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h); 587 return (c != null && c.covers(y, y+h)); 588 } 589 590 /** 591 * {@inheritDoc} 592 * @since 1.2 593 */ contains(Rectangle2D r)594 public boolean contains(Rectangle2D r) { 595 return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 596 } 597 598 /** 599 * {@inheritDoc} 600 * @since 1.2 601 */ intersects(double x, double y, double w, double h)602 public boolean intersects(double x, double y, double w, double h) { 603 if (w < 0 || h < 0) { 604 return false; 605 } 606 if (!getCachedBounds().intersects(x, y, w, h)) { 607 return false; 608 } 609 Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h); 610 return (c == null || !c.isEmpty()); 611 } 612 613 /** 614 * {@inheritDoc} 615 * @since 1.2 616 */ intersects(Rectangle2D r)617 public boolean intersects(Rectangle2D r) { 618 return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 619 } 620 621 /** 622 * Creates a {@link PathIterator} for the outline of this 623 * {@code Area} object. This {@code Area} object is unchanged. 624 * @param at an optional {@code AffineTransform} to be applied to 625 * the coordinates as they are returned in the iteration, or 626 * {@code null} if untransformed coordinates are desired 627 * @return the {@code PathIterator} object that returns the 628 * geometry of the outline of this {@code Area}, one 629 * segment at a time. 630 * @since 1.2 631 */ getPathIterator(AffineTransform at)632 public PathIterator getPathIterator(AffineTransform at) { 633 return new AreaIterator(curves, at); 634 } 635 636 /** 637 * Creates a {@code PathIterator} for the flattened outline of 638 * this {@code Area} object. Only uncurved path segments 639 * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point 640 * types are returned by the iterator. This {@code Area} 641 * object is unchanged. 642 * @param at an optional {@code AffineTransform} to be 643 * applied to the coordinates as they are returned in the 644 * iteration, or {@code null} if untransformed coordinates 645 * are desired 646 * @param flatness the maximum amount that the control points 647 * for a given curve can vary from colinear before a subdivided 648 * curve is replaced by a straight line connecting the end points 649 * @return the {@code PathIterator} object that returns the 650 * geometry of the outline of this {@code Area}, one segment 651 * at a time. 652 * @since 1.2 653 */ getPathIterator(AffineTransform at, double flatness)654 public PathIterator getPathIterator(AffineTransform at, double flatness) { 655 return new FlatteningPathIterator(getPathIterator(at), flatness); 656 } 657 } 658 659 class AreaIterator implements PathIterator { 660 private AffineTransform transform; 661 private Vector<Curve> curves; 662 private int index; 663 private Curve prevcurve; 664 private Curve thiscurve; 665 AreaIterator(Vector<Curve> curves, AffineTransform at)666 public AreaIterator(Vector<Curve> curves, AffineTransform at) { 667 this.curves = curves; 668 this.transform = at; 669 if (curves.size() >= 1) { 670 thiscurve = curves.get(0); 671 } 672 } 673 getWindingRule()674 public int getWindingRule() { 675 // REMIND: Which is better, EVEN_ODD or NON_ZERO? 676 // The paths calculated could be classified either way. 677 //return WIND_EVEN_ODD; 678 return WIND_NON_ZERO; 679 } 680 isDone()681 public boolean isDone() { 682 return (prevcurve == null && thiscurve == null); 683 } 684 next()685 public void next() { 686 if (prevcurve != null) { 687 prevcurve = null; 688 } else { 689 prevcurve = thiscurve; 690 index++; 691 if (index < curves.size()) { 692 thiscurve = curves.get(index); 693 if (thiscurve.getOrder() != 0 && 694 prevcurve.getX1() == thiscurve.getX0() && 695 prevcurve.getY1() == thiscurve.getY0()) 696 { 697 prevcurve = null; 698 } 699 } else { 700 thiscurve = null; 701 } 702 } 703 } 704 currentSegment(float[] coords)705 public int currentSegment(float[] coords) { 706 double[] dcoords = new double[6]; 707 int segtype = currentSegment(dcoords); 708 int numpoints = (segtype == SEG_CLOSE ? 0 709 : (segtype == SEG_QUADTO ? 2 710 : (segtype == SEG_CUBICTO ? 3 711 : 1))); 712 for (int i = 0; i < numpoints * 2; i++) { 713 coords[i] = (float) dcoords[i]; 714 } 715 return segtype; 716 } 717 currentSegment(double[] coords)718 public int currentSegment(double[] coords) { 719 int segtype; 720 int numpoints; 721 if (prevcurve != null) { 722 // Need to finish off junction between curves 723 if (thiscurve == null || thiscurve.getOrder() == 0) { 724 return SEG_CLOSE; 725 } 726 coords[0] = thiscurve.getX0(); 727 coords[1] = thiscurve.getY0(); 728 segtype = SEG_LINETO; 729 numpoints = 1; 730 } else if (thiscurve == null) { 731 throw new NoSuchElementException("area iterator out of bounds"); 732 } else { 733 segtype = thiscurve.getSegment(coords); 734 numpoints = thiscurve.getOrder(); 735 if (numpoints == 0) { 736 numpoints = 1; 737 } 738 } 739 if (transform != null) { 740 transform.transform(coords, 0, coords, 0, numpoints); 741 } 742 return segtype; 743 } 744 } 745