1 /* 2 * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. 3 * 2006 Rob Buis <buis (at) kde.org> 4 * Copyright (C) 2007 Eric Seidel <eric (at) webkit.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 29 #include "config.h" 30 #include "Path.h" 31 32 #include "FloatPoint.h" 33 #include "FloatRect.h" 34 #include "PathTraversalState.h" 35 #include <math.h> 36 #include <wtf/MathExtras.h> 37 38 static const float QUARTER = 0.552f; // approximation of control point positions on a bezier 39 // to simulate a quarter of a circle. 40 namespace WebCore { 41 42 static void pathLengthApplierFunction(void* info, const PathElement* element) 43 { 44 PathTraversalState& traversalState = *static_cast<PathTraversalState*>(info); 45 if (traversalState.m_success) 46 return; 47 traversalState.m_previous = traversalState.m_current; 48 FloatPoint* points = element->points; 49 float segmentLength = 0.0f; 50 switch (element->type) { 51 case PathElementMoveToPoint: 52 segmentLength = traversalState.moveTo(points[0]); 53 break; 54 case PathElementAddLineToPoint: 55 segmentLength = traversalState.lineTo(points[0]); 56 break; 57 case PathElementAddQuadCurveToPoint: 58 segmentLength = traversalState.quadraticBezierTo(points[0], points[1]); 59 break; 60 case PathElementAddCurveToPoint: 61 segmentLength = traversalState.cubicBezierTo(points[0], points[1], points[2]); 62 break; 63 case PathElementCloseSubpath: 64 segmentLength = traversalState.closeSubpath(); 65 break; 66 } 67 traversalState.m_totalLength += segmentLength; 68 if ((traversalState.m_action == PathTraversalState::TraversalPointAtLength || 69 traversalState.m_action == PathTraversalState::TraversalNormalAngleAtLength) && 70 (traversalState.m_totalLength >= traversalState.m_desiredLength)) { 71 FloatSize change = traversalState.m_current - traversalState.m_previous; 72 float slope = atan2f(change.height(), change.width()); 73 74 if (traversalState.m_action == PathTraversalState::TraversalPointAtLength) { 75 float offset = traversalState.m_desiredLength - traversalState.m_totalLength; 76 traversalState.m_current.move(offset * cosf(slope), offset * sinf(slope)); 77 } else { 78 static const float rad2deg = 180.0f / piFloat; 79 traversalState.m_normalAngle = slope * rad2deg; 80 } 81 82 traversalState.m_success = true; 83 } 84 } 85 86 float Path::length() 87 { 88 PathTraversalState traversalState(PathTraversalState::TraversalTotalLength); 89 apply(&traversalState, pathLengthApplierFunction); 90 return traversalState.m_totalLength; 91 } 92 93 FloatPoint Path::pointAtLength(float length, bool& ok) 94 { 95 PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength); 96 traversalState.m_desiredLength = length; 97 apply(&traversalState, pathLengthApplierFunction); 98 ok = traversalState.m_success; 99 return traversalState.m_current; 100 } 101 102 float Path::normalAngleAtLength(float length, bool& ok) 103 { 104 PathTraversalState traversalState(PathTraversalState::TraversalNormalAngleAtLength); 105 traversalState.m_desiredLength = length; 106 apply(&traversalState, pathLengthApplierFunction); 107 ok = traversalState.m_success; 108 return traversalState.m_normalAngle; 109 } 110 111 Path Path::createRoundedRectangle(const FloatRect& rectangle, const FloatSize& roundingRadii) 112 { 113 Path path; 114 float x = rectangle.x(); 115 float y = rectangle.y(); 116 float width = rectangle.width(); 117 float height = rectangle.height(); 118 float rx = roundingRadii.width(); 119 float ry = roundingRadii.height(); 120 if (width <= 0.0f || height <= 0.0f) 121 return path; 122 123 float dx = rx, dy = ry; 124 // If rx is greater than half of the width of the rectangle 125 // then set rx to half of the width (required in SVG spec) 126 if (dx > width * 0.5f) 127 dx = width * 0.5f; 128 129 // If ry is greater than half of the height of the rectangle 130 // then set ry to half of the height (required in SVG spec) 131 if (dy > height * 0.5f) 132 dy = height * 0.5f; 133 134 path.moveTo(FloatPoint(x + dx, y)); 135 136 if (dx < width * 0.5f) 137 path.addLineTo(FloatPoint(x + width - rx, y)); 138 139 path.addBezierCurveTo(FloatPoint(x + width - dx * (1 - QUARTER), y), FloatPoint(x + width, y + dy * (1 - QUARTER)), FloatPoint(x + width, y + dy)); 140 141 if (dy < height * 0.5) 142 path.addLineTo(FloatPoint(x + width, y + height - dy)); 143 144 path.addBezierCurveTo(FloatPoint(x + width, y + height - dy * (1 - QUARTER)), FloatPoint(x + width - dx * (1 - QUARTER), y + height), FloatPoint(x + width - dx, y + height)); 145 146 if (dx < width * 0.5) 147 path.addLineTo(FloatPoint(x + dx, y + height)); 148 149 path.addBezierCurveTo(FloatPoint(x + dx * (1 - QUARTER), y + height), FloatPoint(x, y + height - dy * (1 - QUARTER)), FloatPoint(x, y + height - dy)); 150 151 if (dy < height * 0.5) 152 path.addLineTo(FloatPoint(x, y + dy)); 153 154 path.addBezierCurveTo(FloatPoint(x, y + dy * (1 - QUARTER)), FloatPoint(x + dx * (1 - QUARTER), y), FloatPoint(x + dx, y)); 155 156 path.closeSubpath(); 157 158 return path; 159 } 160 161 Path Path::createRoundedRectangle(const FloatRect& rectangle, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) 162 { 163 Path path; 164 165 float width = rectangle.width(); 166 float height = rectangle.height(); 167 if (width <= 0.0 || height <= 0.0) 168 return path; 169 170 if (width < topLeftRadius.width() + topRightRadius.width() 171 || width < bottomLeftRadius.width() + bottomRightRadius.width() 172 || height < topLeftRadius.height() + bottomLeftRadius.height() 173 || height < topRightRadius.height() + bottomRightRadius.height()) 174 // If all the radii cannot be accommodated, return a rect. 175 return createRectangle(rectangle); 176 177 float x = rectangle.x(); 178 float y = rectangle.y(); 179 180 path.moveTo(FloatPoint(x + topLeftRadius.width(), y)); 181 182 path.addLineTo(FloatPoint(x + width - topRightRadius.width(), y)); 183 184 path.addBezierCurveTo(FloatPoint(x + width - topRightRadius.width() * (1 - QUARTER), y), FloatPoint(x + width, y + topRightRadius.height() * (1 - QUARTER)), FloatPoint(x + width, y + topRightRadius.height())); 185 186 path.addLineTo(FloatPoint(x + width, y + height - bottomRightRadius.height())); 187 188 path.addBezierCurveTo(FloatPoint(x + width, y + height - bottomRightRadius.height() * (1 - QUARTER)), FloatPoint(x + width - bottomRightRadius.width() * (1 - QUARTER), y + height), FloatPoint(x + width - bottomRightRadius.width(), y + height)); 189 190 path.addLineTo(FloatPoint(x + bottomLeftRadius.width(), y + height)); 191 192 path.addBezierCurveTo(FloatPoint(x + bottomLeftRadius.width() * (1 - QUARTER), y + height), FloatPoint(x, y + height - bottomLeftRadius.height() * (1 - QUARTER)), FloatPoint(x, y + height - bottomLeftRadius.height())); 193 194 path.addLineTo(FloatPoint(x, y + topLeftRadius.height())); 195 196 path.addBezierCurveTo(FloatPoint(x, y + topLeftRadius.height() * (1 - QUARTER)), FloatPoint(x + topLeftRadius.width() * (1 - QUARTER), y), FloatPoint(x + topLeftRadius.width(), y)); 197 198 path.closeSubpath(); 199 200 return path; 201 } 202 203 Path Path::createRectangle(const FloatRect& rectangle) 204 { 205 Path path; 206 float x = rectangle.x(); 207 float y = rectangle.y(); 208 float width = rectangle.width(); 209 float height = rectangle.height(); 210 if (width <= 0.0f || height <= 0.0f) 211 return path; 212 213 path.moveTo(FloatPoint(x, y)); 214 path.addLineTo(FloatPoint(x + width, y)); 215 path.addLineTo(FloatPoint(x + width, y + height)); 216 path.addLineTo(FloatPoint(x, y + height)); 217 path.closeSubpath(); 218 219 return path; 220 } 221 222 Path Path::createEllipse(const FloatPoint& center, float rx, float ry) 223 { 224 float cx = center.x(); 225 float cy = center.y(); 226 Path path; 227 if (rx <= 0.0f || ry <= 0.0f) 228 return path; 229 230 float x = cx; 231 float y = cy; 232 233 unsigned step = 0, num = 100; 234 bool running = true; 235 while (running) 236 { 237 if (step == num) 238 { 239 running = false; 240 break; 241 } 242 243 float angle = static_cast<float>(step) / static_cast<float>(num) * 2.0f * piFloat; 244 x = cx + cosf(angle) * rx; 245 y = cy + sinf(angle) * ry; 246 247 step++; 248 if (step == 1) 249 path.moveTo(FloatPoint(x, y)); 250 else 251 path.addLineTo(FloatPoint(x, y)); 252 } 253 254 path.closeSubpath(); 255 256 return path; 257 } 258 259 Path Path::createCircle(const FloatPoint& center, float r) 260 { 261 return createEllipse(center, r, r); 262 } 263 264 Path Path::createLine(const FloatPoint& start, const FloatPoint& end) 265 { 266 Path path; 267 if (start.x() == end.x() && start.y() == end.y()) 268 return path; 269 270 path.moveTo(start); 271 path.addLineTo(end); 272 273 return path; 274 } 275 276 } 277