1 /*
   2  * Copyright (c) 2011, 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.prism.impl.shape;
  27 
  28 
  29 import com.sun.javafx.geom.PathIterator;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.Rectangle;
  32 import com.sun.javafx.geom.Shape;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 import com.sun.marlin.MarlinConst;
  35 import com.sun.marlin.MarlinProperties;
  36 import com.sun.marlin.DMarlinRenderer;
  37 import com.sun.marlin.DPathConsumer2D;
  38 import com.sun.marlin.DRendererContext;
  39 import com.sun.marlin.DStroker;
  40 import com.sun.marlin.DTransformingPathConsumer2D;
  41 import com.sun.marlin.MarlinUtils;
  42 import com.sun.prism.BasicStroke;
  43 import java.util.Arrays;
  44 
  45 public final class DMarlinPrismUtils {
  46 
  47     private static final boolean FORCE_NO_AA = false;
  48 
  49     // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
  50     static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
  51 
  52     static final boolean DO_TRACE_PATH = false;
  53 
  54     static final boolean DO_CLIP = MarlinProperties.isDoClip();
  55     static final boolean DO_CLIP_FILL = true;
  56     static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
  57 
  58     static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
  59     static final float LOWER_BND = -UPPER_BND;
  60 
  61     /**
  62      * Private constructor to prevent instantiation.
  63      */
  64     private DMarlinPrismUtils() {
  65     }
  66 
  67     private static DPathConsumer2D initStroker(
  68             final DRendererContext rdrCtx,
  69             final BasicStroke stroke,
  70             final float lineWidth,
  71             BaseTransform tx,
  72             final DPathConsumer2D out)
  73     {
  74         // We use strokerat so that in Stroker and Dasher we can work only
  75         // with the pre-transformation coordinates. This will repeat a lot of
  76         // computations done in the path iterator, but the alternative is to
  77         // work with transformed paths and compute untransformed coordinates
  78         // as needed. This would be faster but I do not think the complexity
  79         // of working with both untransformed and transformed coordinates in
  80         // the same code is worth it.
  81         // However, if a path's width is constant after a transformation,
  82         // we can skip all this untransforming.
  83 
  84         // As pathTo() will check transformed coordinates for invalid values
  85         // (NaN / Infinity) to ignore such points, it is necessary to apply the
  86         // transformation before the path processing.
  87         BaseTransform strokerTx = null;
  88 
  89         int dashLen = -1;
  90         boolean recycleDashes = false;
  91         double width = lineWidth;
  92         float[] dashes = stroke.getDashArray();
  93         double[] dashesD = null;
  94         double dashphase = stroke.getDashPhase();
  95 
  96         // Ensure converting dashes to double precision:
  97         if (dashes != null) {
  98             recycleDashes = true;
  99             dashLen = dashes.length;
 100             dashesD = rdrCtx.dasher.copyDashArray(dashes);
 101         }
 102 
 103         if ((tx != null) && !tx.isIdentity()) {
 104             final double a = tx.getMxx();
 105             final double b = tx.getMxy();
 106             final double c = tx.getMyx();
 107             final double d = tx.getMyy();
 108 
 109             // If the transform is a constant multiple of an orthogonal transformation
 110             // then every length is just multiplied by a constant, so we just
 111             // need to transform input paths to stroker and tell stroker
 112             // the scaled width. This condition is satisfied if
 113             // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 114             // leave a bit of room for error.
 115             if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
 116                 final double scale = Math.sqrt(a*a + c*c);
 117 
 118                 if (dashesD != null) {
 119                     for (int i = 0; i < dashLen; i++) {
 120                         dashesD[i] *= scale;
 121                     }
 122                     dashphase *= scale;
 123                 }
 124                 width *= scale;
 125 
 126                 // by now strokerat == null. Input paths to
 127                 // stroker (and maybe dasher) will have the full transform tx
 128                 // applied to them and nothing will happen to the output paths.
 129             } else {
 130                 strokerTx = tx;
 131 
 132                 // by now strokerat == tx. Input paths to
 133                 // stroker (and maybe dasher) will have the full transform tx
 134                 // applied to them, then they will be normalized, and then
 135                 // the inverse of *only the non translation part of tx* will
 136                 // be applied to the normalized paths. This won't cause problems
 137                 // in stroker, because, suppose tx = T*A, where T is just the
 138                 // translation part of tx, and A is the rest. T*A has already
 139                 // been applied to Stroker/Dasher's input. Then Ainv will be
 140                 // applied. Ainv*T*A is not equal to T, but it is a translation,
 141                 // which means that none of stroker's assumptions about its
 142                 // input will be violated. After all this, A will be applied
 143                 // to stroker's output.
 144             }
 145         } else {
 146             // either tx is null or it's the identity. In either case
 147             // we don't transform the path.
 148             tx = null;
 149         }
 150 
 151         // Prepare the pipeline:
 152         DPathConsumer2D pc = out;
 153 
 154         final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 155 
 156         if (DO_TRACE_PATH) {
 157             // trace Stroker:
 158             pc = transformerPC2D.traceStroker(pc);
 159         }
 160 
 161         if (MarlinConst.USE_SIMPLIFIER) {
 162             // Use simplifier after stroker before Renderer
 163             // to remove collinear segments (notably due to cap square)
 164             pc = rdrCtx.simplifier.init(pc);
 165         }
 166 
 167         // deltaTransformConsumer may adjust the clip rectangle:
 168         pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 169 
 170         // stroker will adjust the clip rectangle (width / miter limit):
 171         pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 172                 stroke.getLineJoin(), stroke.getMiterLimit(),
 173                 (dashesD == null));
 174 
 175         // Curve Monotizer:
 176         rdrCtx.monotonizer.init(width);
 177 
 178         if (dashesD != null) {
 179             if (DO_TRACE_PATH) {
 180                 pc = transformerPC2D.traceDasher(pc);
 181             }
 182             pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase,
 183                                     recycleDashes);
 184 
 185             if (DISABLE_2ND_STROKER_CLIPPING) {
 186                 // disable stoker clipping:
 187                 rdrCtx.stroker.disableClipping();
 188             }
 189 
 190         } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) {
 191             if (DO_TRACE_PATH) {
 192                 pc = transformerPC2D.traceClosedPathDetector(pc);
 193             }
 194 
 195             // If no dash and clip is enabled:
 196             // detect closedPaths (polygons) for caps
 197             pc = transformerPC2D.detectClosedPath(pc);
 198         }
 199         pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
 200 
 201         if (DO_TRACE_PATH) {
 202             // trace Input:
 203             pc = transformerPC2D.traceInput(pc);
 204         }
 205         /*
 206          * Pipeline seems to be:
 207          * shape.getPathIterator(tx)
 208          * -> (inverseDeltaTransformConsumer)
 209          * -> (Dasher)
 210          * -> Stroker
 211          * -> (deltaTransformConsumer)
 212          *
 213          * -> (CollinearSimplifier) to remove redundant segments
 214          *
 215          * -> pc2d = Renderer (bounding box)
 216          */
 217         return pc;
 218     }
 219 
 220     private static boolean nearZero(final double num) {
 221         return Math.abs(num) < 2.0d * Math.ulp(num);
 222     }
 223 
 224     private static DPathConsumer2D initRenderer(
 225             final DRendererContext rdrCtx,
 226             final BasicStroke stroke,
 227             final BaseTransform tx,
 228             final Rectangle clip,
 229             final int piRule,
 230             final DMarlinRenderer renderer)
 231     {
 232         if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
 233             // Define the initial clip bounds:
 234             final double[] clipRect = rdrCtx.clipRect;
 235 
 236             // Adjust the clipping rectangle with the renderer offsets
 237             final double rdrOffX = renderer.getOffsetX();
 238             final double rdrOffY = renderer.getOffsetY();
 239 
 240             // add a small rounding error:
 241             final double margin = 1e-3d;
 242 
 243             clipRect[0] = clip.y
 244                             - margin + rdrOffY;
 245             clipRect[1] = clip.y + clip.height
 246                             + margin + rdrOffY;
 247             clipRect[2] = clip.x
 248                             - margin + rdrOffX;
 249             clipRect[3] = clip.x + clip.width
 250                             + margin + rdrOffX;
 251 
 252             if (MarlinConst.DO_LOG_CLIP) {
 253                 MarlinUtils.logInfo("clipRect (clip): "
 254                                     + Arrays.toString(rdrCtx.clipRect));
 255             }
 256 
 257             // Enable clipping:
 258             rdrCtx.doClip = true;
 259         }
 260 
 261         if (stroke != null) {
 262             renderer.init(clip.x, clip.y, clip.width, clip.height,
 263                           MarlinConst.WIND_NON_ZERO);
 264 
 265             return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer);
 266         } else {
 267             // Filler:
 268             final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ?
 269                 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO;
 270 
 271             renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 272 
 273             DPathConsumer2D pc = renderer;
 274 
 275             final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 276 
 277             if (DO_CLIP_FILL && rdrCtx.doClip) {
 278                 if (DO_TRACE_PATH) {
 279                     // trace Filler:
 280                     pc = rdrCtx.transformerPC2D.traceFiller(pc);
 281                 }
 282                 pc = rdrCtx.transformerPC2D.pathClipper(pc);
 283             }
 284 
 285             if (DO_TRACE_PATH) {
 286                 // trace Input:
 287                 pc = transformerPC2D.traceInput(pc);
 288             }
 289             return pc;
 290         }
 291     }
 292 
 293     public static DMarlinRenderer setupRenderer(
 294             final DRendererContext rdrCtx,
 295             final Shape shape,
 296             final BasicStroke stroke,
 297             final BaseTransform xform,
 298             final Rectangle rclip,
 299             final boolean antialiasedShape)
 300     {
 301         // Test if transform is identity:
 302         final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;
 303 
 304         final DMarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 305                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 306 
 307         if (shape instanceof Path2D) {
 308             final Path2D p2d = (Path2D)shape;
 309             final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
 310             feedConsumer(rdrCtx, p2d, tf, pc2d);
 311         } else {
 312             final PathIterator pi = shape.getPathIterator(tf);
 313             final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
 314             feedConsumer(rdrCtx, pi, pc2d);
 315         }
 316         return r;
 317     }
 318 
 319     public static void strokeTo(
 320             final DRendererContext rdrCtx,
 321             final Shape shape,
 322             final BasicStroke stroke,
 323             final float lineWidth,
 324             final DPathConsumer2D out)
 325     {
 326         final DPathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out);
 327 
 328         if (shape instanceof Path2D) {
 329             feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
 330         } else {
 331             feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
 332         }
 333     }
 334 
 335     private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi,
 336                                      DPathConsumer2D pc2d)
 337     {
 338         if (MarlinConst.USE_PATH_SIMPLIFIER) {
 339             // Use path simplifier at the first step
 340             // to remove useless points
 341             pc2d = rdrCtx.pathSimplifier.init(pc2d);
 342         }
 343 
 344         // mark context as DIRTY:
 345         rdrCtx.dirty = true;
 346 
 347         final float[] coords = rdrCtx.float6;
 348 
 349         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 350         // - removed skip flag = !subpathStarted
 351         // - removed pathClosed (ie subpathStarted not set to false)
 352         boolean subpathStarted = false;
 353 
 354         for (; !pi.isDone(); pi.next()) {
 355             switch (pi.currentSegment(coords)) {
 356             case PathIterator.SEG_MOVETO:
 357                 /* Checking SEG_MOVETO coordinates if they are out of the
 358                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 359                  * and Infinity values. Skipping next path segment in case of
 360                  * invalid data.
 361                  */
 362                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 363                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 364                 {
 365                     pc2d.moveTo(coords[0], coords[1]);
 366                     subpathStarted = true;
 367                 }
 368                 break;
 369             case PathIterator.SEG_LINETO:
 370                 /* Checking SEG_LINETO coordinates if they are out of the
 371                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 372                  * and Infinity values. Ignoring current path segment in case
 373                  * of invalid data. If segment is skipped its endpoint
 374                  * (if valid) is used to begin new subpath.
 375                  */
 376                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 377                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 378                 {
 379                     if (subpathStarted) {
 380                         pc2d.lineTo(coords[0], coords[1]);
 381                     } else {
 382                         pc2d.moveTo(coords[0], coords[1]);
 383                         subpathStarted = true;
 384                     }
 385                 }
 386                 break;
 387             case PathIterator.SEG_QUADTO:
 388                 // Quadratic curves take two points
 389                 /* Checking SEG_QUADTO coordinates if they are out of the
 390                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 391                  * and Infinity values. Ignoring current path segment in case
 392                  * of invalid endpoints's data. Equivalent to the SEG_LINETO
 393                  * if endpoint coordinates are valid but there are invalid data
 394                  * among other coordinates
 395                  */
 396                 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
 397                     coords[3] < UPPER_BND && coords[3] > LOWER_BND)
 398                 {
 399                     if (subpathStarted) {
 400                         if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 401                             coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 402                         {
 403                             pc2d.quadTo(coords[0], coords[1],
 404                                         coords[2], coords[3]);
 405                         } else {
 406                             pc2d.lineTo(coords[2], coords[3]);
 407                         }
 408                     } else {
 409                         pc2d.moveTo(coords[2], coords[3]);
 410                         subpathStarted = true;
 411                     }
 412                 }
 413                 break;
 414             case PathIterator.SEG_CUBICTO:
 415                 // Cubic curves take three points
 416                 /* Checking SEG_CUBICTO coordinates if they are out of the
 417                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 418                  * and Infinity values. Ignoring current path segment in case
 419                  * of invalid endpoints's data. Equivalent to the SEG_LINETO
 420                  * if endpoint coordinates are valid but there are invalid data
 421                  * among other coordinates
 422                  */
 423                 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
 424                     coords[5] < UPPER_BND && coords[5] > LOWER_BND)
 425                 {
 426                     if (subpathStarted) {
 427                         if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 428                             coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
 429                             coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
 430                             coords[3] < UPPER_BND && coords[3] > LOWER_BND)
 431                         {
 432                             pc2d.curveTo(coords[0], coords[1],
 433                                          coords[2], coords[3],
 434                                          coords[4], coords[5]);
 435                         } else {
 436                             pc2d.lineTo(coords[4], coords[5]);
 437                         }
 438                     } else {
 439                         pc2d.moveTo(coords[4], coords[5]);
 440                         subpathStarted = true;
 441                     }
 442                 }
 443                 break;
 444             case PathIterator.SEG_CLOSE:
 445                 if (subpathStarted) {
 446                     pc2d.closePath();
 447                     // do not set subpathStarted to false
 448                     // in case of missing moveTo() after close()
 449                 }
 450                 break;
 451             default:
 452             }
 453         }
 454         pc2d.pathDone();
 455 
 456         // mark context as CLEAN:
 457         rdrCtx.dirty = false;
 458     }
 459 
 460     private static void feedConsumer(final DRendererContext rdrCtx,
 461                                      final Path2D p2d,
 462                                      final BaseTransform xform,
 463                                      DPathConsumer2D pc2d)
 464     {
 465         if (MarlinConst.USE_PATH_SIMPLIFIER) {
 466             // Use path simplifier at the first step
 467             // to remove useless points
 468             pc2d = rdrCtx.pathSimplifier.init(pc2d);
 469         }
 470 
 471         // mark context as DIRTY:
 472         rdrCtx.dirty = true;
 473 
 474         final float[] coords = rdrCtx.float6;
 475 
 476         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 477         // - removed skip flag = !subpathStarted
 478         // - removed pathClosed (ie subpathStarted not set to false)
 479         boolean subpathStarted = false;
 480 
 481         final float[] pCoords = p2d.getFloatCoordsNoClone();
 482         final byte[] pTypes = p2d.getCommandsNoClone();
 483         final int nsegs = p2d.getNumCommands();
 484 
 485         for (int i = 0, coff = 0; i < nsegs; i++) {
 486             switch (pTypes[i]) {
 487             case PathIterator.SEG_MOVETO:
 488                 if (xform == null) {
 489                     coords[0] = pCoords[coff];
 490                     coords[1] = pCoords[coff+1];
 491                 } else {
 492                     xform.transform(pCoords, coff, coords, 0, 1);
 493                 }
 494                 coff += 2;
 495                 /* Checking SEG_MOVETO coordinates if they are out of the
 496                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 497                  * and Infinity values. Skipping next path segment in case of
 498                  * invalid data.
 499                  */
 500                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 501                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 502                 {
 503                     pc2d.moveTo(coords[0], coords[1]);
 504                     subpathStarted = true;
 505                 }
 506                 break;
 507             case PathIterator.SEG_LINETO:
 508                 if (xform == null) {
 509                     coords[0] = pCoords[coff];
 510                     coords[1] = pCoords[coff+1];
 511                 } else {
 512                     xform.transform(pCoords, coff, coords, 0, 1);
 513                 }
 514                 coff += 2;
 515                 /* Checking SEG_LINETO coordinates if they are out of the
 516                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 517                  * and Infinity values. Ignoring current path segment in case
 518                  * of invalid data. If segment is skipped its endpoint
 519                  * (if valid) is used to begin new subpath.
 520                  */
 521                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 522                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 523                 {
 524                     if (subpathStarted) {
 525                         pc2d.lineTo(coords[0], coords[1]);
 526                     } else {
 527                         pc2d.moveTo(coords[0], coords[1]);
 528                         subpathStarted = true;
 529                     }
 530                 }
 531                 break;
 532             case PathIterator.SEG_QUADTO:
 533                 if (xform == null) {
 534                     coords[0] = pCoords[coff];
 535                     coords[1] = pCoords[coff+1];
 536                     coords[2] = pCoords[coff+2];
 537                     coords[3] = pCoords[coff+3];
 538                 } else {
 539                     xform.transform(pCoords, coff, coords, 0, 2);
 540                 }
 541                 coff += 4;
 542                 // Quadratic curves take two points
 543                 /* Checking SEG_QUADTO coordinates if they are out of the
 544                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 545                  * and Infinity values. Ignoring current path segment in case
 546                  * of invalid endpoints's data. Equivalent to the SEG_LINETO
 547                  * if endpoint coordinates are valid but there are invalid data
 548                  * among other coordinates
 549                  */
 550                 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
 551                     coords[3] < UPPER_BND && coords[3] > LOWER_BND)
 552                 {
 553                     if (subpathStarted) {
 554                         if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 555                             coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 556                         {
 557                             pc2d.quadTo(coords[0], coords[1],
 558                                         coords[2], coords[3]);
 559                         } else {
 560                             pc2d.lineTo(coords[2], coords[3]);
 561                         }
 562                     } else {
 563                         pc2d.moveTo(coords[2], coords[3]);
 564                         subpathStarted = true;
 565                     }
 566                 }
 567                 break;
 568             case PathIterator.SEG_CUBICTO:
 569                 if (xform == null) {
 570                     coords[0] = pCoords[coff];
 571                     coords[1] = pCoords[coff+1];
 572                     coords[2] = pCoords[coff+2];
 573                     coords[3] = pCoords[coff+3];
 574                     coords[4] = pCoords[coff+4];
 575                     coords[5] = pCoords[coff+5];
 576                 } else {
 577                     xform.transform(pCoords, coff, coords, 0, 3);
 578                 }
 579                 coff += 6;
 580                 // Cubic curves take three points
 581                 /* Checking SEG_CUBICTO coordinates if they are out of the
 582                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 583                  * and Infinity values. Ignoring current path segment in case
 584                  * of invalid endpoints's data. Equivalent to the SEG_LINETO
 585                  * if endpoint coordinates are valid but there are invalid data
 586                  * among other coordinates
 587                  */
 588                 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
 589                     coords[5] < UPPER_BND && coords[5] > LOWER_BND)
 590                 {
 591                     if (subpathStarted) {
 592                         if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 593                             coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
 594                             coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
 595                             coords[3] < UPPER_BND && coords[3] > LOWER_BND)
 596                         {
 597                             pc2d.curveTo(coords[0], coords[1],
 598                                          coords[2], coords[3],
 599                                          coords[4], coords[5]);
 600                         } else {
 601                             pc2d.lineTo(coords[4], coords[5]);
 602                         }
 603                     } else {
 604                         pc2d.moveTo(coords[4], coords[5]);
 605                         subpathStarted = true;
 606                     }
 607                 }
 608                 break;
 609             case PathIterator.SEG_CLOSE:
 610                 if (subpathStarted) {
 611                     pc2d.closePath();
 612                     // do not set subpathStarted to false
 613                     // in case of missing moveTo() after close()
 614                 }
 615                 break;
 616             default:
 617             }
 618         }
 619         pc2d.pathDone();
 620 
 621         // mark context as CLEAN:
 622         rdrCtx.dirty = false;
 623     }
 624 }