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 com.sun.marlin; 27 28 import java.util.Arrays; 29 import com.sun.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 public 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 private static final double C = 0.5522847498307933d; 76 77 private static final int MAX_N_CURVES = 11; 78 79 private DPathConsumer2D out; 80 81 private int capStyle; 82 private int joinStyle; 83 84 private double lineWidth2; 85 private double invHalfLineWidth2Sq; 86 87 private final double[] offset0 = new double[2]; 88 private final double[] offset1 = new double[2]; 89 private final double[] offset2 = new double[2]; 90 private final double[] miter = new double[2]; 91 private double miterLimitSq; 92 93 private int prev; 94 95 // The starting point of the path, and the slope there. 96 private double sx0, sy0, sdx, sdy; 97 // the current point and the slope there. 98 private double cx0, cy0, cdx, cdy; // c stands for current 99 // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the 100 // first and last points on the left parallel path. Since this path is 101 // parallel, it's slope at any point is parallel to the slope of the 102 // original path (thought they may have different directions), so these 103 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that 104 // would be error prone and hard to read, so we keep these anyway. 105 private double smx, smy, cmx, cmy; 106 107 private final PolyStack reverse; 108 109 // This is where the curve to be processed is put. We give it 110 // enough room to store all curves. 111 private final double[] middle = new double[MAX_N_CURVES * 6 + 2]; 112 private final double[] lp = new double[8]; 113 private final double[] rp = new double[8]; 114 private final double[] subdivTs = new double[MAX_N_CURVES - 1]; 115 116 // per-thread renderer context 117 final DRendererContext rdrCtx; 118 119 // dirty curve 120 final DCurve curve; 121 122 // Bounds of the drawing region, at pixel precision. 123 private double[] clipRect; 124 125 // the outcode of the current point 126 private int cOutCode = 0; 127 128 // the outcode of the starting point 129 private int sOutCode = 0; 130 131 // flag indicating if the path is opened (clipped) 132 private boolean opened = false; 133 // flag indicating if the starting point's cap is done 134 private boolean capStart = false; 135 136 /** 137 * Constructs a <code>DStroker</code>. 138 * @param rdrCtx per-thread renderer context 139 */ 140 DStroker(final DRendererContext rdrCtx) { 141 this.rdrCtx = rdrCtx; 142 143 this.reverse = (rdrCtx.stats != null) ? 144 new PolyStack(rdrCtx, 145 rdrCtx.stats.stat_str_polystack_types, 146 rdrCtx.stats.stat_str_polystack_curves, 147 rdrCtx.stats.hist_str_polystack_curves, 148 rdrCtx.stats.stat_array_str_polystack_curves, 149 rdrCtx.stats.stat_array_str_polystack_types) 150 : new PolyStack(rdrCtx); 151 152 this.curve = rdrCtx.curve; 153 } 154 155 /** 156 * Inits the <code>DStroker</code>. 157 * 158 * @param pc2d an output <code>DPathConsumer2D</code>. 159 * @param lineWidth the desired line width in pixels 160 * @param capStyle the desired end cap style, one of 161 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 162 * <code>CAP_SQUARE</code>. 163 * @param joinStyle the desired line join style, one of 164 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 165 * <code>JOIN_BEVEL</code>. 166 * @param miterLimit the desired miter limit 167 * @param scale scaling factor applied to clip boundaries 168 * @param rdrOffX renderer's coordinate offset on X axis 169 * @param rdrOffY renderer's coordinate offset on Y axis 170 * @return this instance 171 */ 172 public DStroker init(DPathConsumer2D pc2d, 173 double lineWidth, 174 int capStyle, 175 int joinStyle, 176 double miterLimit, 177 final double scale, 178 double rdrOffX, 179 double rdrOffY) 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 198 // round joins / caps: 199 final double widthLimit = 200 ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ? 201 : lineWidth2; 202 203 double boundsMargin; 204 if (joinStyle == JOIN_MITER) { 205 boundsMargin = Math.max(widthLimit, limit); 206 } else { 207 boundsMargin = widthLimit; 208 } 209 if (scale != 1.0d) { 210 boundsMargin *= scale; 211 rdrOffX *= scale; 212 rdrOffY *= scale; 213 } 214 215 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 216 // adjust clip rectangle (ymin, ymax, xmin, xmax): 217 final double[] _clipRect = rdrCtx.clipRect; 218 _clipRect[0] -= boundsMargin - rdrOffY; 219 _clipRect[1] += boundsMargin + rdrOffY; 220 _clipRect[2] -= boundsMargin - rdrOffX; 221 _clipRect[3] += boundsMargin + rdrOffX; 222 this.clipRect = _clipRect; 223 } else { 224 this.clipRect = null; 225 } 226 return this; // fluent API 227 } 228 229 /** 230 * Disposes this stroker: 231 * clean up before reusing this instance 232 */ 233 void dispose() { 234 reverse.dispose(); 235 236 opened = false; 237 capStart = false; 238 239 if (DO_CLEAN_DIRTY) { 240 // Force zero-fill dirty arrays: 241 Arrays.fill(offset0, 0.0d); 242 Arrays.fill(offset1, 0.0d); 243 Arrays.fill(offset2, 0.0d); 244 Arrays.fill(miter, 0.0d); 245 Arrays.fill(middle, 0.0d); 246 Arrays.fill(lp, 0.0d); 247 Arrays.fill(rp, 0.0d); 248 Arrays.fill(subdivTs, 0.0d); 249 } 250 } 251 252 private static void computeOffset(final double lx, final double ly, 253 final double w, final double[] m) 254 { 255 double len = lx*lx + ly*ly; 256 if (len == 0.0d) { 257 m[0] = 0.0d; 258 m[1] = 0.0d; 259 } else { 260 len = Math.sqrt(len); 261 m[0] = (ly * w) / len; 262 m[1] = -(lx * w) / len; 263 } 264 } 265 266 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 267 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 268 // the smallest angle between it and dx2,dy2). 269 // This is equivalent to detecting whether a point q is on the right side 270 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 271 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 272 // clockwise order. 273 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. 274 private static boolean isCW(final double dx1, final double dy1, 275 final double dx2, final double dy2) 276 { 277 return dx1 * dy2 <= dy1 * dx2; 278 } 279 280 private void drawRoundJoin(double x, double y, 281 double omx, double omy, double mx, double my, 282 boolean rev, 283 double threshold) 284 { 285 if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) { 286 return; 287 } 288 289 double domx = omx - mx; 290 double domy = omy - my; 291 double len = domx*domx + domy*domy; 292 if (len < threshold) { 293 return; 294 } 295 296 if (rev) { 297 omx = -omx; 298 omy = -omy; 299 mx = -mx; 300 my = -my; 301 } 302 drawRoundJoin(x, y, omx, omy, mx, my, rev); 303 } 304 305 private void drawRoundJoin(double cx, double cy, 306 double omx, double omy, 307 double mx, double my, 308 boolean rev) 309 { 310 // The sign of the dot product of mx,my and omx,omy is equal to the 311 // the sign of the cosine of ext 312 // (ext is the angle between omx,omy and mx,my). 313 final double cosext = omx * mx + omy * my; 314 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 315 // need 1 curve to approximate the circle section that joins omx,omy 316 // and mx,my. 317 final int numCurves = (cosext >= 0.0d) ? 1 : 2; 318 319 switch (numCurves) { 320 case 1: 321 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 322 break; 323 case 2: 324 // we need to split the arc into 2 arcs spanning the same angle. 325 // The point we want will be one of the 2 intersections of the 326 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 327 // circle. We could find this by scaling the vector 328 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 329 // on the circle), but that can have numerical problems when the angle 330 // between omx,omy and mx,my is close to 180 degrees. So we compute a 331 // normal of (omx,omy)-(mx,my). This will be the direction of the 332 // perpendicular bisector. To get one of the intersections, we just scale 333 // this vector that its length is lineWidth2 (this works because the 334 // perpendicular bisector goes through the origin). This scaling doesn't 335 // have numerical problems because we know that lineWidth2 divided by 336 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 337 // we know the angle of the arc is > 90 degrees). 338 double nx = my - omy, ny = omx - mx; 339 double nlen = Math.sqrt(nx*nx + ny*ny); 340 double scale = lineWidth2/nlen; 341 double mmx = nx * scale, mmy = ny * scale; 342 343 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 344 // computed the wrong intersection so we get the other one. 345 // The test above is equivalent to if (rev). 346 if (rev) { 347 mmx = -mmx; 348 mmy = -mmy; 349 } 350 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 351 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 352 break; 353 default: 354 } 355 } 356 357 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. 358 private void drawBezApproxForArc(final double cx, final double cy, 359 final double omx, final double omy, 360 final double mx, final double my, 361 boolean rev) 362 { 363 final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 364 365 // check round off errors producing cos(ext) > 1 and a NaN below 366 // cos(ext) == 1 implies colinear segments and an empty join anyway 367 if (cosext2 >= 0.5d) { 368 // just return to avoid generating a flat curve: 369 return; 370 } 371 372 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 373 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 374 // define the bezier curve we're computing. 375 // It is computed using the constraints that P1-P0 and P3-P2 are parallel 376 // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. 377 double cv = ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) / 378 (1.0d + Math.sqrt(cosext2 + 0.5d))); 379 // if clockwise, we need to negate cv. 380 if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) 381 cv = -cv; 382 } 383 final double x1 = cx + omx; 384 final double y1 = cy + omy; 385 final double x2 = x1 - cv * omy; 386 final double y2 = y1 + cv * omx; 387 388 final double x4 = cx + mx; 389 final double y4 = cy + my; 390 final double x3 = x4 + cv * my; 391 final double y3 = y4 - cv * mx; 392 393 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 394 } 395 396 private void drawRoundCap(double cx, double cy, double mx, double my) { 397 final double Cmx = C * mx; 398 final double Cmy = C * my; 399 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 400 cx - my + Cmx, cy + mx + Cmy, 401 cx - my, cy + mx); 402 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 403 cx - mx - Cmy, cy - my + Cmx, 404 cx - mx, cy - my); 405 } 406 407 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 408 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 409 private static void computeMiter(final double x0, final double y0, 410 final double x1, final double y1, 411 final double x0p, final double y0p, 412 final double x1p, final double y1p, 413 final double[] m, int off) 414 { 415 double x10 = x1 - x0; 416 double y10 = y1 - y0; 417 double x10p = x1p - x0p; 418 double y10p = y1p - y0p; 419 420 // if this is 0, the lines are parallel. If they go in the 421 // same direction, there is no intersection so m[off] and 422 // m[off+1] will contain infinity, so no miter will be drawn. 423 // If they go in the same direction that means that the start of the 424 // current segment and the end of the previous segment have the same 425 // tangent, in which case this method won't even be involved in 426 // miter drawing because it won't be called by drawMiter (because 427 // (mx == omx && my == omy) will be true, and drawMiter will return 428 // immediately). 429 double den = x10*y10p - x10p*y10; 430 double t = x10p*(y0-y0p) - y10p*(x0-x0p); 431 t /= den; 432 m[off++] = x0 + t*x10; 433 m[off] = y0 + t*y10; 434 } 435 436 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 437 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 438 private static void safeComputeMiter(final double x0, final double y0, 439 final double x1, final double y1, 440 final double x0p, final double y0p, 441 final double x1p, final double y1p, 442 final double[] m, int off) 443 { 444 double x10 = x1 - x0; 445 double y10 = y1 - y0; 446 double x10p = x1p - x0p; 447 double y10p = y1p - y0p; 448 449 // if this is 0, the lines are parallel. If they go in the 450 // same direction, there is no intersection so m[off] and 451 // m[off+1] will contain infinity, so no miter will be drawn. 452 // If they go in the same direction that means that the start of the 453 // current segment and the end of the previous segment have the same 454 // tangent, in which case this method won't even be involved in 455 // miter drawing because it won't be called by drawMiter (because 456 // (mx == omx && my == omy) will be true, and drawMiter will return 457 // immediately). 458 double den = x10*y10p - x10p*y10; 459 if (den == 0.0d) { 460 m[off++] = (x0 + x0p) / 2.0d; 461 m[off] = (y0 + y0p) / 2.0d; 462 return; 463 } 464 double t = x10p*(y0-y0p) - y10p*(x0-x0p); 465 t /= den; 466 m[off++] = x0 + t*x10; 467 m[off] = y0 + t*y10; 468 } 469 470 private void drawMiter(final double pdx, final double pdy, 471 final double x0, final double y0, 472 final double dx, final double dy, 473 double omx, double omy, double mx, double my, 474 boolean rev) 475 { 476 if ((mx == omx && my == omy) || 477 (pdx == 0.0d && pdy == 0.0d) || 478 (dx == 0.0d && dy == 0.0d)) 479 { 480 return; 481 } 482 483 if (rev) { 484 omx = -omx; 485 omy = -omy; 486 mx = -mx; 487 my = -my; 488 } 489 490 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 491 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, 492 miter, 0); 493 494 final double miterX = miter[0]; 495 final double miterY = miter[1]; 496 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 497 498 // If the lines are parallel, lenSq will be either NaN or +inf 499 // (actually, I'm not sure if the latter is possible. The important 500 // thing is that -inf is not possible, because lenSq is a square). 501 // For both of those values, the comparison below will fail and 502 // no miter will be drawn, which is correct. 503 if (lenSq < miterLimitSq) { 504 emitLineTo(miterX, miterY, rev); 505 } 506 } 507 508 @Override 509 public void moveTo(double x0, double y0) { 510 moveTo(x0, y0, cOutCode); 511 // update starting point: 512 this.sx0 = x0; 513 this.sy0 = y0; 514 this.sdx = 1.0d; 515 this.sdy = 0.0d; 516 this.opened = false; 517 this.capStart = false; 518 519 if (clipRect != null) { 520 final int outcode = DHelpers.outcode(x0, y0, clipRect); 521 this.cOutCode = outcode; 522 this.sOutCode = outcode; 523 } 524 } 525 526 private void moveTo(double x0, double y0, 527 final int outcode) 528 { 529 if (prev == MOVE_TO) { 530 this.cx0 = x0; 531 this.cy0 = y0; 532 } else { 533 if (prev == DRAWING_OP_TO) { 534 finish(outcode); 535 } 536 this.prev = MOVE_TO; 537 this.cx0 = x0; 538 this.cy0 = y0; 539 this.cdx = 1.0d; 540 this.cdy = 0.0d; 541 } 542 } 543 544 @Override 545 public void lineTo(double x1, double y1) { 546 lineTo(x1, y1, false); 547 } 548 549 private void lineTo(double x1, double y1, boolean force) { 550 final int outcode0 = this.cOutCode; 551 if (!force && clipRect != null) { 552 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 553 this.cOutCode = outcode1; 554 555 // basic rejection criteria 556 if ((outcode0 & outcode1) != 0) { 557 moveTo(x1, y1, outcode0); 558 opened = true; 559 return; 560 } 561 } 562 563 double dx = x1 - cx0; 564 double dy = y1 - cy0; 565 if (dx == 0.0d && dy == 0.0d) { 566 dx = 1.0d; 567 } 568 computeOffset(dx, dy, lineWidth2, offset0); 569 final double mx = offset0[0]; 570 final double my = offset0[1]; 571 572 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 573 574 emitLineTo(cx0 + mx, cy0 + my); 575 emitLineTo( x1 + mx, y1 + my); 576 577 emitLineToRev(cx0 - mx, cy0 - my); 578 emitLineToRev( x1 - mx, y1 - my); 579 580 this.prev = DRAWING_OP_TO; 581 this.cx0 = x1; 582 this.cy0 = y1; 583 this.cdx = dx; 584 this.cdy = dy; 585 this.cmx = mx; 586 this.cmy = my; 587 } 588 589 @Override 590 public void closePath() { 591 // distinguish empty path at all vs opened path ? 592 if (prev != DRAWING_OP_TO && !opened) { 593 if (prev == CLOSE) { 594 return; 595 } 596 emitMoveTo(cx0, cy0 - lineWidth2); 597 598 this.sdx = 1.0d; 599 this.sdy = 0.0d; 600 this.cdx = 1.0d; 601 this.cdy = 0.0d; 602 603 this.smx = 0.0d; 604 this.smy = -lineWidth2; 605 this.cmx = 0.0d; 606 this.cmy = -lineWidth2; 607 608 finish(cOutCode); 609 return; 610 } 611 612 if (sOutCode == 0) { 613 if (cx0 != sx0 || cy0 != sy0) { 614 lineTo(sx0, sy0, true); 615 } 616 617 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode); 618 619 emitLineTo(sx0 + smx, sy0 + smy); 620 621 if (opened) { 622 emitLineTo(sx0 - smx, sy0 - smy); 623 } else { 624 emitMoveTo(sx0 - smx, sy0 - smy); 625 } 626 } 627 // Ignore caps like finish(false) 628 emitReverse(); 629 630 this.prev = CLOSE; 631 632 if (opened) { 633 // do not emit close 634 opened = false; 635 } else { 636 emitClose(); 637 } 638 } 639 640 private void emitReverse() { 641 reverse.popAll(out); 642 } 643 644 @Override 645 public void pathDone() { 646 if (prev == DRAWING_OP_TO) { 647 finish(cOutCode); 648 } 649 650 out.pathDone(); 651 652 // this shouldn't matter since this object won't be used 653 // after the call to this method. 654 this.prev = CLOSE; 655 656 // Dispose this instance: 657 dispose(); 658 } 659 660 private void finish(final int outcode) { 661 // Problem: impossible to guess if the path will be closed in advance 662 // i.e. if caps must be drawn or not ? 663 // Solution: use the ClosedPathDetector before Stroker to determine 664 // if the path is a closed path or not 665 if (!rdrCtx.closedPath) { 666 if (outcode == 0) { 667 // current point = end's cap: 668 if (capStyle == CAP_ROUND) { 669 drawRoundCap(cx0, cy0, cmx, cmy); 670 } else if (capStyle == CAP_SQUARE) { 671 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 672 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 673 } 674 } 675 emitReverse(); 676 677 if (!capStart) { 678 capStart = true; 679 680 if (sOutCode == 0) { 681 // starting point = initial cap: 682 if (capStyle == CAP_ROUND) { 683 drawRoundCap(sx0, sy0, -smx, -smy); 684 } else if (capStyle == CAP_SQUARE) { 685 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 686 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 687 } 688 } 689 } 690 } else { 691 emitReverse(); 692 } 693 emitClose(); 694 } 695 696 private void emitMoveTo(final double x0, final double y0) { 697 out.moveTo(x0, y0); 698 } 699 700 private void emitLineTo(final double x1, final double y1) { 701 out.lineTo(x1, y1); 702 } 703 704 private void emitLineToRev(final double x1, final double y1) { 705 reverse.pushLine(x1, y1); 706 } 707 708 private void emitLineTo(final double x1, final double y1, 709 final boolean rev) 710 { 711 if (rev) { 712 emitLineToRev(x1, y1); 713 } else { 714 emitLineTo(x1, y1); 715 } 716 } 717 718 private void emitQuadTo(final double x1, final double y1, 719 final double x2, final double y2) 720 { 721 out.quadTo(x1, y1, x2, y2); 722 } 723 724 private void emitQuadToRev(final double x0, final double y0, 725 final double x1, final double y1) 726 { 727 reverse.pushQuad(x0, y0, x1, y1); 728 } 729 730 private void emitCurveTo(final double x1, final double y1, 731 final double x2, final double y2, 732 final double x3, final double y3) 733 { 734 out.curveTo(x1, y1, x2, y2, x3, y3); 735 } 736 737 private void emitCurveToRev(final double x0, final double y0, 738 final double x1, final double y1, 739 final double x2, final double y2) 740 { 741 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 742 } 743 744 private void emitCurveTo(final double x0, final double y0, 745 final double x1, final double y1, 746 final double x2, final double y2, 747 final double x3, final double y3, final boolean rev) 748 { 749 if (rev) { 750 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 751 } else { 752 out.curveTo(x1, y1, x2, y2, x3, y3); 753 } 754 } 755 756 private void emitClose() { 757 out.closePath(); 758 } 759 760 private void drawJoin(double pdx, double pdy, 761 double x0, double y0, 762 double dx, double dy, 763 double omx, double omy, 764 double mx, double my, 765 final int outcode) 766 { 767 if (prev != DRAWING_OP_TO) { 768 emitMoveTo(x0 + mx, y0 + my); 769 if (!opened) { 770 this.sdx = dx; 771 this.sdy = dy; 772 this.smx = mx; 773 this.smy = my; 774 } 775 } else { 776 final boolean cw = isCW(pdx, pdy, dx, dy); 777 if (outcode == 0) { 778 if (joinStyle == JOIN_MITER) { 779 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 780 } else if (joinStyle == JOIN_ROUND) { 781 drawRoundJoin(x0, y0, 782 omx, omy, 783 mx, my, cw, 784 ROUND_JOIN_THRESHOLD); 785 } 786 } 787 emitLineTo(x0, y0, !cw); 788 } 789 prev = DRAWING_OP_TO; 790 } 791 792 private static boolean within(final double x1, final double y1, 793 final double x2, final double y2, 794 final double ERR) 795 { 796 assert ERR > 0 : ""; 797 // compare taxicab distance. ERR will always be small, so using 798 // true distance won't give much benefit 799 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs 800 DHelpers.within(y1, y2, ERR)); // this is just as good. 801 } 802 803 private void getLineOffsets(double x1, double y1, 804 double x2, double y2, 805 double[] left, double[] right) { 806 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 807 final double mx = offset0[0]; 808 final double my = offset0[1]; 809 left[0] = x1 + mx; 810 left[1] = y1 + my; 811 left[2] = x2 + mx; 812 left[3] = y2 + my; 813 right[0] = x1 - mx; 814 right[1] = y1 - my; 815 right[2] = x2 - mx; 816 right[3] = y2 - my; 817 } 818 819 private int computeOffsetCubic(double[] pts, final int off, 820 double[] leftOff, double[] rightOff) 821 { 822 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 823 // vanishes, which creates problems with computeOffset. Usually 824 // this happens when this stroker object is trying to widen 825 // a curve with a cusp. What happens is that curveTo splits 826 // the input curve at the cusp, and passes it to this function. 827 // because of inaccuracies in the splitting, we consider points 828 // equal if they're very close to each other. 829 final double x1 = pts[off + 0], y1 = pts[off + 1]; 830 final double x2 = pts[off + 2], y2 = pts[off + 3]; 831 final double x3 = pts[off + 4], y3 = pts[off + 5]; 832 final double x4 = pts[off + 6], y4 = pts[off + 7]; 833 834 double dx4 = x4 - x3; 835 double dy4 = y4 - y3; 836 double dx1 = x2 - x1; 837 double dy1 = y2 - y1; 838 839 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 840 // in which case ignore if p1 == p2 841 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 842 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4)); 843 if (p1eqp2 && p3eqp4) { 844 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 845 return 4; 846 } else if (p1eqp2) { 847 dx1 = x3 - x1; 848 dy1 = y3 - y1; 849 } else if (p3eqp4) { 850 dx4 = x4 - x2; 851 dy4 = y4 - y2; 852 } 853 854 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 855 double dotsq = (dx1 * dx4 + dy1 * dy4); 856 dotsq *= dotsq; 857 double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 858 if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) { 859 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 860 return 4; 861 } 862 863 // What we're trying to do in this function is to approximate an ideal 864 // offset curve (call it I) of the input curve B using a bezier curve Bp. 865 // The constraints I use to get the equations are: 866 // 867 // 1. The computed curve Bp should go through I(0) and I(1). These are 868 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 869 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 870 // 871 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 872 // (by the way, the operator || in the comments below means "aligned with". 873 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 874 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 875 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 876 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 877 // nonzero constant.) 878 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 879 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 880 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 881 // is true for any bezier curve; therefore, we get the equations 882 // (1) p2p = c1 * (p2-p1) + p1p 883 // (2) p3p = c2 * (p4-p3) + p4p 884 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 885 // of unknowns from 4 to 2 (i.e. just c1 and c2). 886 // To eliminate these 2 unknowns we use the following constraint: 887 // 888 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 889 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 890 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 891 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 892 // We can substitute (1) and (2) from above into (4) and we get: 893 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 894 // which is equivalent to 895 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 896 // 897 // The right side of this is a 2D vector, and we know I(0.5), which gives us 898 // Bp(0.5), which gives us the value of the right side. 899 // The left side is just a matrix vector multiplication in disguise. It is 900 // 901 // [x2-x1, x4-x3][c1] 902 // [y2-y1, y4-y3][c2] 903 // which, is equal to 904 // [dx1, dx4][c1] 905 // [dy1, dy4][c2] 906 // At this point we are left with a simple linear system and we solve it by 907 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 908 // p2p and p3p. 909 910 double x = (x1 + 3.0d * (x2 + x3) + x4) / 8.0d; 911 double y = (y1 + 3.0d * (y2 + y3) + y4) / 8.0d; 912 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 913 // c*B'(0.5) for some constant c. 914 double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 915 916 // this computes the offsets at t=0, 0.5, 1, using the property that 917 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 918 // the (dx/dt, dy/dt) vectors at the endpoints. 919 computeOffset(dx1, dy1, lineWidth2, offset0); 920 computeOffset(dxm, dym, lineWidth2, offset1); 921 computeOffset(dx4, dy4, lineWidth2, offset2); 922 double x1p = x1 + offset0[0]; // start 923 double y1p = y1 + offset0[1]; // point 924 double xi = x + offset1[0]; // interpolation 925 double yi = y + offset1[1]; // point 926 double x4p = x4 + offset2[0]; // end 927 double y4p = y4 + offset2[1]; // point 928 929 double invdet43 = 4.0d / (3.0d * (dx1 * dy4 - dy1 * dx4)); 930 931 double two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 932 double two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 933 double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 934 double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 935 936 double x2p, y2p, x3p, y3p; 937 x2p = x1p + c1*dx1; 938 y2p = y1p + c1*dy1; 939 x3p = x4p + c2*dx4; 940 y3p = y4p + c2*dy4; 941 942 leftOff[0] = x1p; leftOff[1] = y1p; 943 leftOff[2] = x2p; leftOff[3] = y2p; 944 leftOff[4] = x3p; leftOff[5] = y3p; 945 leftOff[6] = x4p; leftOff[7] = y4p; 946 947 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 948 xi = xi - 2.0d * offset1[0]; yi = yi - 2.0d * offset1[1]; 949 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 950 951 two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 952 two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 953 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 954 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 955 956 x2p = x1p + c1*dx1; 957 y2p = y1p + c1*dy1; 958 x3p = x4p + c2*dx4; 959 y3p = y4p + c2*dy4; 960 961 rightOff[0] = x1p; rightOff[1] = y1p; 962 rightOff[2] = x2p; rightOff[3] = y2p; 963 rightOff[4] = x3p; rightOff[5] = y3p; 964 rightOff[6] = x4p; rightOff[7] = y4p; 965 return 8; 966 } 967 968 // compute offset curves using bezier spline through t=0.5 (i.e. 969 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 970 // return the kind of curve in the right and left arrays. 971 private int computeOffsetQuad(double[] pts, final int off, 972 double[] leftOff, double[] rightOff) 973 { 974 final double x1 = pts[off + 0], y1 = pts[off + 1]; 975 final double x2 = pts[off + 2], y2 = pts[off + 3]; 976 final double x3 = pts[off + 4], y3 = pts[off + 5]; 977 978 final double dx3 = x3 - x2; 979 final double dy3 = y3 - y2; 980 final double dx1 = x2 - x1; 981 final double dy1 = y2 - y1; 982 983 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 984 // vanishes, which creates problems with computeOffset. Usually 985 // this happens when this stroker object is trying to widen 986 // a curve with a cusp. What happens is that curveTo splits 987 // the input curve at the cusp, and passes it to this function. 988 // because of inaccuracies in the splitting, we consider points 989 // equal if they're very close to each other. 990 991 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 992 // in which case ignore. 993 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 994 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3)); 995 if (p1eqp2 || p2eqp3) { 996 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 997 return 4; 998 } 999 1000 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1001 double dotsq = (dx1 * dx3 + dy1 * dy3); 1002 dotsq *= dotsq; 1003 double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1004 if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) { 1005 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1006 return 4; 1007 } 1008 1009 // this computes the offsets at t=0, 0.5, 1, using the property that 1010 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1011 // the (dx/dt, dy/dt) vectors at the endpoints. 1012 computeOffset(dx1, dy1, lineWidth2, offset0); 1013 computeOffset(dx3, dy3, lineWidth2, offset1); 1014 1015 double x1p = x1 + offset0[0]; // start 1016 double y1p = y1 + offset0[1]; // point 1017 double x3p = x3 + offset1[0]; // end 1018 double y3p = y3 + offset1[1]; // point 1019 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); 1020 leftOff[0] = x1p; leftOff[1] = y1p; 1021 leftOff[4] = x3p; leftOff[5] = y3p; 1022 1023 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1024 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1025 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); 1026 rightOff[0] = x1p; rightOff[1] = y1p; 1027 rightOff[4] = x3p; rightOff[5] = y3p; 1028 return 6; 1029 } 1030 1031 // finds values of t where the curve in pts should be subdivided in order 1032 // to get good offset curves a distance of w away from the middle curve. 1033 // Stores the points in ts, and returns how many of them there were. 1034 private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts, 1035 final int type, final double w) 1036 { 1037 final double x12 = pts[2] - pts[0]; 1038 final double y12 = pts[3] - pts[1]; 1039 // if the curve is already parallel to either axis we gain nothing 1040 // from rotating it. 1041 if (y12 != 0.0d && x12 != 0.0d) { 1042 // we rotate it so that the first vector in the control polygon is 1043 // parallel to the x-axis. This will ensure that rotated quarter 1044 // circles won't be subdivided. 1045 final double hypot = Math.sqrt(x12 * x12 + y12 * y12); 1046 final double cos = x12 / hypot; 1047 final double sin = y12 / hypot; 1048 final double x1 = cos * pts[0] + sin * pts[1]; 1049 final double y1 = cos * pts[1] - sin * pts[0]; 1050 final double x2 = cos * pts[2] + sin * pts[3]; 1051 final double y2 = cos * pts[3] - sin * pts[2]; 1052 final double x3 = cos * pts[4] + sin * pts[5]; 1053 final double y3 = cos * pts[5] - sin * pts[4]; 1054 1055 switch(type) { 1056 case 8: 1057 final double x4 = cos * pts[6] + sin * pts[7]; 1058 final double y4 = cos * pts[7] - sin * pts[6]; 1059 c.set(x1, y1, x2, y2, x3, y3, x4, y4); 1060 break; 1061 case 6: 1062 c.set(x1, y1, x2, y2, x3, y3); 1063 break; 1064 default: 1065 } 1066 } else { 1067 c.set(pts, type); 1068 } 1069 1070 int ret = 0; 1071 // we subdivide at values of t such that the remaining rotated 1072 // curves are monotonic in x and y. 1073 ret += c.dxRoots(ts, ret); 1074 ret += c.dyRoots(ts, ret); 1075 // subdivide at inflection points. 1076 if (type == 8) { 1077 // quadratic curves can't have inflection points 1078 ret += c.infPoints(ts, ret); 1079 } 1080 1081 // now we must subdivide at points where one of the offset curves will have 1082 // a cusp. This happens at ts where the radius of curvature is equal to w. 1083 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d); 1084 1085 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d); 1086 DHelpers.isort(ts, 0, ret); 1087 return ret; 1088 } 1089 1090 @Override public void curveTo(double x1, double y1, 1091 double x2, double y2, 1092 double x3, 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(double x1, double y1, 1214 double x2, 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 }