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