1 /*
   2  * Copyright (c) 2010, 2016, 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 com.sun.javafx.util.Logging;
  29 import com.sun.javafx.geom.Path2D;
  30 import com.sun.javafx.scene.DirtyBits;
  31 import com.sun.javafx.scene.NodeHelper;
  32 import com.sun.javafx.scene.shape.SVGPathHelper;
  33 import com.sun.javafx.scene.shape.ShapeHelper;
  34 import com.sun.javafx.sg.prism.NGNode;
  35 import com.sun.javafx.sg.prism.NGSVGPath;
  36 import com.sun.javafx.tk.Toolkit;
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.ObjectPropertyBase;
  39 import javafx.beans.property.StringProperty;
  40 import javafx.beans.property.StringPropertyBase;
  41 import javafx.scene.Node;
  42 import javafx.scene.paint.Paint;
  43 
  44 /**
  45  * The {@code SVGPath} class represents a simple shape that is constructed by
  46  * parsing SVG path data from a String.
  47  *
  48 <PRE>
  49 import javafx.scene.shape.*;
  50 
  51 SVGPath svg = new SVGPath();
  52 svg.setContent("M40,60 C42,48 44,30 25,32");
  53 </PRE>
  54  * @since JavaFX 2.0
  55  */
  56 public class SVGPath extends Shape {
  57     static {
  58         SVGPathHelper.setSVGPathAccessor(new SVGPathHelper.SVGPathAccessor() {
  59             @Override
  60             public NGNode doCreatePeer(Node node) {
  61                 return ((SVGPath) node).doCreatePeer();
  62             }
  63 
  64             @Override
  65             public void doUpdatePeer(Node node) {
  66                 ((SVGPath) node).doUpdatePeer();
  67             }
  68 
  69             @Override
  70             public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
  71                 return ((SVGPath) shape).doConfigShape();
  72             }
  73         });
  74     }
  75 
  76     /**
  77      * Defines the filling rule constant for determining the interior of the path.
  78      * The value must be one of the following constants:
  79      * {@code FillRile.EVEN_ODD} or {@code FillRule.NON_ZERO}.
  80      * The default value is {@code FillRule.NON_ZERO}.
  81      *
  82      * @defaultValue FillRule.NON_ZERO
  83      */
  84     private ObjectProperty<FillRule> fillRule;
  85 
  86     private Path2D path2d;
  87 
  88     {
  89         // To initialize the class helper at the begining each constructor of this class
  90         SVGPathHelper.initHelper(this);
  91     }
  92 
  93     /**
  94      * Creates an empty instance of SVGPath.
  95      */
  96     public SVGPath() {
  97     }
  98 
  99     public final void setFillRule(FillRule value) {
 100         if (fillRule != null || value != FillRule.NON_ZERO) {
 101             fillRuleProperty().set(value);
 102         }
 103     }
 104 
 105     public final FillRule getFillRule() {
 106         return fillRule == null ? FillRule.NON_ZERO : fillRule.get();
 107     }
 108 
 109     public final ObjectProperty<FillRule> fillRuleProperty() {
 110         if (fillRule == null) {
 111             fillRule = new ObjectPropertyBase<FillRule>(FillRule.NON_ZERO) {
 112 
 113                 @Override
 114                 public void invalidated() {
 115                     NodeHelper.markDirty(SVGPath.this, DirtyBits.SHAPE_FILLRULE);
 116                     impl_geomChanged();
 117                 }
 118 
 119                 @Override
 120                 public Object getBean() {
 121                     return SVGPath.this;
 122                 }
 123 
 124                 @Override
 125                 public String getName() {
 126                     return "fillRule";
 127                 }
 128             };
 129         }
 130         return fillRule;
 131     }
 132 
 133     /**
 134      * Defines the SVG Path encoded string as specified at:
 135      * <a href="http://www.w3.org/TR/SVG/paths.html">http://www.w3.org/TR/SVG/paths.html</a>.
 136      *
 137      * @defaultValue empty string
 138      */
 139     private StringProperty content;
 140 
 141 
 142     public final void setContent(String value) {
 143         contentProperty().set(value);
 144     }
 145 
 146     public final String getContent() {
 147         return content == null ? "" : content.get();
 148     }
 149 
 150     public final StringProperty contentProperty() {
 151         if (content == null) {
 152             content = new StringPropertyBase("") {
 153 
 154                 @Override
 155                 public void invalidated() {
 156                     NodeHelper.markDirty(SVGPath.this, DirtyBits.NODE_CONTENTS);
 157                     impl_geomChanged();
 158                     path2d = null;
 159                 }
 160 
 161                 @Override
 162                 public Object getBean() {
 163                     return SVGPath.this;
 164                 }
 165 
 166                 @Override
 167                 public String getName() {
 168                     return "content";
 169                 }
 170             };
 171         }
 172         return content;
 173     }
 174 
 175     private Object svgPathObject;
 176 
 177     /*
 178      * Note: This method MUST only be called via its accessor method.
 179      */
 180     private NGNode doCreatePeer() {
 181         return new NGSVGPath();
 182     }
 183 
 184     /*
 185      * Note: This method MUST only be called via its accessor method.
 186      */
 187     private Path2D doConfigShape() {
 188         if (path2d == null) {
 189             path2d = createSVGPath2D();
 190         } else {
 191             path2d.setWindingRule(getFillRule() == FillRule.NON_ZERO ?
 192                                   Path2D.WIND_NON_ZERO : Path2D.WIND_EVEN_ODD);
 193         }
 194 
 195         return path2d;
 196     }
 197 
 198     /*
 199      * Note: This method MUST only be called via its accessor method.
 200      */
 201     private void doUpdatePeer() {
 202         if (NodeHelper.isDirty(this, DirtyBits.SHAPE_FILLRULE) ||
 203             NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS))
 204         {
 205             final NGSVGPath peer = NodeHelper.getPeer(this);
 206             if (peer.acceptsPath2dOnUpdate()) {
 207                 if (svgPathObject == null) {
 208                     svgPathObject = new Path2D();
 209                 }
 210                 Path2D tempPathObject = (Path2D) svgPathObject;
 211                 tempPathObject.setTo((Path2D) ShapeHelper.configShape(this));
 212             } else {
 213                 svgPathObject = createSVGPathObject();
 214             }
 215             peer.setContent(svgPathObject);
 216         }
 217     }
 218 
 219     /**
 220      * Returns a string representation of this {@code SVGPath} object.
 221      * @return a string representation of this {@code SVGPath} object.
 222      */
 223     @Override
 224     public String toString() {
 225         final StringBuilder sb = new StringBuilder("SVGPath[");
 226 
 227         String id = getId();
 228         if (id != null) {
 229             sb.append("id=").append(id).append(", ");
 230         }
 231 
 232         sb.append("content=\"").append(getContent()).append("\"");
 233 
 234         sb.append(", fill=").append(getFill());
 235         sb.append(", fillRule=").append(getFillRule());
 236 
 237         Paint stroke = getStroke();
 238         if (stroke != null) {
 239             sb.append(", stroke=").append(stroke);
 240             sb.append(", strokeWidth=").append(getStrokeWidth());
 241         }
 242 
 243         return sb.append("]").toString();
 244     }
 245 
 246     private Path2D createSVGPath2D() {
 247         try {
 248             return Toolkit.getToolkit().createSVGPath2D(this);
 249         } catch (final RuntimeException e) {
 250             Logging.getJavaFXLogger().warning(
 251                     "Failed to configure svg path \"{0}\": {1}",
 252                     getContent(), e.getMessage());
 253 
 254             return Toolkit.getToolkit().createSVGPath2D(new SVGPath());
 255         }
 256     }
 257 
 258     private Object createSVGPathObject() {
 259         try {
 260             return Toolkit.getToolkit().createSVGPathObject(this);
 261         } catch (final RuntimeException e) {
 262             Logging.getJavaFXLogger().warning(
 263                     "Failed to configure svg path \"{0}\": {1}",
 264                     getContent(), e.getMessage());
 265 
 266             return Toolkit.getToolkit().createSVGPathObject(new SVGPath());
 267         }
 268     }
 269 }