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