< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java

Print this page


   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 import com.sun.marlin.DHelpers.PolyStack;


  30 
  31 // TODO: some of the arithmetic here is too verbose and prone to hard to
  32 // debug typos. We should consider making a small Point/Vector class that
  33 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  34 public final class DStroker implements DPathConsumer2D, MarlinConst {
  35 
  36     private static final int MOVE_TO = 0;
  37     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  38     private static final int CLOSE = 2;
  39 
  40     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  41     // didn't want to change the values of the constant below when I converted
  42     // it to floating point, so that's why the divisions by 2^16 are there.
  43     private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
  44 
  45     // kappa = (4/3) * (SQRT(2) - 1)
  46     private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  47 
  48     // SQRT(2)
  49     private static final double SQRT_2 = Math.sqrt(2.0d);
  50 
  51     private static final int MAX_N_CURVES = 11;
  52 
  53     private DPathConsumer2D out;
  54 
  55     private int capStyle;
  56     private int joinStyle;
  57 
  58     private double lineWidth2;
  59     private double invHalfLineWidth2Sq;
  60 
  61     private final double[] offset0 = new double[2];
  62     private final double[] offset1 = new double[2];
  63     private final double[] offset2 = new double[2];
  64     private final double[] miter = new double[2];
  65     private double miterLimitSq;
  66 
  67     private int prev;
  68 
  69     // The starting point of the path, and the slope there.
  70     private double sx0, sy0, sdx, sdy;
  71     // the current point and the slope there.
  72     private double cx0, cy0, cdx, cdy; // c stands for current
  73     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
  74     // first and last points on the left parallel path. Since this path is
  75     // parallel, it's slope at any point is parallel to the slope of the
  76     // original path (thought they may have different directions), so these
  77     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
  78     // would be error prone and hard to read, so we keep these anyway.
  79     private double smx, smy, cmx, cmy;
  80 
  81     private final PolyStack reverse;
  82 
  83     // This is where the curve to be processed is put. We give it
  84     // enough room to store all curves.
  85     private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
  86     private final double[] lp = new double[8];
  87     private final double[] rp = new double[8];
  88     private final double[] subdivTs = new double[MAX_N_CURVES - 1];
  89 
  90     // per-thread renderer context
  91     final DRendererContext rdrCtx;
  92 
  93     // dirty curve
  94     final DCurve curve;
  95 
  96     // Bounds of the drawing region, at pixel precision.
  97     private double[] clipRect;
  98 
  99     // the outcode of the current point
 100     private int cOutCode = 0;
 101 
 102     // the outcode of the starting point
 103     private int sOutCode = 0;
 104 
 105     // flag indicating if the path is opened (clipped)
 106     private boolean opened = false;
 107     // flag indicating if the starting point's cap is done
 108     private boolean capStart = false;





 109 
 110     /**
 111      * Constructs a <code>DStroker</code>.
 112      * @param rdrCtx per-thread renderer context
 113      */
 114     DStroker(final DRendererContext rdrCtx) {
 115         this.rdrCtx = rdrCtx;
 116 
 117         this.reverse = (rdrCtx.stats != null) ?
 118             new PolyStack(rdrCtx,
 119                     rdrCtx.stats.stat_str_polystack_types,
 120                     rdrCtx.stats.stat_str_polystack_curves,
 121                     rdrCtx.stats.hist_str_polystack_curves,
 122                     rdrCtx.stats.stat_array_str_polystack_curves,
 123                     rdrCtx.stats.stat_array_str_polystack_types)
 124             : new PolyStack(rdrCtx);
 125 
 126         this.curve = rdrCtx.curve;

 127     }
 128 
 129     /**
 130      * Inits the <code>DStroker</code>.
 131      *
 132      * @param pc2d an output <code>DPathConsumer2D</code>.
 133      * @param lineWidth the desired line width in pixels
 134      * @param capStyle the desired end cap style, one of
 135      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 136      * <code>CAP_SQUARE</code>.
 137      * @param joinStyle the desired line join style, one of
 138      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 139      * <code>JOIN_BEVEL</code>.
 140      * @param miterLimit the desired miter limit
 141      * @param scale scaling factor applied to clip boundaries
 142      * @param rdrOffX renderer's coordinate offset on X axis
 143      * @param rdrOffY renderer's coordinate offset on Y axis

 144      * @return this instance
 145      */
 146     public DStroker init(final DPathConsumer2D pc2d,
 147                          final double lineWidth,
 148                          final int capStyle,
 149                          final int joinStyle,
 150                          final double miterLimit,
 151                          final double scale,
 152                          double rdrOffX,
 153                          double rdrOffY)

 154     {
 155         this.out = pc2d;
 156 
 157         this.lineWidth2 = lineWidth / 2.0d;
 158         this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);


 159         this.capStyle = capStyle;
 160         this.joinStyle = joinStyle;
 161 
 162         final double limit = miterLimit * lineWidth2;
 163         this.miterLimitSq = limit * limit;
 164 
 165         this.prev = CLOSE;
 166 
 167         rdrCtx.stroking = 1;
 168 
 169         if (rdrCtx.doClip) {
 170             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 171             double margin = lineWidth2;
 172 
 173             if (capStyle == CAP_SQUARE) {
 174                 margin *= SQRT_2;
 175             }
 176             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 177                 margin = limit;
 178             }
 179             if (scale != 1.0d) {
 180                 margin  *= scale;
 181                 rdrOffX *= scale;
 182                 rdrOffY *= scale;
 183             }
 184             // add a small rounding error:
 185             margin += 1e-3d;
 186 
 187             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 188             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 189             final double[] _clipRect = rdrCtx.clipRect;
 190             _clipRect[0] -= margin - rdrOffY;
 191             _clipRect[1] += margin + rdrOffY;
 192             _clipRect[2] -= margin - rdrOffX;
 193             _clipRect[3] += margin + rdrOffX;
 194             this.clipRect = _clipRect;









 195         } else {
 196             this.clipRect = null;
 197             this.cOutCode = 0;
 198             this.sOutCode = 0;
 199         }
 200         return this; // fluent API
 201     }
 202 






 203     /**
 204      * Disposes this stroker:
 205      * clean up before reusing this instance
 206      */
 207     void dispose() {
 208         reverse.dispose();
 209 
 210         opened   = false;
 211         capStart = false;
 212 
 213         if (DO_CLEAN_DIRTY) {
 214             // Force zero-fill dirty arrays:
 215             Arrays.fill(offset0, 0.0d);
 216             Arrays.fill(offset1, 0.0d);
 217             Arrays.fill(offset2, 0.0d);
 218             Arrays.fill(miter, 0.0d);
 219             Arrays.fill(middle, 0.0d);
 220             Arrays.fill(lp, 0.0d);
 221             Arrays.fill(rp, 0.0d);
 222             Arrays.fill(subdivTs, 0.0d);
 223         }
 224     }
 225 
 226     private static void computeOffset(final double lx, final double ly,
 227                                       final double w, final double[] m)
 228     {
 229         double len = lx*lx + ly*ly;
 230         if (len == 0.0d) {
 231             m[0] = 0.0d;
 232             m[1] = 0.0d;
 233         } else {
 234             len = Math.sqrt(len);
 235             m[0] =  (ly * w) / len;
 236             m[1] = -(lx * w) / len;
 237         }
 238     }
 239 
 240     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
 241     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
 242     // the smallest angle between it and dx2,dy2).
 243     // This is equivalent to detecting whether a point q is on the right side
 244     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
 245     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
 246     // clockwise order.
 247     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
 248     private static boolean isCW(final double dx1, final double dy1,
 249                                 final double dx2, final double dy2)
 250     {
 251         return dx1 * dy2 <= dy1 * dx2;
 252     }
 253 
 254     private void drawRoundJoin(double x, double y,
 255                                double omx, double omy, double mx, double my,
 256                                boolean rev,
 257                                double threshold)
 258     {
 259         if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
 260             return;
 261         }
 262 
 263         double domx = omx - mx;
 264         double domy = omy - my;
 265         double len = domx*domx + domy*domy;
 266         if (len < threshold) {

 267             return;
 268         }
 269 
 270         if (rev) {
 271             omx = -omx;
 272             omy = -omy;
 273             mx  = -mx;
 274             my  = -my;
 275         }
 276         drawRoundJoin(x, y, omx, omy, mx, my, rev);
 277     }
 278 
 279     private void drawRoundJoin(double cx, double cy,
 280                                double omx, double omy,
 281                                double mx, double my,
 282                                boolean rev)
 283     {
 284         // The sign of the dot product of mx,my and omx,omy is equal to the
 285         // the sign of the cosine of ext
 286         // (ext is the angle between omx,omy and mx,my).
 287         final double cosext = omx * mx + omy * my;
 288         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 289         // need 1 curve to approximate the circle section that joins omx,omy
 290         // and mx,my.
 291         final int numCurves = (cosext >= 0.0d) ? 1 : 2;
 292 
 293         switch (numCurves) {
 294         case 1:
 295             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 296             break;
 297         case 2:
 298             // we need to split the arc into 2 arcs spanning the same angle.
 299             // The point we want will be one of the 2 intersections of the
 300             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
 301             // circle. We could find this by scaling the vector
 302             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
 303             // on the circle), but that can have numerical problems when the angle
 304             // between omx,omy and mx,my is close to 180 degrees. So we compute a
 305             // normal of (omx,omy)-(mx,my). This will be the direction of the
 306             // perpendicular bisector. To get one of the intersections, we just scale
 307             // this vector that its length is lineWidth2 (this works because the
 308             // perpendicular bisector goes through the origin). This scaling doesn't
 309             // have numerical problems because we know that lineWidth2 divided by
 310             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
 311             // we know the angle of the arc is > 90 degrees).
 312             double nx = my - omy, ny = omx - mx;
 313             double nlen = Math.sqrt(nx*nx + ny*ny);
 314             double scale = lineWidth2/nlen;
 315             double mmx = nx * scale, mmy = ny * scale;
 316 
 317             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
 318             // computed the wrong intersection so we get the other one.
 319             // The test above is equivalent to if (rev).
 320             if (rev) {
 321                 mmx = -mmx;
 322                 mmy = -mmy;
 323             }
 324             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
 325             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
 326             break;
 327         default:
 328         }
 329     }
 330 
 331     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
 332     private void drawBezApproxForArc(final double cx, final double cy,
 333                                      final double omx, final double omy,
 334                                      final double mx, final double my,
 335                                      boolean rev)
 336     {
 337         final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
 338 
 339         // check round off errors producing cos(ext) > 1 and a NaN below
 340         // cos(ext) == 1 implies colinear segments and an empty join anyway
 341         if (cosext2 >= 0.5d) {
 342             // just return to avoid generating a flat curve:
 343             return;
 344         }
 345 
 346         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
 347         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that


 367         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
 368     }
 369 
 370     private void drawRoundCap(double cx, double cy, double mx, double my) {
 371         final double Cmx = C * mx;
 372         final double Cmy = C * my;
 373         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
 374                     cx - my + Cmx, cy + mx + Cmy,
 375                     cx - my,       cy + mx);
 376         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
 377                     cx - mx - Cmy, cy - my + Cmx,
 378                     cx - mx,       cy - my);
 379     }
 380 
 381     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 382     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 383     private static void computeMiter(final double x0, final double y0,
 384                                      final double x1, final double y1,
 385                                      final double x0p, final double y0p,
 386                                      final double x1p, final double y1p,
 387                                      final double[] m, int off)
 388     {
 389         double x10 = x1 - x0;
 390         double y10 = y1 - y0;
 391         double x10p = x1p - x0p;
 392         double y10p = y1p - y0p;
 393 
 394         // if this is 0, the lines are parallel. If they go in the
 395         // same direction, there is no intersection so m[off] and
 396         // m[off+1] will contain infinity, so no miter will be drawn.
 397         // If they go in the same direction that means that the start of the
 398         // current segment and the end of the previous segment have the same
 399         // tangent, in which case this method won't even be involved in
 400         // miter drawing because it won't be called by drawMiter (because
 401         // (mx == omx && my == omy) will be true, and drawMiter will return
 402         // immediately).
 403         double den = x10*y10p - x10p*y10;
 404         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 405         t /= den;
 406         m[off++] = x0 + t*x10;
 407         m[off]   = y0 + t*y10;
 408     }
 409 
 410     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 411     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 412     private static void safeComputeMiter(final double x0, final double y0,
 413                                          final double x1, final double y1,
 414                                          final double x0p, final double y0p,
 415                                          final double x1p, final double y1p,
 416                                          final double[] m, int off)
 417     {
 418         double x10 = x1 - x0;
 419         double y10 = y1 - y0;
 420         double x10p = x1p - x0p;
 421         double y10p = y1p - y0p;
 422 
 423         // if this is 0, the lines are parallel. If they go in the
 424         // same direction, there is no intersection so m[off] and
 425         // m[off+1] will contain infinity, so no miter will be drawn.
 426         // If they go in the same direction that means that the start of the
 427         // current segment and the end of the previous segment have the same
 428         // tangent, in which case this method won't even be involved in
 429         // miter drawing because it won't be called by drawMiter (because
 430         // (mx == omx && my == omy) will be true, and drawMiter will return
 431         // immediately).
 432         double den = x10*y10p - x10p*y10;
 433         if (den == 0.0d) {
 434             m[off++] = (x0 + x0p) / 2.0d;
 435             m[off]   = (y0 + y0p) / 2.0d;
 436             return;




 437         }
 438         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 439         t /= den;
 440         m[off++] = x0 + t*x10;
 441         m[off] = y0 + t*y10;
 442     }
 443 
 444     private void drawMiter(final double pdx, final double pdy,
 445                            final double x0, final double y0,
 446                            final double dx, final double dy,
 447                            double omx, double omy, double mx, double my,

 448                            boolean rev)
 449     {
 450         if ((mx == omx && my == omy) ||
 451             (pdx == 0.0d && pdy == 0.0d) ||
 452             (dx == 0.0d && dy == 0.0d))
 453         {
 454             return;
 455         }
 456 
 457         if (rev) {
 458             omx = -omx;
 459             omy = -omy;
 460             mx  = -mx;
 461             my  = -my;
 462         }
 463 
 464         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 465                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
 466                      miter, 0);
 467 
 468         final double miterX = miter[0];
 469         final double miterY = miter[1];
 470         double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 471 
 472         // If the lines are parallel, lenSq will be either NaN or +inf
 473         // (actually, I'm not sure if the latter is possible. The important
 474         // thing is that -inf is not possible, because lenSq is a square).
 475         // For both of those values, the comparison below will fail and
 476         // no miter will be drawn, which is correct.
 477         if (lenSq < miterLimitSq) {
 478             emitLineTo(miterX, miterY, rev);
 479         }
 480     }
 481 
 482     @Override
 483     public void moveTo(final double x0, final double y0) {
 484         moveTo(x0, y0, cOutCode);
 485         // update starting point:
 486         this.sx0 = x0;
 487         this.sy0 = y0;
 488         this.sdx = 1.0d;
 489         this.sdy = 0.0d;
 490         this.opened   = false;
 491         this.capStart = false;
 492 
 493         if (clipRect != null) {
 494             final int outcode = DHelpers.outcode(x0, y0, clipRect);
 495             this.cOutCode = outcode;
 496             this.sOutCode = outcode;
 497         }
 498     }
 499 
 500     private void moveTo(final double x0, final double y0,
 501                         final int outcode)
 502     {
 503         if (prev == MOVE_TO) {
 504             this.cx0 = x0;
 505             this.cy0 = y0;
 506         } else {
 507             if (prev == DRAWING_OP_TO) {
 508                 finish(outcode);
 509             }
 510             this.prev = MOVE_TO;
 511             this.cx0 = x0;
 512             this.cy0 = y0;
 513             this.cdx = 1.0d;
 514             this.cdy = 0.0d;
 515         }
 516     }
 517 
 518     @Override
 519     public void lineTo(final double x1, final double y1) {
 520         lineTo(x1, y1, false);
 521     }
 522 
 523     private void lineTo(final double x1, final double y1,
 524                         final boolean force)
 525     {
 526         final int outcode0 = this.cOutCode;

 527         if (!force && clipRect != null) {
 528             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 529             this.cOutCode = outcode1;
 530 
 531             // basic rejection criteria
 532             if ((outcode0 & outcode1) != 0) {
 533                 moveTo(x1, y1, outcode0);
 534                 opened = true;
 535                 return;






















 536             }


 537         }
 538 
 539         double dx = x1 - cx0;
 540         double dy = y1 - cy0;
 541         if (dx == 0.0d && dy == 0.0d) {
 542             dx = 1.0d;
 543         }
 544         computeOffset(dx, dy, lineWidth2, offset0);
 545         final double mx = offset0[0];
 546         final double my = offset0[1];
 547 
 548         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 549 
 550         emitLineTo(cx0 + mx, cy0 + my);
 551         emitLineTo( x1 + mx,  y1 + my);
 552 
 553         emitLineToRev(cx0 - mx, cy0 - my);
 554         emitLineToRev( x1 - mx,  y1 - my);
 555 
 556         this.prev = DRAWING_OP_TO;


 738                           double x0, double y0,
 739                           double dx, double dy,
 740                           double omx, double omy,
 741                           double mx, double my,
 742                           final int outcode)
 743     {
 744         if (prev != DRAWING_OP_TO) {
 745             emitMoveTo(x0 + mx, y0 + my);
 746             if (!opened) {
 747                 this.sdx = dx;
 748                 this.sdy = dy;
 749                 this.smx = mx;
 750                 this.smy = my;
 751             }
 752         } else {
 753             final boolean cw = isCW(pdx, pdy, dx, dy);
 754             if (outcode == 0) {
 755                 if (joinStyle == JOIN_MITER) {
 756                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 757                 } else if (joinStyle == JOIN_ROUND) {
 758                     drawRoundJoin(x0, y0,
 759                                   omx, omy,
 760                                   mx, my, cw,
 761                                   ROUND_JOIN_THRESHOLD);
 762                 }
 763             }
 764             emitLineTo(x0, y0, !cw);
 765         }
 766         prev = DRAWING_OP_TO;
 767     }
 768 
 769     private static boolean within(final double x1, final double y1,
 770                                   final double x2, final double y2,
 771                                   final double ERR)
 772     {
 773         assert ERR > 0 : "";
 774         // compare taxicab distance. ERR will always be small, so using
 775         // true distance won't give much benefit
 776         return (DHelpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 777                 DHelpers.within(y1, y2, ERR)); // this is just as good.
 778     }
 779 
 780     private void getLineOffsets(double x1, double y1,
 781                                 double x2, double y2,
 782                                 double[] left, double[] right) {

 783         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 784         final double mx = offset0[0];
 785         final double my = offset0[1];
 786         left[0] = x1 + mx;
 787         left[1] = y1 + my;
 788         left[2] = x2 + mx;
 789         left[3] = y2 + my;

 790         right[0] = x1 - mx;
 791         right[1] = y1 - my;
 792         right[2] = x2 - mx;
 793         right[3] = y2 - my;
 794     }
 795 
 796     private int computeOffsetCubic(double[] pts, final int off,
 797                                    double[] leftOff, double[] rightOff)

 798     {
 799         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 800         // vanishes, which creates problems with computeOffset. Usually
 801         // this happens when this stroker object is trying to widen
 802         // a curve with a cusp. What happens is that curveTo splits
 803         // the input curve at the cusp, and passes it to this function.
 804         // because of inaccuracies in the splitting, we consider points
 805         // equal if they're very close to each other.
 806         final double x1 = pts[off + 0], y1 = pts[off + 1];
 807         final double x2 = pts[off + 2], y2 = pts[off + 3];
 808         final double x3 = pts[off + 4], y3 = pts[off + 5];
 809         final double x4 = pts[off + 6], y4 = pts[off + 7];
 810 
 811         double dx4 = x4 - x3;
 812         double dy4 = y4 - y3;
 813         double dx1 = x2 - x1;
 814         double dy1 = y2 - y1;
 815 
 816         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 817         // in which case ignore if p1 == p2
 818         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
 819         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));

 820         if (p1eqp2 && p3eqp4) {
 821             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 822             return 4;
 823         } else if (p1eqp2) {
 824             dx1 = x3 - x1;
 825             dy1 = y3 - y1;
 826         } else if (p3eqp4) {
 827             dx4 = x4 - x2;
 828             dy4 = y4 - y2;
 829         }
 830 
 831         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 832         double dotsq = (dx1 * dx4 + dy1 * dy4);
 833         dotsq *= dotsq;
 834         double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;

 835         if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
 836             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 837             return 4;
 838         }
 839 
 840 //      What we're trying to do in this function is to approximate an ideal
 841 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 842 //      The constraints I use to get the equations are:
 843 //
 844 //      1. The computed curve Bp should go through I(0) and I(1). These are
 845 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 846 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 847 //
 848 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 849 //      (by the way, the operator || in the comments below means "aligned with".
 850 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 851 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 852 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 853 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 854 //      nonzero constant.)


 928         two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
 929         two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
 930         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 931         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 932 
 933         x2p = x1p + c1*dx1;
 934         y2p = y1p + c1*dy1;
 935         x3p = x4p + c2*dx4;
 936         y3p = y4p + c2*dy4;
 937 
 938         rightOff[0] = x1p; rightOff[1] = y1p;
 939         rightOff[2] = x2p; rightOff[3] = y2p;
 940         rightOff[4] = x3p; rightOff[5] = y3p;
 941         rightOff[6] = x4p; rightOff[7] = y4p;
 942         return 8;
 943     }
 944 
 945     // compute offset curves using bezier spline through t=0.5 (i.e.
 946     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 947     // return the kind of curve in the right and left arrays.
 948     private int computeOffsetQuad(double[] pts, final int off,
 949                                   double[] leftOff, double[] rightOff)

 950     {
 951         final double x1 = pts[off + 0], y1 = pts[off + 1];
 952         final double x2 = pts[off + 2], y2 = pts[off + 3];
 953         final double x3 = pts[off + 4], y3 = pts[off + 5];
 954 
 955         final double dx3 = x3 - x2;
 956         final double dy3 = y3 - y2;
 957         final double dx1 = x2 - x1;
 958         final double dy1 = y2 - y1;
 959 
 960         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 961         // vanishes, which creates problems with computeOffset. Usually
 962         // this happens when this stroker object is trying to widen
 963         // a curve with a cusp. What happens is that curveTo splits
 964         // the input curve at the cusp, and passes it to this function.
 965         // because of inaccuracies in the splitting, we consider points
 966         // equal if they're very close to each other.
 967 
 968         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 969         // in which case ignore.
 970         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
 971         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));

 972         if (p1eqp2 || p2eqp3) {
 973             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 974             return 4;
 975         }
 976 
 977         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 978         double dotsq = (dx1 * dx3 + dy1 * dy3);
 979         dotsq *= dotsq;
 980         double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;

 981         if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
 982             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 983             return 4;
 984         }
 985 
 986         // this computes the offsets at t=0, 0.5, 1, using the property that
 987         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
 988         // the (dx/dt, dy/dt) vectors at the endpoints.
 989         computeOffset(dx1, dy1, lineWidth2, offset0);
 990         computeOffset(dx3, dy3, lineWidth2, offset1);
 991 
 992         double x1p = x1 + offset0[0]; // start
 993         double y1p = y1 + offset0[1]; // point
 994         double x3p = x3 + offset1[0]; // end
 995         double y3p = y3 + offset1[1]; // point
 996         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
 997         leftOff[0] = x1p; leftOff[1] = y1p;
 998         leftOff[4] = x3p; leftOff[5] = y3p;
 999 
