/* * 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; } } }