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