1 /*
   2  * Copyright (c) 2007, 2018, 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 java.awt.geom.AffineTransform;
  29 import java.awt.geom.Path2D;
  30 import java.util.Arrays;
  31 import sun.java2d.marlin.DHelpers.IndexStack;
  32 import sun.java2d.marlin.DHelpers.PolyStack;
  33 
  34 final class DTransformingPathConsumer2D {
  35 
  36     // smaller uncertainty in double variant
  37     static final double CLIP_RECT_PADDING = 0.25d;
  38 
  39     private final DRendererContext rdrCtx;
  40 
  41     // recycled ClosedPathDetector instance from detectClosedPath()
  42     private final ClosedPathDetector   cpDetector;
  43 
  44     // recycled PathClipFilter instance from pathClipper()
  45     private final PathClipFilter       pathClipper;
  46 
  47     // recycled DPathConsumer2D instance from wrapPath2D()
  48     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  49 
  50     // recycled DPathConsumer2D instances from deltaTransformConsumer()
  51     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  52     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  53 
  54     // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
  55     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  56     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  57 
  58     // recycled PathTracer instances from tracer...() methods
  59     private final PathTracer tracerInput      = new PathTracer("[Input]");
  60     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  61     private final PathTracer tracerFiller     = new PathTracer("Filler");
  62     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  63     private final PathTracer tracerDasher     = new PathTracer("Dasher");
  64 
  65     DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
  66         // used by RendererContext
  67         this.rdrCtx = rdrCtx;
  68         this.cpDetector = new ClosedPathDetector(rdrCtx);
  69         this.pathClipper = new PathClipFilter(rdrCtx);
  70     }
  71 
  72     DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
  73         return wp_Path2DWrapper.init(p2d);
  74     }
  75 
  76     DPathConsumer2D traceInput(DPathConsumer2D out) {
  77         return tracerInput.init(out);
  78     }
  79 
  80     DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
  81         return tracerCPDetector.init(out);
  82     }
  83 
  84     DPathConsumer2D traceFiller(DPathConsumer2D out) {
  85         return tracerFiller.init(out);
  86     }
  87 
  88     DPathConsumer2D traceStroker(DPathConsumer2D out) {
  89         return tracerStroker.init(out);
  90     }
  91 
  92     DPathConsumer2D traceDasher(DPathConsumer2D out) {
  93         return tracerDasher.init(out);
  94     }
  95 
  96     DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
  97         return cpDetector.init(out);
  98     }
  99 
 100     DPathConsumer2D pathClipper(DPathConsumer2D out) {
 101         return pathClipper.init(out);
 102     }
 103 
 104     DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
 105                                           AffineTransform at)
 106     {
 107         if (at == null) {
 108             return out;
 109         }
 110         final double mxx = at.getScaleX();
 111         final double mxy = at.getShearX();
 112         final double myx = at.getShearY();
 113         final double myy = at.getScaleY();
 114 
 115         if (mxy == 0.0d && myx == 0.0d) {
 116             if (mxx == 1.0d && myy == 1.0d) {
 117                 return out;
 118             } else {
 119                 // Scale only
 120                 if (rdrCtx.doClip) {
 121                     // adjust clip rectangle (ymin, ymax, xmin, xmax):
 122                     adjustClipScale(rdrCtx.clipRect, mxx, myy);
 123                 }
 124                 return dt_DeltaScaleFilter.init(out, mxx, myy);
 125             }
 126         } else {
 127             if (rdrCtx.doClip) {
 128                 // adjust clip rectangle (ymin, ymax, xmin, xmax):
 129                 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
 130             }
 131             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
 132         }
 133     }
 134 
 135     private static void adjustClipOffset(final double[] clipRect) {
 136         clipRect[0] += Renderer.RDR_OFFSET_Y;
 137         clipRect[1] += Renderer.RDR_OFFSET_Y;
 138         clipRect[2] += Renderer.RDR_OFFSET_X;
 139         clipRect[3] += Renderer.RDR_OFFSET_X;
 140     }
 141 
 142     private static void adjustClipScale(final double[] clipRect,
 143                                         final double mxx, final double myy)
 144     {
 145         adjustClipOffset(clipRect);
 146 
 147         // Adjust the clipping rectangle (iv_DeltaScaleFilter):
 148         clipRect[0] /= myy;
 149         clipRect[1] /= myy;
 150         clipRect[2] /= mxx;
 151         clipRect[3] /= mxx;
 152     }
 153 
 154     private static void adjustClipInverseDelta(final double[] clipRect,
 155                                                final double mxx, final double mxy,
 156                                                final double myx, final double myy)
 157     {
 158         adjustClipOffset(clipRect);
 159 
 160         // Adjust the clipping rectangle (iv_DeltaTransformFilter):
 161         final double det = mxx * myy - mxy * myx;
 162         final double imxx =  myy / det;
 163         final double imxy = -mxy / det;
 164         final double imyx = -myx / det;
 165         final double imyy =  mxx / det;
 166 
 167         double xmin, xmax, ymin, ymax;
 168         double x, y;
 169         // xmin, ymin:
 170         x = clipRect[2] * imxx + clipRect[0] * imxy;
 171         y = clipRect[2] * imyx + clipRect[0] * imyy;
 172 
 173         xmin = xmax = x;
 174         ymin = ymax = y;
 175 
 176         // xmax, ymin:
 177         x = clipRect[3] * imxx + clipRect[0] * imxy;
 178         y = clipRect[3] * imyx + clipRect[0] * imyy;
 179 
 180         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 181         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 182 
 183         // xmin, ymax:
 184         x = clipRect[2] * imxx + clipRect[1] * imxy;
 185         y = clipRect[2] * imyx + clipRect[1] * imyy;
 186 
 187         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 188         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 189 
 190         // xmax, ymax:
 191         x = clipRect[3] * imxx + clipRect[1] * imxy;
 192         y = clipRect[3] * imyx + clipRect[1] * imyy;
 193 
 194         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 195         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 196 
 197         clipRect[0] = ymin;
 198         clipRect[1] = ymax;
 199         clipRect[2] = xmin;
 200         clipRect[3] = xmax;
 201     }
 202 
 203     DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out,
 204                                                  AffineTransform at)
 205     {
 206         if (at == null) {
 207             return out;
 208         }
 209         double mxx = at.getScaleX();
 210         double mxy = at.getShearX();
 211         double myx = at.getShearY();
 212         double myy = at.getScaleY();
 213 
 214         if (mxy == 0.0d && myx == 0.0d) {
 215             if (mxx == 1.0d && myy == 1.0d) {
 216                 return out;
 217             } else {
 218                 return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy);
 219             }
 220         } else {
 221             final double det = mxx * myy - mxy * myx;
 222             return iv_DeltaTransformFilter.init(out,
 223                                                 myy / det,
 224                                                -mxy / det,
 225                                                -myx / det,
 226                                                 mxx / det);
 227         }
 228     }
 229 
 230     static final class DeltaScaleFilter implements DPathConsumer2D {
 231         private DPathConsumer2D out;
 232         private double sx, sy;
 233 
 234         DeltaScaleFilter() {}
 235 
 236         DeltaScaleFilter init(DPathConsumer2D out,
 237                               double mxx, double myy)
 238         {
 239             this.out = out;
 240             sx = mxx;
 241             sy = myy;
 242             return this; // fluent API
 243         }
 244 
 245         @Override
 246         public void moveTo(double x0, double y0) {
 247             out.moveTo(x0 * sx, y0 * sy);
 248         }
 249 
 250         @Override
 251         public void lineTo(double x1, double y1) {
 252             out.lineTo(x1 * sx, y1 * sy);
 253         }
 254 
 255         @Override
 256         public void quadTo(double x1, double y1,
 257                            double x2, double y2)
 258         {
 259             out.quadTo(x1 * sx, y1 * sy,
 260                        x2 * sx, y2 * sy);
 261         }
 262 
 263         @Override
 264         public void curveTo(double x1, double y1,
 265                             double x2, double y2,
 266                             double x3, double y3)
 267         {
 268             out.curveTo(x1 * sx, y1 * sy,
 269                         x2 * sx, y2 * sy,
 270                         x3 * sx, y3 * sy);
 271         }
 272 
 273         @Override
 274         public void closePath() {
 275             out.closePath();
 276         }
 277 
 278         @Override
 279         public void pathDone() {
 280             out.pathDone();
 281         }
 282 
 283         @Override
 284         public long getNativeConsumer() {
 285             return 0;
 286         }
 287     }
 288 
 289     static final class DeltaTransformFilter implements DPathConsumer2D {
 290         private DPathConsumer2D out;
 291         private double mxx, mxy, myx, myy;
 292 
 293         DeltaTransformFilter() {}
 294 
 295         DeltaTransformFilter init(DPathConsumer2D out,
 296                                   double mxx, double mxy,
 297                                   double myx, double myy)
 298         {
 299             this.out = out;
 300             this.mxx = mxx;
 301             this.mxy = mxy;
 302             this.myx = myx;
 303             this.myy = myy;
 304             return this; // fluent API
 305         }
 306 
 307         @Override
 308         public void moveTo(double x0, double y0) {
 309             out.moveTo(x0 * mxx + y0 * mxy,
 310                        x0 * myx + y0 * myy);
 311         }
 312 
 313         @Override
 314         public void lineTo(double x1, double y1) {
 315             out.lineTo(x1 * mxx + y1 * mxy,
 316                        x1 * myx + y1 * myy);
 317         }
 318 
 319         @Override
 320         public void quadTo(double x1, double y1,
 321                            double x2, double y2)
 322         {
 323             out.quadTo(x1 * mxx + y1 * mxy,
 324                        x1 * myx + y1 * myy,
 325                        x2 * mxx + y2 * mxy,
 326                        x2 * myx + y2 * myy);
 327         }
 328 
 329         @Override
 330         public void curveTo(double x1, double y1,
 331                             double x2, double y2,
 332                             double x3, double y3)
 333         {
 334             out.curveTo(x1 * mxx + y1 * mxy,
 335                         x1 * myx + y1 * myy,
 336                         x2 * mxx + y2 * mxy,
 337                         x2 * myx + y2 * myy,
 338                         x3 * mxx + y3 * mxy,
 339                         x3 * myx + y3 * myy);
 340         }
 341 
 342         @Override
 343         public void closePath() {
 344             out.closePath();
 345         }
 346 
 347         @Override
 348         public void pathDone() {
 349             out.pathDone();
 350         }
 351 
 352         @Override
 353         public long getNativeConsumer() {
 354             return 0;
 355         }
 356     }
 357 
 358     static final class Path2DWrapper implements DPathConsumer2D {
 359         private Path2D.Double p2d;
 360 
 361         Path2DWrapper() {}
 362 
 363         Path2DWrapper init(Path2D.Double p2d) {
 364             this.p2d = p2d;
 365             return this;
 366         }
 367 
 368         @Override
 369         public void moveTo(double x0, double y0) {
 370             p2d.moveTo(x0, y0);
 371         }
 372 
 373         @Override
 374         public void lineTo(double x1, double y1) {
 375             p2d.lineTo(x1, y1);
 376         }
 377 
 378         @Override
 379         public void closePath() {
 380             p2d.closePath();
 381         }
 382 
 383         @Override
 384         public void pathDone() {}
 385 
 386         @Override
 387         public void curveTo(double x1, double y1,
 388                             double x2, double y2,
 389                             double x3, double y3)
 390         {
 391             p2d.curveTo(x1, y1, x2, y2, x3, y3);
 392         }
 393 
 394         @Override
 395         public void quadTo(double x1, double y1, double x2, double y2) {
 396             p2d.quadTo(x1, y1, x2, y2);
 397         }
 398 
 399         @Override
 400         public long getNativeConsumer() {
 401             throw new InternalError("Not using a native peer");
 402         }
 403     }
 404 
 405     static final class ClosedPathDetector implements DPathConsumer2D {
 406 
 407         private final DRendererContext rdrCtx;
 408         private final PolyStack stack;
 409 
 410         private DPathConsumer2D out;
 411 
 412         ClosedPathDetector(final DRendererContext rdrCtx) {
 413             this.rdrCtx = rdrCtx;
 414             this.stack = (rdrCtx.stats != null) ?
 415                 new PolyStack(rdrCtx,
 416                         rdrCtx.stats.stat_cpd_polystack_types,
 417                         rdrCtx.stats.stat_cpd_polystack_curves,
 418                         rdrCtx.stats.hist_cpd_polystack_curves,
 419                         rdrCtx.stats.stat_array_cpd_polystack_curves,
 420                         rdrCtx.stats.stat_array_cpd_polystack_types)
 421                 : new PolyStack(rdrCtx);
 422         }
 423 
 424         ClosedPathDetector init(DPathConsumer2D out) {
 425             this.out = out;
 426             return this; // fluent API
 427         }
 428 
 429         /**
 430          * Disposes this instance:
 431          * clean up before reusing this instance
 432          */
 433         void dispose() {
 434             stack.dispose();
 435         }
 436 
 437         @Override
 438         public void pathDone() {
 439             // previous path is not closed:
 440             finish(false);
 441             out.pathDone();
 442 
 443             // TODO: fix possible leak if exception happened
 444             // Dispose this instance:
 445             dispose();
 446         }
 447 
 448         @Override
 449         public void closePath() {
 450             // path is closed
 451             finish(true);
 452             out.closePath();
 453         }
 454 
 455         @Override
 456         public void moveTo(double x0, double y0) {
 457             // previous path is not closed:
 458             finish(false);
 459             out.moveTo(x0, y0);
 460         }
 461 
 462         private void finish(final boolean closed) {
 463             rdrCtx.closedPath = closed;
 464             stack.pullAll(out);
 465         }
 466 
 467         @Override
 468         public void lineTo(double x1, double y1) {
 469             stack.pushLine(x1, y1);
 470         }
 471 
 472         @Override
 473         public void curveTo(double x3, double y3,
 474                             double x2, double y2,
 475                             double x1, double y1)
 476         {
 477             stack.pushCubic(x1, y1, x2, y2, x3, y3);
 478         }
 479 
 480         @Override
 481         public void quadTo(double x2, double y2, double x1, double y1) {
 482             stack.pushQuad(x1, y1, x2, y2);
 483         }
 484 
 485         @Override
 486         public long getNativeConsumer() {
 487             throw new InternalError("Not using a native peer");
 488         }
 489     }
 490 
 491     static final class PathClipFilter implements DPathConsumer2D {
 492 
 493         private DPathConsumer2D out;
 494 
 495         // Bounds of the drawing region, at pixel precision.
 496         private final double[] clipRect;
 497 
 498         private final double[] corners = new double[8];
 499         private boolean init_corners = false;
 500 
 501         private final IndexStack stack;
 502 
 503         // the current outcode of the current sub path
 504         private int cOutCode = 0;
 505 
 506         // the cumulated (and) outcode of the complete path
 507         private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 508 
 509         private boolean outside = false;
 510 
 511         // The current point (TODO stupid repeated info)
 512         private double cx0, cy0;
 513 
 514         // The current point OUTSIDE
 515         private double cox0, coy0;
 516 
 517         private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
 518         private final CurveClipSplitter curveSplitter;
 519 
 520         PathClipFilter(final DRendererContext rdrCtx) {
 521             this.clipRect = rdrCtx.clipRect;
 522             this.curveSplitter = rdrCtx.curveClipSplitter;
 523 
 524             this.stack = (rdrCtx.stats != null) ?
 525                 new IndexStack(rdrCtx,
 526                         rdrCtx.stats.stat_pcf_idxstack_indices,
 527                         rdrCtx.stats.hist_pcf_idxstack_indices,
 528                         rdrCtx.stats.stat_array_pcf_idxstack_indices)
 529                 : new IndexStack(rdrCtx);
 530         }
 531 
 532         PathClipFilter init(final DPathConsumer2D out) {
 533             this.out = out;
 534 
 535             // Adjust the clipping rectangle with the renderer offsets
 536             final double rdrOffX = DRenderer.RDR_OFFSET_X;
 537             final double rdrOffY = DRenderer.RDR_OFFSET_Y;
 538 
 539             // add a small rounding error:
 540             final double margin = 1e-3d;
 541 
 542             final double[] _clipRect = this.clipRect;
 543             _clipRect[0] -= margin - rdrOffY;
 544             _clipRect[1] += margin + rdrOffY;
 545             _clipRect[2] -= margin - rdrOffX;
 546             _clipRect[3] += margin + rdrOffX;
 547 
 548             if (MarlinConst.DO_CLIP_SUBDIVIDER) {
 549                 // adjust padded clip rectangle:
 550                 curveSplitter.init();
 551             }
 552 
 553             this.init_corners = true;
 554             this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 555 
 556             return this; // fluent API
 557         }
 558 
 559         /**
 560          * Disposes this instance:
 561          * clean up before reusing this instance
 562          */
 563         void dispose() {
 564             stack.dispose();
 565         }
 566 
 567         private void finishPath() {
 568             if (outside) {
 569                 // criteria: inside or totally outside ?
 570                 if (gOutCode == 0) {
 571                     finish();
 572                 } else {
 573                     this.outside = false;
 574                     stack.reset();
 575                 }
 576             }
 577         }
 578 
 579         private void finish() {
 580             this.outside = false;
 581 
 582             if (!stack.isEmpty()) {
 583                 if (init_corners) {
 584                     init_corners = false;
 585 
 586                     final double[] _corners = corners;
 587                     final double[] _clipRect = clipRect;
 588                     // Top Left (0):
 589                     _corners[0] = _clipRect[2];
 590                     _corners[1] = _clipRect[0];
 591                     // Bottom Left (1):
 592                     _corners[2] = _clipRect[2];
 593                     _corners[3] = _clipRect[1];
 594                     // Top right (2):
 595                     _corners[4] = _clipRect[3];
 596                     _corners[5] = _clipRect[0];
 597                     // Bottom Right (3):
 598                     _corners[6] = _clipRect[3];
 599                     _corners[7] = _clipRect[1];
 600                 }
 601                 stack.pullAll(corners, out);
 602             }
 603             out.lineTo(cox0, coy0);
 604             this.cx0 = cox0;
 605             this.cy0 = coy0;
 606         }
 607 
 608         @Override
 609         public void pathDone() {
 610             finishPath();
 611 
 612             out.pathDone();
 613 
 614             // TODO: fix possible leak if exception happened
 615             // Dispose this instance:
 616             dispose();
 617         }
 618 
 619         @Override
 620         public void closePath() {
 621             finishPath();
 622 
 623             out.closePath();
 624         }
 625 
 626         @Override
 627         public void moveTo(final double x0, final double y0) {
 628             finishPath();
 629 
 630             this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
 631             this.outside = false;
 632             out.moveTo(x0, y0);
 633             this.cx0 = x0;
 634             this.cy0 = y0;
 635         }
 636 
 637         @Override
 638         public void lineTo(final double xe, final double ye) {
 639             final int outcode0 = this.cOutCode;
 640             final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
 641 
 642             // Should clip
 643             final int orCode = (outcode0 | outcode1);
 644             if (orCode != 0) {
 645                 final int sideCode = (outcode0 & outcode1);
 646 
 647                 // basic rejection criteria:
 648                 if (sideCode == 0) {
 649                     // ovelap clip:
 650                     if (subdivide) {
 651                         // avoid reentrance
 652                         subdivide = false;
 653                         boolean ret;
 654                         // subdivide curve => callback with subdivided parts:
 655                         if (outside) {
 656                             ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
 657                                                           orCode, this);
 658                         } else {
 659                             ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
 660                                                           orCode, this);
 661                         }
 662                         // reentrance is done:
 663                         subdivide = true;
 664                         if (ret) {
 665                             return;
 666                         }
 667                     }
 668                     // already subdivided so render it
 669                 } else {
 670                     this.cOutCode = outcode1;
 671                     this.gOutCode &= sideCode;
 672                     // keep last point coordinate before entering the clip again:
 673                     this.outside = true;
 674                     this.cox0 = xe;
 675                     this.coy0 = ye;
 676 
 677                     clip(sideCode, outcode0, outcode1);
 678                     return;
 679                 }
 680             }
 681 
 682             this.cOutCode = outcode1;
 683             this.gOutCode = 0;
 684 
 685             if (outside) {
 686                 finish();
 687             }
 688             // clipping disabled:
 689             out.lineTo(xe, ye);
 690             this.cx0 = xe;
 691             this.cy0 = ye;
 692         }
 693 
 694         private void clip(final int sideCode,
 695                           final int outcode0,
 696                           final int outcode1)
 697         {
 698             // corner or cross-boundary on left or right side:
 699             if ((outcode0 != outcode1)
 700                     && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
 701             {
 702                 // combine outcodes:
 703                 final int mergeCode = (outcode0 | outcode1);
 704                 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
 705                 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
 706                 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
 707 
 708                 // add corners to outside stack:
 709                 switch (tbCode) {
 710                     case MarlinConst.OUTCODE_TOP:
 711                         stack.push(off); // top
 712                         return;
 713                     case MarlinConst.OUTCODE_BOTTOM:
 714                         stack.push(off + 1); // bottom
 715                         return;
 716                     default:
 717                         // both TOP / BOTTOM:
 718                         if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
 719                             // top to bottom
 720                             stack.push(off); // top
 721                             stack.push(off + 1); // bottom
 722                         } else {
 723                             // bottom to top
 724                             stack.push(off + 1); // bottom
 725                             stack.push(off); // top
 726                         }
 727                 }
 728             }
 729         }
 730 
 731         @Override
 732         public void curveTo(final double x1, final double y1,
 733                             final double x2, final double y2,
 734                             final double xe, final double ye)
 735         {
 736             final int outcode0 = this.cOutCode;
 737             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 738             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
 739             final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
 740 
 741             // Should clip
 742             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
 743             if (orCode != 0) {
 744                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
 745 
 746                 // basic rejection criteria:
 747                 if (sideCode == 0) {
 748                     // ovelap clip:
 749                     if (subdivide) {
 750                         // avoid reentrance
 751                         subdivide = false;
 752                         // subdivide curve => callback with subdivided parts:
 753                         boolean ret;
 754                         if (outside) {
 755                             ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
 756                                                            x2, y2, xe, ye,
 757                                                            orCode, this);
 758                         } else {
 759                             ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
 760                                                            x2, y2, xe, ye,
 761                                                            orCode, this);
 762                         }
 763                         // reentrance is done:
 764                         subdivide = true;
 765                         if (ret) {
 766                             return;
 767                         }
 768                     }
 769                     // already subdivided so render it
 770                 } else {
 771                     this.cOutCode = outcode3;
 772                     this.gOutCode &= sideCode;
 773                     // keep last point coordinate before entering the clip again:
 774                     this.outside = true;
 775                     this.cox0 = xe;
 776                     this.coy0 = ye;
 777 
 778                     clip(sideCode, outcode0, outcode3);
 779                     return;
 780                 }
 781             }
 782 
 783             this.cOutCode = outcode3;
 784             this.gOutCode = 0;
 785 
 786             if (outside) {
 787                 finish();
 788             }
 789             // clipping disabled:
 790             out.curveTo(x1, y1, x2, y2, xe, ye);
 791             this.cx0 = xe;
 792             this.cy0 = ye;
 793         }
 794 
 795         @Override
 796         public void quadTo(final double x1, final double y1,
 797                            final double xe, final double ye)
 798         {
 799             final int outcode0 = this.cOutCode;
 800             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 801             final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
 802 
 803             // Should clip
 804             final int orCode = (outcode0 | outcode1 | outcode2);
 805             if (orCode != 0) {
 806                 final int sideCode = outcode0 & outcode1 & outcode2;
 807 
 808                 // basic rejection criteria:
 809                 if (sideCode == 0) {
 810                     // ovelap clip:
 811                     if (subdivide) {
 812                         // avoid reentrance
 813                         subdivide = false;
 814                         // subdivide curve => callback with subdivided parts:
 815                         boolean ret;
 816                         if (outside) {
 817                             ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
 818                                                           xe, ye, orCode, this);
 819                         } else {
 820                             ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
 821                                                           xe, ye, orCode, this);
 822                         }
 823                         // reentrance is done:
 824                         subdivide = true;
 825                         if (ret) {
 826                             return;
 827                         }
 828                     }
 829                     // already subdivided so render it
 830                 } else {
 831                     this.cOutCode = outcode2;
 832                     this.gOutCode &= sideCode;
 833                     // keep last point coordinate before entering the clip again:
 834                     this.outside = true;
 835                     this.cox0 = xe;
 836                     this.coy0 = ye;
 837 
 838                     clip(sideCode, outcode0, outcode2);
 839                     return;
 840                 }
 841             }
 842 
 843             this.cOutCode = outcode2;
 844             this.gOutCode = 0;
 845 
 846             if (outside) {
 847                 finish();
 848             }
 849             // clipping disabled:
 850             out.quadTo(x1, y1, xe, ye);
 851             this.cx0 = xe;
 852             this.cy0 = ye;
 853         }
 854 
 855         @Override
 856         public long getNativeConsumer() {
 857             throw new InternalError("Not using a native peer");
 858         }
 859     }
 860 
 861     static final class CurveClipSplitter {
 862 
 863         static final double LEN_TH = MarlinProperties.getSubdividerMinLength();
 864         static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
 865 
 866         private static final boolean TRACE = false;
 867 
 868         private static final int MAX_N_CURVES = 3 * 4;
 869 
 870         // clip rectangle (ymin, ymax, xmin, xmax):
 871         final double[] clipRect;
 872 
 873         // clip rectangle (ymin, ymax, xmin, xmax) including padding:
 874         final double[] clipRectPad = new double[4];
 875         private boolean init_clipRectPad = false;
 876 
 877         // This is where the curve to be processed is put. We give it
 878         // enough room to store all curves.
 879         final double[] middle = new double[MAX_N_CURVES * 8 + 2];
 880         // t values at subdivision points
 881         private final double[] subdivTs = new double[MAX_N_CURVES];
 882 
 883         // dirty curve
 884         private final DCurve curve;
 885 
 886         CurveClipSplitter(final DRendererContext rdrCtx) {
 887             this.clipRect = rdrCtx.clipRect;
 888             this.curve = rdrCtx.curve;
 889         }
 890 
 891         void init() {
 892             this.init_clipRectPad = true;
 893         }
 894 
 895         private void initPaddedClip() {
 896             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 897             // adjust padded clip rectangle (ymin, ymax, xmin, xmax):
 898             // add a rounding error (curve subdivision ~ 0.1px):
 899             final double[] _clipRect = clipRect;
 900             final double[] _clipRectPad = clipRectPad;
 901 
 902             _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
 903             _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
 904             _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
 905             _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
 906 
 907             if (TRACE) {
 908                 MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
 909                                         + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
 910             }
 911         }
 912 
 913         boolean splitLine(final double x0, final double y0,
 914                           final double x1, final double y1,
 915                           final int outCodeOR,
 916                           final DPathConsumer2D out)
 917         {
 918             if (TRACE) {
 919                 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
 920             }
 921 
 922             if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
 923                 return false;
 924             }
 925 
 926             final double[] mid = middle;
 927             mid[0] = x0;  mid[1] = y0;
 928             mid[2] = x1;  mid[3] = y1;
 929 
 930             return subdivideAtIntersections(4, outCodeOR, out);
 931         }
 932 
 933         boolean splitQuad(final double x0, final double y0,
 934                           final double x1, final double y1,
 935                           final double x2, final double y2,
 936                           final int outCodeOR,
 937                           final DPathConsumer2D out)
 938         {
 939             if (TRACE) {
 940                 MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
 941             }
 942 
 943             if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
 944                 return false;
 945             }
 946 
 947             final double[] mid = middle;
 948             mid[0] = x0;  mid[1] = y0;
 949             mid[2] = x1;  mid[3] = y1;
 950             mid[4] = x2;  mid[5] = y2;
 951 
 952             return subdivideAtIntersections(6, outCodeOR, out);
 953         }
 954 
 955         boolean splitCurve(final double x0, final double y0,
 956                            final double x1, final double y1,
 957                            final double x2, final double y2,
 958                            final double x3, final double y3,
 959                            final int outCodeOR,
 960                            final DPathConsumer2D out)
 961         {
 962             if (TRACE) {
 963                 MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
 964             }
 965 
 966             if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
 967                 return false;
 968             }
 969 
 970             final double[] mid = middle;
 971             mid[0] = x0;  mid[1] = y0;
 972             mid[2] = x1;  mid[3] = y1;
 973             mid[4] = x2;  mid[5] = y2;
 974             mid[6] = x3;  mid[7] = y3;
 975 
 976             return subdivideAtIntersections(8, outCodeOR, out);
 977         }
 978 
 979         private boolean subdivideAtIntersections(final int type, final int outCodeOR,
 980                                                  final DPathConsumer2D out)
 981         {
 982             final double[] mid = middle;
 983             final double[] subTs = subdivTs;
 984 
 985             if (init_clipRectPad) {
 986                 init_clipRectPad = false;
 987                 initPaddedClip();
 988             }
 989 
 990             final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type,
 991                                                         outCodeOR, clipRectPad);
 992 
 993             if (TRACE) {
 994                 MarlinUtils.logInfo("nSplits: "+ nSplits);
 995                 MarlinUtils.logInfo("subTs: "+Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
 996             }
 997             if (nSplits == 0) {
 998                 // only curve support shortcut
 999                 return false;
1000             }
1001             double prevT = 0.0d;
1002 
1003             for (int i = 0, off = 0; i < nSplits; i++, off += type) {
1004                 final double t = subTs[i];
1005 
1006                 DHelpers.subdivideAt((t - prevT) / (1.0d - prevT),
1007                                      mid, off, mid, off, type);
1008                 prevT = t;
1009             }
1010 
1011             for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
1012                 if (TRACE) {
1013                     MarlinUtils.logInfo("Part Curve "+Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
1014                 }
1015                 emitCurrent(type, mid, off, out);
1016             }
1017             return true;
1018         }
1019 
1020         static void emitCurrent(final int type, final double[] pts,
1021                                 final int off, final DPathConsumer2D out)
1022         {
1023             // if instead of switch (perf + most probable cases first)
1024             if (type == 8) {
1025                 out.curveTo(pts[off + 2], pts[off + 3],
1026                             pts[off + 4], pts[off + 5],
1027                             pts[off + 6], pts[off + 7]);
1028             } else if (type == 4) {
1029                 out.lineTo(pts[off + 2], pts[off + 3]);
1030             } else {
1031                 out.quadTo(pts[off + 2], pts[off + 3],
1032                            pts[off + 4], pts[off + 5]);
1033             }
1034         }
1035     }
1036 
1037     static final class CurveBasicMonotonizer {
1038 
1039         private static final int MAX_N_CURVES = 11;
1040 
1041         // squared half line width (for stroker)
1042         private double lw2;
1043 
1044         // number of splitted curves
1045         int nbSplits;
1046 
1047         // This is where the curve to be processed is put. We give it
1048         // enough room to store all curves.
1049         final double[] middle = new double[MAX_N_CURVES * 6 + 2];
1050         // t values at subdivision points
1051         private final double[] subdivTs = new double[MAX_N_CURVES - 1];
1052 
1053         // dirty curve
1054         private final DCurve curve;
1055 
1056         CurveBasicMonotonizer(final DRendererContext rdrCtx) {
1057             this.curve = rdrCtx.curve;
1058         }
1059 
1060         void init(final double lineWidth) {
1061             this.lw2 = (lineWidth * lineWidth) / 4.0d;
1062         }
1063 
1064         CurveBasicMonotonizer curve(final double x0, final double y0,
1065                                     final double x1, final double y1,
1066                                     final double x2, final double y2,
1067                                     final double x3, final double y3)
1068         {
1069             final double[] mid = middle;
1070             mid[0] = x0;  mid[1] = y0;
1071             mid[2] = x1;  mid[3] = y1;
1072             mid[4] = x2;  mid[5] = y2;
1073             mid[6] = x3;  mid[7] = y3;
1074 
1075             final double[] subTs = subdivTs;
1076             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1077 
1078             double prevT = 0.0d;
1079             for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1080                 final double t = subTs[i];
1081 
1082                 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1083                                           mid, off, mid, off, off + 6);
1084                 prevT = t;
1085             }
1086 
1087             this.nbSplits = nSplits;
1088             return this;
1089         }
1090 
1091         CurveBasicMonotonizer quad(final double x0, final double y0,
1092                                    final double x1, final double y1,
1093                                    final double x2, final double y2)
1094         {
1095             final double[] mid = middle;
1096             mid[0] = x0;  mid[1] = y0;
1097             mid[2] = x1;  mid[3] = y1;
1098             mid[4] = x2;  mid[5] = y2;
1099 
1100             final double[] subTs = subdivTs;
1101             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1102 
1103             double prevt = 0.0d;
1104             for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1105                 final double t = subTs[i];
1106                 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1107                                          mid, off, mid, off, off + 4);
1108                 prevt = t;
1109             }
1110 
1111             this.nbSplits = nSplits;
1112             return this;
1113         }
1114     }
1115 
1116     static final class PathTracer implements DPathConsumer2D {
1117         private final String prefix;
1118         private DPathConsumer2D out;
1119 
1120         PathTracer(String name) {
1121             this.prefix = name + ": ";
1122         }
1123 
1124         PathTracer init(DPathConsumer2D out) {
1125             this.out = out;
1126             return this; // fluent API
1127         }
1128 
1129         @Override
1130         public void moveTo(double x0, double y0) {
1131             log("moveTo (" + x0 + ", " + y0 + ')');
1132             out.moveTo(x0, y0);
1133         }
1134 
1135         @Override
1136         public void lineTo(double x1, double y1) {
1137             log("lineTo (" + x1 + ", " + y1 + ')');
1138             out.lineTo(x1, y1);
1139         }
1140 
1141         @Override
1142         public void curveTo(double x1, double y1,
1143                             double x2, double y2,
1144                             double x3, double y3)
1145         {
1146             log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ") P3(" + x3 + ", " + y3 + ')');
1147             out.curveTo(x1, y1, x2, y2, x3, y3);
1148         }
1149 
1150         @Override
1151         public void quadTo(double x1, double y1, double x2, double y2) {
1152             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
1153             out.quadTo(x1, y1, x2, y2);
1154         }
1155 
1156         @Override
1157         public void closePath() {
1158             log("closePath");
1159             out.closePath();
1160         }
1161 
1162         @Override
1163         public void pathDone() {
1164             log("pathDone");
1165             out.pathDone();
1166         }
1167 
1168         private void log(final String message) {
1169             MarlinUtils.logInfo(prefix + message);
1170         }
1171 
1172         @Override
1173         public long getNativeConsumer() {
1174             throw new InternalError("Not using a native peer");
1175         }
1176     }
1177 }