0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Denis M. Kishenko
0019: * @version $Revision$
0020: */package java.awt;
0021:
0022: import java.awt.geom.GeneralPath;
0023: import java.awt.geom.PathIterator;
0024:
0025: import org.apache.harmony.awt.internal.nls.Messages;
0026: import org.apache.harmony.misc.HashCode;
0027:
0028: public class BasicStroke implements Stroke {
0029:
0030: public static final int CAP_BUTT = 0;
0031: public static final int CAP_ROUND = 1;
0032: public static final int CAP_SQUARE = 2;
0033:
0034: public static final int JOIN_MITER = 0;
0035: public static final int JOIN_ROUND = 1;
0036: public static final int JOIN_BEVEL = 2;
0037:
0038: /**
0039: * Constants for calculating
0040: */
0041: static final int MAX_LEVEL = 20; // Maximal deepness of curve subdivision
0042: static final double CURVE_DELTA = 2.0; // Width tolerance
0043: static final double CORNER_ANGLE = 4.0; // Minimum corner angel
0044: static final double CORNER_ZERO = 0.01; // Zero angle
0045: static final double CUBIC_ARC = 4.0 / 3.0 * (Math.sqrt(2.0) - 1);
0046:
0047: /**
0048: * Stroke width
0049: */
0050: float width;
0051:
0052: /**
0053: * Stroke cap type
0054: */
0055: int cap;
0056:
0057: /**
0058: * Stroke join type
0059: */
0060: int join;
0061:
0062: /**
0063: * Stroke miter limit
0064: */
0065: float miterLimit;
0066:
0067: /**
0068: * Stroke dashes array
0069: */
0070: float dash[];
0071:
0072: /**
0073: * Stroke dash phase
0074: */
0075: float dashPhase;
0076:
0077: /**
0078: * The temporary pre-calculated values
0079: */
0080: double curveDelta;
0081: double cornerDelta;
0082: double zeroDelta;
0083:
0084: double w2;
0085: double fmx, fmy;
0086: double scx, scy, smx, smy;
0087: double mx, my, cx, cy;
0088:
0089: /**
0090: * The temporary indicators
0091: */
0092: boolean isMove;
0093: boolean isFirst;
0094: boolean checkMove;
0095:
0096: /**
0097: * The temporary and destination work paths
0098: */
0099: BufferedPath dst, lp, rp, sp;
0100:
0101: /**
0102: * Stroke dasher class
0103: */
0104: Dasher dasher;
0105:
0106: public BasicStroke() {
0107: this (1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
0108: }
0109:
0110: public BasicStroke(float width, int cap, int join,
0111: float miterLimit, float[] dash, float dashPhase) {
0112: if (width < 0.0f) {
0113: // awt.133=Negative width
0114: throw new IllegalArgumentException(Messages
0115: .getString("awt.133")); //$NON-NLS-1$
0116: }
0117: if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
0118: // awt.134=Illegal cap
0119: throw new IllegalArgumentException(Messages
0120: .getString("awt.134")); //$NON-NLS-1$
0121: }
0122: if (join != JOIN_MITER && join != JOIN_ROUND
0123: && join != JOIN_BEVEL) {
0124: // awt.135=Illegal join
0125: throw new IllegalArgumentException(Messages
0126: .getString("awt.135")); //$NON-NLS-1$
0127: }
0128: if (join == JOIN_MITER && miterLimit < 1.0f) {
0129: // awt.136=miterLimit less than 1.0f
0130: throw new IllegalArgumentException(Messages
0131: .getString("awt.136")); //$NON-NLS-1$
0132: }
0133: if (dash != null) {
0134: if (dashPhase < 0.0f) {
0135: // awt.137=Negative dashPhase
0136: throw new IllegalArgumentException(Messages
0137: .getString("awt.137")); //$NON-NLS-1$
0138: }
0139: if (dash.length == 0) {
0140: // awt.138=Zero dash length
0141: throw new IllegalArgumentException(Messages
0142: .getString("awt.138")); //$NON-NLS-1$
0143: }
0144: ZERO: {
0145: for (int i = 0; i < dash.length; i++) {
0146: if (dash[i] < 0.0) {
0147: // awt.139=Negative dash[{0}]
0148: throw new IllegalArgumentException(Messages
0149: .getString("awt.139", i)); //$NON-NLS-1$
0150: }
0151: if (dash[i] > 0.0) {
0152: break ZERO;
0153: }
0154: }
0155: // awt.13A=All dash lengths zero
0156: throw new IllegalArgumentException(Messages
0157: .getString("awt.13A")); //$NON-NLS-1$
0158: }
0159: }
0160: this .width = width;
0161: this .cap = cap;
0162: this .join = join;
0163: this .miterLimit = miterLimit;
0164: this .dash = dash;
0165: this .dashPhase = dashPhase;
0166: }
0167:
0168: public BasicStroke(float width, int cap, int join, float miterLimit) {
0169: this (width, cap, join, miterLimit, null, 0.0f);
0170: }
0171:
0172: public BasicStroke(float width, int cap, int join) {
0173: this (width, cap, join, 10.0f, null, 0.0f);
0174: }
0175:
0176: public BasicStroke(float width) {
0177: this (width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
0178: }
0179:
0180: public float getLineWidth() {
0181: return width;
0182: }
0183:
0184: public int getEndCap() {
0185: return cap;
0186: }
0187:
0188: public int getLineJoin() {
0189: return join;
0190: }
0191:
0192: public float getMiterLimit() {
0193: return miterLimit;
0194: }
0195:
0196: public float[] getDashArray() {
0197: return dash;
0198: }
0199:
0200: public float getDashPhase() {
0201: return dashPhase;
0202: }
0203:
0204: @Override
0205: public int hashCode() {
0206: HashCode hash = new HashCode();
0207: hash.append(width);
0208: hash.append(cap);
0209: hash.append(join);
0210: hash.append(miterLimit);
0211: if (dash != null) {
0212: hash.append(dashPhase);
0213: for (float element : dash) {
0214: hash.append(element);
0215: }
0216: }
0217: return hash.hashCode();
0218: }
0219:
0220: @Override
0221: public boolean equals(Object obj) {
0222: if (obj == this ) {
0223: return true;
0224: }
0225: if (obj instanceof BasicStroke) {
0226: BasicStroke bs = (BasicStroke) obj;
0227: return bs.width == width && bs.cap == cap
0228: && bs.join == join && bs.miterLimit == miterLimit
0229: && bs.dashPhase == dashPhase
0230: && java.util.Arrays.equals(bs.dash, dash);
0231: }
0232: return false;
0233: }
0234:
0235: /**
0236: * Calculates allowable curve derivation
0237: */
0238: double getCurveDelta(double width) {
0239: double a = width + CURVE_DELTA;
0240: double cos = 1.0 - 2.0 * width * width / (a * a);
0241: double sin = Math.sqrt(1.0 - cos * cos);
0242: return Math.abs(sin / cos);
0243: }
0244:
0245: /**
0246: * Calculates value to detect small angle
0247: */
0248: double getCornerDelta(double width) {
0249: return width * width * Math.sin(Math.PI * CORNER_ANGLE / 180.0);
0250: }
0251:
0252: /**
0253: * Calculates value to detect zero angle
0254: */
0255: double getZeroDelta(double width) {
0256: return width * width * Math.sin(Math.PI * CORNER_ZERO / 180.0);
0257: }
0258:
0259: public Shape createStrokedShape(Shape s) {
0260: w2 = width / 2.0;
0261: curveDelta = getCurveDelta(w2);
0262: cornerDelta = getCornerDelta(w2);
0263: zeroDelta = getZeroDelta(w2);
0264:
0265: dst = new BufferedPath();
0266: lp = new BufferedPath();
0267: rp = new BufferedPath();
0268:
0269: if (dash == null) {
0270: createSolidShape(s.getPathIterator(null));
0271: } else {
0272: createDashedShape(s.getPathIterator(null));
0273: }
0274:
0275: return dst.createGeneralPath();
0276: }
0277:
0278: /**
0279: * Generates solid stroked shape without dash
0280: * @param p - the PathIterator of source shape
0281: */
0282: void createSolidShape(PathIterator p) {
0283: double coords[] = new double[6];
0284: mx = my = cx = cy = 0.0;
0285: isMove = false;
0286: isFirst = false;
0287: checkMove = true;
0288: boolean isClosed = true;
0289:
0290: while (!p.isDone()) {
0291: switch (p.currentSegment(coords)) {
0292: case PathIterator.SEG_MOVETO:
0293: if (!isClosed) {
0294: closeSolidShape();
0295: }
0296: rp.clean();
0297: mx = cx = coords[0];
0298: my = cy = coords[1];
0299: isMove = true;
0300: isClosed = false;
0301: break;
0302: case PathIterator.SEG_LINETO:
0303: addLine(cx, cy, cx = coords[0], cy = coords[1], true);
0304: break;
0305: case PathIterator.SEG_QUADTO:
0306: addQuad(cx, cy, coords[0], coords[1], cx = coords[2],
0307: cy = coords[3]);
0308: break;
0309: case PathIterator.SEG_CUBICTO:
0310: addCubic(cx, cy, coords[0], coords[1], coords[2],
0311: coords[3], cx = coords[4], cy = coords[5]);
0312: break;
0313: case PathIterator.SEG_CLOSE:
0314: addLine(cx, cy, mx, my, false);
0315: addJoin(lp, mx, my, lp.xMove, lp.yMove, true);
0316: addJoin(rp, mx, my, rp.xMove, rp.yMove, false);
0317: lp.closePath();
0318: rp.closePath();
0319: lp.appendReverse(rp);
0320: isClosed = true;
0321: break;
0322: }
0323: p.next();
0324: }
0325: if (!isClosed) {
0326: closeSolidShape();
0327: }
0328:
0329: dst = lp;
0330: }
0331:
0332: /**
0333: * Closes solid shape path
0334: */
0335: void closeSolidShape() {
0336: addCap(lp, cx, cy, rp.xLast, rp.yLast);
0337: lp.combine(rp);
0338: addCap(lp, mx, my, lp.xMove, lp.yMove);
0339: lp.closePath();
0340: }
0341:
0342: /**
0343: * Generates dashed stroked shape
0344: * @param p - the PathIterator of source shape
0345: */
0346: void createDashedShape(PathIterator p) {
0347: double coords[] = new double[6];
0348: mx = my = cx = cy = 0.0;
0349: smx = smy = scx = scy = 0.0;
0350: isMove = false;
0351: checkMove = false;
0352: boolean isClosed = true;
0353:
0354: while (!p.isDone()) {
0355: switch (p.currentSegment(coords)) {
0356: case PathIterator.SEG_MOVETO:
0357:
0358: if (!isClosed) {
0359: closeDashedShape();
0360: }
0361:
0362: dasher = new Dasher(dash, dashPhase);
0363: lp.clean();
0364: rp.clean();
0365: sp = null;
0366: isFirst = true;
0367: isMove = true;
0368: isClosed = false;
0369: mx = cx = coords[0];
0370: my = cy = coords[1];
0371: break;
0372: case PathIterator.SEG_LINETO:
0373: addDashLine(cx, cy, cx = coords[0], cy = coords[1]);
0374: break;
0375: case PathIterator.SEG_QUADTO:
0376: addDashQuad(cx, cy, coords[0], coords[1],
0377: cx = coords[2], cy = coords[3]);
0378: break;
0379: case PathIterator.SEG_CUBICTO:
0380: addDashCubic(cx, cy, coords[0], coords[1], coords[2],
0381: coords[3], cx = coords[4], cy = coords[5]);
0382: break;
0383: case PathIterator.SEG_CLOSE:
0384: addDashLine(cx, cy, cx = mx, cy = my);
0385:
0386: if (dasher.isConnected()) {
0387: // Connect current and head segments
0388: addJoin(lp, fmx, fmy, sp.xMove, sp.yMove, true);
0389: lp.join(sp);
0390: addJoin(lp, fmx, fmy, rp.xLast, rp.yLast, true);
0391: lp.combine(rp);
0392: addCap(lp, smx, smy, lp.xMove, lp.yMove);
0393: lp.closePath();
0394: dst.append(lp);
0395: sp = null;
0396: } else {
0397: closeDashedShape();
0398: }
0399:
0400: isClosed = true;
0401: break;
0402: }
0403: p.next();
0404: }
0405:
0406: if (!isClosed) {
0407: closeDashedShape();
0408: }
0409:
0410: }
0411:
0412: /**
0413: * Closes dashed shape path
0414: */
0415: void closeDashedShape() {
0416: // Add head segment
0417: if (sp != null) {
0418: addCap(sp, fmx, fmy, sp.xMove, sp.yMove);
0419: sp.closePath();
0420: dst.append(sp);
0421: }
0422: if (lp.typeSize > 0) {
0423: // Close current segment
0424: if (!dasher.isClosed()) {
0425: addCap(lp, scx, scy, rp.xLast, rp.yLast);
0426: lp.combine(rp);
0427: addCap(lp, smx, smy, lp.xMove, lp.yMove);
0428: lp.closePath();
0429: }
0430: dst.append(lp);
0431: }
0432: }
0433:
0434: /**
0435: * Adds cap to the work path
0436: * @param p - the BufferedPath object of work path
0437: * @param x0 - the x coordinate of the source path
0438: * @param y0 - the y coordinate on the source path
0439: * @param x2 - the x coordinate of the next point on the work path
0440: * @param y2 - the y coordinate of the next point on the work path
0441: */
0442: void addCap(BufferedPath p, double x0, double y0, double x2,
0443: double y2) {
0444: double x1 = p.xLast;
0445: double y1 = p.yLast;
0446: double x10 = x1 - x0;
0447: double y10 = y1 - y0;
0448: double x20 = x2 - x0;
0449: double y20 = y2 - y0;
0450:
0451: switch (cap) {
0452: case CAP_BUTT:
0453: p.lineTo(x2, y2);
0454: break;
0455: case CAP_ROUND:
0456: double mx = x10 * CUBIC_ARC;
0457: double my = y10 * CUBIC_ARC;
0458:
0459: double x3 = x0 + y10;
0460: double y3 = y0 - x10;
0461:
0462: x10 *= CUBIC_ARC;
0463: y10 *= CUBIC_ARC;
0464: x20 *= CUBIC_ARC;
0465: y20 *= CUBIC_ARC;
0466:
0467: p.cubicTo(x1 + y10, y1 - x10, x3 + mx, y3 + my, x3, y3);
0468: p.cubicTo(x3 - mx, y3 - my, x2 - y20, y2 + x20, x2, y2);
0469: break;
0470: case CAP_SQUARE:
0471: p.lineTo(x1 + y10, y1 - x10);
0472: p.lineTo(x2 - y20, y2 + x20);
0473: p.lineTo(x2, y2);
0474: break;
0475: }
0476: }
0477:
0478: /**
0479: * Adds bevel and miter join to the work path
0480: * @param p - the BufferedPath object of work path
0481: * @param x0 - the x coordinate of the source path
0482: * @param y0 - the y coordinate on the source path
0483: * @param x2 - the x coordinate of the next point on the work path
0484: * @param y2 - the y coordinate of the next point on the work path
0485: * @param isLeft - the orientation of work path, true if work path lies to the left from source path, false otherwise
0486: */
0487: void addJoin(BufferedPath p, double x0, double y0, double x2,
0488: double y2, boolean isLeft) {
0489: double x1 = p.xLast;
0490: double y1 = p.yLast;
0491: double x10 = x1 - x0;
0492: double y10 = y1 - y0;
0493: double x20 = x2 - x0;
0494: double y20 = y2 - y0;
0495: double sin0 = x10 * y20 - y10 * x20;
0496:
0497: // Small corner
0498: if (-cornerDelta < sin0 && sin0 < cornerDelta) {
0499: double cos0 = x10 * x20 + y10 * y20;
0500: if (cos0 > 0.0) {
0501: // if zero corner do nothing
0502: if (-zeroDelta > sin0 || sin0 > zeroDelta) {
0503: double x3 = x0 + w2 * w2 * (y20 - y10) / sin0;
0504: double y3 = y0 + w2 * w2 * (x10 - x20) / sin0;
0505: p.setLast(x3, y3);
0506: }
0507: return;
0508: }
0509: // Zero corner
0510: if (-zeroDelta < sin0 && sin0 < zeroDelta) {
0511: p.lineTo(x2, y2);
0512: }
0513: return;
0514: }
0515:
0516: if (isLeft ^ (sin0 < 0.0)) {
0517: // Twisted corner
0518: p.lineTo(x0, y0);
0519: p.lineTo(x2, y2);
0520: } else {
0521: switch (join) {
0522: case JOIN_BEVEL:
0523: p.lineTo(x2, y2);
0524: break;
0525: case JOIN_MITER:
0526: double s1 = x1 * x10 + y1 * y10;
0527: double s2 = x2 * x20 + y2 * y20;
0528: double x3 = (s1 * y20 - s2 * y10) / sin0;
0529: double y3 = (s2 * x10 - s1 * x20) / sin0;
0530: double x30 = x3 - x0;
0531: double y30 = y3 - y0;
0532: double miterLength = Math.sqrt(x30 * x30 + y30 * y30);
0533: if (miterLength < miterLimit * w2) {
0534: p.lineTo(x3, y3);
0535: }
0536: p.lineTo(x2, y2);
0537: break;
0538: case JOIN_ROUND:
0539: addRoundJoin(p, x0, y0, x2, y2, isLeft);
0540: break;
0541: }
0542: }
0543: }
0544:
0545: /**
0546: * Adds round join to the work path
0547: * @param p - the BufferedPath object of work path
0548: * @param x0 - the x coordinate of the source path
0549: * @param y0 - the y coordinate on the source path
0550: * @param x2 - the x coordinate of the next point on the work path
0551: * @param y2 - the y coordinate of the next point on the work path
0552: * @param isLeft - the orientation of work path, true if work path lies to the left from source path, false otherwise
0553: */
0554: void addRoundJoin(BufferedPath p, double x0, double y0, double x2,
0555: double y2, boolean isLeft) {
0556: double x1 = p.xLast;
0557: double y1 = p.yLast;
0558: double x10 = x1 - x0;
0559: double y10 = y1 - y0;
0560: double x20 = x2 - x0;
0561: double y20 = y2 - y0;
0562:
0563: double x30 = x10 + x20;
0564: double y30 = y10 + y20;
0565:
0566: double l30 = Math.sqrt(x30 * x30 + y30 * y30);
0567:
0568: if (l30 < 1E-5) {
0569: p.lineTo(x2, y2);
0570: return;
0571: }
0572:
0573: double w = w2 / l30;
0574:
0575: x30 *= w;
0576: y30 *= w;
0577:
0578: double x3 = x0 + x30;
0579: double y3 = y0 + y30;
0580:
0581: double cos = x10 * x20 + y10 * y20;
0582: double a = Math.acos(cos / (w2 * w2));
0583: if (cos >= 0.0) {
0584: double k = 4.0 / 3.0 * Math.tan(a / 4.0);
0585: if (isLeft) {
0586: k = -k;
0587: }
0588:
0589: x10 *= k;
0590: y10 *= k;
0591: x20 *= k;
0592: y20 *= k;
0593:
0594: p.cubicTo(x1 - y10, y1 + x10, x2 + y20, y2 - x20, x2, y2);
0595: } else {
0596: double k = 4.0 / 3.0 * Math.tan(a / 8.0);
0597: if (isLeft) {
0598: k = -k;
0599: }
0600:
0601: x10 *= k;
0602: y10 *= k;
0603: x20 *= k;
0604: y20 *= k;
0605: x30 *= k;
0606: y30 *= k;
0607:
0608: p.cubicTo(x1 - y10, y1 + x10, x3 + y30, y3 - x30, x3, y3);
0609: p.cubicTo(x3 - y30, y3 + x30, x2 + y20, y2 - x20, x2, y2);
0610: }
0611:
0612: }
0613:
0614: /**
0615: * Adds solid line segment to the work path
0616: * @param x1 - the x coordinate of the start line point
0617: * @param y1 - the y coordinate of the start line point
0618: * @param x2 - the x coordinate of the end line point
0619: * @param y2 - the y coordinate of the end line point
0620: * @param zero - if true it's allowable to add zero length line segment
0621: */
0622: void addLine(double x1, double y1, double x2, double y2,
0623: boolean zero) {
0624: double dx = x2 - x1;
0625: double dy = y2 - y1;
0626:
0627: if (dx == 0.0 && dy == 0.0) {
0628: if (!zero) {
0629: return;
0630: }
0631: dx = w2;
0632: dy = 0;
0633: } else {
0634: double w = w2 / Math.sqrt(dx * dx + dy * dy);
0635: dx *= w;
0636: dy *= w;
0637: }
0638:
0639: double lx1 = x1 - dy;
0640: double ly1 = y1 + dx;
0641: double rx1 = x1 + dy;
0642: double ry1 = y1 - dx;
0643:
0644: if (checkMove) {
0645: if (isMove) {
0646: isMove = false;
0647: lp.moveTo(lx1, ly1);
0648: rp.moveTo(rx1, ry1);
0649: } else {
0650: addJoin(lp, x1, y1, lx1, ly1, true);
0651: addJoin(rp, x1, y1, rx1, ry1, false);
0652: }
0653: }
0654:
0655: lp.lineTo(x2 - dy, y2 + dx);
0656: rp.lineTo(x2 + dy, y2 - dx);
0657: }
0658:
0659: /**
0660: * Adds solid quad segment to the work path
0661: * @param x1 - the x coordinate of the first control point
0662: * @param y1 - the y coordinate of the first control point
0663: * @param x2 - the x coordinate of the second control point
0664: * @param y2 - the y coordinate of the second control point
0665: * @param x3 - the x coordinate of the third control point
0666: * @param y3 - the y coordinate of the third control point
0667: */
0668: void addQuad(double x1, double y1, double x2, double y2, double x3,
0669: double y3) {
0670: double x21 = x2 - x1;
0671: double y21 = y2 - y1;
0672: double x23 = x2 - x3;
0673: double y23 = y2 - y3;
0674:
0675: double l21 = Math.sqrt(x21 * x21 + y21 * y21);
0676: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
0677:
0678: if (l21 == 0.0 && l23 == 0.0) {
0679: addLine(x1, y1, x3, y3, false);
0680: return;
0681: }
0682:
0683: if (l21 == 0.0) {
0684: addLine(x2, y2, x3, y3, false);
0685: return;
0686: }
0687:
0688: if (l23 == 0.0) {
0689: addLine(x1, y1, x2, y2, false);
0690: return;
0691: }
0692:
0693: double w;
0694: w = w2 / l21;
0695: double mx1 = -y21 * w;
0696: double my1 = x21 * w;
0697: w = w2 / l23;
0698: double mx3 = y23 * w;
0699: double my3 = -x23 * w;
0700:
0701: double lx1 = x1 + mx1;
0702: double ly1 = y1 + my1;
0703: double rx1 = x1 - mx1;
0704: double ry1 = y1 - my1;
0705:
0706: if (checkMove) {
0707: if (isMove) {
0708: isMove = false;
0709: lp.moveTo(lx1, ly1);
0710: rp.moveTo(rx1, ry1);
0711: } else {
0712: addJoin(lp, x1, y1, lx1, ly1, true);
0713: addJoin(rp, x1, y1, rx1, ry1, false);
0714: }
0715: }
0716:
0717: if (x21 * y23 - y21 * x23 == 0.0) {
0718: // On line curve
0719: if (x21 * x23 + y21 * y23 > 0.0) {
0720: // Twisted curve
0721: if (l21 == l23) {
0722: double px = x1 + (x21 + x23) / 4.0;
0723: double py = y1 + (y21 + y23) / 4.0;
0724: lp.lineTo(px + mx1, py + my1);
0725: rp.lineTo(px - mx1, py - my1);
0726: lp.lineTo(px - mx1, py - my1);
0727: rp.lineTo(px + mx1, py + my1);
0728: lp.lineTo(x3 - mx1, y3 - my1);
0729: rp.lineTo(x3 + mx1, y3 + my1);
0730: } else {
0731: double px1, py1;
0732: double k = l21 / (l21 + l23);
0733: double px = x1 + (x21 + x23) * k * k;
0734: double py = y1 + (y21 + y23) * k * k;
0735: px1 = (x1 + px) / 2.0;
0736: py1 = (y1 + py) / 2.0;
0737: lp.quadTo(px1 + mx1, py1 + my1, px + mx1, py + my1);
0738: rp.quadTo(px1 - mx1, py1 - my1, px - mx1, py - my1);
0739: lp.lineTo(px - mx1, py - my1);
0740: rp.lineTo(px + mx1, py + my1);
0741: px1 = (x3 + px) / 2.0;
0742: py1 = (y3 + py) / 2.0;
0743: lp.quadTo(px1 - mx1, py1 - my1, x3 - mx1, y3 - my1);
0744: rp.quadTo(px1 + mx1, py1 + my1, x3 + mx1, y3 + my1);
0745: }
0746: } else {
0747: // Simple curve
0748: lp.quadTo(x2 + mx1, y2 + my1, x3 + mx3, y3 + my3);
0749: rp.quadTo(x2 - mx1, y2 - my1, x3 - mx3, y3 - my3);
0750: }
0751: } else {
0752: addSubQuad(x1, y1, x2, y2, x3, y3, 0);
0753: }
0754: }
0755:
0756: /**
0757: * Subdivides solid quad curve to make outline for source quad segment and adds it to work path
0758: * @param x1 - the x coordinate of the first control point
0759: * @param y1 - the y coordinate of the first control point
0760: * @param x2 - the x coordinate of the second control point
0761: * @param y2 - the y coordinate of the second control point
0762: * @param x3 - the x coordinate of the third control point
0763: * @param y3 - the y coordinate of the third control point
0764: * @param level - the maximum level of subdivision deepness
0765: */
0766: void addSubQuad(double x1, double y1, double x2, double y2,
0767: double x3, double y3, int level) {
0768: double x21 = x2 - x1;
0769: double y21 = y2 - y1;
0770: double x23 = x2 - x3;
0771: double y23 = y2 - y3;
0772:
0773: double cos = x21 * x23 + y21 * y23;
0774: double sin = x21 * y23 - y21 * x23;
0775:
0776: if (level < MAX_LEVEL
0777: && (cos >= 0.0 || (Math.abs(sin / cos) > curveDelta))) {
0778: double c1x = (x2 + x1) / 2.0;
0779: double c1y = (y2 + y1) / 2.0;
0780: double c2x = (x2 + x3) / 2.0;
0781: double c2y = (y2 + y3) / 2.0;
0782: double c3x = (c1x + c2x) / 2.0;
0783: double c3y = (c1y + c2y) / 2.0;
0784: addSubQuad(x1, y1, c1x, c1y, c3x, c3y, level + 1);
0785: addSubQuad(c3x, c3y, c2x, c2y, x3, y3, level + 1);
0786: } else {
0787: double w;
0788: double l21 = Math.sqrt(x21 * x21 + y21 * y21);
0789: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
0790: w = w2 / sin;
0791: double mx2 = (x21 * l23 + x23 * l21) * w;
0792: double my2 = (y21 * l23 + y23 * l21) * w;
0793: w = w2 / l23;
0794: double mx3 = y23 * w;
0795: double my3 = -x23 * w;
0796: lp.quadTo(x2 + mx2, y2 + my2, x3 + mx3, y3 + my3);
0797: rp.quadTo(x2 - mx2, y2 - my2, x3 - mx3, y3 - my3);
0798: }
0799: }
0800:
0801: /**
0802: * Adds solid cubic segment to the work path
0803: * @param x1 - the x coordinate of the first control point
0804: * @param y1 - the y coordinate of the first control point
0805: * @param x2 - the x coordinate of the second control point
0806: * @param y2 - the y coordinate of the second control point
0807: * @param x3 - the x coordinate of the third control point
0808: * @param y3 - the y coordinate of the third control point
0809: * @param x4 - the x coordinate of the fours control point
0810: * @param y4 - the y coordinate of the fours control point
0811: */
0812: void addCubic(double x1, double y1, double x2, double y2,
0813: double x3, double y3, double x4, double y4) {
0814: double x12 = x1 - x2;
0815: double y12 = y1 - y2;
0816: double x23 = x2 - x3;
0817: double y23 = y2 - y3;
0818: double x34 = x3 - x4;
0819: double y34 = y3 - y4;
0820:
0821: double l12 = Math.sqrt(x12 * x12 + y12 * y12);
0822: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
0823: double l34 = Math.sqrt(x34 * x34 + y34 * y34);
0824:
0825: // All edges are zero
0826: if (l12 == 0.0 && l23 == 0.0 && l34 == 0.0) {
0827: addLine(x1, y1, x4, y4, false);
0828: return;
0829: }
0830:
0831: // One zero edge
0832: if (l12 == 0.0 && l23 == 0.0) {
0833: addLine(x3, y3, x4, y4, false);
0834: return;
0835: }
0836:
0837: if (l23 == 0.0 && l34 == 0.0) {
0838: addLine(x1, y1, x2, y2, false);
0839: return;
0840: }
0841:
0842: if (l12 == 0.0 && l34 == 0.0) {
0843: addLine(x2, y2, x3, y3, false);
0844: return;
0845: }
0846:
0847: double w, mx1, my1, mx4, my4;
0848: boolean onLine;
0849:
0850: if (l12 == 0.0) {
0851: w = w2 / l23;
0852: mx1 = y23 * w;
0853: my1 = -x23 * w;
0854: w = w2 / l34;
0855: mx4 = y34 * w;
0856: my4 = -x34 * w;
0857: onLine = -x23 * y34 + y23 * x34 == 0.0; // sin3
0858: } else if (l34 == 0.0) {
0859: w = w2 / l12;
0860: mx1 = y12 * w;
0861: my1 = -x12 * w;
0862: w = w2 / l23;
0863: mx4 = y23 * w;
0864: my4 = -x23 * w;
0865: onLine = -x12 * y23 + y12 * x23 == 0.0; // sin2
0866: } else {
0867: w = w2 / l12;
0868: mx1 = y12 * w;
0869: my1 = -x12 * w;
0870: w = w2 / l34;
0871: mx4 = y34 * w;
0872: my4 = -x34 * w;
0873: if (l23 == 0.0) {
0874: onLine = -x12 * y34 + y12 * x34 == 0.0;
0875: } else {
0876: onLine = -x12 * y34 + y12 * x34 == 0.0
0877: && -x12 * y23 + y12 * x23 == 0.0 && // sin2
0878: -x23 * y34 + y23 * x34 == 0.0; // sin3
0879: }
0880: }
0881:
0882: double lx1 = x1 + mx1;
0883: double ly1 = y1 + my1;
0884: double rx1 = x1 - mx1;
0885: double ry1 = y1 - my1;
0886:
0887: if (checkMove) {
0888: if (isMove) {
0889: isMove = false;
0890: lp.moveTo(lx1, ly1);
0891: rp.moveTo(rx1, ry1);
0892: } else {
0893: addJoin(lp, x1, y1, lx1, ly1, true);
0894: addJoin(rp, x1, y1, rx1, ry1, false);
0895: }
0896: }
0897:
0898: if (onLine) {
0899: if ((x1 == x2 && y1 < y2) || x1 < x2) {
0900: l12 = -l12;
0901: }
0902: if ((x2 == x3 && y2 < y3) || x2 < x3) {
0903: l23 = -l23;
0904: }
0905: if ((x3 == x4 && y3 < y4) || x3 < x4) {
0906: l34 = -l34;
0907: }
0908: double d = l23 * l23 - l12 * l34;
0909: double roots[] = new double[3];
0910: int rc = 0;
0911: if (d == 0.0) {
0912: double t = (l12 - l23) / (l12 + l34 - l23 - l23);
0913: if (0.0 < t && t < 1.0) {
0914: roots[rc++] = t;
0915: }
0916: } else if (d > 0.0) {
0917: d = Math.sqrt(d);
0918: double z = l12 + l34 - l23 - l23;
0919: double t;
0920: t = (l12 - l23 + d) / z;
0921: if (0.0 < t && t < 1.0) {
0922: roots[rc++] = t;
0923: }
0924: t = (l12 - l23 - d) / z;
0925: if (0.0 < t && t < 1.0) {
0926: roots[rc++] = t;
0927: }
0928: }
0929:
0930: if (rc > 0) {
0931: // Sort roots
0932: if (rc == 2 && roots[0] > roots[1]) {
0933: double tmp = roots[0];
0934: roots[0] = roots[1];
0935: roots[1] = tmp;
0936: }
0937: roots[rc++] = 1.0;
0938:
0939: double ax = -x34 - x12 + x23 + x23;
0940: double ay = -y34 - y12 + y23 + y23;
0941: double bx = 3.0 * (-x23 + x12);
0942: double by = 3.0 * (-y23 + y12);
0943: double cx = 3.0 * (-x12);
0944: double cy = 3.0 * (-y12);
0945: double xPrev = x1;
0946: double yPrev = y1;
0947: for (int i = 0; i < rc; i++) {
0948: double t = roots[i];
0949: double px = t * (t * (t * ax + bx) + cx) + x1;
0950: double py = t * (t * (t * ay + by) + cy) + y1;
0951: double px1 = (xPrev + px) / 2.0;
0952: double py1 = (yPrev + py) / 2.0;
0953: lp.cubicTo(px1 + mx1, py1 + my1, px1 + mx1, py1
0954: + my1, px + mx1, py + my1);
0955: rp.cubicTo(px1 - mx1, py1 - my1, px1 - mx1, py1
0956: - my1, px - mx1, py - my1);
0957: if (i < rc - 1) {
0958: lp.lineTo(px - mx1, py - my1);
0959: rp.lineTo(px + mx1, py + my1);
0960: }
0961: xPrev = px;
0962: yPrev = py;
0963: mx1 = -mx1;
0964: my1 = -my1;
0965: }
0966: } else {
0967: lp.cubicTo(x2 + mx1, y2 + my1, x3 + mx4, y3 + my4, x4
0968: + mx4, y4 + my4);
0969: rp.cubicTo(x2 - mx1, y2 - my1, x3 - mx4, y3 - my4, x4
0970: - mx4, y4 - my4);
0971: }
0972: } else {
0973: addSubCubic(x1, y1, x2, y2, x3, y3, x4, y4, 0);
0974: }
0975: }
0976:
0977: /**
0978: * Subdivides solid cubic curve to make outline for source quad segment and adds it to work path
0979: * @param x1 - the x coordinate of the first control point
0980: * @param y1 - the y coordinate of the first control point
0981: * @param x2 - the x coordinate of the second control point
0982: * @param y2 - the y coordinate of the second control point
0983: * @param x3 - the x coordinate of the third control point
0984: * @param y3 - the y coordinate of the third control point
0985: * @param x4 - the x coordinate of the fours control point
0986: * @param y4 - the y coordinate of the fours control point
0987: * @param level - the maximum level of subdivision deepness
0988: */
0989: void addSubCubic(double x1, double y1, double x2, double y2,
0990: double x3, double y3, double x4, double y4, int level) {
0991: double x12 = x1 - x2;
0992: double y12 = y1 - y2;
0993: double x23 = x2 - x3;
0994: double y23 = y2 - y3;
0995: double x34 = x3 - x4;
0996: double y34 = y3 - y4;
0997:
0998: double cos2 = -x12 * x23 - y12 * y23;
0999: double cos3 = -x23 * x34 - y23 * y34;
1000: double sin2 = -x12 * y23 + y12 * x23;
1001: double sin3 = -x23 * y34 + y23 * x34;
1002: double sin0 = -x12 * y34 + y12 * x34;
1003: double cos0 = -x12 * x34 - y12 * y34;
1004:
1005: if (level < MAX_LEVEL
1006: && (sin2 != 0.0 || sin3 != 0.0 || sin0 != 0.0)
1007: && (cos2 >= 0.0 || cos3 >= 0.0 || cos0 >= 0.0
1008: || (Math.abs(sin2 / cos2) > curveDelta)
1009: || (Math.abs(sin3 / cos3) > curveDelta) || (Math
1010: .abs(sin0 / cos0) > curveDelta))) {
1011: double cx = (x2 + x3) / 2.0;
1012: double cy = (y2 + y3) / 2.0;
1013: double lx2 = (x2 + x1) / 2.0;
1014: double ly2 = (y2 + y1) / 2.0;
1015: double rx3 = (x3 + x4) / 2.0;
1016: double ry3 = (y3 + y4) / 2.0;
1017: double lx3 = (cx + lx2) / 2.0;
1018: double ly3 = (cy + ly2) / 2.0;
1019: double rx2 = (cx + rx3) / 2.0;
1020: double ry2 = (cy + ry3) / 2.0;
1021: cx = (lx3 + rx2) / 2.0;
1022: cy = (ly3 + ry2) / 2.0;
1023: addSubCubic(x1, y1, lx2, ly2, lx3, ly3, cx, cy, level + 1);
1024: addSubCubic(cx, cy, rx2, ry2, rx3, ry3, x4, y4, level + 1);
1025: } else {
1026: double w, mx1, my1, mx2, my2, mx3, my3, mx4, my4;
1027: double l12 = Math.sqrt(x12 * x12 + y12 * y12);
1028: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
1029: double l34 = Math.sqrt(x34 * x34 + y34 * y34);
1030:
1031: if (l12 == 0.0) {
1032: w = w2 / l23;
1033: mx1 = y23 * w;
1034: my1 = -x23 * w;
1035: w = w2 / l34;
1036: mx4 = y34 * w;
1037: my4 = -x34 * w;
1038: } else if (l34 == 0.0) {
1039: w = w2 / l12;
1040: mx1 = y12 * w;
1041: my1 = -x12 * w;
1042: w = w2 / l23;
1043: mx4 = y23 * w;
1044: my4 = -x23 * w;
1045: } else {
1046: // Common case
1047: w = w2 / l12;
1048: mx1 = y12 * w;
1049: my1 = -x12 * w;
1050: w = w2 / l34;
1051: mx4 = y34 * w;
1052: my4 = -x34 * w;
1053: }
1054:
1055: if (sin2 == 0.0) {
1056: mx2 = mx1;
1057: my2 = my1;
1058: } else {
1059: w = w2 / sin2;
1060: mx2 = -(x12 * l23 - x23 * l12) * w;
1061: my2 = -(y12 * l23 - y23 * l12) * w;
1062: }
1063: if (sin3 == 0.0) {
1064: mx3 = mx4;
1065: my3 = my4;
1066: } else {
1067: w = w2 / sin3;
1068: mx3 = -(x23 * l34 - x34 * l23) * w;
1069: my3 = -(y23 * l34 - y34 * l23) * w;
1070: }
1071:
1072: lp.cubicTo(x2 + mx2, y2 + my2, x3 + mx3, y3 + my3,
1073: x4 + mx4, y4 + my4);
1074: rp.cubicTo(x2 - mx2, y2 - my2, x3 - mx3, y3 - my3,
1075: x4 - mx4, y4 - my4);
1076: }
1077: }
1078:
1079: /**
1080: * Adds dashed line segment to the work path
1081: * @param x1 - the x coordinate of the start line point
1082: * @param y1 - the y coordinate of the start line point
1083: * @param x2 - the x coordinate of the end line point
1084: * @param y2 - the y coordinate of the end line point
1085: */
1086: void addDashLine(double x1, double y1, double x2, double y2) {
1087: double x21 = x2 - x1;
1088: double y21 = y2 - y1;
1089:
1090: double l21 = Math.sqrt(x21 * x21 + y21 * y21);
1091:
1092: if (l21 == 0.0) {
1093: return;
1094: }
1095:
1096: double px1, py1;
1097: px1 = py1 = 0.0;
1098: double w = w2 / l21;
1099: double mx = -y21 * w;
1100: double my = x21 * w;
1101:
1102: dasher.init(new DashIterator.Line(l21));
1103:
1104: while (!dasher.eof()) {
1105: double t = dasher.getValue();
1106: scx = x1 + t * x21;
1107: scy = y1 + t * y21;
1108:
1109: if (dasher.isOpen()) {
1110: px1 = scx;
1111: py1 = scy;
1112: double lx1 = px1 + mx;
1113: double ly1 = py1 + my;
1114: double rx1 = px1 - mx;
1115: double ry1 = py1 - my;
1116: if (isMove) {
1117: isMove = false;
1118: smx = px1;
1119: smy = py1;
1120: rp.clean();
1121: lp.moveTo(lx1, ly1);
1122: rp.moveTo(rx1, ry1);
1123: } else {
1124: addJoin(lp, x1, y1, lx1, ly1, true);
1125: addJoin(rp, x1, y1, rx1, ry1, false);
1126: }
1127: } else if (dasher.isContinue()) {
1128: double px2 = scx;
1129: double py2 = scy;
1130: lp.lineTo(px2 + mx, py2 + my);
1131: rp.lineTo(px2 - mx, py2 - my);
1132: if (dasher.close) {
1133: addCap(lp, px2, py2, rp.xLast, rp.yLast);
1134: lp.combine(rp);
1135: if (isFirst) {
1136: isFirst = false;
1137: fmx = smx;
1138: fmy = smy;
1139: sp = lp;
1140: lp = new BufferedPath();
1141: } else {
1142: addCap(lp, smx, smy, lp.xMove, lp.yMove);
1143: lp.closePath();
1144: }
1145: isMove = true;
1146: }
1147: }
1148:
1149: dasher.next();
1150: }
1151: }
1152:
1153: /**
1154: * Adds dashed quad segment to the work path
1155: * @param x1 - the x coordinate of the first control point
1156: * @param y1 - the y coordinate of the first control point
1157: * @param x2 - the x coordinate of the second control point
1158: * @param y2 - the y coordinate of the second control point
1159: * @param x3 - the x coordinate of the third control point
1160: * @param y3 - the y coordinate of the third control point
1161: */
1162: void addDashQuad(double x1, double y1, double x2, double y2,
1163: double x3, double y3) {
1164:
1165: double x21 = x2 - x1;
1166: double y21 = y2 - y1;
1167: double x23 = x2 - x3;
1168: double y23 = y2 - y3;
1169:
1170: double l21 = Math.sqrt(x21 * x21 + y21 * y21);
1171: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
1172:
1173: if (l21 == 0.0 && l23 == 0.0) {
1174: return;
1175: }
1176:
1177: if (l21 == 0.0) {
1178: addDashLine(x2, y2, x3, y3);
1179: return;
1180: }
1181:
1182: if (l23 == 0.0) {
1183: addDashLine(x1, y1, x2, y2);
1184: return;
1185: }
1186:
1187: double ax = x1 + x3 - x2 - x2;
1188: double ay = y1 + y3 - y2 - y2;
1189: double bx = x2 - x1;
1190: double by = y2 - y1;
1191: double cx = x1;
1192: double cy = y1;
1193:
1194: double px1, py1, dx1, dy1;
1195: px1 = py1 = dx1 = dy1 = 0.0;
1196: double prev = 0.0;
1197:
1198: dasher.init(new DashIterator.Quad(x1, y1, x2, y2, x3, y3));
1199:
1200: while (!dasher.eof()) {
1201: double t = dasher.getValue();
1202: double dx = t * ax + bx;
1203: double dy = t * ay + by;
1204: scx = t * (dx + bx) + cx; // t^2 * ax + 2.0 * t * bx + cx
1205: scy = t * (dy + by) + cy; // t^2 * ay + 2.0 * t * by + cy
1206: if (dasher.isOpen()) {
1207: px1 = scx;
1208: py1 = scy;
1209: dx1 = dx;
1210: dy1 = dy;
1211: double w = w2 / Math.sqrt(dx1 * dx1 + dy1 * dy1);
1212: double mx1 = -dy1 * w;
1213: double my1 = dx1 * w;
1214: double lx1 = px1 + mx1;
1215: double ly1 = py1 + my1;
1216: double rx1 = px1 - mx1;
1217: double ry1 = py1 - my1;
1218: if (isMove) {
1219: isMove = false;
1220: smx = px1;
1221: smy = py1;
1222: rp.clean();
1223: lp.moveTo(lx1, ly1);
1224: rp.moveTo(rx1, ry1);
1225: } else {
1226: addJoin(lp, x1, y1, lx1, ly1, true);
1227: addJoin(rp, x1, y1, rx1, ry1, false);
1228: }
1229: } else if (dasher.isContinue()) {
1230: double px3 = scx;
1231: double py3 = scy;
1232: double sx = x2 - x23 * prev;
1233: double sy = y2 - y23 * prev;
1234: double t2 = (t - prev) / (1 - prev);
1235: double px2 = px1 + (sx - px1) * t2;
1236: double py2 = py1 + (sy - py1) * t2;
1237:
1238: addQuad(px1, py1, px2, py2, px3, py3);
1239: if (dasher.isClosed()) {
1240: addCap(lp, px3, py3, rp.xLast, rp.yLast);
1241: lp.combine(rp);
1242: if (isFirst) {
1243: isFirst = false;
1244: fmx = smx;
1245: fmy = smy;
1246: sp = lp;
1247: lp = new BufferedPath();
1248: } else {
1249: addCap(lp, smx, smy, lp.xMove, lp.yMove);
1250: lp.closePath();
1251: }
1252: isMove = true;
1253: }
1254: }
1255:
1256: prev = t;
1257: dasher.next();
1258: }
1259: }
1260:
1261: /**
1262: * Adds dashed cubic segment to the work path
1263: * @param x1 - the x coordinate of the first control point
1264: * @param y1 - the y coordinate of the first control point
1265: * @param x2 - the x coordinate of the second control point
1266: * @param y2 - the y coordinate of the second control point
1267: * @param x3 - the x coordinate of the third control point
1268: * @param y3 - the y coordinate of the third control point
1269: * @param x4 - the x coordinate of the fours control point
1270: * @param y4 - the y coordinate of the fours control point
1271: */
1272: void addDashCubic(double x1, double y1, double x2, double y2,
1273: double x3, double y3, double x4, double y4) {
1274:
1275: double x12 = x1 - x2;
1276: double y12 = y1 - y2;
1277: double x23 = x2 - x3;
1278: double y23 = y2 - y3;
1279: double x34 = x3 - x4;
1280: double y34 = y3 - y4;
1281:
1282: double l12 = Math.sqrt(x12 * x12 + y12 * y12);
1283: double l23 = Math.sqrt(x23 * x23 + y23 * y23);
1284: double l34 = Math.sqrt(x34 * x34 + y34 * y34);
1285:
1286: // All edges are zero
1287: if (l12 == 0.0 && l23 == 0.0 && l34 == 0.0) {
1288: // NOTHING
1289: return;
1290: }
1291:
1292: // One zero edge
1293: if (l12 == 0.0 && l23 == 0.0) {
1294: addDashLine(x3, y3, x4, y4);
1295: return;
1296: }
1297:
1298: if (l23 == 0.0 && l34 == 0.0) {
1299: addDashLine(x1, y1, x2, y2);
1300: return;
1301: }
1302:
1303: if (l12 == 0.0 && l34 == 0.0) {
1304: addDashLine(x2, y2, x3, y3);
1305: return;
1306: }
1307:
1308: double ax = x4 - x1 + 3.0 * (x2 - x3);
1309: double ay = y4 - y1 + 3.0 * (y2 - y3);
1310: double bx = 3.0 * (x1 + x3 - x2 - x2);
1311: double by = 3.0 * (y1 + y3 - y2 - y2);
1312: double cx = 3.0 * (x2 - x1);
1313: double cy = 3.0 * (y2 - y1);
1314: double dx = x1;
1315: double dy = y1;
1316:
1317: double px1 = 0.0;
1318: double py1 = 0.0;
1319: double prev = 0.0;
1320:
1321: dasher.init(new DashIterator.Cubic(x1, y1, x2, y2, x3, y3, x4,
1322: y4));
1323:
1324: while (!dasher.eof()) {
1325:
1326: double t = dasher.getValue();
1327: scx = t * (t * (t * ax + bx) + cx) + dx;
1328: scy = t * (t * (t * ay + by) + cy) + dy;
1329: if (dasher.isOpen()) {
1330: px1 = scx;
1331: py1 = scy;
1332: double dx1 = t * (t * (ax + ax + ax) + bx + bx) + cx;
1333: double dy1 = t * (t * (ay + ay + ay) + by + by) + cy;
1334: double w = w2 / Math.sqrt(dx1 * dx1 + dy1 * dy1);
1335: double mx1 = -dy1 * w;
1336: double my1 = dx1 * w;
1337: double lx1 = px1 + mx1;
1338: double ly1 = py1 + my1;
1339: double rx1 = px1 - mx1;
1340: double ry1 = py1 - my1;
1341: if (isMove) {
1342: isMove = false;
1343: smx = px1;
1344: smy = py1;
1345: rp.clean();
1346: lp.moveTo(lx1, ly1);
1347: rp.moveTo(rx1, ry1);
1348: } else {
1349: addJoin(lp, x1, y1, lx1, ly1, true);
1350: addJoin(rp, x1, y1, rx1, ry1, false);
1351: }
1352: } else if (dasher.isContinue()) {
1353: double sx1 = x2 - x23 * prev;
1354: double sy1 = y2 - y23 * prev;
1355: double sx2 = x3 - x34 * prev;
1356: double sy2 = y3 - y34 * prev;
1357: double sx3 = sx1 + (sx2 - sx1) * prev;
1358: double sy3 = sy1 + (sy2 - sy1) * prev;
1359: double t2 = (t - prev) / (1 - prev);
1360: double sx4 = sx3 + (sx2 - sx3) * t2;
1361: double sy4 = sy3 + (sy2 - sy3) * t2;
1362:
1363: double px4 = scx;
1364: double py4 = scy;
1365: double px2 = px1 + (sx3 - px1) * t2;
1366: double py2 = py1 + (sy3 - py1) * t2;
1367: double px3 = px2 + (sx4 - px2) * t2;
1368: double py3 = py2 + (sy4 - py2) * t2;
1369:
1370: addCubic(px1, py1, px2, py2, px3, py3, px4, py4);
1371: if (dasher.isClosed()) {
1372: addCap(lp, px4, py4, rp.xLast, rp.yLast);
1373: lp.combine(rp);
1374: if (isFirst) {
1375: isFirst = false;
1376: fmx = smx;
1377: fmy = smy;
1378: sp = lp;
1379: lp = new BufferedPath();
1380: } else {
1381: addCap(lp, smx, smy, lp.xMove, lp.yMove);
1382: lp.closePath();
1383: }
1384: isMove = true;
1385: }
1386: }
1387:
1388: prev = t;
1389: dasher.next();
1390: }
1391: }
1392:
1393: /**
1394: * Dasher class provides dashing for particular dash style
1395: */
1396: class Dasher {
1397:
1398: double pos;
1399: boolean close, visible, first;
1400: float dash[];
1401: float phase;
1402: int index;
1403: DashIterator iter;
1404:
1405: Dasher(float dash[], float phase) {
1406: this .dash = dash;
1407: this .phase = phase;
1408: index = 0;
1409: pos = phase;
1410: visible = true;
1411: while (pos >= dash[index]) {
1412: visible = !visible;
1413: pos -= dash[index];
1414: index = (index + 1) % dash.length;
1415: }
1416: pos = -pos;
1417: first = visible;
1418: }
1419:
1420: void init(DashIterator iter) {
1421: this .iter = iter;
1422: close = true;
1423: }
1424:
1425: boolean isOpen() {
1426: return visible && pos < iter.length;
1427: }
1428:
1429: boolean isContinue() {
1430: return !visible && pos > 0;
1431: }
1432:
1433: boolean isClosed() {
1434: return close;
1435: }
1436:
1437: boolean isConnected() {
1438: return first && !close;
1439: }
1440:
1441: boolean eof() {
1442: if (!close) {
1443: pos -= iter.length;
1444: return true;
1445: }
1446: if (pos >= iter.length) {
1447: if (visible) {
1448: pos -= iter.length;
1449: return true;
1450: }
1451: close = pos == iter.length;
1452: }
1453: return false;
1454: }
1455:
1456: void next() {
1457: if (close) {
1458: pos += dash[index];
1459: index = (index + 1) % dash.length;
1460: } else {
1461: // Go back
1462: index = (index + dash.length - 1) % dash.length;
1463: pos -= dash[index];
1464: }
1465: visible = !visible;
1466: }
1467:
1468: double getValue() {
1469: double t = iter.getNext(pos);
1470: return t < 0 ? 0 : (t > 1 ? 1 : t);
1471: }
1472:
1473: }
1474:
1475: /**
1476: * DashIterator class provides dashing for particular segment type
1477: */
1478: static abstract class DashIterator {
1479:
1480: static final double FLATNESS = 1.0;
1481:
1482: static class Line extends DashIterator {
1483:
1484: Line(double len) {
1485: length = len;
1486: }
1487:
1488: @Override
1489: double getNext(double dashPos) {
1490: return dashPos / length;
1491: }
1492:
1493: }
1494:
1495: static class Quad extends DashIterator {
1496:
1497: int valSize;
1498: int valPos;
1499: double curLen;
1500: double prevLen;
1501: double lastLen;
1502: double[] values;
1503: double step;
1504:
1505: Quad(double x1, double y1, double x2, double y2, double x3,
1506: double y3) {
1507:
1508: double nx = x1 + x3 - x2 - x2;
1509: double ny = y1 + y3 - y2 - y2;
1510:
1511: int n = (int) (1 + Math.sqrt(0.75
1512: * (Math.abs(nx) + Math.abs(ny)) * FLATNESS));
1513: step = 1.0 / n;
1514:
1515: double ax = x1 + x3 - x2 - x2;
1516: double ay = y1 + y3 - y2 - y2;
1517: double bx = 2.0 * (x2 - x1);
1518: double by = 2.0 * (y2 - y1);
1519:
1520: double dx1 = step * (step * ax + bx);
1521: double dy1 = step * (step * ay + by);
1522: double dx2 = step * (step * ax * 2.0);
1523: double dy2 = step * (step * ay * 2.0);
1524: double vx = x1;
1525: double vy = y1;
1526:
1527: valSize = n;
1528: values = new double[valSize];
1529: double pvx = vx;
1530: double pvy = vy;
1531: length = 0.0;
1532: for (int i = 0; i < n; i++) {
1533: vx += dx1;
1534: vy += dy1;
1535: dx1 += dx2;
1536: dy1 += dy2;
1537: double lx = vx - pvx;
1538: double ly = vy - pvy;
1539: values[i] = Math.sqrt(lx * lx + ly * ly);
1540: length += values[i];
1541: pvx = vx;
1542: pvy = vy;
1543: }
1544:
1545: valPos = 0;
1546: curLen = 0.0;
1547: prevLen = 0.0;
1548: }
1549:
1550: @Override
1551: double getNext(double dashPos) {
1552: double t = 2.0;
1553: while (curLen <= dashPos && valPos < valSize) {
1554: prevLen = curLen;
1555: curLen += lastLen = values[valPos++];
1556: }
1557: if (curLen > dashPos) {
1558: t = (valPos - 1 + (dashPos - prevLen) / lastLen)
1559: * step;
1560: }
1561: return t;
1562: }
1563:
1564: }
1565:
1566: static class Cubic extends DashIterator {
1567:
1568: int valSize;
1569: int valPos;
1570: double curLen;
1571: double prevLen;
1572: double lastLen;
1573: double[] values;
1574: double step;
1575:
1576: Cubic(double x1, double y1, double x2, double y2,
1577: double x3, double y3, double x4, double y4) {
1578:
1579: double nx1 = x1 + x3 - x2 - x2;
1580: double ny1 = y1 + y3 - y2 - y2;
1581: double nx2 = x2 + x4 - x3 - x3;
1582: double ny2 = y2 + y4 - y3 - y3;
1583:
1584: double max = Math.max(Math.abs(nx1) + Math.abs(ny1),
1585: Math.abs(nx2) + Math.abs(ny2));
1586: int n = (int) (1 + Math.sqrt(0.75 * max) * FLATNESS);
1587: step = 1.0 / n;
1588:
1589: double ax = x4 - x1 + 3.0 * (x2 - x3);
1590: double ay = y4 - y1 + 3.0 * (y2 - y3);
1591: double bx = 3.0 * (x1 + x3 - x2 - x2);
1592: double by = 3.0 * (y1 + y3 - y2 - y2);
1593: double cx = 3.0 * (x2 - x1);
1594: double cy = 3.0 * (y2 - y1);
1595:
1596: double dx1 = step * (step * (step * ax + bx) + cx);
1597: double dy1 = step * (step * (step * ay + by) + cy);
1598: double dx2 = step
1599: * (step * (step * ax * 6.0 + bx * 2.0));
1600: double dy2 = step
1601: * (step * (step * ay * 6.0 + by * 2.0));
1602: double dx3 = step * (step * (step * ax * 6.0));
1603: double dy3 = step * (step * (step * ay * 6.0));
1604: double vx = x1;
1605: double vy = y1;
1606:
1607: valSize = n;
1608: values = new double[valSize];
1609: double pvx = vx;
1610: double pvy = vy;
1611: length = 0.0;
1612: for (int i = 0; i < n; i++) {
1613: vx += dx1;
1614: vy += dy1;
1615: dx1 += dx2;
1616: dy1 += dy2;
1617: dx2 += dx3;
1618: dy2 += dy3;
1619: double lx = vx - pvx;
1620: double ly = vy - pvy;
1621: values[i] = Math.sqrt(lx * lx + ly * ly);
1622: length += values[i];
1623: pvx = vx;
1624: pvy = vy;
1625: }
1626:
1627: valPos = 0;
1628: curLen = 0.0;
1629: prevLen = 0.0;
1630: }
1631:
1632: @Override
1633: double getNext(double dashPos) {
1634: double t = 2.0;
1635: while (curLen <= dashPos && valPos < valSize) {
1636: prevLen = curLen;
1637: curLen += lastLen = values[valPos++];
1638: }
1639: if (curLen > dashPos) {
1640: t = (valPos - 1 + (dashPos - prevLen) / lastLen)
1641: * step;
1642: }
1643: return t;
1644: }
1645:
1646: }
1647:
1648: double length;
1649:
1650: abstract double getNext(double dashPos);
1651:
1652: }
1653:
1654: /**
1655: * BufferedPath class provides work path storing and processing
1656: */
1657: static class BufferedPath {
1658:
1659: private static final int bufCapacity = 10;
1660:
1661: static int pointShift[] = { 2, // MOVETO
1662: 2, // LINETO
1663: 4, // QUADTO
1664: 6, // CUBICTO
1665: 0 }; // CLOSE
1666:
1667: byte[] types;
1668: float[] points;
1669: int typeSize;
1670: int pointSize;
1671:
1672: float xLast;
1673: float yLast;
1674: float xMove;
1675: float yMove;
1676:
1677: public BufferedPath() {
1678: types = new byte[bufCapacity];
1679: points = new float[bufCapacity * 2];
1680: }
1681:
1682: void checkBuf(int typeCount, int pointCount) {
1683: if (typeSize + typeCount > types.length) {
1684: byte tmp[] = new byte[typeSize
1685: + Math.max(bufCapacity, typeCount)];
1686: System.arraycopy(types, 0, tmp, 0, typeSize);
1687: types = tmp;
1688: }
1689: if (pointSize + pointCount > points.length) {
1690: float tmp[] = new float[pointSize
1691: + Math.max(bufCapacity * 2, pointCount)];
1692: System.arraycopy(points, 0, tmp, 0, pointSize);
1693: points = tmp;
1694: }
1695: }
1696:
1697: boolean isEmpty() {
1698: return typeSize == 0;
1699: }
1700:
1701: void clean() {
1702: typeSize = 0;
1703: pointSize = 0;
1704: }
1705:
1706: void moveTo(double x, double y) {
1707: checkBuf(1, 2);
1708: types[typeSize++] = PathIterator.SEG_MOVETO;
1709: points[pointSize++] = xMove = (float) x;
1710: points[pointSize++] = yMove = (float) y;
1711: }
1712:
1713: void lineTo(double x, double y) {
1714: checkBuf(1, 2);
1715: types[typeSize++] = PathIterator.SEG_LINETO;
1716: points[pointSize++] = xLast = (float) x;
1717: points[pointSize++] = yLast = (float) y;
1718: }
1719:
1720: void quadTo(double x1, double y1, double x2, double y2) {
1721: checkBuf(1, 4);
1722: types[typeSize++] = PathIterator.SEG_QUADTO;
1723: points[pointSize++] = (float) x1;
1724: points[pointSize++] = (float) y1;
1725: points[pointSize++] = xLast = (float) x2;
1726: points[pointSize++] = yLast = (float) y2;
1727: }
1728:
1729: void cubicTo(double x1, double y1, double x2, double y2,
1730: double x3, double y3) {
1731: checkBuf(1, 6);
1732: types[typeSize++] = PathIterator.SEG_CUBICTO;
1733: points[pointSize++] = (float) x1;
1734: points[pointSize++] = (float) y1;
1735: points[pointSize++] = (float) x2;
1736: points[pointSize++] = (float) y2;
1737: points[pointSize++] = xLast = (float) x3;
1738: points[pointSize++] = yLast = (float) y3;
1739: }
1740:
1741: void closePath() {
1742: checkBuf(1, 0);
1743: types[typeSize++] = PathIterator.SEG_CLOSE;
1744: }
1745:
1746: void setLast(double x, double y) {
1747: points[pointSize - 2] = xLast = (float) x;
1748: points[pointSize - 1] = yLast = (float) y;
1749: }
1750:
1751: void append(BufferedPath p) {
1752: checkBuf(p.typeSize, p.pointSize);
1753: System.arraycopy(p.points, 0, points, pointSize,
1754: p.pointSize);
1755: System.arraycopy(p.types, 0, types, typeSize, p.typeSize);
1756: pointSize += p.pointSize;
1757: typeSize += p.typeSize;
1758: xLast = points[pointSize - 2];
1759: yLast = points[pointSize - 1];
1760: }
1761:
1762: void appendReverse(BufferedPath p) {
1763: checkBuf(p.typeSize, p.pointSize);
1764: // Skip last point, beacause it's the first point of the second path
1765: for (int i = p.pointSize - 2; i >= 0; i -= 2) {
1766: points[pointSize++] = p.points[i + 0];
1767: points[pointSize++] = p.points[i + 1];
1768: }
1769: // Skip first type, beacuse it's always MOVETO
1770: int closeIndex = 0;
1771: for (int i = p.typeSize - 1; i >= 0; i--) {
1772: byte type = p.types[i];
1773: if (type == PathIterator.SEG_MOVETO) {
1774: types[closeIndex] = PathIterator.SEG_MOVETO;
1775: types[typeSize++] = PathIterator.SEG_CLOSE;
1776: } else {
1777: if (type == PathIterator.SEG_CLOSE) {
1778: closeIndex = typeSize;
1779: }
1780: types[typeSize++] = type;
1781: }
1782: }
1783: xLast = points[pointSize - 2];
1784: yLast = points[pointSize - 1];
1785: }
1786:
1787: void join(BufferedPath p) {
1788: // Skip MOVETO
1789: checkBuf(p.typeSize - 1, p.pointSize - 2);
1790: System.arraycopy(p.points, 2, points, pointSize,
1791: p.pointSize - 2);
1792: System.arraycopy(p.types, 1, types, typeSize,
1793: p.typeSize - 1);
1794: pointSize += p.pointSize - 2;
1795: typeSize += p.typeSize - 1;
1796: xLast = points[pointSize - 2];
1797: yLast = points[pointSize - 1];
1798: }
1799:
1800: void combine(BufferedPath p) {
1801: checkBuf(p.typeSize - 1, p.pointSize - 2);
1802: // Skip last point, beacause it's the first point of the second path
1803: for (int i = p.pointSize - 4; i >= 0; i -= 2) {
1804: points[pointSize++] = p.points[i + 0];
1805: points[pointSize++] = p.points[i + 1];
1806: }
1807: // Skip first type, beacuse it's always MOVETO
1808: for (int i = p.typeSize - 1; i >= 1; i--) {
1809: types[typeSize++] = p.types[i];
1810: }
1811: xLast = points[pointSize - 2];
1812: yLast = points[pointSize - 1];
1813: }
1814:
1815: GeneralPath createGeneralPath() {
1816: GeneralPath p = new GeneralPath();
1817: int j = 0;
1818: for (int i = 0; i < typeSize; i++) {
1819: int type = types[i];
1820: switch (type) {
1821: case PathIterator.SEG_MOVETO:
1822: p.moveTo(points[j], points[j + 1]);
1823: break;
1824: case PathIterator.SEG_LINETO:
1825: p.lineTo(points[j], points[j + 1]);
1826: break;
1827: case PathIterator.SEG_QUADTO:
1828: p.quadTo(points[j], points[j + 1], points[j + 2],
1829: points[j + 3]);
1830: break;
1831: case PathIterator.SEG_CUBICTO:
1832: p
1833: .curveTo(points[j], points[j + 1],
1834: points[j + 2], points[j + 3],
1835: points[j + 4], points[j + 5]);
1836: break;
1837: case PathIterator.SEG_CLOSE:
1838: p.closePath();
1839: break;
1840: }
1841: j += pointShift[type];
1842: }
1843: return p;
1844: }
1845:
1846: }
1847:
1848: }
|