1 /*
   2  * Copyright (c) 2010, 2014, 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 javafx.scene.shape;
  27 
  28 import javafx.beans.Observable;
  29 import javafx.beans.property.BooleanProperty;
  30 import javafx.beans.property.DoubleProperty;
  31 import javafx.beans.property.ObjectProperty;
  32 import javafx.beans.property.Property;
  33 import javafx.collections.ListChangeListener.Change;
  34 import javafx.collections.ObservableList;
  35 import javafx.css.CssMetaData;
  36 import javafx.css.Styleable;
  37 import javafx.css.StyleableBooleanProperty;
  38 import javafx.css.StyleableDoubleProperty;
  39 import javafx.css.StyleableObjectProperty;
  40 import javafx.css.StyleableProperty;
  41 import javafx.scene.Node;
  42 import javafx.scene.paint.Color;
  43 import javafx.scene.paint.Paint;
  44 import java.util.ArrayList;
  45 import java.util.Collections;
  46 import java.util.List;
  47 import com.sun.javafx.util.Utils;
  48 import com.sun.javafx.beans.event.AbstractNotifyListener;
  49 import com.sun.javafx.collections.TrackableObservableList;
  50 import com.sun.javafx.css.converters.BooleanConverter;
  51 import com.sun.javafx.css.converters.EnumConverter;
  52 import com.sun.javafx.css.converters.PaintConverter;
  53 import com.sun.javafx.css.converters.SizeConverter;
  54 import com.sun.javafx.geom.Area;
  55 import com.sun.javafx.geom.BaseBounds;
  56 import com.sun.javafx.geom.PathIterator;
  57 import com.sun.javafx.geom.transform.Affine3D;
  58 import com.sun.javafx.geom.transform.BaseTransform;
  59 import com.sun.javafx.jmx.MXNodeAlgorithm;
  60 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  61 import com.sun.javafx.scene.DirtyBits;
  62 import com.sun.javafx.sg.prism.NGNode;
  63 import com.sun.javafx.sg.prism.NGShape;
  64 import com.sun.javafx.tk.Toolkit;
  65 import java.lang.ref.Reference;
  66 import java.lang.ref.WeakReference;
  67 
  68 
  69 /**
  70  * The {@code Shape} class provides definitions of common properties for
  71  * objects that represent some form of geometric shape.  These properties
  72  * include:
  73  * <ul>
  74  * <li>The {@link Paint} to be applied to the fillable interior of the
  75  * shape (see {@link #setFill setFill}).
  76  * <li>The {@link Paint} to be applied to stroke the outline of the
  77  * shape (see {@link #setStroke setStroke}).
  78  * <li>The decorative properties of the stroke, including:
  79  * <ul>
  80  * <li>The width of the border stroke.
  81  * <li>Whether the border is drawn as an exterior padding to the edges
  82  * of the shape, as an interior edging that follows the inside of the border,
  83  * or as a wide path that follows along the border straddling it equally
  84  * both inside and outside (see {@link StrokeType}).
  85  * <li>Decoration styles for the joins between path segments and the
  86  * unclosed ends of paths.
  87  * <li>Dashing attributes.
  88  * </ul>
  89  * </ul>
  90  * <h4>Interaction with coordinate systems</h4>
  91  * Most nodes tend to have only integer translations applied to them and
  92  * quite often they are defined using integer coordinates as well.  For
  93  * this common case, fills of shapes with straight line edges tend to be
  94  * crisp since they line up with the cracks between pixels that fall on
  95  * integer device coordinates and thus tend to naturally cover entire pixels.
  96  * <p>
  97  * On the other hand, stroking those same shapes can often lead to fuzzy
  98  * outlines because the default stroking attributes specify both that the
  99  * default stroke width is 1.0 coordinates which often maps to exactly 1
 100  * device pixel and also that the stroke should straddle the border of the
 101  * shape, falling half on either side of the border.
 102  * Since the borders in many common shapes tend to fall directly on integer
 103  * coordinates and those integer coordinates often map precisely to integer
 104  * device locations, the borders tend to result in 50% coverage over the
 105  * pixel rows and columns on either side of the border of the shape rather
 106  * than 100% coverage on one or the other.  Thus, fills may typically be
 107  * crisp, but strokes are often fuzzy.
 108  * <p>
 109  * Two common solutions to avoid these fuzzy outlines are to use wider
 110  * strokes that cover more pixels completely - typically a stroke width of
 111  * 2.0 will achieve this if there are no scale transforms in effect - or
 112  * to specify either the {@link StrokeType#INSIDE} or {@link StrokeType#OUTSIDE}
 113  * stroke styles - which will bias the default single unit stroke onto one
 114  * of the full pixel rows or columns just inside or outside the border of
 115  * the shape.
 116  * @since JavaFX 2.0
 117  */
 118 public abstract class Shape extends Node {
 119 
 120     /**
 121      * @treatAsPrivate implementation detail
 122      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 123      */
 124     @Deprecated
 125     @Override
 126     protected NGNode impl_createPeer() {
 127         throw new AssertionError(
 128             "Subclasses of Shape must implement impl_createPGNode");
 129     }
 130 
 131     StrokeLineJoin convertLineJoin(StrokeLineJoin t) {
 132         return t;
 133     }
 134 
 135     public final void setStrokeType(StrokeType value) {
 136         strokeTypeProperty().set(value);
 137     }
 138 
 139     public final StrokeType getStrokeType() {
 140         return (strokeAttributes == null) ? DEFAULT_STROKE_TYPE
 141                                           : strokeAttributes.getType();
 142     }
 143 
 144     /**
 145      * Defines the direction (inside, centered, or outside) that the strokeWidth
 146      * is applied to the boundary of the shape.
 147      *
 148      * <p>
 149      * The image shows a shape without stroke and with a thick stroke applied
 150      * inside, centered and outside.
 151      * </p><p>
 152      * <img src="doc-files/stroketype.png"/>
 153      * </p>
 154      *
 155      * @see StrokeType
 156      * @defaultValue CENTERED
 157      */
 158     public final ObjectProperty<StrokeType> strokeTypeProperty() {
 159         return getStrokeAttributes().typeProperty();
 160     }
 161 
 162     public final void setStrokeWidth(double value) {
 163         strokeWidthProperty().set(value);
 164     }
 165 
 166     public final double getStrokeWidth() {
 167         return (strokeAttributes == null) ? DEFAULT_STROKE_WIDTH
 168                                           : strokeAttributes.getWidth();
 169     }
 170 
 171     /**
 172      * Defines a square pen line width. A value of 0.0 specifies a hairline
 173      * stroke. A value of less than 0.0 will be treated as 0.0.
 174      *
 175      * @defaultValue 1.0
 176      */
 177     public final DoubleProperty strokeWidthProperty() {
 178         return getStrokeAttributes().widthProperty();
 179     }
 180 
 181     public final void setStrokeLineJoin(StrokeLineJoin value) {
 182         strokeLineJoinProperty().set(value);
 183     }
 184 
 185     public final StrokeLineJoin getStrokeLineJoin() {
 186         return (strokeAttributes == null)
 187                 ? DEFAULT_STROKE_LINE_JOIN
 188                 : strokeAttributes.getLineJoin();
 189     }
 190 
 191     /**
 192      * Defines the decoration applied where path segments meet.
 193      * The value must have one of the following values:
 194      * {@code StrokeLineJoin.MITER}, {@code StrokeLineJoin.BEVEL},
 195      * and {@code StrokeLineJoin.ROUND}. The image shows a shape
 196      * using the values in the mentioned order.
 197      * </p><p>
 198      * <img src="doc-files/strokelinejoin.png"/>
 199      * </p>
 200      *
 201      * @see StrokeLineJoin
 202      * @defaultValue MITER
 203      */
 204     public final ObjectProperty<StrokeLineJoin> strokeLineJoinProperty() {
 205         return getStrokeAttributes().lineJoinProperty();
 206     }
 207 
 208     public final void setStrokeLineCap(StrokeLineCap value) {
 209         strokeLineCapProperty().set(value);
 210     }
 211 
 212     public final StrokeLineCap getStrokeLineCap() {
 213         return (strokeAttributes == null) ? DEFAULT_STROKE_LINE_CAP
 214                                           : strokeAttributes.getLineCap();
 215     }
 216 
 217     /**
 218      * The end cap style of this {@code Shape} as one of the following
 219      * values that define possible end cap styles:
 220      * {@code StrokeLineCap.BUTT}, {@code StrokeLineCap.ROUND},
 221      * and  {@code StrokeLineCap.SQUARE}. The image shows a line
 222      * using the values in the mentioned order.
 223      * </p><p>
 224      * <img src="doc-files/strokelinecap.png"/>
 225      * </p>
 226      *
 227      * @see StrokeLineCap
 228      * @defaultValue SQUARE
 229      */
 230     public final ObjectProperty<StrokeLineCap> strokeLineCapProperty() {
 231         return getStrokeAttributes().lineCapProperty();
 232     }
 233 
 234     public final void setStrokeMiterLimit(double value) {
 235         strokeMiterLimitProperty().set(value);
 236     }
 237 
 238     public final double getStrokeMiterLimit() {
 239         return (strokeAttributes == null) ? DEFAULT_STROKE_MITER_LIMIT
 240                                           : strokeAttributes.getMiterLimit();
 241     }
 242 
 243     /**
 244      * Defines the limit for the {@code StrokeLineJoin.MITER} line join style.
 245      * A value of less than 1.0 will be treated as 1.0.
 246      *
 247      * <p>
 248      * The image demonstrates the behavior. Miter length ({@code A}) is computed
 249      * as the distance of the most inside point to the most outside point of
 250      * the joint, with the stroke width as a unit. If the miter length is bigger
 251      * than the given miter limit, the miter is cut at the edge of the shape
 252      * ({@code B}). For the situation in the image it means that the miter
 253      * will be cut at {@code B} for limit values less than {@code 4.65}.
 254      * </p><p>
 255      * <img src="doc-files/strokemiterlimit.png"/>
 256      * </p>
 257      *
 258      * @defaultValue 10.0
 259      */
 260     public final DoubleProperty strokeMiterLimitProperty() {
 261         return getStrokeAttributes().miterLimitProperty();
 262     }
 263 
 264     public final void setStrokeDashOffset(double value) {
 265         strokeDashOffsetProperty().set(value);
 266     }
 267 
 268     public final double getStrokeDashOffset() {
 269         return (strokeAttributes == null) ? DEFAULT_STROKE_DASH_OFFSET
 270                                           : strokeAttributes.getDashOffset();
 271     }
 272 
 273     /**
 274      * Defines a distance specified in user coordinates that represents
 275      * an offset into the dashing pattern. In other words, the dash phase
 276      * defines the point in the dashing pattern that will correspond
 277      * to the beginning of the stroke.
 278      *
 279      * <p>
 280      * The image shows a stroke with dash array {@code [25, 20, 5, 20]} and
 281      * a stroke with the same pattern and offset {@code 45} which shifts
 282      * the pattern about the length of the first dash segment and
 283      * the following space.
 284      * </p><p>
 285      * <img src="doc-files/strokedashoffset.png"/>
 286      * </p>
 287      *
 288      * @defaultValue 0
 289      */
 290     public final DoubleProperty strokeDashOffsetProperty() {
 291         return getStrokeAttributes().dashOffsetProperty();
 292     }
 293 
 294     /**
 295      * Defines the array representing the lengths of the dash segments.
 296      * Alternate entries in the array represent the user space lengths
 297      * of the opaque and transparent segments of the dashes.
 298      * As the pen moves along the outline of the {@code Shape} to be stroked,
 299      * the user space distance that the pen travels is accumulated.
 300      * The distance value is used to index into the dash array.
 301      * The pen is opaque when its current cumulative distance maps
 302      * to an even element of the dash array (counting from {@code 0}) and
 303      * transparent otherwise.
 304      * <p>
 305      * An empty strokeDashArray indicates a solid line with no spaces.
 306      * An odd length strokeDashArray behaves the same as an even length
 307      * array constructed by implicitly repeating the indicated odd length
 308      * array twice in succession ({@code [20, 5, 15]} behaves as if it
 309      * were {@code [20, 5, 15, 20, 5, 15]}).
 310      * <p>
 311      * Note that each dash segment will be capped by the decoration specified
 312      * by the current stroke line cap.
 313      *
 314      * <p>
 315      * The image shows a shape with stroke dash array {@code [25, 20, 5, 20]}
 316      * and 3 different values for the stroke line cap:
 317      * {@code StrokeLineCap.BUTT}, {@code StrokeLineCap.SQUARE} (the default),
 318      * and {@code StrokeLineCap.ROUND}
 319      * </p><p>
 320      * <img src="doc-files/strokedasharray.png"/>
 321      * </p>
 322      *
 323      * @defaultValue empty
 324      */
 325     public final ObservableList<Double> getStrokeDashArray() {        
 326         return getStrokeAttributes().dashArrayProperty();
 327     }
 328 
 329     private NGShape.Mode computeMode() {
 330         if (getFill() != null && getStroke() != null) {
 331             return NGShape.Mode.STROKE_FILL;
 332         } else if (getFill() != null) {
 333             return NGShape.Mode.FILL;
 334         } else if (getStroke() != null) {
 335             return NGShape.Mode.STROKE;
 336         } else {
 337             return NGShape.Mode.EMPTY;
 338         }
 339     }
 340 
 341     /**
 342      * @treatAsPrivate implementation detail
 343      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 344      */
 345     @Deprecated
 346     protected NGShape.Mode impl_mode = NGShape.Mode.FILL;
 347 
 348     private void checkModeChanged() {
 349         NGShape.Mode newMode = computeMode();
 350         if (impl_mode != newMode) {
 351             impl_mode = newMode;
 352 
 353             impl_markDirty(DirtyBits.SHAPE_MODE);
 354             impl_geomChanged();
 355         }
 356     }
 357 
 358     /**
 359      * Defines parameters to fill the interior of an {@code Shape}
 360      * using the settings of the {@code Paint} context.
 361      * The default value is {@code Color.BLACK} for all shapes except
 362      * Line, Polyline, and Path. The default value is {@code null} for
 363      * those shapes.
 364      */
 365     private ObjectProperty<Paint> fill;
 366 
 367 
 368     public final void setFill(Paint value) {       
 369         fillProperty().set(value);
 370     }
 371    
 372     public final Paint getFill() {
 373         return fill == null ? Color.BLACK : fill.get();
 374     }
 375 
 376     Paint old_fill;
 377     public final ObjectProperty<Paint> fillProperty() {              
 378         if (fill == null) {
 379             fill = new StyleableObjectProperty<Paint>(Color.BLACK) {
 380                 
 381                 boolean needsListener = false;
 382                 
 383                 @Override public void invalidated() {
 384                     
 385                     Paint _fill = get();
 386                     
 387                     if (needsListener) {
 388                         Toolkit.getPaintAccessor().
 389                                 removeListener(old_fill, platformImageChangeListener);
 390                     }
 391                     needsListener = _fill != null &&
 392                             Toolkit.getPaintAccessor().isMutable(_fill);
 393                     old_fill = _fill;
 394                     
 395                     if (needsListener) {
 396                         Toolkit.getPaintAccessor().
 397                                 addListener(_fill, platformImageChangeListener);
 398                     }       
 399                     
 400                     impl_markDirty(DirtyBits.SHAPE_FILL);
 401                     checkModeChanged();
 402                 }
 403                 
 404                 @Override
 405                 public CssMetaData<Shape,Paint> getCssMetaData() {
 406                     return StyleableProperties.FILL;
 407                 }
 408 
 409                 @Override
 410                 public Object getBean() {
 411                     return Shape.this;
 412                 }
 413 
 414                 @Override
 415                 public String getName() {
 416                     return "fill";
 417                 }
 418             };
 419         }
 420         return fill;
 421     }
 422 
 423     /**
 424      * Defines parameters of a stroke that is drawn around the outline of
 425      * a {@code Shape} using the settings of the specified {@code Paint}.
 426      * The default value is {@code null} for all shapes except
 427      * Line, Polyline, and Path. The default value is {@code Color.BLACK} for
 428      * those shapes.
 429      */
 430     private ObjectProperty<Paint> stroke;
 431 
 432 
 433     public final void setStroke(Paint value) {
 434         strokeProperty().set(value);
 435     }
 436 
 437     private final AbstractNotifyListener platformImageChangeListener =
 438             new AbstractNotifyListener() {
 439         @Override
 440         public void invalidated(Observable valueModel) {
 441             impl_markDirty(DirtyBits.SHAPE_FILL);
 442             impl_markDirty(DirtyBits.SHAPE_STROKE);
 443             impl_geomChanged();
 444             checkModeChanged();           
 445         }
 446     };
 447        
 448     public final Paint getStroke() {
 449         return stroke == null ? null : stroke.get();
 450     }
 451 
 452     Paint old_stroke;
 453     public final ObjectProperty<Paint> strokeProperty() {
 454         if (stroke == null) {
 455             stroke = new StyleableObjectProperty<Paint>() {
 456 
 457                 boolean needsListener = false;
 458 
 459                 @Override public void invalidated() {
 460 
 461                     Paint _stroke = get();
 462 
 463                     if (needsListener) {
 464                         Toolkit.getPaintAccessor().
 465                                 removeListener(old_stroke, platformImageChangeListener);
 466                     }
 467                     needsListener = _stroke != null &&
 468                             Toolkit.getPaintAccessor().isMutable(_stroke);
 469                     old_stroke = _stroke;
 470 
 471                     if (needsListener) {
 472                         Toolkit.getPaintAccessor().
 473                                 addListener(_stroke, platformImageChangeListener);
 474                     }
 475 
 476                     impl_markDirty(DirtyBits.SHAPE_STROKE);
 477                     checkModeChanged();
 478                 }
 479                 
 480                 @Override
 481                 public CssMetaData<Shape,Paint> getCssMetaData() {
 482                     return StyleableProperties.STROKE;
 483                 }
 484 
 485                 @Override
 486                 public Object getBean() {
 487                     return Shape.this;
 488                 }
 489 
 490                 @Override
 491                 public String getName() {
 492                     return "stroke";
 493                 }
 494             };
 495         }
 496         return stroke;
 497     }
 498 
 499     /**
 500      * Defines whether antialiasing hints are used or not for this {@code Shape}.
 501      * If the value equals true the rendering hints are applied.
 502      *
 503      * @defaultValue true
 504      */
 505     private BooleanProperty smooth;
 506 
 507 
 508     public final void setSmooth(boolean value) {
 509         smoothProperty().set(value);
 510     }
 511 
 512     public final boolean isSmooth() {
 513         return smooth == null ? true : smooth.get();
 514     }
 515 
 516     public final BooleanProperty smoothProperty() {
 517         if (smooth == null) {
 518             smooth = new StyleableBooleanProperty(true) {
 519 
 520                 @Override
 521                 public void invalidated() {
 522                     impl_markDirty(DirtyBits.NODE_SMOOTH);
 523                 }
 524                 
 525                 @Override
 526                 public CssMetaData<Shape,Boolean> getCssMetaData() {
 527                     return StyleableProperties.SMOOTH;
 528                 }
 529 
 530                 @Override
 531                 public Object getBean() {
 532                     return Shape.this;
 533                 }
 534 
 535                 @Override
 536                 public String getName() {
 537                     return "smooth";
 538                 }
 539             };
 540         }
 541         return smooth;
 542     }
 543 
 544     /***************************************************************************
 545      *                                                                         *
 546      *                         Stylesheet Handling                             *
 547      *                                                                         *
 548      **************************************************************************/
 549 
 550     /** 
 551      * Some sub-class of Shape, such as {@link Line}, override the
 552      * default value for the {@link Shape#fill} property. This allows
 553      * CSS to get the correct initial value.
 554      * @treatAsPrivate Implementation detail
 555      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 556      */
 557     @Deprecated
 558     protected Paint impl_cssGetFillInitialValue() {
 559         return Color.BLACK;
 560     }    
 561     
 562     /** 
 563      * Some sub-class of Shape, such as {@link Line}, override the
 564      * default value for the {@link Shape#stroke} property. This allows
 565      * CSS to get the correct initial value.
 566      * @treatAsPrivate Implementation detail
 567      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 568      */
 569     @Deprecated
 570     protected Paint impl_cssGetStrokeInitialValue() {
 571         return null;
 572     }    
 573     
 574 
 575     /**
 576       * Super-lazy instantiation pattern from Bill Pugh.
 577       * @treatAsPrivate implementation detail
 578       */
 579      private static class StyleableProperties {
 580 
 581         /**
 582         * @css -fx-fill: <a href="../doc-files/cssref.html#typepaint">&lt;paint&gt;</a>
 583         * @see Shape#fill
 584         */
 585         private static final CssMetaData<Shape,Paint> FILL =
 586             new CssMetaData<Shape,Paint>("-fx-fill", 
 587                 PaintConverter.getInstance(), Color.BLACK) {
 588 
 589             @Override
 590             public boolean isSettable(Shape node) {
 591                 return node.fill == null || !node.fill.isBound();
 592             }
 593 
 594             @Override
 595             public StyleableProperty<Paint> getStyleableProperty(Shape node) {
 596                 return (StyleableProperty<Paint>)node.fillProperty();
 597             }
 598 
 599             @Override
 600             public Paint getInitialValue(Shape node) {
 601                 // Some shapes have a different initial value for fill. 
 602                 // Give a way to have them return the correct initial value.
 603                 return node.impl_cssGetFillInitialValue();
 604             }
 605             
 606         };
 607 
 608         /**
 609         * @css -fx-smooth: <a href="../doc-files/cssref.html#typeboolean">&lt;boolean&gt;</a>
 610         * @see Shape#smooth
 611         */
 612         private static final CssMetaData<Shape,Boolean> SMOOTH =
 613             new CssMetaData<Shape,Boolean>("-fx-smooth", 
 614                 BooleanConverter.getInstance(), Boolean.TRUE) {
 615 
 616             @Override
 617             public boolean isSettable(Shape node) {
 618                 return node.smooth == null || !node.smooth.isBound();
 619             }
 620 
 621             @Override
 622             public StyleableProperty<Boolean> getStyleableProperty(Shape node) {
 623                 return (StyleableProperty<Boolean>)node.smoothProperty();
 624             }
 625 
 626         };
 627 
 628         /**
 629         * @css -fx-stroke: <a href="../doc-files/cssref.html#typepaint">&lt;paint&gt;</a>
 630         * @see Shape#stroke
 631         */    
 632         private static final CssMetaData<Shape,Paint> STROKE =
 633             new CssMetaData<Shape,Paint>("-fx-stroke", 
 634                 PaintConverter.getInstance()) {
 635 
 636             @Override
 637             public boolean isSettable(Shape node) {
 638                 return node.stroke == null || !node.stroke.isBound();
 639             }
 640 
 641             @Override
 642             public StyleableProperty<Paint> getStyleableProperty(Shape node) {
 643                 return (StyleableProperty<Paint>)node.strokeProperty();
 644             }
 645             
 646             @Override
 647             public Paint getInitialValue(Shape node) {
 648                 // Some shapes have a different initial value for stroke. 
 649                 // Give a way to have them return the correct initial value.
 650                 return node.impl_cssGetStrokeInitialValue();
 651             }
 652             
 653 
 654         };
 655 
 656         /**
 657         * @css -fx-stroke-dash-array: <a href="#typesize" class="typelink">&lt;size&gt;</a>
 658         *                    [<a href="#typesize" class="typelink">&lt;size&gt;</a>]+
 659         * <p>
 660         * Note:
 661         * Because {@link StrokeAttributes#dashArray} is not itself a 
 662         * {@link Property}, 
 663         * the <code>getProperty()</code> method of this CssMetaData 
 664         * returns the {@link StrokeAttributes#dashArray} wrapped in an
 665         * {@link ObjectProperty}. This is inconsistent with other
 666         * StyleableProperties which return the actual {@link Property}. 
 667         * </p>
 668         * @see StrokeAttributes#dashArray
 669         */    
 670         private static final CssMetaData<Shape,Number[]> STROKE_DASH_ARRAY =
 671             new CssMetaData<Shape,Number[]>("-fx-stroke-dash-array",
 672                 SizeConverter.SequenceConverter.getInstance(), 
 673                 new Double[0]) {
 674 
 675             @Override
 676             public boolean isSettable(Shape node) {
 677                 return true;
 678             }
 679 
 680             @Override
 681             public StyleableProperty<Number[]> getStyleableProperty(final Shape node) {
 682                 return (StyleableProperty<Number[]>)node.getStrokeAttributes().cssDashArrayProperty();
 683             }
 684 
 685         };
 686 
 687         /**
 688         * @css -fx-stroke-dash-offset: <a href="#typesize" class="typelink">&lt;size&gt;</a>
 689         * @see #strokeDashOffsetProperty() 
 690         */        
 691         private static final CssMetaData<Shape,Number> STROKE_DASH_OFFSET =
 692             new CssMetaData<Shape,Number>("-fx-stroke-dash-offset",
 693                 SizeConverter.getInstance(), 0.0) {
 694 
 695             @Override
 696             public boolean isSettable(Shape node) {
 697                 return node.strokeAttributes == null ||
 698                         node.strokeAttributes.canSetDashOffset();
 699             }
 700 
 701             @Override
 702             public StyleableProperty<Number> getStyleableProperty(Shape node) {
 703                 return (StyleableProperty<Number>)node.strokeDashOffsetProperty();
 704             }
 705 
 706         };
 707 
 708         /**
 709         * @css -fx-stroke-line-cap: [ square | butt | round ]
 710         * @see #strokeLineCapProperty() 
 711         */        
 712         private static final CssMetaData<Shape,StrokeLineCap> STROKE_LINE_CAP =
 713             new CssMetaData<Shape,StrokeLineCap>("-fx-stroke-line-cap",
 714                 new EnumConverter<StrokeLineCap>(StrokeLineCap.class), 
 715                 StrokeLineCap.SQUARE) {
 716 
 717             @Override
 718             public boolean isSettable(Shape node) {
 719                 return node.strokeAttributes == null ||
 720                         node.strokeAttributes.canSetLineCap();
 721             }
 722 
 723             @Override
 724             public StyleableProperty<StrokeLineCap> getStyleableProperty(Shape node) {
 725                 return (StyleableProperty<StrokeLineCap>)node.strokeLineCapProperty();
 726             }
 727 
 728         };
 729 
 730         /**
 731         * @css -fx-stroke-line-join: [ miter | bevel | round ]
 732         * @see #strokeLineJoinProperty() 
 733         */        
 734         private static final CssMetaData<Shape,StrokeLineJoin> STROKE_LINE_JOIN =
 735             new CssMetaData<Shape,StrokeLineJoin>("-fx-stroke-line-join",
 736                 new EnumConverter<StrokeLineJoin>(StrokeLineJoin.class), 
 737                 StrokeLineJoin.MITER) {
 738 
 739             @Override
 740             public boolean isSettable(Shape node) {
 741                 return node.strokeAttributes == null ||
 742                         node.strokeAttributes.canSetLineJoin();
 743             }
 744 
 745             @Override
 746             public StyleableProperty<StrokeLineJoin> getStyleableProperty(Shape node) {
 747                 return (StyleableProperty<StrokeLineJoin>)node.strokeLineJoinProperty();
 748             }
 749 
 750         };
 751 
 752         /**
 753         * @css -fx-stroke-type: [ inside | outside | centered ]
 754         * @see #strokeTypeProperty() 
 755         */        
 756         private static final CssMetaData<Shape,StrokeType> STROKE_TYPE =
 757             new CssMetaData<Shape,StrokeType>("-fx-stroke-type",
 758                 new EnumConverter<StrokeType>(StrokeType.class), 
 759                 StrokeType.CENTERED) {
 760 
 761             @Override
 762             public boolean isSettable(Shape node) {
 763                 return node.strokeAttributes == null ||
 764                         node.strokeAttributes.canSetType();
 765             }
 766 
 767             @Override
 768             public StyleableProperty<StrokeType> getStyleableProperty(Shape node) {
 769                 return (StyleableProperty<StrokeType>)node.strokeTypeProperty();
 770             }
 771 
 772 
 773         };
 774 
 775         /**
 776         * @css -fx-stroke-miter-limit: <a href="#typesize" class="typelink">&lt;size&gt;</a>
 777         * @see #strokeMiterLimitProperty() 
 778         */        
 779         private static final CssMetaData<Shape,Number> STROKE_MITER_LIMIT =
 780             new CssMetaData<Shape,Number>("-fx-stroke-miter-limit",
 781                 SizeConverter.getInstance(), 10.0) {
 782 
 783             @Override
 784             public boolean isSettable(Shape node) {
 785                 return node.strokeAttributes == null ||
 786                         node.strokeAttributes.canSetMiterLimit();
 787             }
 788 
 789             @Override
 790             public StyleableProperty<Number> getStyleableProperty(Shape node) {
 791                 return (StyleableProperty<Number>)node.strokeMiterLimitProperty();
 792             }
 793 
 794         };
 795 
 796         /**
 797         * @css -fx-stroke-width: <a href="#typesize" class="typelink">&lt;size&gt;</a>
 798         * @see #strokeWidthProperty() 
 799         */        
 800         private static final CssMetaData<Shape,Number> STROKE_WIDTH =
 801             new CssMetaData<Shape,Number>("-fx-stroke-width",
 802                 SizeConverter.getInstance(), 1.0) {
 803 
 804             @Override
 805             public boolean isSettable(Shape node) {
 806                 return node.strokeAttributes == null ||
 807                         node.strokeAttributes.canSetWidth();
 808             }
 809 
 810             @Override
 811             public StyleableProperty<Number> getStyleableProperty(Shape node) {
 812                 return (StyleableProperty<Number>)node.strokeWidthProperty();
 813             }
 814 
 815         };         
 816          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 817          static {
 818 
 819             final List<CssMetaData<? extends Styleable, ?>> styleables =
 820                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Node.getClassCssMetaData());
 821             styleables.add(FILL);
 822             styleables.add(SMOOTH);
 823             styleables.add(STROKE);
 824             styleables.add(STROKE_DASH_ARRAY);
 825             styleables.add(STROKE_DASH_OFFSET);
 826             styleables.add(STROKE_LINE_CAP);
 827             styleables.add(STROKE_LINE_JOIN);
 828             styleables.add(STROKE_TYPE);
 829             styleables.add(STROKE_MITER_LIMIT);
 830             styleables.add(STROKE_WIDTH);
 831             STYLEABLES = Collections.unmodifiableList(styleables);
 832          }
 833     }
 834 
 835     /**
 836      * @return The CssMetaData associated with this class, which may include the
 837      * CssMetaData of its super classes.
 838      * @since JavaFX 8.0
 839      */
 840     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 841         return StyleableProperties.STYLEABLES;
 842     }
 843 
 844     /**
 845      * {@inheritDoc}
 846      *
 847      * @since JavaFX 8.0
 848      */
 849     
 850     
 851     @Override
 852     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 853         return getClassCssMetaData();
 854     }
 855 
 856     /**
 857      * @treatAsPrivate implementation detail
 858      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 859      */
 860     @Deprecated
 861     @Override
 862     public BaseBounds impl_computeGeomBounds(BaseBounds bounds,
 863                                              BaseTransform tx) {
 864         return computeShapeBounds(bounds, tx, impl_configShape());
 865     }
 866 
 867     /**
 868      * @treatAsPrivate implementation detail
 869      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 870      */
 871     @Deprecated
 872     @Override
 873     protected boolean impl_computeContains(double localX, double localY) {
 874         return computeShapeContains(localX, localY, impl_configShape());
 875     }
 876 
 877     /**
 878      * @treatAsPrivate implementation detail
 879      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 880      */
 881     @Deprecated
 882     public abstract com.sun.javafx.geom.Shape impl_configShape();
 883 
 884     private static final double MIN_STROKE_WIDTH = 0.0f;
 885     private static final double MIN_STROKE_MITER_LIMIT = 1.0f;
 886 
 887     private void updatePGShape() {
 888         final NGShape peer = impl_getPeer();
 889         if (strokeAttributesDirty && (getStroke() != null)) {
 890             // set attributes of stroke only when stroke paint is not null
 891             final float[] pgDashArray =
 892                     (hasStrokeDashArray())
 893                             ? toPGDashArray(getStrokeDashArray())
 894                             : DEFAULT_PG_STROKE_DASH_ARRAY;
 895 
 896             peer.setDrawStroke(
 897                         (float)Utils.clampMin(getStrokeWidth(),
 898                                               MIN_STROKE_WIDTH),
 899                         getStrokeType(),
 900                         getStrokeLineCap(),
 901                         convertLineJoin(getStrokeLineJoin()),
 902                         (float)Utils.clampMin(getStrokeMiterLimit(),
 903                                               MIN_STROKE_MITER_LIMIT),
 904                         pgDashArray, (float)getStrokeDashOffset());
 905 
 906            strokeAttributesDirty = false;
 907         }
 908 
 909         if (impl_isDirty(DirtyBits.SHAPE_MODE)) {
 910             peer.setMode(impl_mode);
 911         }
 912 
 913         if (impl_isDirty(DirtyBits.SHAPE_FILL)) {
 914             Paint localFill = getFill();
 915             peer.setFillPaint(localFill == null ? null :
 916                     Toolkit.getPaintAccessor().getPlatformPaint(localFill));
 917         }
 918 
 919         if (impl_isDirty(DirtyBits.SHAPE_STROKE)) {
 920             Paint localStroke = getStroke();
 921             peer.setDrawPaint(localStroke == null ? null :
 922                     Toolkit.getPaintAccessor().getPlatformPaint(localStroke));
 923         }
 924 
 925         if (impl_isDirty(DirtyBits.NODE_SMOOTH)) {
 926             peer.setSmooth(isSmooth());
 927         }
 928     }
 929 
 930     /**
 931      * @treatAsPrivate implementation detail
 932      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 933      */
 934     @Deprecated
 935     @Override
 936     protected void impl_markDirty(DirtyBits dirtyBits) {
 937         final Runnable listener = shapeChangeListener != null ? shapeChangeListener.get() : null;
 938         if (listener != null && impl_isDirtyEmpty()) {
 939             listener.run();
 940         }
 941 
 942         super.impl_markDirty(dirtyBits);
 943     }
 944 
 945     private Reference<Runnable> shapeChangeListener;
 946 
 947     /**
 948      * @treatAsPrivate implementation detail
 949      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 950      */
 951     @Deprecated
 952     public void impl_setShapeChangeListener(Runnable listener) {
 953         if (shapeChangeListener != null) shapeChangeListener.clear();
 954         shapeChangeListener = listener != null ? new WeakReference(listener) : null;
 955     }
 956 
 957     /**
 958      * @treatAsPrivate implementation detail
 959      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 960      */
 961     @Deprecated
 962     @Override
 963     public void impl_updatePeer() {
 964         super.impl_updatePeer();
 965         updatePGShape();
 966     }
 967 
 968     /**
 969      * Helper function for rectangular shapes such as Rectangle and Ellipse
 970      * for computing their bounds.
 971      */
 972     BaseBounds computeBounds(BaseBounds bounds, BaseTransform tx,
 973                                    double upad, double dpad,
 974                                    double x, double y,
 975                                    double w, double h)
 976     {
 977         // if the w or h is < 0 then bounds is empty
 978         if (w < 0.0f || h < 0.0f) return bounds.makeEmpty();
 979 
 980         double x0 = x;
 981         double y0 = y;
 982         double x1 = w;
 983         double y1 = h;
 984         double _dpad = dpad;
 985         if (tx.isTranslateOrIdentity()) {
 986             x1 += x0;
 987             y1 += y0;
 988             if (tx.getType() == BaseTransform.TYPE_TRANSLATION) {
 989                 final double dx = tx.getMxt();
 990                 final double dy = tx.getMyt();
 991                 x0 += dx;
 992                 y0 += dy;
 993                 x1 += dx;
 994                 y1 += dy;
 995             }
 996             _dpad += upad;
 997         } else {
 998             x0 -= upad;
 999             y0 -= upad;
1000             x1 += upad*2;
1001             y1 += upad*2;
1002             // Each corner is transformed by an equation similar to:
1003             //     x' = x * mxx + y * mxy + mxt
1004             //     y' = x * myx + y * myy + myt
1005             // Since all of the corners are translated by mxt,myt we
1006             // can ignore them when doing the min/max calculations
1007             // and add them in once when we are done.  We then have
1008             // to do min/max operations on 4 points defined as:
1009             //     x' = x * mxx + y * mxy
1010             //     y' = x * myx + y * myy
1011             // Furthermore, the four corners that we will be transforming
1012             // are not four independent coordinates, they are in a
1013             // rectangular formation.  To that end, if we translated
1014             // the transform to x,y and scaled it by width,height then
1015             // we could compute the min/max of the unit rectangle 0,0,1x1.
1016             // The transform would then be adjusted as follows:
1017             // First, the translation to x,y only affects the mxt,myt
1018             // components of the transform which we can hold off on adding
1019             // until we are done with the min/max.  The adjusted translation
1020             // components would be:
1021             //     mxt' = x * mxx + y * mxy + mxt
1022             //     myt' = x * myx + y * myy + myt
1023             // Second, the scale affects the components as follows:
1024             //     mxx' = mxx * width
1025             //     mxy' = mxy * height
1026             //     myx' = myx * width
1027             //     myy' = myy * height
1028             // The min/max of that rectangle then degenerates to:
1029             //     x00' = 0 * mxx' + 0 * mxy' = 0
1030             //     y00' = 0 * myx' + 0 * myy' = 0
1031             //     x01' = 0 * mxx' + 1 * mxy' = mxy'
1032             //     y01' = 0 * myx' + 1 * myy' = myy'
1033             //     x10' = 1 * mxx' + 0 * mxy' = mxx'
1034             //     y10' = 1 * myx' + 0 * myy' = myx'
1035             //     x11' = 1 * mxx' + 1 * mxy' = mxx' + mxy'
1036             //     y11' = 1 * myx' + 1 * myy' = myx' + myy'
1037             double mxx = tx.getMxx();
1038             double mxy = tx.getMxy();
1039             double myx = tx.getMyx();
1040             double myy = tx.getMyy();
1041             // Computed translated translation components
1042             final double mxt = (x0 * mxx + y0 * mxy + tx.getMxt());
1043             final double myt = (x0 * myx + y0 * myy + tx.getMyt());
1044             // Scale non-translation components by w/h
1045             mxx *= x1;
1046             mxy *= y1;
1047             myx *= x1;
1048             myy *= y1;
1049             x0 = (Math.min(Math.min(0,mxx),Math.min(mxy,mxx+mxy)))+mxt;
1050             y0 = (Math.min(Math.min(0,myx),Math.min(myy,myx+myy)))+myt;
1051             x1 = (Math.max(Math.max(0,mxx),Math.max(mxy,mxx+mxy)))+mxt;
1052             y1 = (Math.max(Math.max(0,myx),Math.max(myy,myx+myy)))+myt;
1053         }
1054         x0 -= _dpad;
1055         y0 -= _dpad;
1056         x1 += _dpad;
1057         y1 += _dpad;
1058 
1059         bounds = bounds.deriveWithNewBounds((float)x0, (float)y0, 0.0f,
1060                 (float)x1, (float)y1, 0.0f);
1061         return bounds;
1062     }
1063 
1064     BaseBounds computeShapeBounds(BaseBounds bounds, BaseTransform tx,
1065                                 com.sun.javafx.geom.Shape s)
1066     {
1067         // empty mode means no bounds!
1068         if (impl_mode == NGShape.Mode.EMPTY) {
1069             return bounds.makeEmpty();
1070         }
1071 
1072         float[] bbox = {
1073             Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
1074             Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
1075         };
1076         boolean includeShape = (impl_mode != NGShape.Mode.STROKE);
1077         boolean includeStroke = (impl_mode != NGShape.Mode.FILL);
1078         if (includeStroke && (getStrokeType() == StrokeType.INSIDE)) {
1079             includeShape = true;
1080             includeStroke = false;
1081         }
1082 
1083         if (includeStroke) {
1084             final StrokeType type = getStrokeType();
1085             double sw = Utils.clampMin(getStrokeWidth(), MIN_STROKE_WIDTH);
1086             StrokeLineCap cap = getStrokeLineCap();
1087             StrokeLineJoin join = convertLineJoin(getStrokeLineJoin());
1088             float miterlimit =
1089                 (float) Utils.clampMin(getStrokeMiterLimit(), MIN_STROKE_MITER_LIMIT);
1090             // Note that we ignore dashing for computing bounds and testing
1091             // point containment, both to save time in bounds calculations
1092             // and so that animated dashing does not keep perturbing the bounds...
1093             Toolkit.getToolkit().accumulateStrokeBounds(
1094                     s,
1095                     bbox, type, sw,
1096                     cap, join, miterlimit, tx);
1097             // Account for "minimum pen size" by expanding by 0.5 device
1098             // pixels all around...
1099             bbox[0] -= 0.5;
1100             bbox[1] -= 0.5;
1101             bbox[2] += 0.5;
1102             bbox[3] += 0.5;
1103         } else if (includeShape) {
1104             com.sun.javafx.geom.Shape.accumulate(bbox, s, tx);
1105         }
1106 
1107         if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) {
1108             // They are probably +/-INFINITY which would yield NaN if subtracted
1109             // Let's just return a "safe" empty bbox..
1110             return bounds.makeEmpty();
1111         }
1112         bounds = bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f,
1113                 bbox[2], bbox[3], 0.0f);
1114         return bounds;
1115     }
1116 
1117     boolean computeShapeContains(double localX, double localY,
1118                                  com.sun.javafx.geom.Shape s) {
1119         if (impl_mode == NGShape.Mode.EMPTY) {
1120             return false;
1121         }
1122 
1123         boolean includeShape = (impl_mode != NGShape.Mode.STROKE);
1124         boolean includeStroke = (impl_mode != NGShape.Mode.FILL);
1125         if (includeStroke && includeShape &&
1126             (getStrokeType() == StrokeType.INSIDE))
1127         {
1128             includeStroke = false;
1129         }
1130 
1131         if (includeShape) {
1132             if (s.contains((float)localX, (float)localY)) {
1133                 return true;
1134             }
1135         }
1136 
1137         if (includeStroke) {
1138             StrokeType type = getStrokeType();
1139             double sw = Utils.clampMin(getStrokeWidth(), MIN_STROKE_WIDTH);
1140             StrokeLineCap cap = getStrokeLineCap();
1141             StrokeLineJoin join = convertLineJoin(getStrokeLineJoin());
1142             float miterlimit =
1143                 (float) Utils.clampMin(getStrokeMiterLimit(), MIN_STROKE_MITER_LIMIT);
1144             // Note that we ignore dashing for computing bounds and testing
1145             // point containment, both to save time in bounds calculations
1146             // and so that animated dashing does not keep perturbing the bounds...
1147             return Toolkit.getToolkit().strokeContains(s, localX, localY,
1148                                                        type, sw, cap,
1149                                                        join, miterlimit);
1150         }
1151 
1152         return false;
1153     }
1154 
1155     private boolean strokeAttributesDirty = true;
1156 
1157     private StrokeAttributes strokeAttributes;
1158 
1159     private StrokeAttributes getStrokeAttributes() {
1160         if (strokeAttributes == null) {
1161             strokeAttributes = new StrokeAttributes();
1162         }
1163 
1164         return strokeAttributes;
1165     }
1166 
1167     private boolean hasStrokeDashArray() {
1168         return (strokeAttributes != null) && strokeAttributes.hasDashArray();
1169     }
1170 
1171     private static float[] toPGDashArray(final List<Double> dashArray) {
1172         final int size = dashArray.size();
1173         final float[] pgDashArray = new float[size];
1174         for (int i = 0; i < size; i++) {
1175             pgDashArray[i] = dashArray.get(i).floatValue();
1176         }
1177 
1178         return pgDashArray;
1179     }
1180 
1181     private static final StrokeType DEFAULT_STROKE_TYPE = StrokeType.CENTERED;
1182     private static final double DEFAULT_STROKE_WIDTH = 1.0;
1183     private static final StrokeLineJoin DEFAULT_STROKE_LINE_JOIN =
1184             StrokeLineJoin.MITER;
1185     private static final StrokeLineCap DEFAULT_STROKE_LINE_CAP = 
1186             StrokeLineCap.SQUARE;
1187     private static final double DEFAULT_STROKE_MITER_LIMIT = 10.0;
1188     private static final double DEFAULT_STROKE_DASH_OFFSET = 0;
1189     private static final float[] DEFAULT_PG_STROKE_DASH_ARRAY = new float[0];
1190 
1191     private final class StrokeAttributes {
1192         private ObjectProperty<StrokeType> type;
1193         private DoubleProperty width;
1194         private ObjectProperty<StrokeLineJoin> lineJoin;
1195         private ObjectProperty<StrokeLineCap> lineCap;
1196         private DoubleProperty miterLimit;
1197         private DoubleProperty dashOffset;
1198         private ObservableList<Double> dashArray;
1199 
1200         public final StrokeType getType() {
1201             return (type == null) ? DEFAULT_STROKE_TYPE : type.get();
1202         }
1203 
1204         public final ObjectProperty<StrokeType> typeProperty() {
1205             if (type == null) {
1206                 type = new StyleableObjectProperty<StrokeType>(DEFAULT_STROKE_TYPE) {
1207                 
1208                     @Override
1209                     public void invalidated() {
1210                         StrokeAttributes.this.invalidated(
1211                                 StyleableProperties.STROKE_TYPE);
1212                     }
1213                 
1214                     @Override
1215                     public CssMetaData<Shape,StrokeType> getCssMetaData() {
1216                         return StyleableProperties.STROKE_TYPE;
1217                     }
1218 
1219                     @Override
1220                     public Object getBean() {
1221                         return Shape.this;
1222                     }
1223 
1224                     @Override
1225                     public String getName() {
1226                         return "strokeType";
1227                     }
1228                 };
1229             }
1230             return type;
1231         }
1232 
1233         public double getWidth() {
1234             return (width == null) ? DEFAULT_STROKE_WIDTH : width.get();
1235         }
1236 
1237         public final DoubleProperty widthProperty() {
1238             if (width == null) {
1239                 width = new StyleableDoubleProperty(DEFAULT_STROKE_WIDTH) {
1240                 
1241                     @Override
1242                     public void invalidated() {
1243                         StrokeAttributes.this.invalidated(
1244                                 StyleableProperties.STROKE_WIDTH);
1245                     }
1246                 
1247                     @Override
1248                     public CssMetaData<Shape,Number> getCssMetaData() {
1249                         return StyleableProperties.STROKE_WIDTH;
1250                     }
1251 
1252                     @Override
1253                     public Object getBean() {
1254                         return Shape.this;
1255                     }
1256 
1257                     @Override
1258                     public String getName() {
1259                         return "strokeWidth";
1260                     }
1261                 };
1262             }
1263             return width;
1264         }
1265 
1266         public StrokeLineJoin getLineJoin() {
1267             return (lineJoin == null) ? DEFAULT_STROKE_LINE_JOIN
1268                                       : lineJoin.get();
1269         }
1270 
1271         public final ObjectProperty<StrokeLineJoin> lineJoinProperty() {
1272             if (lineJoin == null) {
1273                 lineJoin = new StyleableObjectProperty<StrokeLineJoin>(
1274                                        DEFAULT_STROKE_LINE_JOIN) {
1275                 
1276                     @Override
1277                     public void invalidated() {
1278                         StrokeAttributes.this.invalidated(
1279                                 StyleableProperties.STROKE_LINE_JOIN);
1280                     }
1281                 
1282                     @Override
1283                     public CssMetaData<Shape,StrokeLineJoin> getCssMetaData() {
1284                         return StyleableProperties.STROKE_LINE_JOIN;
1285                     }
1286 
1287                     @Override
1288                     public Object getBean() {
1289                         return Shape.this;
1290                     }
1291 
1292                     @Override
1293                     public String getName() {
1294                         return "strokeLineJoin";
1295                     }
1296                 };
1297             }
1298             return lineJoin;
1299         }
1300 
1301         public StrokeLineCap getLineCap() {
1302             return (lineCap == null) ? DEFAULT_STROKE_LINE_CAP
1303                                      : lineCap.get();
1304         }
1305 
1306         public final ObjectProperty<StrokeLineCap> lineCapProperty() {
1307             if (lineCap == null) {
1308                 lineCap = new StyleableObjectProperty<StrokeLineCap>(
1309                                       DEFAULT_STROKE_LINE_CAP) {
1310                 
1311                     @Override
1312                     public void invalidated() {
1313                         StrokeAttributes.this.invalidated(
1314                                 StyleableProperties.STROKE_LINE_CAP);
1315                     }
1316                 
1317                     @Override
1318                     public CssMetaData<Shape,StrokeLineCap> getCssMetaData() {
1319                         return StyleableProperties.STROKE_LINE_CAP;
1320                     }
1321 
1322                     @Override
1323                     public Object getBean() {
1324                         return Shape.this;
1325                     }
1326 
1327                     @Override
1328                     public String getName() {
1329                         return "strokeLineCap";
1330                     }
1331                 };
1332             }
1333 
1334             return lineCap;
1335         }
1336 
1337         public double getMiterLimit() {
1338             return (miterLimit == null) ? DEFAULT_STROKE_MITER_LIMIT
1339                                         : miterLimit.get();
1340         }
1341 
1342         public final DoubleProperty miterLimitProperty() {
1343             if (miterLimit == null) {
1344                 miterLimit = new StyleableDoubleProperty(
1345                                          DEFAULT_STROKE_MITER_LIMIT) {
1346                     @Override
1347                     public void invalidated() {
1348                         StrokeAttributes.this.invalidated(
1349                                 StyleableProperties.STROKE_MITER_LIMIT);
1350                     }
1351                 
1352                     @Override
1353                     public CssMetaData<Shape,Number> getCssMetaData() {
1354                         return StyleableProperties.STROKE_MITER_LIMIT;
1355                     }
1356 
1357                     @Override
1358                     public Object getBean() {
1359                         return Shape.this;
1360                     }
1361 
1362                     @Override
1363                     public String getName() {
1364                         return "strokeMiterLimit";
1365                     }
1366                 };
1367             }
1368 
1369             return miterLimit;
1370         }
1371 
1372         public double getDashOffset() {
1373             return (dashOffset == null) ? DEFAULT_STROKE_DASH_OFFSET
1374                                         : dashOffset.get();
1375         }
1376 
1377         public final DoubleProperty dashOffsetProperty() {
1378             if (dashOffset == null) {
1379                 dashOffset = new StyleableDoubleProperty(
1380                                          DEFAULT_STROKE_DASH_OFFSET) {
1381                                              
1382                     @Override
1383                     public void invalidated() {
1384                         StrokeAttributes.this.invalidated(
1385                                 StyleableProperties.STROKE_DASH_OFFSET);
1386                     }
1387                 
1388                     @Override
1389                     public CssMetaData<Shape,Number> getCssMetaData() {
1390                         return StyleableProperties.STROKE_DASH_OFFSET;
1391                     }
1392 
1393                     @Override
1394                     public Object getBean() {
1395                         return Shape.this;
1396                     }
1397 
1398                     @Override
1399                     public String getName() {
1400                         return "strokeDashOffset";
1401                     }
1402                 };
1403             }
1404 
1405             return dashOffset;
1406         }
1407 
1408         // TODO: Need to handle set from css - should clear array and add all.  
1409         public ObservableList<Double> dashArrayProperty() {
1410             if (dashArray == null) {
1411                 dashArray = new TrackableObservableList<Double>() {
1412                     @Override
1413                     protected void onChanged(Change<Double> c) {
1414                         StrokeAttributes.this.invalidated(
1415                                 StyleableProperties.STROKE_DASH_ARRAY);
1416                     }
1417                 }; 
1418             }
1419             return dashArray;
1420         }
1421         
1422         private ObjectProperty<Number[]> cssDashArray = null;
1423         private ObjectProperty<Number[]> cssDashArrayProperty() {
1424             if (cssDashArray == null) {
1425                 cssDashArray = new StyleableObjectProperty<Number[]>() 
1426                 {
1427 
1428                     @Override
1429                     public void set(Number[] v) {
1430                         
1431                         ObservableList<Double> list = dashArrayProperty();
1432                         list.clear();
1433                         if (v != null && v.length > 0) {
1434                             for (int n=0; n<v.length; n++) {
1435                                 list.add(v[n].doubleValue());
1436                             }
1437                         }
1438                         
1439                         // no need to hold onto the array
1440                     }
1441 
1442                     @Override
1443                     public Double[] get() {
1444                         List<Double> list = dashArrayProperty();
1445                         return list.toArray(new Double[list.size()]);
1446                     }
1447 
1448                     @Override
1449                     public Object getBean() {
1450                         return Shape.this;
1451                     }
1452 
1453                     @Override
1454                     public String getName() {
1455                         return "cssDashArray";
1456                     }
1457 
1458                     @Override
1459                     public CssMetaData<Shape,Number[]> getCssMetaData() {
1460                         return StyleableProperties.STROKE_DASH_ARRAY;
1461                     }
1462                 };
1463             }
1464 
1465             return cssDashArray;
1466         }
1467 
1468         public boolean canSetType() {
1469             return (type == null) || !type.isBound();
1470         }
1471 
1472         public boolean canSetWidth() {
1473             return (width == null) || !width.isBound();
1474         }
1475 
1476         public boolean canSetLineJoin() {
1477             return (lineJoin == null) || !lineJoin.isBound();
1478         }
1479 
1480         public boolean canSetLineCap() {
1481             return (lineCap == null) || !lineCap.isBound();
1482         }
1483 
1484         public boolean canSetMiterLimit() {
1485             return (miterLimit == null) || !miterLimit.isBound();
1486         }
1487 
1488         public boolean canSetDashOffset() {
1489             return (dashOffset == null) || !dashOffset.isBound();
1490         }
1491 
1492         public boolean hasDashArray() {
1493             return (dashArray != null);
1494         }
1495 
1496         private void invalidated(final CssMetaData<Shape, ?> propertyCssKey) {
1497             impl_markDirty(DirtyBits.SHAPE_STROKEATTRS);
1498             strokeAttributesDirty = true;
1499             if (propertyCssKey != StyleableProperties.STROKE_DASH_OFFSET) {
1500                 // all stroke attributes change geometry except for the
1501                 // stroke dash offset
1502                 impl_geomChanged();
1503             }
1504         }
1505     }
1506 
1507     /**
1508      * @treatAsPrivate implementation detail
1509      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1510      */
1511     @Deprecated
1512     @Override
1513     public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
1514         return alg.processLeafNode(this, ctx);
1515     }
1516 
1517     // PENDING_DOC_REVIEW
1518     /**
1519      * Returns a new {@code Shape} which is created as a union of the specified
1520      * input shapes.
1521      * <p>
1522      * The operation works with geometric areas occupied by the input shapes.
1523      * For a single {@code Shape} such area includes the area occupied by the
1524      * fill if the shape has a non-null fill and the area occupied by the stroke
1525      * if the shape has a non-null stroke. So the area is empty for a shape
1526      * with {@code null} stroke and {@code null} fill. The area of an input
1527      * shape considered by the operation is independent on the type and
1528      * configuration of the paint used for fill or stroke. Before the final
1529      * operation the areas of the input shapes are transformed to the parent
1530      * coordinate space of their respective topmost parent nodes.
1531      * <p>
1532      * The resulting shape will include areas that were contained in any of the
1533      * input shapes.
1534      * <p>
1535 
1536 <PRE>
1537 
1538          shape1       +       shape2       =       result
1539    +----------------+   +----------------+   +----------------+
1540    |################|   |################|   |################|
1541    |##############  |   |  ##############|   |################|
1542    |############    |   |    ############|   |################|
1543    |##########      |   |      ##########|   |################|
1544    |########        |   |        ########|   |################|
1545    |######          |   |          ######|   |######    ######|
1546    |####            |   |            ####|   |####        ####|
1547    |##              |   |              ##|   |##            ##|
1548    +----------------+   +----------------+   +----------------+
1549 
1550 </PRE>
1551 
1552      * @param shape1 the first shape
1553      * @param shape2 the second shape
1554      * @return the created {@code Shape}
1555      */
1556     public static Shape union(final Shape shape1, final Shape shape2) {
1557         final Area result = shape1.getTransformedArea();
1558         result.add(shape2.getTransformedArea());
1559         return createFromGeomShape(result);
1560     }
1561 
1562     // PENDING_DOC_REVIEW
1563     /**
1564      * Returns a new {@code Shape} which is created by subtracting the specified
1565      * second shape from the first shape.
1566      * <p>
1567      * The operation works with geometric areas occupied by the input shapes.
1568      * For a single {@code Shape} such area includes the area occupied by the
1569      * fill if the shape has a non-null fill and the area occupied by the stroke
1570      * if the shape has a non-null stroke. So the area is empty for a shape
1571      * with {@code null} stroke and {@code null} fill. The area of an input
1572      * shape considered by the operation is independent on the type and
1573      * configuration of the paint used for fill or stroke. Before the final
1574      * operation the areas of the input shapes are transformed to the parent
1575      * coordinate space of their respective topmost parent nodes.
1576      * <p>
1577      * The resulting shape will include areas that were contained only in the
1578      * first shape and not in the second shape.
1579      * <p>
1580 
1581 <PRE>
1582 
1583          shape1       -       shape2       =       result
1584    +----------------+   +----------------+   +----------------+
1585    |################|   |################|   |                |
1586    |##############  |   |  ##############|   |##              |
1587    |############    |   |    ############|   |####            |
1588    |##########      |   |      ##########|   |######          |
1589    |########        |   |        ########|   |########        |
1590    |######          |   |          ######|   |######          |
1591    |####            |   |            ####|   |####            |
1592    |##              |   |              ##|   |##              |
1593    +----------------+   +----------------+   +----------------+
1594 
1595 </PRE>
1596 
1597      * @param shape1 the first shape
1598      * @param shape2 the second shape
1599      * @return the created {@code Shape}
1600      */
1601     public static Shape subtract(final Shape shape1, final Shape shape2) {
1602         final Area result = shape1.getTransformedArea();
1603         result.subtract(shape2.getTransformedArea());
1604         return createFromGeomShape(result);
1605     }
1606 
1607     // PENDING_DOC_REVIEW
1608     /**
1609      * Returns a new {@code Shape} which is created as an intersection of the
1610      * specified input shapes.
1611      * <p>
1612      * The operation works with geometric areas occupied by the input shapes.
1613      * For a single {@code Shape} such area includes the area occupied by the
1614      * fill if the shape has a non-null fill and the area occupied by the stroke
1615      * if the shape has a non-null stroke. So the area is empty for a shape
1616      * with {@code null} stroke and {@code null} fill. The area of an input
1617      * shape considered by the operation is independent on the type and
1618      * configuration of the paint used for fill or stroke. Before the final
1619      * operation the areas of the input shapes are transformed to the parent
1620      * coordinate space of their respective topmost parent nodes.
1621      * <p>
1622      * The resulting shape will include only areas that were contained in both
1623      * of the input shapes.
1624      * <p>
1625 
1626 <PRE>
1627 
1628          shape1       +       shape2       =       result
1629    +----------------+   +----------------+   +----------------+
1630    |################|   |################|   |################|
1631    |##############  |   |  ##############|   |  ############  |
1632    |############    |   |    ############|   |    ########    |
1633    |##########      |   |      ##########|   |      ####      |
1634    |########        |   |        ########|   |                |
1635    |######          |   |          ######|   |                |
1636    |####            |   |            ####|   |                |
1637    |##              |   |              ##|   |                |
1638    +----------------+   +----------------+   +----------------+
1639 
1640 </PRE>
1641 
1642      * @param shape1 the first shape
1643      * @param shape2 the second shape
1644      * @return the created {@code Shape}
1645      */
1646     public static Shape intersect(final Shape shape1, final Shape shape2) {
1647         final Area result = shape1.getTransformedArea();
1648         result.intersect(shape2.getTransformedArea());
1649         return createFromGeomShape(result);
1650     }
1651 
1652     private Area getTransformedArea() {
1653         return getTransformedArea(calculateNodeToSceneTransform(this));
1654     }
1655 
1656     private Area getTransformedArea(final BaseTransform transform) {
1657         if (impl_mode == NGShape.Mode.EMPTY) {
1658             return new Area();
1659         }
1660 
1661         final com.sun.javafx.geom.Shape fillShape = impl_configShape();
1662         if ((impl_mode == NGShape.Mode.FILL)
1663                 || (impl_mode == NGShape.Mode.STROKE_FILL)
1664                        && (getStrokeType() == StrokeType.INSIDE)) {
1665             return createTransformedArea(fillShape, transform);
1666         }
1667 
1668         final StrokeType strokeType = getStrokeType();
1669         final double strokeWidth =
1670                 Utils.clampMin(getStrokeWidth(), MIN_STROKE_WIDTH);
1671         final StrokeLineCap strokeLineCap = getStrokeLineCap();
1672         final StrokeLineJoin strokeLineJoin = convertLineJoin(getStrokeLineJoin());
1673         final float strokeMiterLimit =
1674                 (float) Utils.clampMin(getStrokeMiterLimit(),
1675                                        MIN_STROKE_MITER_LIMIT);
1676         final float[] dashArray =
1677                 (hasStrokeDashArray())
1678                         ? toPGDashArray(getStrokeDashArray())
1679                         : DEFAULT_PG_STROKE_DASH_ARRAY;
1680 
1681         final com.sun.javafx.geom.Shape strokeShape =
1682                 Toolkit.getToolkit().createStrokedShape(
1683                         fillShape, strokeType, strokeWidth, strokeLineCap,
1684                         strokeLineJoin, strokeMiterLimit,
1685                         dashArray, (float) getStrokeDashOffset());
1686 
1687         if (impl_mode == NGShape.Mode.STROKE) {
1688             return createTransformedArea(strokeShape, transform);
1689         }
1690 
1691         // fill and stroke
1692         final Area combinedArea = new Area(fillShape);
1693         combinedArea.add(new Area(strokeShape));
1694 
1695         return createTransformedArea(combinedArea, transform);
1696     }
1697 
1698     private static BaseTransform calculateNodeToSceneTransform(Node node) {
1699         final Affine3D cumulativeTransformation = new Affine3D();
1700 
1701         do {
1702             cumulativeTransformation.preConcatenate(
1703                     node.impl_getLeafTransform());
1704             node = node.getParent();
1705         } while (node != null);
1706 
1707         return cumulativeTransformation;
1708     }
1709 
1710     private static Area createTransformedArea(
1711             final com.sun.javafx.geom.Shape geomShape,
1712             final BaseTransform transform) {
1713         return transform.isIdentity()
1714                    ? new Area(geomShape)
1715                    : new Area(geomShape.getPathIterator(transform));
1716     }
1717 
1718     private static Path createFromGeomShape(
1719             final com.sun.javafx.geom.Shape geomShape) {
1720         final Path path = new Path();
1721         final ObservableList<PathElement> elements = path.getElements();
1722 
1723         final PathIterator iterator = geomShape.getPathIterator(null);
1724         final float coords[] = new float[6];
1725 
1726         while (!iterator.isDone()) {
1727             final int segmentType = iterator.currentSegment(coords);
1728             switch (segmentType) {
1729                 case PathIterator.SEG_MOVETO:
1730                     elements.add(new MoveTo(coords[0], coords[1]));
1731                     break;
1732                 case PathIterator.SEG_LINETO:
1733                     elements.add(new LineTo(coords[0], coords[1]));
1734                     break;
1735                 case PathIterator.SEG_QUADTO:
1736                     elements.add(new QuadCurveTo(coords[0], coords[1],
1737                                                  coords[2], coords[3]));
1738                     break;
1739                 case PathIterator.SEG_CUBICTO:
1740                     elements.add(new CubicCurveTo(coords[0], coords[1],
1741                                                   coords[2], coords[3],
1742                                                   coords[4], coords[5]));
1743                     break;
1744                 case PathIterator.SEG_CLOSE:
1745                     elements.add(new ClosePath());
1746                     break;
1747             }
1748 
1749             iterator.next();
1750         }
1751 
1752         path.setFillRule((iterator.getWindingRule()
1753                              == PathIterator.WIND_EVEN_ODD)
1754                                  ? FillRule.EVEN_ODD
1755                                  : FillRule.NON_ZERO);
1756 
1757         path.setFill(Color.BLACK);
1758         path.setStroke(null);
1759 
1760         return path;
1761     }
1762 }