/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Denis M. Kishenko * @version $Revision$ */ package java.awt.geom; import java.awt.Rectangle; import java.awt.Shape; import java.util.NoSuchElementException; import org.apache.harmony.awt.gl.Crossing; import org.apache.harmony.awt.internal.nls.Messages; /** * The class GeneralPath represents a shape whose outline is given by different * types of curved and straight segments. * * @since Android 1.0 */ public final class GeneralPath implements Shape, Cloneable { /** * The Constant WIND_EVEN_ODD see {@link PathIterator#WIND_EVEN_ODD}. */ public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; /** * The Constant WIND_NON_ZERO see {@link PathIterator#WIND_NON_ZERO}. */ public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; /** * The buffers size. */ private static final int BUFFER_SIZE = 10; /** * The buffers capacity. */ private static final int BUFFER_CAPACITY = 10; /** * The point's types buffer. */ byte[] types; /** * The points buffer. */ float[] points; /** * The point's type buffer size. */ int typeSize; /** * The points buffer size. */ int pointSize; /** * The path rule. */ int rule; /** * The space amount in points buffer for different segmenet's types. */ static int pointShift[] = { 2, // MOVETO 2, // LINETO 4, // QUADTO 6, // CUBICTO 0 }; // CLOSE /* * GeneralPath path iterator */ /** * The Class Iterator is the subclass of Iterator for traversing the outline * of a GeneralPath. */ class Iterator implements PathIterator { /** * The current cursor position in types buffer. */ int typeIndex; /** * The current cursor position in points buffer. */ int pointIndex; /** * The source GeneralPath object. */ GeneralPath p; /** * The path iterator transformation. */ AffineTransform t; /** * Constructs a new GeneralPath.Iterator for given general path. * * @param path * the source GeneralPath object. */ Iterator(GeneralPath path) { this(path, null); } /** * Constructs a new GeneralPath.Iterator for given general path and * transformation. * * @param path * the source GeneralPath object. * @param at * the AffineTransform object to apply rectangle path. */ Iterator(GeneralPath path, AffineTransform at) { this.p = path; this.t = at; } public int getWindingRule() { return p.getWindingRule(); } public boolean isDone() { return typeIndex >= p.typeSize; } public void next() { typeIndex++; } public int currentSegment(double[] coords) { if (isDone()) { // awt.4B=Iterator out of bounds throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ } int type = p.types[typeIndex]; int count = GeneralPath.pointShift[type]; for (int i = 0; i < count; i++) { coords[i] = p.points[pointIndex + i]; } if (t != null) { t.transform(coords, 0, coords, 0, count / 2); } pointIndex += count; return type; } public int currentSegment(float[] coords) { if (isDone()) { // awt.4B=Iterator out of bounds throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ } int type = p.types[typeIndex]; int count = GeneralPath.pointShift[type]; System.arraycopy(p.points, pointIndex, coords, 0, count); if (t != null) { t.transform(coords, 0, coords, 0, count / 2); } pointIndex += count; return type; } } /** * Instantiates a new general path with the winding rule set to * {@link PathIterator#WIND_NON_ZERO} and the initial capacity (number of * segments) set to the default value 10. */ public GeneralPath() { this(WIND_NON_ZERO, BUFFER_SIZE); } /** * Instantiates a new general path with the given winding rule and the * initial capacity (number of segments) set to the default value 10. * * @param rule * the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or * {@link PathIterator#WIND_NON_ZERO}. */ public GeneralPath(int rule) { this(rule, BUFFER_SIZE); } /** * Instantiates a new general path with the given winding rule and initial * capacity (number of segments). * * @param rule * the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or * {@link PathIterator#WIND_NON_ZERO}. * @param initialCapacity * the number of segments the path is set to hold. */ public GeneralPath(int rule, int initialCapacity) { setWindingRule(rule); types = new byte[initialCapacity]; points = new float[initialCapacity * 2]; } /** * Creates a new GeneralPath from the outline of the given shape. * * @param shape * the shape. */ public GeneralPath(Shape shape) { this(WIND_NON_ZERO, BUFFER_SIZE); PathIterator p = shape.getPathIterator(null); setWindingRule(p.getWindingRule()); append(p, false); } /** * Sets the winding rule, which determines how to decide whether a point * that isn't on the path itself is inside or outside of the shape. * * @param rule * the new winding rule. * @throws IllegalArgumentException * if the winding rule is neither * {@link PathIterator#WIND_EVEN_ODD} nor * {@link PathIterator#WIND_NON_ZERO}. */ public void setWindingRule(int rule) { if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { // awt.209=Invalid winding rule value throw new java.lang.IllegalArgumentException(Messages.getString("awt.209")); //$NON-NLS-1$ } this.rule = rule; } /** * Gets the winding rule. * * @return the winding rule, either {@link PathIterator#WIND_EVEN_ODD} or * {@link PathIterator#WIND_NON_ZERO}. */ public int getWindingRule() { return rule; } /** * Checks the point data buffer sizes to see whether pointCount additional * point-data elements can fit. (Note that the number of point data elements * to add is more than one per point -- it depends on the type of point * being added.) Reallocates the buffers to enlarge the size if necessary. * * @param pointCount * the number of point data elements to be added. * @param checkMove * whether to check for existing points. * @throws IllegalPathStateException * checkMove is true and the path is currently empty. */ void checkBuf(int pointCount, boolean checkMove) { if (checkMove && typeSize == 0) { // awt.20A=First segment should be SEG_MOVETO type throw new IllegalPathStateException(Messages.getString("awt.20A")); //$NON-NLS-1$ } if (typeSize == types.length) { byte tmp[] = new byte[typeSize + BUFFER_CAPACITY]; System.arraycopy(types, 0, tmp, 0, typeSize); types = tmp; } if (pointSize + pointCount > points.length) { float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; System.arraycopy(points, 0, tmp, 0, pointSize); points = tmp; } } /** * Appends a new point to the end of this general path, disconnected from * the existing path. * * @param x * the x coordinate of the next point to append. * @param y * the y coordinate of the next point to append. */ public void moveTo(float x, float y) { if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) { points[pointSize - 2] = x; points[pointSize - 1] = y; } else { checkBuf(2, false); types[typeSize++] = PathIterator.SEG_MOVETO; points[pointSize++] = x; points[pointSize++] = y; } } /** * Appends a new segment to the end of this general path by making a * straight line segment from the current endpoint to the given new point. * * @param x * the x coordinate of the next point to append. * @param y * the y coordinate of the next point to append. */ public void lineTo(float x, float y) { checkBuf(2, true); types[typeSize++] = PathIterator.SEG_LINETO; points[pointSize++] = x; points[pointSize++] = y; } /** * Appends a new segment to the end of this general path by making a * quadratic curve from the current endpoint to the point (x2, y2) using the * point (x1, y1) as the quadratic curve's control point. * * @param x1 * the x coordinate of the quadratic curve's control point. * @param y1 * the y coordinate of the quadratic curve's control point. * @param x2 * the x coordinate of the quadratic curve's end point. * @param y2 * the y coordinate of the quadratic curve's end point. */ public void quadTo(float x1, float y1, float x2, float y2) { checkBuf(4, true); types[typeSize++] = PathIterator.SEG_QUADTO; points[pointSize++] = x1; points[pointSize++] = y1; points[pointSize++] = x2; points[pointSize++] = y2; } /** * Appends a new segment to the end of this general path by making a cubic * curve from the current endpoint to the point (x3, y3) using (x1, y1) and * (x2, y2) as control points. * * @see java.awt.geom.CubicCurve2D * @param x1 * the x coordinate of the new cubic segment's first control * point. * @param y1 * the y coordinate of the new cubic segment's first control * point. * @param x2 * the x coordinate of the new cubic segment's second control * point. * @param y2 * the y coordinate of the new cubic segment's second control * point. * @param x3 * the x coordinate of the new cubic segment's end point. * @param y3 * the y coordinate of the new cubic segment's end point. */ public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) { checkBuf(6, true); types[typeSize++] = PathIterator.SEG_CUBICTO; points[pointSize++] = x1; points[pointSize++] = y1; points[pointSize++] = x2; points[pointSize++] = y2; points[pointSize++] = x3; points[pointSize++] = y3; } /** * Appends the type information to declare that the current endpoint closes * the curve. */ public void closePath() { if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) { checkBuf(0, true); types[typeSize++] = PathIterator.SEG_CLOSE; } } /** * Appends the outline of the specified shape onto the end of this * GeneralPath. * * @param shape * the shape whose outline is to be appended. * @param connect * true to connect this path's current endpoint to the first * point of the shape's outline or false to append the shape's * outline without connecting it. * @throws NullPointerException * if the shape parameter is null. */ public void append(Shape shape, boolean connect) { PathIterator p = shape.getPathIterator(null); append(p, connect); } /** * Appends the path defined by the specified PathIterator onto the end of * this GeneralPath. * * @param path * the PathIterator that defines the new path to append. * @param connect * true to connect this path's current endpoint to the first * point of the shape's outline or false to append the shape's * outline without connecting it. */ public void append(PathIterator path, boolean connect) { while (!path.isDone()) { float coords[] = new float[6]; switch (path.currentSegment(coords)) { case PathIterator.SEG_MOVETO: if (!connect || typeSize == 0) { moveTo(coords[0], coords[1]); break; } if (types[typeSize - 1] != PathIterator.SEG_CLOSE && points[pointSize - 2] == coords[0] && points[pointSize - 1] == coords[1]) { break; } // NO BREAK; case PathIterator.SEG_LINETO: lineTo(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: quadTo(coords[0], coords[1], coords[2], coords[3]); break; case PathIterator.SEG_CUBICTO: curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_CLOSE: closePath(); break; } path.next(); connect = false; } } /** * Gets the current end point of the path. * * @return the current end point of the path. */ public Point2D getCurrentPoint() { if (typeSize == 0) { return null; } int j = pointSize - 2; if (types[typeSize - 1] == PathIterator.SEG_CLOSE) { for (int i = typeSize - 2; i > 0; i--) { int type = types[i]; if (type == PathIterator.SEG_MOVETO) { break; } j -= pointShift[type]; } } return new Point2D.Float(points[j], points[j + 1]); } /** * Resets the GeneralPath to being an empty path. The underlying point and * segment data is not deleted but rather the end indices of the data arrays * are set to zero. */ public void reset() { typeSize = 0; pointSize = 0; } /** * Transform all of the coordinates of this path according to the specified * AffineTransform. * * @param t * the AffineTransform. */ public void transform(AffineTransform t) { t.transform(points, 0, points, 0, pointSize / 2); } /** * Creates a new GeneralPath whose data is given by this path's data * transformed according to the specified AffineTransform. * * @param t * the AffineTransform. * @return the new GeneralPath whose data is given by this path's data * transformed according to the specified AffineTransform. */ public Shape createTransformedShape(AffineTransform t) { GeneralPath p = (GeneralPath)clone(); if (t != null) { p.transform(t); } return p; } public Rectangle2D getBounds2D() { float rx1, ry1, rx2, ry2; if (pointSize == 0) { rx1 = ry1 = rx2 = ry2 = 0.0f; } else { int i = pointSize - 1; ry1 = ry2 = points[i--]; rx1 = rx2 = points[i--]; while (i > 0) { float y = points[i--]; float x = points[i--]; if (x < rx1) { rx1 = x; } else if (x > rx2) { rx2 = x; } if (y < ry1) { ry1 = y; } else if (y > ry2) { ry2 = y; } } } return new Rectangle2D.Float(rx1, ry1, rx2 - rx1, ry2 - ry1); } public Rectangle getBounds() { return getBounds2D().getBounds(); } /** * Checks the cross count (number of times a ray from the point crosses the * shape's boundary) to determine whether the number of crossings * corresponds to a point inside the shape or not (according to the shape's * path rule). * * @param cross * the point's cross count. * @return true if the point is inside the path, or false otherwise. */ boolean isInside(int cross) { if (rule == WIND_NON_ZERO) { return Crossing.isInsideNonZero(cross); } return Crossing.isInsideEvenOdd(cross); } public boolean contains(double px, double py) { return isInside(Crossing.crossShape(this, px, py)); } public boolean contains(double rx, double ry, double rw, double rh) { int cross = Crossing.intersectShape(this, rx, ry, rw, rh); return cross != Crossing.CROSSING && isInside(cross); } public boolean intersects(double rx, double ry, double rw, double rh) { int cross = Crossing.intersectShape(this, rx, ry, rw, rh); return cross == Crossing.CROSSING || isInside(cross); } public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } public PathIterator getPathIterator(AffineTransform t) { return new Iterator(this, t); } public PathIterator getPathIterator(AffineTransform t, double flatness) { return new FlatteningPathIterator(getPathIterator(t), flatness); } @Override public Object clone() { try { GeneralPath p = (GeneralPath)super.clone(); p.types = types.clone(); p.points = points.clone(); return p; } catch (CloneNotSupportedException e) { throw new InternalError(); } } }