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 sun.awt.geom.PathConsumer2D; 29 import java.awt.geom.AffineTransform; 30 import java.awt.geom.Path2D; 31 32 final class TransformingPathConsumer2D { 33 34 TransformingPathConsumer2D() { 35 // used by RendererContext 36 } 37 38 // recycled PathConsumer2D instance from wrapPath2d() 39 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); 40 41 PathConsumer2D wrapPath2d(Path2D.Float p2d) 42 { 43 return wp_Path2DWrapper.init(p2d); 44 } 45 46 // recycled PathConsumer2D instances from deltaTransformConsumer() 47 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); 48 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); 49 50 PathConsumer2D deltaTransformConsumer(PathConsumer2D out, 51 AffineTransform at) 52 { 53 if (at == null) { 54 return out; 55 } 56 float mxx = (float) at.getScaleX(); 57 float mxy = (float) at.getShearX(); 58 float myx = (float) at.getShearY(); 59 float myy = (float) at.getScaleY(); 60 61 if (mxy == 0.0f && myx == 0.0f) { 62 if (mxx == 1.0f && myy == 1.0f) { 63 return out; 64 } else { 65 return dt_DeltaScaleFilter.init(out, mxx, myy); 66 } 67 } else { 68 return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); 69 } 70 } 71 72 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() 73 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); 74 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); 75 76 PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, 77 AffineTransform at) 78 { 79 if (at == null) { 80 return out; 81 } 82 float mxx = (float) at.getScaleX(); 83 float mxy = (float) at.getShearX(); 84 float myx = (float) at.getShearY(); 85 float myy = (float) at.getScaleY(); 86 87 if (mxy == 0.0f && myx == 0.0f) { 88 if (mxx == 1.0f && myy == 1.0f) { 89 return out; 90 } else { 91 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); 92 } 93 } else { 94 float det = mxx * myy - mxy * myx; 95 return iv_DeltaTransformFilter.init(out, 96 myy / det, 97 -mxy / det, 98 -myx / det, 99 mxx / det); 100 } 101 } 102 103 104 static final class DeltaScaleFilter implements PathConsumer2D { 105 private PathConsumer2D out; 106 private float sx, sy; 107 108 DeltaScaleFilter() {} 109 110 DeltaScaleFilter init(PathConsumer2D out, 111 float mxx, float myy) 112 { 113 this.out = out; 114 sx = mxx; 115 sy = myy; 116 return this; // fluent API 117 } 118 119 @Override 120 public void moveTo(float x0, float y0) { 121 out.moveTo(x0 * sx, y0 * sy); 122 } 123 251 252 @Override 253 public void closePath() { 254 p2d.closePath(); 255 } 256 257 @Override 258 public void pathDone() {} 259 260 @Override 261 public void curveTo(float x1, float y1, 262 float x2, float y2, 263 float x3, float y3) 264 { 265 p2d.curveTo(x1, y1, x2, y2, x3, y3); 266 } 267 268 @Override 269 public void quadTo(float x1, float y1, float x2, float y2) { 270 p2d.quadTo(x1, y1, x2, y2); 271 } 272 273 @Override 274 public long getNativeConsumer() { 275 throw new InternalError("Not using a native peer"); 276 } 277 } 278 } | 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 sun.awt.geom.PathConsumer2D; 29 import java.awt.geom.AffineTransform; 30 import java.awt.geom.Path2D; 31 import sun.java2d.marlin.Helpers.IndexStack; 32 import sun.java2d.marlin.Helpers.PolyStack; 33 34 final class TransformingPathConsumer2D { 35 36 private final RendererContext rdrCtx; 37 38 // recycled ClosedPathDetector instance from detectClosedPath() 39 private final ClosedPathDetector cpDetector; 40 41 // recycled PathClipFilter instance from pathClipper() 42 private final PathClipFilter pathClipper; 43 44 // recycled PathConsumer2D instance from wrapPath2D() 45 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); 46 47 // recycled PathConsumer2D instances from deltaTransformConsumer() 48 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); 49 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); 50 51 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() 52 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); 53 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); 54 55 // recycled PathTracer instances from tracer...() methods 56 private final PathTracer tracerInput = new PathTracer("[Input]"); 57 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); 58 private final PathTracer tracerFiller = new PathTracer("Filler"); 59 private final PathTracer tracerStroker = new PathTracer("Stroker"); 60 61 TransformingPathConsumer2D(final RendererContext rdrCtx) { 62 // used by RendererContext 63 this.rdrCtx = rdrCtx; 64 this.cpDetector = new ClosedPathDetector(rdrCtx); 65 this.pathClipper = new PathClipFilter(rdrCtx); 66 } 67 68 PathConsumer2D wrapPath2D(Path2D.Float p2d) 69 { 70 return wp_Path2DWrapper.init(p2d); 71 } 72 73 PathConsumer2D traceInput(PathConsumer2D out) { 74 return tracerInput.init(out); 75 } 76 77 PathConsumer2D traceClosedPathDetector(PathConsumer2D out) { 78 return tracerCPDetector.init(out); 79 } 80 81 PathConsumer2D traceFiller(PathConsumer2D out) { 82 return tracerFiller.init(out); 83 } 84 85 PathConsumer2D traceStroker(PathConsumer2D out) { 86 return tracerStroker.init(out); 87 } 88 89 PathConsumer2D detectClosedPath(PathConsumer2D out) 90 { 91 return cpDetector.init(out); 92 } 93 94 PathConsumer2D pathClipper(PathConsumer2D out) 95 { 96 return pathClipper.init(out); 97 } 98 99 PathConsumer2D deltaTransformConsumer(PathConsumer2D out, 100 AffineTransform at) 101 { 102 if (at == null) { 103 return out; 104 } 105 final float mxx = (float) at.getScaleX(); 106 final float mxy = (float) at.getShearX(); 107 final float myx = (float) at.getShearY(); 108 final float myy = (float) at.getScaleY(); 109 110 if (mxy == 0.0f && myx == 0.0f) { 111 if (mxx == 1.0f && myy == 1.0f) { 112 return out; 113 } else { 114 // Scale only 115 if (rdrCtx.doClip) { 116 // adjust clip rectangle (ymin, ymax, xmin, xmax): 117 adjustClipScale(rdrCtx.clipRect, mxx, myy); 118 } 119 return dt_DeltaScaleFilter.init(out, mxx, myy); 120 } 121 } else { 122 if (rdrCtx.doClip) { 123 // adjust clip rectangle (ymin, ymax, xmin, xmax): 124 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); 125 } 126 return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); 127 } 128 } 129 130 private static void adjustClipOffset(final float[] clipRect) { 131 clipRect[0] += Renderer.RDR_OFFSET_Y; 132 clipRect[1] += Renderer.RDR_OFFSET_Y; 133 clipRect[2] += Renderer.RDR_OFFSET_X; 134 clipRect[3] += Renderer.RDR_OFFSET_X; 135 } 136 137 private static void adjustClipScale(final float[] clipRect, 138 final float mxx, final float myy) 139 { 140 adjustClipOffset(clipRect); 141 142 // Adjust the clipping rectangle (iv_DeltaScaleFilter): 143 clipRect[0] /= myy; 144 clipRect[1] /= myy; 145 clipRect[2] /= mxx; 146 clipRect[3] /= mxx; 147 } 148 149 private static void adjustClipInverseDelta(final float[] clipRect, 150 final float mxx, final float mxy, 151 final float myx, final float myy) 152 { 153 adjustClipOffset(clipRect); 154 155 // Adjust the clipping rectangle (iv_DeltaTransformFilter): 156 final float det = mxx * myy - mxy * myx; 157 final float imxx = myy / det; 158 final float imxy = -mxy / det; 159 final float imyx = -myx / det; 160 final float imyy = mxx / det; 161 162 float xmin, xmax, ymin, ymax; 163 float x, y; 164 // xmin, ymin: 165 x = clipRect[2] * imxx + clipRect[0] * imxy; 166 y = clipRect[2] * imyx + clipRect[0] * imyy; 167 168 xmin = xmax = x; 169 ymin = ymax = y; 170 171 // xmax, ymin: 172 x = clipRect[3] * imxx + clipRect[0] * imxy; 173 y = clipRect[3] * imyx + clipRect[0] * imyy; 174 175 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 176 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 177 178 // xmin, ymax: 179 x = clipRect[2] * imxx + clipRect[1] * imxy; 180 y = clipRect[2] * imyx + clipRect[1] * imyy; 181 182 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 183 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 184 185 // xmax, ymax: 186 x = clipRect[3] * imxx + clipRect[1] * imxy; 187 y = clipRect[3] * imyx + clipRect[1] * imyy; 188 189 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 190 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 191 192 clipRect[0] = ymin; 193 clipRect[1] = ymax; 194 clipRect[2] = xmin; 195 clipRect[3] = xmax; 196 } 197 198 PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, 199 AffineTransform at) 200 { 201 if (at == null) { 202 return out; 203 } 204 float mxx = (float) at.getScaleX(); 205 float mxy = (float) at.getShearX(); 206 float myx = (float) at.getShearY(); 207 float myy = (float) at.getScaleY(); 208 209 if (mxy == 0.0f && myx == 0.0f) { 210 if (mxx == 1.0f && myy == 1.0f) { 211 return out; 212 } else { 213 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); 214 } 215 } else { 216 final float det = mxx * myy - mxy * myx; 217 return iv_DeltaTransformFilter.init(out, 218 myy / det, 219 -mxy / det, 220 -myx / det, 221 mxx / det); 222 } 223 } 224 225 static final class DeltaScaleFilter implements PathConsumer2D { 226 private PathConsumer2D out; 227 private float sx, sy; 228 229 DeltaScaleFilter() {} 230 231 DeltaScaleFilter init(PathConsumer2D out, 232 float mxx, float myy) 233 { 234 this.out = out; 235 sx = mxx; 236 sy = myy; 237 return this; // fluent API 238 } 239 240 @Override 241 public void moveTo(float x0, float y0) { 242 out.moveTo(x0 * sx, y0 * sy); 243 } 244 372 373 @Override 374 public void closePath() { 375 p2d.closePath(); 376 } 377 378 @Override 379 public void pathDone() {} 380 381 @Override 382 public void curveTo(float x1, float y1, 383 float x2, float y2, 384 float x3, float y3) 385 { 386 p2d.curveTo(x1, y1, x2, y2, x3, y3); 387 } 388 389 @Override 390 public void quadTo(float x1, float y1, float x2, float y2) { 391 p2d.quadTo(x1, y1, x2, y2); 392 } 393 394 @Override 395 public long getNativeConsumer() { 396 throw new InternalError("Not using a native peer"); 397 } 398 } 399 400 static final class ClosedPathDetector implements PathConsumer2D { 401 402 private final RendererContext rdrCtx; 403 private final PolyStack stack; 404 405 private PathConsumer2D out; 406 407 ClosedPathDetector(final RendererContext rdrCtx) { 408 this.rdrCtx = rdrCtx; 409 this.stack = (rdrCtx.stats != null) ? 410 new PolyStack(rdrCtx, 411 rdrCtx.stats.stat_cpd_polystack_types, 412 rdrCtx.stats.stat_cpd_polystack_curves, 413 rdrCtx.stats.hist_cpd_polystack_curves, 414 rdrCtx.stats.stat_array_cpd_polystack_curves, 415 rdrCtx.stats.stat_array_cpd_polystack_types) 416 : new PolyStack(rdrCtx); 417 } 418 419 ClosedPathDetector init(PathConsumer2D out) { 420 this.out = out; 421 return this; // fluent API 422 } 423 424 /** 425 * Disposes this instance: 426 * clean up before reusing this instance 427 */ 428 void dispose() { 429 stack.dispose(); 430 } 431 432 @Override 433 public void pathDone() { 434 // previous path is not closed: 435 finish(false); 436 out.pathDone(); 437 438 // TODO: fix possible leak if exception happened 439 // Dispose this instance: 440 dispose(); 441 } 442 443 @Override 444 public void closePath() { 445 // path is closed 446 finish(true); 447 out.closePath(); 448 } 449 450 @Override 451 public void moveTo(float x0, float y0) { 452 // previous path is not closed: 453 finish(false); 454 out.moveTo(x0, y0); 455 } 456 457 private void finish(final boolean closed) { 458 rdrCtx.closedPath = closed; 459 stack.pullAll(out); 460 } 461 462 @Override 463 public void lineTo(float x1, float y1) { 464 stack.pushLine(x1, y1); 465 } 466 467 @Override 468 public void curveTo(float x3, float y3, 469 float x2, float y2, 470 float x1, float y1) 471 { 472 stack.pushCubic(x1, y1, x2, y2, x3, y3); 473 } 474 475 @Override 476 public void quadTo(float x2, float y2, float x1, float y1) { 477 stack.pushQuad(x1, y1, x2, y2); 478 } 479 480 @Override 481 public long getNativeConsumer() { 482 throw new InternalError("Not using a native peer"); 483 } 484 } 485 486 static final class PathClipFilter implements PathConsumer2D { 487 488 private PathConsumer2D out; 489 490 // Bounds of the drawing region, at pixel precision. 491 private final float[] clipRect; 492 493 private final float[] corners = new float[8]; 494 private boolean init_corners = false; 495 496 private final IndexStack stack; 497 498 // the current outcode of the current sub path 499 private int cOutCode = 0; 500 501 // the cumulated (and) outcode of the complete path 502 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 503 504 private boolean outside = false; 505 506 // The current point OUTSIDE 507 private float cx0, cy0; 508 509 PathClipFilter(final RendererContext rdrCtx) { 510 this.clipRect = rdrCtx.clipRect; 511 this.stack = (rdrCtx.stats != null) ? 512 new IndexStack(rdrCtx, 513 rdrCtx.stats.stat_pcf_idxstack_indices, 514 rdrCtx.stats.hist_pcf_idxstack_indices, 515 rdrCtx.stats.stat_array_pcf_idxstack_indices) 516 : new IndexStack(rdrCtx); 517 } 518 519 PathClipFilter init(final PathConsumer2D out) { 520 this.out = out; 521 522 // Adjust the clipping rectangle with the renderer offsets 523 final float rdrOffX = Renderer.RDR_OFFSET_X; 524 final float rdrOffY = Renderer.RDR_OFFSET_Y; 525 526 // add a small rounding error: 527 final float margin = 1e-3f; 528 529 final float[] _clipRect = this.clipRect; 530 _clipRect[0] -= margin - rdrOffY; 531 _clipRect[1] += margin + rdrOffY; 532 _clipRect[2] -= margin - rdrOffX; 533 _clipRect[3] += margin + rdrOffX; 534 535 this.init_corners = true; 536 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 537 538 return this; // fluent API 539 } 540 541 /** 542 * Disposes this instance: 543 * clean up before reusing this instance 544 */ 545 void dispose() { 546 stack.dispose(); 547 } 548 549 private void finishPath() { 550 if (outside) { 551 // criteria: inside or totally outside ? 552 if (gOutCode == 0) { 553 finish(); 554 } else { 555 this.outside = false; 556 stack.reset(); 557 } 558 } 559 } 560 561 private void finish() { 562 this.outside = false; 563 564 if (!stack.isEmpty()) { 565 if (init_corners) { 566 init_corners = false; 567 568 final float[] _corners = corners; 569 final float[] _clipRect = clipRect; 570 // Top Left (0): 571 _corners[0] = _clipRect[2]; 572 _corners[1] = _clipRect[0]; 573 // Bottom Left (1): 574 _corners[2] = _clipRect[2]; 575 _corners[3] = _clipRect[1]; 576 // Top right (2): 577 _corners[4] = _clipRect[3]; 578 _corners[5] = _clipRect[0]; 579 // Bottom Right (3): 580 _corners[6] = _clipRect[3]; 581 _corners[7] = _clipRect[1]; 582 } 583 stack.pullAll(corners, out); 584 } 585 out.lineTo(cx0, cy0); 586 } 587 588 @Override 589 public void pathDone() { 590 finishPath(); 591 592 out.pathDone(); 593 594 // TODO: fix possible leak if exception happened 595 // Dispose this instance: 596 dispose(); 597 } 598 599 @Override 600 public void closePath() { 601 finishPath(); 602 603 out.closePath(); 604 } 605 606 @Override 607 public void moveTo(final float x0, final float y0) { 608 finishPath(); 609 610 final int outcode = Helpers.outcode(x0, y0, clipRect); 611 this.cOutCode = outcode; 612 this.outside = false; 613 out.moveTo(x0, y0); 614 } 615 616 @Override 617 public void lineTo(final float xe, final float ye) { 618 final int outcode0 = this.cOutCode; 619 final int outcode1 = Helpers.outcode(xe, ye, clipRect); 620 this.cOutCode = outcode1; 621 622 final int sideCode = (outcode0 & outcode1); 623 624 // basic rejection criteria: 625 if (sideCode == 0) { 626 this.gOutCode = 0; 627 } else { 628 this.gOutCode &= sideCode; 629 // keep last point coordinate before entering the clip again: 630 this.outside = true; 631 this.cx0 = xe; 632 this.cy0 = ye; 633 634 clip(sideCode, outcode0, outcode1); 635 return; 636 } 637 if (outside) { 638 finish(); 639 } 640 // clipping disabled: 641 out.lineTo(xe, ye); 642 } 643 644 private void clip(final int sideCode, 645 final int outcode0, 646 final int outcode1) 647 { 648 // corner or cross-boundary on left or right side: 649 if ((outcode0 != outcode1) 650 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0)) 651 { 652 // combine outcodes: 653 final int mergeCode = (outcode0 | outcode1); 654 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B; 655 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R; 656 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2; 657 658 // add corners to outside stack: 659 switch (tbCode) { 660 case MarlinConst.OUTCODE_TOP: 661 // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); 662 stack.push(off); // top 663 return; 664 case MarlinConst.OUTCODE_BOTTOM: 665 // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); 666 stack.push(off + 1); // bottom 667 return; 668 default: 669 // both TOP / BOTTOM: 670 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { 671 // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); 672 // top to bottom 673 stack.push(off); // top 674 stack.push(off + 1); // bottom 675 } else { 676 // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); 677 // bottom to top 678 stack.push(off + 1); // bottom 679 stack.push(off); // top 680 } 681 } 682 } 683 } 684 685 @Override 686 public void curveTo(final float x1, final float y1, 687 final float x2, final float y2, 688 final float xe, final float ye) 689 { 690 final int outcode0 = this.cOutCode; 691 final int outcode3 = Helpers.outcode(xe, ye, clipRect); 692 this.cOutCode = outcode3; 693 694 int sideCode = outcode0 & outcode3; 695 696 if (sideCode == 0) { 697 this.gOutCode = 0; 698 } else { 699 sideCode &= Helpers.outcode(x1, y1, clipRect); 700 sideCode &= Helpers.outcode(x2, y2, clipRect); 701 this.gOutCode &= sideCode; 702 703 // basic rejection criteria: 704 if (sideCode != 0) { 705 // keep last point coordinate before entering the clip again: 706 this.outside = true; 707 this.cx0 = xe; 708 this.cy0 = ye; 709 710 clip(sideCode, outcode0, outcode3); 711 return; 712 } 713 } 714 if (outside) { 715 finish(); 716 } 717 // clipping disabled: 718 out.curveTo(x1, y1, x2, y2, xe, ye); 719 } 720 721 @Override 722 public void quadTo(final float x1, final float y1, 723 final float xe, final float ye) 724 { 725 final int outcode0 = this.cOutCode; 726 final int outcode2 = Helpers.outcode(xe, ye, clipRect); 727 this.cOutCode = outcode2; 728 729 int sideCode = outcode0 & outcode2; 730 731 if (sideCode == 0) { 732 this.gOutCode = 0; 733 } else { 734 sideCode &= Helpers.outcode(x1, y1, clipRect); 735 this.gOutCode &= sideCode; 736 737 // basic rejection criteria: 738 if (sideCode != 0) { 739 // keep last point coordinate before entering the clip again: 740 this.outside = true; 741 this.cx0 = xe; 742 this.cy0 = ye; 743 744 clip(sideCode, outcode0, outcode2); 745 return; 746 } 747 } 748 if (outside) { 749 finish(); 750 } 751 // clipping disabled: 752 out.quadTo(x1, y1, xe, ye); 753 } 754 755 @Override 756 public long getNativeConsumer() { 757 throw new InternalError("Not using a native peer"); 758 } 759 } 760 761 static final class PathTracer implements PathConsumer2D { 762 private final String prefix; 763 private PathConsumer2D out; 764 765 PathTracer(String name) { 766 this.prefix = name + ": "; 767 } 768 769 PathTracer init(PathConsumer2D out) { 770 this.out = out; 771 return this; // fluent API 772 } 773 774 @Override 775 public void moveTo(float x0, float y0) { 776 log("moveTo (" + x0 + ", " + y0 + ')'); 777 out.moveTo(x0, y0); 778 } 779 780 @Override 781 public void lineTo(float x1, float y1) { 782 log("lineTo (" + x1 + ", " + y1 + ')'); 783 out.lineTo(x1, y1); 784 } 785 786 @Override 787 public void curveTo(float x1, float y1, 788 float x2, float y2, 789 float x3, float y3) 790 { 791 log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); 792 out.curveTo(x1, y1, x2, y2, x3, y3); 793 } 794 795 @Override 796 public void quadTo(float x1, float y1, float x2, float y2) { 797 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); 798 out.quadTo(x1, y1, x2, y2); 799 } 800 801 @Override 802 public void closePath() { 803 log("closePath"); 804 out.closePath(); 805 } 806 807 @Override 808 public void pathDone() { 809 log("pathDone"); 810 out.pathDone(); 811 } 812 813 private void log(final String message) { 814 System.out.println(prefix + message); 815 } 816 817 @Override 818 public long getNativeConsumer() { 819 throw new InternalError("Not using a native peer"); 820 } 821 } 822 } |