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