< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java

Print this page




   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 }
< prev index next >