9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.marlin;
27
28 import java.util.Arrays;
29
30 // TODO: some of the arithmetic here is too verbose and prone to hard to
31 // debug typos. We should consider making a small Point/Vector class that
32 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
33 public final class DStroker implements DPathConsumer2D, MarlinConst {
34
35 private static final int MOVE_TO = 0;
36 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
37 private static final int CLOSE = 2;
38
39 /**
40 * Constant value for join style.
41 */
42 public static final int JOIN_MITER = 0;
43
44 /**
45 * Constant value for join style.
46 */
47 public static final int JOIN_ROUND = 1;
48
101 // original path (thought they may have different directions), so these
102 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
103 // would be error prone and hard to read, so we keep these anyway.
104 private double smx, smy, cmx, cmy;
105
106 private final PolyStack reverse;
107
108 // This is where the curve to be processed is put. We give it
109 // enough room to store all curves.
110 private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
111 private final double[] lp = new double[8];
112 private final double[] rp = new double[8];
113 private final double[] subdivTs = new double[MAX_N_CURVES - 1];
114
115 // per-thread renderer context
116 final DRendererContext rdrCtx;
117
118 // dirty curve
119 final DCurve curve;
120
121 /**
122 * Constructs a <code>DStroker</code>.
123 * @param rdrCtx per-thread renderer context
124 */
125 DStroker(final DRendererContext rdrCtx) {
126 this.rdrCtx = rdrCtx;
127
128 this.reverse = new PolyStack(rdrCtx);
129 this.curve = rdrCtx.curve;
130 }
131
132 /**
133 * Inits the <code>DStroker</code>.
134 *
135 * @param pc2d an output <code>DPathConsumer2D</code>.
136 * @param lineWidth the desired line width in pixels
137 * @param capStyle the desired end cap style, one of
138 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
139 * <code>CAP_SQUARE</code>.
140 * @param joinStyle the desired line join style, one of
141 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
142 * <code>JOIN_BEVEL</code>.
143 * @param miterLimit the desired miter limit
144 * @return this instance
145 */
146 public DStroker init(DPathConsumer2D pc2d,
147 double lineWidth,
148 int capStyle,
149 int joinStyle,
150 double miterLimit)
151 {
152 this.out = pc2d;
153
154 this.lineWidth2 = lineWidth / 2.0d;
155 this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
156 this.capStyle = capStyle;
157 this.joinStyle = joinStyle;
158
159 double limit = miterLimit * lineWidth2;
160 this.miterLimitSq = limit * limit;
161
162 this.prev = CLOSE;
163
164 rdrCtx.stroking = 1;
165
166 return this; // fluent API
167 }
168
169 /**
170 * Disposes this stroker:
171 * clean up before reusing this instance
172 */
173 void dispose() {
174 reverse.dispose();
175
176 if (DO_CLEAN_DIRTY) {
177 // Force zero-fill dirty arrays:
178 Arrays.fill(offset0, 0.0d);
179 Arrays.fill(offset1, 0.0d);
180 Arrays.fill(offset2, 0.0d);
181 Arrays.fill(miter, 0.0d);
182 Arrays.fill(middle, 0.0d);
183 Arrays.fill(lp, 0.0d);
184 Arrays.fill(rp, 0.0d);
185 Arrays.fill(subdivTs, 0.0d);
186 }
187 }
188
189 private static void computeOffset(final double lx, final double ly,
190 final double w, final double[] m)
191 {
192 double len = lx*lx + ly*ly;
193 if (len == 0.0d) {
194 m[0] = 0.0d;
195 m[1] = 0.0d;
427 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
428 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
429 miter, 0);
430
431 final double miterX = miter[0];
432 final double miterY = miter[1];
433 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
434
435 // If the lines are parallel, lenSq will be either NaN or +inf
436 // (actually, I'm not sure if the latter is possible. The important
437 // thing is that -inf is not possible, because lenSq is a square).
438 // For both of those values, the comparison below will fail and
439 // no miter will be drawn, which is correct.
440 if (lenSq < miterLimitSq) {
441 emitLineTo(miterX, miterY, rev);
442 }
443 }
444
445 @Override
446 public void moveTo(double x0, double y0) {
447 if (prev == DRAWING_OP_TO) {
448 finish();
449 }
450 this.sx0 = this.cx0 = x0;
451 this.sy0 = this.cy0 = y0;
452 this.cdx = this.sdx = 1.0d;
453 this.cdy = this.sdy = 0.0d;
454 this.prev = MOVE_TO;
455 }
456
457 @Override
458 public void lineTo(double x1, double y1) {
459 double dx = x1 - cx0;
460 double dy = y1 - cy0;
461 if (dx == 0.0d && dy == 0.0d) {
462 dx = 1.0d;
463 }
464 computeOffset(dx, dy, lineWidth2, offset0);
465 final double mx = offset0[0];
466 final double my = offset0[1];
467
468 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
469
470 emitLineTo(cx0 + mx, cy0 + my);
471 emitLineTo( x1 + mx, y1 + my);
472
473 emitLineToRev(cx0 - mx, cy0 - my);
474 emitLineToRev( x1 - mx, y1 - my);
475
476 this.cmx = mx;
477 this.cmy = my;
478 this.cdx = dx;
479 this.cdy = dy;
480 this.cx0 = x1;
481 this.cy0 = y1;
482 this.prev = DRAWING_OP_TO;
483 }
484
485 @Override
486 public void closePath() {
487 if (prev != DRAWING_OP_TO) {
488 if (prev == CLOSE) {
489 return;
490 }
491 emitMoveTo(cx0, cy0 - lineWidth2);
492 this.cmx = this.smx = 0.0d;
493 this.cmy = this.smy = -lineWidth2;
494 this.cdx = this.sdx = 1.0d;
495 this.cdy = this.sdy = 0.0d;
496 finish();
497 return;
498 }
499
500 if (cx0 != sx0 || cy0 != sy0) {
501 lineTo(sx0, sy0);
502 }
503
504 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
505
506 emitLineTo(sx0 + smx, sy0 + smy);
507
508 emitMoveTo(sx0 - smx, sy0 - smy);
509 emitReverse();
510
511 this.prev = CLOSE;
512 emitClose();
513 }
514
515 private void emitReverse() {
516 reverse.popAll(out);
517 }
518
519 @Override
520 public void pathDone() {
521 if (prev == DRAWING_OP_TO) {
522 finish();
523 }
524
525 out.pathDone();
526
527 // this shouldn't matter since this object won't be used
528 // after the call to this method.
529 this.prev = CLOSE;
530
531 // Dispose this instance:
532 dispose();
533 }
534
535 private void finish() {
536 if (capStyle == CAP_ROUND) {
537 drawRoundCap(cx0, cy0, cmx, cmy);
538 } else if (capStyle == CAP_SQUARE) {
539 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
540 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
541 }
542
543 emitReverse();
544
545 if (capStyle == CAP_ROUND) {
546 drawRoundCap(sx0, sy0, -smx, -smy);
547 } else if (capStyle == CAP_SQUARE) {
548 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
549 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
550 }
551
552 emitClose();
553 }
554
555 private void emitMoveTo(final double x0, final double y0) {
556 out.moveTo(x0, y0);
557 }
558
559 private void emitLineTo(final double x1, final double y1) {
560 out.lineTo(x1, y1);
561 }
562
563 private void emitLineToRev(final double x1, final double y1) {
564 reverse.pushLine(x1, y1);
565 }
566
567 private void emitLineTo(final double x1, final double y1,
568 final boolean rev)
569 {
570 if (rev) {
571 emitLineToRev(x1, y1);
603 private void emitCurveTo(final double x0, final double y0,
604 final double x1, final double y1,
605 final double x2, final double y2,
606 final double x3, final double y3, final boolean rev)
607 {
608 if (rev) {
609 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
610 } else {
611 out.curveTo(x1, y1, x2, y2, x3, y3);
612 }
613 }
614
615 private void emitClose() {
616 out.closePath();
617 }
618
619 private void drawJoin(double pdx, double pdy,
620 double x0, double y0,
621 double dx, double dy,
622 double omx, double omy,
623 double mx, double my)
624 {
625 if (prev != DRAWING_OP_TO) {
626 emitMoveTo(x0 + mx, y0 + my);
627 this.sdx = dx;
628 this.sdy = dy;
629 this.smx = mx;
630 this.smy = my;
631 } else {
632 boolean cw = isCW(pdx, pdy, dx, dy);
633 if (joinStyle == JOIN_MITER) {
634 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
635 } else if (joinStyle == JOIN_ROUND) {
636 drawRoundJoin(x0, y0,
637 omx, omy,
638 mx, my, cw,
639 ROUND_JOIN_THRESHOLD);
640 }
641 emitLineTo(x0, y0, !cw);
642 }
643 prev = DRAWING_OP_TO;
644 }
645
646 private static boolean within(final double x1, final double y1,
647 final double x2, final double y2,
648 final double ERR)
649 {
650 assert ERR > 0 : "";
651 // compare taxicab distance. ERR will always be small, so using
652 // true distance won't give much benefit
653 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
654 DHelpers.within(y1, y2, ERR)); // this is just as good.
655 }
656
657 private void getLineOffsets(double x1, double y1,
658 double x2, double y2,
659 double[] left, double[] right) {
928 ret += c.dyRoots(ts, ret);
929 // subdivide at inflection points.
930 if (type == 8) {
931 // quadratic curves can't have inflection points
932 ret += c.infPoints(ts, ret);
933 }
934
935 // now we must subdivide at points where one of the offset curves will have
936 // a cusp. This happens at ts where the radius of curvature is equal to w.
937 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
938
939 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
940 DHelpers.isort(ts, 0, ret);
941 return ret;
942 }
943
944 @Override public void curveTo(double x1, double y1,
945 double x2, double y2,
946 double x3, double y3)
947 {
948 final double[] mid = middle;
949
950 mid[0] = cx0; mid[1] = cy0;
951 mid[2] = x1; mid[3] = y1;
952 mid[4] = x2; mid[5] = y2;
953 mid[6] = x3; mid[7] = y3;
954
955 // need these so we can update the state at the end of this method
956 final double xf = mid[6], yf = mid[7];
957 double dxs = mid[2] - mid[0];
958 double dys = mid[3] - mid[1];
959 double dxf = mid[6] - mid[4];
960 double dyf = mid[7] - mid[5];
961
962 boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
963 boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
964 if (p1eqp2) {
965 dxs = mid[4] - mid[0];
966 dys = mid[5] - mid[1];
967 if (dxs == 0.0d && dys == 0.0d) {
968 dxs = mid[6] - mid[0];
969 dys = mid[7] - mid[1];
970 }
971 }
972 if (p3eqp4) {
973 dxf = mid[6] - mid[2];
974 dyf = mid[7] - mid[3];
975 if (dxf == 0.0d && dyf == 0.0d) {
976 dxf = mid[6] - mid[0];
977 dyf = mid[7] - mid[1];
978 }
979 }
980 if (dxs == 0.0d && dys == 0.0d) {
981 // this happens if the "curve" is just a point
982 lineTo(mid[0], mid[1]);
983 return;
984 }
985
986 // if these vectors are too small, normalize them, to avoid future
987 // precision problems.
988 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
989 double len = Math.sqrt(dxs*dxs + dys*dys);
990 dxs /= len;
991 dys /= len;
992 }
993 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
994 double len = Math.sqrt(dxf*dxf + dyf*dyf);
995 dxf /= len;
996 dyf /= len;
997 }
998
999 computeOffset(dxs, dys, lineWidth2, offset0);
1000 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1001
1002 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1003
1004 double prevT = 0.0d;
1005 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1006 final double t = subdivTs[i];
1007 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1008 mid, off, mid, off, mid, off + 6);
1009 prevT = t;
1010 }
1011
1012 final double[] l = lp;
1013 final double[] r = rp;
1014
1015 int kind = 0;
1016 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1017 kind = computeOffsetCubic(mid, off, l, r);
1018
1019 emitLineTo(l[0], l[1]);
1020
1021 switch(kind) {
1022 case 8:
1023 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1024 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1025 break;
1026 case 4:
1027 emitLineTo(l[2], l[3]);
1028 emitLineToRev(r[0], r[1]);
1029 break;
1030 default:
1031 }
1032 emitLineToRev(r[kind - 2], r[kind - 1]);
1033 }
1034
1035 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1036 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1037 this.cdx = dxf;
1038 this.cdy = dyf;
1039 this.cx0 = xf;
1040 this.cy0 = yf;
1041 this.prev = DRAWING_OP_TO;
1042 }
1043
1044 @Override public void quadTo(double x1, double y1, double x2, double y2) {
1045 final double[] mid = middle;
1046
1047 mid[0] = cx0; mid[1] = cy0;
1048 mid[2] = x1; mid[3] = y1;
1049 mid[4] = x2; mid[5] = y2;
1050
1051 // need these so we can update the state at the end of this method
1052 final double xf = mid[4], yf = mid[5];
1053 double dxs = mid[2] - mid[0];
1054 double dys = mid[3] - mid[1];
1055 double dxf = mid[4] - mid[2];
1056 double dyf = mid[5] - mid[3];
1057 if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1058 dxs = dxf = mid[4] - mid[0];
1059 dys = dyf = mid[5] - mid[1];
1060 }
1061 if (dxs == 0.0d && dys == 0.0d) {
1062 // this happens if the "curve" is just a point
1063 lineTo(mid[0], mid[1]);
1064 return;
1065 }
1066 // if these vectors are too small, normalize them, to avoid future
1067 // precision problems.
1068 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1069 double len = Math.sqrt(dxs*dxs + dys*dys);
1070 dxs /= len;
1071 dys /= len;
1072 }
1073 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1074 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1075 dxf /= len;
1076 dyf /= len;
1077 }
1078
1079 computeOffset(dxs, dys, lineWidth2, offset0);
1080 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1081
1082 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1083
1084 double prevt = 0.0d;
1085 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1086 final double t = subdivTs[i];
1087 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1088 mid, off, mid, off, mid, off + 4);
1089 prevt = t;
1090 }
1091
1092 final double[] l = lp;
1093 final double[] r = rp;
1094
1095 int kind = 0;
1096 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1097 kind = computeOffsetQuad(mid, off, l, r);
1098
1099 emitLineTo(l[0], l[1]);
1100
1101 switch(kind) {
1102 case 6:
1103 emitQuadTo(l[2], l[3], l[4], l[5]);
1104 emitQuadToRev(r[0], r[1], r[2], r[3]);
1105 break;
1106 case 4:
1107 emitLineTo(l[2], l[3]);
1108 emitLineToRev(r[0], r[1]);
1109 break;
1110 default:
1111 }
1112 emitLineToRev(r[kind - 2], r[kind - 1]);
1113 }
1114
1115 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1116 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1117 this.cdx = dxf;
1118 this.cdy = dyf;
1119 this.cx0 = xf;
1120 this.cy0 = yf;
1121 this.prev = DRAWING_OP_TO;
1122 }
1123
1124 // a stack of polynomial curves where each curve shares endpoints with
1125 // adjacent ones.
1126 static final class PolyStack {
1127 private static final byte TYPE_LINETO = (byte) 0;
1128 private static final byte TYPE_QUADTO = (byte) 1;
1129 private static final byte TYPE_CUBICTO = (byte) 2;
1130
1131 // curves capacity = edges count (8192) = edges x 2 (coords)
1132 private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
1133
1134 // types capacity = edges count (4096)
1135 private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
1136
1137 double[] curves;
1138 int end;
1139 byte[] curveTypes;
1140 int numCurves;
1141
1142 // per-thread renderer context
1143 final DRendererContext rdrCtx;
1144
1145 // curves ref (dirty)
1146 final DoubleArrayCache.Reference curves_ref;
1147 // curveTypes ref (dirty)
1148 final ByteArrayCache.Reference curveTypes_ref;
1149
1150 // used marks (stats only)
1151 int curveTypesUseMark;
1152 int curvesUseMark;
1153
1154 /**
1155 * Constructor
1156 * @param rdrCtx per-thread renderer context
1157 */
1158 PolyStack(final DRendererContext rdrCtx) {
1159 this.rdrCtx = rdrCtx;
1160
1161 curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K
1162 curves = curves_ref.initial;
1163
1164 curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
1165 curveTypes = curveTypes_ref.initial;
1166 numCurves = 0;
1167 end = 0;
1168
1169 if (DO_STATS) {
1170 curveTypesUseMark = 0;
1171 curvesUseMark = 0;
1172 }
1173 }
1174
1175 /**
1176 * Disposes this PolyStack:
1177 * clean up before reusing this instance
1178 */
1179 void dispose() {
1180 end = 0;
1181 numCurves = 0;
1182
1183 if (DO_STATS) {
1184 rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
1185 rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
1186 rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
1187
1188 // reset marks
1189 curveTypesUseMark = 0;
1190 curvesUseMark = 0;
1191 }
1192
1193 // Return arrays:
1194 // curves and curveTypes are kept dirty
1195 curves = curves_ref.putArray(curves);
1196 curveTypes = curveTypes_ref.putArray(curveTypes);
1197 }
1198
1199 private void ensureSpace(final int n) {
1200 // use substraction to avoid integer overflow:
1201 if (curves.length - end < n) {
1202 if (DO_STATS) {
1203 rdrCtx.stats.stat_array_stroker_polystack_curves
1204 .add(end + n);
1205 }
1206 curves = curves_ref.widenArray(curves, end, end + n);
1207 }
1208 if (curveTypes.length <= numCurves) {
1209 if (DO_STATS) {
1210 rdrCtx.stats.stat_array_stroker_polystack_curveTypes
1211 .add(numCurves + 1);
1212 }
1213 curveTypes = curveTypes_ref.widenArray(curveTypes,
1214 numCurves,
1215 numCurves + 1);
1216 }
1217 }
1218
1219 void pushCubic(double x0, double y0,
1220 double x1, double y1,
1221 double x2, double y2)
1222 {
1223 ensureSpace(6);
1224 curveTypes[numCurves++] = TYPE_CUBICTO;
1225 // we reverse the coordinate order to make popping easier
1226 final double[] _curves = curves;
1227 int e = end;
1228 _curves[e++] = x2; _curves[e++] = y2;
1229 _curves[e++] = x1; _curves[e++] = y1;
1230 _curves[e++] = x0; _curves[e++] = y0;
1231 end = e;
1232 }
1233
1234 void pushQuad(double x0, double y0,
1235 double x1, double y1)
1236 {
1237 ensureSpace(4);
1238 curveTypes[numCurves++] = TYPE_QUADTO;
1239 final double[] _curves = curves;
1240 int e = end;
1241 _curves[e++] = x1; _curves[e++] = y1;
1242 _curves[e++] = x0; _curves[e++] = y0;
1243 end = e;
1244 }
1245
1246 void pushLine(double x, double y) {
1247 ensureSpace(2);
1248 curveTypes[numCurves++] = TYPE_LINETO;
1249 curves[end++] = x; curves[end++] = y;
1250 }
1251
1252 void popAll(DPathConsumer2D io) {
1253 if (DO_STATS) {
1254 // update used marks:
1255 if (numCurves > curveTypesUseMark) {
1256 curveTypesUseMark = numCurves;
1257 }
1258 if (end > curvesUseMark) {
1259 curvesUseMark = end;
1260 }
1261 }
1262 final byte[] _curveTypes = curveTypes;
1263 final double[] _curves = curves;
1264 int nc = numCurves;
1265 int e = end;
1266
1267 while (nc != 0) {
1268 switch(_curveTypes[--nc]) {
1269 case TYPE_LINETO:
1270 e -= 2;
1271 io.lineTo(_curves[e], _curves[e+1]);
1272 continue;
1273 case TYPE_QUADTO:
1274 e -= 4;
1275 io.quadTo(_curves[e+0], _curves[e+1],
1276 _curves[e+2], _curves[e+3]);
1277 continue;
1278 case TYPE_CUBICTO:
1279 e -= 6;
1280 io.curveTo(_curves[e+0], _curves[e+1],
1281 _curves[e+2], _curves[e+3],
1282 _curves[e+4], _curves[e+5]);
1283 continue;
1284 default:
1285 }
1286 }
1287 numCurves = 0;
1288 end = 0;
1289 }
1290
1291 @Override
1292 public String toString() {
1293 String ret = "";
1294 int nc = numCurves;
1295 int last = end;
1296 int len;
1297 while (nc != 0) {
1298 switch(curveTypes[--nc]) {
1299 case TYPE_LINETO:
1300 len = 2;
1301 ret += "line: ";
1302 break;
1303 case TYPE_QUADTO:
1304 len = 4;
1305 ret += "quad: ";
1306 break;
1307 case TYPE_CUBICTO:
1308 len = 6;
1309 ret += "cubic: ";
1310 break;
1311 default:
1312 len = 0;
1313 }
1314 last -= len;
1315 ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
1316 + "\n";
1317 }
1318 return ret;
1319 }
1320 }
1321 }
|
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.marlin;
27
28 import java.util.Arrays;
29 import com.sun.marlin.DHelpers.PolyStack;
30
31 // TODO: some of the arithmetic here is too verbose and prone to hard to
32 // debug typos. We should consider making a small Point/Vector class that
33 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
34 public final class DStroker implements DPathConsumer2D, MarlinConst {
35
36 private static final int MOVE_TO = 0;
37 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
38 private static final int CLOSE = 2;
39
40 /**
41 * Constant value for join style.
42 */
43 public static final int JOIN_MITER = 0;
44
45 /**
46 * Constant value for join style.
47 */
48 public static final int JOIN_ROUND = 1;
49
102 // original path (thought they may have different directions), so these
103 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
104 // would be error prone and hard to read, so we keep these anyway.
105 private double smx, smy, cmx, cmy;
106
107 private final PolyStack reverse;
108
109 // This is where the curve to be processed is put. We give it
110 // enough room to store all curves.
111 private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
112 private final double[] lp = new double[8];
113 private final double[] rp = new double[8];
114 private final double[] subdivTs = new double[MAX_N_CURVES - 1];
115
116 // per-thread renderer context
117 final DRendererContext rdrCtx;
118
119 // dirty curve
120 final DCurve curve;
121
122 // Bounds of the drawing region, at pixel precision.
123 private double[] clipRect;
124
125 // the outcode of the current point
126 private int cOutCode = 0;
127
128 // the outcode of the starting point
129 private int sOutCode = 0;
130
131 // flag indicating if the path is opened (clipped)
132 private boolean opened = false;
133 // flag indicating if the starting point's cap is done
134 private boolean capStart = false;
135
136 /**
137 * Constructs a <code>DStroker</code>.
138 * @param rdrCtx per-thread renderer context
139 */
140 DStroker(final DRendererContext rdrCtx) {
141 this.rdrCtx = rdrCtx;
142
143 this.reverse = (rdrCtx.stats != null) ?
144 new PolyStack(rdrCtx,
145 rdrCtx.stats.stat_str_polystack_types,
146 rdrCtx.stats.stat_str_polystack_curves,
147 rdrCtx.stats.hist_str_polystack_curves,
148 rdrCtx.stats.stat_array_str_polystack_curves,
149 rdrCtx.stats.stat_array_str_polystack_types)
150 : new PolyStack(rdrCtx);
151
152 this.curve = rdrCtx.curve;
153 }
154
155 /**
156 * Inits the <code>DStroker</code>.
157 *
158 * @param pc2d an output <code>DPathConsumer2D</code>.
159 * @param lineWidth the desired line width in pixels
160 * @param capStyle the desired end cap style, one of
161 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
162 * <code>CAP_SQUARE</code>.
163 * @param joinStyle the desired line join style, one of
164 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
165 * <code>JOIN_BEVEL</code>.
166 * @param miterLimit the desired miter limit
167 * @param scale scaling factor applied to clip boundaries
168 * @param rdrOffX renderer's coordinate offset on X axis
169 * @param rdrOffY renderer's coordinate offset on Y axis
170 * @return this instance
171 */
172 public DStroker init(DPathConsumer2D pc2d,
173 double lineWidth,
174 int capStyle,
175 int joinStyle,
176 double miterLimit,
177 final double scale,
178 double rdrOffX,
179 double rdrOffY)
180 {
181 this.out = pc2d;
182
183 this.lineWidth2 = lineWidth / 2.0d;
184 this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
185 this.capStyle = capStyle;
186 this.joinStyle = joinStyle;
187
188 final double limit = miterLimit * lineWidth2;
189 this.miterLimitSq = limit * limit;
190
191 this.prev = CLOSE;
192
193 rdrCtx.stroking = 1;
194
195 if (rdrCtx.doClip) {
196 // Adjust the clipping rectangle with the stroker margin (miter limit, width)
197
198 // round joins / caps:
199 final double widthLimit =
200 ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ?
201 : lineWidth2;
202
203 double boundsMargin;
204 if (joinStyle == JOIN_MITER) {
205 boundsMargin = Math.max(widthLimit, limit);
206 } else {
207 boundsMargin = widthLimit;
208 }
209 if (scale != 1.0d) {
210 boundsMargin *= scale;
211 rdrOffX *= scale;
212 rdrOffY *= scale;
213 }
214
215 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
216 // adjust clip rectangle (ymin, ymax, xmin, xmax):
217 final double[] _clipRect = rdrCtx.clipRect;
218 _clipRect[0] -= boundsMargin - rdrOffY;
219 _clipRect[1] += boundsMargin + rdrOffY;
220 _clipRect[2] -= boundsMargin - rdrOffX;
221 _clipRect[3] += boundsMargin + rdrOffX;
222 this.clipRect = _clipRect;
223 } else {
224 this.clipRect = null;
225 }
226 return this; // fluent API
227 }
228
229 /**
230 * Disposes this stroker:
231 * clean up before reusing this instance
232 */
233 void dispose() {
234 reverse.dispose();
235
236 opened = false;
237 capStart = false;
238
239 if (DO_CLEAN_DIRTY) {
240 // Force zero-fill dirty arrays:
241 Arrays.fill(offset0, 0.0d);
242 Arrays.fill(offset1, 0.0d);
243 Arrays.fill(offset2, 0.0d);
244 Arrays.fill(miter, 0.0d);
245 Arrays.fill(middle, 0.0d);
246 Arrays.fill(lp, 0.0d);
247 Arrays.fill(rp, 0.0d);
248 Arrays.fill(subdivTs, 0.0d);
249 }
250 }
251
252 private static void computeOffset(final double lx, final double ly,
253 final double w, final double[] m)
254 {
255 double len = lx*lx + ly*ly;
256 if (len == 0.0d) {
257 m[0] = 0.0d;
258 m[1] = 0.0d;
490 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
491 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
492 miter, 0);
493
494 final double miterX = miter[0];
495 final double miterY = miter[1];
496 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
497
498 // If the lines are parallel, lenSq will be either NaN or +inf
499 // (actually, I'm not sure if the latter is possible. The important
500 // thing is that -inf is not possible, because lenSq is a square).
501 // For both of those values, the comparison below will fail and
502 // no miter will be drawn, which is correct.
503 if (lenSq < miterLimitSq) {
504 emitLineTo(miterX, miterY, rev);
505 }
506 }
507
508 @Override
509 public void moveTo(double x0, double y0) {
510 moveTo(x0, y0, cOutCode);
511 // update starting point:
512 this.sx0 = x0;
513 this.sy0 = y0;
514 this.sdx = 1.0d;
515 this.sdy = 0.0d;
516 this.opened = false;
517 this.capStart = false;
518
519 if (clipRect != null) {
520 final int outcode = DHelpers.outcode(x0, y0, clipRect);
521 this.cOutCode = outcode;
522 this.sOutCode = outcode;
523 }
524 }
525
526 private void moveTo(double x0, double y0,
527 final int outcode)
528 {
529 if (prev == MOVE_TO) {
530 this.cx0 = x0;
531 this.cy0 = y0;
532 } else {
533 if (prev == DRAWING_OP_TO) {
534 finish(outcode);
535 }
536 this.prev = MOVE_TO;
537 this.cx0 = x0;
538 this.cy0 = y0;
539 this.cdx = 1.0d;
540 this.cdy = 0.0d;
541 }
542 }
543
544 @Override
545 public void lineTo(double x1, double y1) {
546 lineTo(x1, y1, false);
547 }
548
549 private void lineTo(double x1, double y1, boolean force) {
550 final int outcode0 = this.cOutCode;
551 if (!force && clipRect != null) {
552 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
553 this.cOutCode = outcode1;
554
555 // basic rejection criteria
556 if ((outcode0 & outcode1) != 0) {
557 moveTo(x1, y1, outcode0);
558 opened = true;
559 return;
560 }
561 }
562
563 double dx = x1 - cx0;
564 double dy = y1 - cy0;
565 if (dx == 0.0d && dy == 0.0d) {
566 dx = 1.0d;
567 }
568 computeOffset(dx, dy, lineWidth2, offset0);
569 final double mx = offset0[0];
570 final double my = offset0[1];
571
572 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
573
574 emitLineTo(cx0 + mx, cy0 + my);
575 emitLineTo( x1 + mx, y1 + my);
576
577 emitLineToRev(cx0 - mx, cy0 - my);
578 emitLineToRev( x1 - mx, y1 - my);
579
580 this.prev = DRAWING_OP_TO;
581 this.cx0 = x1;
582 this.cy0 = y1;
583 this.cdx = dx;
584 this.cdy = dy;
585 this.cmx = mx;
586 this.cmy = my;
587 }
588
589 @Override
590 public void closePath() {
591 // distinguish empty path at all vs opened path ?
592 if (prev != DRAWING_OP_TO && !opened) {
593 if (prev == CLOSE) {
594 return;
595 }
596 emitMoveTo(cx0, cy0 - lineWidth2);
597
598 this.sdx = 1.0d;
599 this.sdy = 0.0d;
600 this.cdx = 1.0d;
601 this.cdy = 0.0d;
602
603 this.smx = 0.0d;
604 this.smy = -lineWidth2;
605 this.cmx = 0.0d;
606 this.cmy = -lineWidth2;
607
608 finish(cOutCode);
609 return;
610 }
611
612 if (sOutCode == 0) {
613 if (cx0 != sx0 || cy0 != sy0) {
614 lineTo(sx0, sy0, true);
615 }
616
617 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode);
618
619 emitLineTo(sx0 + smx, sy0 + smy);
620
621 if (opened) {
622 emitLineTo(sx0 - smx, sy0 - smy);
623 } else {
624 emitMoveTo(sx0 - smx, sy0 - smy);
625 }
626 }
627 // Ignore caps like finish(false)
628 emitReverse();
629
630 this.prev = CLOSE;
631
632 if (opened) {
633 // do not emit close
634 opened = false;
635 } else {
636 emitClose();
637 }
638 }
639
640 private void emitReverse() {
641 reverse.popAll(out);
642 }
643
644 @Override
645 public void pathDone() {
646 if (prev == DRAWING_OP_TO) {
647 finish(cOutCode);
648 }
649
650 out.pathDone();
651
652 // this shouldn't matter since this object won't be used
653 // after the call to this method.
654 this.prev = CLOSE;
655
656 // Dispose this instance:
657 dispose();
658 }
659
660 private void finish(final int outcode) {
661 // Problem: impossible to guess if the path will be closed in advance
662 // i.e. if caps must be drawn or not ?
663 // Solution: use the ClosedPathDetector before Stroker to determine
664 // if the path is a closed path or not
665 if (!rdrCtx.closedPath) {
666 if (outcode == 0) {
667 // current point = end's cap:
668 if (capStyle == CAP_ROUND) {
669 drawRoundCap(cx0, cy0, cmx, cmy);
670 } else if (capStyle == CAP_SQUARE) {
671 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
672 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
673 }
674 }
675 emitReverse();
676
677 if (!capStart) {
678 capStart = true;
679
680 if (sOutCode == 0) {
681 // starting point = initial cap:
682 if (capStyle == CAP_ROUND) {
683 drawRoundCap(sx0, sy0, -smx, -smy);
684 } else if (capStyle == CAP_SQUARE) {
685 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
686 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
687 }
688 }
689 }
690 } else {
691 emitReverse();
692 }
693 emitClose();
694 }
695
696 private void emitMoveTo(final double x0, final double y0) {
697 out.moveTo(x0, y0);
698 }
699
700 private void emitLineTo(final double x1, final double y1) {
701 out.lineTo(x1, y1);
702 }
703
704 private void emitLineToRev(final double x1, final double y1) {
705 reverse.pushLine(x1, y1);
706 }
707
708 private void emitLineTo(final double x1, final double y1,
709 final boolean rev)
710 {
711 if (rev) {
712 emitLineToRev(x1, y1);
744 private void emitCurveTo(final double x0, final double y0,
745 final double x1, final double y1,
746 final double x2, final double y2,
747 final double x3, final double y3, final boolean rev)
748 {
749 if (rev) {
750 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
751 } else {
752 out.curveTo(x1, y1, x2, y2, x3, y3);
753 }
754 }
755
756 private void emitClose() {
757 out.closePath();
758 }
759
760 private void drawJoin(double pdx, double pdy,
761 double x0, double y0,
762 double dx, double dy,
763 double omx, double omy,
764 double mx, double my,
765 final int outcode)
766 {
767 if (prev != DRAWING_OP_TO) {
768 emitMoveTo(x0 + mx, y0 + my);
769 if (!opened) {
770 this.sdx = dx;
771 this.sdy = dy;
772 this.smx = mx;
773 this.smy = my;
774 }
775 } else {
776 final boolean cw = isCW(pdx, pdy, dx, dy);
777 if (outcode == 0) {
778 if (joinStyle == JOIN_MITER) {
779 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
780 } else if (joinStyle == JOIN_ROUND) {
781 drawRoundJoin(x0, y0,
782 omx, omy,
783 mx, my, cw,
784 ROUND_JOIN_THRESHOLD);
785 }
786 }
787 emitLineTo(x0, y0, !cw);
788 }
789 prev = DRAWING_OP_TO;
790 }
791
792 private static boolean within(final double x1, final double y1,
793 final double x2, final double y2,
794 final double ERR)
795 {
796 assert ERR > 0 : "";
797 // compare taxicab distance. ERR will always be small, so using
798 // true distance won't give much benefit
799 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
800 DHelpers.within(y1, y2, ERR)); // this is just as good.
801 }
802
803 private void getLineOffsets(double x1, double y1,
804 double x2, double y2,
805 double[] left, double[] right) {
1074 ret += c.dyRoots(ts, ret);
1075 // subdivide at inflection points.
1076 if (type == 8) {
1077 // quadratic curves can't have inflection points
1078 ret += c.infPoints(ts, ret);
1079 }
1080
1081 // now we must subdivide at points where one of the offset curves will have
1082 // a cusp. This happens at ts where the radius of curvature is equal to w.
1083 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
1084
1085 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
1086 DHelpers.isort(ts, 0, ret);
1087 return ret;
1088 }
1089
1090 @Override public void curveTo(double x1, double y1,
1091 double x2, double y2,
1092 double x3, double y3)
1093 {
1094 final int outcode0 = this.cOutCode;
1095 if (clipRect != null) {
1096 final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
1097 this.cOutCode = outcode3;
1098
1099 if (outcode3 != 0) {
1100 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1101 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1102
1103 // basic rejection criteria
1104 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1105 moveTo(x3, y3, outcode0);
1106 opened = true;
1107 return;
1108 }
1109 }
1110 }
1111
1112 final double[] mid = middle;
1113
1114 mid[0] = cx0; mid[1] = cy0;
1115 mid[2] = x1; mid[3] = y1;
1116 mid[4] = x2; mid[5] = y2;
1117 mid[6] = x3; mid[7] = y3;
1118
1119 // need these so we can update the state at the end of this method
1120 final double xf = x3, yf = y3;
1121 double dxs = mid[2] - mid[0];
1122 double dys = mid[3] - mid[1];
1123 double dxf = mid[6] - mid[4];
1124 double dyf = mid[7] - mid[5];
1125
1126 boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
1127 boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
1128 if (p1eqp2) {
1129 dxs = mid[4] - mid[0];
1130 dys = mid[5] - mid[1];
1131 if (dxs == 0.0d && dys == 0.0d) {
1132 dxs = mid[6] - mid[0];
1133 dys = mid[7] - mid[1];
1134 }
1135 }
1136 if (p3eqp4) {
1137 dxf = mid[6] - mid[2];
1138 dyf = mid[7] - mid[3];
1139 if (dxf == 0.0d && dyf == 0.0d) {
1140 dxf = mid[6] - mid[0];
1141 dyf = mid[7] - mid[1];
1142 }
1143 }
1144 if (dxs == 0.0d && dys == 0.0d) {
1145 // this happens if the "curve" is just a point
1146 // fix outcode0 for lineTo() call:
1147 if (clipRect != null) {
1148 this.cOutCode = outcode0;
1149 }
1150 lineTo(mid[0], mid[1]);
1151 return;
1152 }
1153
1154 // if these vectors are too small, normalize them, to avoid future
1155 // precision problems.
1156 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1157 double len = Math.sqrt(dxs*dxs + dys*dys);
1158 dxs /= len;
1159 dys /= len;
1160 }
1161 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1162 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1163 dxf /= len;
1164 dyf /= len;
1165 }
1166
1167 computeOffset(dxs, dys, lineWidth2, offset0);
1168 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1169
1170 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1171
1172 double prevT = 0.0d;
1173 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1174 final double t = subdivTs[i];
1175 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1176 mid, off, mid, off, mid, off + 6);
1177 prevT = t;
1178 }
1179
1180 final double[] l = lp;
1181 final double[] r = rp;
1182
1183 int kind = 0;
1184 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1185 kind = computeOffsetCubic(mid, off, l, r);
1186
1187 emitLineTo(l[0], l[1]);
1188
1189 switch(kind) {
1190 case 8:
1191 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1192 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1193 break;
1194 case 4:
1195 emitLineTo(l[2], l[3]);
1196 emitLineToRev(r[0], r[1]);
1197 break;
1198 default:
1199 }
1200 emitLineToRev(r[kind - 2], r[kind - 1]);
1201 }
1202
1203 this.prev = DRAWING_OP_TO;
1204 this.cx0 = xf;
1205 this.cy0 = yf;
1206 this.cdx = dxf;
1207 this.cdy = dyf;
1208 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1209 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1210 }
1211
1212 @Override
1213 public void quadTo(double x1, double y1,
1214 double x2, double y2)
1215 {
1216 final int outcode0 = this.cOutCode;
1217 if (clipRect != null) {
1218 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1219 this.cOutCode = outcode2;
1220
1221 if (outcode2 != 0) {
1222 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1223
1224 // basic rejection criteria
1225 if ((outcode0 & outcode1 & outcode2) != 0) {
1226 moveTo(x2, y2, outcode0);
1227 opened = true;
1228 return;
1229 }
1230 }
1231 }
1232
1233 final double[] mid = middle;
1234
1235 mid[0] = cx0; mid[1] = cy0;
1236 mid[2] = x1; mid[3] = y1;
1237 mid[4] = x2; mid[5] = y2;
1238
1239 // need these so we can update the state at the end of this method
1240 final double xf = x2, yf = y2;
1241 double dxs = mid[2] - mid[0];
1242 double dys = mid[3] - mid[1];
1243 double dxf = mid[4] - mid[2];
1244 double dyf = mid[5] - mid[3];
1245 if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1246 dxs = dxf = mid[4] - mid[0];
1247 dys = dyf = mid[5] - mid[1];
1248 }
1249 if (dxs == 0.0d && dys == 0.0d) {
1250 // this happens if the "curve" is just a point
1251 // fix outcode0 for lineTo() call:
1252 if (clipRect != null) {
1253 this.cOutCode = outcode0;
1254 }
1255 lineTo(mid[0], mid[1]);
1256 return;
1257 }
1258 // if these vectors are too small, normalize them, to avoid future
1259 // precision problems.
1260 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1261 double len = Math.sqrt(dxs*dxs + dys*dys);
1262 dxs /= len;
1263 dys /= len;
1264 }
1265 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1266 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1267 dxf /= len;
1268 dyf /= len;
1269 }
1270
1271 computeOffset(dxs, dys, lineWidth2, offset0);
1272 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1273
1274 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1275
1276 double prevt = 0.0d;
1277 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1278 final double t = subdivTs[i];
1279 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1280 mid, off, mid, off, mid, off + 4);
1281 prevt = t;
1282 }
1283
1284 final double[] l = lp;
1285 final double[] r = rp;
1286
1287 int kind = 0;
1288 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1289 kind = computeOffsetQuad(mid, off, l, r);
1290
1291 emitLineTo(l[0], l[1]);
1292
1293 switch(kind) {
1294 case 6:
1295 emitQuadTo(l[2], l[3], l[4], l[5]);
1296 emitQuadToRev(r[0], r[1], r[2], r[3]);
1297 break;
1298 case 4:
1299 emitLineTo(l[2], l[3]);
1300 emitLineToRev(r[0], r[1]);
1301 break;
1302 default:
1303 }
1304 emitLineToRev(r[kind - 2], r[kind - 1]);
1305 }
1306
1307 this.prev = DRAWING_OP_TO;
1308 this.cx0 = xf;
1309 this.cy0 = yf;
1310 this.cdx = dxf;
1311 this.cdy = dyf;
1312 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1313 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1314 }
1315
1316 }
|