1000         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1001         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1002         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
1003         rightOff[0] = x1p; rightOff[1] = y1p;
1004         rightOff[4] = x3p; rightOff[5] = y3p;
1005         return 6;
1006     }
1007 
1008     // finds values of t where the curve in pts should be subdivided in order
1009     // to get good offset curves a distance of w away from the middle curve.
1010     // Stores the points in ts, and returns how many of them there were.
1011     private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts,
1012                                         final int type, final double w)
1013     {
1014         final double x12 = pts[2] - pts[0];
1015         final double y12 = pts[3] - pts[1];
1016         // if the curve is already parallel to either axis we gain nothing
1017         // from rotating it.
1018         if (y12 != 0.0d && x12 != 0.0d) {
1019             // we rotate it so that the first vector in the control polygon is
1020             // parallel to the x-axis. This will ensure that rotated quarter
1021             // circles won't be subdivided.
1022             final double hypot = Math.sqrt(x12 * x12 + y12 * y12);
1023             final double cos = x12 / hypot;
1024             final double sin = y12 / hypot;
1025             final double x1 = cos * pts[0] + sin * pts[1];
1026             final double y1 = cos * pts[1] - sin * pts[0];
1027             final double x2 = cos * pts[2] + sin * pts[3];
1028             final double y2 = cos * pts[3] - sin * pts[2];
1029             final double x3 = cos * pts[4] + sin * pts[5];
1030             final double y3 = cos * pts[5] - sin * pts[4];
1031 
1032             switch(type) {
1033             case 8:
1034                 final double x4 = cos * pts[6] + sin * pts[7];
1035                 final double y4 = cos * pts[7] - sin * pts[6];
1036                 c.set(x1, y1, x2, y2, x3, y3, x4, y4);
1037                 break;
1038             case 6:
1039                 c.set(x1, y1, x2, y2, x3, y3);
1040                 break;
1041             default:
1042             }
1043         } else {
1044             c.set(pts, type);
1045         }
1046 
1047         int ret = 0;
1048         // we subdivide at values of t such that the remaining rotated
1049         // curves are monotonic in x and y.
1050         ret += c.dxRoots(ts, ret);
1051         ret += c.dyRoots(ts, ret);
1052         // subdivide at inflection points.
1053         if (type == 8) {
1054             // quadratic curves can't have inflection points
1055             ret += c.infPoints(ts, ret);
1056         }
1057 
1058         // now we must subdivide at points where one of the offset curves will have
1059         // a cusp. This happens at ts where the radius of curvature is equal to w.
1060         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
1061 
1062         ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
1063         DHelpers.isort(ts, 0, ret);
1064         return ret;
1065     }
1066 
1067     @Override
1068     public void curveTo(final double x1, final double y1,
1069                         final double x2, final double y2,
1070                         final double x3, final double y3)
1071     {
1072         final int outcode0 = this.cOutCode;

1073         if (clipRect != null) {


1074             final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
1075             this.cOutCode = outcode3;
1076 
1077             if ((outcode0 & outcode3) != 0) {
1078                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1079                 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1080 
1081                 // basic rejection criteria
1082                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1083                     moveTo(x3, y3, outcode0);


















1084                     opened = true;
1085                     return;
1086                 }
1087             }
1088         }
1089 
1090         final double[] mid = middle;
1091 
1092         mid[0] = cx0; mid[1] = cy0;
1093         mid[2] = x1;  mid[3] = y1;
1094         mid[4] = x2;  mid[5] = y2;
1095         mid[6] = x3;  mid[7] = y3;
1096 





