1 // Copyright (c) 2008, Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 #include "config.h" 31 #include "Path.h" 32 33 #include "AffineTransform.h" 34 #include "FloatRect.h" 35 #include "ImageBuffer.h" 36 #include "StrokeStyleApplier.h" 37 38 #include "SkPath.h" 39 #include "SkRegion.h" 40 #include "SkiaUtils.h" 41 42 #include <wtf/MathExtras.h> 43 44 namespace WebCore { 45 46 Path::Path() 47 { 48 m_path = new SkPath; 49 } 50 51 Path::Path(const Path& other) 52 { 53 m_path = new SkPath(*other.m_path); 54 } 55 56 Path::~Path() 57 { 58 delete m_path; 59 } 60 61 Path& Path::operator=(const Path& other) 62 { 63 *m_path = *other.m_path; 64 return *this; 65 } 66 67 bool Path::isEmpty() const 68 { 69 return m_path->isEmpty(); 70 } 71 72 bool Path::hasCurrentPoint() const 73 { 74 return m_path->getPoints(NULL, 0) != 0; 75 } 76 77 bool Path::contains(const FloatPoint& point, WindRule rule) const 78 { 79 return SkPathContainsPoint(m_path, point, 80 rule == RULE_NONZERO ? SkPath::kWinding_FillType : SkPath::kEvenOdd_FillType); 81 } 82 83 void Path::translate(const FloatSize& size) 84 { 85 m_path->offset(WebCoreFloatToSkScalar(size.width()), WebCoreFloatToSkScalar(size.height())); 86 } 87 88 FloatRect Path::boundingRect() const 89 { 90 return m_path->getBounds(); 91 } 92 93 void Path::moveTo(const FloatPoint& point) 94 { 95 m_path->moveTo(point); 96 } 97 98 void Path::addLineTo(const FloatPoint& point) 99 { 100 m_path->lineTo(point); 101 } 102 103 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& ep) 104 { 105 m_path->quadTo(cp, ep); 106 } 107 108 void Path::addBezierCurveTo(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& ep) 109 { 110 m_path->cubicTo(p1, p2, ep); 111 } 112 113 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) 114 { 115 m_path->arcTo(p1, p2, WebCoreFloatToSkScalar(radius)); 116 } 117 118 void Path::closeSubpath() 119 { 120 m_path->close(); 121 } 122 123 void Path::addArc(const FloatPoint& p, float r, float sa, float ea, bool anticlockwise) { 124 SkScalar cx = WebCoreFloatToSkScalar(p.x()); 125 SkScalar cy = WebCoreFloatToSkScalar(p.y()); 126 SkScalar radius = WebCoreFloatToSkScalar(r); 127 SkScalar s360 = SkIntToScalar(360); 128 129 SkRect oval; 130 oval.set(cx - radius, cy - radius, cx + radius, cy + radius); 131 132 float sweep = ea - sa; 133 SkScalar startDegrees = WebCoreFloatToSkScalar(sa * 180 / piFloat); 134 SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat); 135 // Check for a circle. 136 if (sweepDegrees >= s360 || sweepDegrees <= -s360) { 137 // Move to the start position (0 sweep means we add a single point). 138 m_path->arcTo(oval, startDegrees, 0, false); 139 // Draw the circle. 140 m_path->addOval(oval); 141 // Force a moveTo the end position. 142 m_path->arcTo(oval, startDegrees + sweepDegrees, 0, true); 143 } else { 144 // Counterclockwise arcs should be drawn with negative sweeps, while 145 // clockwise arcs should be drawn with positive sweeps. Check to see 146 // if the situation is reversed and correct it by adding or subtracting 147 // a full circle 148 if (anticlockwise && sweepDegrees > 0) { 149 sweepDegrees -= s360; 150 } else if (!anticlockwise && sweepDegrees < 0) { 151 sweepDegrees += s360; 152 } 153 154 m_path->arcTo(oval, startDegrees, sweepDegrees, false); 155 } 156 } 157 158 void Path::addRect(const FloatRect& rect) 159 { 160 m_path->addRect(rect); 161 } 162 163 void Path::addEllipse(const FloatRect& rect) 164 { 165 m_path->addOval(rect); 166 } 167 168 void Path::clear() 169 { 170 m_path->reset(); 171 } 172 173 static FloatPoint* convertPathPoints(FloatPoint dst[], const SkPoint src[], int count) 174 { 175 for (int i = 0; i < count; i++) { 176 dst[i].setX(SkScalarToFloat(src[i].fX)); 177 dst[i].setY(SkScalarToFloat(src[i].fY)); 178 } 179 return dst; 180 } 181 182 void Path::apply(void* info, PathApplierFunction function) const 183 { 184 SkPath::Iter iter(*m_path, false); 185 SkPoint pts[4]; 186 PathElement pathElement; 187 FloatPoint pathPoints[3]; 188 189 for (;;) { 190 switch (iter.next(pts)) { 191 case SkPath::kMove_Verb: 192 pathElement.type = PathElementMoveToPoint; 193 pathElement.points = convertPathPoints(pathPoints, &pts[0], 1); 194 break; 195 case SkPath::kLine_Verb: 196 pathElement.type = PathElementAddLineToPoint; 197 pathElement.points = convertPathPoints(pathPoints, &pts[1], 1); 198 break; 199 case SkPath::kQuad_Verb: 200 pathElement.type = PathElementAddQuadCurveToPoint; 201 pathElement.points = convertPathPoints(pathPoints, &pts[1], 2); 202 break; 203 case SkPath::kCubic_Verb: 204 pathElement.type = PathElementAddCurveToPoint; 205 pathElement.points = convertPathPoints(pathPoints, &pts[1], 3); 206 break; 207 case SkPath::kClose_Verb: 208 pathElement.type = PathElementCloseSubpath; 209 pathElement.points = convertPathPoints(pathPoints, 0, 0); 210 break; 211 case SkPath::kDone_Verb: 212 return; 213 } 214 function(info, &pathElement); 215 } 216 } 217 218 void Path::transform(const AffineTransform& xform) 219 { 220 m_path->transform(xform); 221 } 222 223 String Path::debugString() const 224 { 225 String result; 226 227 SkPath::Iter iter(*m_path, false); 228 SkPoint pts[4]; 229 230 int numPoints = m_path->getPoints(0, 0); 231 SkPath::Verb verb; 232 233 do { 234 verb = iter.next(pts); 235 switch (verb) { 236 case SkPath::kMove_Verb: 237 result += String::format("M%.2f,%.2f ", pts[0].fX, pts[0].fY); 238 numPoints -= 1; 239 break; 240 case SkPath::kLine_Verb: 241 if (!iter.isCloseLine()) { 242 result += String::format("L%.2f,%.2f ", pts[1].fX, pts[1].fY); 243 numPoints -= 1; 244 } 245 break; 246 case SkPath::kQuad_Verb: 247 result += String::format("Q%.2f,%.2f,%.2f,%.2f ", 248 pts[1].fX, pts[1].fY, 249 pts[2].fX, pts[2].fY); 250 numPoints -= 2; 251 break; 252 case SkPath::kCubic_Verb: 253 result += String::format("C%.2f,%.2f,%.2f,%.2f,%.2f,%.2f ", 254 pts[1].fX, pts[1].fY, 255 pts[2].fX, pts[2].fY, 256 pts[3].fX, pts[3].fY); 257 numPoints -= 3; 258 break; 259 case SkPath::kClose_Verb: 260 result += "Z "; 261 break; 262 case SkPath::kDone_Verb: 263 break; 264 } 265 } while (verb != SkPath::kDone_Verb); 266 267 // If you have a path that ends with an M, Skia will not iterate the 268 // trailing M. That's nice of it, but Apple's paths output the trailing M 269 // and we want out layout dumps to look like theirs 270 if (numPoints) { 271 ASSERT(numPoints==1); 272 m_path->getLastPt(pts); 273 result += String::format("M%.2f,%.2f ", pts[0].fX, pts[0].fY); 274 } 275 276 return result.stripWhiteSpace(); 277 } 278 279 // Computes the bounding box for the stroke and style currently selected into 280 // the given bounding box. This also takes into account the stroke width. 281 static FloatRect boundingBoxForCurrentStroke(const GraphicsContext* context) 282 { 283 SkPaint paint; 284 context->platformContext()->setupPaintForStroking(&paint, 0, 0); 285 SkPath boundingPath; 286 paint.getFillPath(context->platformContext()->currentPathInLocalCoordinates(), &boundingPath); 287 return boundingPath.getBounds(); 288 } 289 290 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) 291 { 292 GraphicsContext* scratch = scratchContext(); 293 scratch->save(); 294 scratch->beginPath(); 295 scratch->addPath(*this); 296 297 if (applier) 298 applier->strokeStyle(scratch); 299 300 FloatRect r = boundingBoxForCurrentStroke(scratch); 301 scratch->restore(); 302 return r; 303 } 304 305 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const 306 { 307 ASSERT(applier); 308 GraphicsContext* scratch = scratchContext(); 309 scratch->save(); 310 311 applier->strokeStyle(scratch); 312 313 SkPaint paint; 314 scratch->platformContext()->setupPaintForStroking(&paint, 0, 0); 315 SkPath strokePath; 316 paint.getFillPath(*platformPath(), &strokePath); 317 bool contains = SkPathContainsPoint(&strokePath, point, 318 SkPath::kWinding_FillType); 319 320 scratch->restore(); 321 return contains; 322 } 323 } // namespace WebCore 324