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