1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.webkit.prism;
  27 
  28 import com.sun.javafx.geom.Arc2D;
  29 import com.sun.javafx.geom.Ellipse2D;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.PathIterator;
  32 import com.sun.javafx.geom.Point2D;
  33 import com.sun.javafx.geom.RectBounds;
  34 import com.sun.javafx.geom.RoundRectangle2D;
  35 import com.sun.javafx.geom.transform.BaseTransform;
  36 import com.sun.webkit.graphics.WCPath;
  37 import com.sun.webkit.graphics.WCPathIterator;
  38 import com.sun.webkit.graphics.WCRectangle;
  39 import com.sun.javafx.logging.PlatformLogger;
  40 import com.sun.javafx.logging.PlatformLogger.Level;
  41 
  42 final class WCPathImpl extends WCPath<Path2D> {
  43     private final Path2D path;
  44     private boolean hasCP = false;
  45 
  46     private final static PlatformLogger log =
  47             PlatformLogger.getLogger(WCPathImpl.class.getName());
  48 
  49     WCPathImpl() {
  50         if (log.isLoggable(Level.FINE)) {
  51             log.fine("Create empty WCPathImpl({0})", getID());
  52         }
  53         path = new Path2D();
  54     }
  55 
  56     WCPathImpl(WCPathImpl wcp) {
  57         if (log.isLoggable(Level.FINE)) {
  58             log.fine("Create WCPathImpl({0}) from WCPathImpl({1})",
  59                     new Object[] { getID(), wcp.getID()});
  60         }
  61         path = new Path2D(wcp.path);
  62         hasCP = wcp.hasCP;
  63     }
  64 
  65     public void addRect(double x, double y, double w, double h) {
  66         if (log.isLoggable(Level.FINE)) {
  67             log.fine("WCPathImpl({0}).addRect({1},{2},{3},{4})",
  68                         new Object[] {getID(), x, y, w, h});
  69         }
  70         hasCP = true;
  71         path.append(new RoundRectangle2D(
  72                 (float)x, (float)y, (float)w, (int)h, 0.0f, 0.0f), false);
  73     }
  74 
  75     public void addEllipse(double x, double y, double w, double h)
  76     {
  77         if (log.isLoggable(Level.FINE)) {
  78             log.fine("WCPathImpl({0}).addEllipse({1},{2},{3},{4})",
  79                     new Object[] {getID(), x, y, w, h});
  80         }
  81         hasCP = true;
  82         path.append(new Ellipse2D((float)x, (float)y, (float)w, (float)h), false);
  83     }
  84 
  85     public void addArcTo(double x1, double y1, double x2, double y2, double r)
  86     {
  87         if (log.isLoggable(Level.FINE)) {
  88             log.fine("WCPathImpl({0}).addArcTo({1},{2},{3},{4})",
  89                     new Object[] {getID(), x1, y1, x2, y2});
  90         }
  91 
  92         Arc2D arc = new Arc2D();
  93         arc.setArcByTangent(
  94                 path.getCurrentPoint(),
  95                 new Point2D((float) x1, (float) y1),
  96                 new Point2D((float) x2, (float) y2),
  97                 (float) r);
  98 
  99         hasCP = true;
 100         path.append(arc, true);
 101     }
 102 
 103     public void addArc(double x, double y, double r, double sa,
 104                        double ea, boolean aclockwise)
 105     {
 106         // Use single precision float as native
 107         final float TWO_PI = 2.0f * (float) Math.PI;
 108         float startAngle = (float) sa;
 109         float endAngle = (float) ea;
 110 
 111         if (log.isLoggable(Level.FINE)) {
 112             log.fine("WCPathImpl({0}).addArc(x={1},y={2},r={3},sa=|{4}|,ea=|{5}|,aclock={6})",
 113                     new Object[] {getID(), x, y, r, startAngle, endAngle, aclockwise});
 114         }
 115 
 116         hasCP = true;
 117 
 118         float newEndAngle = endAngle;
 119         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc
 120         // If the anticlockwise argument is false and endAngle-startAngle is equal
 121         // to or greater than 2pi, or,
 122         // if the anticlockwise argument is true and startAngle-endAngle is equal to
 123         // or greater than 2pi,
 124         // then the arc is the whole circumference of this ellipse, and the point at
 125         // startAngle along this circle's circumference, measured in radians clockwise
 126         // from the ellipse's semi-major axis, acts as both the start point and the
 127         // end point.
 128 
 129         // below condition is already handled in normalizeAngles(), CanvasPath.cpp.
 130         // if (!anticlockwise && end_angle - start_angle >= twoPiFloat) {
 131         //   new_end_angle = start_angle + twoPiFloat;
 132         // } else if (anticlockwise && start_angle - end_angle >= twoPiFloat) {
 133         //   new_end_angle = start_angle - twoPiFloat;
 134         // } else
 135 
 136         // Otherwise, the arc is the path along the circumference of this ellipse
 137         // from the start point to the end point, going anti-clockwise if the
 138         // anticlockwise argument is true, and clockwise otherwise.
 139         // Since the points are on the ellipse, as opposed to being simply angles
 140         // from zero, the arc can never cover an angle greater than 2pi radians.
 141         //
 142         // NOTE: When startAngle = 0, endAngle = 2Pi and anticlockwise = true, the
 143         // spec does not indicate clearly.
 144         // We draw the entire circle, because some web sites use arc(x, y, r, 0,
 145         // 2*Math.PI, true) to draw circle.
 146         // We preserve backward-compatibility.
 147         if (!aclockwise && startAngle > endAngle) {
 148             newEndAngle = startAngle + (TWO_PI - ((startAngle - endAngle) % TWO_PI));
 149         } else if (aclockwise && startAngle < endAngle) {
 150             newEndAngle = startAngle - (TWO_PI - ((endAngle - startAngle) % TWO_PI));
 151         }
 152 
 153         path.append(new Arc2D((float) (x - r), (float) (y - r),
 154                               (float) (2 * r), (float) (2 * r),
 155                               (float) Math.toDegrees(-startAngle),
 156                               (float) Math.toDegrees(startAngle - newEndAngle), Arc2D.OPEN), true);
 157     }
 158 
 159     public boolean contains(int rule, double x, double y) {
 160         if (log.isLoggable(Level.FINE)) {
 161             log.fine("WCPathImpl({0}).contains({1},{2},{3})",
 162                     new Object[] {getID(), rule, x, y});
 163         }
 164         final int savedRule = path.getWindingRule();
 165         path.setWindingRule(rule);
 166         final boolean res = path.contains((float)x, (float)y);
 167         path.setWindingRule(savedRule);
 168 
 169         return res;
 170     }
 171 
 172     @Override
 173     public WCRectangle getBounds() {
 174         RectBounds b = path.getBounds();
 175         return new WCRectangle(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
 176     }
 177 
 178     public void clear() {
 179         if (log.isLoggable(Level.FINE)) {
 180             log.fine("WCPathImpl({0}).clear()", getID());
 181         }
 182         hasCP = false;
 183         path.reset();
 184     }
 185 
 186     public void moveTo(double x, double y) {
 187         if (log.isLoggable(Level.FINE)) {
 188             log.fine("WCPathImpl({0}).moveTo({1},{2})",
 189                     new Object[] {getID(), x, y});
 190         }
 191         hasCP = true;
 192         path.moveTo((float)x, (float)y);
 193     }
 194 
 195     public void addLineTo(double x, double y) {
 196         if (log.isLoggable(Level.FINE)) {
 197             log.fine("WCPathImpl({0}).addLineTo({1},{2})",
 198                     new Object[] {getID(), x, y});
 199         }
 200         hasCP = true;
 201         path.lineTo((float)x, (float)y);
 202     }
 203 
 204     public void addQuadCurveTo(double x0, double y0, double x1, double y1) {
 205         if (log.isLoggable(Level.FINE)) {
 206             log.fine("WCPathImpl({0}).addQuadCurveTo({1},{2},{3},{4})",
 207                     new Object[] {getID(), x0, y0, x1, y1});
 208         }
 209         hasCP = true;
 210         path.quadTo((float)x0, (float)y0, (float)x1, (float)y1);
 211     }
 212 
 213     public void addBezierCurveTo(double x0, double y0, double x1, double y1,
 214                                  double x2, double y2) {
 215         if (log.isLoggable(Level.FINE)) {
 216             log.fine("WCPathImpl({0}).addBezierCurveTo({1},{2},{3},{4},{5},{6})",
 217                     new Object[] {getID(), x0, y0, x1, y1, x2, y2});
 218         }
 219         hasCP = true;
 220         path.curveTo((float)x0, (float)y0, (float)x1, (float)y1,
 221                      (float)x2, (float)y2);
 222     }
 223 
 224     public void addPath(WCPath p) {
 225         if (log.isLoggable(Level.FINE)) {
 226             log.fine("WCPathImpl({0}).addPath({1})",
 227                     new Object[] {getID(), p.getID()});
 228         }
 229         hasCP = hasCP || ((WCPathImpl)p).hasCP;
 230         path.append(((WCPathImpl)p).path, false);
 231     }
 232 
 233     public void closeSubpath() {
 234         if (log.isLoggable(Level.FINE)) {
 235             log.fine("WCPathImpl({0}).closeSubpath()", getID());
 236         }
 237         path.closePath();
 238     }
 239 
 240     public boolean hasCurrentPoint() {
 241         return hasCP;
 242     }
 243 
 244     public boolean isEmpty() {
 245         PathIterator pi = path.getPathIterator(null);
 246         float [] coords = new float[6];
 247         while(!pi.isDone()) {
 248             switch(pi.currentSegment(coords)) {
 249                 case PathIterator.SEG_LINETO:
 250                 case PathIterator.SEG_QUADTO:
 251                 case PathIterator.SEG_CUBICTO:
 252                     return false;
 253             }
 254             pi.next();
 255         }
 256         return true;
 257     }
 258 
 259     public int getWindingRule() {
 260         return 1 - this.path.getWindingRule(); // convert prism to webkit
 261     }
 262 
 263     public void setWindingRule(int rule) {
 264         this.path.setWindingRule(1 - rule); // convert webkit to prism
 265     }
 266 
 267     public Path2D getPlatformPath() {
 268         if (log.isLoggable(Level.FINE)) {
 269             log.fine("WCPathImpl({0}).getPath() BEGIN=====", getID());
 270             PathIterator pi = path.getPathIterator(null);
 271             float [] coords = new float[6];
 272             while(!pi.isDone()) {
 273                 switch(pi.currentSegment(coords)) {
 274                     case PathIterator.SEG_MOVETO:
 275                         log.fine("SEG_MOVETO ({0},{1})",
 276                                 new Object[] {coords[0], coords[1]});
 277                         break;
 278                     case PathIterator.SEG_LINETO:
 279                         log.fine("SEG_LINETO ({0},{1})",
 280                                 new Object[] {coords[0], coords[1]});
 281                         break;
 282                     case PathIterator.SEG_QUADTO:
 283                         log.fine("SEG_QUADTO ({0},{1},{2},{3})",
 284                                 new Object[] {coords[0], coords[1], coords[2], coords[3]});
 285                         break;
 286                     case PathIterator.SEG_CUBICTO:
 287                         log.fine("SEG_CUBICTO ({0},{1},{2},{3},{4},{5})",
 288                                 new Object[] {coords[0], coords[1], coords[2], coords[3],
 289                                               coords[4], coords[5]});
 290                         break;
 291                     case PathIterator.SEG_CLOSE:
 292                         log.fine("SEG_CLOSE");
 293                         break;
 294                 }
 295                 pi.next();
 296             }
 297             log.fine("========getPath() END=====");
 298         }
 299         return path;
 300     }
 301 
 302     public WCPathIterator getPathIterator() {
 303         final PathIterator pi = path.getPathIterator(null);
 304         return new WCPathIterator() {
 305             @Override public int getWindingRule() {
 306                 return pi.getWindingRule();
 307             }
 308 
 309             @Override public boolean isDone() {
 310                 return pi.isDone();
 311             }
 312 
 313             @Override public void next() {
 314                 pi.next();
 315             }
 316 
 317             @Override public int currentSegment(double[] coords) {
 318                 float [] _coords = new float[6];
 319                 int segmentType = pi.currentSegment(_coords);
 320                 for (int i = 0; i < coords.length; i++) {
 321                     coords[i] = _coords[i];
 322                 }
 323                 return segmentType;
 324             }
 325         };
 326     }
 327 
 328     public void translate(double x, double y)
 329     {
 330         if (log.isLoggable(Level.FINE)) {
 331             log.fine("WCPathImpl({0}).translate({1}, {2})",
 332                     new Object[] {getID(), x, y});
 333         }
 334         path.transform(BaseTransform.getTranslateInstance(x, y));
 335     }
 336 
 337     public void transform(double mxx, double myx,
 338                           double mxy, double myy,
 339                           double mxt, double myt)
 340     {
 341         if (log.isLoggable(Level.FINE)) {
 342             log.fine("WCPathImpl({0}).transform({1},{2},{3},{4},{5},{6})",
 343                     new Object[] {getID(), mxx, myx, mxy, myy, mxt, myt});
 344         }
 345         path.transform(BaseTransform.getInstance(mxx, myx, mxy, myy, mxt, myt));
 346     }
 347 }