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