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 */ DStroker(final DRendererContext rdrCtx)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 */ init(final DPathConsumer2D pc2d, final double lineWidth, final int capStyle, final int joinStyle, final double miterLimit, final boolean subdivideCurves)145 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 disableClipping()209 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 */ dispose()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 computeOffset(final double lx, final double ly, final double w, final double[] m)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. isCW(final double dx1, final double dy1, final double dx2, final double dy2)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 mayDrawRoundJoin(double cx, double cy, double omx, double omy, double mx, double my, boolean rev)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 drawRoundJoin(double cx, double cy, double omx, double omy, double mx, double my, boolean rev)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. drawBezApproxForArc(final double cx, final double cy, final double omx, final double omy, final double mx, final double my, boolean rev)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 drawRoundCap(double cx, double cy, double mx, double my)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] computeMiter(final double x0, final double y0, final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, final double[] m)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] safeComputeMiter(final double x0, final double y0, final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, final double[] m)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 drawMiter(final double pdx, final double pdy, final double x0, final double y0, final double dx, final double dy, double omx, double omy, double mx, double my, boolean rev)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 moveTo(final double x0, final double y0)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 _moveTo(final double x0, final double y0, final int outcode)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 lineTo(final double x1, final double y1)524 public void lineTo(final double x1, final double y1) { 525 lineTo(x1, y1, false); 526 } 527 lineTo(final double x1, final double y1, final boolean force)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 // overlap 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 closePath()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 this.cx0 = sx0; 638 this.cy0 = sy0; 639 this.cOutCode = sOutCode; 640 641 if (opened) { 642 // do not emit close 643 opened = false; 644 } else { 645 emitClose(); 646 } 647 } 648 emitReverse()649 private void emitReverse() { 650 reverse.popAll(out); 651 } 652 653 @Override pathDone()654 public void pathDone() { 655 if (prev == DRAWING_OP_TO) { 656 finish(cOutCode); 657 } 658 659 out.pathDone(); 660 661 // this shouldn't matter since this object won't be used 662 // after the call to this method. 663 this.prev = CLOSE; 664 665 // Dispose this instance: 666 dispose(); 667 } 668 finish(final int outcode)669 private void finish(final int outcode) { 670 // Problem: impossible to guess if the path will be closed in advance 671 // i.e. if caps must be drawn or not ? 672 // Solution: use the ClosedPathDetector before Stroker to determine 673 // if the path is a closed path or not 674 if (rdrCtx.closedPath) { 675 emitReverse(); 676 } else { 677 if (outcode == 0) { 678 // current point = end's cap: 679 if (capStyle == CAP_ROUND) { 680 drawRoundCap(cx0, cy0, cmx, cmy); 681 } else if (capStyle == CAP_SQUARE) { 682 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 683 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 684 } 685 } 686 emitReverse(); 687 688 if (!capStart) { 689 capStart = true; 690 691 if (sOutCode == 0) { 692 // starting point = initial cap: 693 if (capStyle == CAP_ROUND) { 694 drawRoundCap(sx0, sy0, -smx, -smy); 695 } else if (capStyle == CAP_SQUARE) { 696 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 697 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 698 } 699 } 700 } 701 } 702 emitClose(); 703 } 704 emitMoveTo(final double x0, final double y0)705 private void emitMoveTo(final double x0, final double y0) { 706 out.moveTo(x0, y0); 707 } 708 emitLineTo(final double x1, final double y1)709 private void emitLineTo(final double x1, final double y1) { 710 out.lineTo(x1, y1); 711 } 712 emitLineToRev(final double x1, final double y1)713 private void emitLineToRev(final double x1, final double y1) { 714 reverse.pushLine(x1, y1); 715 } 716 emitLineTo(final double x1, final double y1, final boolean rev)717 private void emitLineTo(final double x1, final double y1, 718 final boolean rev) 719 { 720 if (rev) { 721 emitLineToRev(x1, y1); 722 } else { 723 emitLineTo(x1, y1); 724 } 725 } 726 emitQuadTo(final double x1, final double y1, final double x2, final double y2)727 private void emitQuadTo(final double x1, final double y1, 728 final double x2, final double y2) 729 { 730 out.quadTo(x1, y1, x2, y2); 731 } 732 emitQuadToRev(final double x0, final double y0, final double x1, final double y1)733 private void emitQuadToRev(final double x0, final double y0, 734 final double x1, final double y1) 735 { 736 reverse.pushQuad(x0, y0, x1, y1); 737 } 738 emitCurveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3)739 private void emitCurveTo(final double x1, final double y1, 740 final double x2, final double y2, 741 final double x3, final double y3) 742 { 743 out.curveTo(x1, y1, x2, y2, x3, y3); 744 } 745 emitCurveToRev(final double x0, final double y0, final double x1, final double y1, final double x2, final double y2)746 private void emitCurveToRev(final double x0, final double y0, 747 final double x1, final double y1, 748 final double x2, final double y2) 749 { 750 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 751 } 752 emitCurveTo(final double x0, final double y0, final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final boolean rev)753 private void emitCurveTo(final double x0, final double y0, 754 final double x1, final double y1, 755 final double x2, final double y2, 756 final double x3, final double y3, final boolean rev) 757 { 758 if (rev) { 759 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 760 } else { 761 out.curveTo(x1, y1, x2, y2, x3, y3); 762 } 763 } 764 emitClose()765 private void emitClose() { 766 out.closePath(); 767 } 768 drawJoin(double pdx, double pdy, double x0, double y0, double dx, double dy, double omx, double omy, double mx, double my, final int outcode)769 private void drawJoin(double pdx, double pdy, 770 double x0, double y0, 771 double dx, double dy, 772 double omx, double omy, 773 double mx, double my, 774 final int outcode) 775 { 776 if (prev != DRAWING_OP_TO) { 777 emitMoveTo(x0 + mx, y0 + my); 778 if (!opened) { 779 this.sdx = dx; 780 this.sdy = dy; 781 this.smx = mx; 782 this.smy = my; 783 } 784 } else { 785 final boolean cw = isCW(pdx, pdy, dx, dy); 786 if (outcode == 0) { 787 if (joinStyle == JOIN_MITER) { 788 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 789 } else if (joinStyle == JOIN_ROUND) { 790 mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw); 791 } 792 } 793 emitLineTo(x0, y0, !cw); 794 } 795 prev = DRAWING_OP_TO; 796 } 797 within(final double x1, final double y1, final double x2, final double y2, final double err)798 private static boolean within(final double x1, final double y1, 799 final double x2, final double y2, 800 final double err) 801 { 802 assert err > 0 : ""; 803 // compare taxicab distance. ERR will always be small, so using 804 // true distance won't give much benefit 805 return (DHelpers.within(x1, x2, err) && // we want to avoid calling Math.abs 806 DHelpers.within(y1, y2, err)); // this is just as good. 807 } 808 getLineOffsets(final double x1, final double y1, final double x2, final double y2, final double[] left, final double[] right)809 private void getLineOffsets(final double x1, final double y1, 810 final double x2, final double y2, 811 final double[] left, final double[] right) 812 { 813 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 814 final double mx = offset0[0]; 815 final double my = offset0[1]; 816 left[0] = x1 + mx; 817 left[1] = y1 + my; 818 left[2] = x2 + mx; 819 left[3] = y2 + my; 820 821 right[0] = x1 - mx; 822 right[1] = y1 - my; 823 right[2] = x2 - mx; 824 right[3] = y2 - my; 825 } 826 computeOffsetCubic(final double[] pts, final int off, final double[] leftOff, final double[] rightOff)827 private int computeOffsetCubic(final double[] pts, final int off, 828 final double[] leftOff, 829 final double[] rightOff) 830 { 831 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 832 // vanishes, which creates problems with computeOffset. Usually 833 // this happens when this stroker object is trying to widen 834 // a curve with a cusp. What happens is that curveTo splits 835 // the input curve at the cusp, and passes it to this function. 836 // because of inaccuracies in the splitting, we consider points 837 // equal if they're very close to each other. 838 final double x1 = pts[off ], y1 = pts[off + 1]; 839 final double x2 = pts[off + 2], y2 = pts[off + 3]; 840 final double x3 = pts[off + 4], y3 = pts[off + 5]; 841 final double x4 = pts[off + 6], y4 = pts[off + 7]; 842 843 double dx4 = x4 - x3; 844 double dy4 = y4 - y3; 845 double dx1 = x2 - x1; 846 double dy1 = y2 - y1; 847 848 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 849 // in which case ignore if p1 == p2 850 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 851 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4)); 852 853 if (p1eqp2 && p3eqp4) { 854 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 855 return 4; 856 } else if (p1eqp2) { 857 dx1 = x3 - x1; 858 dy1 = y3 - y1; 859 } else if (p3eqp4) { 860 dx4 = x4 - x2; 861 dy4 = y4 - y2; 862 } 863 864 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 865 double dotsq = (dx1 * dx4 + dy1 * dy4); 866 dotsq *= dotsq; 867 double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 868 869 if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) { 870 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 871 return 4; 872 } 873 874 // What we're trying to do in this function is to approximate an ideal 875 // offset curve (call it I) of the input curve B using a bezier curve Bp. 876 // The constraints I use to get the equations are: 877 // 878 // 1. The computed curve Bp should go through I(0) and I(1). These are 879 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 880 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 881 // 882 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 883 // (by the way, the operator || in the comments below means "aligned with". 884 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 885 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 886 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 887 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 888 // nonzero constant.) 889 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 890 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 891 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 892 // is true for any bezier curve; therefore, we get the equations 893 // (1) p2p = c1 * (p2-p1) + p1p 894 // (2) p3p = c2 * (p4-p3) + p4p 895 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 896 // of unknowns from 4 to 2 (i.e. just c1 and c2). 897 // To eliminate these 2 unknowns we use the following constraint: 898 // 899 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 900 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 901 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 902 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 903 // We can substitute (1) and (2) from above into (4) and we get: 904 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 905 // which is equivalent to 906 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 907 // 908 // The right side of this is a 2D vector, and we know I(0.5), which gives us 909 // Bp(0.5), which gives us the value of the right side. 910 // The left side is just a matrix vector multiplication in disguise. It is 911 // 912 // [x2-x1, x4-x3][c1] 913 // [y2-y1, y4-y3][c2] 914 // which, is equal to 915 // [dx1, dx4][c1] 916 // [dy1, dy4][c2] 917 // At this point we are left with a simple linear system and we solve it by 918 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 919 // p2p and p3p. 920 921 double x = (x1 + 3.0d * (x2 + x3) + x4) / 8.0d; 922 double y = (y1 + 3.0d * (y2 + y3) + y4) / 8.0d; 923 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 924 // c*B'(0.5) for some constant c. 925 double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 926 927 // this computes the offsets at t=0, 0.5, 1, using the property that 928 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 929 // the (dx/dt, dy/dt) vectors at the endpoints. 930 computeOffset(dx1, dy1, lineWidth2, offset0); 931 computeOffset(dxm, dym, lineWidth2, offset1); 932 computeOffset(dx4, dy4, lineWidth2, offset2); 933 double x1p = x1 + offset0[0]; // start 934 double y1p = y1 + offset0[1]; // point 935 double xi = x + offset1[0]; // interpolation 936 double yi = y + offset1[1]; // point 937 double x4p = x4 + offset2[0]; // end 938 double y4p = y4 + offset2[1]; // point 939 940 double invdet43 = 4.0d / (3.0d * (dx1 * dy4 - dy1 * dx4)); 941 942 double two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 943 double two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 944 double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 945 double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 946 947 double x2p, y2p, x3p, y3p; 948 x2p = x1p + c1*dx1; 949 y2p = y1p + c1*dy1; 950 x3p = x4p + c2*dx4; 951 y3p = y4p + c2*dy4; 952 953 leftOff[0] = x1p; leftOff[1] = y1p; 954 leftOff[2] = x2p; leftOff[3] = y2p; 955 leftOff[4] = x3p; leftOff[5] = y3p; 956 leftOff[6] = x4p; leftOff[7] = y4p; 957 958 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 959 xi = xi - 2.0d * offset1[0]; yi = yi - 2.0d * offset1[1]; 960 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 961 962 two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p; 963 two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p; 964 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 965 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 966 967 x2p = x1p + c1*dx1; 968 y2p = y1p + c1*dy1; 969 x3p = x4p + c2*dx4; 970 y3p = y4p + c2*dy4; 971 972 rightOff[0] = x1p; rightOff[1] = y1p; 973 rightOff[2] = x2p; rightOff[3] = y2p; 974 rightOff[4] = x3p; rightOff[5] = y3p; 975 rightOff[6] = x4p; rightOff[7] = y4p; 976 return 8; 977 } 978 979 // compute offset curves using bezier spline through t=0.5 (i.e. 980 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 981 // return the kind of curve in the right and left arrays. computeOffsetQuad(final double[] pts, final int off, final double[] leftOff, final double[] rightOff)982 private int computeOffsetQuad(final double[] pts, final int off, 983 final double[] leftOff, 984 final double[] rightOff) 985 { 986 final double x1 = pts[off ], y1 = pts[off + 1]; 987 final double x2 = pts[off + 2], y2 = pts[off + 3]; 988 final double x3 = pts[off + 4], y3 = pts[off + 5]; 989 990 final double dx3 = x3 - x2; 991 final double dy3 = y3 - y2; 992 final double dx1 = x2 - x1; 993 final double dy1 = y2 - y1; 994 995 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 996 // vanishes, which creates problems with computeOffset. Usually 997 // this happens when this stroker object is trying to widen 998 // a curve with a cusp. What happens is that curveTo splits 999 // the input curve at the cusp, and passes it to this function. 1000 // because of inaccuracies in the splitting, we consider points 1001 // equal if they're very close to each other. 1002 1003 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 1004 // in which case ignore. 1005 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); 1006 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3)); 1007 1008 if (p1eqp2 || p2eqp3) { 1009 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1010 return 4; 1011 } 1012 1013 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1014 double dotsq = (dx1 * dx3 + dy1 * dy3); 1015 dotsq *= dotsq; 1016 double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1017 1018 if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) { 1019 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1020 return 4; 1021 } 1022 1023 // this computes the offsets at t=0, 0.5, 1, using the property that 1024 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1025 // the (dx/dt, dy/dt) vectors at the endpoints. 1026 computeOffset(dx1, dy1, lineWidth2, offset0); 1027 computeOffset(dx3, dy3, lineWidth2, offset1); 1028 1029 double x1p = x1 + offset0[0]; // start 1030 double y1p = y1 + offset0[1]; // point 1031 double x3p = x3 + offset1[0]; // end 1032 double y3p = y3 + offset1[1]; // point 1033 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff); 1034 leftOff[0] = x1p; leftOff[1] = y1p; 1035 leftOff[4] = x3p; leftOff[5] = y3p; 1036 1037 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1038 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1039 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff); 1040 rightOff[0] = x1p; rightOff[1] = y1p; 1041 rightOff[4] = x3p; rightOff[5] = y3p; 1042 return 6; 1043 } 1044 1045 @Override curveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3)1046 public void curveTo(final double x1, final double y1, 1047 final double x2, final double y2, 1048 final double x3, final double y3) 1049 { 1050 final int outcode0 = this.cOutCode; 1051 1052 if (clipRect != null) { 1053 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 1054 final int outcode2 = DHelpers.outcode(x2, y2, clipRect); 1055 final int outcode3 = DHelpers.outcode(x3, y3, clipRect); 1056 1057 // Should clip 1058 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); 1059 if (orCode != 0) { 1060 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; 1061 1062 // basic rejection criteria: 1063 if (sideCode == 0) { 1064 // overlap clip: 1065 if (subdivide) { 1066 // avoid reentrance 1067 subdivide = false; 1068 // subdivide curve => callback with subdivided parts: 1069 boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, 1070 x2, y2, x3, y3, 1071 orCode, this); 1072 // reentrance is done: 1073 subdivide = true; 1074 if (ret) { 1075 return; 1076 } 1077 } 1078 // already subdivided so render it 1079 } else { 1080 this.cOutCode = outcode3; 1081 _moveTo(x3, y3, outcode0); 1082 opened = true; 1083 return; 1084 } 1085 } 1086 1087 this.cOutCode = outcode3; 1088 } 1089 _curveTo(x1, y1, x2, y2, x3, y3, outcode0); 1090 } 1091 _curveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final int outcode0)1092 private void _curveTo(final double x1, final double y1, 1093 final double x2, final double y2, 1094 final double x3, final double y3, 1095 final int outcode0) 1096 { 1097 // need these so we can update the state at the end of this method 1098 double dxs = x1 - cx0; 1099 double dys = y1 - cy0; 1100 double dxf = x3 - x2; 1101 double dyf = y3 - y2; 1102 1103 if ((dxs == 0.0d) && (dys == 0.0d)) { 1104 dxs = x2 - cx0; 1105 dys = y2 - cy0; 1106 if ((dxs == 0.0d) && (dys == 0.0d)) { 1107 dxs = x3 - cx0; 1108 dys = y3 - cy0; 1109 } 1110 } 1111 if ((dxf == 0.0d) && (dyf == 0.0d)) { 1112 dxf = x3 - x1; 1113 dyf = y3 - y1; 1114 if ((dxf == 0.0d) && (dyf == 0.0d)) { 1115 dxf = x3 - cx0; 1116 dyf = y3 - cy0; 1117 } 1118 } 1119 if ((dxs == 0.0d) && (dys == 0.0d)) { 1120 // this happens if the "curve" is just a point 1121 // fix outcode0 for lineTo() call: 1122 if (clipRect != null) { 1123 this.cOutCode = outcode0; 1124 } 1125 lineTo(cx0, cy0); 1126 return; 1127 } 1128 1129 // if these vectors are too small, normalize them, to avoid future 1130 // precision problems. 1131 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) { 1132 final double len = Math.sqrt(dxs * dxs + dys * dys); 1133 dxs /= len; 1134 dys /= len; 1135 } 1136 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) { 1137 final double len = Math.sqrt(dxf * dxf + dyf * dyf); 1138 dxf /= len; 1139 dyf /= len; 1140 } 1141 1142 computeOffset(dxs, dys, lineWidth2, offset0); 1143 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1144 1145 int nSplits = 0; 1146 final double[] mid; 1147 final double[] l = lp; 1148 1149 if (monotonize) { 1150 // monotonize curve: 1151 final CurveBasicMonotonizer monotonizer 1152 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); 1153 1154 nSplits = monotonizer.nbSplits; 1155 mid = monotonizer.middle; 1156 } else { 1157 // use left instead: 1158 mid = l; 1159 mid[0] = cx0; mid[1] = cy0; 1160 mid[2] = x1; mid[3] = y1; 1161 mid[4] = x2; mid[5] = y2; 1162 mid[6] = x3; mid[7] = y3; 1163 } 1164 final double[] r = rp; 1165 1166 int kind = 0; 1167 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1168 kind = computeOffsetCubic(mid, off, l, r); 1169 1170 emitLineTo(l[0], l[1]); 1171 1172 switch(kind) { 1173 case 8: 1174 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1175 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1176 break; 1177 case 4: 1178 emitLineTo(l[2], l[3]); 1179 emitLineToRev(r[0], r[1]); 1180 break; 1181 default: 1182 } 1183 emitLineToRev(r[kind - 2], r[kind - 1]); 1184 } 1185 1186 this.prev = DRAWING_OP_TO; 1187 this.cx0 = x3; 1188 this.cy0 = y3; 1189 this.cdx = dxf; 1190 this.cdy = dyf; 1191 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; 1192 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; 1193 } 1194 1195 @Override quadTo(final double x1, final double y1, final double x2, final double y2)1196 public void quadTo(final double x1, final double y1, 1197 final double x2, final double y2) 1198 { 1199 final int outcode0 = this.cOutCode; 1200 1201 if (clipRect != null) { 1202 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 1203 final int outcode2 = DHelpers.outcode(x2, y2, clipRect); 1204 1205 // Should clip 1206 final int orCode = (outcode0 | outcode1 | outcode2); 1207 if (orCode != 0) { 1208 final int sideCode = outcode0 & outcode1 & outcode2; 1209 1210 // basic rejection criteria: 1211 if (sideCode == 0) { 1212 // overlap clip: 1213 if (subdivide) { 1214 // avoid reentrance 1215 subdivide = false; 1216 // subdivide curve => call lineTo() with subdivided curves: 1217 boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, 1218 x2, y2, orCode, this); 1219 // reentrance is done: 1220 subdivide = true; 1221 if (ret) { 1222 return; 1223 } 1224 } 1225 // already subdivided so render it 1226 } else { 1227 this.cOutCode = outcode2; 1228 _moveTo(x2, y2, outcode0); 1229 opened = true; 1230 return; 1231 } 1232 } 1233 1234 this.cOutCode = outcode2; 1235 } 1236 _quadTo(x1, y1, x2, y2, outcode0); 1237 } 1238 _quadTo(final double x1, final double y1, final double x2, final double y2, final int outcode0)1239 private void _quadTo(final double x1, final double y1, 1240 final double x2, final double y2, 1241 final int outcode0) 1242 { 1243 // need these so we can update the state at the end of this method 1244 double dxs = x1 - cx0; 1245 double dys = y1 - cy0; 1246 double dxf = x2 - x1; 1247 double dyf = y2 - y1; 1248 1249 if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) { 1250 dxs = dxf = x2 - cx0; 1251 dys = dyf = y2 - cy0; 1252 } 1253 if ((dxs == 0.0d) && (dys == 0.0d)) { 1254 // this happens if the "curve" is just a point 1255 // fix outcode0 for lineTo() call: 1256 if (clipRect != null) { 1257 this.cOutCode = outcode0; 1258 } 1259 lineTo(cx0, cy0); 1260 return; 1261 } 1262 // if these vectors are too small, normalize them, to avoid future 1263 // precision problems. 1264 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) { 1265 final double len = Math.sqrt(dxs * dxs + dys * dys); 1266 dxs /= len; 1267 dys /= len; 1268 } 1269 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) { 1270 final double len = Math.sqrt(dxf * dxf + dyf * dyf); 1271 dxf /= len; 1272 dyf /= len; 1273 } 1274 computeOffset(dxs, dys, lineWidth2, offset0); 1275 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1276 1277 int nSplits = 0; 1278 final double[] mid; 1279 final double[] l = lp; 1280 1281 if (monotonize) { 1282 // monotonize quad: 1283 final CurveBasicMonotonizer monotonizer 1284 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); 1285 1286 nSplits = monotonizer.nbSplits; 1287 mid = monotonizer.middle; 1288 } else { 1289 // use left instead: 1290 mid = l; 1291 mid[0] = cx0; mid[1] = cy0; 1292 mid[2] = x1; mid[3] = y1; 1293 mid[4] = x2; mid[5] = y2; 1294 } 1295 final double[] r = rp; 1296 1297 int kind = 0; 1298 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1299 kind = computeOffsetQuad(mid, off, l, r); 1300 1301 emitLineTo(l[0], l[1]); 1302 1303 switch(kind) { 1304 case 6: 1305 emitQuadTo(l[2], l[3], l[4], l[5]); 1306 emitQuadToRev(r[0], r[1], r[2], r[3]); 1307 break; 1308 case 4: 1309 emitLineTo(l[2], l[3]); 1310 emitLineToRev(r[0], r[1]); 1311 break; 1312 default: 1313 } 1314 emitLineToRev(r[kind - 2], r[kind - 1]); 1315 } 1316 1317 this.prev = DRAWING_OP_TO; 1318 this.cx0 = x2; 1319 this.cy0 = y2; 1320 this.cdx = dxf; 1321 this.cdy = dyf; 1322 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; 1323 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; 1324 } 1325 getNativeConsumer()1326 @Override public long getNativeConsumer() { 1327 throw new InternalError("Stroker doesn't use a native consumer"); 1328 } 1329 } 1330