Package com.jgraph.gaeawt.java.awt

Source Code of com.jgraph.gaeawt.java.awt.BasicStroke$Dasher

/*
*  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
*/
package com.jgraph.gaeawt.java.awt;

import org.apache.harmony.awt.internal.nls.Messages;
import org.apache.harmony.misc.HashCode;

import com.jgraph.gaeawt.java.awt.geom.GeneralPath;
import com.jgraph.gaeawt.java.awt.geom.PathIterator;

public class BasicStroke implements Stroke {

    public static final int CAP_BUTT = 0;
    public static final int CAP_ROUND = 1;
    public static final int CAP_SQUARE = 2;

    public static final int JOIN_MITER = 0;
    public static final int JOIN_ROUND = 1;
    public static final int JOIN_BEVEL = 2;
   
    /**
     * Constants for calculating
     */
    static final int MAX_LEVEL = 20;        // Maximal deepness of curve subdivision
    static final double CURVE_DELTA = 2.0// Width tolerance
    static final double CORNER_ANGLE = 4.0; // Minimum corner angel
    static final double CORNER_ZERO = 0.01; // Zero angle
    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;
    double cornerDelta;
    double zeroDelta;

    double w2;
    double fmx, fmy;
    double scx, scy, smx, smy;
    double mx, my, cx, cy;

    /**
     * The temporary indicators
     */
    boolean isMove;
    boolean isFirst;
    boolean checkMove;
   
    /**
     * The temporary and destination work paths
     */
    BufferedPath dst, lp, rp, sp;
   
    /**
     * Stroke dasher class
     */
    Dasher dasher;

    public BasicStroke() {
        this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
    }

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

    public BasicStroke(float width, int cap, int join, float miterLimit) {
        this(width, cap, join, miterLimit, null, 0.0f);
    }

    public BasicStroke(float width, int cap, int join) {
        this(width, cap, join, 10.0f, null, 0.0f);
    }

    public BasicStroke(float width) {
        this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
    }

    public float getLineWidth() {
        return width;
    }

    public int getEndCap() {
        return cap;
    }

    public int getLineJoin() {
        return join;
    }

    public float getMiterLimit() {
        return miterLimit;
    }

    public float[] getDashArray() {
        return dash;
    }

    public float getDashPhase() {
        return dashPhase;
    }

    @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();
    }

    @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
     */
    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 value to detect small angle
     */
    double getCornerDelta(double width) {
        return width * width * Math.sin(Math.PI * CORNER_ANGLE / 180.0);
    }

    /**
     * Calculates value to detect zero angle
     */
    double getZeroDelta(double width) {
        return width * width * Math.sin(Math.PI * CORNER_ZERO / 180.0);
    }

    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 solid stroked shape without dash
     * @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 {
       
        double pos;
        boolean close, visible, first;
        float dash[];
        float phase;
        int index;
        DashIterator iter;
       
        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;
        }
       
        void init(DashIterator iter) {
            this.iter = iter;
            close = true;
        }
       
        boolean isOpen() {
            return visible && pos < iter.length;
        }
       
        boolean isContinue() {
            return !visible && pos > 0;
        }
       
        boolean isClosed() {
            return close;
        }
       
        boolean isConnected() {
            return first && !close;
        }

        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;
        }
       
        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;
        }
       
        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 {

        static final double FLATNESS = 1.0;

        static class Line extends DashIterator {

            Line(double len) {
                length = len;
            }

            @Override
            double getNext(double dashPos) {
                return dashPos / length;
            }

        }

        static class Quad extends DashIterator {

            int valSize;
            int valPos;
            double curLen;
            double prevLen;
            double lastLen;
            double[] values;
            double step;

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

        }

        static class Cubic extends DashIterator {

            int valSize;
            int valPos;
            double curLen;
            double prevLen;
            double lastLen;
            double[] values;
            double step;

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

        }

        double length;

        abstract double getNext(double dashPos);

    }

    /**
     * BufferedPath class provides work path storing and processing
     */
    static class BufferedPath {

        private static final int bufCapacity = 10;

        static int pointShift[] = {
                2// MOVETO
                2// LINETO
                4// QUADTO
                6// CUBICTO
                0}; // CLOSE

        byte[] types;
        float[] points;
        int typeSize;
        int pointSize;

        float xLast;
        float yLast;
        float xMove;
        float yMove;

        public BufferedPath() {
            types = new byte[bufCapacity];
            points = new float[bufCapacity * 2];
        }

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

        boolean isEmpty() {
            return typeSize == 0;
        }

        void clean() {
            typeSize = 0;
            pointSize = 0;
        }

        void moveTo(double x, double y) {
            checkBuf(1, 2);
            types[typeSize++] = PathIterator.SEG_MOVETO;
            points[pointSize++] = xMove = (float)x;
            points[pointSize++] = yMove = (float)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;
        }

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

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

        void closePath() {
            checkBuf(1, 0);
            types[typeSize++] = PathIterator.SEG_CLOSE;
        }

        void setLast(double x, double y) {
            points[pointSize - 2] = xLast = (float)x;
            points[pointSize - 1] = yLast = (float)y;
        }

        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];
        }

        void appendReverse(BufferedPath p) {
          if (p.pointSize < 2)
          {
            return;
          }
         
            checkBuf(p.typeSize, p.pointSize);
            // Skip last point, because 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, because 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;
                }
            }
           
            if (xLast < 2 || yLast < 1)
            {
              Exception e = new Exception();
              e.printStackTrace();
            }
            xLast = points[pointSize - 2];
            yLast = points[pointSize - 1];
        }

        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];
        }

        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];
        }

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

    }

}

TOP

Related Classes of com.jgraph.gaeawt.java.awt.BasicStroke$Dasher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.