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 }