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     {
  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 
 245         @Override
 246         public void lineTo(float x1, float y1) {
 247             out.lineTo(x1 * sx, y1 * sy);
 248         }
 249 
 250         @Override
 251         public void quadTo(float x1, float y1,
 252                            float x2, float y2)
 253         {
 254             out.quadTo(x1 * sx, y1 * sy,
 255                        x2 * sx, y2 * sy);
 256         }
 257 
 258         @Override
 259         public void curveTo(float x1, float y1,
 260                             float x2, float y2,
 261                             float x3, float y3)
 262         {
 263             out.curveTo(x1 * sx, y1 * sy,
 264                         x2 * sx, y2 * sy,
 265                         x3 * sx, y3 * sy);
 266         }
 267 
 268         @Override
 269         public void closePath() {
 270             out.closePath();
 271         }
 272 
 273         @Override
 274         public void pathDone() {
 275             out.pathDone();
 276         }
 277 
 278         @Override
 279         public long getNativeConsumer() {
 280             return 0;
 281         }
 282     }
 283 
 284     static final class DeltaTransformFilter implements PathConsumer2D {
 285         private PathConsumer2D out;
 286         private float mxx, mxy, myx, myy;
 287 
 288         DeltaTransformFilter() {}
 289 
 290         DeltaTransformFilter init(PathConsumer2D out,
 291                                   float mxx, float mxy,
 292                                   float myx, float myy)
 293         {
 294             this.out = out;
 295             this.mxx = mxx;
 296             this.mxy = mxy;
 297             this.myx = myx;
 298             this.myy = myy;
 299             return this; // fluent API
 300         }
 301 
 302         @Override
 303         public void moveTo(float x0, float y0) {
 304             out.moveTo(x0 * mxx + y0 * mxy,
 305                        x0 * myx + y0 * myy);
 306         }
 307 
 308         @Override
 309         public void lineTo(float x1, float y1) {
 310             out.lineTo(x1 * mxx + y1 * mxy,
 311                        x1 * myx + y1 * myy);
 312         }
 313 
 314         @Override
 315         public void quadTo(float x1, float y1,
 316                            float x2, float y2)
 317         {
 318             out.quadTo(x1 * mxx + y1 * mxy,
 319                        x1 * myx + y1 * myy,
 320                        x2 * mxx + y2 * mxy,
 321                        x2 * myx + y2 * myy);
 322         }
 323 
 324         @Override
 325         public void curveTo(float x1, float y1,
 326                             float x2, float y2,
 327                             float x3, float y3)
 328         {
 329             out.curveTo(x1 * mxx + y1 * mxy,
 330                         x1 * myx + y1 * myy,
 331                         x2 * mxx + y2 * mxy,
 332                         x2 * myx + y2 * myy,
 333                         x3 * mxx + y3 * mxy,
 334                         x3 * myx + y3 * myy);
 335         }
 336 
 337         @Override
 338         public void closePath() {
 339             out.closePath();
 340         }
 341 
 342         @Override
 343         public void pathDone() {
 344             out.pathDone();
 345         }
 346 
 347         @Override
 348         public long getNativeConsumer() {
 349             return 0;
 350         }
 351     }
 352 
 353     static final class Path2DWrapper implements PathConsumer2D {
 354         private Path2D.Float p2d;
 355 
 356         Path2DWrapper() {}
 357 
 358         Path2DWrapper init(Path2D.Float p2d) {
 359             this.p2d = p2d;
 360             return this;
 361         }
 362 
 363         @Override
 364         public void moveTo(float x0, float y0) {
 365             p2d.moveTo(x0, y0);
 366         }
 367 
 368         @Override
 369         public void lineTo(float x1, float y1) {
 370             p2d.lineTo(x1, y1);
 371         }
 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 }