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