1 /* 2 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Library General Public 6 * License as published by the Free Software Foundation; either 7 * version 2 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public License 15 * along with this library; see the file COPYING.LIB. If not, write to 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 * Boston, MA 02110-1301, USA. 18 */ 19 20 #include "config.h" 21 #include "Path.h" 22 23 #include "AffineTransform.h" 24 #include "FloatRect.h" 25 #include "GraphicsContext.h" 26 #include "NotImplemented.h" 27 #include "PainterOpenVG.h" 28 #include "PlatformPathOpenVG.h" 29 #include "PlatformString.h" 30 #include "StrokeStyleApplier.h" 31 #include "VGUtils.h" 32 33 #include <openvg.h> 34 #include <wtf/MathExtras.h> 35 36 #define WEBKIT_VG_PATH_CAPABILITIES VG_PATH_CAPABILITY_ALL 37 38 #define FUZZY_COMPARE(number, reference, delta) \ 39 (number >= (reference - delta) && number <= (reference + delta)) 40 41 namespace WebCore { 42 43 PlatformPathOpenVG::PlatformPathOpenVG() 44 : SharedResourceOpenVG() 45 { 46 createPath(); 47 } 48 49 PlatformPathOpenVG::PlatformPathOpenVG(const PlatformPathOpenVG& other) 50 : SharedResourceOpenVG() 51 , m_currentPoint(other.m_currentPoint) 52 , m_subpathStartPoint(other.m_subpathStartPoint) 53 { 54 createPath(); 55 // makeCompatibleContextCurrent() is called by createPath(), so not necessary here. 56 vgAppendPath(m_vgPath, other.m_vgPath); 57 ASSERT_VG_NO_ERROR(); 58 } 59 60 PlatformPathOpenVG& PlatformPathOpenVG::operator=(const PlatformPathOpenVG& other) 61 { 62 if (&other != this) { 63 clear(); 64 // makeCompatibleContextCurrent() is called by clear(), so not necessary here. 65 vgAppendPath(m_vgPath, other.m_vgPath); 66 ASSERT_VG_NO_ERROR(); 67 } 68 return *this; 69 } 70 71 PlatformPathOpenVG::~PlatformPathOpenVG() 72 { 73 makeCompatibleContextCurrent(); 74 75 vgDestroyPath(m_vgPath); 76 ASSERT_VG_NO_ERROR(); 77 } 78 79 void PlatformPathOpenVG::clear() 80 { 81 makeCompatibleContextCurrent(); 82 83 vgClearPath(m_vgPath, WEBKIT_VG_PATH_CAPABILITIES); 84 ASSERT_VG_NO_ERROR(); 85 86 m_subpathStartPoint.setX(0); 87 m_subpathStartPoint.setY(0); 88 m_currentPoint = m_subpathStartPoint; 89 } 90 91 void PlatformPathOpenVG::createPath() 92 { 93 makeSharedContextCurrent(); 94 95 m_vgPath = vgCreatePath( 96 VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 97 1.0 /* scale */, 0.0 /* bias */, 98 0 /* expected number of segments */, 99 0 /* expected number of total coordinates */, 100 WEBKIT_VG_PATH_CAPABILITIES); 101 ASSERT_VG_NO_ERROR(); 102 } 103 104 105 Path::Path() 106 { 107 m_path = new PlatformPathOpenVG(); 108 } 109 110 Path::~Path() 111 { 112 delete m_path; 113 } 114 115 Path::Path(const Path& other) 116 { 117 m_path = new PlatformPathOpenVG(*(other.m_path)); 118 } 119 120 Path& Path::operator=(const Path& other) 121 { 122 *m_path = *(other.m_path); 123 return *this; 124 } 125 126 FloatPoint Path::currentPoint() const 127 { 128 // FIXME: is this the way to return the current point of the subpath? 129 return m_currentPoint; 130 } 131 132 133 bool Path::contains(const FloatPoint& point, WindRule rule) const 134 { 135 notImplemented(); 136 137 // OpenVG has no path-contains function, so for now we approximate by 138 // using the bounding rect of the path. 139 return boundingRect().contains(point); 140 } 141 142 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const 143 { 144 notImplemented(); 145 146 // OpenVG has no path-contains function, so for now we approximate by 147 // using the stroke bounding rect of the path. 148 return (const_cast<Path*>(this))->strokeBoundingRect().contains(point); 149 } 150 151 void Path::translate(const FloatSize& size) 152 { 153 AffineTransform transformation; 154 transformation.translate(size.width(), size.height()); 155 transform(transformation); 156 } 157 158 FloatRect Path::boundingRect() const 159 { 160 VGfloat minX; 161 VGfloat minY; 162 VGfloat width; 163 VGfloat height; 164 165 m_path->makeCompatibleContextCurrent(); 166 vgPathBounds(m_path->vgPath(), &minX, &minY, &width, &height); 167 ASSERT_VG_NO_ERROR(); 168 169 return FloatRect(FloatPoint(minX, minY), FloatSize(width, height)); 170 } 171 172 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const 173 { 174 notImplemented(); 175 176 // vgPathBounds() ignores stroke parameters, and we don't currently have 177 // an approximation that takes stroke parameters into account. 178 return boundingRect(); 179 } 180 181 void Path::moveTo(const FloatPoint& point) 182 { 183 static const VGubyte pathSegments[] = { VG_MOVE_TO_ABS }; 184 const VGfloat pathData[] = { point.x(), point.y() }; 185 186 m_path->makeCompatibleContextCurrent(); 187 vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); 188 ASSERT_VG_NO_ERROR(); 189 190 m_path->m_currentPoint = m_path->m_subpathStartPoint = point; 191 } 192 193 void Path::addLineTo(const FloatPoint& point) 194 { 195 static const VGubyte pathSegments[] = { VG_LINE_TO_ABS }; 196 const VGfloat pathData[] = { point.x(), point.y() }; 197 198 m_path->makeCompatibleContextCurrent(); 199 vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); 200 ASSERT_VG_NO_ERROR(); 201 202 m_path->m_currentPoint = point; 203 } 204 205 void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& endPoint) 206 { 207 static const VGubyte pathSegments[] = { VG_QUAD_TO_ABS }; 208 const VGfloat pathData[] = { controlPoint.x(), controlPoint.y(), endPoint.x(), endPoint.y() }; 209 210 m_path->makeCompatibleContextCurrent(); 211 vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); 212 ASSERT_VG_NO_ERROR(); 213 214 m_path->m_currentPoint = endPoint; 215 } 216 217 void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& endPoint) 218 { 219 static const VGubyte pathSegments[] = { VG_CUBIC_TO_ABS }; 220 const VGfloat pathData[] = { controlPoint1.x(), controlPoint1.y(), controlPoint2.x(), controlPoint2.y(), endPoint.x(), endPoint.y() }; 221 222 m_path->makeCompatibleContextCurrent(); 223 vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); 224 ASSERT_VG_NO_ERROR(); 225 226 m_path->m_currentPoint = endPoint; 227 } 228 229 void Path::addArcTo(const FloatPoint& point1, const FloatPoint& point2, float radius) 230 { 231 // See http://philip.html5.org/tests/canvas/suite/tests/spec.html#arcto. 232 233 const FloatPoint& point0 = m_path->m_currentPoint; 234 if (!radius || point0 == point1 || point1 == point2) { 235 addLineTo(point1); 236 return; 237 } 238 239 FloatSize v01 = point0 - point1; 240 FloatSize v21 = point2 - point1; 241 242 // sin(A - B) = sin(A) * cos(B) - sin(B) * cos(A) 243 double cross = v01.width() * v21.height() - v01.height() * v21.width(); 244 245 if (fabs(cross) < 1E-10) { 246 // on one line 247 addLineTo(point1); 248 return; 249 } 250 251 double d01 = hypot(v01.width(), v01.height()); 252 double d21 = hypot(v21.width(), v21.height()); 253 double angle = (piDouble - fabs(asin(cross / (d01 * d21)))) * 0.5; 254 double span = radius * tan(angle); 255 double rate = span / d01; 256 FloatPoint startPoint = FloatPoint(point1.x() + v01.width() * rate, 257 point1.y() + v01.height() * rate); 258 rate = span / d21; 259 FloatPoint endPoint = FloatPoint(point1.x() + v21.width() * rate, 260 point1.y() + v21.height() * rate); 261 262 // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO 263 // respectively SCCWARC_TO and LCCWARC_TO arcs. We always use small 264 // arcs for arcTo(), as the arc is defined as the "shortest arc" of the 265 // circle specified in HTML 5. 266 267 // Fs: sweep flag, specifying whether the arc is drawn in increasing (true) 268 // or decreasing (0) direction. 269 const bool anticlockwise = cross < 0; 270 271 // Translate the large arc and sweep flags into an OpenVG segment command. 272 const VGubyte segmentCommand = anticlockwise ? VG_SCCWARC_TO_ABS : VG_SCWARC_TO_ABS; 273 274 const VGubyte pathSegments[] = { 275 VG_LINE_TO_ABS, 276 segmentCommand 277 }; 278 const VGfloat pathData[] = { 279 startPoint.x(), startPoint.y(), 280 radius, radius, 0, endPoint.x(), endPoint.y() 281 }; 282 283 m_path->makeCompatibleContextCurrent(); 284 vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData); 285 ASSERT_VG_NO_ERROR(); 286 287 m_path->m_currentPoint = endPoint; 288 } 289 290 void Path::closeSubpath() 291 { 292 static const VGubyte pathSegments[] = { VG_CLOSE_PATH }; 293 // pathData must not be 0, but certain compilers also don't create 294 // zero-size arrays. So let's use a random aligned value (sizeof(VGfloat)), 295 // it won't be accessed anyways as VG_CLOSE_PATH doesn't take coordinates. 296 static const VGfloat* pathData = reinterpret_cast<VGfloat*>(sizeof(VGfloat)); 297 298 m_path->makeCompatibleContextCurrent(); 299 vgAppendPathData(m_path->vgPath(), 1, pathSegments, pathData); 300 ASSERT_VG_NO_ERROR(); 301 302 m_path->m_currentPoint = m_path->m_subpathStartPoint; 303 } 304 305 void Path::addArc(const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise) 306 { 307 // The OpenVG spec says nothing about inf as radius or start/end angle. 308 // WebKit seems to pass those (e.g. https://bugs.webkit.org/show_bug.cgi?id=16449), 309 // so abort instead of risking undefined behavior. 310 if (!isfinite(radius) || !isfinite(startAngle) || !isfinite(endAngle)) 311 return; 312 313 // For some reason, the HTML 5 spec defines the angle as going clockwise 314 // from the positive X axis instead of going standard anticlockwise. 315 // So let's make it a proper angle in order to keep sanity. 316 startAngle = fmod((2.0 * piDouble) - startAngle, 2.0 * piDouble); 317 endAngle = fmod((2.0 * piDouble) - endAngle, 2.0 * piDouble); 318 319 // Make it so that endAngle > startAngle. fmod() above takes care of 320 // keeping the difference below 360 degrees. 321 if (endAngle <= startAngle) 322 endAngle += 2.0 * piDouble; 323 324 const VGfloat angleDelta = anticlockwise 325 ? (endAngle - startAngle) 326 : (startAngle - endAngle + (2.0 * piDouble)); 327 328 // OpenVG uses endpoint parameterization while this method receives its 329 // values in center parameterization. It lacks an ellipse rotation 330 // parameter so we use 0 for that, and also the radius is only a single 331 // value which makes for rh == rv. In order to convert from endpoint to 332 // center parameterization, we use the formulas from the OpenVG/SVG specs: 333 334 // (x,y) = (cos rot, -sin rot; sin rot, -cos rot) * (rh * cos angle, rv * sin angle) + (center.x, center.y) 335 // rot is 0, which simplifies this a bit: 336 // (x,y) = (1, 0; 0, -1) * (rh * cos angle, rv * sin angle) + (center.x, center.y) 337 // = (1 * rh * cos angle + 0 * rv * sin angle, 0 * rh * cos angle + -1 * rv * sin angle) + (center.x, center.y) 338 // = (rh * cos angle, -rv * sin angle) + (center.x, center.y) 339 // (Set angle = {startAngle, endAngle} to retrieve the respective endpoints.) 340 341 const VGfloat startX = radius * cos(startAngle) + center.x(); 342 const VGfloat startY = -radius * sin(startAngle) + center.y(); 343 const VGfloat endX = radius * cos(endAngle) + center.x(); 344 const VGfloat endY = -radius * sin(endAngle) + center.y(); 345 346 // Fa: large arc flag, makes the difference between SCWARC_TO and LCWARC_TO 347 // respectively SCCWARC_TO and LCCWARC_TO arcs. 348 const bool largeArc = (angleDelta > piDouble); 349 350 // Fs: sweep flag, specifying whether the arc is drawn in increasing (true) 351 // or decreasing (0) direction. No need to calculate this value, as it 352 // we already get it passed as a parameter (Fs == !anticlockwise). 353 354 // Translate the large arc and sweep flags into an OpenVG segment command. 355 // As OpenVG thinks of everything upside down, we need to reverse the 356 // anticlockwise parameter in order to get the specified rotation. 357 const VGubyte segmentCommand = !anticlockwise 358 ? (largeArc ? VG_LCCWARC_TO_ABS : VG_SCCWARC_TO_ABS) 359 : (largeArc ? VG_LCWARC_TO_ABS : VG_SCWARC_TO_ABS); 360 361 // So now, we've got all the parameters in endpoint parameterization format 362 // as OpenVG requires it. Which means we can just pass it like this. 363 const VGubyte pathSegments[] = { 364 hasCurrentPoint() ? VG_LINE_TO_ABS : VG_MOVE_TO_ABS, 365 segmentCommand 366 }; 367 const VGfloat pathData[] = { 368 startX, startY, 369 radius, radius, 0, endX, endY 370 }; 371 372 m_path->makeCompatibleContextCurrent(); 373 vgAppendPathData(m_path->vgPath(), 2, pathSegments, pathData); 374 ASSERT_VG_NO_ERROR(); 375 376 m_path->m_currentPoint.setX(endX); 377 m_path->m_currentPoint.setY(endY); 378 } 379 380 void Path::addRect(const FloatRect& rect) 381 { 382 static const VGubyte pathSegments[] = { 383 VG_MOVE_TO_ABS, 384 VG_HLINE_TO_REL, 385 VG_VLINE_TO_REL, 386 VG_HLINE_TO_REL, 387 VG_CLOSE_PATH 388 }; 389 const VGfloat pathData[] = { 390 rect.x(), rect.y(), 391 rect.width(), 392 rect.height(), 393 -rect.width() 394 }; 395 396 m_path->makeCompatibleContextCurrent(); 397 vgAppendPathData(m_path->vgPath(), 5, pathSegments, pathData); 398 ASSERT_VG_NO_ERROR(); 399 400 m_path->m_currentPoint = m_path->m_subpathStartPoint = rect.location(); 401 } 402 403 void Path::addEllipse(const FloatRect& rect) 404 { 405 static const VGubyte pathSegments[] = { 406 VG_MOVE_TO_ABS, 407 VG_SCCWARC_TO_REL, 408 VG_SCCWARC_TO_REL, 409 VG_CLOSE_PATH 410 }; 411 const VGfloat pathData[] = { 412 rect.x() + rect.width() / 2.0, rect.y(), 413 rect.width() / 2.0, rect.height() / 2.0, 0, 0, rect.height(), 414 rect.width() / 2.0, rect.height() / 2.0, 0, 0, -rect.height() 415 }; 416 417 m_path->makeCompatibleContextCurrent(); 418 vgAppendPathData(m_path->vgPath(), 4, pathSegments, pathData); 419 ASSERT_VG_NO_ERROR(); 420 } 421 422 void Path::clear() 423 { 424 m_path->clear(); 425 } 426 427 bool Path::isEmpty() const 428 { 429 m_path->makeCompatibleContextCurrent(); 430 return !vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS); 431 } 432 433 bool Path::hasCurrentPoint() const 434 { 435 m_path->makeCompatibleContextCurrent(); 436 return vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS) > 0; 437 } 438 439 void Path::apply(void* info, PathApplierFunction function) const 440 { 441 // OpenVG provides no means to retrieve path segment information. 442 // This is *very* unfortunate, we might need to store the segments in 443 // memory if we want to implement this function properly. 444 // See http://www.khronos.org/message_boards/viewtopic.php?f=6&t=1887 445 notImplemented(); 446 } 447 448 void Path::transform(const AffineTransform& transformation) 449 { 450 PlatformPathOpenVG* dst = new PlatformPathOpenVG(); 451 // dst->makeCompatibleContextCurrent() is called by the platform path 452 // constructor, therefore not necessary to call it again here. 453 PainterOpenVG::transformPath(dst->vgPath(), m_path->vgPath(), transformation); 454 delete m_path; 455 m_path = dst; 456 457 m_path->m_currentPoint = transformation.mapPoint(m_path->m_currentPoint); 458 m_path->m_subpathStartPoint = transformation.mapPoint(m_path->m_subpathStartPoint); 459 } 460 461 462 // Path::length(), Path::pointAtLength() and Path::normalAngleAtLength() are 463 // reimplemented here instead of in Path.cpp, because OpenVG has its own 464 // functions and Path::apply() doesn't really work as long as we rely on VGPath 465 // as primary path storage. 466 467 float Path::length() const 468 { 469 m_path->makeCompatibleContextCurrent(); 470 VGfloat length = vgPathLength(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS)); 471 ASSERT_VG_NO_ERROR(); 472 return length; 473 } 474 475 FloatPoint Path::pointAtLength(float length, bool& ok) const 476 { 477 VGfloat x = 0, y = 0; 478 m_path->makeCompatibleContextCurrent(); 479 480 vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS), 481 length, &x, &y, 0, 0); 482 ok = (vgGetError() == VG_NO_ERROR); 483 return FloatPoint(x, y); 484 } 485 486 float Path::normalAngleAtLength(float length, bool& ok) const 487 { 488 VGfloat tangentX, tangentY; 489 m_path->makeCompatibleContextCurrent(); 490 491 vgPointAlongPath(m_path->vgPath(), 0, vgGetParameteri(m_path->vgPath(), VG_PATH_NUM_SEGMENTS), 492 length, 0, 0, &tangentX, &tangentY); 493 ok = (vgGetError() == VG_NO_ERROR); 494 return atan2f(tangentY, tangentX) * 180.0 / piFloat; // convert to degrees 495 } 496 497 } 498