1 /* 2 * Copyright (C) 2002, 2003 The Karbon Developers 3 * Copyright (C) 2006 Alexander Kellett <lypanov (at) kde.org> 4 * Copyright (C) 2006, 2007 Rob Buis <buis (at) kde.org> 5 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. 6 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 26 #if ENABLE(SVG) 27 #include "SVGPathParser.h" 28 29 #include "AffineTransform.h" 30 #include <wtf/MathExtras.h> 31 32 static const float gOneOverThree = 1 / 3.f; 33 34 namespace WebCore { 35 36 SVGPathParser::SVGPathParser() 37 : m_consumer(0) 38 { 39 } 40 41 void SVGPathParser::parseClosePathSegment() 42 { 43 // Reset m_currentPoint for the next path. 44 if (m_pathParsingMode == NormalizedParsing) 45 m_currentPoint = m_subPathPoint; 46 m_closePath = true; 47 m_consumer->closePath(); 48 } 49 50 bool SVGPathParser::parseMoveToSegment() 51 { 52 FloatPoint targetPoint; 53 if (!m_source->parseMoveToSegment(targetPoint)) 54 return false; 55 56 if (m_pathParsingMode == NormalizedParsing) { 57 if (m_mode == RelativeCoordinates) 58 m_currentPoint += targetPoint; 59 else 60 m_currentPoint = targetPoint; 61 m_subPathPoint = m_currentPoint; 62 m_consumer->moveTo(m_currentPoint, m_closePath, AbsoluteCoordinates); 63 } else 64 m_consumer->moveTo(targetPoint, m_closePath, m_mode); 65 m_closePath = false; 66 return true; 67 } 68 69 bool SVGPathParser::parseLineToSegment() 70 { 71 FloatPoint targetPoint; 72 if (!m_source->parseLineToSegment(targetPoint)) 73 return false; 74 75 if (m_pathParsingMode == NormalizedParsing) { 76 if (m_mode == RelativeCoordinates) 77 m_currentPoint += targetPoint; 78 else 79 m_currentPoint = targetPoint; 80 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates); 81 } else 82 m_consumer->lineTo(targetPoint, m_mode); 83 return true; 84 } 85 86 bool SVGPathParser::parseLineToHorizontalSegment() 87 { 88 float toX; 89 if (!m_source->parseLineToHorizontalSegment(toX)) 90 return false; 91 92 if (m_pathParsingMode == NormalizedParsing) { 93 if (m_mode == RelativeCoordinates) 94 m_currentPoint.move(toX, 0); 95 else 96 m_currentPoint.setX(toX); 97 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates); 98 } else 99 m_consumer->lineToHorizontal(toX, m_mode); 100 return true; 101 } 102 103 bool SVGPathParser::parseLineToVerticalSegment() 104 { 105 float toY; 106 if (!m_source->parseLineToVerticalSegment(toY)) 107 return false; 108 109 if (m_pathParsingMode == NormalizedParsing) { 110 if (m_mode == RelativeCoordinates) 111 m_currentPoint.move(0, toY); 112 else 113 m_currentPoint.setY(toY); 114 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates); 115 } else 116 m_consumer->lineToVertical(toY, m_mode); 117 return true; 118 } 119 120 bool SVGPathParser::parseCurveToCubicSegment() 121 { 122 FloatPoint point1; 123 FloatPoint point2; 124 FloatPoint targetPoint; 125 if (!m_source->parseCurveToCubicSegment(point1, point2, targetPoint)) 126 return false; 127 128 if (m_pathParsingMode == NormalizedParsing) { 129 if (m_mode == RelativeCoordinates) { 130 point1 += m_currentPoint; 131 point2 += m_currentPoint; 132 targetPoint += m_currentPoint; 133 } 134 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates); 135 136 m_controlPoint = point2; 137 m_currentPoint = targetPoint; 138 } else 139 m_consumer->curveToCubic(point1, point2, targetPoint, m_mode); 140 return true; 141 } 142 143 bool SVGPathParser::parseCurveToCubicSmoothSegment() 144 { 145 FloatPoint point2; 146 FloatPoint targetPoint; 147 if (!m_source->parseCurveToCubicSmoothSegment(point2, targetPoint)) 148 return false; 149 150 if (m_lastCommand != PathSegCurveToCubicAbs 151 && m_lastCommand != PathSegCurveToCubicRel 152 && m_lastCommand != PathSegCurveToCubicSmoothAbs 153 && m_lastCommand != PathSegCurveToCubicSmoothRel) 154 m_controlPoint = m_currentPoint; 155 156 if (m_pathParsingMode == NormalizedParsing) { 157 FloatPoint point1 = m_currentPoint; 158 point1.scale(2, 2); 159 point1.move(-m_controlPoint.x(), -m_controlPoint.y()); 160 if (m_mode == RelativeCoordinates) { 161 point2 += m_currentPoint; 162 targetPoint += m_currentPoint; 163 } 164 165 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates); 166 167 m_controlPoint = point2; 168 m_currentPoint = targetPoint; 169 } else 170 m_consumer->curveToCubicSmooth(point2, targetPoint, m_mode); 171 return true; 172 } 173 174 bool SVGPathParser::parseCurveToQuadraticSegment() 175 { 176 FloatPoint point1; 177 FloatPoint targetPoint; 178 if (!m_source->parseCurveToQuadraticSegment(point1, targetPoint)) 179 return false; 180 181 if (m_pathParsingMode == NormalizedParsing) { 182 m_controlPoint = point1; 183 FloatPoint point1 = m_currentPoint; 184 point1.move(2 * m_controlPoint.x(), 2 * m_controlPoint.y()); 185 FloatPoint point2(targetPoint.x() + 2 * m_controlPoint.x(), targetPoint.y() + 2 * m_controlPoint.y()); 186 if (m_mode == RelativeCoordinates) { 187 point1.move(2 * m_currentPoint.x(), 2 * m_currentPoint.y()); 188 point2.move(3 * m_currentPoint.x(), 3 * m_currentPoint.y()); 189 targetPoint += m_currentPoint; 190 } 191 point1.scale(gOneOverThree, gOneOverThree); 192 point2.scale(gOneOverThree, gOneOverThree); 193 194 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates); 195 196 if (m_mode == RelativeCoordinates) 197 m_controlPoint += m_currentPoint; 198 m_currentPoint = targetPoint; 199 } else 200 m_consumer->curveToQuadratic(point1, targetPoint, m_mode); 201 return true; 202 } 203 204 bool SVGPathParser::parseCurveToQuadraticSmoothSegment() 205 { 206 FloatPoint targetPoint; 207 if (!m_source->parseCurveToQuadraticSmoothSegment(targetPoint)) 208 return false; 209 210 if (m_lastCommand != PathSegCurveToQuadraticAbs 211 && m_lastCommand != PathSegCurveToQuadraticRel 212 && m_lastCommand != PathSegCurveToQuadraticSmoothAbs 213 && m_lastCommand != PathSegCurveToQuadraticSmoothRel) 214 m_controlPoint = m_currentPoint; 215 216 if (m_pathParsingMode == NormalizedParsing) { 217 FloatPoint cubicPoint = m_currentPoint; 218 cubicPoint.scale(2, 2); 219 cubicPoint.move(-m_controlPoint.x(), -m_controlPoint.y()); 220 FloatPoint point1(m_currentPoint.x() + 2 * cubicPoint.x(), m_currentPoint.y() + 2 * cubicPoint.y()); 221 FloatPoint point2(targetPoint.x() + 2 * cubicPoint.x(), targetPoint.y() + 2 * cubicPoint.y()); 222 if (m_mode == RelativeCoordinates) { 223 point2 += m_currentPoint; 224 targetPoint += m_currentPoint; 225 } 226 point1.scale(gOneOverThree, gOneOverThree); 227 point2.scale(gOneOverThree, gOneOverThree); 228 229 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates); 230 231 m_controlPoint = cubicPoint; 232 m_currentPoint = targetPoint; 233 } else 234 m_consumer->curveToQuadraticSmooth(targetPoint, m_mode); 235 return true; 236 } 237 238 bool SVGPathParser::parseArcToSegment() 239 { 240 float rx; 241 float ry; 242 float angle; 243 bool largeArc; 244 bool sweep; 245 FloatPoint targetPoint; 246 if (!m_source->parseArcToSegment(rx, ry, angle, largeArc, sweep, targetPoint)) 247 return false; 248 249 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints. 250 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters 251 rx = fabsf(rx); 252 ry = fabsf(ry); 253 if (!rx || !ry) { 254 if (m_pathParsingMode == NormalizedParsing) { 255 if (m_mode == RelativeCoordinates) 256 m_currentPoint += targetPoint; 257 else 258 m_currentPoint = targetPoint; 259 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates); 260 } else 261 m_consumer->lineTo(targetPoint, m_mode); 262 return true; 263 } 264 265 if (m_pathParsingMode == NormalizedParsing) { 266 FloatPoint point1 = m_currentPoint; 267 if (m_mode == RelativeCoordinates) 268 targetPoint += m_currentPoint; 269 m_currentPoint = targetPoint; 270 return decomposeArcToCubic(angle, rx, ry, point1, targetPoint, largeArc, sweep); 271 } 272 m_consumer->arcTo(rx, ry, angle, largeArc, sweep, targetPoint, m_mode); 273 return true; 274 } 275 276 bool SVGPathParser::parsePathDataFromSource(PathParsingMode pathParsingMode) 277 { 278 ASSERT(m_source); 279 ASSERT(m_consumer); 280 281 m_pathParsingMode = pathParsingMode; 282 283 m_controlPoint = FloatPoint(); 284 m_currentPoint = FloatPoint(); 285 m_subPathPoint = FloatPoint(); 286 m_closePath = true; 287 288 // Skip any leading spaces. 289 if (!m_source->moveToNextToken()) 290 return false; 291 292 SVGPathSegType command; 293 m_source->parseSVGSegmentType(command); 294 m_lastCommand = PathSegUnknown; 295 296 // Path must start with moveto. 297 if (command != PathSegMoveToAbs && command != PathSegMoveToRel) 298 return false; 299 300 while (true) { 301 // Skip spaces between command and first coordinate. 302 m_source->moveToNextToken(); 303 m_mode = AbsoluteCoordinates; 304 switch (command) { 305 case PathSegMoveToRel: 306 m_mode = RelativeCoordinates; 307 case PathSegMoveToAbs: 308 if (!parseMoveToSegment()) 309 return false; 310 break; 311 case PathSegLineToRel: 312 m_mode = RelativeCoordinates; 313 case PathSegLineToAbs: 314 if (!parseLineToSegment()) 315 return false; 316 break; 317 case PathSegLineToHorizontalRel: 318 m_mode = RelativeCoordinates; 319 case PathSegLineToHorizontalAbs: 320 if (!parseLineToHorizontalSegment()) 321 return false; 322 break; 323 case PathSegLineToVerticalRel: 324 m_mode = RelativeCoordinates; 325 case PathSegLineToVerticalAbs: 326 if (!parseLineToVerticalSegment()) 327 return false; 328 break; 329 case PathSegClosePath: 330 parseClosePathSegment(); 331 break; 332 case PathSegCurveToCubicRel: 333 m_mode = RelativeCoordinates; 334 case PathSegCurveToCubicAbs: 335 if (!parseCurveToCubicSegment()) 336 return false; 337 break; 338 case PathSegCurveToCubicSmoothRel: 339 m_mode = RelativeCoordinates; 340 case PathSegCurveToCubicSmoothAbs: 341 if (!parseCurveToCubicSmoothSegment()) 342 return false; 343 break; 344 case PathSegCurveToQuadraticRel: 345 m_mode = RelativeCoordinates; 346 case PathSegCurveToQuadraticAbs: 347 if (!parseCurveToQuadraticSegment()) 348 return false; 349 break; 350 case PathSegCurveToQuadraticSmoothRel: 351 m_mode = RelativeCoordinates; 352 case PathSegCurveToQuadraticSmoothAbs: 353 if (!parseCurveToQuadraticSmoothSegment()) 354 return false; 355 break; 356 case PathSegArcRel: 357 m_mode = RelativeCoordinates; 358 case PathSegArcAbs: 359 if (!parseArcToSegment()) 360 return false; 361 break; 362 default: 363 return false; 364 } 365 if (!m_consumer->continueConsuming()) 366 return true; 367 368 m_lastCommand = command; 369 370 if (!m_source->hasMoreData()) 371 return true; 372 373 command = m_source->nextCommand(command); 374 375 if (m_lastCommand != PathSegCurveToCubicAbs 376 && m_lastCommand != PathSegCurveToCubicRel 377 && m_lastCommand != PathSegCurveToCubicSmoothAbs 378 && m_lastCommand != PathSegCurveToCubicSmoothRel 379 && m_lastCommand != PathSegCurveToQuadraticAbs 380 && m_lastCommand != PathSegCurveToQuadraticRel 381 && m_lastCommand != PathSegCurveToQuadraticSmoothAbs 382 && m_lastCommand != PathSegCurveToQuadraticSmoothRel) 383 m_controlPoint = m_currentPoint; 384 385 m_consumer->incrementPathSegmentCount(); 386 } 387 388 return false; 389 } 390 391 void SVGPathParser::cleanup() 392 { 393 ASSERT(m_source); 394 ASSERT(m_consumer); 395 396 m_consumer->cleanup(); 397 m_source = 0; 398 m_consumer = 0; 399 } 400 401 // This works by converting the SVG arc to "simple" beziers. 402 // Partly adapted from Niko's code in kdelibs/kdecore/svgicons. 403 // See also SVG implementation notes: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter 404 bool SVGPathParser::decomposeArcToCubic(float angle, float rx, float ry, FloatPoint& point1, FloatPoint& point2, bool largeArcFlag, bool sweepFlag) 405 { 406 FloatSize midPointDistance = point1 - point2; 407 midPointDistance.scale(0.5f); 408 409 AffineTransform pointTransform; 410 pointTransform.rotate(-angle); 411 412 FloatPoint transformedMidPoint = pointTransform.mapPoint(FloatPoint(midPointDistance.width(), midPointDistance.height())); 413 float squareRx = rx * rx; 414 float squareRy = ry * ry; 415 float squareX = transformedMidPoint.x() * transformedMidPoint.x(); 416 float squareY = transformedMidPoint.y() * transformedMidPoint.y(); 417 418 // Check if the radii are big enough to draw the arc, scale radii if not. 419 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii 420 float radiiScale = squareX / squareRx + squareY / squareRy; 421 if (radiiScale > 1) { 422 rx *= sqrtf(radiiScale); 423 ry *= sqrtf(radiiScale); 424 } 425 426 pointTransform.makeIdentity(); 427 pointTransform.scale(1 / rx, 1 / ry); 428 pointTransform.rotate(-angle); 429 430 point1 = pointTransform.mapPoint(point1); 431 point2 = pointTransform.mapPoint(point2); 432 FloatSize delta = point2 - point1; 433 434 float d = delta.width() * delta.width() + delta.height() * delta.height(); 435 float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); 436 437 float scaleFactor = sqrtf(scaleFactorSquared); 438 if (sweepFlag == largeArcFlag) 439 scaleFactor = -scaleFactor; 440 441 delta.scale(scaleFactor); 442 FloatPoint centerPoint = FloatPoint(0.5f * (point1.x() + point2.x()) - delta.height(), 443 0.5f * (point1.y() + point2.y()) + delta.width()); 444 445 float theta1 = atan2f(point1.y() - centerPoint.y(), point1.x() - centerPoint.x()); 446 float theta2 = atan2f(point2.y() - centerPoint.y(), point2.x() - centerPoint.x()); 447 448 float thetaArc = theta2 - theta1; 449 if (thetaArc < 0 && sweepFlag) 450 thetaArc += 2 * piFloat; 451 else if (thetaArc > 0 && !sweepFlag) 452 thetaArc -= 2 * piFloat; 453 454 pointTransform.makeIdentity(); 455 pointTransform.rotate(angle); 456 pointTransform.scale(rx, ry); 457 458 // Some results of atan2 on some platform implementations are not exact enough. So that we get more 459 // cubic curves than expected here. Adding 0.001f reduces the count of sgements to the correct count. 460 int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f))); 461 for (int i = 0; i < segments; ++i) { 462 float startTheta = theta1 + i * thetaArc / segments; 463 float endTheta = theta1 + (i + 1) * thetaArc / segments; 464 465 float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta)); 466 if (!isfinite(t)) 467 return false; 468 float sinStartTheta = sinf(startTheta); 469 float cosStartTheta = cosf(startTheta); 470 float sinEndTheta = sinf(endTheta); 471 float cosEndTheta = cosf(endTheta); 472 473 point1 = FloatPoint(cosStartTheta - t * sinStartTheta, sinStartTheta + t * cosStartTheta); 474 point1.move(centerPoint.x(), centerPoint.y()); 475 FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta); 476 targetPoint.move(centerPoint.x(), centerPoint.y()); 477 point2 = targetPoint; 478 point2.move(t * sinEndTheta, -t * cosEndTheta); 479 480 m_consumer->curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2), 481 pointTransform.mapPoint(targetPoint), AbsoluteCoordinates); 482 } 483 return true; 484 } 485 486 } 487 488 #endif // ENABLE(SVG) 489