replicant-frameworks_native/awt/java/awt/BasicStroke.java
2009-03-03 19:31:44 -08:00

2444 lines
72 KiB
Java

/*
* 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;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import org.apache.harmony.awt.internal.nls.Messages;
import org.apache.harmony.misc.HashCode;
/**
* The BasicStroke class specifies a set of rendering attributes for the
* outlines of graphics primitives. The BasicStroke attributes describe the
* shape of the pen which draws the outline of a Shape and the decorations
* applied at the ends and joins of path segments of the Shape. The BasicStroke
* has the following rendering attributes:
* <p>
* <ul>
* <li>line width -the pen width which draws the outlines.</li>
* <li>end caps - indicates the decoration applied to the ends of unclosed
* subpaths and dash segments. The BasicStroke defines three different
* decorations: CAP_BUTT, CAP_ROUND, and CAP_SQUARE.</li>
* <li>line joins - indicates the decoration applied at the intersection of two
* path segments and at the intersection of the endpoints of a subpath. The
* BasicStroke defines three decorations: JOIN_BEVEL, JOIN_MITER, and
* JOIN_ROUND.</li>
* <li>miter limit - the limit to trim a line join that has a JOIN_MITER
* decoration.</li>
* <li>dash attributes - the definition of how to make a dash pattern by
* alternating between opaque and transparent sections</li>
* </ul>
* </p>
*
* @since Android 1.0
*/
public class BasicStroke implements Stroke {
/**
* The Constant CAP_BUTT indicates the ends of unclosed subpaths and dash
* segments have no added decoration.
*/
public static final int CAP_BUTT = 0;
/**
* The Constant CAP_ROUND indicates the ends of unclosed subpaths and dash
* segments have a round decoration.
*/
public static final int CAP_ROUND = 1;
/**
* The Constant CAP_SQUARE indicates the ends of unclosed subpaths and dash
* segments have a square projection.
*/
public static final int CAP_SQUARE = 2;
/**
* The Constant JOIN_MITER indicates that path segments are joined by
* extending their outside edges until they meet.
*/
public static final int JOIN_MITER = 0;
/**
* The Constant JOIN_ROUND indicates that path segments are joined by
* rounding off the corner at a radius of half the line width.
*/
public static final int JOIN_ROUND = 1;
/**
* The Constant JOIN_BEVEL indicates that path segments are joined by
* connecting the outer corners of their wide outlines with a straight
* segment.
*/
public static final int JOIN_BEVEL = 2;
/**
* Constants for calculating.
*/
static final int MAX_LEVEL = 20; // Maximal deepness of curve subdivision
/**
* The Constant CURVE_DELTA.
*/
static final double CURVE_DELTA = 2.0; // Width tolerance
/**
* The Constant CORNER_ANGLE.
*/
static final double CORNER_ANGLE = 4.0; // Minimum corner angle
/**
* The Constant CORNER_ZERO.
*/
static final double CORNER_ZERO = 0.01; // Zero angle
/**
* The Constant CUBIC_ARC.
*/
static final double CUBIC_ARC = 4.0 / 3.0 * (Math.sqrt(2.0) - 1);
/**
* Stroke width.
*/
float width;
/**
* Stroke cap type.
*/
int cap;
/**
* Stroke join type.
*/
int join;
/**
* Stroke miter limit.
*/
float miterLimit;
/**
* Stroke dashes array.
*/
float dash[];
/**
* Stroke dash phase.
*/
float dashPhase;
/**
* The temporary pre-calculated values.
*/
double curveDelta;
/**
* The corner delta.
*/
double cornerDelta;
/**
* The zero delta.
*/
double zeroDelta;
/**
* The w2.
*/
double w2;
/**
* The fmy.
*/
double fmx, fmy;
/**
* The smy.
*/
double scx, scy, smx, smy;
/**
* The cy.
*/
double mx, my, cx, cy;
/**
* The temporary indicators.
*/
boolean isMove;
/**
* The is first.
*/
boolean isFirst;
/**
* The check move.
*/
boolean checkMove;
/**
* The temporary and destination work paths.
*/
BufferedPath dst, lp, rp, sp;
/**
* Stroke dasher class.
*/
Dasher dasher;
/**
* Instantiates a new BasicStroke with default width, cap, join, limit, dash
* attributes parameters. The default parameters are a solid line of width
* 1.0, CAP_SQUARE, JOIN_MITER, a miter limit of 10.0, null dash attributes,
* and a dash phase of 0.0f.
*/
public BasicStroke() {
this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
}
/**
* Instantiates a new BasicStroke with the specified width, caps, joins,
* limit, dash attributes, dash phase parameters.
*
* @param width
* the width of BasikStroke.
* @param cap
* the end decoration of BasikStroke.
* @param join
* the join segments decoration.
* @param miterLimit
* the limit to trim the miter join.
* @param dash
* the array with the dashing pattern.
* @param dashPhase
* the offset to start the dashing pattern.
*/
public BasicStroke(float width, int cap, int join, float miterLimit, float[] dash,
float dashPhase) {
if (width < 0.0f) {
// awt.133=Negative width
throw new IllegalArgumentException(Messages.getString("awt.133")); //$NON-NLS-1$
}
if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
// awt.134=Illegal cap
throw new IllegalArgumentException(Messages.getString("awt.134")); //$NON-NLS-1$
}
if (join != JOIN_MITER && join != JOIN_ROUND && join != JOIN_BEVEL) {
// awt.135=Illegal join
throw new IllegalArgumentException(Messages.getString("awt.135")); //$NON-NLS-1$
}
if (join == JOIN_MITER && miterLimit < 1.0f) {
// awt.136=miterLimit less than 1.0f
throw new IllegalArgumentException(Messages.getString("awt.136")); //$NON-NLS-1$
}
if (dash != null) {
if (dashPhase < 0.0f) {
// awt.137=Negative dashPhase
throw new IllegalArgumentException(Messages.getString("awt.137")); //$NON-NLS-1$
}
if (dash.length == 0) {
// awt.138=Zero dash length
throw new IllegalArgumentException(Messages.getString("awt.138")); //$NON-NLS-1$
}
ZERO: {
for (int i = 0; i < dash.length; i++) {
if (dash[i] < 0.0) {
// awt.139=Negative dash[{0}]
throw new IllegalArgumentException(Messages.getString("awt.139", i)); //$NON-NLS-1$
}
if (dash[i] > 0.0) {
break ZERO;
}
}
// awt.13A=All dash lengths zero
throw new IllegalArgumentException(Messages.getString("awt.13A")); //$NON-NLS-1$
}
}
this.width = width;
this.cap = cap;
this.join = join;
this.miterLimit = miterLimit;
this.dash = dash;
this.dashPhase = dashPhase;
}
/**
* Instantiates a new BasicStroke with specified width, cap, join, limit and
* default dash attributes parameters.
*
* @param width
* the width of BasikStroke.
* @param cap
* the end decoration of BasikStroke.
* @param join
* the join segments decoration.
* @param miterLimit
* the limit to trim the miter join.
*/
public BasicStroke(float width, int cap, int join, float miterLimit) {
this(width, cap, join, miterLimit, null, 0.0f);
}
/**
* Instantiates a new BasicStroke with specified width, cap, join and
* default limit and dash attributes parameters.
*
* @param width
* the width of BasikStroke.
* @param cap
* the end decoration of BasikStroke.
* @param join
* the join segments decoration.
*/
public BasicStroke(float width, int cap, int join) {
this(width, cap, join, 10.0f, null, 0.0f);
}
/**
* Instantiates a new BasicStroke with specified width and default cap,
* join, limit, dash attributes parameters.
*
* @param width
* the width of BasicStroke.
*/
public BasicStroke(float width) {
this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
}
/**
* Gets the line width of the BasicStroke.
*
* @return the line width of the BasicStroke.
*/
public float getLineWidth() {
return width;
}
/**
* Gets the end cap style of the BasicStroke.
*
* @return the end cap style of the BasicStroke.
*/
public int getEndCap() {
return cap;
}
/**
* Gets the line join style of the BasicStroke.
*
* @return the line join style of the BasicStroke.
*/
public int getLineJoin() {
return join;
}
/**
* Gets the miter limit of the BasicStroke (the limit to trim the miter
* join).
*
* @return the miter limit of the BasicStroke.
*/
public float getMiterLimit() {
return miterLimit;
}
/**
* Gets the dash attributes array of the BasicStroke.
*
* @return the dash attributes array of the BasicStroke.
*/
public float[] getDashArray() {
return dash;
}
/**
* Gets the dash phase of the BasicStroke.
*
* @return the dash phase of the BasicStroke.
*/
public float getDashPhase() {
return dashPhase;
}
/**
* Returns hash code of this BasicStroke.
*
* @return the hash code of this BasicStroke.
*/
@Override
public int hashCode() {
HashCode hash = new HashCode();
hash.append(width);
hash.append(cap);
hash.append(join);
hash.append(miterLimit);
if (dash != null) {
hash.append(dashPhase);
for (float element : dash) {
hash.append(element);
}
}
return hash.hashCode();
}
/**
* Compares this BasicStroke object with the specified Object.
*
* @param obj
* the Object to be compared.
* @return true, if the Object is a BasicStroke with the same data values as
* this BasicStroke; false otherwise.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof BasicStroke) {
BasicStroke bs = (BasicStroke)obj;
return bs.width == width && bs.cap == cap && bs.join == join
&& bs.miterLimit == miterLimit && bs.dashPhase == dashPhase
&& java.util.Arrays.equals(bs.dash, dash);
}
return false;
}
/**
* Calculates allowable curve derivation.
*
* @param width
* the width.
* @return the curve delta.
*/
double getCurveDelta(double width) {
double a = width + CURVE_DELTA;
double cos = 1.0 - 2.0 * width * width / (a * a);
double sin = Math.sqrt(1.0 - cos * cos);
return Math.abs(sin / cos);
}
/**
* Calculates the value to detect a small angle.
*
* @param width
* the width.
* @return the corner delta.
*/
double getCornerDelta(double width) {
return width * width * Math.sin(Math.PI * CORNER_ANGLE / 180.0);
}
/**
* Calculates value to detect a zero angle.
*
* @param width
* the width.
* @return the zero delta.
*/
double getZeroDelta(double width) {
return width * width * Math.sin(Math.PI * CORNER_ZERO / 180.0);
}
/**
* Creates a Shape from the outline of the specified shape drawn with this
* BasicStroke.
*
* @param s
* the specified Shape to be stroked.
* @return the Shape of the stroked outline.
* @see java.awt.Stroke#createStrokedShape(java.awt.Shape)
*/
public Shape createStrokedShape(Shape s) {
w2 = width / 2.0;
curveDelta = getCurveDelta(w2);
cornerDelta = getCornerDelta(w2);
zeroDelta = getZeroDelta(w2);
dst = new BufferedPath();
lp = new BufferedPath();
rp = new BufferedPath();
if (dash == null) {
createSolidShape(s.getPathIterator(null));
} else {
createDashedShape(s.getPathIterator(null));
}
return dst.createGeneralPath();
}
/**
* Generates a shape with a solid (not dashed) outline.
*
* @param p
* the PathIterator of source shape.
*/
void createSolidShape(PathIterator p) {
double coords[] = new double[6];
mx = my = cx = cy = 0.0;
isMove = false;
isFirst = false;
checkMove = true;
boolean isClosed = true;
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (!isClosed) {
closeSolidShape();
}
rp.clean();
mx = cx = coords[0];
my = cy = coords[1];
isMove = true;
isClosed = false;
break;
case PathIterator.SEG_LINETO:
addLine(cx, cy, cx = coords[0], cy = coords[1], true);
break;
case PathIterator.SEG_QUADTO:
addQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3]);
break;
case PathIterator.SEG_CUBICTO:
addCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], cx = coords[4],
cy = coords[5]);
break;
case PathIterator.SEG_CLOSE:
addLine(cx, cy, mx, my, false);
addJoin(lp, mx, my, lp.xMove, lp.yMove, true);
addJoin(rp, mx, my, rp.xMove, rp.yMove, false);
lp.closePath();
rp.closePath();
lp.appendReverse(rp);
isClosed = true;
break;
}
p.next();
}
if (!isClosed) {
closeSolidShape();
}
dst = lp;
}
/**
* Closes solid shape path.
*/
void closeSolidShape() {
addCap(lp, cx, cy, rp.xLast, rp.yLast);
lp.combine(rp);
addCap(lp, mx, my, lp.xMove, lp.yMove);
lp.closePath();
}
/**
* Generates dashed stroked shape.
*
* @param p
* the PathIterator of source shape.
*/
void createDashedShape(PathIterator p) {
double coords[] = new double[6];
mx = my = cx = cy = 0.0;
smx = smy = scx = scy = 0.0;
isMove = false;
checkMove = false;
boolean isClosed = true;
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (!isClosed) {
closeDashedShape();
}
dasher = new Dasher(dash, dashPhase);
lp.clean();
rp.clean();
sp = null;
isFirst = true;
isMove = true;
isClosed = false;
mx = cx = coords[0];
my = cy = coords[1];
break;
case PathIterator.SEG_LINETO:
addDashLine(cx, cy, cx = coords[0], cy = coords[1]);
break;
case PathIterator.SEG_QUADTO:
addDashQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3]);
break;
case PathIterator.SEG_CUBICTO:
addDashCubic(cx, cy, coords[0], coords[1], coords[2], coords[3],
cx = coords[4], cy = coords[5]);
break;
case PathIterator.SEG_CLOSE:
addDashLine(cx, cy, cx = mx, cy = my);
if (dasher.isConnected()) {
// Connect current and head segments
addJoin(lp, fmx, fmy, sp.xMove, sp.yMove, true);
lp.join(sp);
addJoin(lp, fmx, fmy, rp.xLast, rp.yLast, true);
lp.combine(rp);
addCap(lp, smx, smy, lp.xMove, lp.yMove);
lp.closePath();
dst.append(lp);
sp = null;
} else {
closeDashedShape();
}
isClosed = true;
break;
}
p.next();
}
if (!isClosed) {
closeDashedShape();
}
}
/**
* Closes dashed shape path.
*/
void closeDashedShape() {
// Add head segment
if (sp != null) {
addCap(sp, fmx, fmy, sp.xMove, sp.yMove);
sp.closePath();
dst.append(sp);
}
if (lp.typeSize > 0) {
// Close current segment
if (!dasher.isClosed()) {
addCap(lp, scx, scy, rp.xLast, rp.yLast);
lp.combine(rp);
addCap(lp, smx, smy, lp.xMove, lp.yMove);
lp.closePath();
}
dst.append(lp);
}
}
/**
* Adds cap to the work path.
*
* @param p
* the BufferedPath object of work path.
* @param x0
* the x coordinate of the source path.
* @param y0
* the y coordinate on the source path.
* @param x2
* the x coordinate of the next point on the work path.
* @param y2
* the y coordinate of the next point on the work path.
*/
void addCap(BufferedPath p, double x0, double y0, double x2, double y2) {
double x1 = p.xLast;
double y1 = p.yLast;
double x10 = x1 - x0;
double y10 = y1 - y0;
double x20 = x2 - x0;
double y20 = y2 - y0;
switch (cap) {
case CAP_BUTT:
p.lineTo(x2, y2);
break;
case CAP_ROUND:
double mx = x10 * CUBIC_ARC;
double my = y10 * CUBIC_ARC;
double x3 = x0 + y10;
double y3 = y0 - x10;
x10 *= CUBIC_ARC;
y10 *= CUBIC_ARC;
x20 *= CUBIC_ARC;
y20 *= CUBIC_ARC;
p.cubicTo(x1 + y10, y1 - x10, x3 + mx, y3 + my, x3, y3);
p.cubicTo(x3 - mx, y3 - my, x2 - y20, y2 + x20, x2, y2);
break;
case CAP_SQUARE:
p.lineTo(x1 + y10, y1 - x10);
p.lineTo(x2 - y20, y2 + x20);
p.lineTo(x2, y2);
break;
}
}
/**
* Adds bevel and miter join to the work path.
*
* @param p
* the BufferedPath object of work path.
* @param x0
* the x coordinate of the source path.
* @param y0
* the y coordinate on the source path.
* @param x2
* the x coordinate of the next point on the work path.
* @param y2
* the y coordinate of the next point on the work path.
* @param isLeft
* the orientation of work path, true if work path lies to the
* left from source path, false otherwise.
*/
void addJoin(BufferedPath p, double x0, double y0, double x2, double y2, boolean isLeft) {
double x1 = p.xLast;
double y1 = p.yLast;
double x10 = x1 - x0;
double y10 = y1 - y0;
double x20 = x2 - x0;
double y20 = y2 - y0;
double sin0 = x10 * y20 - y10 * x20;
// Small corner
if (-cornerDelta < sin0 && sin0 < cornerDelta) {
double cos0 = x10 * x20 + y10 * y20;
if (cos0 > 0.0) {
// if zero corner do nothing
if (-zeroDelta > sin0 || sin0 > zeroDelta) {
double x3 = x0 + w2 * w2 * (y20 - y10) / sin0;
double y3 = y0 + w2 * w2 * (x10 - x20) / sin0;
p.setLast(x3, y3);
}
return;
}
// Zero corner
if (-zeroDelta < sin0 && sin0 < zeroDelta) {
p.lineTo(x2, y2);
}
return;
}
if (isLeft ^ (sin0 < 0.0)) {
// Twisted corner
p.lineTo(x0, y0);
p.lineTo(x2, y2);
} else {
switch (join) {
case JOIN_BEVEL:
p.lineTo(x2, y2);
break;
case JOIN_MITER:
double s1 = x1 * x10 + y1 * y10;
double s2 = x2 * x20 + y2 * y20;
double x3 = (s1 * y20 - s2 * y10) / sin0;
double y3 = (s2 * x10 - s1 * x20) / sin0;
double x30 = x3 - x0;
double y30 = y3 - y0;
double miterLength = Math.sqrt(x30 * x30 + y30 * y30);
if (miterLength < miterLimit * w2) {
p.lineTo(x3, y3);
}
p.lineTo(x2, y2);
break;
case JOIN_ROUND:
addRoundJoin(p, x0, y0, x2, y2, isLeft);
break;
}
}
}
/**
* Adds round join to the work path.
*
* @param p
* the BufferedPath object of work path.
* @param x0
* the x coordinate of the source path.
* @param y0
* the y coordinate on the source path.
* @param x2
* the x coordinate of the next point on the work path.
* @param y2
* the y coordinate of the next point on the work path.
* @param isLeft
* the orientation of work path, true if work path lies to the
* left from source path, false otherwise.
*/
void addRoundJoin(BufferedPath p, double x0, double y0, double x2, double y2, boolean isLeft) {
double x1 = p.xLast;
double y1 = p.yLast;
double x10 = x1 - x0;
double y10 = y1 - y0;
double x20 = x2 - x0;
double y20 = y2 - y0;
double x30 = x10 + x20;
double y30 = y10 + y20;
double l30 = Math.sqrt(x30 * x30 + y30 * y30);
if (l30 < 1E-5) {
p.lineTo(x2, y2);
return;
}
double w = w2 / l30;
x30 *= w;
y30 *= w;
double x3 = x0 + x30;
double y3 = y0 + y30;
double cos = x10 * x20 + y10 * y20;
double a = Math.acos(cos / (w2 * w2));
if (cos >= 0.0) {
double k = 4.0 / 3.0 * Math.tan(a / 4.0);
if (isLeft) {
k = -k;
}
x10 *= k;
y10 *= k;
x20 *= k;
y20 *= k;
p.cubicTo(x1 - y10, y1 + x10, x2 + y20, y2 - x20, x2, y2);
} else {
double k = 4.0 / 3.0 * Math.tan(a / 8.0);
if (isLeft) {
k = -k;
}
x10 *= k;
y10 *= k;
x20 *= k;
y20 *= k;
x30 *= k;
y30 *= k;
p.cubicTo(x1 - y10, y1 + x10, x3 + y30, y3 - x30, x3, y3);
p.cubicTo(x3 - y30, y3 + x30, x2 + y20, y2 - x20, x2, y2);
}
}
/**
* Adds solid line segment to the work path.
*
* @param x1
* the x coordinate of the start line point.
* @param y1
* the y coordinate of the start line point.
* @param x2
* the x coordinate of the end line point.
* @param y2
* the y coordinate of the end line point.
* @param zero
* if true it's allowable to add zero length line segment.
*/
void addLine(double x1, double y1, double x2, double y2, boolean zero) {
double dx = x2 - x1;
double dy = y2 - y1;
if (dx == 0.0 && dy == 0.0) {
if (!zero) {
return;
}
dx = w2;
dy = 0;
} else {
double w = w2 / Math.sqrt(dx * dx + dy * dy);
dx *= w;
dy *= w;
}
double lx1 = x1 - dy;
double ly1 = y1 + dx;
double rx1 = x1 + dy;
double ry1 = y1 - dx;
if (checkMove) {
if (isMove) {
isMove = false;
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
}
lp.lineTo(x2 - dy, y2 + dx);
rp.lineTo(x2 + dy, y2 - dx);
}
/**
* Adds solid quad segment to the work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
*/
void addQuad(double x1, double y1, double x2, double y2, double x3, double y3) {
double x21 = x2 - x1;
double y21 = y2 - y1;
double x23 = x2 - x3;
double y23 = y2 - y3;
double l21 = Math.sqrt(x21 * x21 + y21 * y21);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
if (l21 == 0.0 && l23 == 0.0) {
addLine(x1, y1, x3, y3, false);
return;
}
if (l21 == 0.0) {
addLine(x2, y2, x3, y3, false);
return;
}
if (l23 == 0.0) {
addLine(x1, y1, x2, y2, false);
return;
}
double w;
w = w2 / l21;
double mx1 = -y21 * w;
double my1 = x21 * w;
w = w2 / l23;
double mx3 = y23 * w;
double my3 = -x23 * w;
double lx1 = x1 + mx1;
double ly1 = y1 + my1;
double rx1 = x1 - mx1;
double ry1 = y1 - my1;
if (checkMove) {
if (isMove) {
isMove = false;
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
}
if (x21 * y23 - y21 * x23 == 0.0) {
// On line curve
if (x21 * x23 + y21 * y23 > 0.0) {
// Twisted curve
if (l21 == l23) {
double px = x1 + (x21 + x23) / 4.0;
double py = y1 + (y21 + y23) / 4.0;
lp.lineTo(px + mx1, py + my1);
rp.lineTo(px - mx1, py - my1);
lp.lineTo(px - mx1, py - my1);
rp.lineTo(px + mx1, py + my1);
lp.lineTo(x3 - mx1, y3 - my1);
rp.lineTo(x3 + mx1, y3 + my1);
} else {
double px1, py1;
double k = l21 / (l21 + l23);
double px = x1 + (x21 + x23) * k * k;
double py = y1 + (y21 + y23) * k * k;
px1 = (x1 + px) / 2.0;
py1 = (y1 + py) / 2.0;
lp.quadTo(px1 + mx1, py1 + my1, px + mx1, py + my1);
rp.quadTo(px1 - mx1, py1 - my1, px - mx1, py - my1);
lp.lineTo(px - mx1, py - my1);
rp.lineTo(px + mx1, py + my1);
px1 = (x3 + px) / 2.0;
py1 = (y3 + py) / 2.0;
lp.quadTo(px1 - mx1, py1 - my1, x3 - mx1, y3 - my1);
rp.quadTo(px1 + mx1, py1 + my1, x3 + mx1, y3 + my1);
}
} else {
// Simple curve
lp.quadTo(x2 + mx1, y2 + my1, x3 + mx3, y3 + my3);
rp.quadTo(x2 - mx1, y2 - my1, x3 - mx3, y3 - my3);
}
} else {
addSubQuad(x1, y1, x2, y2, x3, y3, 0);
}
}
/**
* Subdivides solid quad curve to make outline for source quad segment and
* adds it to work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
* @param level
* the maximum level of subdivision deepness.
*/
void addSubQuad(double x1, double y1, double x2, double y2, double x3, double y3, int level) {
double x21 = x2 - x1;
double y21 = y2 - y1;
double x23 = x2 - x3;
double y23 = y2 - y3;
double cos = x21 * x23 + y21 * y23;
double sin = x21 * y23 - y21 * x23;
if (level < MAX_LEVEL && (cos >= 0.0 || (Math.abs(sin / cos) > curveDelta))) {
double c1x = (x2 + x1) / 2.0;
double c1y = (y2 + y1) / 2.0;
double c2x = (x2 + x3) / 2.0;
double c2y = (y2 + y3) / 2.0;
double c3x = (c1x + c2x) / 2.0;
double c3y = (c1y + c2y) / 2.0;
addSubQuad(x1, y1, c1x, c1y, c3x, c3y, level + 1);
addSubQuad(c3x, c3y, c2x, c2y, x3, y3, level + 1);
} else {
double w;
double l21 = Math.sqrt(x21 * x21 + y21 * y21);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
w = w2 / sin;
double mx2 = (x21 * l23 + x23 * l21) * w;
double my2 = (y21 * l23 + y23 * l21) * w;
w = w2 / l23;
double mx3 = y23 * w;
double my3 = -x23 * w;
lp.quadTo(x2 + mx2, y2 + my2, x3 + mx3, y3 + my3);
rp.quadTo(x2 - mx2, y2 - my2, x3 - mx3, y3 - my3);
}
}
/**
* Adds solid cubic segment to the work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
* @param x4
* the x coordinate of the fours control point.
* @param y4
* the y coordinate of the fours control point.
*/
void addCubic(double x1, double y1, double x2, double y2, double x3, double y3, double x4,
double y4) {
double x12 = x1 - x2;
double y12 = y1 - y2;
double x23 = x2 - x3;
double y23 = y2 - y3;
double x34 = x3 - x4;
double y34 = y3 - y4;
double l12 = Math.sqrt(x12 * x12 + y12 * y12);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
double l34 = Math.sqrt(x34 * x34 + y34 * y34);
// All edges are zero
if (l12 == 0.0 && l23 == 0.0 && l34 == 0.0) {
addLine(x1, y1, x4, y4, false);
return;
}
// One zero edge
if (l12 == 0.0 && l23 == 0.0) {
addLine(x3, y3, x4, y4, false);
return;
}
if (l23 == 0.0 && l34 == 0.0) {
addLine(x1, y1, x2, y2, false);
return;
}
if (l12 == 0.0 && l34 == 0.0) {
addLine(x2, y2, x3, y3, false);
return;
}
double w, mx1, my1, mx4, my4;
boolean onLine;
if (l12 == 0.0) {
w = w2 / l23;
mx1 = y23 * w;
my1 = -x23 * w;
w = w2 / l34;
mx4 = y34 * w;
my4 = -x34 * w;
onLine = -x23 * y34 + y23 * x34 == 0.0; // sin3
} else if (l34 == 0.0) {
w = w2 / l12;
mx1 = y12 * w;
my1 = -x12 * w;
w = w2 / l23;
mx4 = y23 * w;
my4 = -x23 * w;
onLine = -x12 * y23 + y12 * x23 == 0.0; // sin2
} else {
w = w2 / l12;
mx1 = y12 * w;
my1 = -x12 * w;
w = w2 / l34;
mx4 = y34 * w;
my4 = -x34 * w;
if (l23 == 0.0) {
onLine = -x12 * y34 + y12 * x34 == 0.0;
} else {
onLine = -x12 * y34 + y12 * x34 == 0.0 && -x12 * y23 + y12 * x23 == 0.0 && // sin2
-x23 * y34 + y23 * x34 == 0.0; // sin3
}
}
double lx1 = x1 + mx1;
double ly1 = y1 + my1;
double rx1 = x1 - mx1;
double ry1 = y1 - my1;
if (checkMove) {
if (isMove) {
isMove = false;
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
}
if (onLine) {
if ((x1 == x2 && y1 < y2) || x1 < x2) {
l12 = -l12;
}
if ((x2 == x3 && y2 < y3) || x2 < x3) {
l23 = -l23;
}
if ((x3 == x4 && y3 < y4) || x3 < x4) {
l34 = -l34;
}
double d = l23 * l23 - l12 * l34;
double roots[] = new double[3];
int rc = 0;
if (d == 0.0) {
double t = (l12 - l23) / (l12 + l34 - l23 - l23);
if (0.0 < t && t < 1.0) {
roots[rc++] = t;
}
} else if (d > 0.0) {
d = Math.sqrt(d);
double z = l12 + l34 - l23 - l23;
double t;
t = (l12 - l23 + d) / z;
if (0.0 < t && t < 1.0) {
roots[rc++] = t;
}
t = (l12 - l23 - d) / z;
if (0.0 < t && t < 1.0) {
roots[rc++] = t;
}
}
if (rc > 0) {
// Sort roots
if (rc == 2 && roots[0] > roots[1]) {
double tmp = roots[0];
roots[0] = roots[1];
roots[1] = tmp;
}
roots[rc++] = 1.0;
double ax = -x34 - x12 + x23 + x23;
double ay = -y34 - y12 + y23 + y23;
double bx = 3.0 * (-x23 + x12);
double by = 3.0 * (-y23 + y12);
double cx = 3.0 * (-x12);
double cy = 3.0 * (-y12);
double xPrev = x1;
double yPrev = y1;
for (int i = 0; i < rc; i++) {
double t = roots[i];
double px = t * (t * (t * ax + bx) + cx) + x1;
double py = t * (t * (t * ay + by) + cy) + y1;
double px1 = (xPrev + px) / 2.0;
double py1 = (yPrev + py) / 2.0;
lp.cubicTo(px1 + mx1, py1 + my1, px1 + mx1, py1 + my1, px + mx1, py + my1);
rp.cubicTo(px1 - mx1, py1 - my1, px1 - mx1, py1 - my1, px - mx1, py - my1);
if (i < rc - 1) {
lp.lineTo(px - mx1, py - my1);
rp.lineTo(px + mx1, py + my1);
}
xPrev = px;
yPrev = py;
mx1 = -mx1;
my1 = -my1;
}
} else {
lp.cubicTo(x2 + mx1, y2 + my1, x3 + mx4, y3 + my4, x4 + mx4, y4 + my4);
rp.cubicTo(x2 - mx1, y2 - my1, x3 - mx4, y3 - my4, x4 - mx4, y4 - my4);
}
} else {
addSubCubic(x1, y1, x2, y2, x3, y3, x4, y4, 0);
}
}
/**
* Subdivides solid cubic curve to make outline for source quad segment and
* adds it to work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
* @param x4
* the x coordinate of the fours control point.
* @param y4
* the y coordinate of the fours control point.
* @param level
* the maximum level of subdivision deepness.
*/
void addSubCubic(double x1, double y1, double x2, double y2, double x3, double y3, double x4,
double y4, int level) {
double x12 = x1 - x2;
double y12 = y1 - y2;
double x23 = x2 - x3;
double y23 = y2 - y3;
double x34 = x3 - x4;
double y34 = y3 - y4;
double cos2 = -x12 * x23 - y12 * y23;
double cos3 = -x23 * x34 - y23 * y34;
double sin2 = -x12 * y23 + y12 * x23;
double sin3 = -x23 * y34 + y23 * x34;
double sin0 = -x12 * y34 + y12 * x34;
double cos0 = -x12 * x34 - y12 * y34;
if (level < MAX_LEVEL
&& (sin2 != 0.0 || sin3 != 0.0 || sin0 != 0.0)
&& (cos2 >= 0.0 || cos3 >= 0.0 || cos0 >= 0.0
|| (Math.abs(sin2 / cos2) > curveDelta)
|| (Math.abs(sin3 / cos3) > curveDelta) || (Math.abs(sin0 / cos0) > curveDelta))) {
double cx = (x2 + x3) / 2.0;
double cy = (y2 + y3) / 2.0;
double lx2 = (x2 + x1) / 2.0;
double ly2 = (y2 + y1) / 2.0;
double rx3 = (x3 + x4) / 2.0;
double ry3 = (y3 + y4) / 2.0;
double lx3 = (cx + lx2) / 2.0;
double ly3 = (cy + ly2) / 2.0;
double rx2 = (cx + rx3) / 2.0;
double ry2 = (cy + ry3) / 2.0;
cx = (lx3 + rx2) / 2.0;
cy = (ly3 + ry2) / 2.0;
addSubCubic(x1, y1, lx2, ly2, lx3, ly3, cx, cy, level + 1);
addSubCubic(cx, cy, rx2, ry2, rx3, ry3, x4, y4, level + 1);
} else {
double w, mx1, my1, mx2, my2, mx3, my3, mx4, my4;
double l12 = Math.sqrt(x12 * x12 + y12 * y12);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
double l34 = Math.sqrt(x34 * x34 + y34 * y34);
if (l12 == 0.0) {
w = w2 / l23;
mx1 = y23 * w;
my1 = -x23 * w;
w = w2 / l34;
mx4 = y34 * w;
my4 = -x34 * w;
} else if (l34 == 0.0) {
w = w2 / l12;
mx1 = y12 * w;
my1 = -x12 * w;
w = w2 / l23;
mx4 = y23 * w;
my4 = -x23 * w;
} else {
// Common case
w = w2 / l12;
mx1 = y12 * w;
my1 = -x12 * w;
w = w2 / l34;
mx4 = y34 * w;
my4 = -x34 * w;
}
if (sin2 == 0.0) {
mx2 = mx1;
my2 = my1;
} else {
w = w2 / sin2;
mx2 = -(x12 * l23 - x23 * l12) * w;
my2 = -(y12 * l23 - y23 * l12) * w;
}
if (sin3 == 0.0) {
mx3 = mx4;
my3 = my4;
} else {
w = w2 / sin3;
mx3 = -(x23 * l34 - x34 * l23) * w;
my3 = -(y23 * l34 - y34 * l23) * w;
}
lp.cubicTo(x2 + mx2, y2 + my2, x3 + mx3, y3 + my3, x4 + mx4, y4 + my4);
rp.cubicTo(x2 - mx2, y2 - my2, x3 - mx3, y3 - my3, x4 - mx4, y4 - my4);
}
}
/**
* Adds dashed line segment to the work path.
*
* @param x1
* the x coordinate of the start line point.
* @param y1
* the y coordinate of the start line point.
* @param x2
* the x coordinate of the end line point.
* @param y2
* the y coordinate of the end line point.
*/
void addDashLine(double x1, double y1, double x2, double y2) {
double x21 = x2 - x1;
double y21 = y2 - y1;
double l21 = Math.sqrt(x21 * x21 + y21 * y21);
if (l21 == 0.0) {
return;
}
double px1, py1;
px1 = py1 = 0.0;
double w = w2 / l21;
double mx = -y21 * w;
double my = x21 * w;
dasher.init(new DashIterator.Line(l21));
while (!dasher.eof()) {
double t = dasher.getValue();
scx = x1 + t * x21;
scy = y1 + t * y21;
if (dasher.isOpen()) {
px1 = scx;
py1 = scy;
double lx1 = px1 + mx;
double ly1 = py1 + my;
double rx1 = px1 - mx;
double ry1 = py1 - my;
if (isMove) {
isMove = false;
smx = px1;
smy = py1;
rp.clean();
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
} else if (dasher.isContinue()) {
double px2 = scx;
double py2 = scy;
lp.lineTo(px2 + mx, py2 + my);
rp.lineTo(px2 - mx, py2 - my);
if (dasher.close) {
addCap(lp, px2, py2, rp.xLast, rp.yLast);
lp.combine(rp);
if (isFirst) {
isFirst = false;
fmx = smx;
fmy = smy;
sp = lp;
lp = new BufferedPath();
} else {
addCap(lp, smx, smy, lp.xMove, lp.yMove);
lp.closePath();
}
isMove = true;
}
}
dasher.next();
}
}
/**
* Adds dashed quad segment to the work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
*/
void addDashQuad(double x1, double y1, double x2, double y2, double x3, double y3) {
double x21 = x2 - x1;
double y21 = y2 - y1;
double x23 = x2 - x3;
double y23 = y2 - y3;
double l21 = Math.sqrt(x21 * x21 + y21 * y21);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
if (l21 == 0.0 && l23 == 0.0) {
return;
}
if (l21 == 0.0) {
addDashLine(x2, y2, x3, y3);
return;
}
if (l23 == 0.0) {
addDashLine(x1, y1, x2, y2);
return;
}
double ax = x1 + x3 - x2 - x2;
double ay = y1 + y3 - y2 - y2;
double bx = x2 - x1;
double by = y2 - y1;
double cx = x1;
double cy = y1;
double px1, py1, dx1, dy1;
px1 = py1 = dx1 = dy1 = 0.0;
double prev = 0.0;
dasher.init(new DashIterator.Quad(x1, y1, x2, y2, x3, y3));
while (!dasher.eof()) {
double t = dasher.getValue();
double dx = t * ax + bx;
double dy = t * ay + by;
scx = t * (dx + bx) + cx; // t^2 * ax + 2.0 * t * bx + cx
scy = t * (dy + by) + cy; // t^2 * ay + 2.0 * t * by + cy
if (dasher.isOpen()) {
px1 = scx;
py1 = scy;
dx1 = dx;
dy1 = dy;
double w = w2 / Math.sqrt(dx1 * dx1 + dy1 * dy1);
double mx1 = -dy1 * w;
double my1 = dx1 * w;
double lx1 = px1 + mx1;
double ly1 = py1 + my1;
double rx1 = px1 - mx1;
double ry1 = py1 - my1;
if (isMove) {
isMove = false;
smx = px1;
smy = py1;
rp.clean();
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
} else if (dasher.isContinue()) {
double px3 = scx;
double py3 = scy;
double sx = x2 - x23 * prev;
double sy = y2 - y23 * prev;
double t2 = (t - prev) / (1 - prev);
double px2 = px1 + (sx - px1) * t2;
double py2 = py1 + (sy - py1) * t2;
addQuad(px1, py1, px2, py2, px3, py3);
if (dasher.isClosed()) {
addCap(lp, px3, py3, rp.xLast, rp.yLast);
lp.combine(rp);
if (isFirst) {
isFirst = false;
fmx = smx;
fmy = smy;
sp = lp;
lp = new BufferedPath();
} else {
addCap(lp, smx, smy, lp.xMove, lp.yMove);
lp.closePath();
}
isMove = true;
}
}
prev = t;
dasher.next();
}
}
/**
* Adds dashed cubic segment to the work path.
*
* @param x1
* the x coordinate of the first control point.
* @param y1
* the y coordinate of the first control point.
* @param x2
* the x coordinate of the second control point.
* @param y2
* the y coordinate of the second control point.
* @param x3
* the x coordinate of the third control point.
* @param y3
* the y coordinate of the third control point.
* @param x4
* the x coordinate of the fours control point.
* @param y4
* the y coordinate of the fours control point.
*/
void addDashCubic(double x1, double y1, double x2, double y2, double x3, double y3, double x4,
double y4) {
double x12 = x1 - x2;
double y12 = y1 - y2;
double x23 = x2 - x3;
double y23 = y2 - y3;
double x34 = x3 - x4;
double y34 = y3 - y4;
double l12 = Math.sqrt(x12 * x12 + y12 * y12);
double l23 = Math.sqrt(x23 * x23 + y23 * y23);
double l34 = Math.sqrt(x34 * x34 + y34 * y34);
// All edges are zero
if (l12 == 0.0 && l23 == 0.0 && l34 == 0.0) {
// NOTHING
return;
}
// One zero edge
if (l12 == 0.0 && l23 == 0.0) {
addDashLine(x3, y3, x4, y4);
return;
}
if (l23 == 0.0 && l34 == 0.0) {
addDashLine(x1, y1, x2, y2);
return;
}
if (l12 == 0.0 && l34 == 0.0) {
addDashLine(x2, y2, x3, y3);
return;
}
double ax = x4 - x1 + 3.0 * (x2 - x3);
double ay = y4 - y1 + 3.0 * (y2 - y3);
double bx = 3.0 * (x1 + x3 - x2 - x2);
double by = 3.0 * (y1 + y3 - y2 - y2);
double cx = 3.0 * (x2 - x1);
double cy = 3.0 * (y2 - y1);
double dx = x1;
double dy = y1;
double px1 = 0.0;
double py1 = 0.0;
double prev = 0.0;
dasher.init(new DashIterator.Cubic(x1, y1, x2, y2, x3, y3, x4, y4));
while (!dasher.eof()) {
double t = dasher.getValue();
scx = t * (t * (t * ax + bx) + cx) + dx;
scy = t * (t * (t * ay + by) + cy) + dy;
if (dasher.isOpen()) {
px1 = scx;
py1 = scy;
double dx1 = t * (t * (ax + ax + ax) + bx + bx) + cx;
double dy1 = t * (t * (ay + ay + ay) + by + by) + cy;
double w = w2 / Math.sqrt(dx1 * dx1 + dy1 * dy1);
double mx1 = -dy1 * w;
double my1 = dx1 * w;
double lx1 = px1 + mx1;
double ly1 = py1 + my1;
double rx1 = px1 - mx1;
double ry1 = py1 - my1;
if (isMove) {
isMove = false;
smx = px1;
smy = py1;
rp.clean();
lp.moveTo(lx1, ly1);
rp.moveTo(rx1, ry1);
} else {
addJoin(lp, x1, y1, lx1, ly1, true);
addJoin(rp, x1, y1, rx1, ry1, false);
}
} else if (dasher.isContinue()) {
double sx1 = x2 - x23 * prev;
double sy1 = y2 - y23 * prev;
double sx2 = x3 - x34 * prev;
double sy2 = y3 - y34 * prev;
double sx3 = sx1 + (sx2 - sx1) * prev;
double sy3 = sy1 + (sy2 - sy1) * prev;
double t2 = (t - prev) / (1 - prev);
double sx4 = sx3 + (sx2 - sx3) * t2;
double sy4 = sy3 + (sy2 - sy3) * t2;
double px4 = scx;
double py4 = scy;
double px2 = px1 + (sx3 - px1) * t2;
double py2 = py1 + (sy3 - py1) * t2;
double px3 = px2 + (sx4 - px2) * t2;
double py3 = py2 + (sy4 - py2) * t2;
addCubic(px1, py1, px2, py2, px3, py3, px4, py4);
if (dasher.isClosed()) {
addCap(lp, px4, py4, rp.xLast, rp.yLast);
lp.combine(rp);
if (isFirst) {
isFirst = false;
fmx = smx;
fmy = smy;
sp = lp;
lp = new BufferedPath();
} else {
addCap(lp, smx, smy, lp.xMove, lp.yMove);
lp.closePath();
}
isMove = true;
}
}
prev = t;
dasher.next();
}
}
/**
* Dasher class provides dashing for particular dash style.
*/
class Dasher {
/**
* The pos.
*/
double pos;
/**
* The first.
*/
boolean close, visible, first;
/**
* The dash.
*/
float dash[];
/**
* The phase.
*/
float phase;
/**
* The index.
*/
int index;
/**
* The iter.
*/
DashIterator iter;
/**
* Instantiates a new dasher.
*
* @param dash
* the dash.
* @param phase
* the phase.
*/
Dasher(float dash[], float phase) {
this.dash = dash;
this.phase = phase;
index = 0;
pos = phase;
visible = true;
while (pos >= dash[index]) {
visible = !visible;
pos -= dash[index];
index = (index + 1) % dash.length;
}
pos = -pos;
first = visible;
}
/**
* Inits the.
*
* @param iter
* the iter.
*/
void init(DashIterator iter) {
this.iter = iter;
close = true;
}
/**
* Checks if is open.
*
* @return true, if is open.
*/
boolean isOpen() {
return visible && pos < iter.length;
}
/**
* Checks if is continue.
*
* @return true, if is continue.
*/
boolean isContinue() {
return !visible && pos > 0;
}
/**
* Checks if is closed.
*
* @return true, if is closed.
*/
boolean isClosed() {
return close;
}
/**
* Checks if is connected.
*
* @return true, if is connected.
*/
boolean isConnected() {
return first && !close;
}
/**
* Eof.
*
* @return true, if successful.
*/
boolean eof() {
if (!close) {
pos -= iter.length;
return true;
}
if (pos >= iter.length) {
if (visible) {
pos -= iter.length;
return true;
}
close = pos == iter.length;
}
return false;
}
/**
* Next.
*/
void next() {
if (close) {
pos += dash[index];
index = (index + 1) % dash.length;
} else {
// Go back
index = (index + dash.length - 1) % dash.length;
pos -= dash[index];
}
visible = !visible;
}
/**
* Gets the value.
*
* @return the value.
*/
double getValue() {
double t = iter.getNext(pos);
return t < 0 ? 0 : (t > 1 ? 1 : t);
}
}
/**
* DashIterator class provides dashing for particular segment type.
*/
static abstract class DashIterator {
/**
* The Constant FLATNESS.
*/
static final double FLATNESS = 1.0;
/**
* The Class Line.
*/
static class Line extends DashIterator {
/**
* Instantiates a new line.
*
* @param len
* the len.
*/
Line(double len) {
length = len;
}
@Override
double getNext(double dashPos) {
return dashPos / length;
}
}
/**
* The Class Quad.
*/
static class Quad extends DashIterator {
/**
* The val size.
*/
int valSize;
/**
* The val pos.
*/
int valPos;
/**
* The cur len.
*/
double curLen;
/**
* The prev len.
*/
double prevLen;
/**
* The last len.
*/
double lastLen;
/**
* The values.
*/
double[] values;
/**
* The step.
*/
double step;
/**
* Instantiates a new quad.
*
* @param x1
* the x1.
* @param y1
* the y1.
* @param x2
* the x2.
* @param y2
* the y2.
* @param x3
* the x3.
* @param y3
* the y3.
*/
Quad(double x1, double y1, double x2, double y2, double x3, double y3) {
double nx = x1 + x3 - x2 - x2;
double ny = y1 + y3 - y2 - y2;
int n = (int)(1 + Math.sqrt(0.75 * (Math.abs(nx) + Math.abs(ny)) * FLATNESS));
step = 1.0 / n;
double ax = x1 + x3 - x2 - x2;
double ay = y1 + y3 - y2 - y2;
double bx = 2.0 * (x2 - x1);
double by = 2.0 * (y2 - y1);
double dx1 = step * (step * ax + bx);
double dy1 = step * (step * ay + by);
double dx2 = step * (step * ax * 2.0);
double dy2 = step * (step * ay * 2.0);
double vx = x1;
double vy = y1;
valSize = n;
values = new double[valSize];
double pvx = vx;
double pvy = vy;
length = 0.0;
for (int i = 0; i < n; i++) {
vx += dx1;
vy += dy1;
dx1 += dx2;
dy1 += dy2;
double lx = vx - pvx;
double ly = vy - pvy;
values[i] = Math.sqrt(lx * lx + ly * ly);
length += values[i];
pvx = vx;
pvy = vy;
}
valPos = 0;
curLen = 0.0;
prevLen = 0.0;
}
@Override
double getNext(double dashPos) {
double t = 2.0;
while (curLen <= dashPos && valPos < valSize) {
prevLen = curLen;
curLen += lastLen = values[valPos++];
}
if (curLen > dashPos) {
t = (valPos - 1 + (dashPos - prevLen) / lastLen) * step;
}
return t;
}
}
/**
* The Class Cubic.
*/
static class Cubic extends DashIterator {
/**
* The val size.
*/
int valSize;
/**
* The val pos.
*/
int valPos;
/**
* The cur len.
*/
double curLen;
/**
* The prev len.
*/
double prevLen;
/**
* The last len.
*/
double lastLen;
/**
* The values.
*/
double[] values;
/**
* The step.
*/
double step;
/**
* Instantiates a new cubic.
*
* @param x1
* the x1.
* @param y1
* the y1.
* @param x2
* the x2.
* @param y2
* the y2.
* @param x3
* the x3.
* @param y3
* the y3.
* @param x4
* the x4.
* @param y4
* the y4.
*/
Cubic(double x1, double y1, double x2, double y2, double x3, double y3, double x4,
double y4) {
double nx1 = x1 + x3 - x2 - x2;
double ny1 = y1 + y3 - y2 - y2;
double nx2 = x2 + x4 - x3 - x3;
double ny2 = y2 + y4 - y3 - y3;
double max = Math.max(Math.abs(nx1) + Math.abs(ny1), Math.abs(nx2) + Math.abs(ny2));
int n = (int)(1 + Math.sqrt(0.75 * max) * FLATNESS);
step = 1.0 / n;
double ax = x4 - x1 + 3.0 * (x2 - x3);
double ay = y4 - y1 + 3.0 * (y2 - y3);
double bx = 3.0 * (x1 + x3 - x2 - x2);
double by = 3.0 * (y1 + y3 - y2 - y2);
double cx = 3.0 * (x2 - x1);
double cy = 3.0 * (y2 - y1);
double dx1 = step * (step * (step * ax + bx) + cx);
double dy1 = step * (step * (step * ay + by) + cy);
double dx2 = step * (step * (step * ax * 6.0 + bx * 2.0));
double dy2 = step * (step * (step * ay * 6.0 + by * 2.0));
double dx3 = step * (step * (step * ax * 6.0));
double dy3 = step * (step * (step * ay * 6.0));
double vx = x1;
double vy = y1;
valSize = n;
values = new double[valSize];
double pvx = vx;
double pvy = vy;
length = 0.0;
for (int i = 0; i < n; i++) {
vx += dx1;
vy += dy1;
dx1 += dx2;
dy1 += dy2;
dx2 += dx3;
dy2 += dy3;
double lx = vx - pvx;
double ly = vy - pvy;
values[i] = Math.sqrt(lx * lx + ly * ly);
length += values[i];
pvx = vx;
pvy = vy;
}
valPos = 0;
curLen = 0.0;
prevLen = 0.0;
}
@Override
double getNext(double dashPos) {
double t = 2.0;
while (curLen <= dashPos && valPos < valSize) {
prevLen = curLen;
curLen += lastLen = values[valPos++];
}
if (curLen > dashPos) {
t = (valPos - 1 + (dashPos - prevLen) / lastLen) * step;
}
return t;
}
}
/**
* The length.
*/
double length;
/**
* Gets the next.
*
* @param dashPos
* the dash pos.
* @return the next.
*/
abstract double getNext(double dashPos);
}
/**
* BufferedPath class provides work path storing and processing.
*/
static class BufferedPath {
/**
* The Constant bufCapacity.
*/
private static final int bufCapacity = 10;
/**
* The point shift.
*/
static int pointShift[] = {
2, // MOVETO
2, // LINETO
4, // QUADTO
6, // CUBICTO
0
}; // CLOSE
/**
* The types.
*/
byte[] types;
/**
* The points.
*/
float[] points;
/**
* The type size.
*/
int typeSize;
/**
* The point size.
*/
int pointSize;
/**
* The x last.
*/
float xLast;
/**
* The y last.
*/
float yLast;
/**
* The x move.
*/
float xMove;
/**
* The y move.
*/
float yMove;
/**
* Instantiates a new buffered path.
*/
public BufferedPath() {
types = new byte[bufCapacity];
points = new float[bufCapacity * 2];
}
/**
* Check buf.
*
* @param typeCount
* the type count.
* @param pointCount
* the point count.
*/
void checkBuf(int typeCount, int pointCount) {
if (typeSize + typeCount > types.length) {
byte tmp[] = new byte[typeSize + Math.max(bufCapacity, typeCount)];
System.arraycopy(types, 0, tmp, 0, typeSize);
types = tmp;
}
if (pointSize + pointCount > points.length) {
float tmp[] = new float[pointSize + Math.max(bufCapacity * 2, pointCount)];
System.arraycopy(points, 0, tmp, 0, pointSize);
points = tmp;
}
}
/**
* Checks if is empty.
*
* @return true, if is empty.
*/
boolean isEmpty() {
return typeSize == 0;
}
/**
* Clean.
*/
void clean() {
typeSize = 0;
pointSize = 0;
}
/**
* Move to.
*
* @param x
* the x.
* @param y
* the y.
*/
void moveTo(double x, double y) {
checkBuf(1, 2);
types[typeSize++] = PathIterator.SEG_MOVETO;
points[pointSize++] = xMove = (float)x;
points[pointSize++] = yMove = (float)y;
}
/**
* Line to.
*
* @param x
* the x.
* @param y
* the y.
*/
void lineTo(double x, double y) {
checkBuf(1, 2);
types[typeSize++] = PathIterator.SEG_LINETO;
points[pointSize++] = xLast = (float)x;
points[pointSize++] = yLast = (float)y;
}
/**
* Quad to.
*
* @param x1
* the x1.
* @param y1
* the y1.
* @param x2
* the x2.
* @param y2
* the y2.
*/
void quadTo(double x1, double y1, double x2, double y2) {
checkBuf(1, 4);
types[typeSize++] = PathIterator.SEG_QUADTO;
points[pointSize++] = (float)x1;
points[pointSize++] = (float)y1;
points[pointSize++] = xLast = (float)x2;
points[pointSize++] = yLast = (float)y2;
}
/**
* Cubic to.
*
* @param x1
* the x1.
* @param y1
* the y1.
* @param x2
* the x2.
* @param y2
* the y2.
* @param x3
* the x3.
* @param y3
* the y3.
*/
void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) {
checkBuf(1, 6);
types[typeSize++] = PathIterator.SEG_CUBICTO;
points[pointSize++] = (float)x1;
points[pointSize++] = (float)y1;
points[pointSize++] = (float)x2;
points[pointSize++] = (float)y2;
points[pointSize++] = xLast = (float)x3;
points[pointSize++] = yLast = (float)y3;
}
/**
* Close path.
*/
void closePath() {
checkBuf(1, 0);
types[typeSize++] = PathIterator.SEG_CLOSE;
}
/**
* Sets the last.
*
* @param x
* the x.
* @param y
* the y.
*/
void setLast(double x, double y) {
points[pointSize - 2] = xLast = (float)x;
points[pointSize - 1] = yLast = (float)y;
}
/**
* Append.
*
* @param p
* the p.
*/
void append(BufferedPath p) {
checkBuf(p.typeSize, p.pointSize);
System.arraycopy(p.points, 0, points, pointSize, p.pointSize);
System.arraycopy(p.types, 0, types, typeSize, p.typeSize);
pointSize += p.pointSize;
typeSize += p.typeSize;
xLast = points[pointSize - 2];
yLast = points[pointSize - 1];
}
/**
* Append reverse.
*
* @param p
* the p.
*/
void appendReverse(BufferedPath p) {
checkBuf(p.typeSize, p.pointSize);
// Skip last point, beacause it's the first point of the second path
for (int i = p.pointSize - 2; i >= 0; i -= 2) {
points[pointSize++] = p.points[i + 0];
points[pointSize++] = p.points[i + 1];
}
// Skip first type, beacuse it's always MOVETO
int closeIndex = 0;
for (int i = p.typeSize - 1; i >= 0; i--) {
byte type = p.types[i];
if (type == PathIterator.SEG_MOVETO) {
types[closeIndex] = PathIterator.SEG_MOVETO;
types[typeSize++] = PathIterator.SEG_CLOSE;
} else {
if (type == PathIterator.SEG_CLOSE) {
closeIndex = typeSize;
}
types[typeSize++] = type;
}
}
xLast = points[pointSize - 2];
yLast = points[pointSize - 1];
}
/**
* Join.
*
* @param p
* the p.
*/
void join(BufferedPath p) {
// Skip MOVETO
checkBuf(p.typeSize - 1, p.pointSize - 2);
System.arraycopy(p.points, 2, points, pointSize, p.pointSize - 2);
System.arraycopy(p.types, 1, types, typeSize, p.typeSize - 1);
pointSize += p.pointSize - 2;
typeSize += p.typeSize - 1;
xLast = points[pointSize - 2];
yLast = points[pointSize - 1];
}
/**
* Combine.
*
* @param p
* the p.
*/
void combine(BufferedPath p) {
checkBuf(p.typeSize - 1, p.pointSize - 2);
// Skip last point, beacause it's the first point of the second path
for (int i = p.pointSize - 4; i >= 0; i -= 2) {
points[pointSize++] = p.points[i + 0];
points[pointSize++] = p.points[i + 1];
}
// Skip first type, beacuse it's always MOVETO
for (int i = p.typeSize - 1; i >= 1; i--) {
types[typeSize++] = p.types[i];
}
xLast = points[pointSize - 2];
yLast = points[pointSize - 1];
}
/**
* Creates the general path.
*
* @return the general path.
*/
GeneralPath createGeneralPath() {
GeneralPath p = new GeneralPath();
int j = 0;
for (int i = 0; i < typeSize; i++) {
int type = types[i];
switch (type) {
case PathIterator.SEG_MOVETO:
p.moveTo(points[j], points[j + 1]);
break;
case PathIterator.SEG_LINETO:
p.lineTo(points[j], points[j + 1]);
break;
case PathIterator.SEG_QUADTO:
p.quadTo(points[j], points[j + 1], points[j + 2], points[j + 3]);
break;
case PathIterator.SEG_CUBICTO:
p.curveTo(points[j], points[j + 1], points[j + 2], points[j + 3],
points[j + 4], points[j + 5]);
break;
case PathIterator.SEG_CLOSE:
p.closePath();
break;
}
j += pointShift[type];
}
return p;
}
}
}