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