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