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