summaryrefslogtreecommitdiffstats
path: root/WebCore/svg/SVGPathParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/svg/SVGPathParser.cpp')
-rw-r--r--WebCore/svg/SVGPathParser.cpp492
1 files changed, 492 insertions, 0 deletions
diff --git a/WebCore/svg/SVGPathParser.cpp b/WebCore/svg/SVGPathParser.cpp
new file mode 100644
index 0000000..66bd00f
--- /dev/null
+++ b/WebCore/svg/SVGPathParser.cpp
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2002, 2003 The Karbon Developers
+ * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
+ * Copyright (C) 2006, 2007 Rob Buis <buis@kde.org>
+ * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#if ENABLE(SVG)
+#include "SVGPathParser.h"
+
+#include "AffineTransform.h"
+#include <wtf/MathExtras.h>
+
+static const float gOneOverThree = 1 / 3.f;
+
+namespace WebCore {
+
+SVGPathParser::SVGPathParser()
+ : m_consumer(0)
+{
+}
+
+SVGPathParser::~SVGPathParser()
+{
+}
+
+void SVGPathParser::parseClosePathSegment()
+{
+ // Reset m_currentPoint for the next path.
+ if (m_pathParsingMode == NormalizedParsing)
+ m_currentPoint = m_subPathPoint;
+ m_closePath = true;
+ m_consumer->closePath();
+}
+
+bool SVGPathParser::parseMoveToSegment()
+{
+ FloatPoint targetPoint;
+ if (!m_source->parseMoveToSegment(targetPoint))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates)
+ m_currentPoint += targetPoint;
+ else
+ m_currentPoint = targetPoint;
+ m_subPathPoint = m_currentPoint;
+ m_consumer->moveTo(m_currentPoint, m_closePath, AbsoluteCoordinates);
+ } else
+ m_consumer->moveTo(targetPoint, m_closePath, m_mode);
+ m_closePath = false;
+ return true;
+}
+
+bool SVGPathParser::parseLineToSegment()
+{
+ FloatPoint targetPoint;
+ if (!m_source->parseLineToSegment(targetPoint))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates)
+ m_currentPoint += targetPoint;
+ else
+ m_currentPoint = targetPoint;
+ m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+ } else
+ m_consumer->lineTo(targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseLineToHorizontalSegment()
+{
+ float toX;
+ if (!m_source->parseLineToHorizontalSegment(toX))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates)
+ m_currentPoint.move(toX, 0);
+ else
+ m_currentPoint.setX(toX);
+ m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+ } else
+ m_consumer->lineToHorizontal(toX, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseLineToVerticalSegment()
+{
+ float toY;
+ if (!m_source->parseLineToVerticalSegment(toY))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates)
+ m_currentPoint.move(0, toY);
+ else
+ m_currentPoint.setY(toY);
+ m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+ } else
+ m_consumer->lineToVertical(toY, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseCurveToCubicSegment()
+{
+ FloatPoint point1;
+ FloatPoint point2;
+ FloatPoint targetPoint;
+ if (!m_source->parseCurveToCubicSegment(point1, point2, targetPoint))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates) {
+ point1 += m_currentPoint;
+ point2 += m_currentPoint;
+ targetPoint += m_currentPoint;
+ }
+ m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
+
+ m_controlPoint = point2;
+ m_currentPoint = targetPoint;
+ } else
+ m_consumer->curveToCubic(point1, point2, targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseCurveToCubicSmoothSegment()
+{
+ FloatPoint point2;
+ FloatPoint targetPoint;
+ if (!m_source->parseCurveToCubicSmoothSegment(point2, targetPoint))
+ return false;
+
+ if (m_lastCommand != PathSegCurveToCubicAbs
+ && m_lastCommand != PathSegCurveToCubicRel
+ && m_lastCommand != PathSegCurveToCubicSmoothAbs
+ && m_lastCommand != PathSegCurveToCubicSmoothRel)
+ m_controlPoint = m_currentPoint;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ FloatPoint point1 = m_currentPoint;
+ point1.scale(2, 2);
+ point1.move(-m_controlPoint.x(), -m_controlPoint.y());
+ if (m_mode == RelativeCoordinates) {
+ point2 += m_currentPoint;
+ targetPoint += m_currentPoint;
+ }
+
+ m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
+
+ m_controlPoint = point2;
+ m_currentPoint = targetPoint;
+ } else
+ m_consumer->curveToCubicSmooth(point2, targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseCurveToQuadraticSegment()
+{
+ FloatPoint point1;
+ FloatPoint targetPoint;
+ if (!m_source->parseCurveToQuadraticSegment(point1, targetPoint))
+ return false;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ m_controlPoint = point1;
+ FloatPoint point1 = m_currentPoint;
+ point1.move(2 * m_controlPoint.x(), 2 * m_controlPoint.y());
+ FloatPoint point2(targetPoint.x() + 2 * m_controlPoint.x(), targetPoint.y() + 2 * m_controlPoint.y());
+ if (m_mode == RelativeCoordinates) {
+ point1.move(2 * m_currentPoint.x(), 2 * m_currentPoint.y());
+ point2.move(3 * m_currentPoint.x(), 3 * m_currentPoint.y());
+ targetPoint += m_currentPoint;
+ }
+ point1.scale(gOneOverThree, gOneOverThree);
+ point2.scale(gOneOverThree, gOneOverThree);
+
+ m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
+
+ if (m_mode == RelativeCoordinates)
+ m_controlPoint += m_currentPoint;
+ m_currentPoint = targetPoint;
+ } else
+ m_consumer->curveToQuadratic(point1, targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseCurveToQuadraticSmoothSegment()
+{
+ FloatPoint targetPoint;
+ if (!m_source->parseCurveToQuadraticSmoothSegment(targetPoint))
+ return false;
+
+ if (m_lastCommand != PathSegCurveToQuadraticAbs
+ && m_lastCommand != PathSegCurveToQuadraticRel
+ && m_lastCommand != PathSegCurveToQuadraticSmoothAbs
+ && m_lastCommand != PathSegCurveToQuadraticSmoothRel)
+ m_controlPoint = m_currentPoint;
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ FloatPoint cubicPoint = m_currentPoint;
+ cubicPoint.scale(2, 2);
+ cubicPoint.move(-m_controlPoint.x(), -m_controlPoint.y());
+ FloatPoint point1(m_currentPoint.x() + 2 * cubicPoint.x(), m_currentPoint.y() + 2 * cubicPoint.y());
+ FloatPoint point2(targetPoint.x() + 2 * cubicPoint.x(), targetPoint.y() + 2 * cubicPoint.y());
+ if (m_mode == RelativeCoordinates) {
+ point2 += m_currentPoint;
+ targetPoint += m_currentPoint;
+ }
+ point1.scale(gOneOverThree, gOneOverThree);
+ point2.scale(gOneOverThree, gOneOverThree);
+
+ m_consumer->curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates);
+
+ m_controlPoint = cubicPoint;
+ m_currentPoint = targetPoint;
+ } else
+ m_consumer->curveToQuadraticSmooth(targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parseArcToSegment()
+{
+ float rx;
+ float ry;
+ float angle;
+ bool largeArc;
+ bool sweep;
+ FloatPoint targetPoint;
+ if (!m_source->parseArcToSegment(rx, ry, angle, largeArc, sweep, targetPoint))
+ return false;
+
+ // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints.
+ // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+ if (!rx || !ry) {
+ if (m_pathParsingMode == NormalizedParsing) {
+ if (m_mode == RelativeCoordinates)
+ m_currentPoint += targetPoint;
+ else
+ m_currentPoint = targetPoint;
+ m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+ } else
+ m_consumer->lineTo(targetPoint, m_mode);
+ return true;
+ }
+
+ if (m_pathParsingMode == NormalizedParsing) {
+ FloatPoint point1 = m_currentPoint;
+ if (m_mode == RelativeCoordinates)
+ targetPoint += m_currentPoint;
+ m_currentPoint = targetPoint;
+ return decomposeArcToCubic(angle, rx, ry, point1, targetPoint, largeArc, sweep);
+ }
+ m_consumer->arcTo(rx, ry, angle, largeArc, sweep, targetPoint, m_mode);
+ return true;
+}
+
+bool SVGPathParser::parsePathDataFromSource(PathParsingMode pathParsingMode)
+{
+ ASSERT(m_source);
+ ASSERT(m_consumer);
+
+ m_pathParsingMode = pathParsingMode;
+
+ m_controlPoint = FloatPoint();
+ m_currentPoint = FloatPoint();
+ m_subPathPoint = FloatPoint();
+ m_closePath = true;
+
+ // Skip any leading spaces.
+ if (!m_source->moveToNextToken())
+ return false;
+
+ SVGPathSegType command;
+ m_source->parseSVGSegmentType(command);
+ m_lastCommand = PathSegUnknown;
+
+ // Path must start with moveto.
+ if (command != PathSegMoveToAbs && command != PathSegMoveToRel)
+ return false;
+
+ while (true) {
+ // Skip spaces between command and first coordinate.
+ m_source->moveToNextToken();
+ m_mode = AbsoluteCoordinates;
+ switch (command) {
+ case PathSegMoveToRel:
+ m_mode = RelativeCoordinates;
+ case PathSegMoveToAbs:
+ if (!parseMoveToSegment())
+ return false;
+ break;
+ case PathSegLineToRel:
+ m_mode = RelativeCoordinates;
+ case PathSegLineToAbs:
+ if (!parseLineToSegment())
+ return false;
+ break;
+ case PathSegLineToHorizontalRel:
+ m_mode = RelativeCoordinates;
+ case PathSegLineToHorizontalAbs:
+ if (!parseLineToHorizontalSegment())
+ return false;
+ break;
+ case PathSegLineToVerticalRel:
+ m_mode = RelativeCoordinates;
+ case PathSegLineToVerticalAbs:
+ if (!parseLineToVerticalSegment())
+ return false;
+ break;
+ case PathSegClosePath:
+ parseClosePathSegment();
+ break;
+ case PathSegCurveToCubicRel:
+ m_mode = RelativeCoordinates;
+ case PathSegCurveToCubicAbs:
+ if (!parseCurveToCubicSegment())
+ return false;
+ break;
+ case PathSegCurveToCubicSmoothRel:
+ m_mode = RelativeCoordinates;
+ case PathSegCurveToCubicSmoothAbs:
+ if (!parseCurveToCubicSmoothSegment())
+ return false;
+ break;
+ case PathSegCurveToQuadraticRel:
+ m_mode = RelativeCoordinates;
+ case PathSegCurveToQuadraticAbs:
+ if (!parseCurveToQuadraticSegment())
+ return false;
+ break;
+ case PathSegCurveToQuadraticSmoothRel:
+ m_mode = RelativeCoordinates;
+ case PathSegCurveToQuadraticSmoothAbs:
+ if (!parseCurveToQuadraticSmoothSegment())
+ return false;
+ break;
+ case PathSegArcRel:
+ m_mode = RelativeCoordinates;
+ case PathSegArcAbs:
+ if (!parseArcToSegment())
+ return false;
+ break;
+ default:
+ return false;
+ }
+ if (!m_consumer->continueConsuming())
+ return true;
+
+ m_lastCommand = command;
+
+ if (!m_source->hasMoreData())
+ return true;
+
+ command = m_source->nextCommand(command);
+
+ if (m_lastCommand != PathSegCurveToCubicAbs
+ && m_lastCommand != PathSegCurveToCubicRel
+ && m_lastCommand != PathSegCurveToCubicSmoothAbs
+ && m_lastCommand != PathSegCurveToCubicSmoothRel
+ && m_lastCommand != PathSegCurveToQuadraticAbs
+ && m_lastCommand != PathSegCurveToQuadraticRel
+ && m_lastCommand != PathSegCurveToQuadraticSmoothAbs
+ && m_lastCommand != PathSegCurveToQuadraticSmoothRel)
+ m_controlPoint = m_currentPoint;
+
+ m_consumer->incrementPathSegmentCount();
+ }
+
+ return false;
+}
+
+void SVGPathParser::cleanup()
+{
+ ASSERT(m_source);
+ ASSERT(m_consumer);
+
+ m_consumer->cleanup();
+ m_source = 0;
+ m_consumer = 0;
+}
+
+// This works by converting the SVG arc to "simple" beziers.
+// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
+// See also SVG implementation notes: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+bool SVGPathParser::decomposeArcToCubic(float angle, float rx, float ry, FloatPoint& point1, FloatPoint& point2, bool largeArcFlag, bool sweepFlag)
+{
+ FloatSize midPointDistance = point1 - point2;
+ midPointDistance.scale(0.5f);
+
+ AffineTransform pointTransform;
+ pointTransform.rotate(-angle);
+
+ FloatPoint transformedMidPoint = pointTransform.mapPoint(FloatPoint(midPointDistance.width(), midPointDistance.height()));
+ float squareRx = rx * rx;
+ float squareRy = ry * ry;
+ float squareX = transformedMidPoint.x() * transformedMidPoint.x();
+ float squareY = transformedMidPoint.y() * transformedMidPoint.y();
+
+ // Check if the radii are big enough to draw the arc, scale radii if not.
+ // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
+ float radiiScale = squareX / squareRx + squareY / squareRy;
+ if (radiiScale > 1) {
+ rx *= sqrtf(radiiScale);
+ ry *= sqrtf(radiiScale);
+ }
+
+ pointTransform.makeIdentity();
+ pointTransform.scale(1 / rx, 1 / ry);
+ pointTransform.rotate(-angle);
+
+ point1 = pointTransform.mapPoint(point1);
+ point2 = pointTransform.mapPoint(point2);
+ FloatSize delta = point2 - point1;
+
+ float d = delta.width() * delta.width() + delta.height() * delta.height();
+ float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
+
+ float scaleFactor = sqrtf(scaleFactorSquared);
+ if (sweepFlag == largeArcFlag)
+ scaleFactor = -scaleFactor;
+
+ delta.scale(scaleFactor);
+ FloatPoint centerPoint = FloatPoint(0.5f * (point1.x() + point2.x()) - delta.height(),
+ 0.5f * (point1.y() + point2.y()) + delta.width());
+
+ float theta1 = atan2f(point1.y() - centerPoint.y(), point1.x() - centerPoint.x());
+ float theta2 = atan2f(point2.y() - centerPoint.y(), point2.x() - centerPoint.x());
+
+ float thetaArc = theta2 - theta1;
+ if (thetaArc < 0 && sweepFlag)
+ thetaArc += 2 * piFloat;
+ else if (thetaArc > 0 && !sweepFlag)
+ thetaArc -= 2 * piFloat;
+
+ pointTransform.makeIdentity();
+ pointTransform.rotate(angle);
+ pointTransform.scale(rx, ry);
+
+ // Some results of atan2 on some platform implementations are not exact enough. So that we get more
+ // cubic curves than expected here. Adding 0.001f reduces the count of sgements to the correct count.
+ int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f)));
+ for (int i = 0; i < segments; ++i) {
+ float startTheta = theta1 + i * thetaArc / segments;
+ float endTheta = theta1 + (i + 1) * thetaArc / segments;
+
+ float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta));
+ if (!isfinite(t))
+ return false;
+ float sinStartTheta = sinf(startTheta);
+ float cosStartTheta = cosf(startTheta);
+ float sinEndTheta = sinf(endTheta);
+ float cosEndTheta = cosf(endTheta);
+
+ point1 = FloatPoint(cosStartTheta - t * sinStartTheta, sinStartTheta + t * cosStartTheta);
+ point1.move(centerPoint.x(), centerPoint.y());
+ FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta);
+ targetPoint.move(centerPoint.x(), centerPoint.y());
+ point2 = targetPoint;
+ point2.move(t * sinEndTheta, -t * cosEndTheta);
+
+ m_consumer->curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2),
+ pointTransform.mapPoint(targetPoint), AbsoluteCoordinates);
+ }
+ return true;
+}
+
+}
+
+#endif // ENABLE(SVG)