1097         // need these so we can update the state at the end of this method
1098         final double xf = x3, yf = y3;
1099         double dxs = mid[2] - mid[0];
1100         double dys = mid[3] - mid[1];
1101         double dxf = mid[6] - mid[4];
1102         double dyf = mid[7] - mid[5];
1103 
1104         boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
1105         boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
1106         if (p1eqp2) {
1107             dxs = mid[4] - mid[0];
1108             dys = mid[5] - mid[1];
1109             if (dxs == 0.0d && dys == 0.0d) {
1110                 dxs = mid[6] - mid[0];
1111                 dys = mid[7] - mid[1];
1112             }
1113         }
1114         if (p3eqp4) {
1115             dxf = mid[6] - mid[2];
1116             dyf = mid[7] - mid[3];
1117             if (dxf == 0.0d && dyf == 0.0d) {
1118                 dxf = mid[6] - mid[0];
1119                 dyf = mid[7] - mid[1];
1120             }
1121         }
1122         if (dxs == 0.0d && dys == 0.0d) {
1123             // this happens if the "curve" is just a point
1124             // fix outcode0 for lineTo() call:
1125             if (clipRect != null) {
1126                 this.cOutCode = outcode0;
1127             }
1128             lineTo(mid[0], mid[1]);
1129             return;
1130         }
1131 
1132         // if these vectors are too small, normalize them, to avoid future
1133         // precision problems.
1134         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1135             double len = Math.sqrt(dxs*dxs + dys*dys);
1136             dxs /= len;
1137             dys /= len;
1138         }
1139         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1140             double len = Math.sqrt(dxf*dxf + dyf*dyf);
1141             dxf /= len;
1142             dyf /= len;
1143         }
1144 
1145         computeOffset(dxs, dys, lineWidth2, offset0);
1146         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1147 
1148         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);


