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