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