1149 
1150         double prevT = 0.0d;
1151         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1152             final double t = subdivTs[i];
1153             DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1154                                      mid, off, mid, off, mid, off + 6);
1155             prevT = t;
1156         }
1157 
1158         final double[] l = lp;









1159         final double[] r = rp;
1160 
1161         int kind = 0;
1162         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1163             kind = computeOffsetCubic(mid, off, l, r);
1164 
1165             emitLineTo(l[0], l[1]);
1166 
1167             switch(kind) {
1168             case 8:
1169                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1170                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1171                 break;
1172             case 4:
1173                 emitLineTo(l[2], l[3]);
1174                 emitLineToRev(r[0], r[1]);
1175                 break;
1176             default:
1177             }
1178             emitLineToRev(r[kind - 2], r[kind - 1]);
1179         }
1180 
1181         this.prev = DRAWING_OP_TO;
1182         this.cx0 = xf;
1183         this.cy0 = yf;
1184         this.cdx = dxf;
1185         this.cdy = dyf;
1186         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1187         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1188     }
1189 
1190     @Override
1191     public void quadTo(final double x1, final double y1,
1192                        final double x2, final double y2)
1193     {
1194         final int outcode0 = this.cOutCode;

1195         if (clipRect != null) {

1196             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1197             this.cOutCode = outcode2;
1198 
1199             if ((outcode0 & outcode2) != 0) {
1200                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1201 
1202                 // basic rejection criteria
1203                 if ((outcode0 & outcode1 & outcode2) != 0) {
1204                     moveTo(x2, y2, outcode0);





















1205                     opened = true;
1206                     return;
1207                 }
1208             }
1209         }
1210 
1211         final double[] mid = middle;
1212 
1213         mid[0] = cx0; mid[1] = cy0;
1214         mid[2] = x1;  mid[3] = y1;
1215         mid[4] = x2;  mid[5] = y2;
1216 




1217         // need these so we can update the state at the end of this method
1218         final double xf = x2, yf = y2;
1219         double dxs = mid[2] - mid[0];
1220         double dys = mid[3] - mid[1];
1221         double dxf = mid[4] - mid[2];
1222         double dyf = mid[5] - mid[3];
1223         if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1224             dxs = dxf = mid[4] - mid[0];
1225             dys = dyf = mid[5] - mid[1];
1226         }
1227         if (dxs == 0.0d && dys == 0.0d) {
1228             // this happens if the "curve" is just a point
1229             // fix outcode0 for lineTo() call:
1230             if (clipRect != null) {
1231                 this.cOutCode = outcode0;
1232             }
1233             lineTo(mid[0], mid[1]);
1234             return;
1235         }
1236         // if these vectors are too small, normalize them, to avoid future
1237         // precision problems.
1238         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1239             double len = Math.sqrt(dxs*dxs + dys*dys);
1240             dxs /= len;
1241             dys /= len;
1242         }
1243         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1244             double len = Math.sqrt(dxf*dxf + dyf*dyf);
1245             dxf /= len;
1246             dyf /= len;
1247         }
1248 
1249         computeOffset(dxs, dys, lineWidth2, offset0);
1250         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1251 
1252         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);


1253 
1254         double prevt = 0.0d;
1255         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1256             final double t = subdivTs[i];
1257             DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1258                                     mid, off, mid, off, mid, off + 4);
1259             prevt = t;
1260         }
1261 
1262         final double[] l = lp;








