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