1/* 2 * Copyright (C) 2003, 2006 Apple Inc. All rights reserved. 3 * 2006, 2008 Rob Buis <buis@kde.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "Path.h" 29 30#if USE(CG) 31 32#include "AffineTransform.h" 33#include "FloatRect.h" 34#include "GraphicsContext.h" 35#include "IntRect.h" 36#include "StrokeStyleApplier.h" 37#include <CoreGraphics/CoreGraphics.h> 38#include <wtf/MathExtras.h> 39#include <wtf/RetainPtr.h> 40#include <wtf/text/WTFString.h> 41 42#if PLATFORM(COCOA) 43#include "WebCoreSystemInterface.h" 44#endif 45 46#if PLATFORM(WIN) 47#include <WebKitSystemInterface/WebKitSystemInterface.h> 48#endif 49 50namespace WebCore { 51 52static size_t putBytesNowhere(void*, const void*, size_t count) 53{ 54 return count; 55} 56 57static CGContextRef createScratchContext() 58{ 59 CGDataConsumerCallbacks callbacks = { putBytesNowhere, 0 }; 60 RetainPtr<CGDataConsumerRef> consumer = adoptCF(CGDataConsumerCreate(0, &callbacks)); 61 CGContextRef context = CGPDFContextCreate(consumer.get(), 0, 0); 62 63 CGFloat black[4] = { 0, 0, 0, 1 }; 64 CGContextSetFillColor(context, black); 65 CGContextSetStrokeColor(context, black); 66 67 return context; 68} 69 70static inline CGContextRef scratchContext() 71{ 72 static CGContextRef context = createScratchContext(); 73 return context; 74} 75 76Path::Path() 77 : m_path(0) 78{ 79} 80 81Path::Path(RetainPtr<CGMutablePathRef> p) 82 : m_path(p.leakRef()) 83{ 84} 85 86Path::~Path() 87{ 88 if (m_path) 89 CGPathRelease(m_path); 90} 91 92PlatformPathPtr Path::ensurePlatformPath() 93{ 94 if (!m_path) 95 m_path = CGPathCreateMutable(); 96 return m_path; 97} 98 99Path::Path(const Path& other) 100{ 101 m_path = other.m_path ? CGPathCreateMutableCopy(other.m_path) : 0; 102} 103 104Path& Path::operator=(const Path& other) 105{ 106 CGMutablePathRef path = other.m_path ? CGPathCreateMutableCopy(other.m_path) : 0; 107 if (m_path) 108 CGPathRelease(m_path); 109 m_path = path; 110 return *this; 111} 112 113static void copyClosingSubpathsApplierFunction(void* info, const CGPathElement* element) 114{ 115 CGMutablePathRef path = static_cast<CGMutablePathRef>(info); 116 CGPoint* points = element->points; 117 118 switch (element->type) { 119 case kCGPathElementMoveToPoint: 120 if (!CGPathIsEmpty(path)) // to silence a warning when trying to close an empty path 121 CGPathCloseSubpath(path); // This is the only change from CGPathCreateMutableCopy 122 CGPathMoveToPoint(path, 0, points[0].x, points[0].y); 123 break; 124 case kCGPathElementAddLineToPoint: 125 CGPathAddLineToPoint(path, 0, points[0].x, points[0].y); 126 break; 127 case kCGPathElementAddQuadCurveToPoint: 128 CGPathAddQuadCurveToPoint(path, 0, points[0].x, points[0].y, points[1].x, points[1].y); 129 break; 130 case kCGPathElementAddCurveToPoint: 131 CGPathAddCurveToPoint(path, 0, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); 132 break; 133 case kCGPathElementCloseSubpath: 134 CGPathCloseSubpath(path); 135 break; 136 } 137} 138 139static CGMutablePathRef copyCGPathClosingSubpaths(CGPathRef originalPath) 140{ 141 CGMutablePathRef path = CGPathCreateMutable(); 142 CGPathApply(originalPath, path, copyClosingSubpathsApplierFunction); 143 CGPathCloseSubpath(path); 144 return path; 145} 146 147bool Path::contains(const FloatPoint &point, WindRule rule) const 148{ 149 if (isNull()) 150 return false; 151 152 if (!fastBoundingRect().contains(point)) 153 return false; 154 155 // CGPathContainsPoint returns false for non-closed paths, as a work-around, we copy and close the path first. Radar 4758998 asks for a better CG API to use 156 RetainPtr<CGMutablePathRef> path = adoptCF(copyCGPathClosingSubpaths(m_path)); 157 bool ret = CGPathContainsPoint(path.get(), 0, point, rule == RULE_EVENODD ? true : false); 158 return ret; 159} 160 161bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const 162{ 163 if (isNull()) 164 return false; 165 166 ASSERT(applier); 167 168 CGContextRef context = scratchContext(); 169 170 CGContextSaveGState(context); 171 CGContextBeginPath(context); 172 CGContextAddPath(context, platformPath()); 173 174 GraphicsContext gc(context); 175 applier->strokeStyle(&gc); 176 177 bool hitSuccess = CGContextPathContainsPoint(context, point, kCGPathStroke); 178 CGContextRestoreGState(context); 179 180 return hitSuccess; 181} 182 183void Path::translate(const FloatSize& size) 184{ 185 CGAffineTransform translation = CGAffineTransformMake(1, 0, 0, 1, size.width(), size.height()); 186 CGMutablePathRef newPath = CGPathCreateMutable(); 187 // FIXME: This is potentially wasteful to allocate an empty path only to create a transformed copy. 188 CGPathAddPath(newPath, &translation, ensurePlatformPath()); 189 CGPathRelease(m_path); 190 m_path = newPath; 191} 192 193FloatRect Path::boundingRect() const 194{ 195 if (isNull()) 196 return CGRectZero; 197 198 // CGPathGetBoundingBox includes the path's control points, CGPathGetPathBoundingBox 199 // does not, but only exists on 10.6 and above. 200 201 CGRect bound = CGPathGetPathBoundingBox(m_path); 202 return CGRectIsNull(bound) ? CGRectZero : bound; 203} 204 205FloatRect Path::fastBoundingRect() const 206{ 207 if (isNull()) 208 return CGRectZero; 209 CGRect bound = CGPathGetBoundingBox(m_path); 210 return CGRectIsNull(bound) ? CGRectZero : bound; 211} 212 213FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const 214{ 215 if (isNull()) 216 return CGRectZero; 217 218 CGContextRef context = scratchContext(); 219 220 CGContextSaveGState(context); 221 CGContextBeginPath(context); 222 CGContextAddPath(context, platformPath()); 223 224 if (applier) { 225 GraphicsContext graphicsContext(context); 226 applier->strokeStyle(&graphicsContext); 227 } 228 229 CGContextReplacePathWithStrokedPath(context); 230 CGRect box = CGContextIsPathEmpty(context) ? CGRectZero : CGContextGetPathBoundingBox(context); 231 CGContextRestoreGState(context); 232 233 return CGRectIsNull(box) ? CGRectZero : box; 234} 235 236void Path::moveTo(const FloatPoint& point) 237{ 238 CGPathMoveToPoint(ensurePlatformPath(), 0, point.x(), point.y()); 239} 240 241void Path::addLineTo(const FloatPoint& p) 242{ 243 CGPathAddLineToPoint(ensurePlatformPath(), 0, p.x(), p.y()); 244} 245 246void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p) 247{ 248 CGPathAddQuadCurveToPoint(ensurePlatformPath(), 0, cp.x(), cp.y(), p.x(), p.y()); 249} 250 251void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p) 252{ 253 CGPathAddCurveToPoint(ensurePlatformPath(), 0, cp1.x(), cp1.y(), cp2.x(), cp2.y(), p.x(), p.y()); 254} 255 256void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) 257{ 258 CGPathAddArcToPoint(ensurePlatformPath(), 0, p1.x(), p1.y(), p2.x(), p2.y(), radius); 259} 260 261void Path::platformAddPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) 262{ 263#if PLATFORM(COCOA) 264 bool equalWidths = (topLeftRadius.width() == topRightRadius.width() && topRightRadius.width() == bottomLeftRadius.width() && bottomLeftRadius.width() == bottomRightRadius.width()); 265 bool equalHeights = (topLeftRadius.height() == bottomLeftRadius.height() && bottomLeftRadius.height() == topRightRadius.height() && topRightRadius.height() == bottomRightRadius.height()); 266 267 if (equalWidths && equalHeights) { 268 // Ensure that CG can render the rounded rect. 269 CGFloat radiusWidth = topLeftRadius.width(); 270 CGFloat radiusHeight = topLeftRadius.height(); 271 CGRect rectToDraw = rect; 272 CGFloat rectWidth = CGRectGetWidth(rectToDraw); 273 CGFloat rectHeight = CGRectGetHeight(rectToDraw); 274 if (rectWidth < 2 * radiusWidth) 275 radiusWidth = rectWidth / 2 - std::numeric_limits<CGFloat>::epsilon(); 276 if (rectHeight < 2 * radiusHeight) 277 radiusHeight = rectHeight / 2 - std::numeric_limits<CGFloat>::epsilon(); 278 wkCGPathAddRoundedRect(ensurePlatformPath(), 0, rectToDraw, radiusWidth, radiusHeight); 279 return; 280 } 281#endif 282 283 addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); 284} 285 286void Path::closeSubpath() 287{ 288 // FIXME: Unclear if close commands should have meaning for a null path. 289 if (isNull()) 290 return; 291 292 CGPathCloseSubpath(m_path); 293} 294 295void Path::addArc(const FloatPoint& p, float r, float sa, float ea, bool clockwise) 296{ 297 // Workaround for <rdar://problem/5189233> CGPathAddArc hangs or crashes when passed inf as start or end angle 298 if (std::isfinite(sa) && std::isfinite(ea)) 299 CGPathAddArc(ensurePlatformPath(), 0, p.x(), p.y(), r, sa, ea, clockwise); 300} 301 302void Path::addRect(const FloatRect& r) 303{ 304 CGPathAddRect(ensurePlatformPath(), 0, r); 305} 306 307void Path::addEllipse(const FloatRect& r) 308{ 309 CGPathAddEllipseInRect(ensurePlatformPath(), 0, r); 310} 311 312void Path::addPath(const Path& path, const AffineTransform& transform) 313{ 314 if (!path.platformPath()) 315 return; 316 317 if (!transform.isInvertible()) 318 return; 319 320 CGAffineTransform transformCG = transform; 321 // CG doesn't allow adding a path to itself. Optimize for the common case 322 // and copy the path for the self referencing case. 323 if (ensurePlatformPath() != path.platformPath()) { 324 CGPathAddPath(ensurePlatformPath(), &transformCG, path.platformPath()); 325 return; 326 } 327 CGPathRef pathCopy = CGPathCreateCopy(path.platformPath()); 328 CGPathAddPath(ensurePlatformPath(), &transformCG, path.platformPath()); 329 CGPathRelease(pathCopy); 330} 331 332 333void Path::clear() 334{ 335 if (isNull()) 336 return; 337 338 CGPathRelease(m_path); 339 m_path = CGPathCreateMutable(); 340} 341 342bool Path::isEmpty() const 343{ 344 return isNull() || CGPathIsEmpty(m_path); 345} 346 347bool Path::hasCurrentPoint() const 348{ 349 return !isEmpty(); 350} 351 352FloatPoint Path::currentPoint() const 353{ 354 if (isNull()) 355 return FloatPoint(); 356 return CGPathGetCurrentPoint(m_path); 357} 358 359struct PathApplierInfo { 360 void* info; 361 PathApplierFunction function; 362}; 363 364static void CGPathApplierToPathApplier(void *info, const CGPathElement *element) 365{ 366 PathApplierInfo* pinfo = (PathApplierInfo*)info; 367 FloatPoint points[3]; 368 PathElement pelement; 369 pelement.type = (PathElementType)element->type; 370 pelement.points = points; 371 CGPoint* cgPoints = element->points; 372 switch (element->type) { 373 case kCGPathElementMoveToPoint: 374 case kCGPathElementAddLineToPoint: 375 points[0] = cgPoints[0]; 376 break; 377 case kCGPathElementAddQuadCurveToPoint: 378 points[0] = cgPoints[0]; 379 points[1] = cgPoints[1]; 380 break; 381 case kCGPathElementAddCurveToPoint: 382 points[0] = cgPoints[0]; 383 points[1] = cgPoints[1]; 384 points[2] = cgPoints[2]; 385 break; 386 case kCGPathElementCloseSubpath: 387 break; 388 } 389 pinfo->function(pinfo->info, &pelement); 390} 391 392void Path::apply(void* info, PathApplierFunction function) const 393{ 394 if (isNull()) 395 return; 396 397 PathApplierInfo pinfo; 398 pinfo.info = info; 399 pinfo.function = function; 400 CGPathApply(m_path, &pinfo, CGPathApplierToPathApplier); 401} 402 403void Path::transform(const AffineTransform& transform) 404{ 405 if (transform.isIdentity() || isEmpty()) 406 return; 407 408 CGMutablePathRef path = CGPathCreateMutable(); 409 CGAffineTransform transformCG = transform; 410 CGPathAddPath(path, &transformCG, m_path); 411 CGPathRelease(m_path); 412 m_path = path; 413} 414 415} 416 417#endif // USE(CG) 418