2 * Copyright (C) 2002, 2003 The Karbon Developers
3 * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
4 * Copyright (C) 2006, 2007 Rob Buis <buis@kde.org>
5 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
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.
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.
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.
27 #include "SVGPathParser.h"
29 #include "AffineTransform.h"
30 #include <wtf/MathExtras.h>
32 static const float gOneOverThree = 1 / 3.f;
36 SVGPathParser::SVGPathParser()
41 void SVGPathParser::parseClosePathSegment()
43 // Reset m_currentPoint for the next path.
44 if (m_pathParsingMode == NormalizedParsing)
45 m_currentPoint = m_subPathPoint;
47 m_consumer->closePath();
50 bool SVGPathParser::parseMoveToSegment()
52 FloatPoint targetPoint;
53 if (!m_source->parseMoveToSegment(targetPoint))
56 if (m_pathParsingMode == NormalizedParsing) {
57 if (m_mode == RelativeCoordinates)
58 m_currentPoint += targetPoint;
60 m_currentPoint = targetPoint;
61 m_subPathPoint = m_currentPoint;
62 m_consumer->moveTo(m_currentPoint, m_closePath, AbsoluteCoordinates);
64 m_consumer->moveTo(targetPoint, m_closePath, m_mode);
69 bool SVGPathParser::parseLineToSegment()
71 FloatPoint targetPoint;
72 if (!m_source->parseLineToSegment(targetPoint))
75 if (m_pathParsingMode == NormalizedParsing) {
76 if (m_mode == RelativeCoordinates)
77 m_currentPoint += targetPoint;
79 m_currentPoint = targetPoint;
80 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
82 m_consumer->lineTo(targetPoint, m_mode);
86 bool SVGPathParser::parseLineToHorizontalSegment()
89 if (!m_source->parseLineToHorizontalSegment(toX))
92 if (m_pathParsingMode == NormalizedParsing) {
93 if (m_mode == RelativeCoordinates)
94 m_currentPoint.move(toX, 0);
96 m_currentPoint.setX(toX);
97 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
99 m_consumer->lineToHorizontal(toX, m_mode);
103 bool SVGPathParser::parseLineToVerticalSegment()
106 if (!m_source->parseLineToVerticalSegment(toY))
109 if (m_pathParsingMode == NormalizedParsing) {
110 if (m_mode == RelativeCoordinates)
111 m_currentPoint.move(0, toY);
113 m_currentPoint.setY(toY);
114 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
116 m_consumer->lineToVertical(toY, m_mode);
120 bool SVGPathParser::parseCurveToCubicSegment()
124 FloatPoint targetPoint;
125 if (!m_source->parseCurveToCubicSegment(point1, point2, targetPoint))
128 if (m_pathParsingMode == NormalizedParsing) {
129 if (m_mode == RelativeCoordinates) {
130 point1 += m_currentPoint;
131 point2 += m_currentPoint;
132 targetPoint += m_currentPoint;
134 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
136 m_controlPoint = point2;
137 m_currentPoint = targetPoint;
139 m_consumer->curveToCubic(point1, point2, targetPoint, m_mode);
143 bool SVGPathParser::parseCurveToCubicSmoothSegment()
146 FloatPoint targetPoint;
147 if (!m_source->parseCurveToCubicSmoothSegment(point2, targetPoint))
150 if (m_lastCommand != PathSegCurveToCubicAbs
151 && m_lastCommand != PathSegCurveToCubicRel
152 && m_lastCommand != PathSegCurveToCubicSmoothAbs
153 && m_lastCommand != PathSegCurveToCubicSmoothRel)
154 m_controlPoint = m_currentPoint;
156 if (m_pathParsingMode == NormalizedParsing) {
157 FloatPoint point1 = m_currentPoint;
159 point1.move(-m_controlPoint.x(), -m_controlPoint.y());
160 if (m_mode == RelativeCoordinates) {
161 point2 += m_currentPoint;
162 targetPoint += m_currentPoint;
165 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
167 m_controlPoint = point2;
168 m_currentPoint = targetPoint;
170 m_consumer->curveToCubicSmooth(point2, targetPoint, m_mode);
174 bool SVGPathParser::parseCurveToQuadraticSegment()
177 FloatPoint targetPoint;
178 if (!m_source->parseCurveToQuadraticSegment(point1, targetPoint))
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;
191 point1.scale(gOneOverThree, gOneOverThree);
192 point2.scale(gOneOverThree, gOneOverThree);
194 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
196 if (m_mode == RelativeCoordinates)
197 m_controlPoint += m_currentPoint;
198 m_currentPoint = targetPoint;
200 m_consumer->curveToQuadratic(point1, targetPoint, m_mode);
204 bool SVGPathParser::parseCurveToQuadraticSmoothSegment()
206 FloatPoint targetPoint;
207 if (!m_source->parseCurveToQuadraticSmoothSegment(targetPoint))
210 if (m_lastCommand != PathSegCurveToQuadraticAbs
211 && m_lastCommand != PathSegCurveToQuadraticRel
212 && m_lastCommand != PathSegCurveToQuadraticSmoothAbs
213 && m_lastCommand != PathSegCurveToQuadraticSmoothRel)
214 m_controlPoint = m_currentPoint;
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;
226 point1.scale(gOneOverThree, gOneOverThree);
227 point2.scale(gOneOverThree, gOneOverThree);
229 m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
231 m_controlPoint = cubicPoint;
232 m_currentPoint = targetPoint;
234 m_consumer->curveToQuadraticSmooth(targetPoint, m_mode);
238 bool SVGPathParser::parseArcToSegment()
245 FloatPoint targetPoint;
246 if (!m_source->parseArcToSegment(rx, ry, angle, largeArc, sweep, targetPoint))
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
254 if (m_pathParsingMode == NormalizedParsing) {
255 if (m_mode == RelativeCoordinates)
256 m_currentPoint += targetPoint;
258 m_currentPoint = targetPoint;
259 m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
261 m_consumer->lineTo(targetPoint, m_mode);
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);
272 m_consumer->arcTo(rx, ry, angle, largeArc, sweep, targetPoint, m_mode);
276 bool SVGPathParser::parsePathDataFromSource(PathParsingMode pathParsingMode)
281 m_pathParsingMode = pathParsingMode;
283 m_controlPoint = FloatPoint();
284 m_currentPoint = FloatPoint();
285 m_subPathPoint = FloatPoint();
288 // Skip any leading spaces.
289 if (!m_source->moveToNextToken())
292 SVGPathSegType command;
293 m_source->parseSVGSegmentType(command);
294 m_lastCommand = PathSegUnknown;
296 // Path must start with moveto.
297 if (command != PathSegMoveToAbs && command != PathSegMoveToRel)
301 // Skip spaces between command and first coordinate.
302 m_source->moveToNextToken();
303 m_mode = AbsoluteCoordinates;
305 case PathSegMoveToRel:
306 m_mode = RelativeCoordinates;
307 case PathSegMoveToAbs:
308 if (!parseMoveToSegment())
311 case PathSegLineToRel:
312 m_mode = RelativeCoordinates;
313 case PathSegLineToAbs:
314 if (!parseLineToSegment())
317 case PathSegLineToHorizontalRel:
318 m_mode = RelativeCoordinates;
319 case PathSegLineToHorizontalAbs:
320 if (!parseLineToHorizontalSegment())
323 case PathSegLineToVerticalRel:
324 m_mode = RelativeCoordinates;
325 case PathSegLineToVerticalAbs:
326 if (!parseLineToVerticalSegment())
329 case PathSegClosePath:
330 parseClosePathSegment();
332 case PathSegCurveToCubicRel:
333 m_mode = RelativeCoordinates;
334 case PathSegCurveToCubicAbs:
335 if (!parseCurveToCubicSegment())
338 case PathSegCurveToCubicSmoothRel:
339 m_mode = RelativeCoordinates;
340 case PathSegCurveToCubicSmoothAbs:
341 if (!parseCurveToCubicSmoothSegment())
344 case PathSegCurveToQuadraticRel:
345 m_mode = RelativeCoordinates;
346 case PathSegCurveToQuadraticAbs:
347 if (!parseCurveToQuadraticSegment())
350 case PathSegCurveToQuadraticSmoothRel:
351 m_mode = RelativeCoordinates;
352 case PathSegCurveToQuadraticSmoothAbs:
353 if (!parseCurveToQuadraticSmoothSegment())
357 m_mode = RelativeCoordinates;
359 if (!parseArcToSegment())
365 if (!m_consumer->continueConsuming())
368 m_lastCommand = command;
370 if (!m_source->hasMoreData())
373 command = m_source->nextCommand(command);
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;
385 m_consumer->incrementPathSegmentCount();
391 void SVGPathParser::cleanup()
396 m_consumer->cleanup();
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)
406 FloatSize midPointDistance = point1 - point2;
407 midPointDistance.scale(0.5f);
409 AffineTransform pointTransform;
410 pointTransform.rotate(-angle);
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();
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);
426 pointTransform.makeIdentity();
427 pointTransform.scale(1 / rx, 1 / ry);
428 pointTransform.rotate(-angle);
430 point1 = pointTransform.mapPoint(point1);
431 point2 = pointTransform.mapPoint(point2);
432 FloatSize delta = point2 - point1;
434 float d = delta.width() * delta.width() + delta.height() * delta.height();
435 float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
437 float scaleFactor = sqrtf(scaleFactorSquared);
438 if (sweepFlag == largeArcFlag)
439 scaleFactor = -scaleFactor;
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());
445 float theta1 = atan2f(point1.y() - centerPoint.y(), point1.x() - centerPoint.x());
446 float theta2 = atan2f(point2.y() - centerPoint.y(), point2.x() - centerPoint.x());
448 float thetaArc = theta2 - theta1;
449 if (thetaArc < 0 && sweepFlag)
450 thetaArc += 2 * piFloat;
451 else if (thetaArc > 0 && !sweepFlag)
452 thetaArc -= 2 * piFloat;
454 pointTransform.makeIdentity();
455 pointTransform.rotate(angle);
456 pointTransform.scale(rx, ry);
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;
465 float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta));
468 float sinStartTheta = sinf(startTheta);
469 float cosStartTheta = cosf(startTheta);
470 float sinEndTheta = sinf(endTheta);
471 float cosEndTheta = cosf(endTheta);
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);
480 m_consumer->curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2),
481 pointTransform.mapPoint(targetPoint), AbsoluteCoordinates);
488 #endif // ENABLE(SVG)