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 }