| /* |
| * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package java.awt.geom; |
| |
| import java.awt.Shape; |
| import java.awt.Rectangle; |
| import java.util.Vector; |
| import java.util.Enumeration; |
| import java.util.NoSuchElementException; |
| import sun.awt.geom.Curve; |
| import sun.awt.geom.Crossings; |
| import sun.awt.geom.AreaOp; |
| |
| /** |
| * An {@code Area} object stores and manipulates a |
| * resolution-independent description of an enclosed area of |
| * 2-dimensional space. |
| * {@code Area} objects can be transformed and can perform |
| * various Constructive Area Geometry (CAG) operations when combined |
| * with other {@code Area} objects. |
| * The CAG operations include area |
| * {@link #add addition}, {@link #subtract subtraction}, |
| * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}. |
| * See the linked method documentation for examples of the various |
| * operations. |
| * <p> |
| * The {@code Area} class implements the {@code Shape} |
| * interface and provides full support for all of its hit-testing |
| * and path iteration facilities, but an {@code Area} is more |
| * specific than a generalized path in a number of ways: |
| * <ul> |
| * <li>Only closed paths and sub-paths are stored. |
| * {@code Area} objects constructed from unclosed paths |
| * are implicitly closed during construction as if those paths |
| * had been filled by the {@code Graphics2D.fill} method. |
| * <li>The interiors of the individual stored sub-paths are all |
| * non-empty and non-overlapping. Paths are decomposed during |
| * construction into separate component non-overlapping parts, |
| * empty pieces of the path are discarded, and then these |
| * non-empty and non-overlapping properties are maintained |
| * through all subsequent CAG operations. Outlines of different |
| * component sub-paths may touch each other, as long as they |
| * do not cross so that their enclosed areas overlap. |
| * <li>The geometry of the path describing the outline of the |
| * {@code Area} resembles the path from which it was |
| * constructed only in that it describes the same enclosed |
| * 2-dimensional area, but may use entirely different types |
| * and ordering of the path segments to do so. |
| * </ul> |
| * Interesting issues which are not always obvious when using |
| * the {@code Area} include: |
| * <ul> |
| * <li>Creating an {@code Area} from an unclosed (open) |
| * {@code Shape} results in a closed outline in the |
| * {@code Area} object. |
| * <li>Creating an {@code Area} from a {@code Shape} |
| * which encloses no area (even when "closed") produces an |
| * empty {@code Area}. A common example of this issue |
| * is that producing an {@code Area} from a line will |
| * be empty since the line encloses no area. An empty |
| * {@code Area} will iterate no geometry in its |
| * {@code PathIterator} objects. |
| * <li>A self-intersecting {@code Shape} may be split into |
| * two (or more) sub-paths each enclosing one of the |
| * non-intersecting portions of the original path. |
| * <li>An {@code Area} may take more path segments to |
| * describe the same geometry even when the original |
| * outline is simple and obvious. The analysis that the |
| * {@code Area} class must perform on the path may |
| * not reflect the same concepts of "simple and obvious" |
| * as a human being perceives. |
| * </ul> |
| * |
| * @since 1.2 |
| */ |
| public class Area implements Shape, Cloneable { |
| private static Vector<Curve> EmptyCurves = new Vector<>(); |
| |
| private Vector<Curve> curves; |
| |
| /** |
| * Default constructor which creates an empty area. |
| * @since 1.2 |
| */ |
| public Area() { |
| curves = EmptyCurves; |
| } |
| |
| /** |
| * The {@code Area} class creates an area geometry from the |
| * specified {@link Shape} object. The geometry is explicitly |
| * closed, if the {@code Shape} is not already closed. The |
| * fill rule (even-odd or winding) specified by the geometry of the |
| * {@code Shape} is used to determine the resulting enclosed area. |
| * @param s the {@code Shape} from which the area is constructed |
| * @throws NullPointerException if {@code s} is null |
| * @since 1.2 |
| */ |
| public Area(Shape s) { |
| if (s instanceof Area) { |
| curves = ((Area) s).curves; |
| } else { |
| curves = pathToCurves(s.getPathIterator(null)); |
| } |
| } |
| |
| private static Vector<Curve> pathToCurves(PathIterator pi) { |
| Vector<Curve> curves = new Vector<>(); |
| int windingRule = pi.getWindingRule(); |
| // coords array is big enough for holding: |
| // coordinates returned from currentSegment (6) |
| // OR |
| // two subdivided quadratic curves (2+4+4=10) |
| // AND |
| // 0-1 horizontal splitting parameters |
| // OR |
| // 2 parametric equation derivative coefficients |
| // OR |
| // three subdivided cubic curves (2+6+6+6=20) |
| // AND |
| // 0-2 horizontal splitting parameters |
| // OR |
| // 3 parametric equation derivative coefficients |
| double coords[] = new double[23]; |
| double movx = 0, movy = 0; |
| double curx = 0, cury = 0; |
| double newx, newy; |
| while (!pi.isDone()) { |
| switch (pi.currentSegment(coords)) { |
| case PathIterator.SEG_MOVETO: |
| Curve.insertLine(curves, curx, cury, movx, movy); |
| curx = movx = coords[0]; |
| cury = movy = coords[1]; |
| Curve.insertMove(curves, movx, movy); |
| break; |
| case PathIterator.SEG_LINETO: |
| newx = coords[0]; |
| newy = coords[1]; |
| Curve.insertLine(curves, curx, cury, newx, newy); |
| curx = newx; |
| cury = newy; |
| break; |
| case PathIterator.SEG_QUADTO: |
| newx = coords[2]; |
| newy = coords[3]; |
| Curve.insertQuad(curves, curx, cury, coords); |
| curx = newx; |
| cury = newy; |
| break; |
| case PathIterator.SEG_CUBICTO: |
| newx = coords[4]; |
| newy = coords[5]; |
| Curve.insertCubic(curves, curx, cury, coords); |
| curx = newx; |
| cury = newy; |
| break; |
| case PathIterator.SEG_CLOSE: |
| Curve.insertLine(curves, curx, cury, movx, movy); |
| curx = movx; |
| cury = movy; |
| break; |
| } |
| pi.next(); |
| } |
| Curve.insertLine(curves, curx, cury, movx, movy); |
| AreaOp operator; |
| if (windingRule == PathIterator.WIND_EVEN_ODD) { |
| operator = new AreaOp.EOWindOp(); |
| } else { |
| operator = new AreaOp.NZWindOp(); |
| } |
| return operator.calculate(curves, EmptyCurves); |
| } |
| |
| /** |
| * Adds the shape of the specified {@code Area} to the |
| * shape of this {@code Area}. |
| * The resulting shape of this {@code Area} will include |
| * the union of both shapes, or all areas that were contained |
| * in either this or the specified {@code Area}. |
| * <pre> |
| * // Example: |
| * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); |
| * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); |
| * a1.add(a2); |
| * |
| * a1(before) + a2 = a1(after) |
| * |
| * ################ ################ ################ |
| * ############## ############## ################ |
| * ############ ############ ################ |
| * ########## ########## ################ |
| * ######## ######## ################ |
| * ###### ###### ###### ###### |
| * #### #### #### #### |
| * ## ## ## ## |
| * </pre> |
| * @param rhs the {@code Area} to be added to the |
| * current shape |
| * @throws NullPointerException if {@code rhs} is null |
| * @since 1.2 |
| */ |
| public void add(Area rhs) { |
| curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Subtracts the shape of the specified {@code Area} from the |
| * shape of this {@code Area}. |
| * The resulting shape of this {@code Area} will include |
| * areas that were contained only in this {@code Area} |
| * and not in the specified {@code Area}. |
| * <pre> |
| * // Example: |
| * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); |
| * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); |
| * a1.subtract(a2); |
| * |
| * a1(before) - a2 = a1(after) |
| * |
| * ################ ################ |
| * ############## ############## ## |
| * ############ ############ #### |
| * ########## ########## ###### |
| * ######## ######## ######## |
| * ###### ###### ###### |
| * #### #### #### |
| * ## ## ## |
| * </pre> |
| * @param rhs the {@code Area} to be subtracted from the |
| * current shape |
| * @throws NullPointerException if {@code rhs} is null |
| * @since 1.2 |
| */ |
| public void subtract(Area rhs) { |
| curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Sets the shape of this {@code Area} to the intersection of |
| * its current shape and the shape of the specified {@code Area}. |
| * The resulting shape of this {@code Area} will include |
| * only areas that were contained in both this {@code Area} |
| * and also in the specified {@code Area}. |
| * <pre> |
| * // Example: |
| * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); |
| * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); |
| * a1.intersect(a2); |
| * |
| * a1(before) intersect a2 = a1(after) |
| * |
| * ################ ################ ################ |
| * ############## ############## ############ |
| * ############ ############ ######## |
| * ########## ########## #### |
| * ######## ######## |
| * ###### ###### |
| * #### #### |
| * ## ## |
| * </pre> |
| * @param rhs the {@code Area} to be intersected with this |
| * {@code Area} |
| * @throws NullPointerException if {@code rhs} is null |
| * @since 1.2 |
| */ |
| public void intersect(Area rhs) { |
| curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Sets the shape of this {@code Area} to be the combined area |
| * of its current shape and the shape of the specified {@code Area}, |
| * minus their intersection. |
| * The resulting shape of this {@code Area} will include |
| * only areas that were contained in either this {@code Area} |
| * or in the specified {@code Area}, but not in both. |
| * <pre> |
| * // Example: |
| * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); |
| * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); |
| * a1.exclusiveOr(a2); |
| * |
| * a1(before) xor a2 = a1(after) |
| * |
| * ################ ################ |
| * ############## ############## ## ## |
| * ############ ############ #### #### |
| * ########## ########## ###### ###### |
| * ######## ######## ################ |
| * ###### ###### ###### ###### |
| * #### #### #### #### |
| * ## ## ## ## |
| * </pre> |
| * @param rhs the {@code Area} to be exclusive ORed with this |
| * {@code Area}. |
| * @throws NullPointerException if {@code rhs} is null |
| * @since 1.2 |
| */ |
| public void exclusiveOr(Area rhs) { |
| curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Removes all of the geometry from this {@code Area} and |
| * restores it to an empty area. |
| * @since 1.2 |
| */ |
| public void reset() { |
| curves = new Vector<>(); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Tests whether this {@code Area} object encloses any area. |
| * @return {@code true} if this {@code Area} object |
| * represents an empty area; {@code false} otherwise. |
| * @since 1.2 |
| */ |
| public boolean isEmpty() { |
| return (curves.size() == 0); |
| } |
| |
| /** |
| * Tests whether this {@code Area} consists entirely of |
| * straight edged polygonal geometry. |
| * @return {@code true} if the geometry of this |
| * {@code Area} consists entirely of line segments; |
| * {@code false} otherwise. |
| * @since 1.2 |
| */ |
| public boolean isPolygonal() { |
| Enumeration<Curve> enum_ = curves.elements(); |
| while (enum_.hasMoreElements()) { |
| if (enum_.nextElement().getOrder() > 1) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Tests whether this {@code Area} is rectangular in shape. |
| * @return {@code true} if the geometry of this |
| * {@code Area} is rectangular in shape; {@code false} |
| * otherwise. |
| * @since 1.2 |
| */ |
| public boolean isRectangular() { |
| int size = curves.size(); |
| if (size == 0) { |
| return true; |
| } |
| if (size > 3) { |
| return false; |
| } |
| Curve c1 = curves.get(1); |
| Curve c2 = curves.get(2); |
| if (c1.getOrder() != 1 || c2.getOrder() != 1) { |
| return false; |
| } |
| if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) { |
| return false; |
| } |
| if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) { |
| // One might be able to prove that this is impossible... |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Tests whether this {@code Area} is comprised of a single |
| * closed subpath. This method returns {@code true} if the |
| * path contains 0 or 1 subpaths, or {@code false} if the path |
| * contains more than 1 subpath. The subpaths are counted by the |
| * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments |
| * that appear in the path. |
| * @return {@code true} if the {@code Area} is comprised |
| * of a single basic geometry; {@code false} otherwise. |
| * @since 1.2 |
| */ |
| public boolean isSingular() { |
| if (curves.size() < 3) { |
| return true; |
| } |
| Enumeration<Curve> enum_ = curves.elements(); |
| enum_.nextElement(); // First Order0 "moveto" |
| while (enum_.hasMoreElements()) { |
| if (enum_.nextElement().getOrder() == 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private Rectangle2D cachedBounds; |
| private void invalidateBounds() { |
| cachedBounds = null; |
| } |
| private Rectangle2D getCachedBounds() { |
| if (cachedBounds != null) { |
| return cachedBounds; |
| } |
| Rectangle2D r = new Rectangle2D.Double(); |
| if (curves.size() > 0) { |
| Curve c = curves.get(0); |
| // First point is always an order 0 curve (moveto) |
| r.setRect(c.getX0(), c.getY0(), 0, 0); |
| for (int i = 1; i < curves.size(); i++) { |
| curves.get(i).enlarge(r); |
| } |
| } |
| return (cachedBounds = r); |
| } |
| |
| /** |
| * Returns a high precision bounding {@link Rectangle2D} that |
| * completely encloses this {@code Area}. |
| * <p> |
| * The Area class will attempt to return the tightest bounding |
| * box possible for the Shape. The bounding box will not be |
| * padded to include the control points of curves in the outline |
| * of the Shape, but should tightly fit the actual geometry of |
| * the outline itself. |
| * @return the bounding {@code Rectangle2D} for the |
| * {@code Area}. |
| * @since 1.2 |
| */ |
| public Rectangle2D getBounds2D() { |
| return getCachedBounds().getBounds2D(); |
| } |
| |
| /** |
| * Returns a bounding {@link Rectangle} that completely encloses |
| * this {@code Area}. |
| * <p> |
| * The Area class will attempt to return the tightest bounding |
| * box possible for the Shape. The bounding box will not be |
| * padded to include the control points of curves in the outline |
| * of the Shape, but should tightly fit the actual geometry of |
| * the outline itself. Since the returned object represents |
| * the bounding box with integers, the bounding box can only be |
| * as tight as the nearest integer coordinates that encompass |
| * the geometry of the Shape. |
| * @return the bounding {@code Rectangle} for the |
| * {@code Area}. |
| * @since 1.2 |
| */ |
| public Rectangle getBounds() { |
| return getCachedBounds().getBounds(); |
| } |
| |
| /** |
| * Returns an exact copy of this {@code Area} object. |
| * @return Created clone object |
| * @since 1.2 |
| */ |
| public Object clone() { |
| return new Area(this); |
| } |
| |
| /** |
| * Tests whether the geometries of the two {@code Area} objects |
| * are equal. |
| * This method will return false if the argument is null. |
| * @param other the {@code Area} to be compared to this |
| * {@code Area} |
| * @return {@code true} if the two geometries are equal; |
| * {@code false} otherwise. |
| * @since 1.2 |
| */ |
| public boolean equals(Area other) { |
| // REMIND: A *much* simpler operation should be possible... |
| // Should be able to do a curve-wise comparison since all Areas |
| // should evaluate their curves in the same top-down order. |
| if (other == this) { |
| return true; |
| } |
| if (other == null) { |
| return false; |
| } |
| Vector<Curve> c = new AreaOp.XorOp().calculate(this.curves, other.curves); |
| return c.isEmpty(); |
| } |
| |
| /** |
| * Transforms the geometry of this {@code Area} using the specified |
| * {@link AffineTransform}. The geometry is transformed in place, which |
| * permanently changes the enclosed area defined by this object. |
| * @param t the transformation used to transform the area |
| * @throws NullPointerException if {@code t} is null |
| * @since 1.2 |
| */ |
| public void transform(AffineTransform t) { |
| if (t == null) { |
| throw new NullPointerException("transform must not be null"); |
| } |
| // REMIND: A simpler operation can be performed for some types |
| // of transform. |
| curves = pathToCurves(getPathIterator(t)); |
| invalidateBounds(); |
| } |
| |
| /** |
| * Creates a new {@code Area} object that contains the same |
| * geometry as this {@code Area} transformed by the specified |
| * {@code AffineTransform}. This {@code Area} object |
| * is unchanged. |
| * @param t the specified {@code AffineTransform} used to transform |
| * the new {@code Area} |
| * @throws NullPointerException if {@code t} is null |
| * @return a new {@code Area} object representing the transformed |
| * geometry. |
| * @since 1.2 |
| */ |
| public Area createTransformedArea(AffineTransform t) { |
| Area a = new Area(this); |
| a.transform(t); |
| return a; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean contains(double x, double y) { |
| if (!getCachedBounds().contains(x, y)) { |
| return false; |
| } |
| Enumeration<Curve> enum_ = curves.elements(); |
| int crossings = 0; |
| while (enum_.hasMoreElements()) { |
| Curve c = enum_.nextElement(); |
| crossings += c.crossingsFor(x, y); |
| } |
| return ((crossings & 1) == 1); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean contains(Point2D p) { |
| return contains(p.getX(), p.getY()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean contains(double x, double y, double w, double h) { |
| if (w < 0 || h < 0) { |
| return false; |
| } |
| if (!getCachedBounds().contains(x, y, w, h)) { |
| return false; |
| } |
| Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h); |
| return (c != null && c.covers(y, y+h)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean contains(Rectangle2D r) { |
| return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean intersects(double x, double y, double w, double h) { |
| if (w < 0 || h < 0) { |
| return false; |
| } |
| if (!getCachedBounds().intersects(x, y, w, h)) { |
| return false; |
| } |
| Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h); |
| return (c == null || !c.isEmpty()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.2 |
| */ |
| public boolean intersects(Rectangle2D r) { |
| return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
| } |
| |
| /** |
| * Creates a {@link PathIterator} for the outline of this |
| * {@code Area} object. This {@code Area} object is unchanged. |
| * @param at an optional {@code AffineTransform} to be applied to |
| * the coordinates as they are returned in the iteration, or |
| * {@code null} if untransformed coordinates are desired |
| * @return the {@code PathIterator} object that returns the |
| * geometry of the outline of this {@code Area}, one |
| * segment at a time. |
| * @since 1.2 |
| */ |
| public PathIterator getPathIterator(AffineTransform at) { |
| return new AreaIterator(curves, at); |
| } |
| |
| /** |
| * Creates a {@code PathIterator} for the flattened outline of |
| * this {@code Area} object. Only uncurved path segments |
| * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point |
| * types are returned by the iterator. This {@code Area} |
| * object is unchanged. |
| * @param at an optional {@code AffineTransform} to be |
| * applied to the coordinates as they are returned in the |
| * iteration, or {@code null} if untransformed coordinates |
| * are desired |
| * @param flatness the maximum amount that the control points |
| * for a given curve can vary from colinear before a subdivided |
| * curve is replaced by a straight line connecting the end points |
| * @return the {@code PathIterator} object that returns the |
| * geometry of the outline of this {@code Area}, one segment |
| * at a time. |
| * @since 1.2 |
| */ |
| public PathIterator getPathIterator(AffineTransform at, double flatness) { |
| return new FlatteningPathIterator(getPathIterator(at), flatness); |
| } |
| } |
| |
| class AreaIterator implements PathIterator { |
| private AffineTransform transform; |
| private Vector<Curve> curves; |
| private int index; |
| private Curve prevcurve; |
| private Curve thiscurve; |
| |
| public AreaIterator(Vector<Curve> curves, AffineTransform at) { |
| this.curves = curves; |
| this.transform = at; |
| if (curves.size() >= 1) { |
| thiscurve = curves.get(0); |
| } |
| } |
| |
| public int getWindingRule() { |
| // REMIND: Which is better, EVEN_ODD or NON_ZERO? |
| // The paths calculated could be classified either way. |
| //return WIND_EVEN_ODD; |
| return WIND_NON_ZERO; |
| } |
| |
| public boolean isDone() { |
| return (prevcurve == null && thiscurve == null); |
| } |
| |
| public void next() { |
| if (prevcurve != null) { |
| prevcurve = null; |
| } else { |
| prevcurve = thiscurve; |
| index++; |
| if (index < curves.size()) { |
| thiscurve = curves.get(index); |
| if (thiscurve.getOrder() != 0 && |
| prevcurve.getX1() == thiscurve.getX0() && |
| prevcurve.getY1() == thiscurve.getY0()) |
| { |
| prevcurve = null; |
| } |
| } else { |
| thiscurve = null; |
| } |
| } |
| } |
| |
| public int currentSegment(float coords[]) { |
| double dcoords[] = new double[6]; |
| int segtype = currentSegment(dcoords); |
| int numpoints = (segtype == SEG_CLOSE ? 0 |
| : (segtype == SEG_QUADTO ? 2 |
| : (segtype == SEG_CUBICTO ? 3 |
| : 1))); |
| for (int i = 0; i < numpoints * 2; i++) { |
| coords[i] = (float) dcoords[i]; |
| } |
| return segtype; |
| } |
| |
| public int currentSegment(double coords[]) { |
| int segtype; |
| int numpoints; |
| if (prevcurve != null) { |
| // Need to finish off junction between curves |
| if (thiscurve == null || thiscurve.getOrder() == 0) { |
| return SEG_CLOSE; |
| } |
| coords[0] = thiscurve.getX0(); |
| coords[1] = thiscurve.getY0(); |
| segtype = SEG_LINETO; |
| numpoints = 1; |
| } else if (thiscurve == null) { |
| throw new NoSuchElementException("area iterator out of bounds"); |
| } else { |
| segtype = thiscurve.getSegment(coords); |
| numpoints = thiscurve.getOrder(); |
| if (numpoints == 0) { |
| numpoints = 1; |
| } |
| } |
| if (transform != null) { |
| transform.transform(coords, 0, coords, 0, numpoints); |
| } |
| return segtype; |
| } |
| } |