1/* 2 Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com> 3 Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <wildfox@kde.org> 4 2004, 2005, 2006 Rob Buis <buis@kde.org> 5 2005, 2007 Apple Inc. All Rights reserved. 6 2007 Alp Toker <alp@atoker.com> 7 2008 Dirk Schulze <krit@webkit.org> 8 2011 Igalia S.L. 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Library General Public 12 License as published by the Free Software Foundation; either 13 version 2 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Library General Public License for more details. 19 20 You should have received a copy of the GNU Library General Public License 21 aint with this library; see the file COPYING.LIB. If not, write to 22 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 23 Boston, MA 02110-1301, USA. 24*/ 25 26#include "config.h" 27#include "Path.h" 28 29#include "AffineTransform.h" 30#include "FloatRect.h" 31#include "GraphicsContext.h" 32#include "NotImplemented.h" 33#include "OwnPtrCairo.h" 34#include "PlatformPathCairo.h" 35#include "StrokeStyleApplier.h" 36#include <cairo.h> 37#include <math.h> 38#include <wtf/MathExtras.h> 39#include <wtf/text/WTFString.h> 40 41namespace WebCore { 42 43Path::Path() 44 : m_path(0) 45{ 46} 47 48Path::~Path() 49{ 50 if (m_path) 51 delete m_path; 52} 53 54Path::Path(const Path& other) 55 : m_path(0) 56{ 57 if (other.isNull()) 58 return; 59 60 cairo_t* cr = ensurePlatformPath()->context(); 61 OwnPtr<cairo_path_t> pathCopy = adoptPtr(cairo_copy_path(other.platformPath()->context())); 62 cairo_append_path(cr, pathCopy.get()); 63} 64 65PlatformPathPtr Path::ensurePlatformPath() 66{ 67 if (!m_path) 68 m_path = new CairoPath(); 69 return m_path; 70} 71 72Path& Path::operator=(const Path& other) 73{ 74 if (&other == this) 75 return *this; 76 77 if (other.isNull()) { 78 if (m_path) { 79 delete m_path; 80 m_path = 0; 81 } 82 } else { 83 clear(); 84 cairo_t* cr = ensurePlatformPath()->context(); 85 OwnPtr<cairo_path_t> pathCopy = adoptPtr(cairo_copy_path(other.platformPath()->context())); 86 cairo_append_path(cr, pathCopy.get()); 87 } 88 89 return *this; 90} 91 92void Path::clear() 93{ 94 if (isNull()) 95 return; 96 97 cairo_t* cr = platformPath()->context(); 98 cairo_new_path(cr); 99} 100 101bool Path::isEmpty() const 102{ 103 return isNull() || !cairo_has_current_point(platformPath()->context()); 104} 105 106bool Path::hasCurrentPoint() const 107{ 108 return !isEmpty(); 109} 110 111FloatPoint Path::currentPoint() const 112{ 113 if (isNull()) 114 return FloatPoint(); 115 116 // FIXME: Is this the correct way? 117 double x; 118 double y; 119 cairo_get_current_point(platformPath()->context(), &x, &y); 120 return FloatPoint(x, y); 121} 122 123void Path::translate(const FloatSize& p) 124{ 125 cairo_t* cr = ensurePlatformPath()->context(); 126 cairo_translate(cr, -p.width(), -p.height()); 127} 128 129void Path::moveTo(const FloatPoint& p) 130{ 131 cairo_t* cr = ensurePlatformPath()->context(); 132 cairo_move_to(cr, p.x(), p.y()); 133} 134 135void Path::addLineTo(const FloatPoint& p) 136{ 137 cairo_t* cr = ensurePlatformPath()->context(); 138 cairo_line_to(cr, p.x(), p.y()); 139} 140 141void Path::addRect(const FloatRect& rect) 142{ 143 cairo_t* cr = ensurePlatformPath()->context(); 144 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 145} 146 147/* 148 * inspired by libsvg-cairo 149 */ 150void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& point) 151{ 152 cairo_t* cr = ensurePlatformPath()->context(); 153 double x, y; 154 double x1 = controlPoint.x(); 155 double y1 = controlPoint.y(); 156 double x2 = point.x(); 157 double y2 = point.y(); 158 cairo_get_current_point(cr, &x, &y); 159 cairo_curve_to(cr, 160 x + 2.0 / 3.0 * (x1 - x), y + 2.0 / 3.0 * (y1 - y), 161 x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2), 162 x2, y2); 163} 164 165void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& controlPoint3) 166{ 167 cairo_t* cr = ensurePlatformPath()->context(); 168 cairo_curve_to(cr, controlPoint1.x(), controlPoint1.y(), 169 controlPoint2.x(), controlPoint2.y(), 170 controlPoint3.x(), controlPoint3.y()); 171} 172 173void Path::addArc(const FloatPoint& p, float r, float startAngle, float endAngle, bool anticlockwise) 174{ 175 // http://bugs.webkit.org/show_bug.cgi?id=16449 176 // cairo_arc() functions hang or crash when passed inf as radius or start/end angle 177 if (!std::isfinite(r) || !std::isfinite(startAngle) || !std::isfinite(endAngle)) 178 return; 179 180 cairo_t* cr = ensurePlatformPath()->context(); 181 float sweep = endAngle - startAngle; 182 const float twoPI = 2 * piFloat; 183 if ((sweep <= -twoPI || sweep >= twoPI) 184 && ((anticlockwise && (endAngle < startAngle)) || (!anticlockwise && (startAngle < endAngle)))) { 185 if (anticlockwise) 186 cairo_arc_negative(cr, p.x(), p.y(), r, startAngle, startAngle - twoPI); 187 else 188 cairo_arc(cr, p.x(), p.y(), r, startAngle, startAngle + twoPI); 189 cairo_new_sub_path(cr); 190 cairo_arc(cr, p.x(), p.y(), r, endAngle, endAngle); 191 } else { 192 if (anticlockwise) 193 cairo_arc_negative(cr, p.x(), p.y(), r, startAngle, endAngle); 194 else 195 cairo_arc(cr, p.x(), p.y(), r, startAngle, endAngle); 196 } 197} 198 199static inline float areaOfTriangleFormedByPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& p3) 200{ 201 return p1.x() * (p2.y() - p3.y()) + p2.x() * (p3.y() - p1.y()) + p3.x() * (p1.y() - p2.y()); 202} 203 204void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) 205{ 206 // FIXME: Why do we return if the path is empty? Can't a path start with an arc? 207 if (isEmpty()) 208 return; 209 210 cairo_t* cr = platformPath()->context(); 211 212 double x0, y0; 213 cairo_get_current_point(cr, &x0, &y0); 214 FloatPoint p0(x0, y0); 215 216 // Draw only a straight line to p1 if any of the points are equal or the radius is zero 217 // or the points are collinear (triangle that the points form has area of zero value). 218 if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || !radius 219 || !areaOfTriangleFormedByPoints(p0, p1, p2)) { 220 cairo_line_to(cr, p1.x(), p1.y()); 221 return; 222 } 223 224 FloatPoint p1p0((p0.x() - p1.x()),(p0.y() - p1.y())); 225 FloatPoint p1p2((p2.x() - p1.x()),(p2.y() - p1.y())); 226 float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y()); 227 float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y()); 228 229 double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length); 230 // all points on a line logic 231 if (cos_phi == -1) { 232 cairo_line_to(cr, p1.x(), p1.y()); 233 return; 234 } 235 if (cos_phi == 1) { 236 // add infinite far away point 237 unsigned int max_length = 65535; 238 double factor_max = max_length / p1p0_length; 239 FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y())); 240 cairo_line_to(cr, ep.x(), ep.y()); 241 return; 242 } 243 244 float tangent = radius / tan(acos(cos_phi) / 2); 245 float factor_p1p0 = tangent / p1p0_length; 246 FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y())); 247 248 FloatPoint orth_p1p0(p1p0.y(), -p1p0.x()); 249 float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y()); 250 float factor_ra = radius / orth_p1p0_length; 251 252 // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0 253 double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length); 254 if (cos_alpha < 0.f) 255 orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); 256 257 FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y())); 258 259 // calculate angles for addArc 260 orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); 261 float sa = acos(orth_p1p0.x() / orth_p1p0_length); 262 if (orth_p1p0.y() < 0.f) 263 sa = 2 * piDouble - sa; 264 265 // anticlockwise logic 266 bool anticlockwise = false; 267 268 float factor_p1p2 = tangent / p1p2_length; 269 FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y())); 270 FloatPoint orth_p1p2((t_p1p2.x() - p.x()),(t_p1p2.y() - p.y())); 271 float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y()); 272 float ea = acos(orth_p1p2.x() / orth_p1p2_length); 273 if (orth_p1p2.y() < 0) 274 ea = 2 * piDouble - ea; 275 if ((sa > ea) && ((sa - ea) < piDouble)) 276 anticlockwise = true; 277 if ((sa < ea) && ((ea - sa) > piDouble)) 278 anticlockwise = true; 279 280 cairo_line_to(cr, t_p1p0.x(), t_p1p0.y()); 281 282 addArc(p, radius, sa, ea, anticlockwise); 283} 284 285void Path::addEllipse(const FloatRect& rect) 286{ 287 cairo_t* cr = ensurePlatformPath()->context(); 288 cairo_save(cr); 289 float yRadius = .5 * rect.height(); 290 float xRadius = .5 * rect.width(); 291 cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius); 292 cairo_scale(cr, xRadius, yRadius); 293 cairo_arc(cr, 0., 0., 1., 0., 2 * piDouble); 294 cairo_restore(cr); 295} 296 297void Path::addPath(const Path&, const AffineTransform&) 298{ 299 // FIXME: This should probably be very similar to Path::transform. 300 notImplemented(); 301} 302 303void Path::closeSubpath() 304{ 305 cairo_t* cr = ensurePlatformPath()->context(); 306 cairo_close_path(cr); 307} 308 309FloatRect Path::boundingRect() const 310{ 311 // Should this be isEmpty() or can an empty path have a non-zero origin? 312 if (isNull()) 313 return FloatRect(); 314 315 cairo_t* cr = platformPath()->context(); 316 double x0, x1, y0, y1; 317 cairo_path_extents(cr, &x0, &y0, &x1, &y1); 318 return FloatRect(x0, y0, x1 - x0, y1 - y0); 319} 320 321FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const 322{ 323 // Should this be isEmpty() or can an empty path have a non-zero origin? 324 if (isNull()) 325 return FloatRect(); 326 327 cairo_t* cr = platformPath()->context(); 328 if (applier) { 329 GraphicsContext gc(cr); 330 applier->strokeStyle(&gc); 331 } 332 333 double x0, x1, y0, y1; 334 cairo_stroke_extents(cr, &x0, &y0, &x1, &y1); 335 return FloatRect(x0, y0, x1 - x0, y1 - y0); 336} 337 338bool Path::contains(const FloatPoint& point, WindRule rule) const 339{ 340 if (isNull() || !std::isfinite(point.x()) || !std::isfinite(point.y())) 341 return false; 342 cairo_t* cr = platformPath()->context(); 343 cairo_fill_rule_t cur = cairo_get_fill_rule(cr); 344 cairo_set_fill_rule(cr, rule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 345 bool contains = cairo_in_fill(cr, point.x(), point.y()); 346 cairo_set_fill_rule(cr, cur); 347 return contains; 348} 349 350bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const 351{ 352 if (isNull()) 353 return false; 354 355 ASSERT(applier); 356 cairo_t* cr = platformPath()->context(); 357 GraphicsContext gc(cr); 358 applier->strokeStyle(&gc); 359 360 return cairo_in_stroke(cr, point.x(), point.y()); 361} 362 363void Path::apply(void* info, PathApplierFunction function) const 364{ 365 if (isNull()) 366 return; 367 368 cairo_t* cr = platformPath()->context(); 369 OwnPtr<cairo_path_t> pathCopy = adoptPtr(cairo_copy_path(cr)); 370 cairo_path_data_t* data; 371 PathElement pelement; 372 FloatPoint points[3]; 373 pelement.points = points; 374 375 for (int i = 0; i < pathCopy->num_data; i += pathCopy->data[i].header.length) { 376 data = &pathCopy->data[i]; 377 switch (data->header.type) { 378 case CAIRO_PATH_MOVE_TO: 379 pelement.type = PathElementMoveToPoint; 380 pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); 381 function(info, &pelement); 382 break; 383 case CAIRO_PATH_LINE_TO: 384 pelement.type = PathElementAddLineToPoint; 385 pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); 386 function(info, &pelement); 387 break; 388 case CAIRO_PATH_CURVE_TO: 389 pelement.type = PathElementAddCurveToPoint; 390 pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); 391 pelement.points[1] = FloatPoint(data[2].point.x,data[2].point.y); 392 pelement.points[2] = FloatPoint(data[3].point.x,data[3].point.y); 393 function(info, &pelement); 394 break; 395 case CAIRO_PATH_CLOSE_PATH: 396 pelement.type = PathElementCloseSubpath; 397 function(info, &pelement); 398 break; 399 } 400 } 401} 402 403void Path::transform(const AffineTransform& trans) 404{ 405 cairo_t* cr = ensurePlatformPath()->context(); 406 cairo_matrix_t c_matrix = cairo_matrix_t(trans); 407 cairo_matrix_invert(&c_matrix); 408 cairo_transform(cr, &c_matrix); 409} 410 411} // namespace WebCore 412