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 com.sun.marlin;
  27 
  28 import java.util.Arrays;
  29 
  30 import com.sun.javafx.geom.PathConsumer2D;
  31 import com.sun.marlin.Helpers.PolyStack;
  32 import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
  33 import com.sun.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 public 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     public 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     public 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                     // ovelap 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 
 640         if (opened) {
 641             // do not emit close
 642             opened = false;
 643         } else {
 644             emitClose();
 645         }
 646     }
 647 
 648     private void emitReverse() {
 649         reverse.popAll(out);
 650     }
 651 
 652     @Override
 653     public void pathDone() {
 654         if (prev == DRAWING_OP_TO) {
 655             finish(cOutCode);
 656         }
 657 
 658         out.pathDone();
 659 
 660         // this shouldn't matter since this object won't be used
 661         // after the call to this method.
 662         this.prev = CLOSE;
 663 
 664         // Dispose this instance:
 665         dispose();
 666     }
 667 
 668     private void finish(final int outcode) {
 669         // Problem: impossible to guess if the path will be closed in advance
 670         //          i.e. if caps must be drawn or not ?
 671         // Solution: use the ClosedPathDetector before Stroker to determine
 672         // if the path is a closed path or not
 673         if (!rdrCtx.closedPath) {
 674             if (outcode == 0) {
 675                 // current point = end's cap:
 676                 if (capStyle == CAP_ROUND) {
 677                     drawRoundCap(cx0, cy0, cmx, cmy);
 678                 } else if (capStyle == CAP_SQUARE) {
 679                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
 680                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
 681                 }
 682             }
 683             emitReverse();
 684 
 685             if (!capStart) {
 686                 capStart = true;
 687 
 688                 if (sOutCode == 0) {
 689                     // starting point = initial cap:
 690                     if (capStyle == CAP_ROUND) {
 691                         drawRoundCap(sx0, sy0, -smx, -smy);
 692                     } else if (capStyle == CAP_SQUARE) {
 693                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
 694                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
 695                     }
 696                 }
 697             }
 698         } else {
 699             emitReverse();
 700         }
 701         emitClose();
 702     }
 703 
 704     private void emitMoveTo(final float x0, final float y0) {
 705         out.moveTo(x0, y0);
 706     }
 707 
 708     private void emitLineTo(final float x1, final float y1) {
 709         out.lineTo(x1, y1);
 710     }
 711 
 712     private void emitLineToRev(final float x1, final float y1) {
 713         reverse.pushLine(x1, y1);
 714     }
 715 
 716     private void emitLineTo(final float x1, final float y1,
 717                             final boolean rev)
 718     {
 719         if (rev) {
 720             emitLineToRev(x1, y1);
 721         } else {
 722             emitLineTo(x1, y1);
 723         }
 724     }
 725 
 726     private void emitQuadTo(final float x1, final float y1,
 727                             final float x2, final float y2)
 728     {
 729         out.quadTo(x1, y1, x2, y2);
 730     }
 731 
 732     private void emitQuadToRev(final float x0, final float y0,
 733                                final float x1, final float y1)
 734     {
 735         reverse.pushQuad(x0, y0, x1, y1);
 736     }
 737 
 738     private void emitCurveTo(final float x1, final float y1,
 739                              final float x2, final float y2,
 740                              final float x3, final float y3)
 741     {
 742         out.curveTo(x1, y1, x2, y2, x3, y3);
 743     }
 744 
 745     private void emitCurveToRev(final float x0, final float y0,
 746                                 final float x1, final float y1,
 747                                 final float x2, final float y2)
 748     {
 749         reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 750     }
 751 
 752     private void emitCurveTo(final float x0, final float y0,
 753                              final float x1, final float y1,
 754                              final float x2, final float y2,
 755                              final float x3, final float y3, final boolean rev)
 756     {
 757         if (rev) {
 758             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 759         } else {
 760             out.curveTo(x1, y1, x2, y2, x3, y3);
 761         }
 762     }
 763 
 764     private void emitClose() {
 765         out.closePath();
 766     }
 767 
 768     private void drawJoin(float pdx, float pdy,
 769                           float x0, float y0,
 770                           float dx, float dy,
 771                           float omx, float omy,
 772                           float mx, float my,
 773                           final int outcode)
 774     {
 775         if (prev != DRAWING_OP_TO) {
 776             emitMoveTo(x0 + mx, y0 + my);
 777             if (!opened) {
 778                 this.sdx = dx;
 779                 this.sdy = dy;
 780                 this.smx = mx;
 781                 this.smy = my;
 782             }
 783         } else {
 784             final boolean cw = isCW(pdx, pdy, dx, dy);
 785             if (outcode == 0) {
 786                 if (joinStyle == JOIN_MITER) {
 787                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 788                 } else if (joinStyle == JOIN_ROUND) {
 789                     mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);
 790                 }
 791             }
 792             emitLineTo(x0, y0, !cw);
 793         }
 794         prev = DRAWING_OP_TO;
 795     }
 796 
 797     private static boolean within(final float x1, final float y1,
 798                                   final float x2, final float y2,
 799                                   final float err)
 800     {
 801         assert err > 0 : "";
 802         // compare taxicab distance. ERR will always be small, so using
 803         // true distance won't give much benefit
 804         return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs
 805                 Helpers.within(y1, y2, err));  // this is just as good.
 806     }
 807 
 808     private void getLineOffsets(final float x1, final float y1,
 809                                 final float x2, final float y2,
 810                                 final float[] left, final float[] right)
 811     {
 812         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 813         final float mx = offset0[0];
 814         final float my = offset0[1];
 815         left[0] = x1 + mx;
 816         left[1] = y1 + my;
 817         left[2] = x2 + mx;
 818         left[3] = y2 + my;
 819 
 820         right[0] = x1 - mx;
 821         right[1] = y1 - my;
 822         right[2] = x2 - mx;
 823         right[3] = y2 - my;
 824     }
 825 
 826     private int computeOffsetCubic(final float[] pts, final int off,
 827                                    final float[] leftOff,
 828                                    final float[] rightOff)
 829     {
 830         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 831         // vanishes, which creates problems with computeOffset. Usually
 832         // this happens when this stroker object is trying to widen
 833         // a curve with a cusp. What happens is that curveTo splits
 834         // the input curve at the cusp, and passes it to this function.
 835         // because of inaccuracies in the splitting, we consider points
 836         // equal if they're very close to each other.
 837         final float x1 = pts[off    ], y1 = pts[off + 1];
 838         final float x2 = pts[off + 2], y2 = pts[off + 3];
 839         final float x3 = pts[off + 4], y3 = pts[off + 5];
 840         final float x4 = pts[off + 6], y4 = pts[off + 7];
 841 
 842         float dx4 = x4 - x3;
 843         float dy4 = y4 - y3;
 844         float dx1 = x2 - x1;
 845         float dy1 = y2 - y1;
 846 
 847         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 848         // in which case ignore if p1 == p2
 849         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
 850         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4));
 851 
 852         if (p1eqp2 && p3eqp4) {
 853             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 854             return 4;
 855         } else if (p1eqp2) {
 856             dx1 = x3 - x1;
 857             dy1 = y3 - y1;
 858         } else if (p3eqp4) {
 859             dx4 = x4 - x2;
 860             dy4 = y4 - y2;
 861         }
 862 
 863         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 864         float dotsq = (dx1 * dx4 + dy1 * dy4);
 865         dotsq *= dotsq;
 866         float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
 867 
 868         if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) {
 869             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 870             return 4;
 871         }
 872 
 873 //      What we're trying to do in this function is to approximate an ideal
 874 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 875 //      The constraints I use to get the equations are:
 876 //
 877 //      1. The computed curve Bp should go through I(0) and I(1). These are
 878 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 879 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 880 //
 881 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 882 //      (by the way, the operator || in the comments below means "aligned with".
 883 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 884 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 885 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 886 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 887 //      nonzero constant.)
 888 //      I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and
 889 //      I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1).
 890 //      We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same
 891 //      is true for any bezier curve; therefore, we get the equations
 892 //          (1) p2p = c1 * (p2-p1) + p1p
 893 //          (2) p3p = c2 * (p4-p3) + p4p
 894 //      We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number
 895 //      of unknowns from 4 to 2 (i.e. just c1 and c2).
 896 //      To eliminate these 2 unknowns we use the following constraint:
 897 //
 898 //      3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note
 899 //      that I(0.5) is *the only* reason for computing dxm,dym. This gives us
 900 //          (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to
 901 //          (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3
 902 //      We can substitute (1) and (2) from above into (4) and we get:
 903 //          (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p
 904 //      which is equivalent to
 905 //          (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p)
 906 //
 907 //      The right side of this is a 2D vector, and we know I(0.5), which gives us
 908 //      Bp(0.5), which gives us the value of the right side.
 909 //      The left side is just a matrix vector multiplication in disguise. It is
 910 //
 911 //      [x2-x1, x4-x3][c1]
 912 //      [y2-y1, y4-y3][c2]
 913 //      which, is equal to
 914 //      [dx1, dx4][c1]
 915 //      [dy1, dy4][c2]
 916 //      At this point we are left with a simple linear system and we solve it by
 917 //      getting the inverse of the matrix above. Then we use [c1,c2] to compute
 918 //      p2p and p3p.
 919 
 920         float x = (x1 + 3.0f * (x2 + x3) + x4) / 8.0f;
 921         float y = (y1 + 3.0f * (y2 + y3) + y4) / 8.0f;
 922         // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to
 923         // c*B'(0.5) for some constant c.
 924         float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2;
 925 
 926         // this computes the offsets at t=0, 0.5, 1, using the property that
 927         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
 928         // the (dx/dt, dy/dt) vectors at the endpoints.
 929         computeOffset(dx1, dy1, lineWidth2, offset0);
 930         computeOffset(dxm, dym, lineWidth2, offset1);
 931         computeOffset(dx4, dy4, lineWidth2, offset2);
 932         float x1p = x1 + offset0[0]; // start
 933         float y1p = y1 + offset0[1]; // point
 934         float xi  = x  + offset1[0]; // interpolation
 935         float yi  = y  + offset1[1]; // point
 936         float x4p = x4 + offset2[0]; // end
 937         float y4p = y4 + offset2[1]; // point
 938 
 939         float invdet43 = 4.0f / (3.0f * (dx1 * dy4 - dy1 * dx4));
 940 
 941         float two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p;
 942         float two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p;
 943         float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 944         float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 945 
 946         float x2p, y2p, x3p, y3p;
 947         x2p = x1p + c1*dx1;
 948         y2p = y1p + c1*dy1;
 949         x3p = x4p + c2*dx4;
 950         y3p = y4p + c2*dy4;
 951 
 952         leftOff[0] = x1p; leftOff[1] = y1p;
 953         leftOff[2] = x2p; leftOff[3] = y2p;
 954         leftOff[4] = x3p; leftOff[5] = y3p;
 955         leftOff[6] = x4p; leftOff[7] = y4p;
 956 
 957         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
 958         xi = xi - 2.0f * offset1[0]; yi = yi - 2.0f * offset1[1];
 959         x4p = x4 - offset2[0]; y4p = y4 - offset2[1];
 960 
 961         two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p;
 962         two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p;
 963         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 964         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 965 
 966         x2p = x1p + c1*dx1;
 967         y2p = y1p + c1*dy1;
 968         x3p = x4p + c2*dx4;
 969         y3p = y4p + c2*dy4;
 970 
 971         rightOff[0] = x1p; rightOff[1] = y1p;
 972         rightOff[2] = x2p; rightOff[3] = y2p;
 973         rightOff[4] = x3p; rightOff[5] = y3p;
 974         rightOff[6] = x4p; rightOff[7] = y4p;
 975         return 8;
 976     }
 977 
 978     // compute offset curves using bezier spline through t=0.5 (i.e.
 979     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 980     // return the kind of curve in the right and left arrays.
 981     private int computeOffsetQuad(final float[] pts, final int off,
 982                                   final float[] leftOff,
 983                                   final float[] rightOff)
 984     {
 985         final float x1 = pts[off    ], y1 = pts[off + 1];
 986         final float x2 = pts[off + 2], y2 = pts[off + 3];
 987         final float x3 = pts[off + 4], y3 = pts[off + 5];
 988 
 989         final float dx3 = x3 - x2;
 990         final float dy3 = y3 - y2;
 991         final float dx1 = x2 - x1;
 992         final float dy1 = y2 - y1;
 993 
 994         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 995         // vanishes, which creates problems with computeOffset. Usually
 996         // this happens when this stroker object is trying to widen
 997         // a curve with a cusp. What happens is that curveTo splits
 998         // the input curve at the cusp, and passes it to this function.
 999         // because of inaccuracies in the splitting, we consider points
1000         // equal if they're very close to each other.
1001 
1002         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
1003         // in which case ignore.
1004         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
1005         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3));
1006 
1007         if (p1eqp2 || p2eqp3) {
1008             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1009             return 4;
1010         }
1011 
1012         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
1013         float dotsq = (dx1 * dx3 + dy1 * dy3);
1014         dotsq *= dotsq;
1015         float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
1016 
1017         if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) {
1018             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1019             return 4;
1020         }
1021 
1022         // this computes the offsets at t=0, 0.5, 1, using the property that
1023         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
1024         // the (dx/dt, dy/dt) vectors at the endpoints.
1025         computeOffset(dx1, dy1, lineWidth2, offset0);
1026         computeOffset(dx3, dy3, lineWidth2, offset1);
1027 
1028         float x1p = x1 + offset0[0]; // start
1029         float y1p = y1 + offset0[1]; // point
1030         float x3p = x3 + offset1[0]; // end
1031         float y3p = y3 + offset1[1]; // point
1032         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
1033         leftOff[0] = x1p; leftOff[1] = y1p;
1034         leftOff[4] = x3p; leftOff[5] = y3p;
1035 
1036         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1037         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1038         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
1039         rightOff[0] = x1p; rightOff[1] = y1p;
1040         rightOff[4] = x3p; rightOff[5] = y3p;
1041         return 6;
1042     }
1043 
1044     @Override
1045     public void curveTo(final float x1, final float y1,
1046                         final float x2, final float y2,
1047                         final float x3, final float y3)
1048     {
1049         final int outcode0 = this.cOutCode;
1050 
1051         if (clipRect != null) {
1052             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1053             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1054             final int outcode3 = Helpers.outcode(x3, y3, clipRect);
1055 
1056             // Should clip
1057             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
1058             if (orCode != 0) {
1059                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
1060 
1061                 // basic rejection criteria:
1062                 if (sideCode == 0) {
1063                     // ovelap clip:
1064                     if (subdivide) {
1065                         // avoid reentrance
1066                         subdivide = false;
1067                         // subdivide curve => callback with subdivided parts:
1068                         boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
1069                                                                x2, y2, x3, y3,
1070                                                                orCode, this);
1071                         // reentrance is done:
1072                         subdivide = true;
1073                         if (ret) {
1074                             return;
1075                         }
1076                     }
1077                     // already subdivided so render it
1078                 } else {
1079                     this.cOutCode = outcode3;
1080                     _moveTo(x3, y3, outcode0);
1081                     opened = true;
1082                     return;
1083                 }
1084             }
1085 
1086             this.cOutCode = outcode3;
1087         }
1088         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
1089     }
1090 
1091     private void _curveTo(final float x1, final float y1,
1092                           final float x2, final float y2,
1093                           final float x3, final float y3,
1094                           final int outcode0)
1095     {
1096         // need these so we can update the state at the end of this method
1097         float dxs = x1 - cx0;
1098         float dys = y1 - cy0;
1099         float dxf = x3 - x2;
1100         float dyf = y3 - y2;
1101 
1102         if ((dxs == 0.0f) && (dys == 0.0f)) {
1103             dxs = x2 - cx0;
1104             dys = y2 - cy0;
1105             if ((dxs == 0.0f) && (dys == 0.0f)) {
1106                 dxs = x3 - cx0;
1107                 dys = y3 - cy0;
1108             }
1109         }
1110         if ((dxf == 0.0f) && (dyf == 0.0f)) {
1111             dxf = x3 - x1;
1112             dyf = y3 - y1;
1113             if ((dxf == 0.0f) && (dyf == 0.0f)) {
1114                 dxf = x3 - cx0;
1115                 dyf = y3 - cy0;
1116             }
1117         }
1118         if ((dxs == 0.0f) && (dys == 0.0f)) {
1119             // this happens if the "curve" is just a point
1120             // fix outcode0 for lineTo() call:
1121             if (clipRect != null) {
1122                 this.cOutCode = outcode0;
1123             }
1124             lineTo(cx0, cy0);
1125             return;
1126         }
1127 
1128         // if these vectors are too small, normalize them, to avoid future
1129         // precision problems.
1130         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1131             final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
1132             dxs /= len;
1133             dys /= len;
1134         }
1135         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1136             final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
1137             dxf /= len;
1138             dyf /= len;
1139         }
1140 
1141         computeOffset(dxs, dys, lineWidth2, offset0);
1142         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1143 
1144         int nSplits = 0;
1145         final float[] mid;
1146         final float[] l = lp;
1147 
1148         if (monotonize) {
1149             // monotonize curve:
1150             final CurveBasicMonotonizer monotonizer
1151                 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
1152 
1153             nSplits = monotonizer.nbSplits;
1154             mid = monotonizer.middle;
1155         } else {
1156             // use left instead:
1157             mid = l;
1158             mid[0] = cx0; mid[1] = cy0;
1159             mid[2] = x1;  mid[3] = y1;
1160             mid[4] = x2;  mid[5] = y2;
1161             mid[6] = x3;  mid[7] = y3;
1162         }
1163         final float[] r = rp;
1164 
1165         int kind = 0;
1166         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1167             kind = computeOffsetCubic(mid, off, l, r);
1168 
1169             emitLineTo(l[0], l[1]);
1170 
1171             switch(kind) {
1172             case 8:
1173                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1174                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1175                 break;
1176             case 4:
1177                 emitLineTo(l[2], l[3]);
1178                 emitLineToRev(r[0], r[1]);
1179                 break;
1180             default:
1181             }
1182             emitLineToRev(r[kind - 2], r[kind - 1]);
1183         }
1184 
1185         this.prev = DRAWING_OP_TO;
1186         this.cx0 = x3;
1187         this.cy0 = y3;
1188         this.cdx = dxf;
1189         this.cdy = dyf;
1190         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1191         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1192     }
1193 
1194     @Override
1195     public void quadTo(final float x1, final float y1,
1196                        final float x2, final float y2)
1197     {
1198         final int outcode0 = this.cOutCode;
1199 
1200         if (clipRect != null) {
1201             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1202             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1203 
1204             // Should clip
1205             final int orCode = (outcode0 | outcode1 | outcode2);
1206             if (orCode != 0) {
1207                 final int sideCode = outcode0 & outcode1 & outcode2;
1208 
1209                 // basic rejection criteria:
1210                 if (sideCode == 0) {
1211                     // ovelap clip:
1212                     if (subdivide) {
1213                         // avoid reentrance
1214                         subdivide = false;
1215                         // subdivide curve => call lineTo() with subdivided curves:
1216                         boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
1217                                                               x2, y2, orCode, this);
1218                         // reentrance is done:
1219                         subdivide = true;
1220                         if (ret) {
1221                             return;
1222                         }
1223                     }
1224                     // already subdivided so render it
1225                 } else {
1226                     this.cOutCode = outcode2;
1227                     _moveTo(x2, y2, outcode0);
1228                     opened = true;
1229                     return;
1230                 }
1231             }
1232 
1233             this.cOutCode = outcode2;
1234         }
1235         _quadTo(x1, y1, x2, y2, outcode0);
1236     }
1237 
1238     private void _quadTo(final float x1, final float y1,
1239                           final float x2, final float y2,
1240                           final int outcode0)
1241     {
1242         // need these so we can update the state at the end of this method
1243         float dxs = x1 - cx0;
1244         float dys = y1 - cy0;
1245         float dxf = x2 - x1;
1246         float dyf = y2 - y1;
1247 
1248         if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) {
1249             dxs = dxf = x2 - cx0;
1250             dys = dyf = y2 - cy0;
1251         }
1252         if ((dxs == 0.0f) && (dys == 0.0f)) {
1253             // this happens if the "curve" is just a point
1254             // fix outcode0 for lineTo() call:
1255             if (clipRect != null) {
1256                 this.cOutCode = outcode0;
1257             }
1258             lineTo(cx0, cy0);
1259             return;
1260         }
1261         // if these vectors are too small, normalize them, to avoid future
1262         // precision problems.
1263         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1264             final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
1265             dxs /= len;
1266             dys /= len;
1267         }
1268         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1269             final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
1270             dxf /= len;
1271             dyf /= len;
1272         }
1273         computeOffset(dxs, dys, lineWidth2, offset0);
1274         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1275 
1276         int nSplits = 0;
1277         final float[] mid;
1278         final float[] l = lp;
1279 
1280         if (monotonize) {
1281             // monotonize quad:
1282             final CurveBasicMonotonizer monotonizer
1283                 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
1284 
1285             nSplits = monotonizer.nbSplits;
1286             mid = monotonizer.middle;
1287         } else {
1288             // use left instead:
1289             mid = l;
1290             mid[0] = cx0; mid[1] = cy0;
1291             mid[2] = x1;  mid[3] = y1;
1292             mid[4] = x2;  mid[5] = y2;
1293         }
1294         final float[] r = rp;
1295 
1296         int kind = 0;
1297         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1298             kind = computeOffsetQuad(mid, off, l, r);
1299 
1300             emitLineTo(l[0], l[1]);
1301 
1302             switch(kind) {
1303             case 6:
1304                 emitQuadTo(l[2], l[3], l[4], l[5]);
1305                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1306                 break;
1307             case 4:
1308                 emitLineTo(l[2], l[3]);
1309                 emitLineToRev(r[0], r[1]);
1310                 break;
1311             default:
1312             }
1313             emitLineToRev(r[kind - 2], r[kind - 1]);
1314         }
1315 
1316         this.prev = DRAWING_OP_TO;
1317         this.cx0 = x2;
1318         this.cy0 = y2;
1319         this.cdx = dxf;
1320         this.cdy = dyf;
1321         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1322         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1323     }
1324 }