1 /* 2 * Copyright (C) 2006 Zack Rusin <zack (at) kde.org> 3 * 2006 Rob Buis <buis (at) kde.org> 4 * 2009, 2010 Dirk Schulze <krit (at) webkit.org> 5 * 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "config.h" 31 #include "Path.h" 32 33 #include "AffineTransform.h" 34 #include "FloatRect.h" 35 #include "GraphicsContext.h" 36 #include "ImageBuffer.h" 37 #include "PlatformString.h" 38 #include "StrokeStyleApplier.h" 39 #include <QPainterPath> 40 #include <QTransform> 41 #include <QString> 42 #include <wtf/OwnPtr.h> 43 44 #define _USE_MATH_DEFINES 45 #include <math.h> 46 47 #ifndef M_PI 48 # define M_PI 3.14159265358979323846 49 #endif 50 51 namespace WebCore { 52 53 Path::Path() 54 { 55 } 56 57 Path::~Path() 58 { 59 } 60 61 Path::Path(const Path& other) 62 : m_path(other.m_path) 63 { 64 } 65 66 Path& Path::operator=(const Path& other) 67 { 68 m_path = other.m_path; 69 return *this; 70 } 71 72 bool Path::contains(const FloatPoint& point, WindRule rule) const 73 { 74 Qt::FillRule savedRule = m_path.fillRule(); 75 const_cast<QPainterPath*>(&m_path)->setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill); 76 77 bool contains = m_path.contains(point); 78 79 const_cast<QPainterPath*>(&m_path)->setFillRule(savedRule); 80 return contains; 81 } 82 83 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const 84 { 85 ASSERT(applier); 86 87 // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer 88 // on each call. 89 OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1)); 90 GraphicsContext* gc = scratchImage->context(); 91 QPainterPathStroker stroke; 92 applier->strokeStyle(gc); 93 94 QPen pen = gc->pen(); 95 stroke.setWidth(pen.widthF()); 96 stroke.setCapStyle(pen.capStyle()); 97 stroke.setJoinStyle(pen.joinStyle()); 98 stroke.setMiterLimit(pen.miterLimit()); 99 stroke.setDashPattern(pen.dashPattern()); 100 stroke.setDashOffset(pen.dashOffset()); 101 102 return stroke.createStroke(m_path).contains(point); 103 } 104 105 void Path::translate(const FloatSize& size) 106 { 107 QTransform matrix; 108 matrix.translate(size.width(), size.height()); 109 m_path = m_path * matrix; 110 } 111 112 FloatRect Path::boundingRect() const 113 { 114 return m_path.boundingRect(); 115 } 116 117 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) 118 { 119 // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer 120 // on each call. 121 OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1)); 122 GraphicsContext* gc = scratchImage->context(); 123 QPainterPathStroker stroke; 124 if (applier) { 125 applier->strokeStyle(gc); 126 127 QPen pen = gc->pen(); 128 stroke.setWidth(pen.widthF()); 129 stroke.setCapStyle(pen.capStyle()); 130 stroke.setJoinStyle(pen.joinStyle()); 131 stroke.setMiterLimit(pen.miterLimit()); 132 stroke.setDashPattern(pen.dashPattern()); 133 stroke.setDashOffset(pen.dashOffset()); 134 } 135 return stroke.createStroke(m_path).boundingRect(); 136 } 137 138 void Path::moveTo(const FloatPoint& point) 139 { 140 m_path.moveTo(point); 141 } 142 143 void Path::addLineTo(const FloatPoint& p) 144 { 145 m_path.lineTo(p); 146 } 147 148 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p) 149 { 150 m_path.quadTo(cp, p); 151 } 152 153 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p) 154 { 155 m_path.cubicTo(cp1, cp2, p); 156 } 157 158 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) 159 { 160 FloatPoint p0(m_path.currentPosition()); 161 162 if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) { 163 m_path.lineTo(p1); 164 return; 165 } 166 167 FloatPoint p1p0((p0.x() - p1.x()), (p0.y() - p1.y())); 168 FloatPoint p1p2((p2.x() - p1.x()), (p2.y() - p1.y())); 169 float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y()); 170 float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y()); 171 172 double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length); 173 // all points on a line logic 174 if (cos_phi == -1) { 175 m_path.lineTo(p1); 176 return; 177 } 178 if (cos_phi == 1) { 179 // add infinite far away point 180 unsigned int max_length = 65535; 181 double factor_max = max_length / p1p0_length; 182 FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y())); 183 m_path.lineTo(ep); 184 return; 185 } 186 187 float tangent = radius / tan(acos(cos_phi) / 2); 188 float factor_p1p0 = tangent / p1p0_length; 189 FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y())); 190 191 FloatPoint orth_p1p0(p1p0.y(), -p1p0.x()); 192 float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y()); 193 float factor_ra = radius / orth_p1p0_length; 194 195 // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0 196 double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length); 197 if (cos_alpha < 0.f) 198 orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); 199 200 FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y())); 201 202 // calculate angles for addArc 203 orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); 204 float sa = acos(orth_p1p0.x() / orth_p1p0_length); 205 if (orth_p1p0.y() < 0.f) 206 sa = 2 * piDouble - sa; 207 208 // anticlockwise logic 209 bool anticlockwise = false; 210 211 float factor_p1p2 = tangent / p1p2_length; 212 FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y())); 213 FloatPoint orth_p1p2((t_p1p2.x() - p.x()), (t_p1p2.y() - p.y())); 214 float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y()); 215 float ea = acos(orth_p1p2.x() / orth_p1p2_length); 216 if (orth_p1p2.y() < 0) 217 ea = 2 * piDouble - ea; 218 if ((sa > ea) && ((sa - ea) < piDouble)) 219 anticlockwise = true; 220 if ((sa < ea) && ((ea - sa) > piDouble)) 221 anticlockwise = true; 222 223 m_path.lineTo(t_p1p0); 224 225 addArc(p, radius, sa, ea, anticlockwise); 226 } 227 228 void Path::closeSubpath() 229 { 230 m_path.closeSubpath(); 231 } 232 233 #define DEGREES(t) ((t) * 180.0 / M_PI) 234 void Path::addArc(const FloatPoint& p, float r, float sar, float ear, bool anticlockwise) 235 { 236 qreal xc = p.x(); 237 qreal yc = p.y(); 238 qreal radius = r; 239 240 241 //### HACK 242 // In Qt we don't switch the coordinate system for degrees 243 // and still use the 0,0 as bottom left for degrees so we need 244 // to switch 245 sar = -sar; 246 ear = -ear; 247 anticlockwise = !anticlockwise; 248 //end hack 249 250 float sa = DEGREES(sar); 251 float ea = DEGREES(ear); 252 253 double span = 0; 254 255 double xs = xc - radius; 256 double ys = yc - radius; 257 double width = radius*2; 258 double height = radius*2; 259 260 if (!anticlockwise && (ea < sa)) 261 span += 360; 262 else if (anticlockwise && (sa < ea)) 263 span -= 360; 264 265 // this is also due to switched coordinate system 266 // we would end up with a 0 span instead of 360 267 if (!(qFuzzyCompare(span + (ea - sa) + 1, 1.0) && 268 qFuzzyCompare(qAbs(span), 360.0))) { 269 span += ea - sa; 270 } 271 272 m_path.moveTo(QPointF(xc + radius * cos(sar), 273 yc - radius * sin(sar))); 274 275 m_path.arcTo(xs, ys, width, height, sa, span); 276 } 277 278 void Path::addRect(const FloatRect& r) 279 { 280 m_path.addRect(r.x(), r.y(), r.width(), r.height()); 281 } 282 283 void Path::addEllipse(const FloatRect& r) 284 { 285 m_path.addEllipse(r.x(), r.y(), r.width(), r.height()); 286 } 287 288 void Path::clear() 289 { 290 m_path = QPainterPath(); 291 } 292 293 bool Path::isEmpty() const 294 { 295 // Don't use QPainterPath::isEmpty(), as that also returns true if there's only 296 // one initial MoveTo element in the path. 297 return !m_path.elementCount(); 298 } 299 300 bool Path::hasCurrentPoint() const 301 { 302 return !isEmpty(); 303 } 304 305 String Path::debugString() const 306 { 307 QString ret; 308 for (int i = 0; i < m_path.elementCount(); ++i) { 309 const QPainterPath::Element &cur = m_path.elementAt(i); 310 311 switch (cur.type) { 312 case QPainterPath::MoveToElement: 313 ret += QString(QLatin1String("M%1,%2 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2); 314 break; 315 case QPainterPath::LineToElement: 316 ret += QString(QLatin1String("L%1,%2 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2); 317 break; 318 case QPainterPath::CurveToElement: 319 { 320 const QPainterPath::Element &c1 = m_path.elementAt(i + 1); 321 const QPainterPath::Element &c2 = m_path.elementAt(i + 2); 322 323 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement); 324 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement); 325 326 ret += QString(QLatin1String("C%1,%2,%3,%4,%5,%6 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2).arg(c1.x, 0, 'f', 2) 327 .arg(c1.y, 0, 'f', 2).arg(c2.x, 0, 'f', 2).arg(c2.y, 0, 'f', 2); 328 i += 2; 329 break; 330 } 331 case QPainterPath::CurveToDataElement: 332 Q_ASSERT(false); 333 break; 334 } 335 } 336 337 return ret.trimmed(); 338 } 339 340 void Path::apply(void* info, PathApplierFunction function) const 341 { 342 PathElement pelement; 343 FloatPoint points[3]; 344 pelement.points = points; 345 for (int i = 0; i < m_path.elementCount(); ++i) { 346 const QPainterPath::Element& cur = m_path.elementAt(i); 347 348 switch (cur.type) { 349 case QPainterPath::MoveToElement: 350 pelement.type = PathElementMoveToPoint; 351 pelement.points[0] = QPointF(cur); 352 function(info, &pelement); 353 break; 354 case QPainterPath::LineToElement: 355 pelement.type = PathElementAddLineToPoint; 356 pelement.points[0] = QPointF(cur); 357 function(info, &pelement); 358 break; 359 case QPainterPath::CurveToElement: 360 { 361 const QPainterPath::Element& c1 = m_path.elementAt(i + 1); 362 const QPainterPath::Element& c2 = m_path.elementAt(i + 2); 363 364 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement); 365 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement); 366 367 pelement.type = PathElementAddCurveToPoint; 368 pelement.points[0] = QPointF(cur); 369 pelement.points[1] = QPointF(c1); 370 pelement.points[2] = QPointF(c2); 371 function(info, &pelement); 372 373 i += 2; 374 break; 375 } 376 case QPainterPath::CurveToDataElement: 377 Q_ASSERT(false); 378 } 379 } 380 } 381 382 void Path::transform(const AffineTransform& transform) 383 { 384 m_path = QTransform(transform).map(m_path); 385 } 386 387 } 388 389 // vim: ts=4 sw=4 et 390