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