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