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