1 /*
   2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.marlin;
  27 
  28 import com.sun.javafx.geom.Path2D;
  29 import com.sun.javafx.geom.transform.BaseTransform;
  30 import com.sun.marlin.DHelpers.IndexStack;
  31 import com.sun.marlin.DHelpers.PolyStack;
  32 import java.util.Arrays;
  33 
  34 public final class DTransformingPathConsumer2D {
  35 
  36     // smaller uncertainty in double variant
  37     static final double CLIP_RECT_PADDING = 0.25d;
  38 
  39     private final DRendererContext rdrCtx;
  40 
  41     // recycled ClosedPathDetector instance from detectClosedPath()
  42     private final ClosedPathDetector   cpDetector;
  43 
  44     // recycled PathClipFilter instance from pathClipper()
  45     private final PathClipFilter       pathClipper;
  46 
  47     // recycled DPathConsumer2D instance from wrapPath2D()
  48     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  49 
  50     // recycled DPathConsumer2D instances from deltaTransformConsumer()
  51     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  52     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  53 
  54     // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
  55     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  56     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  57 
  58     // recycled PathTracer instances from tracer...() methods
  59     private final PathTracer tracerInput      = new PathTracer("[Input]");
  60     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  61     private final PathTracer tracerFiller     = new PathTracer("Filler");
  62     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  63     private final PathTracer tracerDasher     = new PathTracer("Dasher");
  64 
  65     DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
  66         // used by RendererContext
  67         this.rdrCtx = rdrCtx;
  68         this.cpDetector = new ClosedPathDetector(rdrCtx);
  69         this.pathClipper = new PathClipFilter(rdrCtx);
  70     }
  71 
  72     public DPathConsumer2D wrapPath2D(Path2D p2d) {
  73         return wp_Path2DWrapper.init(p2d);
  74     }
  75 
  76     public DPathConsumer2D traceInput(DPathConsumer2D out) {
  77         return tracerInput.init(out);
  78     }
  79 
  80     public DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
  81         return tracerCPDetector.init(out);
  82     }
  83 
  84     public DPathConsumer2D traceFiller(DPathConsumer2D out) {
  85         return tracerFiller.init(out);
  86     }
  87 
  88     public DPathConsumer2D traceStroker(DPathConsumer2D out) {
  89         return tracerStroker.init(out);
  90     }
  91 
  92     public DPathConsumer2D traceDasher(DPathConsumer2D out) {
  93         return tracerDasher.init(out);
  94     }
  95 
  96     public DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
  97         return cpDetector.init(out);
  98     }
  99 
 100     public DPathConsumer2D pathClipper(DPathConsumer2D out,
 101                                        final double rdrOffX,
 102                                        final double rdrOffY)
 103     {
 104         return pathClipper.init(out, rdrOffX, rdrOffY);
 105     }
 106 
 107     public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
 108                                                   BaseTransform at,
 109                                                   final double rdrOffX,
 110                                                   final double rdrOffY)
 111     {
 112         if (at == null) {
 113             return out;
 114         }
 115         final double mxx = at.getMxx();
 116         final double mxy = at.getMxy();
 117         final double myx = at.getMyx();
 118         final double myy = at.getMyy();
 119 
 120         if (mxy == 0.0d && myx == 0.0d) {
 121             if (mxx == 1.0d && myy == 1.0d) {
 122                 return out;
 123             } else {
 124                 // Scale only
 125                 if (rdrCtx.doClip) {
 126                     // adjust clip rectangle (ymin, ymax, xmin, xmax):
 127                     adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
 128                     adjustClipScale(rdrCtx.clipRect, mxx, myy);
 129                 }
 130                 return dt_DeltaScaleFilter.init(out, mxx, myy);
 131             }
 132         } else {
 133             if (rdrCtx.doClip) {
 134                 // adjust clip rectangle (ymin, ymax, xmin, xmax):
 135                 adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
 136                 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
 137             }
 138             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
 139         }
 140     }
 141 
 142     private static void adjustClipOffset(final double[] clipRect,
 143                                          final double rdrOffX,
 144                                          final double rdrOffY)
 145     {
 146         clipRect[0] += rdrOffY;
 147         clipRect[1] += rdrOffY;
 148         clipRect[2] += rdrOffX;
 149         clipRect[3] += rdrOffX;
 150     }
 151 
 152     private static void adjustClipScale(final double[] clipRect,
 153                                         final double mxx, final double myy)
 154     {
 155         // Adjust the clipping rectangle (iv_DeltaScaleFilter):
 156         clipRect[0] /= myy;
 157         clipRect[1] /= myy;
 158         clipRect[2] /= mxx;
 159         clipRect[3] /= mxx;
 160     }
 161 
 162     private static void adjustClipInverseDelta(final double[] clipRect,
 163                                                final double mxx, final double mxy,
 164                                                final double myx, final double myy)
 165     {
 166         // Adjust the clipping rectangle (iv_DeltaTransformFilter):
 167         final double det = mxx * myy - mxy * myx;
 168         final double imxx =  myy / det;
 169         final double imxy = -mxy / det;
 170         final double imyx = -myx / det;
 171         final double imyy =  mxx / det;
 172 
 173         double xmin, xmax, ymin, ymax;
 174         double x, y;
 175         // xmin, ymin:
 176         x = clipRect[2] * imxx + clipRect[0] * imxy;
 177         y = clipRect[2] * imyx + clipRect[0] * imyy;
 178 
 179         xmin = xmax = x;
 180         ymin = ymax = y;
 181 
 182         // xmax, ymin:
 183         x = clipRect[3] * imxx + clipRect[0] * imxy;
 184         y = clipRect[3] * imyx + clipRect[0] * imyy;
 185 
 186         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 187         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 188 
 189         // xmin, ymax:
 190         x = clipRect[2] * imxx + clipRect[1] * imxy;
 191         y = clipRect[2] * imyx + clipRect[1] * 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         // xmax, ymax:
 197         x = clipRect[3] * imxx + clipRect[1] * imxy;
 198         y = clipRect[3] * 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         clipRect[0] = ymin;
 204         clipRect[1] = ymax;
 205         clipRect[2] = xmin;
 206         clipRect[3] = xmax;
 207     }
 208 
 209     public DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out,
 210                                                         BaseTransform at)
 211     {
 212         if (at == null) {
 213             return out;
 214         }
 215         double mxx = at.getMxx();
 216         double mxy = at.getMxy();
 217         double myx = at.getMyx();
 218         double myy = at.getMyy();
 219 
 220         if (mxy == 0.0d && myx == 0.0d) {
 221             if (mxx == 1.0d && myy == 1.0d) {
 222                 return out;
 223             } else {
 224                 return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy);
 225             }
 226         } else {
 227             final double det = mxx * myy - mxy * myx;
 228             return iv_DeltaTransformFilter.init(out,
 229                                                 myy / det,
 230                                                -mxy / det,
 231                                                -myx / det,
 232                                                 mxx / det);
 233         }
 234     }
 235 
 236     static final class DeltaScaleFilter implements DPathConsumer2D {
 237         private DPathConsumer2D out;
 238         private double sx, sy;
 239 
 240         DeltaScaleFilter() {}
 241 
 242         DeltaScaleFilter init(DPathConsumer2D out,
 243                               double mxx, double myy)
 244         {
 245             this.out = out;
 246             sx = mxx;
 247             sy = myy;
 248             return this; // fluent API
 249         }
 250 
 251         @Override
 252         public void moveTo(double x0, double y0) {
 253             out.moveTo(x0 * sx, y0 * sy);
 254         }
 255 
 256         @Override
 257         public void lineTo(double x1, double y1) {
 258             out.lineTo(x1 * sx, y1 * sy);
 259         }
 260 
 261         @Override
 262         public void quadTo(double x1, double y1,
 263                            double x2, double y2)
 264         {
 265             out.quadTo(x1 * sx, y1 * sy,
 266                        x2 * sx, y2 * sy);
 267         }
 268 
 269         @Override
 270         public void curveTo(double x1, double y1,
 271                             double x2, double y2,
 272                             double x3, double y3)
 273         {
 274             out.curveTo(x1 * sx, y1 * sy,
 275                         x2 * sx, y2 * sy,
 276                         x3 * sx, y3 * sy);
 277         }
 278 
 279         @Override
 280         public void closePath() {
 281             out.closePath();
 282         }
 283 
 284         @Override
 285         public void pathDone() {
 286             out.pathDone();
 287         }
 288     }
 289 
 290     static final class DeltaTransformFilter implements DPathConsumer2D {
 291         private DPathConsumer2D out;
 292         private double mxx, mxy, myx, myy;
 293 
 294         DeltaTransformFilter() {}
 295 
 296         DeltaTransformFilter init(DPathConsumer2D out,
 297                                   double mxx, double mxy,
 298                                   double myx, double myy)
 299         {
 300             this.out = out;
 301             this.mxx = mxx;
 302             this.mxy = mxy;
 303             this.myx = myx;
 304             this.myy = myy;
 305             return this; // fluent API
 306         }
 307 
 308         @Override
 309         public void moveTo(double x0, double y0) {
 310             out.moveTo(x0 * mxx + y0 * mxy,
 311                        x0 * myx + y0 * myy);
 312         }
 313 
 314         @Override
 315         public void lineTo(double x1, double y1) {
 316             out.lineTo(x1 * mxx + y1 * mxy,
 317                        x1 * myx + y1 * myy);
 318         }
 319 
 320         @Override
 321         public void quadTo(double x1, double y1,
 322                            double x2, double y2)
 323         {
 324             out.quadTo(x1 * mxx + y1 * mxy,
 325                        x1 * myx + y1 * myy,
 326                        x2 * mxx + y2 * mxy,
 327                        x2 * myx + y2 * myy);
 328         }
 329 
 330         @Override
 331         public void curveTo(double x1, double y1,
 332                             double x2, double y2,
 333                             double x3, double y3)
 334         {
 335             out.curveTo(x1 * mxx + y1 * mxy,
 336                         x1 * myx + y1 * myy,
 337                         x2 * mxx + y2 * mxy,
 338                         x2 * myx + y2 * myy,
 339                         x3 * mxx + y3 * mxy,
 340                         x3 * myx + y3 * myy);
 341         }
 342 
 343         @Override
 344         public void closePath() {
 345             out.closePath();
 346         }
 347 
 348         @Override
 349         public void pathDone() {
 350             out.pathDone();
 351         }
 352     }
 353 
 354     static final class Path2DWrapper implements DPathConsumer2D {
 355         private Path2D p2d;
 356 
 357         Path2DWrapper() {}
 358 
 359         Path2DWrapper init(Path2D p2d) {
 360             this.p2d = p2d;
 361             return this;
 362         }
 363 
 364         @Override
 365         public void moveTo(double x0, double y0) {
 366             p2d.moveTo((float)x0, (float)y0);
 367         }
 368 
 369         @Override
 370         public void lineTo(double x1, double y1) {
 371             p2d.lineTo((float)x1, (float)y1);
 372         }
 373 
 374         @Override
 375         public void closePath() {
 376             p2d.closePath();
 377         }
 378 
 379         @Override
 380         public void pathDone() {}
 381 
 382         @Override
 383         public void curveTo(double x1, double y1,
 384                             double x2, double y2,
 385                             double x3, double y3)
 386         {
 387             p2d.curveTo((float)x1, (float)y1, (float)x2, (float)y2,
 388                     (float)x3, (float)y3);
 389         }
 390 
 391         @Override
 392         public void quadTo(double x1, double y1, double x2, double y2) {
 393             p2d.quadTo((float)x1, (float)y1, (float)x2, (float)y2);
 394         }
 395     }
 396 
 397     static final class ClosedPathDetector implements DPathConsumer2D {
 398 
 399         private final DRendererContext rdrCtx;
 400         private final PolyStack stack;
 401 
 402         private DPathConsumer2D out;
 403 
 404         ClosedPathDetector(final DRendererContext 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(DPathConsumer2D 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(double x0, double 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(double x1, double y1) {
 461             stack.pushLine(x1, y1);
 462         }
 463 
 464         @Override
 465         public void curveTo(double x3, double y3,
 466                             double x2, double y2,
 467                             double x1, double y1)
 468         {
 469             stack.pushCubic(x1, y1, x2, y2, x3, y3);
 470         }
 471 
 472         @Override
 473         public void quadTo(double x2, double y2, double x1, double y1) {
 474             stack.pushQuad(x1, y1, x2, y2);
 475         }
 476     }
 477 
 478     static final class PathClipFilter implements DPathConsumer2D {
 479 
 480         private DPathConsumer2D out;
 481 
 482         // Bounds of the drawing region, at pixel precision.
 483         private final double[] clipRect;
 484 
 485         private final double[] corners = new double[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 double cx0, cy0;
 500 
 501         // The current point OUTSIDE
 502         private double cox0, coy0;
 503 
 504         private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
 505         private final CurveClipSplitter curveSplitter;
 506 
 507         PathClipFilter(final DRendererContext 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 DPathConsumer2D out,
 520                             final double rdrOffX,
 521                             final double rdrOffY)
 522         {
 523             this.out = out;
 524 
 525             // add a small rounding error:
 526             final double margin = 1e-3d;
 527 
 528             final double[] _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 double[] _corners = corners;
 574                     final double[] _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 double x0, final double y0) {
 615             finishPath();
 616 
 617             this.cOutCode = DHelpers.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 double xe, final double ye) {
 626             final int outcode0 = this.cOutCode;
 627             final int outcode1 = DHelpers.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 double x1, final double y1,
 720                             final double x2, final double y2,
 721                             final double xe, final double ye)
 722         {
 723             final int outcode0 = this.cOutCode;
 724             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 725             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
 726             final int outcode3 = DHelpers.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 double x1, final double y1,
 784                            final double xe, final double ye)
 785         {
 786             final int outcode0 = this.cOutCode;
 787             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 788             final int outcode2 = DHelpers.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 double LEN_TH = MarlinProperties.getSubdividerMinLength();
 846         static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
 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 double[] clipRect;
 854 
 855         // clip rectangle (ymin, ymax, xmin, xmax) including padding:
 856         final double[] clipRectPad = new double[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 double[] middle = new double[MAX_N_CURVES * 8 + 2];
 862         // t values at subdivision points
 863         private final double[] subdivTs = new double[MAX_N_CURVES];
 864 
 865         // dirty curve
 866         private final DCurve curve;
 867 
 868         CurveClipSplitter(final DRendererContext 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 double[] _clipRect = clipRect;
 882             final double[] _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 double x0, final double y0,
 896                           final double x1, final double y1,
 897                           final int outCodeOR,
 898                           final DPathConsumer2D out)
 899         {
 900             if (TRACE) {
 901                 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
 902             }
 903 
 904             if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
 905                 return false;
 906             }
 907 
 908             final double[] 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 double x0, final double y0,
 916                           final double x1, final double y1,
 917                           final double x2, final double y2,
 918                           final int outCodeOR,
 919                           final DPathConsumer2D 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 && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
 926                 return false;
 927             }
 928 
 929             final double[] 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 double x0, final double y0,
 938                            final double x1, final double y1,
 939                            final double x2, final double y2,
 940                            final double x3, final double y3,
 941                            final int outCodeOR,
 942                            final DPathConsumer2D 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 && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
 949                 return false;
 950             }
 951 
 952             final double[] 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 DPathConsumer2D out)
 963         {
 964             final double[] mid = middle;
 965             final double[] subTs = subdivTs;
 966 
 967             if (init_clipRectPad) {
 968                 init_clipRectPad = false;
 969                 initPaddedClip();
 970             }
 971 
 972             final int nSplits = DHelpers.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             double prevT = 0.0d;
 984 
 985             for (int i = 0, off = 0; i < nSplits; i++, off += type) {
 986                 final double t = subTs[i];
 987 
 988                 DHelpers.subdivideAt((t - prevT) / (1.0d - 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 double[] pts,
1003                                 final int off, final DPathConsumer2D 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 double 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 double[] middle = new double[MAX_N_CURVES * 6 + 2];
1032         // t values at subdivision points
1033         private final double[] subdivTs = new double[MAX_N_CURVES - 1];
1034 
1035         // dirty curve
1036         private final DCurve curve;
1037 
1038         CurveBasicMonotonizer(final DRendererContext rdrCtx) {
1039             this.curve = rdrCtx.curve;
1040         }
1041 
1042         public void init(final double lineWidth) {
1043             this.lw2 = (lineWidth * lineWidth) / 4.0d;
1044         }
1045 
1046         CurveBasicMonotonizer curve(final double x0, final double y0,
1047                                     final double x1, final double y1,
1048                                     final double x2, final double y2,
1049                                     final double x3, final double y3)
1050         {
1051             final double[] 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 double[] subTs = subdivTs;
1058             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1059 
1060             double prevT = 0.0d;
1061             for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1062                 final double t = subTs[i];
1063 
1064                 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - 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 double x0, final double y0,
1074                                    final double x1, final double y1,
1075                                    final double x2, final double y2)
1076         {
1077             final double[] 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 double[] subTs = subdivTs;
1083             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1084 
1085             double prevt = 0.0d;
1086             for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1087                 final double t = subTs[i];
1088                 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - 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 DPathConsumer2D {
1099         private final String prefix;
1100         private DPathConsumer2D out;
1101 
1102         PathTracer(String name) {
1103             this.prefix = name + ": ";
1104         }
1105 
1106         PathTracer init(DPathConsumer2D out) {
1107             this.out = out;
1108             return this; // fluent API
1109         }
1110 
1111         @Override
1112         public void moveTo(double x0, double y0) {
1113             log("moveTo (" + x0 + ", " + y0 + ')');
1114             out.moveTo(x0, y0);
1115         }
1116 
1117         @Override
1118         public void lineTo(double x1, double y1) {
1119             log("lineTo (" + x1 + ", " + y1 + ')');
1120             out.lineTo(x1, y1);
1121         }
1122 
1123         @Override
1124         public void curveTo(double x1, double y1,
1125                             double x2, double y2,
1126                             double x3, double 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(double x1, double y1, double x2, double 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 }