1 /* 2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.marlin; 27 28 import java.util.Arrays; 29 import sun.java2d.marlin.DHelpers.PolyStack; 30 31 // TODO: some of the arithmetic here is too verbose and prone to hard to 32 // debug typos. We should consider making a small Point/Vector class that 33 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such 34 final class DStroker implements DPathConsumer2D, MarlinConst { 35 36 private static final int MOVE_TO = 0; 37 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad 38 private static final int CLOSE = 2; 39 40 /** 41 * Constant value for join style. 42 */ 43 public static final int JOIN_MITER = 0; 44 45 /** 46 * Constant value for join style. 47 */ 48 public static final int JOIN_ROUND = 1; 49 50 /** 51 * Constant value for join style. 52 */ 53 public static final int JOIN_BEVEL = 2; 54 55 /** 56 * Constant value for end cap style. 57 */ 58 public static final int CAP_BUTT = 0; 59 60 /** 61 * Constant value for end cap style. 62 */ 63 public static final int CAP_ROUND = 1; 64 65 /** 66 * Constant value for end cap style. 67 */ 68 public static final int CAP_SQUARE = 2; 69 70 // pisces used to use fixed point arithmetic with 16 decimal digits. I 71 // didn't want to change the values of the constant below when I converted 72 // it to floating point, so that's why the divisions by 2^16 are there. 73 private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d; 74 75 // kappa = (4/3) * (SQRT(2) - 1) 76 private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); 77 78 // SQRT(2) 79 private static final double SQRT_2 = Math.sqrt(2.0d); 80 81 private static final int MAX_N_CURVES = 11; 82 83 private DPathConsumer2D out; 84 85 private int capStyle; 86 private int joinStyle; 87 88 private double lineWidth2; 89 private double invHalfLineWidth2Sq; 90 91 private final double[] offset0 = new double[2]; 92 private final double[] offset1 = new double[2]; 93 private final double[] offset2 = new double[2]; 94 private final double[] miter = new double[2]; 95 private double miterLimitSq; 96 97 private int prev; 98 99 // The starting point of the path, and the slope there. 100 private double sx0, sy0, sdx, sdy; 101 // the current point and the slope there. 102 private double cx0, cy0, cdx, cdy; // c stands for current 103 // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the 104 // first and last points on the left parallel path. Since this path is 105 // parallel, it's slope at any point is parallel to the slope of the 106 // original path (thought they may have different directions), so these 107 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that 108 // would be error prone and hard to read, so we keep these anyway. 109 private double smx, smy, cmx, cmy; 110 111 private final PolyStack reverse; 112 113 // This is where the curve to be processed is put. We give it 114 // enough room to store all curves. 115 private final double[] middle = new double[MAX_N_CURVES * 6 + 2]; 116 private final double[] lp = new double[8]; 117 private final double[] rp = new double[8]; 118 private final double[] subdivTs = new double[MAX_N_CURVES - 1]; 119 120 // per-thread renderer context 121 final DRendererContext rdrCtx; 122 123 // dirty curve 124 final DCurve curve; 125 126 // Bounds of the drawing region, at pixel precision. 127 private double[] clipRect; 128 129 // the outcode of the current point 130 private int cOutCode = 0; 131 132 // the outcode of the starting point 133 private int sOutCode = 0; 134 135 // flag indicating if the path is opened (clipped) 136 private boolean opened = false; 137 // flag indicating if the starting point's cap is done 138 private boolean capStart = false; 139 140 /** 141 * Constructs a <code>DStroker</code>. 142 * @param rdrCtx per-thread renderer context 143 */ 144 DStroker(final DRendererContext rdrCtx) { 145 this.rdrCtx = rdrCtx; 146 147 this.reverse = (rdrCtx.stats != null) ? 148 new PolyStack(rdrCtx, 149 rdrCtx.stats.stat_str_polystack_types, 150 rdrCtx.stats.stat_str_polystack_curves, 151 rdrCtx.stats.hist_str_polystack_curves, 152 rdrCtx.stats.stat_array_str_polystack_curves, 153 rdrCtx.stats.stat_array_str_polystack_types) 154 : new PolyStack(rdrCtx); 155 156 this.curve = rdrCtx.curve; 157 } 158 159 /** 160 * Inits the <code>DStroker</code>. 161 * 162 * @param pc2d an output <code>DPathConsumer2D</code>. 163 * @param lineWidth the desired line width in pixels 164 * @param capStyle the desired end cap style, one of 165 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 166 * <code>CAP_SQUARE</code>. 167 * @param joinStyle the desired line join style, one of 168 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 169 * <code>JOIN_BEVEL</code>. 170 * @param miterLimit the desired miter limit 171 * @param scale scaling factor applied to clip boundaries 172 * @return this instance 173 */ 174 DStroker init(final DPathConsumer2D pc2d, 175 final double lineWidth, 176 final int capStyle, 177 final int joinStyle, 178 final double miterLimit, 179 final double scale) 180 { 181 this.out = pc2d; 182 183 this.lineWidth2 = lineWidth / 2.0d; 184 this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2); 185 this.capStyle = capStyle; 186 this.joinStyle = joinStyle; 187 188 final double limit = miterLimit * lineWidth2; 189 this.miterLimitSq = limit * limit; 190 191 this.prev = CLOSE; 192 193 rdrCtx.stroking = 1; 194 195 if (rdrCtx.doClip) { 196 // Adjust the clipping rectangle with the stroker margin (miter limit, width) 197 double rdrOffX = 0.0d, rdrOffY = 0.0d; 198 double margin = lineWidth2; 199 200 if (capStyle == CAP_SQUARE) { 201 margin *= SQRT_2; 202 } 203 if ((joinStyle == JOIN_MITER) && (margin < limit)) { 204 margin = limit; 205 } 206 if (scale != 1.0d) { 207 margin *= scale; 208 rdrOffX = scale * DRenderer.RDR_OFFSET_X; 209 rdrOffY = scale * DRenderer.RDR_OFFSET_Y; 210 } 211 212 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 213 // adjust clip rectangle (ymin, ymax, xmin, xmax): 214 final double[] _clipRect = rdrCtx.clipRect; 215 _clipRect[0] -= margin - rdrOffY; 216 _clipRect[1] += margin + rdrOffY; 217 _clipRect[2] -= margin - rdrOffX; 218 _clipRect[3] += margin + rdrOffX; 219 this.clipRect = _clipRect; 220 } else { 221 this.clipRect = null; 222 } 223 return this; // fluent API 224 } 225 226 /** 227 * Disposes this stroker: 228 * clean up before reusing this instance 229 */ 230 void dispose() { 231 reverse.dispose(); 232 233 opened = false; 234 capStart = false; 235 236 if (DO_CLEAN_DIRTY) { 237 // Force zero-fill dirty arrays: 238 Arrays.fill(offset0, 0.0d); 239 Arrays.fill(offset1, 0.0d); 240 Arrays.fill(offset2, 0.0d); 241 Arrays.fill(miter, 0.0d); 242 Arrays.fill(middle, 0.0d); 243 Arrays.fill(lp, 0.0d); 244 Arrays.fill(rp, 0.0d); 245 Arrays.fill(subdivTs, 0.0d); 246 } 247 } 248 249 private static void computeOffset(final double lx, final double ly, 250 final double w, final double[] m) 251 { 252 double len = lx*lx + ly*ly; 253 if (len == 0.0d) { 254 m[0] = 0.0d; 255 m[1] = 0.0d; 256 } else { 257 len = Math.sqrt(len); 258 m[0] = (ly * w) / len; 259 m[1] = -(lx * w) / len; 260 } 261 } 262 263 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 264 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 265 // the smallest angle between it and dx2,dy2). 266 // This is equivalent to detecting whether a point q is on the right side 267 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 268 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 269 // clockwise order. 270 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. 271 private static boolean isCW(final double dx1, final double dy1, 272 final double dx2, final double dy2) 273 { 274 return dx1 * dy2 <= dy1 * dx2; 275 } 276 277 private void drawRoundJoin(double x, double y, 278 double omx, double omy, double mx, double my, 279 boolean rev, 280 double threshold) 281 { 282 if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) { 283 return; 284 } 285 286 double domx = omx - mx; 287 double domy = omy - my; 288 double len = domx*domx + domy*domy; 289 if (len < threshold) { 290 return; 291 } 292 293 if (rev) { 294 omx = -omx; 295 omy = -omy; 296 mx = -mx; 297 my = -my; 298 } 299 drawRoundJoin(x, y, omx, omy, mx, my, rev); 300 } 301 302 private void drawRoundJoin(double cx, double cy, 303 double omx, double omy, 304 double mx, double my, 305 boolean rev) 306 { 307 // The sign of the dot product of mx,my and omx,omy is equal to the 308 // the sign of the cosine of ext 309 // (ext is the angle between omx,omy and mx,my). 310 final double cosext = omx * mx + omy * my; 311 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 312 // need 1 curve to approximate the circle section that joins omx,omy 313 // and mx,my. 314 final int numCurves = (cosext >= 0.0d) ? 1 : 2; 315 316 switch (numCurves) { 317 case 1: 318 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 319 break; 320 case 2: 321 // we need to split the arc into 2 arcs spanning the same angle. 322 // The point we want will be one of the 2 intersections of the 323 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 324 // circle. We could find this by scaling the vector 325 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 326 // on the circle), but that can have numerical problems when the angle 327 // between omx,omy and mx,my is close to 180 degrees. So we compute a 328 // normal of (omx,omy)-(mx,my). This will be the direction of the 329 // perpendicular bisector. To get one of the intersections, we just scale 330 // this vector that its length is lineWidth2 (this works because the 331 // perpendicular bisector goes through the origin). This scaling doesn't 332 // have numerical problems because we know that lineWidth2 divided by 333 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 334 // we know the angle of the arc is > 90 degrees). 335 double nx = my - omy, ny = omx - mx; 336 double nlen = Math.sqrt(nx*nx + ny*ny); 337 double scale = lineWidth2/nlen; 338 double mmx = nx * scale, mmy = ny * scale; 339 340 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 341 // computed the wrong intersection so we get the other one. 342 // The test above is equivalent to if (rev). 343 if (rev) { 344 mmx = -mmx; 345 mmy = -mmy; 346 } 347 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 348 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 349 break; 350 default: 351 } 352 } 353 354 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. 355 private void drawBezApproxForArc(final double cx, final double cy, 356 final double omx, final double omy, 357 final double mx, final double my, 358 boolean rev) 359 { 360 final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 361 362 // check round off errors producing cos(ext) > 1 and a NaN below 363 // cos(ext) == 1 implies colinear segments and an empty join anyway 364 if (cosext2 >= 0.5d) { 365 // just return to avoid generating a flat curve: 366 return; 367 } 368 369 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 370 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 371 // define the bezier curve we're computing. 372 // It is computed using the constraints that P1-P0 and P3-P2 are parallel 373 // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. 374 double cv = ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) / 375 (1.0d + Math.sqrt(cosext2 + 0.5d))); 376 // if clockwise, we need to negate cv. 377 if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) 378 cv = -cv; 379 } 380 final double x1 = cx + omx; 381 final double y1 = cy + omy; 382 final double x2 = x1 - cv * omy; 383 final double y2 = y1 + cv * omx; 384 385 final double x4 = cx + mx; 386 final double y4 = cy + my; 387 final double x3 = x4 + cv * my; 388 final double y3 = y4 - cv * mx; 389 390 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 391 } 392 393 private void drawRoundCap(double cx, double cy, double mx, double my) { 394 final double Cmx = C * mx; 395 final double Cmy = C * my; 396 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 397 cx - my + Cmx, cy + mx + Cmy, 398 cx - my, cy + mx); 399 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 400 cx - mx - Cmy, cy - my + Cmx, 401 cx - mx, cy - my); 402 } 403 404 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 405 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 406 private static void computeMiter(final double x0, final double y0, 407 final double x1, final double y1, 408 final double x0p, final double y0p, 409 final double x1p, final double y1p, 410 final double[] m, int off) 411 { 412 double x10 = x1 - x0; 413 double y10 = y1 - y0; 414 double x10p = x1p - x0p; 415 double y10p = y1p - y0p; 416 417 // if this is 0, the lines are parallel. If they go in the 418 // same direction, there is no intersection so m[off] and 419 // m[off+1] will contain infinity, so no miter will be drawn. 420 // If they go in the same direction that means that the start of the 421 // current segment and the end of the previous segment have the same 422 // tangent, in which case this method won't even be involved in 423 // miter drawing because it won't be called by drawMiter (because 424 // (mx == omx && my == omy) will be true, and drawMiter will return 425 // immediately). 426 double den = x10*y10p - x10p*y10; 427 double t = x10p*(y0-y0p) - y10p*(x0-x0p); 428 t /= den; 429 m[off++] = x0 + t*x10; 430 m[off] = y0 + t*y10; 431 } 432 433 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 434 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 435 private static void safeComputeMiter(final double x0, final double y0, 436 final double x1, final double y1, 437 final double x0p, final double y0p, 438 final double x1p, final double y1p, 439 final double[] m, int off) 440 { 441 double x10 = x1 - x0; 442 double y10 = y1 - y0; 443 double x10p = x1p - x0p; 444 double y10p = y1p - y0p; 445 446 // if this is 0, the lines are parallel. If they go in the 447 // same direction, there is no intersection so m[off] and 448 // m[off+1] will contain infinity, so no miter will be drawn. 449 // If they go in the same direction that means that the start of the 450 // current segment and the end of the previous segment have the same 451 // tangent, in which case this method won't even be involved in 452 // miter drawing because it won't be called by drawMiter (because 453 // (mx == omx && my == omy) will be true, and drawMiter will return 454 // immediately). 455 double den = x10*y10p - x10p*y10; 456 if (den == 0.0d) { 457 m[off++] = (x0 + x0p) / 2.0d; 458 m[off] = (y0 + y0p) / 2.0d; 459 return; 460 } 461 double t = x10p*(y0-y0p) - y10p*(x0-x0p); 462 t /= den; 463 m[off++] = x0 + t*x10; 464 m[off] = y0 + t*y10; 465 } 466 467 private void drawMiter(final double pdx, final double pdy, 468 final double x0, final double y0, 469 final double dx, final double dy, 470 double omx, double omy, double mx, double my, 471 boolean rev) 472 { 473 if ((mx == omx && my == omy) || 474 (pdx == 0.0d && pdy == 0.0d) || 475 (dx == 0.0d && dy == 0.0d)) 476 { 477 return; 478 } 479 480 if (rev) { 481 omx = -omx; 482 omy = -omy; 483 mx = -mx; 484 my = -my; 485 } 486 487 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 488 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, 489 miter, 0); 490 491 final double miterX = miter[0]; 492 final double miterY = miter[1]; 493 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 494 495 // If the lines are parallel, lenSq will be either NaN or +inf 496 // (actually, I'm not sure if the latter is possible. The important 497 // thing is that -inf is not possible, because lenSq is a square). 498 // For both of those values, the comparison below will fail and 499 // no miter will be drawn, which is correct. 500 if (lenSq < miterLimitSq) { 501 emitLineTo(miterX, miterY, rev); 502 } 503 } 504 505 @Override 506 public void moveTo(final double x0, final double y0) { 507 moveTo(x0, y0, cOutCode); 508 // update starting point: 509 this.sx0 = x0; 510 this.sy0 = y0; 511 this.sdx = 1.0d; 512 this.sdy = 0.0d; 513 this.opened = false; 514 this.capStart = false; 515 516 if (clipRect != null) { 517 final int outcode = DHelpers.outcode(x0, y0, clipRect); 518 this.cOutCode = outcode; 519 this.sOutCode = outcode; 520 } 521 } 522 523 private void moveTo(final double x0, final double y0, 524 final int outcode) 525 { 526 if (prev == MOVE_TO) { 527 this.cx0 = x0; 528 this.cy0 = y0; 529 } else { 530 if (prev == DRAWING_OP_TO) { 531 finish(outcode); 532 } 533 this.prev = MOVE_TO; 534 this.cx0 = x0; 535 this.cy0 = y0; 536 this.cdx = 1.0d; 537 this.cdy = 0.0d; 538 } 539 } 540 541 @Override 542 public void lineTo(final double x1, final double y1) { 543 lineTo(x1, y1, false); 544 } 545 546 private void lineTo(final double x1, final double y1, 547 final boolean force) 548 { 549 final int outcode0 = this.cOutCode; 550 if (!force && clipRect != null) { 551 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 552 this.cOutCode = outcode1; 553 554 // basic rejection criteria 555 if ((outcode0 & outcode1) != 0) { 556 moveTo(x1, y1, outcode0); 557 opened = true; 558 return; 559 } 560 } 561 562 double dx = x1 - cx0; 563 double dy = y1 - cy0; 564 if (dx == 0.0d && dy == 0.0d) { 565 dx = 1.0d; 566 } 567 computeOffset(dx, dy, lineWidth2, offset0); 568 final double mx = offset0[0]; 569 final double my = offset0[1]; 570 571 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 572 573 emitLineTo(cx0 + mx, cy0 + my); 574 emitLineTo( x1 + mx, y1 + my); 575 576 emitLineToRev(cx0 - mx, cy0 - my); 577 emitLineToRev( x1 - mx, y1 - my); 578 579 this.prev = DRAWING_OP_TO; 580 this.cx0 = x1; 581 this.cy0 = y1; 582 this.cdx = dx; 583 this.cdy = dy; 584 this.cmx = mx; 585 this.cmy = my; 586 } 587 588 @Override 589 public void closePath() { 590 // distinguish empty path at all vs opened path ? 591 if (prev != DRAWING_OP_TO && !opened) { 592 if (prev == CLOSE) { 593 return; 594 } 595 emitMoveTo(cx0, cy0 - lineWidth2); 596 597 this.sdx = 1.0d; 598 this.sdy = 0.0d; 599 this.cdx = 1.0d; 600 this.cdy = 0.0d; 601 602 this.smx = 0.0d; 603 this.smy = -lineWidth2; 604 this.cmx = 0.0d; 605 this.cmy = -lineWidth2; 606 607 finish(cOutCode); 608 return; 609 } 610 611 if (sOutCode == 0) { 612 if (cx0 != sx0 || cy0 != sy0) { 613 lineTo(sx0, sy0, true); 614 } 615 616 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode); 617 618 emitLineTo(sx0 + smx, sy0 + smy); 619 620 if (opened) { 621 emitLineTo(sx0 - smx, sy0 - smy); 622 } else { 623 emitMoveTo(sx0 - smx, sy0 - smy); 624 } 625 } 626 // Ignore caps like finish(false) 627 emitReverse(); 628 629 this.prev = CLOSE; 630 631 if (opened) { 632 // do not emit close 633 opened = false; 634 } else { 635 emitClose(); 636 } 637 } 638 639 private void emitReverse() { 640 reverse.popAll(out); 641 } 642 643 @Override 644 public void pathDone() { 645 if (prev == DRAWING_OP_TO) { 646 finish(cOutCode); 647 } 648 649 out.pathDone(); 650 651 // this shouldn't matter since this object won't be used 652 // after the call to this method. 653 this.prev = CLOSE; 654 655 // Dispose this instance: 656 dispose(); 657 } 658 659 private void finish(final int outcode) { 660 // Problem: impossible to guess if the path will be closed in advance 661 // i.e. if caps must be drawn or not ? 662 // Solution: use the ClosedPathDetector before Stroker to determine 663 // if the path is a closed path or not 664 if (!rdrCtx.closedPath) { 665 if (outcode == 0) { 666 // current point = end's cap: 667 if (capStyle == CAP_ROUND) { 668 drawRoundCap(cx0, cy0, cmx, cmy); 669 } else if (capStyle == CAP_SQUARE) { 670 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 671 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 672 } 673 } 674 emitReverse(); 675 676 if (!capStart) { 677 capStart = true; 678 679 if (sOutCode == 0) { 680 // starting point = initial cap: 681 if (capStyle == CAP_ROUND) { 682 drawRoundCap(sx0, sy0, -smx, -smy); 683 } else if (capStyle == CAP_SQUARE) { 684 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 685 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 686 } 687 } 688 } 689 } else { 690 emitReverse(); 691 } 692 emitClose(); 693 } 694 695 private void emitMoveTo(final double x0, final double y0) { 696 out.moveTo(x0, y0); 697 } 698 699 private void emitLineTo(final double x1, final double y1) { 700 out.lineTo(x1, y1); 701 } 702 703 private void emitLineToRev(final double x1, final double y1) { 704 reverse.pushLine(x1, y1); 705 } 706 707 private void emitLineTo(final double x1, final double y1, 708 final boolean rev) 709 { 710 if (rev) { 711 emitLineToRev(x1, y1); 712 } else { 713 emitLineTo(x1, y1); 714 } 715 } 716 717 private void emitQuadTo(final double x1, final double y1, 718 final double x2, final double y2) 719 { 720 out.quadTo(x1, y1, x2, y2); 721 } 722 723 private void emitQuadToRev(final double x0, final double y0, 724 final double x1, final double y1) 725 { 726 reverse.pushQuad(x0, y0, x1, y1); 727 } 728 729 private void emitCurveTo(final double x1, final double y1, 730 final double x2, final double y2, 731 final double x3, final double y3) 732 { 733 out.curveTo(x1, y1, x2, y2, x3, y3); 734 } 735 736 private void emitCurveToRev(final double x0, final double y0, 737 final double x1, final double y1, 738 final double x2, final double y2) 739 { 740 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 741 } 742 743 private void emitCurveTo(final double x0, final double y0, 744 final double x1, final double y1, 745 final double x2, final double y2, 746 final double x3, final double y3, final boolean rev) 747 { 748 if (rev) { 749 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 750 } else { 751 out.curveTo(x1, y1, x2, y2, x3, y3); 752 } 753 } 754 755 private void emitClose() { 756 out.closePath(); 757 } 758 759 private void drawJoin(double pdx, double pdy, 760 double x0, double y0, 761 double dx, double dy, 762 double omx, double omy, 763 double mx, double my, 764 final int outcode) 765 { 766 if (prev != DRAWING_OP_TO) { 767 emitMoveTo(x0 + mx, y0 + my); 768 if (!opened) { 769 this.sdx = dx; 770 this.sdy = dy; 771 this.smx = mx; 772 this.smy = my; 773 } 774 } else { 775 final boolean cw = isCW(pdx, pdy, dx, dy); 776 if (outcode == 0) { 777 if (joinStyle == JOIN_MITER) { 778 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 779 } else if (joinStyle == JOIN_ROUND) { 780 drawRoundJoin(x0, y0, 781 omx, omy, 782 mx, my, cw, 783 ROUND_JOIN_THRESHOLD); 784 } 785 } 786 emitLineTo(x0, y0, !cw); 787 } 788 prev = DRAWING_OP_TO; 789 } 790 791 private static boolean within(final double x1, final double y1, 792 final double x2, final double y2, 793 final double ERR) 794 { 795 assert ERR > 0 : ""; 796 // compare taxicab distance. ERR will always be small, so using 797 // true distance won't give much benefit 798 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs 799 DHelpers.within(y1, y2, ERR)); // this is just as good. 800 } 801 802 private void getLineOffsets(double x1, double y1, 803 double x2, double y2, 804 double[] left, double[] right) { 805 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 806 final double mx = offset0[0]; 807 final double my = offset0[1]; 808 left[0] = x1 + mx; 809 left[1] = y1 + my; 810 left[2] = x2 + mx; 811 left[3] = y2 + my; 812 right[0] = x1 - mx; 813 right[1] = y1 - my; 814 right[2] = x2 - mx; 815 right[3] = y2 - my; 816 } 817 818 private int computeOffsetCubic(double[] pts, final int off, 819 double[] leftOff, double[] rightOff) 820 { 821 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 822 // vanishes, which creates problems with computeOffset. Usually 823 // this happens when this stroker object is trying to widen 824 // a curve with a cusp. What happens is that curveTo splits 825 // the input curve at the cusp, and passes it to this function. 826 // because of inaccuracies in the splitting, we consider points 827 // equal if they're very close to each other. 828 final double x1 = pts[off + 0], y1 = pts[off + 1]; 829 final double x2 = pts[off + 2], y2 = pts[off + 3]; 830 final double x3 = pts[off + 4], y3 = pts[off + 5]; 831 final double x4 = pts[off + 6], y4 = pts[off + 7]; 832 833 double dx4 = x4 - x3; 834 double dy4 = y4 - y3; 835 double dx1 = x2 - x1; 836 double dy1 = y2 - y1; 837 838 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 839 // in which case ignore if p1 == p2 840 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 841 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4)); 842 if (p1eqp2 && p3eqp4) { 843 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 844 return 4; 845 } else if (p1eqp2) { 846 dx1 = x3 - x1; 847 dy1 = y3 - y1; 848 } else if (p3eqp4) { 849 dx4 = x4 - x2; 850 dy4 = y4 - y2; 851 } 852 853 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 854 double dotsq = (dx1 * dx4 + dy1 * dy4); 855 dotsq *= dotsq; 856 double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 857 if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) { 858 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 859 return 4; 860 } 861 862 // What we're trying to do in this function is to approximate an ideal 863 // offset curve (call it I) of the input curve B using a bezier curve Bp. 864 // The constraints I use to get the equations are: 865 // 866 // 1. The computed curve Bp should go through I(0) and I(1). These are 867 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 868 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 869 // 870 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 871 // (by the way, the operator || in the comments below means "aligned with". 872 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 873 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 874 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 875 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 876 // nonzero constant.) 877 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 878 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 879 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 880 // is true for any bezier curve; therefore, we get the equations 881 // (1) p2p = c1 * (p2-p1) + p1p 882 // (2) p3p = c2 * (p4-p3) + p4p 883 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 884 // of unknowns from 4 to 2 (i.e. just c1 and c2). 885 // To eliminate these 2 unknowns we use the following constraint: 886 // 887 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 888 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 889 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 890 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 891 // We can substitute (1) and (2) from above into (4) and we get: 892 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 893 // which is equivalent to 894 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 895 // 896 // The right side of this is a 2D vector, and we know I(0.5), which gives us 897 // Bp(0.5), which gives us the value of the right side. 898 // The left side is just a matrix vector multiplication in disguise. It is 899 // 900 // [x2-x1, x4-x3][c1] 901 // [y2-y1, y4-y3][c2] 902 // which, is equal to 903 // [dx1, dx4][c1] 904 // [dy1, dy4][c2] 905 // At this point we are left with a simple linear system and we solve it by 906 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 907 // p2p and p3p. 908 909 double x = (x1 + 3.0d * (x2 + x3) + x4) / 8.0d; 910 double y = (y1 + 3.0d * (y2 + y3) + y4) / 8.0d; 911 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 912 // c*B'(0.5) for some constant c. 913 double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 914 915 // this computes the offsets at t=0, 0.5, 1, using the property that 916 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 917 // the (dx/dt, dy/dt) vectors at the endpoints. 918 computeOffset(dx1, dy1, lineWidth2, offset0); 919 computeOffset(dxm, dym, lineWidth2, offset1); 920 computeOffset(dx4, dy4, lineWidth2, offset2); 921 double x1p = x1 + offset0[0]; // start 922 double y1p = y1 + offset0[1]; // point 923 double xi = x + offset1[0]; // interpolation 924 double yi = y + offset1[1]; // point 925 double x4p = x4 + offset2[0]; // end 926 double y4p = y4 + offset2[1]; // point 927 928 double invdet43 = 4.0d / (3.0d * (dx1 * dy4 - dy1 * dx4)); 929 930 double two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 931 double two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 932 double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 933 double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 934 935 double x2p, y2p, x3p, y3p; 936 x2p = x1p + c1*dx1; 937 y2p = y1p + c1*dy1; 938 x3p = x4p + c2*dx4; 939 y3p = y4p + c2*dy4; 940 941 leftOff[0] = x1p; leftOff[1] = y1p; 942 leftOff[2] = x2p; leftOff[3] = y2p; 943 leftOff[4] = x3p; leftOff[5] = y3p; 944 leftOff[6] = x4p; leftOff[7] = y4p; 945 946 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 947 xi = xi - 2.0d * offset1[0]; yi = yi - 2.0d * offset1[1]; 948 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 949 950 two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 951 two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 952 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 953 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 954 955 x2p = x1p + c1*dx1; 956 y2p = y1p + c1*dy1; 957 x3p = x4p + c2*dx4; 958 y3p = y4p + c2*dy4; 959 960 rightOff[0] = x1p; rightOff[1] = y1p; 961 rightOff[2] = x2p; rightOff[3] = y2p; 962 rightOff[4] = x3p; rightOff[5] = y3p; 963 rightOff[6] = x4p; rightOff[7] = y4p; 964 return 8; 965 } 966 967 // compute offset curves using bezier spline through t=0.5 (i.e. 968 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 969 // return the kind of curve in the right and left arrays. 970 private int computeOffsetQuad(double[] pts, final int off, 971 double[] leftOff, double[] rightOff) 972 { 973 final double x1 = pts[off + 0], y1 = pts[off + 1]; 974 final double x2 = pts[off + 2], y2 = pts[off + 3]; 975 final double x3 = pts[off + 4], y3 = pts[off + 5]; 976 977 final double dx3 = x3 - x2; 978 final double dy3 = y3 - y2; 979 final double dx1 = x2 - x1; 980 final double dy1 = y2 - y1; 981 982 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 983 // vanishes, which creates problems with computeOffset. Usually 984 // this happens when this stroker object is trying to widen 985 // a curve with a cusp. What happens is that curveTo splits 986 // the input curve at the cusp, and passes it to this function. 987 // because of inaccuracies in the splitting, we consider points 988 // equal if they're very close to each other. 989 990 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 991 // in which case ignore. 992 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 993 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3)); 994 if (p1eqp2 || p2eqp3) { 995 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 996 return 4; 997 } 998 999 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1000 double dotsq = (dx1 * dx3 + dy1 * dy3); 1001 dotsq *= dotsq; 1002 double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1003 if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) { 1004 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1005 return 4; 1006 } 1007 1008 // this computes the offsets at t=0, 0.5, 1, using the property that 1009 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1010 // the (dx/dt, dy/dt) vectors at the endpoints. 1011 computeOffset(dx1, dy1, lineWidth2, offset0); 1012 computeOffset(dx3, dy3, lineWidth2, offset1); 1013 1014 double x1p = x1 + offset0[0]; // start 1015 double y1p = y1 + offset0[1]; // point 1016 double x3p = x3 + offset1[0]; // end 1017 double y3p = y3 + offset1[1]; // point 1018 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); 1019 leftOff[0] = x1p; leftOff[1] = y1p; 1020 leftOff[4] = x3p; leftOff[5] = y3p; 1021 1022 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1023 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1024 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); 1025 rightOff[0] = x1p; rightOff[1] = y1p; 1026 rightOff[4] = x3p; rightOff[5] = y3p; 1027 return 6; 1028 } 1029 1030 // finds values of t where the curve in pts should be subdivided in order 1031 // to get good offset curves a distance of w away from the middle curve. 1032 // Stores the points in ts, and returns how many of them there were. 1033 private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts, 1034 final int type, final double w) 1035 { 1036 final double x12 = pts[2] - pts[0]; 1037 final double y12 = pts[3] - pts[1]; 1038 // if the curve is already parallel to either axis we gain nothing 1039 // from rotating it. 1040 if (y12 != 0.0d && x12 != 0.0d) { 1041 // we rotate it so that the first vector in the control polygon is 1042 // parallel to the x-axis. This will ensure that rotated quarter 1043 // circles won't be subdivided. 1044 final double hypot = Math.sqrt(x12 * x12 + y12 * y12); 1045 final double cos = x12 / hypot; 1046 final double sin = y12 / hypot; 1047 final double x1 = cos * pts[0] + sin * pts[1]; 1048 final double y1 = cos * pts[1] - sin * pts[0]; 1049 final double x2 = cos * pts[2] + sin * pts[3]; 1050 final double y2 = cos * pts[3] - sin * pts[2]; 1051 final double x3 = cos * pts[4] + sin * pts[5]; 1052 final double y3 = cos * pts[5] - sin * pts[4]; 1053 1054 switch(type) { 1055 case 8: 1056 final double x4 = cos * pts[6] + sin * pts[7]; 1057 final double y4 = cos * pts[7] - sin * pts[6]; 1058 c.set(x1, y1, x2, y2, x3, y3, x4, y4); 1059 break; 1060 case 6: 1061 c.set(x1, y1, x2, y2, x3, y3); 1062 break; 1063 default: 1064 } 1065 } else { 1066 c.set(pts, type); 1067 } 1068 1069 int ret = 0; 1070 // we subdivide at values of t such that the remaining rotated 1071 // curves are monotonic in x and y. 1072 ret += c.dxRoots(ts, ret); 1073 ret += c.dyRoots(ts, ret); 1074 // subdivide at inflection points. 1075 if (type == 8) { 1076 // quadratic curves can't have inflection points 1077 ret += c.infPoints(ts, ret); 1078 } 1079 1080 // now we must subdivide at points where one of the offset curves will have 1081 // a cusp. This happens at ts where the radius of curvature is equal to w. 1082 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d); 1083 1084 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d); 1085 DHelpers.isort(ts, 0, ret); 1086 return ret; 1087 } 1088 1089 @Override 1090 public void curveTo(final double x1, final double y1, 1091 final double x2, final double y2, 1092 final double x3, final double y3) 1093 { 1094 final int outcode0 = this.cOutCode; 1095 if (clipRect != null) { 1096 final int outcode3 = DHelpers.outcode(x3, y3, clipRect); 1097 this.cOutCode = outcode3; 1098 1099 if (outcode3 != 0) { 1100 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 1101 final int outcode2 = DHelpers.outcode(x2, y2, clipRect); 1102 1103 // basic rejection criteria 1104 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { 1105 moveTo(x3, y3, outcode0); 1106 opened = true; 1107 return; 1108 } 1109 } 1110 } 1111 1112 final double[] mid = middle; 1113 1114 mid[0] = cx0; mid[1] = cy0; 1115 mid[2] = x1; mid[3] = y1; 1116 mid[4] = x2; mid[5] = y2; 1117 mid[6] = x3; mid[7] = y3; 1118 1119 // need these so we can update the state at the end of this method 1120 final double xf = x3, yf = y3; 1121 double dxs = mid[2] - mid[0]; 1122 double dys = mid[3] - mid[1]; 1123 double dxf = mid[6] - mid[4]; 1124 double dyf = mid[7] - mid[5]; 1125 1126 boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d); 1127 boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d); 1128 if (p1eqp2) { 1129 dxs = mid[4] - mid[0]; 1130 dys = mid[5] - mid[1]; 1131 if (dxs == 0.0d && dys == 0.0d) { 1132 dxs = mid[6] - mid[0]; 1133 dys = mid[7] - mid[1]; 1134 } 1135 } 1136 if (p3eqp4) { 1137 dxf = mid[6] - mid[2]; 1138 dyf = mid[7] - mid[3]; 1139 if (dxf == 0.0d && dyf == 0.0d) { 1140 dxf = mid[6] - mid[0]; 1141 dyf = mid[7] - mid[1]; 1142 } 1143 } 1144 if (dxs == 0.0d && dys == 0.0d) { 1145 // this happens if the "curve" is just a point 1146 // fix outcode0 for lineTo() call: 1147 if (clipRect != null) { 1148 this.cOutCode = outcode0; 1149 } 1150 lineTo(mid[0], mid[1]); 1151 return; 1152 } 1153 1154 // if these vectors are too small, normalize them, to avoid future 1155 // precision problems. 1156 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) { 1157 double len = Math.sqrt(dxs*dxs + dys*dys); 1158 dxs /= len; 1159 dys /= len; 1160 } 1161 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) { 1162 double len = Math.sqrt(dxf*dxf + dyf*dyf); 1163 dxf /= len; 1164 dyf /= len; 1165 } 1166 1167 computeOffset(dxs, dys, lineWidth2, offset0); 1168 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1169 1170 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); 1171 1172 double prevT = 0.0d; 1173 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { 1174 final double t = subdivTs[i]; 1175 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT), 1176 mid, off, mid, off, mid, off + 6); 1177 prevT = t; 1178 } 1179 1180 final double[] l = lp; 1181 final double[] r = rp; 1182 1183 int kind = 0; 1184 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1185 kind = computeOffsetCubic(mid, off, l, r); 1186 1187 emitLineTo(l[0], l[1]); 1188 1189 switch(kind) { 1190 case 8: 1191 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1192 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1193 break; 1194 case 4: 1195 emitLineTo(l[2], l[3]); 1196 emitLineToRev(r[0], r[1]); 1197 break; 1198 default: 1199 } 1200 emitLineToRev(r[kind - 2], r[kind - 1]); 1201 } 1202 1203 this.prev = DRAWING_OP_TO; 1204 this.cx0 = xf; 1205 this.cy0 = yf; 1206 this.cdx = dxf; 1207 this.cdy = dyf; 1208 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; 1209 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; 1210 } 1211 1212 @Override 1213 public void quadTo(final double x1, final double y1, 1214 final double x2, final double y2) 1215 { 1216 final int outcode0 = this.cOutCode; 1217 if (clipRect != null) { 1218 final int outcode2 = DHelpers.outcode(x2, y2, clipRect); 1219 this.cOutCode = outcode2; 1220 1221 if (outcode2 != 0) { 1222 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 1223 1224 // basic rejection criteria 1225 if ((outcode0 & outcode1 & outcode2) != 0) { 1226 moveTo(x2, y2, outcode0); 1227 opened = true; 1228 return; 1229 } 1230 } 1231 } 1232 1233 final double[] mid = middle; 1234 1235 mid[0] = cx0; mid[1] = cy0; 1236 mid[2] = x1; mid[3] = y1; 1237 mid[4] = x2; mid[5] = y2; 1238 1239 // need these so we can update the state at the end of this method 1240 final double xf = x2, yf = y2; 1241 double dxs = mid[2] - mid[0]; 1242 double dys = mid[3] - mid[1]; 1243 double dxf = mid[4] - mid[2]; 1244 double dyf = mid[5] - mid[3]; 1245 if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) { 1246 dxs = dxf = mid[4] - mid[0]; 1247 dys = dyf = mid[5] - mid[1]; 1248 } 1249 if (dxs == 0.0d && dys == 0.0d) { 1250 // this happens if the "curve" is just a point 1251 // fix outcode0 for lineTo() call: 1252 if (clipRect != null) { 1253 this.cOutCode = outcode0; 1254 } 1255 lineTo(mid[0], mid[1]); 1256 return; 1257 } 1258 // if these vectors are too small, normalize them, to avoid future 1259 // precision problems. 1260 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) { 1261 double len = Math.sqrt(dxs*dxs + dys*dys); 1262 dxs /= len; 1263 dys /= len; 1264 } 1265 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) { 1266 double len = Math.sqrt(dxf*dxf + dyf*dyf); 1267 dxf /= len; 1268 dyf /= len; 1269 } 1270 1271 computeOffset(dxs, dys, lineWidth2, offset0); 1272 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1273 1274 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); 1275 1276 double prevt = 0.0d; 1277 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { 1278 final double t = subdivTs[i]; 1279 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt), 1280 mid, off, mid, off, mid, off + 4); 1281 prevt = t; 1282 } 1283 1284 final double[] l = lp; 1285 final double[] r = rp; 1286 1287 int kind = 0; 1288 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1289 kind = computeOffsetQuad(mid, off, l, r); 1290 1291 emitLineTo(l[0], l[1]); 1292 1293 switch(kind) { 1294 case 6: 1295 emitQuadTo(l[2], l[3], l[4], l[5]); 1296 emitQuadToRev(r[0], r[1], r[2], r[3]); 1297 break; 1298 case 4: 1299 emitLineTo(l[2], l[3]); 1300 emitLineToRev(r[0], r[1]); 1301 break; 1302 default: 1303 } 1304 emitLineToRev(r[kind - 2], r[kind - 1]); 1305 } 1306 1307 this.prev = DRAWING_OP_TO; 1308 this.cx0 = xf; 1309 this.cy0 = yf; 1310 this.cdx = dxf; 1311 this.cdy = dyf; 1312 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; 1313 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; 1314 } 1315 1316 @Override public long getNativeConsumer() { 1317 throw new InternalError("Stroker doesn't use a native consumer"); 1318 } 1319 }