/*
* Copyright (c) 2008, Piccolo2D project, http://piccolo2d.org
* Copyright (c) 1998-2008, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
* contributors may be used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.umd.cs.piccolox.util;
import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import sun.dc.path.FastPathProducer;
import sun.dc.path.PathConsumer;
import sun.dc.path.PathException;
import sun.dc.pr.PathDasher;
import sun.dc.pr.PathStroker;
import sun.dc.pr.Rasterizer;
import edu.umd.cs.piccolo.util.PAffineTransform;
import edu.umd.cs.piccolo.util.PDebug;
import edu.umd.cs.piccolo.util.PPaintContext;
import edu.umd.cs.piccolo.util.PPickPath;
/**
* <b>PFixedWidthStroke</b> is the same as {@link java.awt.BasicStroke} except
* that PFixedWidthStroke has a fixed width on the screen so that even when the
* canvas view is zooming its width stays the same in canvas coordinates. Note
* that this stroke draws in the inside of the stroked shape, instead of the
* normal draw on center behavior.
* <P>
*
* @see edu.umd.cs.piccolo.nodes.PPath
* @version 1.0
* @author Jesse Grosjean
*/
public class PFixedWidthStroke implements Stroke, Serializable {
private static PAffineTransform TEMP_TRANSFORM = new PAffineTransform();
private static GeneralPath TEMP_PATH = new GeneralPath(GeneralPath.WIND_NON_ZERO);
final static int JOIN_MITER = BasicStroke.JOIN_MITER;
final static int JOIN_ROUND = BasicStroke.JOIN_ROUND;
final static int JOIN_BEVEL = BasicStroke.JOIN_BEVEL;
final static int CAP_BUTT = BasicStroke.CAP_BUTT;
final static int CAP_ROUND = BasicStroke.CAP_ROUND;
final static int CAP_SQUARE = BasicStroke.CAP_SQUARE;
private float width;
private int join;
private int cap;
private float miterlimit;
private float dash[];
private float dash_phase;
private static final int RasterizerCaps[] = { Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE };
private static final int RasterizerCorners[] = { Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL };
private class FillAdapter implements PathConsumer {
boolean closed;
GeneralPath path;
public FillAdapter() {
path = TEMP_PATH;
path.reset();
}
public Shape getShape() {
return path;
}
public void beginPath() {
}
public void beginSubpath(float x0, float y0) {
if (closed) {
path.closePath();
closed = false;
}
path.moveTo(x0, y0);
}
public void appendLine(float x1, float y1) {
path.lineTo(x1, y1);
}
public void appendQuadratic(float xm, float ym, float x1, float y1) {
path.quadTo(xm, ym, x1, y1);
}
public void appendCubic(float xm, float ym, float xn, float yn, float x1, float y1) {
path.curveTo(xm, ym, xn, yn, x1, y1);
}
public void closedSubpath() {
closed = true;
}
public void endPath() {
if (closed) {
path.closePath();
closed = false;
}
}
public void useProxy(FastPathProducer proxy) throws PathException {
proxy.sendTo(this);
}
public long getCPathConsumer() {
return 0;
}
public void dispose() {
}
public PathConsumer getConsumer() {
return null;
}
}
public PFixedWidthStroke() {
this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
}
public PFixedWidthStroke(float width) {
this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
}
public PFixedWidthStroke(float width, int cap, int join) {
this(width, cap, join, 10.0f, null, 0.0f);
}
public PFixedWidthStroke(float width, int cap, int join, float miterlimit) {
this(width, cap, join, miterlimit, null, 0.0f);
}
public PFixedWidthStroke(float width, int cap, int join, float miterlimit, float dash[], float dash_phase) {
if (width < 0.0f) {
throw new IllegalArgumentException("negative width");
}
if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
throw new IllegalArgumentException("illegal end cap value");
}
if (join == JOIN_MITER) {
if (miterlimit < 1.0f) {
throw new IllegalArgumentException("miter limit < 1");
}
}
else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
throw new IllegalArgumentException("illegal line join value");
}
if (dash != null) {
if (dash_phase < 0.0f) {
throw new IllegalArgumentException("negative dash phase");
}
boolean allzero = true;
for (int i = 0; i < dash.length; i++) {
float d = dash[i];
if (d > 0.0) {
allzero = false;
}
else if (d < 0.0) {
throw new IllegalArgumentException("negative dash length");
}
}
if (allzero) {
throw new IllegalArgumentException("dash lengths all zero");
}
}
this.width = width;
this.cap = cap;
this.join = join;
this.miterlimit = miterlimit;
if (dash != null) {
this.dash = (float[]) dash.clone();
}
this.dash_phase = dash_phase;
}
public Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public Shape createStrokedShape(Shape s) {
FillAdapter filler = new FillAdapter();
PathStroker stroker = new PathStroker(filler);
PathConsumer consumer;
// Fixed Width Additions, always stroke path inside shape.
// Also keeps dashes fixed when zooming (thanks to Shawn Castrianni -
// Dec 2006)
float fixedScale = 1.0f;
if (PDebug.getProcessingOutput()) {
if (PPaintContext.CURRENT_PAINT_CONTEXT != null) {
fixedScale = 1.0f / (float) PPaintContext.CURRENT_PAINT_CONTEXT.getScale();
}
}
else {
if (PPickPath.CURRENT_PICK_PATH != null) {
fixedScale = 1.0f / (float) PPickPath.CURRENT_PICK_PATH.getScale();
}
}
float fixedWidth = width * fixedScale;
Rectangle2D bounds = s.getBounds2D();
double scale = 1.0;
if (bounds.getWidth() > bounds.getHeight()) {
if (bounds.getWidth() != 0) {
scale = (bounds.getWidth() - fixedWidth) / bounds.getWidth();
}
}
else {
if (bounds.getHeight() != 0) {
scale = (bounds.getHeight() - fixedWidth) / bounds.getHeight();
}
}
TEMP_TRANSFORM.setToIdentity();
TEMP_TRANSFORM.scaleAboutPoint(scale, bounds.getCenterX(), bounds.getCenterY());
stroker.setPenDiameter(fixedWidth);
PathIterator pi = s.getPathIterator(TEMP_TRANSFORM);
stroker.setPenT4(null);
stroker.setCaps(RasterizerCaps[cap]);
stroker.setCorners(RasterizerCorners[join], miterlimit);
if (dash != null) {
// Fixed Width Additions
float fixedDash[] = new float[dash.length];
for (int i = 0; i < dash.length; i++) {
fixedDash[i] = dash[i] * fixedScale;
}
float fixedDashPhase = dash_phase * fixedScale;
PathDasher dasher = new PathDasher(stroker);
dasher.setDash(fixedDash, fixedDashPhase);
dasher.setDashT4(null);
consumer = dasher;
}
else {
consumer = stroker;
}
try {
consumer.beginPath();
boolean pathClosed = false;
float mx = 0.0f;
float my = 0.0f;
float point[] = new float[6];
while (!pi.isDone()) {
int type = pi.currentSegment(point);
if (pathClosed == true) {
pathClosed = false;
if (type != PathIterator.SEG_MOVETO) {
// Force current point back to last moveto point
consumer.beginSubpath(mx, my);
}
}
switch (type) {
case PathIterator.SEG_MOVETO:
mx = point[0];
my = point[1];
consumer.beginSubpath(point[0], point[1]);
break;
case PathIterator.SEG_LINETO:
consumer.appendLine(point[0], point[1]);
break;
case PathIterator.SEG_QUADTO:
// Quadratic curves take two points
consumer.appendQuadratic(point[0], point[1], point[2], point[3]);
break;
case PathIterator.SEG_CUBICTO:
// Cubic curves take three points
consumer.appendCubic(point[0], point[1], point[2], point[3], point[4], point[5]);
break;
case PathIterator.SEG_CLOSE:
consumer.closedSubpath();
pathClosed = true;
break;
}
pi.next();
}
consumer.endPath();
consumer.dispose(); // hack to fix memory leak, shouldn't be
// neccessary but is.
}
catch (PathException e) {
throw new InternalError("Unable to Stroke shape (" + e.getMessage() + ")");
}
return filler.getShape();
}
public boolean equals(Object obj) {
if (!(obj instanceof PFixedWidthStroke)) {
return false;
}
PFixedWidthStroke bs = (PFixedWidthStroke) obj;
if (width != bs.width) {
return false;
}
if (join != bs.join) {
return false;
}
if (cap != bs.cap) {
return false;
}
if (miterlimit != bs.miterlimit) {
return false;
}
if (dash != null) {
if (dash_phase != bs.dash_phase) {
return false;
}
if (!java.util.Arrays.equals(dash, bs.dash)) {
return false;
}
}
else if (bs.dash != null) {
return false;
}
return true;
}
private float[] getDashArray() {
if (dash == null) {
return null;
}
return (float[]) dash.clone();
}
private float getDashPhase() {
return dash_phase;
}
private int getEndCap() {
return cap;
}
private int getLineJoin() {
return join;
}
private float getLineWidth() {
return width;
}
private float getMiterLimit() {
return miterlimit;
}
public int hashCode() {
int hash = Float.floatToIntBits(width);
hash = hash * 31 + join;
hash = hash * 31 + cap;
hash = hash * 31 + Float.floatToIntBits(miterlimit);
if (dash != null) {
hash = hash * 31 + Float.floatToIntBits(dash_phase);
for (int i = 0; i < dash.length; i++) {
hash = hash * 31 + Float.floatToIntBits(dash[i]);
}
}
return hash;
}
}