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