1263         final double[] r = rp;
1264 
1265         int kind = 0;
1266         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1267             kind = computeOffsetQuad(mid, off, l, r);
1268 
1269             emitLineTo(l[0], l[1]);
1270 
1271             switch(kind) {
1272             case 6:
1273                 emitQuadTo(l[2], l[3], l[4], l[5]);
1274                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1275                 break;
1276             case 4:
1277                 emitLineTo(l[2], l[3]);
1278                 emitLineToRev(r[0], r[1]);
1279                 break;
1280             default:
1281             }
1282             emitLineToRev(r[kind - 2], r[kind - 1]);
1283         }
1284 
1285         this.prev = DRAWING_OP_TO;
1286         this.cx0 = xf;
1287         this.cy0 = yf;
1288         this.cdx = dxf;
1289         this.cdy = dyf;
1290         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1291         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1292     }
1293 
1294 }
   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 import com.sun.marlin.DHelpers.PolyStack;
  30 import com.sun.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
  31 import com.sun.marlin.DTransformingPathConsumer2D.CurveClipSplitter;
  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 DStroker implements DPathConsumer2D, 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     // round join threshold = 1 subpixel
  43     private static final double ERR_JOIN = (1.0f / MIN_SUBPIXELS);
  44     private static final double ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;

  45 
  46     // kappa = (4/3) * (SQRT(2) - 1)
  47     private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  48 
  49     // SQRT(2)
  50     private static final double SQRT_2 = Math.sqrt(2.0d);
  51 


  52     private DPathConsumer2D out;
  53 
  54     private int capStyle;
  55     private int joinStyle;
  56 
  57     private double lineWidth2;
  58     private double invHalfLineWidth2Sq;
  59 
  60     private final double[] offset0 = new double[2];
  61     private final double[] offset1 = new double[2];
  62     private final double[] offset2 = new double[2];
  63     private final double[] miter = new double[2];
  64     private double miterLimitSq;
  65 
  66     private int prev;
  67 
  68     // The starting point of the path, and the slope there.
  69     private double sx0, sy0, sdx, sdy;
  70     // the current point and the slope there.
  71     private double cx0, cy0, cdx, cdy; // c stands for current
  72     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
  73     // first and last points on the left parallel path. Since this path is
  74     // parallel, it's slope at any point is parallel to the slope of the
  75     // original path (thought they may have different directions), so these
  76     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
  77     // would be error prone and hard to read, so we keep these anyway.
  78     private double smx, smy, cmx, cmy;
  79 
  80     private final PolyStack reverse;
  81 



  82     private final double[] lp = new double[8];
  83     private final double[] rp = new double[8];

  84 
  85     // per-thread renderer context
  86     final DRendererContext rdrCtx;
  87 
  88     // dirty curve
  89     final DCurve curve;
  90 
  91     // Bounds of the drawing region, at pixel precision.
  92     private double[] clipRect;
  93 
  94     // the outcode of the current point
  95     private int cOutCode = 0;
  96 
  97     // the outcode of the starting point
  98     private int sOutCode = 0;
  99 
 100     // flag indicating if the path is opened (clipped)
 101     private boolean opened = false;
 102     // flag indicating if the starting point's cap is done
 103     private boolean capStart = false;
 104     // flag indicating to monotonize curves
 105     private boolean monotonize;
 106 
 107     private boolean subdivide = false;
 108     private final CurveClipSplitter curveSplitter;
 109 
 110     /**
 111      * Constructs a <code>DStroker</code>.
 112      * @param rdrCtx per-thread renderer context
 113      */
 114     DStroker(final DRendererContext rdrCtx) {
 115         this.rdrCtx = rdrCtx;
 116 
 117         this.reverse = (rdrCtx.stats != null) ?
 118             new PolyStack(rdrCtx,
 119                     rdrCtx.stats.stat_str_polystack_types,
 120                     rdrCtx.stats.stat_str_polystack_curves,
 121                     rdrCtx.stats.hist_str_polystack_curves,
 122                     rdrCtx.stats.stat_array_str_polystack_curves,
 123                     rdrCtx.stats.stat_array_str_polystack_types)
 124             : new PolyStack(rdrCtx);
 125 
 126         this.curve = rdrCtx.curve;
 127         this.curveSplitter = rdrCtx.curveClipSplitter;
 128     }
 129 
 130     /**
 131      * Inits the <code>DStroker</code>.
 132      *
 133      * @param pc2d an output <code>DPathConsumer2D</code>.
 134      * @param lineWidth the desired line width in pixels
 135      * @param capStyle the desired end cap style, one of
 136      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 137      * <code>CAP_SQUARE</code>.
 138      * @param joinStyle the desired line join style, one of
 139      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 140      * <code>JOIN_BEVEL</code>.
 141      * @param miterLimit the desired miter limit
 142      * @param scale scaling factor applied to clip boundaries
 143      * @param rdrOffX renderer's coordinate offset on X axis
 144      * @param rdrOffY renderer's coordinate offset on Y axis
 145      * @param subdivideCurves true to indicate to subdivide curves, false if dasher does
 146      * @return this instance
 147      */
 148     public DStroker init(final DPathConsumer2D pc2d,
 149                          final double lineWidth,
 150                          final int capStyle,
 151                          final int joinStyle,
 152                          final double miterLimit,
 153                          final double scale,
 154                          double rdrOffX,
 155                          double rdrOffY,
 156                          final boolean subdivideCurves)
 157     {
 158         this.out = pc2d;
 159 
 160         this.lineWidth2 = lineWidth / 2.0d;
 161         this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
 162         this.monotonize = subdivideCurves;
 163 
 164         this.capStyle = capStyle;
 165         this.joinStyle = joinStyle;
 166 
 167         final double limit = miterLimit * lineWidth2;
 168         this.miterLimitSq = limit * limit;
 169 
 170         this.prev = CLOSE;
 171 
 172         rdrCtx.stroking = 1;
 173 
 174         if (rdrCtx.doClip) {
 175             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 176             double margin = lineWidth2;
 177 
 178             if (capStyle == CAP_SQUARE) {
 179                 margin *= SQRT_2;
 180             }
 181             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 182                 margin = limit;
 183             }
 184             if (scale != 1.0d) {
 185                 margin  *= scale;
 186                 rdrOffX *= scale;
 187                 rdrOffY *= scale;
 188             }
 189             // add a small rounding error:
 190             margin += 1e-3d;
 191 
 192             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 193             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 194             final double[] _clipRect = rdrCtx.clipRect;
 195             _clipRect[0] -= margin - rdrOffY;
 196             _clipRect[1] += margin + rdrOffY;
 197             _clipRect[2] -= margin - rdrOffX;
 198             _clipRect[3] += margin + rdrOffX;
 199             this.clipRect = _clipRect;
 200 
 201             // initialize curve splitter here for stroker & dasher:
 202             if (DO_CLIP_SUBDIVIDER) {
 203                 subdivide = subdivideCurves;
 204                 // adjust padded clip rectangle:
 205                 curveSplitter.init();
 206             } else {
 207                 subdivide = false;
 208             }
 209         } else {
 210             this.clipRect = null;
 211             this.cOutCode = 0;
 212             this.sOutCode = 0;
 213         }
 214         return this; // fluent API
 215     }
 216 
 217     public void disableClipping() {
 218         this.clipRect = null;
 219         this.cOutCode = 0;
 220         this.sOutCode = 0;
 221     }
 222 
 223     /**
 224      * Disposes this stroker:
 225      * clean up before reusing this instance
 226      */
 227     void dispose() {
 228         reverse.dispose();
 229 
 230         opened   = false;
 231         capStart = false;
 232 
 233         if (DO_CLEAN_DIRTY) {
 234             // Force zero-fill dirty arrays:
 235             Arrays.fill(offset0, 0.0d);
 236             Arrays.fill(offset1, 0.0d);
 237             Arrays.fill(offset2, 0.0d);
 238             Arrays.fill(miter, 0.0d);

 239             Arrays.fill(lp, 0.0d);
 240             Arrays.fill(rp, 0.0d);

 241         }
 242     }
 243 
 244     private static void computeOffset(final double lx, final double ly,
 245                                       final double w, final double[] m)
 246     {
 247         double len = lx*lx + ly*ly;
 248         if (len == 0.0d) {
 249             m[0] = 0.0d;
 250             m[1] = 0.0d;
 251         } else {
 252             len = Math.sqrt(len);
 253             m[0] =  (ly * w) / len;
 254             m[1] = -(lx * w) / len;
 255         }
 256     }
 257 
 258     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
 259     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
 260     // the smallest angle between it and dx2,dy2).
 261     // This is equivalent to detecting whether a point q is on the right side
 262     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
 263     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
 264     // clockwise order.
 265     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
 266     private static boolean isCW(final double dx1, final double dy1,
 267                                 final double dx2, final double dy2)
 268     {
 269         return dx1 * dy2 <= dy1 * dx2;
 270     }
 271 
 272     private void mayDrawRoundJoin(double cx, double cy,
 273                                   double omx, double omy,
 274                                   double mx, double my,
 275                                   boolean rev)
 276     {
 277         if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
 278             return;
 279         }
 280 
 281         final double domx = omx - mx;
 282         final double domy = omy - my;
 283         final double lenSq = domx*domx + domy*domy;
 284 
 285         if (lenSq < ROUND_JOIN_THRESHOLD) {
 286             return;
 287         }
 288 
 289         if (rev) {
 290             omx = -omx;
 291             omy = -omy;
 292             mx  = -mx;
 293             my  = -my;
 294         }
 295         drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
 296     }
 297 
 298     private void drawRoundJoin(double cx, double cy,
 299                                double omx, double omy,
 300                                double mx, double my,
 301                                boolean rev)
 302     {
 303         // The sign of the dot product of mx,my and omx,omy is equal to the
 304         // the sign of the cosine of ext
 305         // (ext is the angle between omx,omy and mx,my).
 306         final double cosext = omx * mx + omy * my;
 307         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 308         // need 1 curve to approximate the circle section that joins omx,omy
 309         // and mx,my.
 310         if (cosext >= 0.0d) {



 311             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 312         } else {

 313             // we need to split the arc into 2 arcs spanning the same angle.
 314             // The point we want will be one of the 2 intersections of the
 315             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
 316             // circle. We could find this by scaling the vector
 317             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
 318             // on the circle), but that can have numerical problems when the angle
 319             // between omx,omy and mx,my is close to 180 degrees. So we compute a
 320             // normal of (omx,omy)-(mx,my). This will be the direction of the
 321             // perpendicular bisector. To get one of the intersections, we just scale
 322             // this vector that its length is lineWidth2 (this works because the
 323             // perpendicular bisector goes through the origin). This scaling doesn't
 324             // have numerical problems because we know that lineWidth2 divided by
 325             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
 326             // we know the angle of the arc is > 90 degrees).
 327             double nx = my - omy, ny = omx - mx;
 328             double nlen = Math.sqrt(nx*nx + ny*ny);
 329             double scale = lineWidth2/nlen;
 330             double mmx = nx * scale, mmy = ny * scale;
 331 
 332             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
 333             // computed the wrong intersection so we get the other one.
 334             // The test above is equivalent to if (rev).
 335             if (rev) {
 336                 mmx = -mmx;
 337                 mmy = -mmy;
 338             }
 339             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
 340             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);


 341         }
 342     }
 343 
 344     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
 345     private void drawBezApproxForArc(final double cx, final double cy,
 346                                      final double omx, final double omy,
 347                                      final double mx, final double my,
 348                                      boolean rev)
 349     {
 350         final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
 351 
 352         // check round off errors producing cos(ext) > 1 and a NaN below
 353         // cos(ext) == 1 implies colinear segments and an empty join anyway
 354         if (cosext2 >= 0.5d) {
 355             // just return to avoid generating a flat curve:
 356             return;
 357         }
 358 
 359         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
 360         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that


 380         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
 381     }
 382 
 383     private void drawRoundCap(double cx, double cy, double mx, double my) {
 384         final double Cmx = C * mx;
 385         final double Cmy = C * my;
 386         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
 387                     cx - my + Cmx, cy + mx + Cmy,
 388                     cx - my,       cy + mx);
 389         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
 390                     cx - mx - Cmy, cy - my + Cmx,
 391                     cx - mx,       cy - my);
 392     }
 393 
 394     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 395     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 396     private static void computeMiter(final double x0, final double y0,
 397                                      final double x1, final double y1,
 398                                      final double x0p, final double y0p,
 399                                      final double x1p, final double y1p,
 400                                      final double[] m)
 401     {
 402         double x10 = x1 - x0;
 403         double y10 = y1 - y0;
 404         double x10p = x1p - x0p;
 405         double y10p = y1p - y0p;
 406 
 407         // if this is 0, the lines are parallel. If they go in the
 408         // same direction, there is no intersection so m[off] and
 409         // m[off+1] will contain infinity, so no miter will be drawn.
 410         // If they go in the same direction that means that the start of the
 411         // current segment and the end of the previous segment have the same
 412         // tangent, in which case this method won't even be involved in
 413         // miter drawing because it won't be called by drawMiter (because
 414         // (mx == omx && my == omy) will be true, and drawMiter will return
 415         // immediately).
 416         double den = x10*y10p - x10p*y10;
 417         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 418         t /= den;
 419         m[0] = x0 + t*x10;
 420         m[1] = y0 + t*y10;
 421     }
 422 
 423     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 424     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 425     private static void safeComputeMiter(final double x0, final double y0,
 426                                          final double x1, final double y1,
 427                                          final double x0p, final double y0p,
 428                                          final double x1p, final double y1p,
 429                                          final double[] m)
 430     {
 431         double x10 = x1 - x0;
 432         double y10 = y1 - y0;
 433         double x10p = x1p - x0p;
 434         double y10p = y1p - y0p;
 435 
 436         // if this is 0, the lines are parallel. If they go in the
 437         // same direction, there is no intersection so m[off] and
 438         // m[off+1] will contain infinity, so no miter will be drawn.
 439         // If they go in the same direction that means that the start of the
 440         // current segment and the end of the previous segment have the same
 441         // tangent, in which case this method won't even be involved in
 442         // miter drawing because it won't be called by drawMiter (because
 443         // (mx == omx && my == omy) will be true, and drawMiter will return
 444         // immediately).
 445         double den = x10*y10p - x10p*y10;
 446         if (den == 0.0d) {
 447             m[2] = (x0 + x0p) / 2.0d;
 448             m[3] = (y0 + y0p) / 2.0d;
 449         } else {
 450             double t = x10p*(y0-y0p) - y10p*(x0-x0p);
 451             t /= den;
 452             m[2] = x0 + t*x10;
 453             m[3] = y0 + t*y10;
 454         }




 455     }
 456 
 457     private void drawMiter(final double pdx, final double pdy,
 458                            final double x0, final double y0,
 459                            final double dx, final double dy,
 460                            double omx, double omy,
 461                            double mx, double my,
 462                            boolean rev)
 463     {
 464         if ((mx == omx && my == omy) ||
 465             (pdx == 0.0d && pdy == 0.0d) ||
 466             (dx == 0.0d && dy == 0.0d))
 467         {
 468             return;
 469         }
 470 
 471         if (rev) {
 472             omx = -omx;
 473             omy = -omy;
 474             mx  = -mx;
 475             my  = -my;
 476         }
 477 
 478         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 479                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);

 480 
 481         final double miterX = miter[0];
 482         final double miterY = miter[1];
 483         double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 484 
 485         // If the lines are parallel, lenSq will be either NaN or +inf
 486         // (actually, I'm not sure if the latter is possible. The important
 487         // thing is that -inf is not possible, because lenSq is a square).
 488         // For both of those values, the comparison below will fail and
 489         // no miter will be drawn, which is correct.
 490         if (lenSq < miterLimitSq) {
 491             emitLineTo(miterX, miterY, rev);
 492         }
 493     }
 494 
 495     @Override
 496     public void moveTo(final double x0, final double y0) {
 497         _moveTo(x0, y0, cOutCode);
 498         // update starting point:
 499         this.sx0 = x0;
 500         this.sy0 = y0;
 501         this.sdx = 1.0d;
 502         this.sdy = 0.0d;
 503         this.opened   = false;
 504         this.capStart = false;
 505 
 506         if (clipRect != null) {
 507             final int outcode = DHelpers.outcode(x0, y0, clipRect);
 508             this.cOutCode = outcode;
 509             this.sOutCode = outcode;
 510         }
 511     }
 512 
 513     private void _moveTo(final double x0, final double y0,
 514                         final int outcode)
 515     {
 516         if (prev == MOVE_TO) {
 517             this.cx0 = x0;
 518             this.cy0 = y0;
 519         } else {
 520             if (prev == DRAWING_OP_TO) {
 521                 finish(outcode);
 522             }
 523             this.prev = MOVE_TO;
 524             this.cx0 = x0;
 525             this.cy0 = y0;
 526             this.cdx = 1.0d;
 527             this.cdy = 0.0d;
 528         }
 529     }
 530 
 531     @Override
 532     public void lineTo(final double x1, final double y1) {
 533         lineTo(x1, y1, false);
 534     }
 535 
 536     private void lineTo(final double x1, final double y1,
 537                         final boolean force)
 538     {
 539         final int outcode0 = this.cOutCode;
 540 
 541         if (!force && clipRect != null) {
 542             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);

 543 
 544             // Should clip
 545             final int orCode = (outcode0 | outcode1);
 546             if (orCode != 0) {
 547                 final int sideCode = outcode0 & outcode1;
 548 
 549                 // basic rejection criteria:
 550                 if (sideCode == 0) {
 551                     // ovelap clip:
 552                     if (subdivide) {
 553                         // avoid reentrance
 554                         subdivide = false;
 555                         // subdivide curve => callback with subdivided parts:
 556                         boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
 557                                                               orCode, this);
 558                         // reentrance is done:
 559                         subdivide = true;
 560                         if (ret) {
 561                             return;
 562                         }
 563                     }
 564                     // already subdivided so render it
 565                 } else {
 566                     this.cOutCode = outcode1;
 567                     _moveTo(x1, y1, outcode0);
 568                     opened = true;
 569                     return;
 570                 }
 571             }
 572 
 573             this.cOutCode = outcode1;
 574         }
 575 
 576         double dx = x1 - cx0;
 577         double dy = y1 - cy0;
 578         if (dx == 0.0d && dy == 0.0d) {
 579             dx = 1.0d;
 580         }
 581         computeOffset(dx, dy, lineWidth2, offset0);
 582         final double mx = offset0[0];
 583         final double my = offset0[1];
 584 
 585         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 586 
 587         emitLineTo(cx0 + mx, cy0 + my);
 588         emitLineTo( x1 + mx,  y1 + my);
 589 
 590         emitLineToRev(cx0 - mx, cy0 - my);
 591         emitLineToRev( x1 - mx,  y1 - my);
 592 
 593         this.prev = DRAWING_OP_TO;


 775                           double x0, double y0,
 776                           double dx, double dy,
 777                           double omx, double omy,
 778                           double mx, double my,
 779                           final int outcode)
 780     {
 781         if (prev != DRAWING_OP_TO) {
 782             emitMoveTo(x0 + mx, y0 + my);
 783             if (!opened) {
 784                 this.sdx = dx;
 785                 this.sdy = dy;
 786                 this.smx = mx;
 787                 this.smy = my;
 788             }
 789         } else {
 790             final boolean cw = isCW(pdx, pdy, dx, dy);
 791             if (outcode == 0) {
 792                 if (joinStyle == JOIN_MITER) {
 793                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 794                 } else if (joinStyle == JOIN_ROUND) {
 795                     mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);



 796                 }
 797             }
 798             emitLineTo(x0, y0, !cw);
 799         }
 800         prev = DRAWING_OP_TO;
 801     }
 802 
 803     private static boolean within(final double x1, final double y1,
 804                                   final double x2, final double y2,
 805                                   final double err)
 806     {
 807         assert err > 0 : "";
 808         // compare taxicab distance. ERR will always be small, so using
 809         // true distance won't give much benefit
 810         return (DHelpers.within(x1, x2, err) && // we want to avoid calling Math.abs
 811                 DHelpers.within(y1, y2, err));  // this is just as good.
 812     }
 813 
 814     private void getLineOffsets(final double x1, final double y1,
 815                                 final double x2, final double y2,
 816                                 final double[] left, final double[] right)
 817     {
 818         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 819         final double mx = offset0[0];
 820         final double my = offset0[1];
 821         left[0] = x1 + mx;
 822         left[1] = y1 + my;
 823         left[2] = x2 + mx;
 824         left[3] = y2 + my;
 825 
 826         right[0] = x1 - mx;
 827         right[1] = y1 - my;
 828         right[2] = x2 - mx;
 829         right[3] = y2 - my;
 830     }
 831 
 832     private int computeOffsetCubic(final double[] pts, final int off,
 833                                    final double[] leftOff,
 834                                    final double[] rightOff)
 835     {
 836         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 837         // vanishes, which creates problems with computeOffset. Usually
 838         // this happens when this stroker object is trying to widen
 839         // a curve with a cusp. What happens is that curveTo splits
 840         // the input curve at the cusp, and passes it to this function.
 841         // because of inaccuracies in the splitting, we consider points
 842         // equal if they're very close to each other.
 843         final double x1 = pts[off    ], y1 = pts[off + 1];
 844         final double x2 = pts[off + 2], y2 = pts[off + 3];
 845         final double x3 = pts[off + 4], y3 = pts[off + 5];
 846         final double x4 = pts[off + 6], y4 = pts[off + 7];
 847 
 848         double dx4 = x4 - x3;
 849         double dy4 = y4 - y3;
 850         double dx1 = x2 - x1;
 851         double dy1 = y2 - y1;
 852 
 853         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 854         // in which case ignore if p1 == p2
 855         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
 856         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));
 857 
 858         if (p1eqp2 && p3eqp4) {
 859             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 860             return 4;
 861         } else if (p1eqp2) {
 862             dx1 = x3 - x1;
 863             dy1 = y3 - y1;
 864         } else if (p3eqp4) {
 865             dx4 = x4 - x2;
 866             dy4 = y4 - y2;
 867         }
 868 
 869         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 870         double dotsq = (dx1 * dx4 + dy1 * dy4);
 871         dotsq *= dotsq;
 872         double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
 873 
 874         if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
 875             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 876             return 4;
 877         }
 878 
 879 //      What we're trying to do in this function is to approximate an ideal
 880 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 881 //      The constraints I use to get the equations are:
 882 //
 883 //      1. The computed curve Bp should go through I(0) and I(1). These are
 884 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 885 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 886 //
 887 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 888 //      (by the way, the operator || in the comments below means "aligned with".
 889 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 890 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 891 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 892 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 893 //      nonzero constant.)


 967         two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
 968         two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
 969         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 970         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 971 
 972         x2p = x1p + c1*dx1;
 973         y2p = y1p + c1*dy1;
 974         x3p = x4p + c2*dx4;
 975         y3p = y4p + c2*dy4;
 976 
 977         rightOff[0] = x1p; rightOff[1] = y1p;
 978         rightOff[2] = x2p; rightOff[3] = y2p;
 979         rightOff[4] = x3p; rightOff[5] = y3p;
 980         rightOff[6] = x4p; rightOff[7] = y4p;
 981         return 8;
 982     }
 983 
 984     // compute offset curves using bezier spline through t=0.5 (i.e.
 985     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 986     // return the kind of curve in the right and left arrays.
 987     private int computeOffsetQuad(final double[] pts, final int off,
 988                                   final double[] leftOff,
 989                                   final double[] rightOff)
 990     {
 991         final double x1 = pts[off    ], y1 = pts[off + 1];
 992         final double x2 = pts[off + 2], y2 = pts[off + 3];
 993         final double x3 = pts[off + 4], y3 = pts[off + 5];
 994 
 995         final double dx3 = x3 - x2;
 996         final double dy3 = y3 - y2;
 997         final double dx1 = x2 - x1;
 998         final double dy1 = y2 - y1;
 999 
1000         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
1001         // vanishes, which creates problems with computeOffset. Usually
1002         // this happens when this stroker object is trying to widen
1003         // a curve with a cusp. What happens is that curveTo splits
1004         // the input curve at the cusp, and passes it to this function.
1005         // because of inaccuracies in the splitting, we consider points
1006         // equal if they're very close to each other.
1007 
1008         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
1009         // in which case ignore.
1010         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
1011         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));
1012 
1013         if (p1eqp2 || p2eqp3) {
1014             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1015             return 4;
1016         }
1017 
1018         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
1019         double dotsq = (dx1 * dx3 + dy1 * dy3);
1020         dotsq *= dotsq;
1021         double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
1022 
1023         if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
1024             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1025             return 4;
1026         }
1027 
1028         // this computes the offsets at t=0, 0.5, 1, using the property that
1029         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
1030         // the (dx/dt, dy/dt) vectors at the endpoints.
1031         computeOffset(dx1, dy1, lineWidth2, offset0);
1032         computeOffset(dx3, dy3, lineWidth2, offset1);
1033 
1034         double x1p = x1 + offset0[0]; // start
1035         double y1p = y1 + offset0[1]; // point
1036         double x3p = x3 + offset1[0]; // end
1037         double y3p = y3 + offset1[1]; // point
1038         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
1039         leftOff[0] = x1p; leftOff[1] = y1p;
1040         leftOff[4] = x3p; leftOff[5] = y3p;
1041 
1042         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1043         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1044         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
1045         rightOff[0] = x1p; rightOff[1] = y1p;
1046         rightOff[4] = x3p; rightOff[5] = y3p;
1047         return 6;
1048     }
1049 



























































