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