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