1050     @Override
1051     public void curveTo(final double x1, final double y1,
1052                         final double x2, final double y2,
1053                         final double x3, final double y3)
1054     {
1055         final int outcode0 = this.cOutCode;
1056 
1057         if (clipRect != null) {
1058             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1059             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1060             final int outcode3 = DHelpers.outcode(x3, y3, clipRect);

1061 
1062             // Should clip
1063             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
1064             if (orCode != 0) {
1065                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
1066 
1067                 // basic rejection criteria:
1068                 if (sideCode == 0) {
1069                     // ovelap clip:
1070                     if (subdivide) {
1071                         // avoid reentrance
1072                         subdivide = false;
1073                         // subdivide curve => callback with subdivided parts:
1074                         boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
1075                                                                x2, y2, x3, y3,
1076                                                                orCode, this);
1077                         // reentrance is done:
1078                         subdivide = true;
1079                         if (ret) {
1080                             return;
1081                         }
1082                     }
1083                     // already subdivided so render it
1084                 } else {
1085                     this.cOutCode = outcode3;
1086                     _moveTo(x3, y3, outcode0);
1087                     opened = true;
1088                     return;
1089                 }
1090             }

1091 
1092             this.cOutCode = outcode3;
1093         }
1094         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
1095     }


1096 
1097     private void _curveTo(final double x1, final double y1,
1098                           final double x2, final double y2,
1099                           final double x3, final double y3,
1100                           final int outcode0)
1101     {
1102         // need these so we can update the state at the end of this method
1103         double dxs = x1 - cx0;
1104         double dys = y1 - cy0;
1105         double dxf = x3 - x2;
1106         double dyf = y3 - y2;
1107 
1108         if ((dxs == 0.0d) && (dys == 0.0d)) {
1109             dxs = x2 - cx0;
1110             dys = y2 - cy0;
1111             if ((dxs == 0.0d) && (dys == 0.0d)) {
1112                 dxs = x3 - cx0;
1113                 dys = y3 - cy0;
1114             }
1115         }
1116         if ((dxf == 0.0d) && (dyf == 0.0d)) {
1117             dxf = x3 - x1;
1118             dyf = y3 - y1;
1119             if ((dxf == 0.0d) && (dyf == 0.0d)) {
1120                 dxf = x3 - cx0;
1121                 dyf = y3 - cy0;



1122             }
1123         }
1124         if ((dxs == 0.0d) && (dys == 0.0d)) {
1125             // this happens if the "curve" is just a point
1126             // fix outcode0 for lineTo() call:
1127             if (clipRect != null) {
1128                 this.cOutCode = outcode0;
1129             }
1130             lineTo(cx0, cy0);
1131             return;
1132         }
1133 
1134         // if these vectors are too small, normalize them, to avoid future
1135         // precision problems.
1136         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1137             final double len = Math.sqrt(dxs * dxs + dys * dys);
1138             dxs /= len;
1139             dys /= len;
1140         }
1141         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1142             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
1143             dxf /= len;
1144             dyf /= len;
1145         }
1146 
1147         computeOffset(dxs, dys, lineWidth2, offset0);
1148         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1149 
1150         int nSplits = 0;
1151         final double[] mid;
1152         final double[] l = lp;
1153 
1154         if (monotonize) {
1155             // monotonize curve:
1156             final CurveBasicMonotonizer monotonizer
1157                 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);



