1 /*
   2  * Copyright (c) 2009, 2014, 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.prism;
  27 
  28 import com.sun.javafx.geom.Area;
  29 import com.sun.javafx.geom.GeneralShapePair;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.PathConsumer2D;
  32 import com.sun.javafx.geom.PathIterator;
  33 import com.sun.javafx.geom.RoundRectangle2D;
  34 import com.sun.javafx.geom.Shape;
  35 import com.sun.javafx.geom.ShapePair;
  36 import com.sun.javafx.geom.transform.BaseTransform;
  37 import com.sun.openpisces.Dasher;
  38 import com.sun.openpisces.Stroker;
  39 import com.sun.prism.impl.shape.OpenPiscesPrismUtils;
  40 
  41 public final class BasicStroke {
  42     public static final int CAP_BUTT = Stroker.CAP_BUTT;
  43     public static final int CAP_ROUND = Stroker.CAP_ROUND;
  44     public static final int CAP_SQUARE = Stroker.CAP_SQUARE;
  45 
  46     public static final int JOIN_BEVEL = Stroker.JOIN_BEVEL;
  47     public static final int JOIN_MITER = Stroker.JOIN_MITER;
  48     public static final int JOIN_ROUND = Stroker.JOIN_ROUND;
  49 
  50     public static final int TYPE_CENTERED = 0;
  51     public static final int TYPE_INNER = 1;
  52     public static final int TYPE_OUTER = 2;
  53 
  54     float width;
  55     int type;
  56     int cap;
  57     int join;
  58     float miterLimit;
  59     float dash[];
  60     float dashPhase;
  61 
  62     public BasicStroke() {
  63         set(TYPE_CENTERED, 1.0f, CAP_SQUARE, JOIN_MITER, 10f);
  64     }
  65 
  66     public BasicStroke(float width, int cap, int join, float miterLimit) {
  67         set(TYPE_CENTERED, width, cap, join, miterLimit);
  68     }
  69 
  70     public BasicStroke(int type, float width,
  71                        int cap, int join, float miterLimit)
  72     {
  73         set(type, width, cap, join, miterLimit);
  74     }
  75 
  76     public BasicStroke(float width, int cap, int join, float miterLimit,
  77                        float[] dash, float dashPhase)
  78     {
  79         set(TYPE_CENTERED, width, cap, join, miterLimit);
  80         set(dash, dashPhase);
  81     }
  82 
  83     public BasicStroke(float width, int cap, int join, float miterLimit,
  84                        double[] dash, float dashPhase)
  85     {
  86         set(TYPE_CENTERED, width, cap, join, miterLimit);
  87         set(dash, dashPhase);
  88     }
  89 
  90     public BasicStroke(int type, float width, int cap, int join, float miterLimit,
  91                        float[] dash, float dashPhase)
  92     {
  93         set(type, width, cap, join, miterLimit);
  94         set(dash, dashPhase);
  95     }
  96 
  97     public BasicStroke(int type, float width, int cap, int join, float miterLimit,
  98                        double[] dash, float dashPhase)
  99     {
 100         set(type, width, cap, join, miterLimit);
 101         set(dash, dashPhase);
 102     }
 103 
 104     public void set(int type, float width,
 105                     int cap, int join, float miterLimit)
 106     {
 107         if (type != TYPE_CENTERED && type != TYPE_INNER && type != TYPE_OUTER) {
 108             throw new IllegalArgumentException("illegal type");
 109         }
 110         if (width < 0.0f) {
 111             throw new IllegalArgumentException("negative width");
 112         }
 113         if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
 114             throw new IllegalArgumentException("illegal end cap value");
 115         }
 116         if (join == JOIN_MITER) {
 117             if (miterLimit < 1.0f) {
 118                 throw new IllegalArgumentException("miter limit < 1");
 119             }
 120         } else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
 121             throw new IllegalArgumentException("illegal line join value");
 122         }
 123         this.type = type;
 124         this.width = width;
 125         this.cap = cap;
 126         this.join = join;
 127         this.miterLimit = miterLimit;
 128     }
 129 
 130     public void set(float dash[], float dashPhase) {
 131         if (dash != null) {
 132             boolean allzero = true;
 133             for (int i = 0; i < dash.length; i++) {
 134                 float d = dash[i];
 135                 if (d > 0.0) {
 136                     allzero = false;
 137                 } else if (d < 0.0) {
 138                     throw new IllegalArgumentException("negative dash length");
 139                 }
 140             }
 141             if (allzero) {
 142                 throw new IllegalArgumentException("dash lengths all zero");
 143             }
 144         }
 145         this.dash = dash;
 146         this.dashPhase = dashPhase;
 147     }
 148 
 149     public void set(double dash[], float dashPhase) {
 150         if (dash != null) {
 151             float newdashes[] = new float[dash.length];
 152             boolean allzero = true;
 153             for (int i = 0; i < dash.length; i++) {
 154                 float d = (float) dash[i];
 155                 if (d > 0.0) {
 156                     allzero = false;
 157                 } else if (d < 0.0) {
 158                     throw new IllegalArgumentException("negative dash length");
 159                 }
 160                 newdashes[i] = d;
 161             }
 162             if (allzero) {
 163                 throw new IllegalArgumentException("dash lengths all zero");
 164             }
 165             this.dash = newdashes;
 166         } else {
 167             this.dash = null;
 168         }
 169         this.dashPhase = dashPhase;
 170     }
 171 
 172     /**
 173      * Returns the stroke type, one of {@code TYPE_CENTERED},
 174      * {@code TYPE_INNER}, or {@code TYPE_OUTER}.
 175      * @return the stroke type
 176      */
 177     public int getType() {
 178         return type;
 179     }
 180 
 181     /**
 182      * Returns the line width.  Line width is represented in user space,
 183      * which is the default-coordinate space used by Java 2D.  See the
 184      * <code>Graphics2D</code> class comments for more information on
 185      * the user space coordinate system.
 186      * @return the line width of this <code>BasicStroke</code>.
 187      */
 188     public float getLineWidth() {
 189         return width;
 190     }
 191 
 192     /**
 193      * Returns the end cap style.
 194      * @return the end cap style of this <code>BasicStroke</code> as one
 195      * of the static <code>int</code> values that define possible end cap
 196      * styles.
 197      */
 198     public int getEndCap() {
 199         return cap;
 200     }
 201 
 202     /**
 203      * Returns the line join style.
 204      * @return the line join style of the <code>BasicStroke</code> as one
 205      * of the static <code>int</code> values that define possible line
 206      * join styles.
 207      */
 208     public int getLineJoin() {
 209         return join;
 210     }
 211 
 212     /**
 213      * Returns the limit of miter joins.
 214      * @return the limit of miter joins of the <code>BasicStroke</code>.
 215      */
 216     public float getMiterLimit() {
 217         return miterLimit;
 218     }
 219 
 220     /**
 221      * Returns true if this stroke object will apply dashing attributes
 222      * to the path.
 223      * @return whether the stroke has dashes
 224      */
 225     public boolean isDashed() {
 226         return (dash != null);
 227     }
 228     /**
 229      * Returns the array representing the lengths of the dash segments.
 230      * Alternate entries in the array represent the user space lengths
 231      * of the opaque and transparent segments of the dashes.
 232      * As the pen moves along the outline of the <code>Shape</code>
 233      * to be stroked, the user space
 234      * distance that the pen travels is accumulated.  The distance
 235      * value is used to index into the dash array.
 236      * The pen is opaque when its current cumulative distance maps
 237      * to an even element of the dash array and transparent otherwise.
 238      * @return the dash array.
 239      */
 240     public float[] getDashArray() {
 241         return dash;
 242     }
 243 
 244     /**
 245      * Returns the current dash phase.
 246      * The dash phase is a distance specified in user coordinates that
 247      * represents an offset into the dashing pattern. In other words, the dash
 248      * phase defines the point in the dashing pattern that will correspond to
 249      * the beginning of the stroke.
 250      * @return the dash phase as a <code>float</code> value.
 251      */
 252     public float getDashPhase() {
 253         return dashPhase;
 254     }
 255 
 256     public Shape createStrokedShape(Shape s) {
 257         Shape ret;
 258         if (s instanceof RoundRectangle2D) {
 259             ret = strokeRoundRectangle((RoundRectangle2D) s);
 260         } else {
 261             ret = null;
 262         }
 263         if (ret != null) {
 264             return ret;
 265         }
 266 
 267         ret = createCenteredStrokedShape(s);
 268 
 269         if (type == TYPE_INNER) {
 270             ret = makeIntersectedShape(ret, s);
 271         } else if (type == TYPE_OUTER) {
 272             ret = makeSubtractedShape(ret, s);
 273         }
 274         return ret;
 275     }
 276 
 277     private boolean isCW(final float dx1, final float dy1,
 278                          final float dx2, final float dy2)
 279     {
 280         return dx1 * dy2 <= dy1 * dx2;
 281     }
 282 
 283     private void computeOffset(final float lx, final float ly,
 284                                final float w, final float[] m, int off) {
 285         final float len = (float) Math.sqrt(lx * lx + ly * ly);
 286         if (len == 0) {
 287             m[off + 0] = m[off + 1] = 0;
 288         } else {
 289             m[off + 0] = (ly * w) / len;
 290             m[off + 1] = -(lx * w) / len;
 291         }
 292     }
 293 
 294     private void computeMiter(final float x0, final float y0,
 295                               final float x1, final float y1,
 296                               final float x0p, final float y0p,
 297                               final float x1p, final float y1p,
 298                               final float[] m, int off)
 299     {
 300         float x10 = x1 - x0;
 301         float y10 = y1 - y0;
 302         float x10p = x1p - x0p;
 303         float y10p = y1p - y0p;
 304 
 305         // if this is 0, the lines are parallel. If they go in the
 306         // same direction, there is no intersection so m[off] and
 307         // m[off+1] will contain infinity, so no miter will be drawn.
 308         // If they go in the same direction that means that the start of the
 309         // current segment and the end of the previous segment have the same
 310         // tangent, in which case this method won't even be involved in
 311         // miter drawing because it won't be called by drawMiter (because
 312         // (mx == omx && my == omy) will be true, and drawMiter will return
 313         // immediately).
 314         float den = x10*y10p - x10p*y10;
 315         float t = x10p*(y0-y0p) - y10p*(x0-x0p);
 316         t /= den;
 317         m[off++] = x0 + t*x10;
 318         m[off] = y0 + t*y10;
 319     }
 320 
 321 
 322     // taken from com.sun.javafx.geom.Shape.accumulateQuad (added the width)
 323     private void accumulateQuad(float bbox[], int off,
 324                                float v0, float vc, float v1, float w)
 325     {
 326         // Breaking this quad down into a polynomial:
 327         // eqn[0] = v0;
 328         // eqn[1] = vc + vc - v0 - v0;
 329         // eqn[2] = v0 - vc - vc + v1;
 330         // Deriving the polynomial:
 331         // eqn'[0] = 1*eqn[1] = 2*(vc-v0)
 332         // eqn'[1] = 2*eqn[2] = 2*((v1-vc)-(vc-v0))
 333         // Solving for zeroes on the derivative:
 334         // e1*t + e0 = 0
 335         // t = -e0/e1;
 336         // t = -2(vc-v0) / 2((v1-vc)-(vc-v0))
 337         // t = (v0-vc) / (v1-vc+v0-vc)
 338         float num = v0 - vc;
 339         float den = v1 - vc + num;
 340         if (den != 0f) {
 341             float t = num / den;
 342             if (t > 0 && t < 1) {
 343                 float u = 1f - t;
 344                 float v = v0 * u * u + 2 * vc * t * u + v1 * t * t;
 345                 if (bbox[off] > v - w) bbox[off] = v - w;
 346                 if (bbox[off+2] < v + w) bbox[off+2] = v + w;
 347             }
 348         }
 349     }
 350 
 351     // taken from com.sun.javafx.geom.Shape.accumulateCubic (added the width)
 352     private void accumulateCubic(float bbox[], int off, float t,
 353                                 float v0, float vc0, float vc1, float v1, float w)
 354     {
 355         if (t > 0 && t < 1) {
 356             float u = 1f - t;
 357             float v =        v0 * u * u * u
 358                       + 3 * vc0 * t * u * u
 359                       + 3 * vc1 * t * t * u
 360                       +      v1 * t * t * t;
 361             if (bbox[off] > v - w) bbox[off] = v - w;
 362             if (bbox[off+2] < v + w) bbox[off+2] = v + w;
 363         }
 364     }
 365 
 366     // taken from com.sun.javafx.geom.Shape.accumulateCubic (added the width)
 367     private void accumulateCubic(float bbox[], int off,
 368                                 float v0, float vc0, float vc1, float v1, float w)
 369     {
 370         // Breaking this cubic down into a polynomial:
 371         // eqn[0] = v0;
 372         // eqn[1] = (vc0 - v0) * 3f;
 373         // eqn[2] = (vc1 - vc0 - vc0 + v0) * 3f;
 374         // eqn[3] = v1 + (vc0 - vc1) * 3f - v0;
 375         // Deriving the polynomial:
 376         // eqn'[0] = 1*eqn[1] = 3(vc0-v0)
 377         // eqn'[1] = 2*eqn[2] = 6((vc1-vc0)-(vc0-v0))
 378         // eqn'[2] = 3*eqn[3] = 3((v1-vc1)-2(vc1-vc0)+(vc0-v0))
 379         // Solving for zeroes on the derivative:
 380         // e2*t*t + e1*t + e0 = a*t*t + b*t + c = 0
 381         // Note that in solving for 0 we can divide all e0,e1,e2 by 3
 382         // t = (-b +/- sqrt(b*b-4ac))/2a
 383         float c = vc0 - v0;
 384         float b = 2f * ((vc1 - vc0) - c);
 385         float a = (v1 - vc1) - b - c;
 386         if (a == 0f) {
 387             // The quadratic parabola has degenerated to a line.
 388             if (b == 0f) {
 389                 // The line has degenerated to a constant.
 390                 return;
 391             }
 392             accumulateCubic(bbox, off, -c/b, v0, vc0, vc1, v1, w);
 393         } else {
 394             // From Numerical Recipes, 5.6, Quadratic and Cubic Equations
 395             float d = b * b - 4f * a * c;
 396             if (d < 0f) {
 397                 // If d < 0.0, then there are no roots
 398                 return;
 399             }
 400             d = (float) Math.sqrt(d);
 401             // For accuracy, calculate one root using:
 402             //     (-b +/- d) / 2a
 403             // and the other using:
 404             //     2c / (-b +/- d)
 405             // Choose the sign of the +/- so that b+d gets larger in magnitude
 406             if (b < 0f) {
 407                 d = -d;
 408             }
 409             float q = (b + d) / -2f;
 410             // We already tested a for being 0 above
 411             accumulateCubic(bbox, off, q/a, v0, vc0, vc1, v1, w);
 412             if (q != 0f) {
 413                 accumulateCubic(bbox, off, c/q, v0, vc0, vc1, v1, w);
 414             }
 415         }
 416     }
 417 
 418     // Basically any type of transform that does not violate a uniform
 419     // unsheared 2D scale.  We may have to scale the associated line width,
 420     // but we can accumulate everything in device space with no problems.
 421     private static final int SAFE_ACCUMULATE_MASK =
 422         (BaseTransform.TYPE_FLIP |
 423          BaseTransform.TYPE_GENERAL_ROTATION |
 424          BaseTransform.TYPE_QUADRANT_ROTATION |
 425          BaseTransform.TYPE_TRANSLATION |
 426          BaseTransform.TYPE_UNIFORM_SCALE);
 427 
 428     public void accumulateShapeBounds(float bbox[], Shape shape, BaseTransform tx) {
 429         if (type == TYPE_INNER) {
 430             Shape.accumulate(bbox, shape, tx);
 431             return;
 432         }
 433         if ((tx.getType() & ~SAFE_ACCUMULATE_MASK) != 0) {
 434             // This is a work-around for RT-15648.  That bug still applies here
 435             // since we should be optimizing that case, but at least with this
 436             // work-around, someone who calls this method, and is not aware of
 437             // that bug, will not be bitten by a bad answer.
 438             Shape.accumulate(bbox, createStrokedShape(shape), tx);
 439             return;
 440         }
 441         PathIterator pi = shape.getPathIterator(tx);
 442         boolean lastSegmentMove = true;
 443         float coords[] = new float[6];
 444         float w = type == TYPE_CENTERED ? getLineWidth() / 2 : getLineWidth();
 445         // Length(Transform(w, 0)) == w * Length(Transform(1, 0))
 446         w *= Math.hypot(tx.getMxx(), tx.getMyx());
 447         // starting x,y; previous x0, y0 and current x1,y1
 448         float sx = 0f, sy = 0f, x0 = 0f, y0 = 0f, x1, y1;
 449         // starting delta x,y; delta x,y; previous delta x,y
 450         float sdx = 0f, sdy = 0f, dx, dy, pdx = 0f, pdy = 0f;
 451         // current offset
 452         float o[] = new float[4];
 453         // previous offset; starting offset
 454         float pox = 0f, poy = 0f, sox = 0f, soy = 0f;
 455 
 456         while (!pi.isDone()) {
 457             int cur = pi.currentSegment(coords);
 458             switch (cur) {
 459                 case PathIterator.SEG_MOVETO:
 460                     if (!lastSegmentMove) {
 461                         accumulateCap(pdx, pdy, x0, y0, pox, poy, bbox, w);
 462                         accumulateCap(-sdx, -sdy, sx, sy, -sox, -soy, bbox, w);
 463                     }
 464 
 465                     x0 = sx = coords[0];
 466                     y0 = sy = coords[1];
 467                     break;
 468                 case PathIterator.SEG_LINETO:
 469                     x1 = coords[0];
 470                     y1 = coords[1];
 471                     dx = x1 - x0;
 472                     dy = y1 - y0;
 473                     if (dx == 0f && dy == 0f) {
 474                         // Ideally these segments should be ignored, but both
 475                         // Java 2D and OpenPisces treat this case as if we
 476                         // were joining to a segment that was horizontal.
 477                         dx = 1f;
 478                     }
 479 
 480                     computeOffset(dx, dy, w, o, 0);
 481 
 482                     if (!lastSegmentMove) {
 483                         accumulateJoin(pdx, pdy, dx, dy, x0, y0, pox, poy, o[0], o[1], bbox, w);
 484                     }
 485 
 486                     x0 = x1;
 487                     y0 = y1;
 488                     pdx = dx;
 489                     pdy = dy;
 490                     pox = o[0];
 491                     poy = o[1];
 492                     if (lastSegmentMove) {
 493                         sdx = pdx;
 494                         sdy = pdy;
 495                         sox = pox;
 496                         soy = poy;
 497                     }
 498                     break;
 499                 case PathIterator.SEG_QUADTO:
 500                     x1 = coords[2];
 501                     y1 = coords[3];
 502                     dx = coords[0] - x0;
 503                     dy = coords[1] - y0;
 504 
 505                     computeOffset(dx, dy, w, o, 0);
 506                     if (!lastSegmentMove) {
 507                         accumulateJoin(pdx, pdy, dx, dy, x0, y0, pox, poy, o[0], o[1], bbox, w);
 508                     }
 509 
 510                     if (bbox[0] > coords[0] - w || bbox[2] < coords[0] + w) {
 511                         accumulateQuad(bbox, 0, x0, coords[0], x1, w);
 512                     }
 513                     if (bbox[1] > coords[1] - w || bbox[3] < coords[1] + w) {
 514                         accumulateQuad(bbox, 1, y0, coords[1], y1, w);
 515                     }
 516                     x0 = x1;
 517                     y0 = y1;
 518                     if (lastSegmentMove) {
 519                         sdx = dx;
 520                         sdy = dy;
 521                         sox = o[0];
 522                         soy = o[1];
 523                     }
 524                     pdx = x1 - coords[0];
 525                     pdy = y1 - coords[1];
 526                     computeOffset(pdx, pdy, w, o, 0);
 527                     pox = o[0];
 528                     poy = o[1];
 529                     break;
 530                 case PathIterator.SEG_CUBICTO:
 531                     x1 = coords[4];
 532                     y1 = coords[5];
 533                     dx = coords[0] - x0;
 534                     dy = coords[1] - y0;
 535 
 536                     computeOffset(dx, dy, w, o, 0);
 537                     if (!lastSegmentMove) {
 538                         accumulateJoin(pdx, pdy, dx, dy, x0, y0, pox, poy, o[0], o[1], bbox, w);
 539                     }
 540 
 541                     if (bbox[0] > coords[0] - w || bbox[2] < coords[0] + w ||
 542                         bbox[0] > coords[2] - w || bbox[2] < coords[2] + w)
 543                     {
 544                         accumulateCubic(bbox, 0, x0, coords[0], coords[2], x1, w);
 545                     }
 546                     if (bbox[1] > coords[1] - w|| bbox[3] < coords[1] + w ||
 547                         bbox[1] > coords[3] - w|| bbox[3] < coords[3] + w)
 548                     {
 549                         accumulateCubic(bbox, 1, y0, coords[1], coords[3], y1, w);
 550                     }
 551                     x0 = x1;
 552                     y0 = y1;
 553                     if (lastSegmentMove) {
 554                         sdx = dx;
 555                         sdy = dy;
 556                         sox = o[0];
 557                         soy = o[1];
 558                     }
 559                     pdx = x1 - coords[2];
 560                     pdy = y1 - coords[3];
 561                     computeOffset(pdx, pdy, w, o, 0);
 562                     pox = o[0];
 563                     poy = o[1];
 564                     break;
 565                 case PathIterator.SEG_CLOSE:
 566                     dx = sx - x0;
 567                     dy = sy - y0;
 568                     x1 = sx;
 569                     y1 = sy;
 570 
 571                     if (!lastSegmentMove) {
 572                         computeOffset(sdx, sdy, w, o, 2);
 573                         if (dx == 0 && dy == 0) {
 574                             accumulateJoin(pdx, pdy, sdx, sdy, sx, sy, pox, poy, o[2], o[3], bbox, w);
 575                         } else {
 576                             computeOffset(dx, dy, w, o, 0);
 577                             accumulateJoin(pdx, pdy, dx, dy, x0, y0, pox, poy, o[0], o[1], bbox, w);
 578                             accumulateJoin(dx, dy, sdx, sdy, x1, y1, o[0], o[1], o[2], o[3], bbox, w);
 579                         }
 580                     }
 581                     x0 = x1;
 582                     y0 = y1;
 583                     break;
 584             }
 585             // Close behaves like a move in certain ways - after close, line will draw a cap,
 586             // like if the close implicitely did move to the original coordinates
 587             lastSegmentMove = cur == PathIterator.SEG_MOVETO || cur == PathIterator.SEG_CLOSE;
 588             pi.next();
 589         }
 590 
 591         if (!lastSegmentMove) {
 592             accumulateCap(pdx, pdy, x0, y0, pox, poy, bbox, w);
 593             accumulateCap(-sdx, -sdy, sx, sy, -sox, -soy, bbox, w);
 594         }
 595     }
 596 
 597     private void accumulate(float o0, float o1, float o2, float o3, float[] bbox) {
 598         if (o0 <= o2) {
 599             if (o0 < bbox[0]) bbox[0] = o0;
 600             if (o2 > bbox[2]) bbox[2] = o2;
 601         } else {
 602             if (o2 < bbox[0]) bbox[0] = o2;
 603             if (o0 > bbox[2]) bbox[2] = o0;
 604         }
 605         if (o1 <= o3) {
 606             if (o1 < bbox[1]) bbox[1] = o1;
 607             if (o3 > bbox[3]) bbox[3] = o3;
 608         } else {
 609             if (o3 < bbox[1]) bbox[1] = o3;
 610             if (o1 > bbox[3]) bbox[3] = o1;
 611         }
 612     }
 613 
 614     private void accumulateOrdered(float o0, float o1, float o2, float o3, float[] bbox) {
 615         if (o0 < bbox[0]) bbox[0] = o0;
 616         if (o2 > bbox[2]) bbox[2] = o2;
 617         if (o1 < bbox[1]) bbox[1] = o1;
 618         if (o3 > bbox[3]) bbox[3] = o3;
 619     }
 620 
 621 
 622     private void accumulateJoin(float pdx, float pdy, float dx, float dy, float x0, float y0,
 623                                 float pox, float poy, float ox, float oy, float[] bbox, float w) {
 624 
 625         if (join == JOIN_BEVEL) {
 626             accumulateBevel(x0, y0, pox, poy, ox, oy, bbox);
 627         } else if (join == JOIN_MITER) {
 628             accumulateMiter(pdx, pdy, dx, dy, pox, poy, ox, oy, x0, y0, bbox, w);
 629         } else { // JOIN_ROUND
 630             accumulateOrdered(x0 - w, y0 - w, x0 + w, y0 + w, bbox);
 631         }
 632 
 633 
 634     }
 635 
 636     private void accumulateCap(float dx, float dy, float x0, float y0,
 637                                float ox, float oy, float[] bbox, float w) {
 638         if (cap == CAP_SQUARE) {
 639             accumulate(x0 + ox - oy, y0 + oy + ox, x0 - ox - oy, y0 - oy + ox, bbox);
 640         } else if (cap == CAP_BUTT) {
 641             accumulate(x0 + ox, y0 + oy, x0 - ox, y0 - oy, bbox);
 642         } else { //cap == CAP_ROUND
 643             accumulateOrdered(x0 - w, y0 - w, x0 + w, y0 + w, bbox);
 644         }
 645 
 646     }
 647 
 648     private float[] tmpMiter = new float[2];
 649 
 650     private void accumulateMiter(float pdx, float pdy, float dx, float dy,
 651                                     float pox, float poy, float ox, float oy,
 652                                     float x0, float y0, float[] bbox, float w) {
 653         // Always accumulate bevel for cases of degenerate miters...
 654         accumulateBevel(x0, y0, pox, poy, ox, oy, bbox);
 655 
 656         boolean cw = isCW(pdx, pdy, dx, dy);
 657 
 658         if (cw) {
 659             pox = -pox;
 660             poy = -poy;
 661             ox = -ox;
 662             oy = -oy;
 663         }
 664 
 665         computeMiter((x0 - pdx) + pox, (y0 - pdy) + poy, x0 + pox, y0 + poy,
 666                      (x0 + dx) + ox, (y0 + dy) + oy, x0 + ox, y0 + oy,
 667                      tmpMiter, 0);
 668         float lenSq = (tmpMiter[0] - x0) * (tmpMiter[0] - x0) + (tmpMiter[1] - y0) * (tmpMiter[1] - y0);
 669 
 670         float miterLimitWidth = miterLimit * w;
 671         if (lenSq < miterLimitWidth * miterLimitWidth) {
 672             accumulateOrdered(tmpMiter[0], tmpMiter[1], tmpMiter[0], tmpMiter[1], bbox);
 673         }
 674     }
 675 
 676 
 677     private void accumulateBevel(float x0, float y0, float pox, float poy, float ox, float oy, float[] bbox) {
 678         accumulate(x0 + pox, y0 + poy, x0 - pox, y0 - poy, bbox);
 679         accumulate(x0 + ox, y0 + oy, x0 - ox, y0 - oy, bbox);
 680     }
 681 
 682     public Shape createCenteredStrokedShape(Shape s) {
 683         Path2D p2d = new Path2D(Path2D.WIND_NON_ZERO);
 684         float lw = (type == TYPE_CENTERED) ? width : width * 2.0f;
 685         PathConsumer2D pc2d =
 686             new Stroker(p2d, lw, cap, join, miterLimit);
 687         if (dash != null) {
 688             pc2d = new Dasher(pc2d, dash, dashPhase);
 689         }
 690         OpenPiscesPrismUtils.feedConsumer(s.getPathIterator(null), pc2d);
 691         return p2d;
 692     }
 693 
 694     static final float SQRT_2 = (float) Math.sqrt(2);
 695 
 696     Shape strokeRoundRectangle(RoundRectangle2D rr) {
 697         if (rr.width < 0 || rr.height < 0) {
 698             return new Path2D();
 699         }
 700         if (isDashed()) {
 701             return null;
 702         }
 703         int j;
 704         float aw = rr.arcWidth;
 705         float ah = rr.arcHeight;
 706         if (aw <= 0f || ah <= 0f) {
 707             aw = ah = 0f;
 708             if (type == TYPE_INNER) {
 709                 j = JOIN_MITER;
 710             } else {
 711                 j = this.join;
 712                 if (j == JOIN_MITER && miterLimit < SQRT_2) {
 713                     j = JOIN_BEVEL;
 714                 }
 715             }
 716         } else {
 717             if (aw < ah * 0.9f || ah < aw * 0.9f) {
 718                 // RT-27416
 719                 // TODO: Need to check these multipliers and
 720                 // optimize this case...
 721                 return null;
 722             }
 723             j = JOIN_ROUND;
 724         }
 725         float id, od;
 726         if (type == TYPE_INNER) {
 727             od = 0f;
 728             id = this.width;
 729         } else if (type == TYPE_OUTER) {
 730             od = this.width;
 731             id = 0f;
 732         } else {
 733             od = id = this.width/2f;
 734         }
 735         Shape outer;
 736         switch (j) {
 737             case JOIN_MITER:
 738                 outer = new RoundRectangle2D(rr.x - od, rr.y - od,
 739                                              rr.width+od*2f, rr.height+od*2f,
 740                                              0f, 0f);
 741                 break;
 742             case JOIN_BEVEL:
 743                 outer = makeBeveledRect(rr.x, rr.y, rr.width, rr.height, od);
 744                 break;
 745             case JOIN_ROUND:
 746                 outer = new RoundRectangle2D(rr.x - od, rr.y - od,
 747                                              rr.width+od*2f, rr.height+od*2f,
 748                                              aw+od*2f, ah+od*2f);
 749                 break;
 750             default:
 751                 throw new InternalError("Unrecognized line join style");
 752         }
 753         if (rr.width <= id*2f || rr.height <= id*2f) {
 754             return outer;
 755         }
 756         aw -= id*2f;
 757         ah -= id*2f;
 758         if (aw <= 0f || ah <= 0f) {
 759             aw = ah = 0f;
 760         }
 761         Shape inner = new RoundRectangle2D(rr.x + id, rr.y + id,
 762                                            rr.width-id*2f, rr.height-id*2f,
 763                                            aw, ah);
 764         Path2D p2d = (outer instanceof Path2D)
 765             ? ((Path2D) outer) : new Path2D(outer);
 766         p2d.setWindingRule(Path2D.WIND_EVEN_ODD);
 767         p2d.append(inner, false);
 768         return p2d;
 769     }
 770 
 771     static Shape makeBeveledRect(float rx, float ry,
 772                                  float rw, float rh,
 773                                  float d)
 774     {
 775         float rx0 = rx;
 776         float ry0 = ry;
 777         float rx1 = rx + rw;
 778         float ry1 = ry + rh;
 779         Path2D p = new Path2D();
 780         p.moveTo(rx0, ry0 - d);
 781         p.lineTo(rx1, ry0 - d);
 782         p.lineTo(rx1 + d, ry0);
 783         p.lineTo(rx1 + d, ry1);
 784         p.lineTo(rx1, ry1 + d);
 785         p.lineTo(rx0, ry1 + d);
 786         p.lineTo(rx0 - d, ry1);
 787         p.lineTo(rx0 - d, ry0);
 788         p.closePath();
 789         return p;
 790     }
 791 
 792     protected Shape makeIntersectedShape(Shape outer, Shape inner) {
 793         return new CAGShapePair(outer, inner, ShapePair.TYPE_INTERSECT);
 794     }
 795 
 796     protected Shape makeSubtractedShape(Shape outer, Shape inner) {
 797         return new CAGShapePair(outer, inner, ShapePair.TYPE_SUBTRACT);
 798     }
 799 
 800     static class CAGShapePair extends GeneralShapePair {
 801         private Shape cagshape;
 802 
 803         public CAGShapePair(Shape outer, Shape inner, int type) {
 804             super(outer, inner, type);
 805         }
 806 
 807         @Override
 808         public PathIterator getPathIterator(BaseTransform tx) {
 809             if (cagshape == null) {
 810                 Area o = new Area(getOuterShape());
 811                 Area i = new Area(getInnerShape());
 812                 if (getCombinationType() == ShapePair.TYPE_INTERSECT) {
 813                     o.intersect(i);
 814                 } else {
 815                     o.subtract(i);
 816                 }
 817                 cagshape = o;
 818             }
 819             return cagshape.getPathIterator(tx);
 820         }
 821     }
 822 
 823     /**
 824      * Returns the hashcode for this stroke.
 825      * @return      a hash code for this stroke.
 826      */
 827     @Override
 828     public int hashCode() {
 829         int hash = Float.floatToIntBits(width);
 830         hash = hash * 31 + join;
 831         hash = hash * 31 + cap;
 832         hash = hash * 31 + Float.floatToIntBits(miterLimit);
 833         if (dash != null) {
 834             hash = hash * 31 + Float.floatToIntBits(dashPhase);
 835             for (int i = 0; i < dash.length; i++) {
 836                 hash = hash * 31 + Float.floatToIntBits(dash[i]);
 837             }
 838         }
 839         return hash;
 840     }
 841 
 842     /**
 843      * Tests if a specified object is equal to this <code>BasicStroke</code>
 844      * by first testing if it is a <code>BasicStroke</code> and then comparing
 845      * its width, join, cap, miter limit, dash, and dash phase attributes with
 846      * those of this <code>BasicStroke</code>.
 847      * @param  obj the specified object to compare to this
 848      *              <code>BasicStroke</code>
 849      * @return <code>true</code> if the width, join, cap, miter limit, dash, and
 850      *            dash phase are the same for both objects;
 851      *            <code>false</code> otherwise.
 852      */
 853     @Override
 854     public boolean equals(Object obj) {
 855         if (!(obj instanceof BasicStroke)) {
 856             return false;
 857         }
 858         BasicStroke bs = (BasicStroke) obj;
 859         if (width != bs.width) {
 860             return false;
 861         }
 862         if (join != bs.join) {
 863             return false;
 864         }
 865         if (cap != bs.cap) {
 866             return false;
 867         }
 868         if (miterLimit != bs.miterLimit) {
 869             return false;
 870         }
 871         if (dash != null) {
 872             if (dashPhase != bs.dashPhase) {
 873                 return false;
 874             }
 875             if (!java.util.Arrays.equals(dash, bs.dash)) {
 876                 return false;
 877             }
 878         }
 879         else if (bs.dash != null) {
 880             return false;
 881         }
 882 
 883         return true;
 884     }
 885 
 886     public BasicStroke copy() {
 887         return new BasicStroke(type, width, cap, join, miterLimit, dash, dashPhase);
 888     }
 889 }