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