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