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