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