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