1 /*
   2  * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   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 sun.java2d.marlin;
  27 
  28 import java.util.Arrays;
  29 import sun.java2d.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 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     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  41     // didn't want to change the values of the constant below when I converted
  42     // it to floating point, so that's why the divisions by 2^16 are there.
  43     private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
  44 
  45     // kappa = (4/3) * (SQRT(2) - 1)
  46     private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  47 
  48     // SQRT(2)
  49     private static final double SQRT_2 = Math.sqrt(2.0d);
  50 
  51     private static final int MAX_N_CURVES = 11;
  52 
  53     private DPathConsumer2D out;
  54 
  55     private int capStyle;
  56     private int joinStyle;
  57 
  58     private double lineWidth2;
  59     private double invHalfLineWidth2Sq;
  60 
  61     private final double[] offset0 = new double[2];
  62     private final double[] offset1 = new double[2];
  63     private final double[] offset2 = new double[2];
  64     private final double[] miter = new double[2];
  65     private double miterLimitSq;
  66 
  67     private int prev;
  68 
  69     // The starting point of the path, and the slope there.
  70     private double sx0, sy0, sdx, sdy;
  71     // the current point and the slope there.
  72     private double cx0, cy0, cdx, cdy; // c stands for current
  73     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
  74     // first and last points on the left parallel path. Since this path is
  75     // parallel, it's slope at any point is parallel to the slope of the
  76     // original path (thought they may have different directions), so these
  77     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
  78     // would be error prone and hard to read, so we keep these anyway.
  79     private double smx, smy, cmx, cmy;
  80 
  81     private final PolyStack reverse;
  82 
  83     // This is where the curve to be processed is put. We give it
  84     // enough room to store all curves.
  85     private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
  86     private final double[] lp = new double[8];
  87     private final double[] rp = new double[8];
  88     private final double[] subdivTs = new double[MAX_N_CURVES - 1];
  89 
  90     // per-thread renderer context
  91     final DRendererContext rdrCtx;
  92 
  93     // dirty curve
  94     final DCurve curve;
  95 
  96     // Bounds of the drawing region, at pixel precision.
  97     private double[] clipRect;
  98 
  99     // the outcode of the current point
 100     private int cOutCode = 0;
 101 
 102     // the outcode of the starting point
 103     private int sOutCode = 0;
 104 
 105     // flag indicating if the path is opened (clipped)
 106     private boolean opened = false;
 107     // flag indicating if the starting point's cap is done
 108     private boolean capStart = false;
 109 
 110     /**
 111      * Constructs a <code>DStroker</code>.
 112      * @param rdrCtx per-thread renderer context
 113      */
 114     DStroker(final DRendererContext rdrCtx) {
 115         this.rdrCtx = rdrCtx;
 116 
 117         this.reverse = (rdrCtx.stats != null) ?
 118             new PolyStack(rdrCtx,
 119                     rdrCtx.stats.stat_str_polystack_types,
 120                     rdrCtx.stats.stat_str_polystack_curves,
 121                     rdrCtx.stats.hist_str_polystack_curves,
 122                     rdrCtx.stats.stat_array_str_polystack_curves,
 123                     rdrCtx.stats.stat_array_str_polystack_types)
 124             : new PolyStack(rdrCtx);
 125 
 126         this.curve = rdrCtx.curve;
 127     }
 128 
 129     /**
 130      * Inits the <code>DStroker</code>.
 131      *
 132      * @param pc2d an output <code>DPathConsumer2D</code>.
 133      * @param lineWidth the desired line width in pixels
 134      * @param capStyle the desired end cap style, one of
 135      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 136      * <code>CAP_SQUARE</code>.
 137      * @param joinStyle the desired line join style, one of
 138      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 139      * <code>JOIN_BEVEL</code>.
 140      * @param miterLimit the desired miter limit
 141      * @param scale scaling factor applied to clip boundaries
 142      * @return this instance
 143      */
 144     DStroker init(final DPathConsumer2D pc2d,
 145                   final double lineWidth,
 146                   final int capStyle,
 147                   final int joinStyle,
 148                   final double miterLimit,
 149                   final double scale)
 150     {
 151         this.out = pc2d;
 152 
 153         this.lineWidth2 = lineWidth / 2.0d;
 154         this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
 155         this.capStyle = capStyle;
 156         this.joinStyle = joinStyle;
 157 
 158         final double limit = miterLimit * lineWidth2;
 159         this.miterLimitSq = limit * limit;
 160 
 161         this.prev = CLOSE;
 162 
 163         rdrCtx.stroking = 1;
 164 
 165         if (rdrCtx.doClip) {
 166             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 167             double rdrOffX = 0.0d, rdrOffY = 0.0d;
 168             double margin = lineWidth2;
 169 
 170             if (capStyle == CAP_SQUARE) {
 171                 margin *= SQRT_2;
 172             }
 173             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 174                 margin = limit;
 175             }
 176             if (scale != 1.0d) {
 177                 margin *= scale;
 178                 rdrOffX = scale * DRenderer.RDR_OFFSET_X;
 179                 rdrOffY = scale * DRenderer.RDR_OFFSET_Y;
 180             }
 181             // add a small rounding error:
 182             margin += 1e-3d;
 183 
 184             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 185             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 186             final double[] _clipRect = rdrCtx.clipRect;
 187             _clipRect[0] -= margin - rdrOffY;
 188             _clipRect[1] += margin + rdrOffY;
 189             _clipRect[2] -= margin - rdrOffX;
 190             _clipRect[3] += margin + rdrOffX;
 191             this.clipRect = _clipRect;
 192         } else {
 193             this.clipRect = null;
 194             this.cOutCode = 0;
 195             this.sOutCode = 0;
 196         }
 197         return this; // fluent API
 198     }
 199 
 200     /**
 201      * Disposes this stroker:
 202      * clean up before reusing this instance
 203      */
 204     void dispose() {
 205         reverse.dispose();
 206 
 207         opened   = false;
 208         capStart = false;
 209 
 210         if (DO_CLEAN_DIRTY) {
 211             // Force zero-fill dirty arrays:
 212             Arrays.fill(offset0, 0.0d);
 213             Arrays.fill(offset1, 0.0d);
 214             Arrays.fill(offset2, 0.0d);
 215             Arrays.fill(miter, 0.0d);
 216             Arrays.fill(middle, 0.0d);
 217             Arrays.fill(lp, 0.0d);
 218             Arrays.fill(rp, 0.0d);
 219             Arrays.fill(subdivTs, 0.0d);
 220         }
 221     }
 222 
 223     private static void computeOffset(final double lx, final double ly,
 224                                       final double w, final double[] m)
 225     {
 226         double len = lx*lx + ly*ly;
 227         if (len == 0.0d) {
 228             m[0] = 0.0d;
 229             m[1] = 0.0d;
 230         } else {
 231             len = Math.sqrt(len);
 232             m[0] =  (ly * w) / len;
 233             m[1] = -(lx * w) / len;
 234         }
 235     }
 236 
 237     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
 238     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
 239     // the smallest angle between it and dx2,dy2).
 240     // This is equivalent to detecting whether a point q is on the right side
 241     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
 242     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
 243     // clockwise order.
 244     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
 245     private static boolean isCW(final double dx1, final double dy1,
 246                                 final double dx2, final double dy2)
 247     {
 248         return dx1 * dy2 <= dy1 * dx2;
 249     }
 250 
 251     private void drawRoundJoin(double x, double y,
 252                                double omx, double omy, double mx, double my,
 253                                boolean rev,
 254                                double threshold)
 255     {
 256         if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
 257             return;
 258         }
 259 
 260         double domx = omx - mx;
 261         double domy = omy - my;
 262         double len = domx*domx + domy*domy;
 263         if (len < threshold) {
 264             return;
 265         }
 266 
 267         if (rev) {
 268             omx = -omx;
 269             omy = -omy;
 270             mx  = -mx;
 271             my  = -my;
 272         }
 273         drawRoundJoin(x, y, omx, omy, mx, my, rev);
 274     }
 275 
 276     private void drawRoundJoin(double cx, double cy,
 277                                double omx, double omy,
 278                                double mx, double my,
 279                                boolean rev)
 280     {
 281         // The sign of the dot product of mx,my and omx,omy is equal to the
 282         // the sign of the cosine of ext
 283         // (ext is the angle between omx,omy and mx,my).
 284         final double cosext = omx * mx + omy * my;
 285         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 286         // need 1 curve to approximate the circle section that joins omx,omy
 287         // and mx,my.
 288         final int numCurves = (cosext >= 0.0d) ? 1 : 2;
 289 
 290         switch (numCurves) {
 291         case 1:
 292             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 293             break;
 294         case 2:
 295             // we need to split the arc into 2 arcs spanning the same angle.
 296             // The point we want will be one of the 2 intersections of the
 297             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
 298             // circle. We could find this by scaling the vector
 299             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
 300             // on the circle), but that can have numerical problems when the angle
 301             // between omx,omy and mx,my is close to 180 degrees. So we compute a
 302             // normal of (omx,omy)-(mx,my). This will be the direction of the
 303             // perpendicular bisector. To get one of the intersections, we just scale
 304             // this vector that its length is lineWidth2 (this works because the
 305             // perpendicular bisector goes through the origin). This scaling doesn't
 306             // have numerical problems because we know that lineWidth2 divided by
 307             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
 308             // we know the angle of the arc is > 90 degrees).
 309             double nx = my - omy, ny = omx - mx;
 310             double nlen = Math.sqrt(nx*nx + ny*ny);
 311             double scale = lineWidth2/nlen;
 312             double mmx = nx * scale, mmy = ny * scale;
 313 
 314             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
 315             // computed the wrong intersection so we get the other one.
 316             // The test above is equivalent to if (rev).
 317             if (rev) {
 318                 mmx = -mmx;
 319                 mmy = -mmy;
 320             }
 321             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
 322             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
 323             break;
 324         default:
 325         }
 326     }
 327 
 328     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
 329     private void drawBezApproxForArc(final double cx, final double cy,
 330                                      final double omx, final double omy,
 331                                      final double mx, final double my,
 332                                      boolean rev)
 333     {
 334         final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
 335 
 336         // check round off errors producing cos(ext) > 1 and a NaN below
 337         // cos(ext) == 1 implies colinear segments and an empty join anyway
 338         if (cosext2 >= 0.5d) {
 339             // just return to avoid generating a flat curve:
 340             return;
 341         }
 342 
 343         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
 344         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that
 345         // define the bezier curve we're computing.
 346         // It is computed using the constraints that P1-P0 and P3-P2 are parallel
 347         // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.
 348         double cv = ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) /
 349                             (1.0d + Math.sqrt(cosext2 + 0.5d)));
 350         // if clockwise, we need to negate cv.
 351         if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)
 352             cv = -cv;
 353         }
 354         final double x1 = cx + omx;
 355         final double y1 = cy + omy;
 356         final double x2 = x1 - cv * omy;
 357         final double y2 = y1 + cv * omx;
 358 
 359         final double x4 = cx + mx;
 360         final double y4 = cy + my;
 361         final double x3 = x4 + cv * my;
 362         final double y3 = y4 - cv * mx;
 363 
 364         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
 365     }
 366 
 367     private void drawRoundCap(double cx, double cy, double mx, double my) {
 368         final double Cmx = C * mx;
 369         final double Cmy = C * my;
 370         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
 371                     cx - my + Cmx, cy + mx + Cmy,
 372                     cx - my,       cy + mx);
 373         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
 374                     cx - mx - Cmy, cy - my + Cmx,
 375                     cx - mx,       cy - my);
 376     }
 377 
 378     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 379     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 380     private static void computeMiter(final double x0, final double y0,
 381                                      final double x1, final double y1,
 382                                      final double x0p, final double y0p,
 383                                      final double x1p, final double y1p,
 384                                      final double[] m, int off)
 385     {
 386         double x10 = x1 - x0;
 387         double y10 = y1 - y0;
 388         double x10p = x1p - x0p;
 389         double y10p = y1p - y0p;
 390 
 391         // if this is 0, the lines are parallel. If they go in the
 392         // same direction, there is no intersection so m[off] and
 393         // m[off+1] will contain infinity, so no miter will be drawn.
 394         // If they go in the same direction that means that the start of the
 395         // current segment and the end of the previous segment have the same
 396         // tangent, in which case this method won't even be involved in
 397         // miter drawing because it won't be called by drawMiter (because
 398         // (mx == omx && my == omy) will be true, and drawMiter will return
 399         // immediately).
 400         double den = x10*y10p - x10p*y10;
 401         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 402         t /= den;
 403         m[off++] = x0 + t*x10;
 404         m[off]   = y0 + t*y10;
 405     }
 406 
 407     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 408     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 409     private static void safeComputeMiter(final double x0, final double y0,
 410                                          final double x1, final double y1,
 411                                          final double x0p, final double y0p,
 412                                          final double x1p, final double y1p,
 413                                          final double[] m, int off)
 414     {
 415         double x10 = x1 - x0;
 416         double y10 = y1 - y0;
 417         double x10p = x1p - x0p;
 418         double y10p = y1p - y0p;
 419 
 420         // if this is 0, the lines are parallel. If they go in the
 421         // same direction, there is no intersection so m[off] and
 422         // m[off+1] will contain infinity, so no miter will be drawn.
 423         // If they go in the same direction that means that the start of the
 424         // current segment and the end of the previous segment have the same
 425         // tangent, in which case this method won't even be involved in
 426         // miter drawing because it won't be called by drawMiter (because
 427         // (mx == omx && my == omy) will be true, and drawMiter will return
 428         // immediately).
 429         double den = x10*y10p - x10p*y10;
 430         if (den == 0.0d) {
 431             m[off++] = (x0 + x0p) / 2.0d;
 432             m[off]   = (y0 + y0p) / 2.0d;
 433             return;
 434         }
 435         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 436         t /= den;
 437         m[off++] = x0 + t*x10;
 438         m[off] = y0 + t*y10;
 439     }
 440 
 441     private void drawMiter(final double pdx, final double pdy,
 442                            final double x0, final double y0,
 443                            final double dx, final double dy,
 444                            double omx, double omy, double mx, double my,
 445                            boolean rev)
 446     {
 447         if ((mx == omx && my == omy) ||
 448             (pdx == 0.0d && pdy == 0.0d) ||
 449             (dx == 0.0d && dy == 0.0d))
 450         {
 451             return;
 452         }
 453 
 454         if (rev) {
 455             omx = -omx;
 456             omy = -omy;
 457             mx  = -mx;
 458             my  = -my;
 459         }
 460 
 461         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 462                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
 463                      miter, 0);
 464 
 465         final double miterX = miter[0];
 466         final double miterY = miter[1];
 467         double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 468 
 469         // If the lines are parallel, lenSq will be either NaN or +inf
 470         // (actually, I'm not sure if the latter is possible. The important
 471         // thing is that -inf is not possible, because lenSq is a square).
 472         // For both of those values, the comparison below will fail and
 473         // no miter will be drawn, which is correct.
 474         if (lenSq < miterLimitSq) {
 475             emitLineTo(miterX, miterY, rev);
 476         }
 477     }
 478 
 479     @Override
 480     public void moveTo(final double x0, final double y0) {
 481         moveTo(x0, y0, cOutCode);
 482         // update starting point:
 483         this.sx0 = x0;
 484         this.sy0 = y0;
 485         this.sdx = 1.0d;
 486         this.sdy = 0.0d;
 487         this.opened   = false;
 488         this.capStart = false;
 489 
 490         if (clipRect != null) {
 491             final int outcode = DHelpers.outcode(x0, y0, clipRect);
 492             this.cOutCode = outcode;
 493             this.sOutCode = outcode;
 494         }
 495     }
 496 
 497     private void moveTo(final double x0, final double y0,
 498                         final int outcode)
 499     {
 500         if (prev == MOVE_TO) {
 501             this.cx0 = x0;
 502             this.cy0 = y0;
 503         } else {
 504             if (prev == DRAWING_OP_TO) {
 505                 finish(outcode);
 506             }
 507             this.prev = MOVE_TO;
 508             this.cx0 = x0;
 509             this.cy0 = y0;
 510             this.cdx = 1.0d;
 511             this.cdy = 0.0d;
 512         }
 513     }
 514 
 515     @Override
 516     public void lineTo(final double x1, final double y1) {
 517         lineTo(x1, y1, false);
 518     }
 519 
 520     private void lineTo(final double x1, final double y1,
 521                         final boolean force)
 522     {
 523         final int outcode0 = this.cOutCode;
 524         if (!force && clipRect != null) {
 525             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 526             this.cOutCode = outcode1;
 527 
 528             // basic rejection criteria
 529             if ((outcode0 & outcode1) != 0) {
 530                 moveTo(x1, y1, outcode0);
 531                 opened = true;
 532                 return;
 533             }
 534         }
 535 
 536         double dx = x1 - cx0;
 537         double dy = y1 - cy0;
 538         if (dx == 0.0d && dy == 0.0d) {
 539             dx = 1.0d;
 540         }
 541         computeOffset(dx, dy, lineWidth2, offset0);
 542         final double mx = offset0[0];
 543         final double my = offset0[1];
 544 
 545         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 546 
 547         emitLineTo(cx0 + mx, cy0 + my);
 548         emitLineTo( x1 + mx,  y1 + my);
 549 
 550         emitLineToRev(cx0 - mx, cy0 - my);
 551         emitLineToRev( x1 - mx,  y1 - my);
 552 
 553         this.prev = DRAWING_OP_TO;
 554         this.cx0 = x1;
 555         this.cy0 = y1;
 556         this.cdx = dx;
 557         this.cdy = dy;
 558         this.cmx = mx;
 559         this.cmy = my;
 560     }
 561 
 562     @Override
 563     public void closePath() {
 564         // distinguish empty path at all vs opened path ?
 565         if (prev != DRAWING_OP_TO && !opened) {
 566             if (prev == CLOSE) {
 567                 return;
 568             }
 569             emitMoveTo(cx0, cy0 - lineWidth2);
 570 
 571             this.sdx = 1.0d;
 572             this.sdy = 0.0d;
 573             this.cdx = 1.0d;
 574             this.cdy = 0.0d;
 575 
 576             this.smx = 0.0d;
 577             this.smy = -lineWidth2;
 578             this.cmx = 0.0d;
 579             this.cmy = -lineWidth2;
 580 
 581             finish(cOutCode);
 582             return;
 583         }
 584 
 585         // basic acceptance criteria
 586         if ((sOutCode & cOutCode) == 0) {
 587             if (cx0 != sx0 || cy0 != sy0) {
 588                 lineTo(sx0, sy0, true);
 589             }
 590 
 591             drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
 592 
 593             emitLineTo(sx0 + smx, sy0 + smy);
 594 
 595             if (opened) {
 596                 emitLineTo(sx0 - smx, sy0 - smy);
 597             } else {
 598                 emitMoveTo(sx0 - smx, sy0 - smy);
 599             }
 600         }
 601         // Ignore caps like finish(false)
 602         emitReverse();
 603 
 604         this.prev = CLOSE;
 605 
 606         if (opened) {
 607             // do not emit close
 608             opened = false;
 609         } else {
 610             emitClose();
 611         }
 612     }
 613 
 614     private void emitReverse() {
 615         reverse.popAll(out);
 616     }
 617 
 618     @Override
 619     public void pathDone() {
 620         if (prev == DRAWING_OP_TO) {
 621             finish(cOutCode);
 622         }
 623 
 624         out.pathDone();
 625 
 626         // this shouldn't matter since this object won't be used
 627         // after the call to this method.
 628         this.prev = CLOSE;
 629 
 630         // Dispose this instance:
 631         dispose();
 632     }
 633 
 634     private void finish(final int outcode) {
 635         // Problem: impossible to guess if the path will be closed in advance
 636         //          i.e. if caps must be drawn or not ?
 637         // Solution: use the ClosedPathDetector before Stroker to determine
 638         // if the path is a closed path or not
 639         if (!rdrCtx.closedPath) {
 640             if (outcode == 0) {
 641                 // current point = end's cap:
 642                 if (capStyle == CAP_ROUND) {
 643                     drawRoundCap(cx0, cy0, cmx, cmy);
 644                 } else if (capStyle == CAP_SQUARE) {
 645                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
 646                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
 647                 }
 648             }
 649             emitReverse();
 650 
 651             if (!capStart) {
 652                 capStart = true;
 653 
 654                 if (sOutCode == 0) {
 655                     // starting point = initial cap:
 656                     if (capStyle == CAP_ROUND) {
 657                         drawRoundCap(sx0, sy0, -smx, -smy);
 658                     } else if (capStyle == CAP_SQUARE) {
 659                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
 660                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
 661                     }
 662                 }
 663             }
 664         } else {
 665             emitReverse();
 666         }
 667         emitClose();
 668     }
 669 
 670     private void emitMoveTo(final double x0, final double y0) {
 671         out.moveTo(x0, y0);
 672     }
 673 
 674     private void emitLineTo(final double x1, final double y1) {
 675         out.lineTo(x1, y1);
 676     }
 677 
 678     private void emitLineToRev(final double x1, final double y1) {
 679         reverse.pushLine(x1, y1);
 680     }
 681 
 682     private void emitLineTo(final double x1, final double y1,
 683                             final boolean rev)
 684     {
 685         if (rev) {
 686             emitLineToRev(x1, y1);
 687         } else {
 688             emitLineTo(x1, y1);
 689         }
 690     }
 691 
 692     private void emitQuadTo(final double x1, final double y1,
 693                             final double x2, final double y2)
 694     {
 695         out.quadTo(x1, y1, x2, y2);
 696     }
 697 
 698     private void emitQuadToRev(final double x0, final double y0,
 699                                final double x1, final double y1)
 700     {
 701         reverse.pushQuad(x0, y0, x1, y1);
 702     }
 703 
 704     private void emitCurveTo(final double x1, final double y1,
 705                              final double x2, final double y2,
 706                              final double x3, final double y3)
 707     {
 708         out.curveTo(x1, y1, x2, y2, x3, y3);
 709     }
 710 
 711     private void emitCurveToRev(final double x0, final double y0,
 712                                 final double x1, final double y1,
 713                                 final double x2, final double y2)
 714     {
 715         reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 716     }
 717 
 718     private void emitCurveTo(final double x0, final double y0,
 719                              final double x1, final double y1,
 720                              final double x2, final double y2,
 721                              final double x3, final double y3, final boolean rev)
 722     {
 723         if (rev) {
 724             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 725         } else {
 726             out.curveTo(x1, y1, x2, y2, x3, y3);
 727         }
 728     }
 729 
 730     private void emitClose() {
 731         out.closePath();
 732     }
 733 
 734     private void drawJoin(double pdx, double pdy,
 735                           double x0, double y0,
 736                           double dx, double dy,
 737                           double omx, double omy,
 738                           double mx, double my,
 739                           final int outcode)
 740     {
 741         if (prev != DRAWING_OP_TO) {
 742             emitMoveTo(x0 + mx, y0 + my);
 743             if (!opened) {
 744                 this.sdx = dx;
 745                 this.sdy = dy;
 746                 this.smx = mx;
 747                 this.smy = my;
 748             }
 749         } else {
 750             final boolean cw = isCW(pdx, pdy, dx, dy);
 751             if (outcode == 0) {
 752                 if (joinStyle == JOIN_MITER) {
 753                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 754                 } else if (joinStyle == JOIN_ROUND) {
 755                     drawRoundJoin(x0, y0,
 756                                   omx, omy,
 757                                   mx, my, cw,
 758                                   ROUND_JOIN_THRESHOLD);
 759                 }
 760             }
 761             emitLineTo(x0, y0, !cw);
 762         }
 763         prev = DRAWING_OP_TO;
 764     }
 765 
 766     private static boolean within(final double x1, final double y1,
 767                                   final double x2, final double y2,
 768                                   final double ERR)
 769     {
 770         assert ERR > 0 : "";
 771         // compare taxicab distance. ERR will always be small, so using
 772         // true distance won't give much benefit
 773         return (DHelpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 774                 DHelpers.within(y1, y2, ERR)); // this is just as good.
 775     }
 776 
 777     private void getLineOffsets(double x1, double y1,
 778                                 double x2, double y2,
 779                                 double[] left, double[] right) {
 780         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 781         final double mx = offset0[0];
 782         final double my = offset0[1];
 783         left[0] = x1 + mx;
 784         left[1] = y1 + my;
 785         left[2] = x2 + mx;
 786         left[3] = y2 + my;
 787         right[0] = x1 - mx;
 788         right[1] = y1 - my;
 789         right[2] = x2 - mx;
 790         right[3] = y2 - my;
 791     }
 792 
 793     private int computeOffsetCubic(double[] pts, final int off,
 794                                    double[] leftOff, double[] rightOff)
 795     {
 796         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 797         // vanishes, which creates problems with computeOffset. Usually
 798         // this happens when this stroker object is trying to widen
 799         // a curve with a cusp. What happens is that curveTo splits
 800         // the input curve at the cusp, and passes it to this function.
 801         // because of inaccuracies in the splitting, we consider points
 802         // equal if they're very close to each other.
 803         final double x1 = pts[off + 0], y1 = pts[off + 1];
 804         final double x2 = pts[off + 2], y2 = pts[off + 3];
 805         final double x3 = pts[off + 4], y3 = pts[off + 5];
 806         final double x4 = pts[off + 6], y4 = pts[off + 7];
 807 
 808         double dx4 = x4 - x3;
 809         double dy4 = y4 - y3;
 810         double dx1 = x2 - x1;
 811         double dy1 = y2 - y1;
 812 
 813         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 814         // in which case ignore if p1 == p2
 815         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
 816         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));
 817         if (p1eqp2 && p3eqp4) {
 818             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 819             return 4;
 820         } else if (p1eqp2) {
 821             dx1 = x3 - x1;
 822             dy1 = y3 - y1;
 823         } else if (p3eqp4) {
 824             dx4 = x4 - x2;
 825             dy4 = y4 - y2;
 826         }
 827 
 828         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 829         double dotsq = (dx1 * dx4 + dy1 * dy4);
 830         dotsq *= dotsq;
 831         double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
 832         if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
 833             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 834             return 4;
 835         }
 836 
 837 //      What we're trying to do in this function is to approximate an ideal
 838 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 839 //      The constraints I use to get the equations are:
 840 //
 841 //      1. The computed curve Bp should go through I(0) and I(1). These are
 842 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 843 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 844 //
 845 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 846 //      (by the way, the operator || in the comments below means "aligned with".
 847 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 848 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 849 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 850 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 851 //      nonzero constant.)
 852 //      I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and
 853 //      I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1).
 854 //      We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same
 855 //      is true for any bezier curve; therefore, we get the equations
 856 //          (1) p2p = c1 * (p2-p1) + p1p
 857 //          (2) p3p = c2 * (p4-p3) + p4p
 858 //      We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number
 859 //      of unknowns from 4 to 2 (i.e. just c1 and c2).
 860 //      To eliminate these 2 unknowns we use the following constraint:
 861 //
 862 //      3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note
 863 //      that I(0.5) is *the only* reason for computing dxm,dym. This gives us
 864 //          (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to
 865 //          (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3
 866 //      We can substitute (1) and (2) from above into (4) and we get:
 867 //          (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p
 868 //      which is equivalent to
 869 //          (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p)
 870 //
 871 //      The right side of this is a 2D vector, and we know I(0.5), which gives us
 872 //      Bp(0.5), which gives us the value of the right side.
 873 //      The left side is just a matrix vector multiplication in disguise. It is
 874 //
 875 //      [x2-x1, x4-x3][c1]
 876 //      [y2-y1, y4-y3][c2]
 877 //      which, is equal to
 878 //      [dx1, dx4][c1]
 879 //      [dy1, dy4][c2]
 880 //      At this point we are left with a simple linear system and we solve it by
 881 //      getting the inverse of the matrix above. Then we use [c1,c2] to compute
 882 //      p2p and p3p.
 883 
 884         double x = (x1 + 3.0d * (x2 + x3) + x4) / 8.0d;
 885         double y = (y1 + 3.0d * (y2 + y3) + y4) / 8.0d;
 886         // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to
 887         // c*B'(0.5) for some constant c.
 888         double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2;
 889 
 890         // this computes the offsets at t=0, 0.5, 1, using the property that
 891         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
 892         // the (dx/dt, dy/dt) vectors at the endpoints.
 893         computeOffset(dx1, dy1, lineWidth2, offset0);
 894         computeOffset(dxm, dym, lineWidth2, offset1);
 895         computeOffset(dx4, dy4, lineWidth2, offset2);
 896         double x1p = x1 + offset0[0]; // start
 897         double y1p = y1 + offset0[1]; // point
 898         double xi  = x  + offset1[0]; // interpolation
 899         double yi  = y  + offset1[1]; // point
 900         double x4p = x4 + offset2[0]; // end
 901         double y4p = y4 + offset2[1]; // point
 902 
 903         double invdet43 = 4.0d / (3.0d * (dx1 * dy4 - dy1 * dx4));
 904 
 905         double two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
 906         double two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
 907         double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 908         double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 909 
 910         double x2p, y2p, x3p, y3p;
 911         x2p = x1p + c1*dx1;
 912         y2p = y1p + c1*dy1;
 913         x3p = x4p + c2*dx4;
 914         y3p = y4p + c2*dy4;
 915 
 916         leftOff[0] = x1p; leftOff[1] = y1p;
 917         leftOff[2] = x2p; leftOff[3] = y2p;
 918         leftOff[4] = x3p; leftOff[5] = y3p;
 919         leftOff[6] = x4p; leftOff[7] = y4p;
 920 
 921         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
 922         xi = xi - 2.0d * offset1[0]; yi = yi - 2.0d * offset1[1];
 923         x4p = x4 - offset2[0]; y4p = y4 - offset2[1];
 924 
 925         two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
 926         two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
 927         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 928         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 929 
 930         x2p = x1p + c1*dx1;
 931         y2p = y1p + c1*dy1;
 932         x3p = x4p + c2*dx4;
 933         y3p = y4p + c2*dy4;
 934 
 935         rightOff[0] = x1p; rightOff[1] = y1p;
 936         rightOff[2] = x2p; rightOff[3] = y2p;
 937         rightOff[4] = x3p; rightOff[5] = y3p;
 938         rightOff[6] = x4p; rightOff[7] = y4p;
 939         return 8;
 940     }
 941 
 942     // compute offset curves using bezier spline through t=0.5 (i.e.
 943     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 944     // return the kind of curve in the right and left arrays.
 945     private int computeOffsetQuad(double[] pts, final int off,
 946                                   double[] leftOff, double[] rightOff)
 947     {
 948         final double x1 = pts[off + 0], y1 = pts[off + 1];
 949         final double x2 = pts[off + 2], y2 = pts[off + 3];
 950         final double x3 = pts[off + 4], y3 = pts[off + 5];
 951 
 952         final double dx3 = x3 - x2;
 953         final double dy3 = y3 - y2;
 954         final double dx1 = x2 - x1;
 955         final double dy1 = y2 - y1;
 956 
 957         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 958         // vanishes, which creates problems with computeOffset. Usually
 959         // this happens when this stroker object is trying to widen
 960         // a curve with a cusp. What happens is that curveTo splits
 961         // the input curve at the cusp, and passes it to this function.
 962         // because of inaccuracies in the splitting, we consider points
 963         // equal if they're very close to each other.
 964 
 965         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 966         // in which case ignore.
 967         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
 968         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));
 969         if (p1eqp2 || p2eqp3) {
 970             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 971             return 4;
 972         }
 973 
 974         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 975         double dotsq = (dx1 * dx3 + dy1 * dy3);
 976         dotsq *= dotsq;
 977         double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
 978         if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
 979             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 980             return 4;
 981         }
 982 
 983         // this computes the offsets at t=0, 0.5, 1, using the property that
 984         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
 985         // the (dx/dt, dy/dt) vectors at the endpoints.
 986         computeOffset(dx1, dy1, lineWidth2, offset0);
 987         computeOffset(dx3, dy3, lineWidth2, offset1);
 988 
 989         double x1p = x1 + offset0[0]; // start
 990         double y1p = y1 + offset0[1]; // point
 991         double x3p = x3 + offset1[0]; // end
 992         double y3p = y3 + offset1[1]; // point
 993         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
 994         leftOff[0] = x1p; leftOff[1] = y1p;
 995         leftOff[4] = x3p; leftOff[5] = y3p;
 996 
 997         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
 998         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
 999         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