1158 
1159             nSplits = monotonizer.nbSplits;
1160             mid = monotonizer.middle;
1161         } else {
1162             // use left instead:
1163             mid = l;
1164             mid[0] = cx0; mid[1] = cy0;
1165             mid[2] = x1;  mid[3] = y1;
1166             mid[4] = x2;  mid[5] = y2;
1167             mid[6] = x3;  mid[7] = y3;
1168         }
1169         final double[] r = rp;
1170 
1171         int kind = 0;
1172         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1173             kind = computeOffsetCubic(mid, off, l, r);
1174 
1175             emitLineTo(l[0], l[1]);
1176 
1177             switch(kind) {
1178             case 8:
1179                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1180                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1181                 break;
1182             case 4:
1183                 emitLineTo(l[2], l[3]);
1184                 emitLineToRev(r[0], r[1]);
1185                 break;
1186             default:
1187             }
1188             emitLineToRev(r[kind - 2], r[kind - 1]);
1189         }
1190 
1191         this.prev = DRAWING_OP_TO;
1192         this.cx0 = x3;
1193         this.cy0 = y3;
1194         this.cdx = dxf;
1195         this.cdy = dyf;
1196         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1197         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1198     }
1199 
1200     @Override
1201     public void quadTo(final double x1, final double y1,
1202                        final double x2, final double y2)
1203     {
1204         final int outcode0 = this.cOutCode;
1205 
1206         if (clipRect != null) {
1207             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1208             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);




1209 
1210             // Should clip
1211             final int orCode = (outcode0 | outcode1 | outcode2);
1212             if (orCode != 0) {
1213                 final int sideCode = outcode0 & outcode1 & outcode2;
1214 
1215                 // basic rejection criteria:
1216                 if (sideCode == 0) {
1217                     // ovelap clip:
1218                     if (subdivide) {
1219                         // avoid reentrance
1220                         subdivide = false;
1221                         // subdivide curve => call lineTo() with subdivided curves:
1222                         boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
1223                                                               x2, y2, orCode, this);
1224                         // reentrance is done:
1225                         subdivide = true;
1226                         if (ret) {
1227                             return;
1228                         }
1229                     }
1230                     // already subdivided so render it
1231                 } else {
1232                     this.cOutCode = outcode2;
1233                     _moveTo(x2, y2, outcode0);
1234                     opened = true;
1235                     return;
1236                 }
1237             }

