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