src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
changeset 48284 fd7fbc929001
parent 47216 71c04702a3d5
child 49496 1ea202af7a97
equal deleted inserted replaced
48283:da1b57b17101 48284:fd7fbc929001
    26 package sun.java2d.marlin;
    26 package sun.java2d.marlin;
    27 
    27 
    28 import sun.awt.geom.PathConsumer2D;
    28 import sun.awt.geom.PathConsumer2D;
    29 import java.awt.geom.AffineTransform;
    29 import java.awt.geom.AffineTransform;
    30 import java.awt.geom.Path2D;
    30 import java.awt.geom.Path2D;
       
    31 import sun.java2d.marlin.Helpers.IndexStack;
       
    32 import sun.java2d.marlin.Helpers.PolyStack;
    31 
    33 
    32 final class TransformingPathConsumer2D {
    34 final class TransformingPathConsumer2D {
    33 
    35 
    34     TransformingPathConsumer2D() {
    36     private final RendererContext rdrCtx;
    35         // used by RendererContext
    37 
    36     }
    38     // recycled ClosedPathDetector instance from detectClosedPath()
    37 
    39     private final ClosedPathDetector   cpDetector;
    38     // recycled PathConsumer2D instance from wrapPath2d()
    40 
       
    41     // recycled PathClipFilter instance from pathClipper()
       
    42     private final PathClipFilter       pathClipper;
       
    43 
       
    44     // recycled PathConsumer2D instance from wrapPath2D()
    39     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
    45     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
    40 
       
    41     PathConsumer2D wrapPath2d(Path2D.Float p2d)
       
    42     {
       
    43         return wp_Path2DWrapper.init(p2d);
       
    44     }
       
    45 
    46 
    46     // recycled PathConsumer2D instances from deltaTransformConsumer()
    47     // recycled PathConsumer2D instances from deltaTransformConsumer()
    47     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
    48     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
    48     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
    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     }
    49 
    95 
    50     PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
    96     PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
    51                                           AffineTransform at)
    97                                           AffineTransform at)
    52     {
    98     {
    53         if (at == null) {
    99         if (at == null) {
    54             return out;
   100             return out;
    55         }
   101         }
    56         float mxx = (float) at.getScaleX();
   102         final float mxx = (float) at.getScaleX();
    57         float mxy = (float) at.getShearX();
   103         final float mxy = (float) at.getShearX();
    58         float myx = (float) at.getShearY();
   104         final float myx = (float) at.getShearY();
    59         float myy = (float) at.getScaleY();
   105         final float myy = (float) at.getScaleY();
    60 
   106 
    61         if (mxy == 0.0f && myx == 0.0f) {
   107         if (mxy == 0.0f && myx == 0.0f) {
    62             if (mxx == 1.0f && myy == 1.0f) {
   108             if (mxx == 1.0f && myy == 1.0f) {
    63                 return out;
   109                 return out;
    64             } else {
   110             } else {
       
   111                 // Scale only
       
   112                 if (rdrCtx.doClip) {
       
   113                     // adjust clip rectangle (ymin, ymax, xmin, xmax):
       
   114                     adjustClipScale(rdrCtx.clipRect, mxx, myy);
       
   115                 }
    65                 return dt_DeltaScaleFilter.init(out, mxx, myy);
   116                 return dt_DeltaScaleFilter.init(out, mxx, myy);
    66             }
   117             }
    67         } else {
   118         } else {
       
   119             if (rdrCtx.doClip) {
       
   120                 // adjust clip rectangle (ymin, ymax, xmin, xmax):
       
   121                 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
       
   122             }
    68             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
   123             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
    69         }
   124         }
    70     }
   125     }
    71 
   126 
    72     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
   127     private static void adjustClipOffset(final float[] clipRect) {
    73     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
   128         clipRect[0] += Renderer.RDR_OFFSET_Y;
    74     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
   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     }
    75 
   194 
    76     PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
   195     PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
    77                                                  AffineTransform at)
   196                                                  AffineTransform at)
    78     {
   197     {
    79         if (at == null) {
   198         if (at == null) {
    89                 return out;
   208                 return out;
    90             } else {
   209             } else {
    91                 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
   210                 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
    92             }
   211             }
    93         } else {
   212         } else {
    94             float det = mxx * myy - mxy * myx;
   213             final float det = mxx * myy - mxy * myx;
    95             return iv_DeltaTransformFilter.init(out,
   214             return iv_DeltaTransformFilter.init(out,
    96                                                 myy / det,
   215                                                 myy / det,
    97                                                -mxy / det,
   216                                                -mxy / det,
    98                                                -myx / det,
   217                                                -myx / det,
    99                                                 mxx / det);
   218                                                 mxx / det);
   100         }
   219         }
   101     }
   220     }
   102 
       
   103 
   221 
   104     static final class DeltaScaleFilter implements PathConsumer2D {
   222     static final class DeltaScaleFilter implements PathConsumer2D {
   105         private PathConsumer2D out;
   223         private PathConsumer2D out;
   106         private float sx, sy;
   224         private float sx, sy;
   107 
   225 
   273         @Override
   391         @Override
   274         public long getNativeConsumer() {
   392         public long getNativeConsumer() {
   275             throw new InternalError("Not using a native peer");
   393             throw new InternalError("Not using a native peer");
   276         }
   394         }
   277     }
   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     }
   278 }
   819 }