1000         rightOff[0] = x1p; rightOff[1] = y1p;
1001         rightOff[4] = x3p; rightOff[5] = y3p;
1002         return 6;
1003     }
1004 
1005     // finds values of t where the curve in pts should be subdivided in order
1006     // to get good offset curves a distance of w away from the middle curve.
1007     // Stores the points in ts, and returns how many of them there were.
1008     private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts,
1009                                         final int type, final double w)
1010     {
1011         final double x12 = pts[2] - pts[0];
1012         final double y12 = pts[3] - pts[1];
1013         // if the curve is already parallel to either axis we gain nothing
1014         // from rotating it.
1015         if (y12 != 0.0d && x12 != 0.0d) {
1016             // we rotate it so that the first vector in the control polygon is
1017             // parallel to the x-axis. This will ensure that rotated quarter
1018             // circles won't be subdivided.
1019             final double hypot = Math.sqrt(x12 * x12 + y12 * y12);
1020             final double cos = x12 / hypot;
1021             final double sin = y12 / hypot;
1022             final double x1 = cos * pts[0] + sin * pts[1];
1023             final double y1 = cos * pts[1] - sin * pts[0];
1024             final double x2 = cos * pts[2] + sin * pts[3];
1025             final double y2 = cos * pts[3] - sin * pts[2];
1026             final double x3 = cos * pts[4] + sin * pts[5];
1027             final double y3 = cos * pts[5] - sin * pts[4];
1028 
1029             switch(type) {
1030             case 8:
1031                 final double x4 = cos * pts[6] + sin * pts[7];
1032                 final double y4 = cos * pts[7] - sin * pts[6];
1033                 c.set(x1, y1, x2, y2, x3, y3, x4, y4);
1034                 break;
1035             case 6:
1036                 c.set(x1, y1, x2, y2, x3, y3);
1037                 break;
1038             default:
1039             }
1040         } else {
1041             c.set(pts, type);
1042         }
1043 
1044         int ret = 0;
1045         // we subdivide at values of t such that the remaining rotated
1046         // curves are monotonic in x and y.
1047         ret += c.dxRoots(ts, ret);
1048         ret += c.dyRoots(ts, ret);
1049         // subdivide at inflection points.
1050         if (type == 8) {
1051             // quadratic curves can't have inflection points
1052             ret += c.infPoints(ts, ret);
1053         }
1054 
1055         // now we must subdivide at points where one of the offset curves will have
1056         // a cusp. This happens at ts where the radius of curvature is equal to w.
1057         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
1058 
1059         ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
1060         DHelpers.isort(ts, 0, ret);
1061         return ret;
1062     }
1063 
1064     @Override
1065     public void curveTo(final double x1, final double y1,
1066                         final double x2, final double y2,
1067                         final double x3, final double y3)
1068     {
1069         final int outcode0 = this.cOutCode;
1070         if (clipRect != null) {
1071             final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
1072             this.cOutCode = outcode3;
1073 
1074             if ((outcode0 & outcode3) != 0) {
1075                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1076                 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1077 
1078                 // basic rejection criteria
1079                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1080                     moveTo(x3, y3, outcode0);
1081                     opened = true;
1082                     return;
1083                 }
1084             }
1085         }
1086 
1087         final double[] mid = middle;
1088 
1089         mid[0] = cx0; mid[1] = cy0;
1090         mid[2] = x1;  mid[3] = y1;
1091         mid[4] = x2;  mid[5] = y2;
1092         mid[6] = x3;  mid[7] = y3;
1093 
1094         // need these so we can update the state at the end of this method
1095         final double xf = x3, yf = y3;
1096         double dxs = mid[2] - mid[0];
1097         double dys = mid[3] - mid[1];
1098         double dxf = mid[6] - mid[4];
1099         double dyf = mid[7] - mid[5];
1100 
1101         boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
1102         boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
1103         if (p1eqp2) {
1104             dxs = mid[4] - mid[0];
1105             dys = mid[5] - mid[1];
1106             if (dxs == 0.0d && dys == 0.0d) {
1107                 dxs = mid[6] - mid[0];
1108                 dys = mid[7] - mid[1];
1109             }
1110         }
1111         if (p3eqp4) {
1112             dxf = mid[6] - mid[2];
1113             dyf = mid[7] - mid[3];
1114             if (dxf == 0.0d && dyf == 0.0d) {
1115                 dxf = mid[6] - mid[0];
1116                 dyf = mid[7] - mid[1];
1117             }
1118         }
1119         if (dxs == 0.0d && dys == 0.0d) {
1120             // this happens if the "curve" is just a point
1121             // fix outcode0 for lineTo() call:
1122             if (clipRect != null) {
1123                 this.cOutCode = outcode0;
1124             }
1125             lineTo(mid[0], mid[1]);
1126             return;
1127         }
1128 
1129         // if these vectors are too small, normalize them, to avoid future
1130         // precision problems.
1131         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1132             double len = Math.sqrt(dxs*dxs + dys*dys);
1133             dxs /= len;
1134             dys /= len;
1135         }
1136         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1137             double len = Math.sqrt(dxf*dxf + dyf*dyf);
1138             dxf /= len;
1139             dyf /= len;
1140         }
1141 
1142         computeOffset(dxs, dys, lineWidth2, offset0);
1143         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1144 
1145         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1146 
1147         double prevT = 0.0d;
1148         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1149             final double t = subdivTs[i];
1150             DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1151                                      mid, off, mid, off, mid, off + 6);
1152             prevT = t;
1153         }
1154 
1155         final double[] l = lp;
1156         final double[] r = rp;
1157 
1158         int kind = 0;
1159         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1160             kind = computeOffsetCubic(mid, off, l, r);
1161 
1162             emitLineTo(l[0], l[1]);
1163 
1164             switch(kind) {
1165             case 8:
1166                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1167                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1168                 break;
1169             case 4:
1170                 emitLineTo(l[2], l[3]);
1171                 emitLineToRev(r[0], r[1]);
1172                 break;
1173             default:
1174             }
1175             emitLineToRev(r[kind - 2], r[kind - 1]);
1176         }
1177 
1178         this.prev = DRAWING_OP_TO;
1179         this.cx0 = xf;
1180         this.cy0 = yf;
1181         this.cdx = dxf;
1182         this.cdy = dyf;
1183         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1184         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1185     }
1186 
1187     @Override
1188     public void quadTo(final double x1, final double y1,
1189                        final double x2, final double y2)
1190     {
1191         final int outcode0 = this.cOutCode;
1192         if (clipRect != null) {
1193             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1194             this.cOutCode = outcode2;
1195 
1196             if ((outcode0 & outcode2) != 0) {
1197                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1198 
1199                 // basic rejection criteria
1200                 if ((outcode0 & outcode1 & outcode2) != 0) {
1201                     moveTo(x2, y2, outcode0);
1202                     opened = true;
1203                     return;
1204                 }
1205             }
1206         }
1207 
1208         final double[] mid = middle;
1209 
1210         mid[0] = cx0; mid[1] = cy0;
1211         mid[2] = x1;  mid[3] = y1;
1212         mid[4] = x2;  mid[5] = y2;
1213 
1214         // need these so we can update the state at the end of this method
1215         final double xf = x2, yf = y2;
1216         double dxs = mid[2] - mid[0];
1217         double dys = mid[3] - mid[1];
1218         double dxf = mid[4] - mid[2];
1219         double dyf = mid[5] - mid[3];
1220         if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1221             dxs = dxf = mid[4] - mid[0];
1222             dys = dyf = mid[5] - mid[1];
1223         }
1224         if (dxs == 0.0d && dys == 0.0d) {
1225             // this happens if the "curve" is just a point
1226             // fix outcode0 for lineTo() call:
1227             if (clipRect != null) {
1228                 this.cOutCode = outcode0;
1229             }
1230             lineTo(mid[0], mid[1]);
1231             return;
1232         }
1233         // if these vectors are too small, normalize them, to avoid future
1234         // precision problems.
1235         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1236             double len = Math.sqrt(dxs*dxs + dys*dys);
1237             dxs /= len;
1238             dys /= len;
1239         }
1240         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1241             double len = Math.sqrt(dxf*dxf + dyf*dyf);
1242             dxf /= len;
1243             dyf /= len;
1244         }
1245 
1246         computeOffset(dxs, dys, lineWidth2, offset0);
1247         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1248 
1249         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1250 
1251         double prevt = 0.0d;
1252         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1253             final double t = subdivTs[i];
1254             DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1255                                     mid, off, mid, off, mid, off + 4);
1256             prevt = t;
1257         }
1258 
1259         final double[] l = lp;
1260         final double[] r = rp;
1261 
1262         int kind = 0;
1263         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1264             kind = computeOffsetQuad(mid, off, l, r);
1265 
1266             emitLineTo(l[0], l[1]);
1267 
1268             switch(kind) {
1269             case 6:
1270                 emitQuadTo(l[2], l[3], l[4], l[5]);
1271                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1272                 break;
1273             case 4:
1274                 emitLineTo(l[2], l[3]);
1275                 emitLineToRev(r[0], r[1]);
1276                 break;
1277             default:
1278             }
1279             emitLineToRev(r[kind - 2], r[kind - 1]);
1280         }
1281 
1282         this.prev = DRAWING_OP_TO;
1283         this.cx0 = xf;
1284         this.cy0 = yf;
1285         this.cdx = dxf;
1286         this.cdy = dyf;
1287         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1288         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1289     }
1290 
1291     @Override public long getNativeConsumer() {
1292         throw new InternalError("Stroker doesn't use a native consumer");
1293     }
1294 }