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 }