1 /* 2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.marlin; 27 28 import 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 return wp_Path2DWrapper.init(p2d); 70 } 71 72 PathConsumer2D traceInput(PathConsumer2D out) { 73 return tracerInput.init(out); 74 } 75 76 PathConsumer2D traceClosedPathDetector(PathConsumer2D out) { 77 return tracerCPDetector.init(out); 78 } 79 80 PathConsumer2D traceFiller(PathConsumer2D out) { 81 return tracerFiller.init(out); 82 } 83 84 PathConsumer2D traceStroker(PathConsumer2D out) { 85 return tracerStroker.init(out); 86 } 87 88 PathConsumer2D detectClosedPath(PathConsumer2D out) { 89 return cpDetector.init(out); 90 } 91 92 PathConsumer2D pathClipper(PathConsumer2D out) { 93 return pathClipper.init(out); 94 } 95 96 PathConsumer2D deltaTransformConsumer(PathConsumer2D out, 97 AffineTransform at) 98 { 99 if (at == null) { 100 return out; 101 } 102 final float mxx = (float) at.getScaleX(); 103 final float mxy = (float) at.getShearX(); 104 final float myx = (float) at.getShearY(); 105 final float myy = (float) at.getScaleY(); 106 107 if (mxy == 0.0f && myx == 0.0f) { 108 if (mxx == 1.0f && myy == 1.0f) { 109 return out; 110 } else { 111 // Scale only 112 if (rdrCtx.doClip) { 113 // adjust clip rectangle (ymin, ymax, xmin, xmax): 114 adjustClipScale(rdrCtx.clipRect, mxx, myy); 115 } 116 return dt_DeltaScaleFilter.init(out, mxx, myy); 117 } 118 } else { 119 if (rdrCtx.doClip) { 120 // adjust clip rectangle (ymin, ymax, xmin, xmax): 121 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); 122 } 123 return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); 124 } 125 } 126 127 private static void adjustClipOffset(final float[] clipRect) { 128 clipRect[0] += Renderer.RDR_OFFSET_Y; 129 clipRect[1] += Renderer.RDR_OFFSET_Y; 130 clipRect[2] += Renderer.RDR_OFFSET_X; 131 clipRect[3] += Renderer.RDR_OFFSET_X; 132 } 133 134 private static void adjustClipScale(final float[] clipRect, 135 final float mxx, final float myy) 136 { 137 adjustClipOffset(clipRect); 138 139 // Adjust the clipping rectangle (iv_DeltaScaleFilter): 140 clipRect[0] /= myy; 141 clipRect[1] /= myy; 142 clipRect[2] /= mxx; 143 clipRect[3] /= mxx; 144 } 145 146 private static void adjustClipInverseDelta(final float[] clipRect, 147 final float mxx, final float mxy, 148 final float myx, final float myy) 149 { 150 adjustClipOffset(clipRect); 151 152 // Adjust the clipping rectangle (iv_DeltaTransformFilter): 153 final float det = mxx * myy - mxy * myx; 154 final float imxx = myy / det; 155 final float imxy = -mxy / det; 156 final float imyx = -myx / det; 157 final float imyy = mxx / det; 158 159 float xmin, xmax, ymin, ymax; 160 float x, y; 161 // xmin, ymin: 162 x = clipRect[2] * imxx + clipRect[0] * imxy; 163 y = clipRect[2] * imyx + clipRect[0] * imyy; 164 165 xmin = xmax = x; 166 ymin = ymax = y; 167 168 // xmax, ymin: 169 x = clipRect[3] * imxx + clipRect[0] * imxy; 170 y = clipRect[3] * imyx + clipRect[0] * imyy; 171 172 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 173 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 174 175 // xmin, ymax: 176 x = clipRect[2] * imxx + clipRect[1] * imxy; 177 y = clipRect[2] * imyx + clipRect[1] * imyy; 178 179 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 180 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 181 182 // xmax, ymax: 183 x = clipRect[3] * imxx + clipRect[1] * imxy; 184 y = clipRect[3] * imyx + clipRect[1] * imyy; 185 186 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 187 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 188 189 clipRect[0] = ymin; 190 clipRect[1] = ymax; 191 clipRect[2] = xmin; 192 clipRect[3] = xmax; 193 } 194 195 PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, 196 AffineTransform at) 197 { 198 if (at == null) { 199 return out; 200 } 201 float mxx = (float) at.getScaleX(); 202 float mxy = (float) at.getShearX(); 203 float myx = (float) at.getShearY(); 204 float myy = (float) at.getScaleY(); 205 206 if (mxy == 0.0f && myx == 0.0f) { 207 if (mxx == 1.0f && myy == 1.0f) { 208 return out; 209 } else { 210 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); 211 } 212 } else { 213 final float det = mxx * myy - mxy * myx; 214 return iv_DeltaTransformFilter.init(out, 215 myy / det, 216 -mxy / det, 217 -myx / det, 218 mxx / det); 219 } 220 } 221 222 static final class DeltaScaleFilter implements PathConsumer2D { 223 private PathConsumer2D out; 224 private float sx, sy; 225 226 DeltaScaleFilter() {} 227 228 DeltaScaleFilter init(PathConsumer2D out, 229 float mxx, float myy) 230 { 231 this.out = out; 232 sx = mxx; 233 sy = myy; 234 return this; // fluent API 235 } 236 237 @Override 238 public void moveTo(float x0, float y0) { 239 out.moveTo(x0 * sx, y0 * sy); 240 } 241 242 @Override 243 public void lineTo(float x1, float y1) { 244 out.lineTo(x1 * sx, y1 * sy); 245 } 246 247 @Override 248 public void quadTo(float x1, float y1, 249 float x2, float y2) 250 { 251 out.quadTo(x1 * sx, y1 * sy, 252 x2 * sx, y2 * sy); 253 } 254 255 @Override 256 public void curveTo(float x1, float y1, 257 float x2, float y2, 258 float x3, float y3) 259 { 260 out.curveTo(x1 * sx, y1 * sy, 261 x2 * sx, y2 * sy, 262 x3 * sx, y3 * sy); 263 } 264 265 @Override 266 public void closePath() { 267 out.closePath(); 268 } 269 270 @Override 271 public void pathDone() { 272 out.pathDone(); 273 } 274 275 @Override 276 public long getNativeConsumer() { 277 return 0; 278 } 279 } 280 281 static final class DeltaTransformFilter implements PathConsumer2D { 282 private PathConsumer2D out; 283 private float mxx, mxy, myx, myy; 284 285 DeltaTransformFilter() {} 286 287 DeltaTransformFilter init(PathConsumer2D out, 288 float mxx, float mxy, 289 float myx, float myy) 290 { 291 this.out = out; 292 this.mxx = mxx; 293 this.mxy = mxy; 294 this.myx = myx; 295 this.myy = myy; 296 return this; // fluent API 297 } 298 299 @Override 300 public void moveTo(float x0, float y0) { 301 out.moveTo(x0 * mxx + y0 * mxy, 302 x0 * myx + y0 * myy); 303 } 304 305 @Override 306 public void lineTo(float x1, float y1) { 307 out.lineTo(x1 * mxx + y1 * mxy, 308 x1 * myx + y1 * myy); 309 } 310 311 @Override 312 public void quadTo(float x1, float y1, 313 float x2, float y2) 314 { 315 out.quadTo(x1 * mxx + y1 * mxy, 316 x1 * myx + y1 * myy, 317 x2 * mxx + y2 * mxy, 318 x2 * myx + y2 * myy); 319 } 320 321 @Override 322 public void curveTo(float x1, float y1, 323 float x2, float y2, 324 float x3, float y3) 325 { 326 out.curveTo(x1 * mxx + y1 * mxy, 327 x1 * myx + y1 * myy, 328 x2 * mxx + y2 * mxy, 329 x2 * myx + y2 * myy, 330 x3 * mxx + y3 * mxy, 331 x3 * myx + y3 * myy); 332 } 333 334 @Override 335 public void closePath() { 336 out.closePath(); 337 } 338 339 @Override 340 public void pathDone() { 341 out.pathDone(); 342 } 343 344 @Override 345 public long getNativeConsumer() { 346 return 0; 347 } 348 } 349 350 static final class Path2DWrapper implements PathConsumer2D { 351 private Path2D.Float p2d; 352 353 Path2DWrapper() {} 354 355 Path2DWrapper init(Path2D.Float p2d) { 356 this.p2d = p2d; 357 return this; 358 } 359 360 @Override 361 public void moveTo(float x0, float y0) { 362 p2d.moveTo(x0, y0); 363 } 364 365 @Override 366 public void lineTo(float x1, float y1) { 367 p2d.lineTo(x1, y1); 368 } 369 370 @Override 371 public void closePath() { 372 p2d.closePath(); 373 } 374 375 @Override 376 public void pathDone() {} 377 378 @Override 379 public void curveTo(float x1, float y1, 380 float x2, float y2, 381 float x3, float y3) 382 { 383 p2d.curveTo(x1, y1, x2, y2, x3, y3); 384 } 385 386 @Override 387 public void quadTo(float x1, float y1, float x2, float y2) { 388 p2d.quadTo(x1, y1, x2, y2); 389 } 390 391 @Override 392 public long getNativeConsumer() { 393 throw new InternalError("Not using a native peer"); 394 } 395 } 396 397 static final class ClosedPathDetector implements PathConsumer2D { 398 399 private final RendererContext rdrCtx; 400 private final PolyStack stack; 401 402 private PathConsumer2D out; 403 404 ClosedPathDetector(final RendererContext rdrCtx) { 405 this.rdrCtx = rdrCtx; 406 this.stack = (rdrCtx.stats != null) ? 407 new PolyStack(rdrCtx, 408 rdrCtx.stats.stat_cpd_polystack_types, 409 rdrCtx.stats.stat_cpd_polystack_curves, 410 rdrCtx.stats.hist_cpd_polystack_curves, 411 rdrCtx.stats.stat_array_cpd_polystack_curves, 412 rdrCtx.stats.stat_array_cpd_polystack_types) 413 : new PolyStack(rdrCtx); 414 } 415 416 ClosedPathDetector init(PathConsumer2D out) { 417 this.out = out; 418 return this; // fluent API 419 } 420 421 /** 422 * Disposes this instance: 423 * clean up before reusing this instance 424 */ 425 void dispose() { 426 stack.dispose(); 427 } 428 429 @Override 430 public void pathDone() { 431 // previous path is not closed: 432 finish(false); 433 out.pathDone(); 434 435 // TODO: fix possible leak if exception happened 436 // Dispose this instance: 437 dispose(); 438 } 439 440 @Override 441 public void closePath() { 442 // path is closed 443 finish(true); 444 out.closePath(); 445 } 446 447 @Override 448 public void moveTo(float x0, float y0) { 449 // previous path is not closed: 450 finish(false); 451 out.moveTo(x0, y0); 452 } 453 454 private void finish(final boolean closed) { 455 rdrCtx.closedPath = closed; 456 stack.pullAll(out); 457 } 458 459 @Override 460 public void lineTo(float x1, float y1) { 461 stack.pushLine(x1, y1); 462 } 463 464 @Override 465 public void curveTo(float x3, float y3, 466 float x2, float y2, 467 float x1, float y1) 468 { 469 stack.pushCubic(x1, y1, x2, y2, x3, y3); 470 } 471 472 @Override 473 public void quadTo(float x2, float y2, float x1, float y1) { 474 stack.pushQuad(x1, y1, x2, y2); 475 } 476 477 @Override 478 public long getNativeConsumer() { 479 throw new InternalError("Not using a native peer"); 480 } 481 } 482 483 static final class PathClipFilter implements PathConsumer2D { 484 485 private PathConsumer2D out; 486 487 // Bounds of the drawing region, at pixel precision. 488 private final float[] clipRect; 489 490 private final float[] corners = new float[8]; 491 private boolean init_corners = false; 492 493 private final IndexStack stack; 494 495 // the current outcode of the current sub path 496 private int cOutCode = 0; 497 498 // the cumulated (and) outcode of the complete path 499 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 500 501 private boolean outside = false; 502 503 // The current point OUTSIDE 504 private float cx0, cy0; 505 506 PathClipFilter(final RendererContext rdrCtx) { 507 this.clipRect = rdrCtx.clipRect; 508 this.stack = (rdrCtx.stats != null) ? 509 new IndexStack(rdrCtx, 510 rdrCtx.stats.stat_pcf_idxstack_indices, 511 rdrCtx.stats.hist_pcf_idxstack_indices, 512 rdrCtx.stats.stat_array_pcf_idxstack_indices) 513 : new IndexStack(rdrCtx); 514 } 515 516 PathClipFilter init(final PathConsumer2D out) { 517 this.out = out; 518 519 // Adjust the clipping rectangle with the renderer offsets 520 final float rdrOffX = Renderer.RDR_OFFSET_X; 521 final float rdrOffY = Renderer.RDR_OFFSET_Y; 522 523 // add a small rounding error: 524 final float margin = 1e-3f; 525 526 final float[] _clipRect = this.clipRect; 527 _clipRect[0] -= margin - rdrOffY; 528 _clipRect[1] += margin + rdrOffY; 529 _clipRect[2] -= margin - rdrOffX; 530 _clipRect[3] += margin + rdrOffX; 531 532 this.init_corners = true; 533 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 534 535 return this; // fluent API 536 } 537 538 /** 539 * Disposes this instance: 540 * clean up before reusing this instance 541 */ 542 void dispose() { 543 stack.dispose(); 544 } 545 546 private void finishPath() { 547 if (outside) { 548 // criteria: inside or totally outside ? 549 if (gOutCode == 0) { 550 finish(); 551 } else { 552 this.outside = false; 553 stack.reset(); 554 } 555 } 556 } 557 558 private void finish() { 559 this.outside = false; 560 561 if (!stack.isEmpty()) { 562 if (init_corners) { 563 init_corners = false; 564 565 final float[] _corners = corners; 566 final float[] _clipRect = clipRect; 567 // Top Left (0): 568 _corners[0] = _clipRect[2]; 569 _corners[1] = _clipRect[0]; 570 // Bottom Left (1): 571 _corners[2] = _clipRect[2]; 572 _corners[3] = _clipRect[1]; 573 // Top right (2): 574 _corners[4] = _clipRect[3]; 575 _corners[5] = _clipRect[0]; 576 // Bottom Right (3): 577 _corners[6] = _clipRect[3]; 578 _corners[7] = _clipRect[1]; 579 } 580 stack.pullAll(corners, out); 581 } 582 out.lineTo(cx0, cy0); 583 } 584 585 @Override 586 public void pathDone() { 587 finishPath(); 588 589 out.pathDone(); 590 591 // TODO: fix possible leak if exception happened 592 // Dispose this instance: 593 dispose(); 594 } 595 596 @Override 597 public void closePath() { 598 finishPath(); 599 600 out.closePath(); 601 } 602 603 @Override 604 public void moveTo(final float x0, final float y0) { 605 finishPath(); 606 607 final int outcode = Helpers.outcode(x0, y0, clipRect); 608 this.cOutCode = outcode; 609 this.outside = false; 610 out.moveTo(x0, y0); 611 } 612 613 @Override 614 public void lineTo(final float xe, final float ye) { 615 final int outcode0 = this.cOutCode; 616 final int outcode1 = Helpers.outcode(xe, ye, clipRect); 617 this.cOutCode = outcode1; 618 619 final int sideCode = (outcode0 & outcode1); 620 621 // basic rejection criteria: 622 if (sideCode == 0) { 623 this.gOutCode = 0; 624 } else { 625 this.gOutCode &= sideCode; 626 // keep last point coordinate before entering the clip again: 627 this.outside = true; 628 this.cx0 = xe; 629 this.cy0 = ye; 630 631 clip(sideCode, outcode0, outcode1); 632 return; 633 } 634 if (outside) { 635 finish(); 636 } 637 // clipping disabled: 638 out.lineTo(xe, ye); 639 } 640 641 private void clip(final int sideCode, 642 final int outcode0, 643 final int outcode1) 644 { 645 // corner or cross-boundary on left or right side: 646 if ((outcode0 != outcode1) 647 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0)) 648 { 649 // combine outcodes: 650 final int mergeCode = (outcode0 | outcode1); 651 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B; 652 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R; 653 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2; 654 655 // add corners to outside stack: 656 switch (tbCode) { 657 case MarlinConst.OUTCODE_TOP: 658 // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); 659 stack.push(off); // top 660 return; 661 case MarlinConst.OUTCODE_BOTTOM: 662 // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); 663 stack.push(off + 1); // bottom 664 return; 665 default: 666 // both TOP / BOTTOM: 667 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { 668 // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); 669 // top to bottom 670 stack.push(off); // top 671 stack.push(off + 1); // bottom 672 } else { 673 // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); 674 // bottom to top 675 stack.push(off + 1); // bottom 676 stack.push(off); // top 677 } 678 } 679 } 680 } 681 682 @Override 683 public void curveTo(final float x1, final float y1, 684 final float x2, final float y2, 685 final float xe, final float ye) 686 { 687 final int outcode0 = this.cOutCode; 688 final int outcode3 = Helpers.outcode(xe, ye, clipRect); 689 this.cOutCode = outcode3; 690 691 int sideCode = outcode0 & outcode3; 692 693 if (sideCode == 0) { 694 this.gOutCode = 0; 695 } else { 696 sideCode &= Helpers.outcode(x1, y1, clipRect); 697 sideCode &= Helpers.outcode(x2, y2, clipRect); 698 this.gOutCode &= sideCode; 699 700 // basic rejection criteria: 701 if (sideCode != 0) { 702 // keep last point coordinate before entering the clip again: 703 this.outside = true; 704 this.cx0 = xe; 705 this.cy0 = ye; 706 707 clip(sideCode, outcode0, outcode3); 708 return; 709 } 710 } 711 if (outside) { 712 finish(); 713 } 714 // clipping disabled: 715 out.curveTo(x1, y1, x2, y2, xe, ye); 716 } 717 718 @Override 719 public void quadTo(final float x1, final float y1, 720 final float xe, final float ye) 721 { 722 final int outcode0 = this.cOutCode; 723 final int outcode2 = Helpers.outcode(xe, ye, clipRect); 724 this.cOutCode = outcode2; 725 726 int sideCode = outcode0 & outcode2; 727 728 if (sideCode == 0) { 729 this.gOutCode = 0; 730 } else { 731 sideCode &= Helpers.outcode(x1, y1, clipRect); 732 this.gOutCode &= sideCode; 733 734 // basic rejection criteria: 735 if (sideCode != 0) { 736 // keep last point coordinate before entering the clip again: 737 this.outside = true; 738 this.cx0 = xe; 739 this.cy0 = ye; 740 741 clip(sideCode, outcode0, outcode2); 742 return; 743 } 744 } 745 if (outside) { 746 finish(); 747 } 748 // clipping disabled: 749 out.quadTo(x1, y1, xe, ye); 750 } 751 752 @Override 753 public long getNativeConsumer() { 754 throw new InternalError("Not using a native peer"); 755 } 756 } 757 758 static final class PathTracer implements PathConsumer2D { 759 private final String prefix; 760 private PathConsumer2D out; 761 762 PathTracer(String name) { 763 this.prefix = name + ": "; 764 } 765 766 PathTracer init(PathConsumer2D out) { 767 this.out = out; 768 return this; // fluent API 769 } 770 771 @Override 772 public void moveTo(float x0, float y0) { 773 log("moveTo (" + x0 + ", " + y0 + ')'); 774 out.moveTo(x0, y0); 775 } 776 777 @Override 778 public void lineTo(float x1, float y1) { 779 log("lineTo (" + x1 + ", " + y1 + ')'); 780 out.lineTo(x1, y1); 781 } 782 783 @Override 784 public void curveTo(float x1, float y1, 785 float x2, float y2, 786 float x3, float y3) 787 { 788 log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); 789 out.curveTo(x1, y1, x2, y2, x3, y3); 790 } 791 792 @Override 793 public void quadTo(float x1, float y1, float x2, float y2) { 794 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); 795 out.quadTo(x1, y1, x2, y2); 796 } 797 798 @Override 799 public void closePath() { 800 log("closePath"); 801 out.closePath(); 802 } 803 804 @Override 805 public void pathDone() { 806 log("pathDone"); 807 out.pathDone(); 808 } 809 810 private void log(final String message) { 811 System.out.println(prefix + message); 812 } 813 814 @Override 815 public long getNativeConsumer() { 816 throw new InternalError("Not using a native peer"); 817 } 818 } 819 }