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