com.sun.javafx.geom.Path2D Maven / Gradle / Ivy
/*
* Copyright (c) 2006, 2022, 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 com.sun.javafx.geom;
import com.sun.javafx.geom.transform.BaseTransform;
import java.util.Arrays;
/**
* The {@code Path2D} class provides a simple, yet flexible
* shape which represents an arbitrary geometric path.
* It can fully represent any path which can be iterated by the
* {@link PathIterator} interface including all of its segment
* types and winding rules and it implements all of the
* basic hit testing methods of the {@link Shape} interface.
*
* Use {@link Path2D} when dealing with data that can be represented
* and used with floating point precision.
*
* {@code Path2D} provides exactly those facilities required for
* basic construction and management of a geometric path and
* implementation of the above interfaces with little added
* interpretation.
* If it is useful to manipulate the interiors of closed
* geometric shapes beyond simple hit testing then the
* {@link Area} class provides additional capabilities
* specifically targeted at closed figures.
* While both classes nominally implement the {@code Shape}
* interface, they differ in purpose and together they provide
* two useful views of a geometric shape where {@code Path2D}
* deals primarily with a trajectory formed by path segments
* and {@code Area} deals more with interpretation and manipulation
* of enclosed regions of 2D geometric space.
*
* The {@link PathIterator} interface has more detailed descriptions
* of the types of segments that make up a path and the winding rules
* that control how to determine which regions are inside or outside
* the path.
*
* @version 1.10, 05/05/07
*/
public class Path2D extends Shape implements PathConsumer2D {
static final int curvecoords[] = {2, 2, 4, 6, 0};
public enum CornerPrefix {
CORNER_ONLY,
MOVE_THEN_CORNER,
LINE_THEN_CORNER
}
/**
* An even-odd winding rule for determining the interior of
* a path.
*
* @see PathIterator#WIND_EVEN_ODD
*/
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
/**
* A non-zero winding rule for determining the interior of a
* path.
*
* @see PathIterator#WIND_NON_ZERO
*/
public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
// For code simplicity, copy these constants to our namespace
// and cast them to byte constants for easy storage.
private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO;
private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO;
private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO;
private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO;
private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE;
byte[] pointTypes;
int numTypes;
int numCoords;
int windingRule;
static final int INIT_SIZE = 20;
static final int EXPAND_MAX = 500;
static final int EXPAND_MAX_COORDS = EXPAND_MAX * 2;
float floatCoords[];
float moveX, moveY;
float prevX, prevY;
float currX, currY;
/**
* Constructs a new empty single precision {@code Path2D} object
* with a default winding rule of {@link #WIND_NON_ZERO}.
*/
public Path2D() {
this(WIND_NON_ZERO, INIT_SIZE);
}
/**
* Constructs a new empty single precision {@code Path2D} object
* with the specified winding rule to control operations that
* require the interior of the path to be defined.
*
* @param rule the winding rule
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
*/
public Path2D(int rule) {
this(rule, INIT_SIZE);
}
/**
* Constructs a new empty single precision {@code Path2D} object
* with the specified winding rule and the specified initial
* capacity to store path segments.
* This number is an initial guess as to how many path segments
* will be added to the path, but the storage is expanded as
* needed to store whatever path segments are added.
*
* @param rule the winding rule
* @param initialCapacity the estimate for the number of path segments
* in the path
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
*/
public Path2D(int rule, int initialCapacity) {
setWindingRule(rule);
this.pointTypes = new byte[initialCapacity];
floatCoords = new float[initialCapacity * 2];
}
/**
* Constructs a new single precision {@code Path2D} object
* from an arbitrary {@link Shape} object.
* All of the initial geometry and the winding rule for this path are
* taken from the specified {@code Shape} object.
*
* @param s the specified {@code Shape} object
*/
public Path2D(Shape s) {
this(s, null);
}
/**
* Constructs a new single precision {@code Path2D} object
* from an arbitrary {@link Shape} object, transformed by an
* {@link BaseTransform} object.
* All of the initial geometry and the winding rule for this path are
* taken from the specified {@code Shape} object and transformed
* by the specified {@code BaseTransform} object.
*
* @param s the specified {@code Shape} object
* @param tx the specified {@code BaseTransform} object
*/
public Path2D(Shape s, BaseTransform tx) {
if (s instanceof Path2D) {
Path2D p2d = (Path2D) s;
setWindingRule(p2d.windingRule);
this.numTypes = p2d.numTypes;
this.pointTypes = Arrays.copyOf(p2d.pointTypes, numTypes);
this.numCoords = p2d.numCoords;
if (tx == null || tx.isIdentity()) {
this.floatCoords = Arrays.copyOf(p2d.floatCoords, numCoords);
this.moveX = p2d.moveX;
this.moveY = p2d.moveY;
this.prevX = p2d.prevX;
this.prevY = p2d.prevY;
this.currX = p2d.currX;
this.currY = p2d.currY;
} else {
this.floatCoords = new float[numCoords + 6];
tx.transform(p2d.floatCoords, 0, this.floatCoords, 0, numCoords / 2);
floatCoords[numCoords + 0] = moveX;
floatCoords[numCoords + 1] = moveY;
floatCoords[numCoords + 2] = prevX;
floatCoords[numCoords + 3] = prevY;
floatCoords[numCoords + 4] = currX;
floatCoords[numCoords + 5] = currY;
tx.transform(this.floatCoords, numCoords, this.floatCoords, numCoords, 3);
moveX = floatCoords[numCoords + 0];
moveY = floatCoords[numCoords + 1];
prevX = floatCoords[numCoords + 2];
prevY = floatCoords[numCoords + 3];
currX = floatCoords[numCoords + 4];
currY = floatCoords[numCoords + 5];
}
} else {
PathIterator pi = s.getPathIterator(tx);
setWindingRule(pi.getWindingRule());
this.pointTypes = new byte[INIT_SIZE];
this.floatCoords = new float[INIT_SIZE * 2];
append(pi, false);
}
}
/**
* Construct a Path2D from pre-composed data.
* Used by internal font code which has obtained the path data
* for a glyph outline, and which promises not to
* mess with the arrays, dropping all other references,
so there's no need to clone them here.
*/
public Path2D(int windingRule,
byte[] pointTypes,
int numTypes,
float[] pointCoords,
int numCoords)
{
this.windingRule = windingRule;
this.pointTypes = pointTypes;
this.numTypes = numTypes;
this.floatCoords = pointCoords;
this.numCoords = numCoords;
}
Point2D getPoint(int coordindex) {
return new Point2D(floatCoords[coordindex],
floatCoords[coordindex+1]);
}
private boolean close(int ix, float fx, float tolerance) {
return (Math.abs(ix - fx) <= tolerance);
}
/**
* Check and return if the fillable interior of the path is a simple
* rectangle on nearly integer bounds and initialize the indicated
* {@link Rectangle} with the integer representation of the rectangle
* if it is.
* The method will return false if the path is not rectangular, or if
* the horizontal and linear segments are not within the indicated
* tolerance of an integer coordinate, or if the resulting rectangle
* cannot be safely represented by the integer attributes of the
* {@code Rectangle} object.
*
* @param retrect the {@code Rectangle} to return the rectangular area,
* or null
* @param tolerance the maximum difference from an integer allowed
* for any edge of the rectangle
* @return true iff the path is a simple rectangle
*/
public boolean checkAndGetIntRect(Rectangle retrect, float tolerance) {
// Valid rectangular paths are:
// 4 segs: MOVE, LINE, LINE, LINE (implicit CLOSE)
// 5 segs: MOVE, LINE, LINE, LINE, LINE
// 5 segs: MOVE, LINE, LINE, LINE, CLOSE
// 6 segs: MOVE, LINE, LINE, LINE, LINE, CLOSE
if (numTypes == 5) {
// points[4] can be LINETO or CLOSE
if (pointTypes[4] != SEG_LINETO && pointTypes[4] != SEG_CLOSE) {
return false;
}
} else if (numTypes == 6) {
// points[4] must be LINETO and
// points[5] must be CLOSE
if (pointTypes[4] != SEG_LINETO) return false;
if (pointTypes[5] != SEG_CLOSE) return false;
} else if (numTypes != 4) {
return false;
}
if (pointTypes[0] != SEG_MOVETO) return false;
if (pointTypes[1] != SEG_LINETO) return false;
if (pointTypes[2] != SEG_LINETO) return false;
if (pointTypes[3] != SEG_LINETO) return false;
int x0 = (int) (floatCoords[0] + 0.5f);
int y0 = (int) (floatCoords[1] + 0.5f);
if (!close(x0, floatCoords[0], tolerance)) return false;
if (!close(y0, floatCoords[1], tolerance)) return false;
int x1 = (int) (floatCoords[2] + 0.5f);
int y1 = (int) (floatCoords[3] + 0.5f);
if (!close(x1, floatCoords[2], tolerance)) return false;
if (!close(y1, floatCoords[3], tolerance)) return false;
int x2 = (int) (floatCoords[4] + 0.5f);
int y2 = (int) (floatCoords[5] + 0.5f);
if (!close(x2, floatCoords[4], tolerance)) return false;
if (!close(y2, floatCoords[5], tolerance)) return false;
int x3 = (int) (floatCoords[6] + 0.5f);
int y3 = (int) (floatCoords[7] + 0.5f);
if (!close(x3, floatCoords[6], tolerance)) return false;
if (!close(y3, floatCoords[7], tolerance)) return false;
if (numTypes > 4 && pointTypes[4] == SEG_LINETO) {
if (!close(x0, floatCoords[8], tolerance)) return false;
if (!close(y0, floatCoords[9], tolerance)) return false;
}
if ((x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) ||
(y0 == y1 && y2 == y3 && x0 == x3 && x1 == x2))
{
// We can use either diagonal to calculate the rectangle:
// (x0, y0) -> (x2, y2)
// (x1, y1) -> (x3, y3)
// We also need to deal with upside down and/or backwards rectangles
int x, y, w, h;
if (x2 < x0) { x = x2; w = x0 - x2; }
else { x = x0; w = x2 - x0; }
if (y2 < y0) { y = y2; h = y0 - y2; }
else { y = y0; h = y2 - y0; }
// Overflow protection...
if (w < 0) return false;
if (h < 0) return false;
if (retrect != null) {
retrect.setBounds(x, y, w, h);
}
return true;
}
return false;
}
void needRoom(boolean needMove, int newCoords) {
if (needMove && (numTypes == 0)) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
int size = pointTypes.length;
if (size == 0) {
pointTypes = new byte[2];
} else if (numTypes >= size) {
pointTypes = expandPointTypes(pointTypes, 1);
}
size = floatCoords.length;
if (numCoords > (floatCoords.length - newCoords)) {
floatCoords = expandCoords(floatCoords, newCoords);
}
}
static byte[] expandPointTypes(byte[] oldPointTypes, int needed) {
final int oldSize = oldPointTypes.length;
final int newSizeMin = oldSize + needed;
if (newSizeMin < oldSize) {
// hard overflow failure - we can't even accommodate
// new items without overflowing
throw new ArrayIndexOutOfBoundsException(
"pointTypes exceeds maximum capacity !");
}
// growth algorithm computation
int grow = oldSize;
if (grow > EXPAND_MAX) {
grow = Math.max(EXPAND_MAX, oldSize >> 3); // 1/8th min
} else if (grow < INIT_SIZE) {
grow = INIT_SIZE; // ensure > 6 (cubics)
}
assert grow > 0;
int newSize = oldSize + grow;
if (newSize < newSizeMin) {
// overflow in growth algorithm computation
newSize = Integer.MAX_VALUE;
}
while (true) {
try {
// try allocating the larger array
return Arrays.copyOf(oldPointTypes, newSize);
} catch (OutOfMemoryError oome) {
if (newSize == newSizeMin) {
throw oome;
}
}
newSize = newSizeMin + (newSize - newSizeMin) / 2;
}
}
static float[] expandCoords(float[] oldCoords, int needed) {
final int oldSize = oldCoords.length;
final int newSizeMin = oldSize + needed;
if (newSizeMin < oldSize) {
// hard overflow failure - we can't even accommodate
// new items without overflowing
throw new ArrayIndexOutOfBoundsException(
"coords exceeds maximum capacity !");
}
// growth algorithm computation
int grow = oldSize;
if (grow > EXPAND_MAX_COORDS) {
grow = Math.max(EXPAND_MAX_COORDS, oldSize >> 3); // 1/8th min
} else if (grow < INIT_SIZE) {
grow = INIT_SIZE; // ensure > 6 (cubics)
}
assert grow > needed;
int newSize = oldSize + grow;
if (newSize < newSizeMin) {
// overflow in growth algorithm computation
newSize = Integer.MAX_VALUE;
}
while (true) {
try {
// try allocating the larger array
return Arrays.copyOf(oldCoords, newSize);
} catch (OutOfMemoryError oome) {
if (newSize == newSizeMin) {
throw oome;
}
}
newSize = newSizeMin + (newSize - newSizeMin) / 2;
}
}
/**
* Adds a point to the path by moving to the specified
* coordinates specified in float precision.
*
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
@Override
public final void moveTo(float x, float y) {
if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
floatCoords[numCoords-2] = moveX = prevX = currX = x;
floatCoords[numCoords-1] = moveY = prevY = currY = y;
} else {
needRoom(false, 2);
pointTypes[numTypes++] = SEG_MOVETO;
floatCoords[numCoords++] = moveX = prevX = currX = x;
floatCoords[numCoords++] = moveY = prevY = currY = y;
}
}
/**
* Adds a point to the path by moving to the specified coordinates
* relative to the current point, specified in float precision.
*
* @param relx the specified relative X coordinate
* @param rely the specified relative Y coordinate
* @see Path2D#moveTo
*/
public final void moveToRel(float relx, float rely) {
if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
floatCoords[numCoords-2] = moveX = prevX = (currX += relx);
floatCoords[numCoords-1] = moveY = prevY = (currY += rely);
} else {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_MOVETO;
floatCoords[numCoords++] = moveX = prevX = (currX += relx);
floatCoords[numCoords++] = moveY = prevY = (currY += rely);
}
}
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new coordinates.
*
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
@Override
public final void lineTo(float x, float y) {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_LINETO;
floatCoords[numCoords++] = prevX = currX = x;
floatCoords[numCoords++] = prevY = currY = y;
}
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new coordinates relative to the
* current point.
*
* @param relx the specified relative X coordinate
* @param rely the specified relative Y coordinate
* @see Path2D#lineTo
*/
public final void lineToRel(float relx, float rely) {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_LINETO;
floatCoords[numCoords++] = prevX = (currX += relx);
floatCoords[numCoords++] = prevY = (currY += rely);
}
/**
* Adds a curved segment to the path, defined by two new points, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified coordinates {@code (x2,y2)},
* using the specified point {@code (x1,y1)} as a quadratic
* parametric control point.
*
* @param x1 the X coordinate of the quadratic control point
* @param y1 the Y coordinate of the quadratic control point
* @param x2 the X coordinate of the final end point
* @param y2 the Y coordinate of the final end point
*/
@Override
public final void quadTo(float x1, float y1,
float x2, float y2)
{
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = x1;
floatCoords[numCoords++] = prevY = y1;
floatCoords[numCoords++] = currX = x2;
floatCoords[numCoords++] = currY = y2;
}
/**
* Adds a curved segment to the path, defined by two new points
* relative to the current point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified relative coordinates {@code (rx2,ry2)},
* using the specified relative point {@code (rx1,ry1)} as a quadratic
* parametric control point.
* This is equivalent to:
*
* quadTo(getCurrentX() + rx1, getCurrentY() + ry1,
* getCurrentX() + rx2, getCurrentY() + ry2);
*
*
* @param relx1 the relative X coordinate of the quadratic control point
* @param rely1 the relative Y coordinate of the quadratic control point
* @param relx2 the relative X coordinate of the final end point
* @param rely2 the relative Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToRel(float relx1, float rely1,
float relx2, float rely2)
{
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = currX + relx1;
floatCoords[numCoords++] = prevY = currY + rely1;
floatCoords[numCoords++] = (currX += relx2);
floatCoords[numCoords++] = (currY += rely2);
}
/**
* Adds a curved segment to the path, defined by a new point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified coordinates {@code (x,y)},
* using a quadratic parametric control point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* If the previous path segment is not a curve, then the control
* point will be positioned at the current point. This is
* equivalent to:
*
* quadTo(getCurrentX() * 2 - ,
* getCurrentY() * 2 - ,
* x, y);
*
*
* @param x2 the X coordinate of the final end point
* @param y2 the Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToSmooth(float x2, float y2) {
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX);
floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY);
floatCoords[numCoords++] = currX = x2;
floatCoords[numCoords++] = currY = y2;
}
/**
* Adds a curved segment to the path, defined by a new point
* relative to the current point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified relative coordinates {@code (x,y)},
* using a quadratic parametric control point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* If the previous path segment is not a curve, then the control
* point will be positioned at the current point. This is
* equivalent to:
*
* quadTo(getCurrentX() * 2 - ,
* getCurrentY() * 2 - ,
* getCurrentX() + x, getCurrentY() + y);
*
*
* @param relx2 the relative X coordinate of the final end point
* @param rely2 the relative Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToSmoothRel(float relx2, float rely2) {
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX);
floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY);
floatCoords[numCoords++] = (currX += relx2);
floatCoords[numCoords++] = (currY += rely2);
}
/**
* Adds a curved segment to the path, defined by three new points, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified coordinates {@code (x3,y3)},
* using the specified points {@code (x1,y1)} and {@code (x2,y2)} as
* Bézier control points.
*
* @param x1 the X coordinate of the first Bézier control point
* @param y1 the Y coordinate of the first Bézier control point
* @param x2 the X coordinate of the second Bézier control point
* @param y2 the Y coordinate of the second Bézier control point
* @param x3 the X coordinate of the final end point
* @param y3 the Y coordinate of the final end point
* @see Path2D#curveTo
*/
@Override
public final void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = x1;
floatCoords[numCoords++] = y1;
floatCoords[numCoords++] = prevX = x2;
floatCoords[numCoords++] = prevY = y2;
floatCoords[numCoords++] = currX = x3;
floatCoords[numCoords++] = currY = y3;
}
/**
* Adds a curved segment to the path, defined by three new points
* relative to the current point, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified coordinates {@code (x3,y3)},
* using the specified points {@code (x1,y1)} and {@code (x2,y2)} as
* Bézier control points.
* This is equivalent to:
*
* curveTo(getCurrentX() + rx1, getCurrentY() + ry1,
* getCurrentX() + rx2, getCurrentY() + ry2,
* getCurrentX() + rx3, getCurrentY() + ry3)
*
*
* @param relx1 the relative X coordinate of the first Bézier control point
* @param rely1 the relative Y coordinate of the first Bézier control point
* @param relx2 the relative X coordinate of the second Bézier control point
* @param rely2 the relative Y coordinate of the second Bézier control point
* @param relx3 the relative X coordinate of the final end point
* @param rely3 the relative Y coordinate of the final end point
* @see Path2D#curveTo
*/
public final void curveToRel(float relx1, float rely1,
float relx2, float rely2,
float relx3, float rely3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX + relx1;
floatCoords[numCoords++] = currY + rely1;
floatCoords[numCoords++] = prevX = currX + relx2;
floatCoords[numCoords++] = prevY = currY + rely2;
floatCoords[numCoords++] = (currX += relx3);
floatCoords[numCoords++] = (currY += rely3);
}
/**
* Adds a curved segment to the path, defined by two new points and
* a third point inferred from the previous curve, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified coordinates {@code (x3,y3)},
* using the specified point {@code (x2,y2)} as the second
* Bézier control point and a first Bézier control
* point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* This is equivalent to:
*
* curveTo(getCurrentX() * 2.0f - ,
* getCurrentY() * 2.0f - ,
* x2, y2, x3, y3);
*
*
* @param x2 the X coordinate of the second Bézier control point
* @param y2 the Y coordinate of the second Bézier control point
* @param x3 the X coordinate of the final end point
* @param y3 the Y coordinate of the final end point
* @see Path2D#curveTo
*/
public final void curveToSmooth(float x2, float y2,
float x3, float y3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX * 2.0f - prevX;
floatCoords[numCoords++] = currY * 2.0f - prevY;
floatCoords[numCoords++] = prevX = x2;
floatCoords[numCoords++] = prevY = y2;
floatCoords[numCoords++] = currX = x3;
floatCoords[numCoords++] = currY = y3;
}
/**
* Adds a curved segment to the path, defined by two new points relative
* to the current point and
* a third point inferred from the previous curve, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified relative coordinates {@code (rx3,ry3)},
* using the specified relative point {@code (rx2,ry2)} as the second
* Bézier control point and a first Bézier control
* point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* This is equivalent to:
*
* curveTo(getCurrentX() * 2.0f - ,
* getCurrentY() * 2.0f - ,
* getCurrentX() + x2, getCurrentY() + y2,
* getCurrentX() + x3, getCurrentY() + y3);
*
*
* @param relx2 the relative X coordinate of the second Bézier control point
* @param rely2 the relative Y coordinate of the second Bézier control point
* @param relx3 the relative X coordinate of the final end point
* @param rely3 the relative Y coordinate of the final end point
* @see Path2D#curveTo
*/
public final void curveToSmoothRel(float relx2, float rely2,
float relx3, float rely3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX * 2.0f - prevX;
floatCoords[numCoords++] = currY * 2.0f - prevY;
floatCoords[numCoords++] = prevX = currX + relx2;
floatCoords[numCoords++] = prevY = currY + rely2;
floatCoords[numCoords++] = (currX += relx3);
floatCoords[numCoords++] = (currY += rely3);
}
/**
* Append a section of a quadrant of an oval to the current path,
* relative to the current point.
* See {@link appendOvalQuadrant} for a precise definition of the
* path segments to be added, considering that this method uses the
* current point of the path as the first pair of coordinates and
* a hard-coded prefix of {@link CornerPrefix.CORNER_ONLY CORNER_ONLY}.
* This method is equivalent to (and only slightly faster than):
*
* appendOvalQuadrant(getCurrentX(), getCurrentY(),
* cx, cy, ex, ey, tfrom, tto,
* CornerPrefix.CORNER_ONLY);
*
* Note that you could define a circle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the
* following 4 calls to this method:
*
* Path2D path = new Path2D();
* float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
* float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
* path.moveTo(cx, y0);
* path.ovalQuadrantTo(x1, y0, x1, cy, 0f, 1f);
* path.ovalQuadrantTo(x1, y1, cx, y1, 0f, 1f);
* path.ovalQuadrantTo(x0, y1, x0, cy, 0f, 1f);
* path.ovalQuadrantTo(x0, y0, cx, y0, 0f, 1f);
* path.closePath();
*
* You could also define a rounded rectangle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner
* arc radius {@code r} less than half the width and the height with the
* following 4 calls to this method:
*
* Path2D path = new Path2D();
* float lx = x0 + r;
* float rx = x1 - r;
* float ty = y0 + r;
* float by = y1 - r;
* path.moveTo(rx, y0);
* path.ovalQuadrantTo(x1, y0, x1, ty, 0f, 1f);
* path.lineTo(x1, by);
* path.ovalQuadrantTo(x1, y1, rx, y1, 0f, 1f);
* path.lineTo(lx, y1);
* path.ovalQuadrantTo(x0, y1, x0, by, 0f, 1f);
* path.lineTo(x0, by);
* path.ovalQuadrantTo(x0, y0, lx, y0, 0f, 1f);
* path.closePath();
*
*
* @param cx the X coordinate of the corner
* @param cy the Y coordinate of the corner
* @param ex the X coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param ey the Y coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param tfrom the fraction of the oval section where the curve should start
* @param tto the fraction of the oval section where the curve should end
* @throws IllegalPathStateException
* if there is no current point in the path
* @throws IllegalArgumentException
* if the {@code tfrom} and {@code tto} values do not satisfy the
* required relationship {@code (0 <= tfrom <= tto <= 1).
*/
public final void ovalQuadrantTo(float cx, float cy,
float ex, float ey,
float tfrom, float tto)
{
if (numTypes < 1) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
appendOvalQuadrant(currX, currY,
cx, cy, ex, ey, tfrom, tto, CornerPrefix.CORNER_ONLY);
}
/**
* Append a section of a quadrant of an oval to the current path.
* The oval from which a quadrant is taken is the oval that would be
* inscribed in a parallelogram defined by 3 points,
* {@code (sx, sy)} which is considered to be the midpoint of the edge
* leading into the corner of the oval where the oval grazes it,
* {@code (cx, cy)} which is considered to be the location of the
* corner of the parallelogram in which the oval is inscribed,
* and {@code (ex, ey)} which is considered to be the midpoint of the
* edge leading away from the corner of the oval where the oval grazes it.
* A typical case involves the two segments being equal in length and
* at right angles to each other in which case the oval is a quarter of
* a circle.
*
* Only the portion of the oval from {@code tfrom} to {@code tto}
* will be included where {@code 0f} represents the point where the
* oval grazes the leading edge, {@code 1f} represents the point where
* the oval grazes the trailing edge, and {@code 0.5f} represents the
* point on the oval closest to the corner (i.e. the "45 degree" point).
* The two values must satisfy the relation
* {@code (0 <= tfrom <= tto <= 1)}.
* If {@code tfrom} is not {@code 0f} then the caller would most likely
* want to use one of the {@code prefix} values that inserts a segment
* leading to the initial point (see below).
*
* An initial {@link moveTo} or {@link lineTo} can be added to direct
* the path to the starting point of the oval section if
* {@link CornerPrefix.MOVE_THEN_CORNER MOVE_THEN_CORNER} or
* {@link CornerPrefix.LINE_THEN_CORNER LINE_THEN_CORNER} are
* specified by the prefix argument.
* The {@code lineTo} path segment will only be added if the current point
* is not already at the indicated location to avoid spurious empty line
* segments.
* The prefix can be specified as
* {@link CornerPrefix.CORNER_ONLY CORNER_ONLY} if the current point
* on the path is known to be at the starting point of the oval section,
* but could otherwise produce odd results if the current point is not
* appropriate.
*
* Note that you could define a circle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the
* following 4 calls to this method:
*
* Path2D path = new Path2D();
* float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
* float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
* path.appendOvalQuadrant(cx, y0, x1, y0, x1, cy, 0f, 1f, MOVE_THEN_CORNER);
* path.appendOvalQuadrant(x1, cy, x1, y1, cx, y1, 0f, 1f, CORNER_ONLY);
* path.appendOvalQuadrant(cx, y1, x0, y1, x0, cy, 0f, 1f, CORNER_ONLY);
* path.appendOvalQuadrant(x0, cy, x0, y0, cx, y0, 0f, 1f, CORNER_ONLY);
* path.closePath();
*
* You could also define a rounded rectangle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner
* arc radius {@code r} less than half the width and the height with the
* following 4 calls to this method:
*
* Path2D path = new Path2D();
* float lx = x0 + r;
* float rx = x1 - r;
* float ty = y0 + r;
* float by = y1 - r;
* path.appendOvalQuadrant(rx, y0, x1, y0, x1, ty, 0f, 1f, MOVE_THEN_CORNER);
* path.appendOvalQuadrant(x1, by, x1, y1, rx, y1, 0f, 1f, LINE_THEN_CORNER);
* path.appendOvalQuadrant(lx, y1, x0, y1, x0, by, 0f, 1f, LINE_THEN_CORNER);
* path.appendOvalQuadrant(x0, by, x0, y0, lx, y0, 0f, 1f, LINE_THEN_CORNER);
* path.closePath();
*
*
* @param sx the X coordinate of the midpoint of the leading edge
* interpolated by the oval
* @param sy the Y coordinate of the midpoint of the leading edge
* interpolated by the oval
* @param cx the X coordinate of the corner
* @param cy the Y coordinate of the corner
* @param ex the X coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param ey the Y coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param tfrom the fraction of the oval section where the curve should start
* @param tto the fraction of the oval section where the curve should end
* @param prefix the specification of what additional path segments should
* be appended to lead the current path to the starting point
* @throws IllegalPathStateException
* if there is no current point in the path and the prefix is
* not {@code CornerPrevix.MOVE_THEN_CORNER MOVE_THEN_CORNER}.
* @throws IllegalArgumentException
* if the {@code tfrom} and {@code tto} values do not satisfy the
* required relationship {@code (0 <= tfrom <= tto <= 1).
*/
public final void appendOvalQuadrant(float sx, float sy,
float cx, float cy,
float ex, float ey,
float tfrom, float tto,
CornerPrefix prefix)
{
if (!(tfrom >= 0f && tfrom <= tto && tto <= 1f)) {
throw new IllegalArgumentException("0 <= tfrom <= tto <= 1 required");
}
float cx0 = (float) (sx + (cx - sx) * EllipseIterator.CtrlVal);
float cy0 = (float) (sy + (cy - sy) * EllipseIterator.CtrlVal);
float cx1 = (float) (ex + (cx - ex) * EllipseIterator.CtrlVal);
float cy1 = (float) (ey + (cy - ey) * EllipseIterator.CtrlVal);
if (tto < 1f) {
float t = 1f - tto;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
cx0 += (sx - cx0) * t;
cy0 += (sy - cy0) * t;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
}
if (tfrom > 0f) {
if (tto < 1f) {
tfrom = tfrom / tto;
}
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
cx0 += (cx1 - cx0) * tfrom;
cy0 += (cy1 - cy0) * tfrom;
cx1 += (ex - cx1) * tfrom;
cy1 += (ey - cy1) * tfrom;
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
cx0 += (cx1 - cx0) * tfrom;
cy0 += (cy1 - cy0) * tfrom;
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
}
if (prefix == CornerPrefix.MOVE_THEN_CORNER) {
// Always execute moveTo so we break the path...
moveTo(sx, sy);
} else if (prefix == CornerPrefix.LINE_THEN_CORNER) {
if (numTypes == 1 ||
sx != currX ||
sy != currY)
{
lineTo(sx, sy);
}
}
if (tfrom == tto ||
(sx == cx0 && cx0 == cx1 && cx1 == ex &&
sy == cy0 && cy0 == cy1 && cy1 == ey))
{
if (prefix != CornerPrefix.LINE_THEN_CORNER) {
lineTo(ex, ey);
}
} else {
curveTo(cx0, cy0, cx1, cy1, ex, ey);
}
}
/**
* Append a portion of an ellipse to the path.
* The ellipse from which the portions are extracted follows the rules:
*
* - The ellipse will have its X axis tilted from horizontal by the
* angle {@code xAxisRotation} specified in radians.
*
- The ellipse will have the X and Y radii (viewed from its tilted
* coordinate system) specified by {@code radiusx} and {@code radiusy}
* unless that ellipse is too small to bridge the gap from the current
* point to the specified destination point in which case a larger
* ellipse with the same ratio of dimensions will be substituted instead.
*
- The ellipse may slide perpendicular to the direction from the
* current point to the specified destination point so that it just
* touches the two points.
* The direction it slides (to the "left" or to the "right") will be
* chosen to meet the criteria specified by the two boolean flags as
* described below.
* Only one direction will allow the method to meet both criteria.
*
- If the {@code largeArcFlag} is true, then the ellipse will sweep
* the longer way around the ellipse that meets these criteria.
*
- If the {@code sweepFlag} is true, then the ellipse will sweep
* clockwise around the ellipse that meets these criteria.
*
* The method will do nothing if the destination point is the same as
* the current point.
* The method will draw a simple line segment to the destination point
* if either of the two radii are zero.
*
* Note: This method adheres to the definition of an elliptical arc path
* segment from the SVG spec:
*
* http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
*
*
* @param radiusx the X radius of the tilted ellipse
* @param radiusy the Y radius of the tilted ellipse
* @param xAxisRotation the angle of tilt of the ellipse
* @param largeArcFlag true iff the path will sweep the long way around
* the ellipse
* @param sweepFlag true iff the path will sweep clockwise around
* the ellipse
* @param x the destination X coordinate
* @param y the destination Y coordinate
* @throws IllegalPathStateException
* if there is no current point in the path
*/
public void arcTo(float radiusx, float radiusy, float xAxisRotation,
boolean largeArcFlag, boolean sweepFlag,
float x, float y)
{
// First ensure preceding moveto
if (numTypes < 1) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
// Reference equations are provided for implementation assistance:
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// We use the following modifications:
// They use a secondary coordinate system which is based on
// - translating to the midpoint between the endpoints
// - rotating so that the xAxis is "horizontal"
// You can see that most of their math then has their secondary
// coordinates being divided by rx and ry everywhere so we scale
// by 1/rx and 1/ry so that we are working on a unit circle:
// [ x' ] [ +cos/rx +sin/rx ] [ x - mx ]
// [ ] = [ ] * [ ]
// [ y' ] [ -sin/ry +cos/ry ] [ y - my ]
// and reversing back to user space coordinates:
// [ x ] [ +cos -sin ] [ x' * rx ] [ mx ]
// [ ] = [ ] * [ ] + [ ]
// [ y ] [ +sin +cos ] [ y' * ry ] [ my ]
double rx = Math.abs(radiusx);
double ry = Math.abs(radiusy);
if (rx == 0 || ry == 0) {
lineTo(x, y);
return;
}
double x1 = currX;
double y1 = currY;
double x2 = x;
double y2 = y;
if (x1 == x2 && y1 == y2) {
return;
}
double cosphi, sinphi;
if (xAxisRotation == 0.0) {
cosphi = 1.0;
sinphi = 0.0;
} else {
cosphi = Math.cos(xAxisRotation);
sinphi = Math.sin(xAxisRotation);
}
double mx = (x1 + x2) / 2.0;
double my = (y1 + y2) / 2.0;
double relx1 = x1 - mx;
double rely1 = y1 - my;
double x1p = (cosphi * relx1 + sinphi * rely1) / rx;
double y1p = (cosphi * rely1 - sinphi * relx1) / ry;
// The documentation for the SVG arc operator recommends computing
// a "scale" value and then scaling the radii appropriately if the
// scale is greater than 1. Technically, they are computing the
// ratio of the distance to the endpoints compared to the distance
// across the indicated section of the ellipse centered at the midpoint.
// If the ratio is greater than 1 then the endpoints are outside the
// ellipse and so the ellipse is not large enough to bridge the gap
// without growing. If they are inside, then we slide the ellipse
// in the appropriate direction as specified by the 2 flags so that
// the transformed relative points are on the edge. If they
// are outside, then we note that we simply have a (distorted) half
// circle to render in that case since the endpoints will be on
// opposite sides of a stretched version of the unmoved ellipse.
double lenpsq = x1p * x1p + y1p * y1p;
if (lenpsq >= 1.0) {
// Unlike the reference equations, we do not need to scale the
// radii here since we will work directly from the transformed
// relative vectors which already have the proper distance from
// the midpoint. (They are already on the "stretched" ellipse.)
// Produce 2 quadrant circles from:
// x1p,y1p => xqp,yqp => x2p,y2p
// where x2p,y2p = -x1p,-y1p
// and xqp,yqp = either y1p,-x1p or -y1p,x1p depending on sweepFlag
// the corners of the quadrants are at:
// x1p+xqp,y1p+yqp and x2p+xqp,y2p+yqp
// or consequently at:
// x1+(xq-mx),y1+(yq-my) and x2+(xq-mx),y2+(yq-my)
double xqpr = y1p * rx;
double yqpr = x1p * ry;
if (sweepFlag) { xqpr = -xqpr; } else { yqpr = -yqpr; }
double relxq = cosphi * xqpr - sinphi * yqpr;
double relyq = cosphi * yqpr + sinphi * xqpr;
double xq = mx + relxq;
double yq = my + relyq;
double xc = x1 + relxq;
double yc = y1 + relyq;
appendOvalQuadrant((float) x1, (float) y1,
(float) xc, (float) yc,
(float) xq, (float) yq,
0f, 1f, CornerPrefix.CORNER_ONLY);
xc = x2 + relxq;
yc = y2 + relyq;
appendOvalQuadrant((float) xq, (float) yq,
(float) xc, (float) yc,
(float) x2, (float) y2,
0f, 1f, CornerPrefix.CORNER_ONLY);
return;
}
// We now need to displace the circle perpendicularly to the line
// between the end points so that the new center is at a unit distance
// to either end point. One component of the new distance will be
// the distance from the midpoint to either end point (the square
// of which is already computed in "den" above). The other component
// of the new distances will be how far we displace the center:
// lenpsq + displen^2 = 1.0
// displen^2 = 1.0 - lenpsq
// displen = sqrt(1 - lenpsq)
// The vector we displace along is the perpendicular of the x1p,y1p
// vector whose length is sqrt(lenpsq) so we need to divide that vector
// by that length to turn it into a unit vector:
// cxp = +/-y1p / sqrt(lenpsq) * displen
// cyp = +/-x1p / sqrt(lenpsq) * displen
// To simplify, we combine the "/sqrt(lenpsq)" factor into displen to
// share the one sqrt() calculation:
// scalef = displen / sqrt(lenpsq) = sqrt((1-lenpsq)/lenpsq)
double scalef = Math.sqrt((1.0 - lenpsq) / lenpsq);
// cxp,cyp is displaced perpendicularly to the relative vector x1p,y1p
// by the scalef value. The perpendicular is either -y1p,x1p or
// y1p,-x1p depending on the values of the flags.
double cxp = scalef * y1p;
double cyp = scalef * x1p;
// The direction of the perpendicular (which component is negated)
// depends on both flags.
if (largeArcFlag == sweepFlag) { cxp = -cxp; } else { cyp = -cyp; }
mx += (cosphi * cxp * rx - sinphi * cyp * ry);
my += (cosphi * cyp * ry + sinphi * cxp * rx);
// Now we sweep by quadrants in the direction specified until we
// reach the angle to the destination point and possibly perform
// one last partial-quadrant arc segment.
// First we need to reexpress our vectors relative to the new center.
double ux = x1p - cxp;
double uy = y1p - cyp;
// x2p = -x1p; x2p-cxp = -x1p-cxp = -(x1p+cxp)
// y2p = -y1p; y2p-cyp = -y1p-cyp = -(y1p+cyp)
double vx = -(x1p + cxp);
double vy = -(y1p + cyp);
// px and py are the factors that produce the perpendicular for ux,uy
// in the direction specified by sweepFlag.
boolean done = false; // set to true when we detect "last quadrant"
float quadlen = 1.0f; // 1.0 yields a full 90 degree arc at a time
boolean wasclose = false; // overshoot prevention
do {
// Compute the next circle quadrant endpoint, cw or ccw
double xqp = uy;
double yqp = ux;
if (sweepFlag) { xqp = -xqp; } else { yqp = -yqp; }
// qp.v > 0 tells us if sweep towards v is < 180
if (xqp * vx + yqp * vy > 0) {
// u.v >= 0 now tells us if sweep towards v is <= 90
// (It is also true for >270, but we already checked for <180)
double dot = ux * vx + uy * vy;
if (dot >= 0) {
// u.v is the cosine of the angle we have left since both
// u and v are unit vectors. We now need to express how
// much we want to shorten this last arc segment in terms
// of 0.0=>1.0 meaning 0=>90 degrees.
quadlen = (float) (Math.acos(dot) / (Math.PI / 2.0));
done = true;
}
// Remember that we were once within 180 degrees so we
// do not accidentally overshoot due to fp rounding error.
wasclose = true;
} else if (wasclose) {
// At some point we were in the <180 case above, but now we
// are back at the >180 case having never gone into the <90
// case where done would have been set to true. This should
// not happen, but since we are computing the perpendiculars
// and then expecting that they will have predictable results
// in the dot product equations, there is a theoretical chance
// of a tiny round-off error that would cause us to overshoot
// from just barely >90 left to suddenly past the 0 point.
// If that ever happens, we will end up in here and we can just
// break out of the loop since that last quadrant we rendered
// should have landed us right on top of the vx,vy location.
break;
}
double relxq = (cosphi * xqp * rx - sinphi * yqp * ry);
double relyq = (cosphi * yqp * ry + sinphi * xqp * rx);
double xq = mx + relxq;
double yq = my + relyq;
double xc = x1 + relxq;
double yc = y1 + relyq;
appendOvalQuadrant((float) x1, (float) y1,
(float) xc, (float) yc,
(float) xq, (float) yq,
0f, quadlen, CornerPrefix.CORNER_ONLY);
x1 = xq;
y1 = yq;
ux = xqp;
uy = yqp;
} while (!done);
}
/**
* Append a portion of an ellipse to the path using relative coordinates.
* This method is identical to calling:
*
* arcTo(radiusX, radiusY, xAxisRotation,
* largeArcFlag, sweepFlag,
* getCurrentX() + rx, getCurrentY() + ry);
*
*
* @param radiusx the X radius of the tilted ellipse
* @param radiusy the Y radius of the tilted ellipse
* @param xAxisRotation the angle of tilt of the ellipse
* @param largeArcFlag true iff the path will sweep the long way around
* the ellipse
* @param sweepFlag true iff the path will sweep clockwise around
* the ellipse
* @param relx the relative destination relative X coordinate
* @param rely the relative destination relative Y coordinate
* @throws IllegalPathStateException
* if there is no current point in the path
* @see Path2D#arcTo
*/
public void arcToRel(float radiusx, float radiusy, float xAxisRotation,
boolean largeArcFlag, boolean sweepFlag,
float relx, float rely)
{
arcTo(radiusx, radiusy, xAxisRotation,
largeArcFlag, sweepFlag,
currX + relx, currY + rely);
}
int pointCrossings(float px, float py) {
float movx, movy, curx, cury, endx, endy;
float coords[] = floatCoords;
curx = movx = coords[0];
cury = movy = coords[1];
int crossings = 0;
int ci = 2;
for (int i = 1; i < numTypes; i++) {
switch (pointTypes[i]) {
case PathIterator.SEG_MOVETO:
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
movx = curx = coords[ci++];
movy = cury = coords[ci++];
break;
case PathIterator.SEG_LINETO:
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
endx = coords[ci++],
endy = coords[ci++]);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_QUADTO:
crossings +=
Shape.pointCrossingsForQuad(px, py,
curx, cury,
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CUBICTO:
crossings +=
Shape.pointCrossingsForCubic(px, py,
curx, cury,
coords[ci++],
coords[ci++],
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CLOSE:
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
break;
}
}
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
return crossings;
}
int rectCrossings(float rxmin, float rymin,
float rxmax, float rymax)
{
float coords[] = floatCoords;
float curx, cury, movx, movy, endx, endy;
curx = movx = coords[0];
cury = movy = coords[1];
int crossings = 0;
int ci = 2;
for (int i = 1;
crossings != Shape.RECT_INTERSECTS && i < numTypes;
i++)
{
switch (pointTypes[i]) {
case PathIterator.SEG_MOVETO:
if (curx != movx || cury != movy) {
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
movx = curx = coords[ci++];
movy = cury = coords[ci++];
break;
case PathIterator.SEG_LINETO:
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
endx = coords[ci++],
endy = coords[ci++]);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_QUADTO:
crossings =
Shape.rectCrossingsForQuad(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CUBICTO:
crossings =
Shape.rectCrossingsForCubic(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
coords[ci++],
coords[ci++],
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CLOSE:
if (curx != movx || cury != movy) {
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
break;
}
}
if (crossings != Shape.RECT_INTERSECTS &&
(curx != movx || cury != movy))
{
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
return crossings;
}
/**
* {@inheritDoc}
*/
public final void append(PathIterator pi, boolean connect) {
float coords[] = new float[6];
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case SEG_MOVETO:
if (!connect || numTypes < 1 || numCoords < 1) {
moveTo(coords[0], coords[1]);
break;
}
if (pointTypes[numTypes - 1] != SEG_CLOSE &&
floatCoords[numCoords-2] == coords[0] &&
floatCoords[numCoords-1] == coords[1])
{
// Collapse out initial moveto/lineto
break;
}
// NO BREAK;
case SEG_LINETO:
lineTo(coords[0], coords[1]);
break;
case SEG_QUADTO:
quadTo(coords[0], coords[1],
coords[2], coords[3]);
break;
case SEG_CUBICTO:
curveTo(coords[0], coords[1],
coords[2], coords[3],
coords[4], coords[5]);
break;
case SEG_CLOSE:
closePath();
break;
}
pi.next();
connect = false;
}
}
/**
* {@inheritDoc}
*/
public final void transform(BaseTransform tx) {
if (numCoords == 0) return;
needRoom(false, 6);
floatCoords[numCoords + 0] = moveX;
floatCoords[numCoords + 1] = moveY;
floatCoords[numCoords + 2] = prevX;
floatCoords[numCoords + 3] = prevY;
floatCoords[numCoords + 4] = currX;
floatCoords[numCoords + 5] = currY;
tx.transform(floatCoords, 0, floatCoords, 0, numCoords / 2 + 3);
moveX = floatCoords[numCoords + 0];
moveY = floatCoords[numCoords + 1];
prevX = floatCoords[numCoords + 2];
prevY = floatCoords[numCoords + 3];
currX = floatCoords[numCoords + 4];
currY = floatCoords[numCoords + 5];
}
/**
* {@inheritDoc}
*/
@Override
public final RectBounds getBounds() {
float x1, y1, x2, y2;
int i = numCoords;
if (i > 0) {
y1 = y2 = floatCoords[--i];
x1 = x2 = floatCoords[--i];
while (i > 0) {
float y = floatCoords[--i];
float x = floatCoords[--i];
if (x < x1) x1 = x;
if (y < y1) y1 = y;
if (x > x2) x2 = x;
if (y > y2) y2 = y;
}
} else {
x1 = y1 = x2 = y2 = 0.0f;
}
return new RectBounds(x1, y1, x2, y2);
}
// The following three methods are used only by Prism to access
// internal structures; not intended for general use!
public final int getNumCommands() {
return numTypes;
}
public final byte[] getCommandsNoClone() {
return pointTypes;
}
public final float[] getFloatCoordsNoClone() {
return floatCoords;
}
/**
* {@inheritDoc}
*
* The iterator for this class is not multi-threaded safe,
* which means that the {@code Path2D} class does not
* guarantee that modifications to the geometry of this
* {@code Path2D} object do not affect any iterations of
* that geometry that are already in process.
*/
@Override
public PathIterator getPathIterator(BaseTransform tx) {
if (tx == null) {
return new CopyIterator(this);
} else {
return new TxIterator(this, tx);
}
}
static class CopyIterator extends Path2D.Iterator {
float floatCoords[];
CopyIterator(Path2D p2df) {
super(p2df);
this.floatCoords = p2df.floatCoords;
}
@Override
public int currentSegment(float[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
System.arraycopy(floatCoords, pointIdx,
coords, 0, numCoords);
}
return type;
}
public int currentSegment(double[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
for (int i = 0; i < numCoords; i++) {
coords[i] = floatCoords[pointIdx + i];
}
}
return type;
}
}
static class TxIterator extends Path2D.Iterator {
float floatCoords[];
BaseTransform transform;
TxIterator(Path2D p2df, BaseTransform tx) {
super(p2df);
this.floatCoords = p2df.floatCoords;
this.transform = tx;
}
@Override
public int currentSegment(float[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
transform.transform(floatCoords, pointIdx,
coords, 0, numCoords / 2);
}
return type;
}
public int currentSegment(double[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
transform.transform(floatCoords, pointIdx,
coords, 0, numCoords / 2);
}
return type;
}
}
/**
* Closes the current subpath by drawing a straight line back to
* the coordinates of the last {@code moveTo}. If the path is already
* closed then this method has no effect.
*/
@Override
public final void closePath() {
if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
needRoom(true, 0);
pointTypes[numTypes++] = SEG_CLOSE;
prevX = currX = moveX;
prevY = currY = moveY;
}
}
@Override
public void pathDone() {
}
/**
* Appends the geometry of the specified {@code Shape} object to the
* path, possibly connecting the new geometry to the existing path
* segments with a line segment.
* If the {@code connect} parameter is {@code true} and the
* path is not empty then any initial {@code moveTo} in the
* geometry of the appended {@code Shape}
* is turned into a {@code lineTo} segment.
* If the destination coordinates of such a connecting {@code lineTo}
* segment match the ending coordinates of a currently open
* subpath then the segment is omitted as superfluous.
* The winding rule of the specified {@code Shape} is ignored
* and the appended geometry is governed by the winding
* rule specified for this path.
*
* @param s the {@code Shape} whose geometry is appended
* to this path
* @param connect a boolean to control whether or not to turn an initial
* {@code moveTo} segment into a {@code lineTo} segment
* to connect the new geometry to the existing path
*/
public final void append(Shape s, boolean connect) {
append(s.getPathIterator(null), connect);
}
static class SVGParser {
final String svgpath;
final int len;
int pos;
boolean allowcomma;
public SVGParser(String svgpath) {
this.svgpath = svgpath;
this.len = svgpath.length();
}
public boolean isDone() {
return (toNextNonWsp() >= len);
}
public char getChar() {
return svgpath.charAt(pos++);
}
public boolean nextIsNumber() {
if (toNextNonWsp() < len) {
switch (svgpath.charAt(pos)) {
case '-':
case '+':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.':
return true;
}
}
return false;
}
public float f() {
return getFloat();
}
public float a() {
return (float) Math.toRadians(getFloat());
}
public float getFloat() {
int start = toNextNonWsp();
this.allowcomma = true;
int end = toNumberEnd();
if (start < end) {
String flstr = svgpath.substring(start, end);
try {
return Float.parseFloat(flstr);
} catch (NumberFormatException e) {
}
throw new IllegalArgumentException("invalid float ("+flstr+
") in path at pos="+start);
}
throw new IllegalArgumentException("end of path looking for float");
}
public boolean b() {
toNextNonWsp();
this.allowcomma = true;
if (pos < len) {
char flag = svgpath.charAt(pos);
switch (flag) {
case '0': pos++; return false;
case '1': pos++; return true;
}
throw new IllegalArgumentException("invalid boolean flag ("+flag+
") in path at pos="+pos);
}
throw new IllegalArgumentException("end of path looking for boolean");
}
private int toNextNonWsp() {
boolean canbecomma = this.allowcomma;
while (pos < len) {
switch (svgpath.charAt(pos)) {
case ',':
if (!canbecomma) {
return pos;
}
canbecomma = false;
break;
case ' ':
case '\t':
case '\r':
case '\n':
break;
default:
return pos;
}
pos++;
}
return pos;
}
private int toNumberEnd() {
boolean allowsign = true;
boolean hasexp = false;
boolean hasdecimal = false;
while (pos < len) {
switch (svgpath.charAt(pos)) {
case '-':
case '+':
if (!allowsign) return pos;
allowsign = false;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
allowsign = false;
break;
case 'E': case 'e':
if (hasexp) return pos;
hasexp = allowsign = true;
break;
case '.':
if (hasexp || hasdecimal) return pos;
hasdecimal = true;
allowsign = false;
break;
default:
return pos;
}
pos++;
}
return pos;
}
}
/**
* Appends the geometry of the path in the specified {@code String}
* argument in the format of an SVG path.
* The specification of the grammar of the language for an SVG path
* is specified on the W3C web page:
*
* http://www.w3.org/TR/SVG/paths.html#PathDataBNF
*
* and the interpretation of the various elements in the format is
* specified on the W3C web page:
*
* http://www.w3.org/TR/SVG/paths.html#PathData
*
*
* @param svgpath the {@code String} object containing the SVG style
* definition of the geometry to be apppended
* @throws IllegalArgumentException
* if {@code svgpath} does not match the indicated SVG path grammar
* @throws IllegalPathStateException
* if there is no current point in the path
*/
public final void appendSVGPath(String svgpath) {
SVGParser p = new SVGParser(svgpath);
p.allowcomma = false;
while (!p.isDone()) {
p.allowcomma = false;
char cmd = p.getChar();
switch (cmd) {
case 'M':
moveTo(p.f(), p.f());
while (p.nextIsNumber()) {
lineTo(p.f(), p.f());
}
break;
case 'm':
if (numTypes > 0) {
moveToRel(p.f(), p.f());
} else {
moveTo(p.f(), p.f());
}
while (p.nextIsNumber()) {
lineToRel(p.f(), p.f());
}
break;
case 'L':
do {
lineTo(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'l':
do {
lineToRel(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'H':
do {
lineTo(p.f(), currY);
} while (p.nextIsNumber());
break;
case 'h':
do {
lineToRel(p.f(), 0);
} while (p.nextIsNumber());
break;
case 'V':
do {
lineTo(currX, p.f());
} while (p.nextIsNumber());
break;
case 'v':
do {
lineToRel(0, p.f());
} while (p.nextIsNumber());
break;
case 'Q':
do {
quadTo(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'q':
do {
quadToRel(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'T':
do {
quadToSmooth(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 't':
do {
quadToSmoothRel(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'C':
do {
curveTo(p.f(), p.f(), p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'c':
do {
curveToRel(p.f(), p.f(), p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'S':
do {
curveToSmooth(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 's':
do {
curveToSmoothRel(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'A':
do {
arcTo(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'a':
do {
arcToRel(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'Z': case 'z': closePath(); break;
default:
throw new IllegalArgumentException("invalid command ("+cmd+
") in SVG path at pos="+p.pos);
}
p.allowcomma = false;
}
}
/**
* Returns the fill style winding rule.
*
* @return an integer representing the current winding rule.
* @see #WIND_EVEN_ODD
* @see #WIND_NON_ZERO
* @see #setWindingRule
*/
public final int getWindingRule() {
return windingRule;
}
/**
* Sets the winding rule for this path to the specified value.
*
* @param rule an integer representing the specified
* winding rule
* @exception IllegalArgumentException if
* {@code rule} is not either
* {@link #WIND_EVEN_ODD} or
* {@link #WIND_NON_ZERO}
* @see #getWindingRule
*/
public final void setWindingRule(int rule) {
if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
throw new IllegalArgumentException("winding rule must be "+
"WIND_EVEN_ODD or "+
"WIND_NON_ZERO");
}
windingRule = rule;
}
/**
* Returns the coordinates most recently added to the end of the path
* as a {@link Point2D} object.
*
* @return a {@code Point2D} object containing the ending coordinates of
* the path or {@code null} if there are no points in the path.
*/
public final Point2D getCurrentPoint() {
if (numTypes < 1) {
return null;
}
return new Point2D(currX, currY);
}
public final float getCurrentX() {
if (numTypes < 1) {
throw new IllegalPathStateException("no current point in empty path");
}
return currX;
}
public final float getCurrentY() {
if (numTypes < 1) {
throw new IllegalPathStateException("no current point in empty path");
}
return currY;
}
/**
* Resets the path to empty. The append position is set back to the
* beginning of the path and all coordinates and point types are
* forgotten.
*/
public final void reset() {
numTypes = numCoords = 0;
moveX = moveY = prevX = prevY = currX = currY = 0;
}
/**
* Returns a new {@code Shape} representing a transformed version
* of this {@code Path2D}.
* Note that the exact type and coordinate precision of the return
* value is not specified for this method.
* The method will return a Shape that contains no less precision
* for the transformed geometry than this {@code Path2D} currently
* maintains, but it may contain no more precision either.
* If the tradeoff of precision vs. storage size in the result is
* important then the convenience constructors in the
* {@link Path2D(Shape, BaseTransform) Path2D}
*
* @param tx the {@code BaseTransform} used to transform a
* new {@code Shape}.
* @return a new {@code Shape}, transformed with the specified
* {@code BaseTransform}.
*/
public final Shape createTransformedShape(BaseTransform tx) {
return new Path2D(this, tx);
}
@Override
public Path2D copy() {
return new Path2D(this);
}
/**
* {@inheritDoc}
*
* Note that this method may return false when the geometry of the
* given {@code Path2D} is identical to the geometry of this object
* but is expressed in a different way. This method will only return
* true when the internal representation of this object is exactly the
* same as that of the given object.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Path2D) {
Path2D p = (Path2D)obj;
if (p.numTypes == this.numTypes &&
p.numCoords == this.numCoords &&
p.windingRule == this.windingRule)
{
for (int i = 0; i < numTypes; i++) {
if (p.pointTypes[i] != this.pointTypes[i]) {
return false;
}
}
for (int i = 0; i < numCoords; i++) {
if (p.floatCoords[i] != this.floatCoords[i]) {
return false;
}
}
return true;
}
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 11 * hash + numTypes;
hash = 11 * hash + numCoords;
hash = 11 * hash + windingRule;
for (int i = 0; i < numTypes; i++) {
hash = 11 * hash + pointTypes[i];
}
for (int i = 0; i < numCoords; i++) {
hash = 11 * hash + Float.floatToIntBits(floatCoords[i]);
}
return hash;
}
/**
* Tests if the specified coordinates are inside the closed
* boundary of the specified {@link PathIterator}.
*
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(double, double)} method.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @return {@code true} if the specified coordinates are inside the
* specified {@code PathIterator}; {@code false} otherwise
*/
public static boolean contains(PathIterator pi, float x, float y) {
if (x * 0f + y * 0f == 0f) {
/* N * 0.0 is 0.0 only if N is finite.
* Here we know that both x and y are finite.
*/
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 1);
int cross = Shape.pointCrossingsForPath(pi, x, y);
return ((cross & mask) != 0);
} else {
/* Either x or y was infinite or NaN.
* A NaN always produces a negative response to any test
* and Infinity values cannot be "inside" any path so
* they should return false as well.
*/
return false;
}
}
/**
* Tests if the specified {@link Point2D} is inside the closed
* boundary of the specified {@link PathIterator}.
*
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(Point2D)} method.
*
* @param pi the specified {@code PathIterator}
* @param p the specified {@code Point2D}
* @return {@code true} if the specified coordinates are inside the
* specified {@code PathIterator}; {@code false} otherwise
*/
public static boolean contains(PathIterator pi, Point2D p) {
return contains(pi, p.x, p.y);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean contains(float x, float y) {
if (x * 0f + y * 0f == 0f) {
/* N * 0.0 is 0.0 only if N is finite.
* Here we know that both x and y are finite.
*/
if (numTypes < 2) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 1);
return ((pointCrossings(x, y) & mask) != 0);
} else {
/* Either x or y was infinite or NaN.
* A NaN always produces a negative response to any test
* and Infinity values cannot be "inside" any path so
* they should return false as well.
*/
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public final boolean contains(Point2D p) {
return contains(p.x, p.y);
}
/**
* Tests if the specified rectangular area is entirely inside the
* closed boundary of the specified {@link PathIterator}.
*
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(double, double, double, double)} method.
*
* This method object may conservatively return false in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such segments could lie entirely within the interior of the
* path if they are part of a path with a {@link #WIND_NON_ZERO}
* winding rule or if the segments are retraced in the reverse
* direction such that the two sets of segments cancel each
* other out without any exterior area falling between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @param w the width of the specified rectangular area
* @param h the height of the specified rectangular area
* @return {@code true} if the specified {@code PathIterator} contains
* the specified rectangluar area; {@code false} otherwise.
*/
public static boolean contains(PathIterator pi,
float x, float y, float w, float h)
{
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2);
int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
return (crossings != Shape.RECT_INTERSECTS &&
(crossings & mask) != 0);
}
/**
* {@inheritDoc}
*
* This method object may conservatively return false in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such segments could lie entirely within the interior of the
* path if they are part of a path with a {@link #WIND_NON_ZERO}
* winding rule or if the segments are retraced in the reverse
* direction such that the two sets of segments cancel each
* other out without any exterior area falling between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*/
@Override
public final boolean contains(float x, float y, float w, float h) {
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
int crossings = rectCrossings(x, y, x+w, y+h);
return (crossings != Shape.RECT_INTERSECTS &&
(crossings & mask) != 0);
}
/**
* Tests if the interior of the specified {@link PathIterator}
* intersects the interior of a specified set of rectangular
* coordinates.
*
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#intersects(double, double, double, double)} method.
*
* This method object may conservatively return true in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such a case may occur if some set of segments of the
* path are retraced in the reverse direction such that the
* two sets of segments cancel each other out without any
* interior area between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @param w the width of the specified rectangular coordinates
* @param h the height of the specified rectangular coordinates
* @return {@code true} if the specified {@code PathIterator} and
* the interior of the specified set of rectangular
* coordinates intersect each other; {@code false} otherwise.
*/
public static boolean intersects(PathIterator pi,
float x, float y, float w, float h)
{
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2);
int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
return (crossings == Shape.RECT_INTERSECTS ||
(crossings & mask) != 0);
}
/**
* {@inheritDoc}
*
* This method object may conservatively return true in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such a case may occur if some set of segments of the
* path are retraced in the reverse direction such that the
* two sets of segments cancel each other out without any
* interior area between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*/
@Override
public final boolean intersects(float x, float y, float w, float h) {
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
int crossings = rectCrossings(x, y, x+w, y+h);
return (crossings == Shape.RECT_INTERSECTS ||
(crossings & mask) != 0);
}
/**
* {@inheritDoc}
*
* The iterator for this class is not multi-threaded safe,
* which means that this {@code Path2D} class does not
* guarantee that modifications to the geometry of this
* {@code Path2D} object do not affect any iterations of
* that geometry that are already in process.
*/
@Override
public PathIterator getPathIterator(BaseTransform tx,
float flatness)
{
return new FlatteningPathIterator(getPathIterator(tx), flatness);
}
static abstract class Iterator implements PathIterator {
int typeIdx;
int pointIdx;
Path2D path;
Iterator(Path2D path) {
this.path = path;
}
@Override
public int getWindingRule() {
return path.getWindingRule();
}
@Override
public boolean isDone() {
return (typeIdx >= path.numTypes);
}
@Override
public void next() {
int type = path.pointTypes[typeIdx++];
pointIdx += curvecoords[type];
}
}
public void setTo(Path2D otherPath) {
numTypes = otherPath.numTypes;
numCoords = otherPath.numCoords;
if (numTypes > pointTypes.length) {
pointTypes = new byte[numTypes];
}
System.arraycopy(otherPath.pointTypes, 0, pointTypes, 0, numTypes);
if (numCoords > floatCoords.length) {
floatCoords = new float[numCoords];
}
System.arraycopy(otherPath.floatCoords, 0, floatCoords, 0, numCoords);
windingRule = otherPath.windingRule;
moveX = otherPath.moveX;
moveY = otherPath.moveY;
prevX = otherPath.prevX;
prevY = otherPath.prevY;
currX = otherPath.currX;
currY = otherPath.currY;
}
}