1 /*
   2  * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.java2d.pisces;
  27 
  28 public class Stroker extends LineSink {
  29 
  30     private static final int MOVE_TO = 0;
  31     private static final int LINE_TO = 1;
  32     private static final int CLOSE = 2;
  33 
  34     /**
  35      * Constant value for join style.
  36      */
  37     public static final int JOIN_MITER = 0;
  38 
  39     /**
  40      * Constant value for join style.
  41      */
  42     public static final int JOIN_ROUND = 1;
  43 
  44     /**
  45      * Constant value for join style.
  46      */
  47     public static final int JOIN_BEVEL = 2;
  48 
  49     /**
  50      * Constant value for end cap style.
  51      */
  52     public static final int CAP_BUTT = 0;
  53 
  54     /**
  55      * Constant value for end cap style.
  56      */
  57     public static final int CAP_ROUND = 1;
  58 
  59     /**
  60      * Constant value for end cap style.
  61      */
  62     public static final int CAP_SQUARE = 2;
  63 
  64     LineSink output;
  65 
  66     int lineWidth;
  67     int capStyle;
  68     int joinStyle;
  69     int miterLimit;
  70 
  71     Transform4 transform;
  72     int m00, m01;
  73     int m10, m11;
  74 
  75     int lineWidth2;
  76     long scaledLineWidth2;
  77 
  78     // For any pen offset (pen_dx, pen_dy) that does not depend on
  79     // the line orientation, the pen should be transformed so that:
  80     //
  81     // pen_dx' = m00*pen_dx + m01*pen_dy
  82     // pen_dy' = m10*pen_dx + m11*pen_dy
  83     //
  84     // For a round pen, this means:
  85     //
  86     // pen_dx(r, theta) = r*cos(theta)
  87     // pen_dy(r, theta) = r*sin(theta)
  88     //
  89     // pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta))
  90     // pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta))
  91     int numPenSegments;
  92     int[] pen_dx;
  93     int[] pen_dy;
  94     boolean[] penIncluded;
  95     int[] join;
  96 
  97     int[] offset = new int[2];
  98     int[] reverse = new int[100];
  99     int[] miter = new int[2];
 100     long miterLimitSq;
 101 
 102     int prev;
 103     int rindex;
 104     boolean started;
 105     boolean lineToOrigin;
 106     boolean joinToOrigin;
 107 
 108     int sx0, sy0, sx1, sy1, x0, y0, x1, y1;
 109     int mx0, my0, mx1, my1, omx, omy;
 110     int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0;
 111 
 112     double m00_2_m01_2;
 113     double m10_2_m11_2;
 114     double m00_m10_m01_m11;
 115 
 116     // An upper bound on the width the the transformed line and the
 117     // untransformed line.
 118     int wub;
 119 
 120     /**
 121      * Empty constructor.  <code>setOutput</code> and
 122      * <code>setParameters</code> must be called prior to calling any
 123      * other methods.
 124      */
 125     public Stroker() {}
 126 
 127     /**
 128      * Constructs a <code>Stroker</code>.
 129      *
 130      * @param output an output <code>LineSink</code>.
 131      * @param lineWidth the desired line width in pixels, in S15.16
 132      * format.
 133      * @param capStyle the desired end cap style, one of
 134      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 135      * <code>CAP_SQUARE</code>.
 136      * @param joinStyle the desired line join style, one of
 137      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 138      * <code>JOIN_BEVEL</code>.
 139      * @param miterLimit the desired miter limit, in S15.16 format.
 140      * @param transform a <code>Transform4</code> object indicating
 141      * the transform that has been previously applied to all incoming
 142      * coordinates.  This is required in order to produce consistently
 143      * shaped end caps and joins.
 144      */
 145     public Stroker(LineSink output,
 146                    int lineWidth,
 147                    int capStyle,
 148                    int joinStyle,
 149                    int miterLimit,
 150                    Transform4 transform) {
 151         setOutput(output);
 152         setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform);
 153     }
 154 
 155     /**
 156      * Sets the output <code>LineSink</code> of this
 157      * <code>Stroker</code>.
 158      *
 159      * @param output an output <code>LineSink</code>.
 160      */
 161     public void setOutput(LineSink output) {
 162         this.output = output;
 163     }
 164 
 165     /**
 166      * Sets the parameters of this <code>Stroker</code>.
 167      * @param lineWidth the desired line width in pixels, in S15.16
 168      * format.
 169      * @param capStyle the desired end cap style, one of
 170      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 171      * <code>CAP_SQUARE</code>.
 172      * @param joinStyle the desired line join style, one of
 173      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 174      * <code>JOIN_BEVEL</code>.
 175      * @param miterLimit the desired miter limit, in S15.16 format.
 176      * @param transform a <code>Transform4</code> object indicating
 177      * the transform that has been previously applied to all incoming
 178      * coordinates.  This is required in order to produce consistently
 179      * shaped end caps and joins.
 180      */
 181     public void setParameters(int lineWidth,
 182                               int capStyle,
 183                               int joinStyle,
 184                               int miterLimit,
 185                               Transform4 transform) {
 186         this.lineWidth = lineWidth;
 187         this.lineWidth2 = lineWidth >> 1;
 188         this.scaledLineWidth2 = ((long)transform.m00*lineWidth2) >> 16;
 189         this.capStyle = capStyle;
 190         this.joinStyle = joinStyle;
 191         this.miterLimit = miterLimit;
 192 
 193         this.transform = transform;
 194         this.m00 = transform.m00;
 195         this.m01 = transform.m01;
 196         this.m10 = transform.m10;
 197         this.m11 = transform.m11;
 198 
 199         this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01;
 200         this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11;
 201         this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11;
 202 
 203         double dm00 = m00/65536.0;
 204         double dm01 = m01/65536.0;
 205         double dm10 = m10/65536.0;
 206         double dm11 = m11/65536.0;
 207         double determinant = dm00*dm11 - dm01*dm10;
 208 
 209         if (joinStyle == JOIN_MITER) {
 210             double limit =
 211                 (miterLimit/65536.0)*(lineWidth2/65536.0)*determinant;
 212             double limitSq = limit*limit;
 213             this.miterLimitSq = (long)(limitSq*65536.0*65536.0);
 214         }
 215 
 216         this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f);
 217         if (pen_dx == null || pen_dx.length < numPenSegments) {
 218             this.pen_dx = new int[numPenSegments];
 219             this.pen_dy = new int[numPenSegments];
 220             this.penIncluded = new boolean[numPenSegments];
 221             this.join = new int[2*numPenSegments];
 222         }
 223 
 224         for (int i = 0; i < numPenSegments; i++) {
 225             double r = lineWidth/2.0;
 226             double theta = (double)i*2.0*Math.PI/numPenSegments;
 227 
 228             double cos = Math.cos(theta);
 229             double sin = Math.sin(theta);
 230             pen_dx[i] = (int)(r*(dm00*cos + dm01*sin));
 231             pen_dy[i] = (int)(r*(dm10*cos + dm11*sin));
 232         }
 233 
 234         wub = (int)((lineWidth * PiscesMath.hypot((long)m01+m00, (long)m10+m11)) >> 16); 
 235         wub = (wub > lineWidth) ? wub : lineWidth;
 236 
 237         prev = CLOSE;
 238         rindex = 0;
 239         started = false;
 240         lineToOrigin = false;
 241     }
 242 
 243     private void computeOffset(int x0, int y0, int x1, int y1, int[] m) {
 244         long lx = (long)x1 - (long)x0;
 245         long ly = (long)y1 - (long)y0;
 246 
 247         int dx, dy;
 248         if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) {
 249             long ilen = PiscesMath.hypot(lx, ly);
 250             if (ilen == 0) {
 251                 dx = dy = 0;
 252             } else {
 253                 dx = (int)( (ly*scaledLineWidth2)/ilen);
 254                 dy = (int)(-(lx*scaledLineWidth2)/ilen);
 255             }
 256         } else {
 257             double dlx = x1 - x0;
 258             double dly = y1 - y0;
 259             double det = (double)m00*m11 - (double)m01*m10;
 260             int sdet = (det > 0) ? 1 : -1;
 261             double a = dly*m00 - dlx*m10;
 262             double b = dly*m01 - dlx*m11;
 263             double dh = PiscesMath.hypot(a, b);
 264             double div = sdet*lineWidth2/(65536.0*dh);
 265             double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11;
 266             double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2;
 267             dx = (int)(ddx*div);
 268             dy = (int)(ddy*div);
 269         }
 270 
 271         m[0] = dx;
 272         m[1] = dy;
 273     }
 274 
 275     private void ensureCapacity(int newrindex) {
 276         if (reverse.length < newrindex) {
 277             int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)];
 278             System.arraycopy(reverse, 0, tmp, 0, rindex);
 279             this.reverse = tmp;
 280         }
 281     }
 282 
 283     private boolean isCCW(int x0, int y0,
 284                           int x1, int y1,
 285                           int x2, int y2) {
 286         // These need to be longs to avoid overflows.
 287         long dx0 = (long)x1 - x0;
 288         long dy0 = (long)y1 - y0;
 289         long dx1 = (long)x2 - x1;
 290         long dy1 = (long)y2 - y1;
 291         return dx0*dy1 < dy0*dx1;
 292     }
 293 
 294     private boolean side(int x, int y, int x0, int y0, int x1, int y1) {
 295         long lx = x;
 296         long ly = y;
 297         long lx0 = x0;
 298         long ly0 = y0;
 299         long lx1 = x1;
 300         long ly1 = y1;
 301 
 302         return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0;
 303     }
 304 
 305     private int computeRoundJoin(int cx, int cy,
 306                                  int xa, int ya,
 307                                  int xb, int yb,
 308                                  int side,
 309                                  boolean flip,
 310                                  int[] join) {
 311         int px, py;
 312         int ncoords = 0;
 313 
 314         boolean centerSide;
 315         if (side == 0) {
 316             centerSide = side(cx, cy, xa, ya, xb, yb);
 317         } else {
 318             centerSide = (side == 1) ? true : false;
 319         }
 320         for (int i = 0; i < numPenSegments; i++) {
 321             px = cx + pen_dx[i];
 322             py = cy + pen_dy[i];
 323 
 324             boolean penSide = side(px, py, xa, ya, xb, yb);
 325             if (penSide != centerSide) {
 326                 penIncluded[i] = true;
 327             } else {
 328                 penIncluded[i] = false;
 329             }
 330         }
 331 
 332         int start = -1, end = -1;
 333         for (int i = 0; i < numPenSegments; i++) {
 334             if (penIncluded[i] &&
 335                 !penIncluded[(i + numPenSegments - 1) % numPenSegments]) {
 336                 start = i;
 337             }
 338             if (penIncluded[i] &&
 339                 !penIncluded[(i + 1) % numPenSegments]) {
 340                 end = i;
 341             }
 342         }
 343 
 344         if (end < start) {
 345             end += numPenSegments;
 346         }
 347 
 348         if (start != -1 && end != -1) {
 349             long dxa = cx + pen_dx[start] - xa;
 350             long dya = cy + pen_dy[start] - ya;
 351             long dxb = cx + pen_dx[start] - xb;
 352             long dyb = cy + pen_dy[start] - yb;
 353 
 354             boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb);
 355             int i = rev ? end : start;
 356             int incr = rev ? -1 : 1;
 357             while (true) {
 358                 int idx = i % numPenSegments;
 359                 px = cx + pen_dx[idx];
 360                 py = cy + pen_dy[idx];
 361                 join[ncoords++] = px;
 362                 join[ncoords++] = py;
 363                 if (i == (rev ? start : end)) {
 364                     break;
 365                 }
 366                 i += incr;
 367             }
 368         }
 369 
 370         return ncoords/2;
 371     }
 372 
 373     private static final long ROUND_JOIN_THRESHOLD = 1000L;
 374     private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L;
 375 
 376     private void drawRoundJoin(int x, int y,
 377                                int omx, int omy, int mx, int my,
 378                                int side,
 379                                boolean flip,
 380                                boolean rev,
 381                                long threshold) {
 382         if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {
 383             return;
 384         }
 385 
 386         long domx = (long)omx - mx;
 387         long domy = (long)omy - my;
 388         long len = domx*domx + domy*domy;
 389         if (len < threshold) {
 390             return;
 391         }
 392 
 393         if (rev) {
 394             omx = -omx;
 395             omy = -omy;
 396             mx = -mx;
 397             my = -my;
 398         }
 399 
 400         int bx0 = x + omx;
 401         int by0 = y + omy;
 402         int bx1 = x + mx;
 403         int by1 = y + my;
 404 
 405         int npoints = computeRoundJoin(x, y,
 406                                        bx0, by0, bx1, by1, side, flip,
 407                                        join);
 408         for (int i = 0; i < npoints; i++) {
 409             emitLineTo(join[2*i], join[2*i + 1], rev);
 410         }
 411     }
 412 
 413     // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1)
 414     // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1]
 415     private void computeMiter(int ix0, int iy0, int ix1, int iy1,
 416                               int ix0p, int iy0p, int ix1p, int iy1p,
 417                               int[] m) {
 418         long x0 = ix0;
 419         long y0 = iy0;
 420         long x1 = ix1;
 421         long y1 = iy1;
 422 
 423         long x0p = ix0p;
 424         long y0p = iy0p;
 425         long x1p = ix1p;
 426         long y1p = iy1p;
 427 
 428         long x10 = x1 - x0;
 429         long y10 = y1 - y0;
 430         long x10p = x1p - x0p;
 431         long y10p = y1p - y0p;
 432 
 433         long den = (x10*y10p - x10p*y10) >> 16;
 434         if (den == 0) {
 435             m[0] = ix0;
 436             m[1] = iy0;
 437             return;
 438         }
 439 
 440         long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16;
 441         m[0] = (int)(x0 + (t*x10)/den);
 442         m[1] = (int)(y0 + (t*y10)/den);
 443     }
 444 
 445     private void drawMiter(int px0, int py0,
 446                            int x0, int y0,
 447                            int x1, int y1,
 448                            int omx, int omy, int mx, int my,
 449                            boolean rev) {
 450         if (mx == omx && my == omy) {
 451             return;
 452         }
 453         if (px0 == x0 && py0 == y0) {
 454             return;
 455         }
 456         if (x0 == x1 && y0 == y1) {
 457             return;
 458         }
 459 
 460         if (rev) {
 461             omx = -omx;
 462             omy = -omy;
 463             mx = -mx;
 464             my = -my;
 465         }
 466 
 467         computeMiter(px0 + omx, py0 + omy, x0 + omx, y0 + omy,
 468                      x0 + mx, y0 + my, x1 + mx, y1 + my,
 469                      miter);
 470 
 471         // Compute miter length in untransformed coordinates
 472         long dx = (long)miter[0] - x0;
 473         long dy = (long)miter[1] - y0;
 474         long a = (dy*m00 - dx*m10) >> 16;
 475         long b = (dy*m01 - dx*m11) >> 16;
 476         long lenSq = a*a + b*b;
 477 
 478         if (lenSq < miterLimitSq) {
 479             emitLineTo(miter[0], miter[1], rev);
 480         }
 481     }
 482 
 483     private static void makeOverflowSafe(int[] coords, int wub) {
 484         int x = coords[0]; 
 485         int y = coords[1];
 486         if ((long)x + wub != x + wub) {
 487             x -= wub; 
 488         }
 489         if ((long)y + wub != y + wub) {
 490             y -= wub;
 491         }
 492         if ((long)x - wub != x - wub) {
 493             x += wub; 
 494         }
 495         if ((long)y - wub != y - wub) {
 496             y += wub;
 497         }
 498         coords[0] = x;
 499         coords[1] = y;
 500     }
 501 
 502     public void moveTo(int x0, int y0) {
 503         // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
 504 
 505         // We can't move too close to the borders set by Integer.MAX_VALUE or 
 506         // MIN_VALUE, because when we try to draw an endcap, or even just adding
 507         // offsets to (x0,y0), overflows might happen.
 508         int[] coords = {x0, y0};
 509         makeOverflowSafe(coords, wub);
 510         x0 = coords[0]; y0 = coords[1];
 511 
 512         if (lineToOrigin) {
 513             // not closing the path, do the previous lineTo
 514             lineToImpl(sx0, sy0, joinToOrigin);
 515             lineToOrigin = false;
 516         }
 517 
 518         if (prev == LINE_TO) {
 519             finish();
 520         }
 521 
 522         this.sx0 = this.x0 = x0;
 523         this.sy0 = this.y0 = y0;
 524         this.rindex = 0;
 525         this.started = false;
 526         this.joinSegment = false;
 527         this.prev = MOVE_TO;
 528     }
 529 
 530     boolean joinSegment = false;
 531 
 532     public void lineJoin() {
 533         // System.out.println("Stroker.lineJoin()");
 534         this.joinSegment = true;
 535     }
 536 
 537     public void lineTo(int x1, int y1) {
 538         // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")");
 539 
 540         if (lineToOrigin) {
 541             if (x1 == sx0 && y1 == sy0) {
 542                 // staying in the starting point
 543                 return;
 544             }
 545 
 546             // not closing the path, do the previous lineTo
 547             lineToImpl(sx0, sy0, joinToOrigin);
 548             lineToOrigin = false;
 549         } else if (x1 == x0 && y1 == y0) {
 550             return;
 551         } else if (x1 == sx0 && y1 == sy0) {
 552             lineToOrigin = true;
 553             joinToOrigin = joinSegment;
 554             joinSegment = false;
 555             return;
 556         }
 557 
 558         lineToImpl(x1, y1, joinSegment);
 559         joinSegment = false;
 560     }
 561 
 562     private void lineToImpl(int x1, int y1, boolean joinSegment) {
 563         computeOffset(x0, y0, x1, y1, offset);
 564         int mx = offset[0];
 565         int my = offset[1];
 566 
 567         // Now we want to make sure that none of the operations involving line
 568         // drawing will overflow. These include adding pen points to (x1,y1),
 569         // drawing a square end cap, and adding/subtracting mx and my to x1 
 570         // and y1 (in the calls to emitLineTo). However, it does not protect
 571         // against possible overflows when drawing miters.
 572         // TODO: protect against possible overflows when drawing miters.
 573         int[] coords = {x1, y1};
 574         makeOverflowSafe(coords, wub);
 575         x1 = coords[0]; y1 = coords[1];
 576 
 577         if (!started) {
 578             emitMoveTo(x0 + mx, y0 + my);
 579             this.sx1 = x1;
 580             this.sy1 = y1;
 581             this.mx0 = mx;
 582             this.my0 = my;
 583             started = true;
 584         } else {
 585             boolean ccw = isCCW(px0, py0, x0, y0, x1, y1);
 586             if (joinSegment) {
 587                 if (joinStyle == JOIN_MITER) {
 588                     drawMiter(px0, py0, x0, y0, x1, y1, omx, omy, mx, my,
 589                               ccw);
 590                 } else if (joinStyle == JOIN_ROUND) {
 591                     drawRoundJoin(x0, y0,
 592                                   omx, omy,
 593                                   mx, my, 0, false, ccw,
 594                                   ROUND_JOIN_THRESHOLD);
 595                 }
 596             } else {
 597                 // Draw internal joins as round
 598                 drawRoundJoin(x0, y0,
 599                               omx, omy,
 600                               mx, my, 0, false, ccw,
 601                               ROUND_JOIN_INTERNAL_THRESHOLD);
 602             }
 603 
 604             emitLineTo(x0, y0, !ccw);
 605         }
 606 
 607         emitLineTo(x0 + mx, y0 + my, false);
 608         emitLineTo(x1 + mx, y1 + my, false);
 609 
 610         emitLineTo(x0 - mx, y0 - my, true);
 611         emitLineTo(x1 - mx, y1 - my, true);
 612 
 613         lx0 = x1 + mx; ly0 = y1 + my;
 614         lx0p = x1 - mx; ly0p = y1 - my;
 615         lx1 = x1; ly1 = y1;
 616 
 617         this.omx = mx;
 618         this.omy = my;
 619         this.px0 = x0;
 620         this.py0 = y0;
 621         this.x0 = x1;
 622         this.y0 = y1;
 623         this.prev = LINE_TO;
 624     }
 625 
 626     public void close() {
 627         // System.out.println("Stroker.close()");
 628 
 629         if (lineToOrigin) {
 630             // ignore the previous lineTo
 631             lineToOrigin = false;
 632         }
 633 
 634         if (!started) {
 635             finish();
 636             return;
 637         }
 638 
 639         computeOffset(x0, y0, sx0, sy0, offset);
 640         int mx = offset[0];
 641         int my = offset[1];
 642 
 643         // Draw penultimate join
 644         boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0);
 645         if (joinSegment) {
 646             if (joinStyle == JOIN_MITER) {
 647                 drawMiter(px0, py0, x0, y0, sx0, sy0, omx, omy, mx, my, ccw);
 648             } else if (joinStyle == JOIN_ROUND) {
 649                 drawRoundJoin(x0, y0, omx, omy, mx, my, 0, false, ccw,
 650                               ROUND_JOIN_THRESHOLD);
 651             }
 652         } else {
 653             // Draw internal joins as round
 654             drawRoundJoin(x0, y0,
 655                           omx, omy,
 656                           mx, my, 0, false, ccw,
 657                           ROUND_JOIN_INTERNAL_THRESHOLD);
 658         }
 659 
 660         emitLineTo(x0 + mx, y0 + my);
 661         emitLineTo(sx0 + mx, sy0 + my);
 662 
 663         ccw = isCCW(x0, y0, sx0, sy0, sx1, sy1);
 664 
 665         // Draw final join on the outside
 666         if (!ccw) {
 667             if (joinStyle == JOIN_MITER) {
 668                 drawMiter(x0, y0, sx0, sy0, sx1, sy1,
 669                           mx, my, mx0, my0, false);
 670             } else if (joinStyle == JOIN_ROUND) {
 671                 drawRoundJoin(sx0, sy0, mx, my, mx0, my0, 0, false, false,
 672                               ROUND_JOIN_THRESHOLD);
 673             }
 674         }
 675 
 676         emitLineTo(sx0 + mx0, sy0 + my0);
 677         emitLineTo(sx0 - mx0, sy0 - my0);  // same as reverse[0], reverse[1]
 678 
 679         // Draw final join on the inside
 680         if (ccw) {
 681             if (joinStyle == JOIN_MITER) {
 682                 drawMiter(x0, y0, sx0, sy0, sx1, sy1,
 683                           -mx, -my, -mx0, -my0, false);
 684             } else if (joinStyle == JOIN_ROUND) {
 685                 drawRoundJoin(sx0, sy0, -mx, -my, -mx0, -my0, 0,
 686                               true, false,
 687                               ROUND_JOIN_THRESHOLD);
 688             }
 689         }
 690 
 691         emitLineTo(sx0 - mx, sy0 - my);
 692         emitLineTo(x0 - mx, y0 - my);
 693         for (int i = rindex - 2; i >= 0; i -= 2) {
 694             emitLineTo(reverse[i], reverse[i + 1]);
 695         }
 696 
 697         this.x0 = this.sx0;
 698         this.y0 = this.sy0;
 699         this.rindex = 0;
 700         this.started = false;
 701         this.joinSegment = false;
 702         this.prev = CLOSE;
 703         emitClose();
 704     }
 705 
 706     public void end() {
 707         // System.out.println("Stroker.end()");
 708 
 709         if (lineToOrigin) {
 710             // not closing the path, do the previous lineTo
 711             lineToImpl(sx0, sy0, joinToOrigin);
 712             lineToOrigin = false;
 713         }
 714 
 715         if (prev == LINE_TO) {
 716             finish();
 717         }
 718 
 719         output.end();
 720         this.joinSegment = false;
 721         this.prev = MOVE_TO;
 722     }
 723 
 724     long lineLength(long ldx, long ldy) {
 725         long ldet = ((long)m00*m11 - (long)m01*m10) >> 16;
 726         long la = ((long)ldy*m00 - (long)ldx*m10)/ldet;
 727         long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet;
 728         long llen = (int)PiscesMath.hypot(la, lb);
 729         return llen;
 730     }
 731 
 732     private void finish() {
 733         if (capStyle == CAP_ROUND) {
 734             drawRoundJoin(x0, y0,
 735                           omx, omy, -omx, -omy, 1, false, false,
 736                           ROUND_JOIN_THRESHOLD);
 737         } else if (capStyle == CAP_SQUARE) {
 738             long ldx = (long)(px0 - x0);
 739             long ldy = (long)(py0 - y0);
 740             long llen = lineLength(ldx, ldy);
 741             long s = (long)lineWidth2*65536/llen;
 742 
 743             int capx = x0 - (int)(ldx*s >> 16);
 744             int capy = y0 - (int)(ldy*s >> 16);
 745 
 746             emitLineTo(capx + omx, capy + omy);
 747             emitLineTo(capx - omx, capy - omy);
 748         }
 749 
 750         for (int i = rindex - 2; i >= 0; i -= 2) {
 751             emitLineTo(reverse[i], reverse[i + 1]);
 752         }
 753         this.rindex = 0;
 754 
 755         if (capStyle == CAP_ROUND) {
 756             drawRoundJoin(sx0, sy0,
 757                           -mx0, -my0, mx0, my0, 1, false, false,
 758                           ROUND_JOIN_THRESHOLD);
 759         } else if (capStyle == CAP_SQUARE) {
 760             long ldx = (long)(sx1 - sx0);
 761             long ldy = (long)(sy1 - sy0);
 762             long llen = lineLength(ldx, ldy);
 763             long s = (long)lineWidth2*65536/llen;
 764 
 765             int capx = sx0 - (int)(ldx*s >> 16);
 766             int capy = sy0 - (int)(ldy*s >> 16);
 767 
 768             emitLineTo(capx - mx0, capy - my0);
 769             emitLineTo(capx + mx0, capy + my0);
 770         }
 771 
 772         emitClose();
 773         this.joinSegment = false;
 774     }
 775 
 776     private void emitMoveTo(int x0, int y0) {
 777         // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
 778         output.moveTo(x0, y0);
 779     }
 780 
 781     private void emitLineTo(int x1, int y1) {
 782         // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
 783         output.lineTo(x1, y1);
 784     }
 785 
 786     private void emitLineTo(int x1, int y1, boolean rev) {
 787         if (rev) {
 788             ensureCapacity(rindex + 2);
 789             reverse[rindex++] = x1;
 790             reverse[rindex++] = y1;
 791         } else {
 792             emitLineTo(x1, y1);
 793         }
 794     }
 795 
 796     private void emitClose() {
 797         // System.out.println("Stroker.emitClose()");
 798         output.close();
 799     }
 800 }