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