1238 
1239             this.cOutCode = outcode2;
1240         }
1241         _quadTo(x1, y1, x2, y2, outcode0);
1242     }

1243 
1244     private void _quadTo(final double x1, final double y1,
1245                          final double x2, final double y2,
1246                          final int outcode0)
1247     {
1248         // need these so we can update the state at the end of this method
1249         double dxs = x1 - cx0;
1250         double dys = y1 - cy0;
1251         double dxf = x2 - x1;
1252         double dyf = y2 - y1;
1253 
1254         if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) {
1255             dxs = dxf = x2 - cx0;
1256             dys = dyf = y2 - cy0;
1257         }
1258         if ((dxs == 0.0d) && (dys == 0.0d)) {
1259             // this happens if the "curve" is just a point
1260             // fix outcode0 for lineTo() call:
1261             if (clipRect != null) {
1262                 this.cOutCode = outcode0;
1263             }
1264             lineTo(cx0, cy0);
1265             return;
1266         }
1267         // if these vectors are too small, normalize them, to avoid future
1268         // precision problems.
1269         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1270             final double len = Math.sqrt(dxs * dxs + dys * dys);
1271             dxs /= len;
1272             dys /= len;
1273         }
1274         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1275             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
1276             dxf /= len;
1277             dyf /= len;
1278         }

1279         computeOffset(dxs, dys, lineWidth2, offset0);
1280         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1281 
1282         int nSplits = 0;
1283         final double[] mid;
1284         final double[] l = lp;
1285 
1286         if (monotonize) {
1287             // monotonize quad:
1288             final CurveBasicMonotonizer monotonizer
1289                 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);



1290 
1291             nSplits = monotonizer.nbSplits;
1292             mid = monotonizer.middle;
1293         } else {
1294             // use left instead:
1295             mid = l;
1296             mid[0] = cx0; mid[1] = cy0;
1297             mid[2] = x1;  mid[3] = y1;
1298             mid[4] = x2;  mid[5] = y2;
1299         }
1300         final double[] r = rp;
1301 
1302         int kind = 0;
1303         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1304             kind = computeOffsetQuad(mid, off, l, r);
1305 
1306             emitLineTo(l[0], l[1]);
1307 
1308             switch(kind) {
1309             case 6:
1310                 emitQuadTo(l[2], l[3], l[4], l[5]);
1311                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1312                 break;
1313             case 4:
1314                 emitLineTo(l[2], l[3]);
1315                 emitLineToRev(r[0], r[1]);
1316                 break;
1317             default:
1318             }
1319             emitLineToRev(r[kind - 2], r[kind - 1]);
1320         }
1321 
1322         this.prev = DRAWING_OP_TO;
1323         this.cx0 = x2;
1324         this.cy0 = y2;
1325         this.cdx = dxf;
1326         this.cdy = dyf;
1327         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1328         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1329     }
1330 
1331 }
< prev index next >