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