WebCore/svg/SVGPathParser.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/svg/SVGPathParser.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2002, 2003 The Karbon Developers
+ *               2006       Alexander Kellett <lypanov@kde.org>
+ *               2006, 2007 Rob Buis <buis@kde.org>
+ * Copyrigth (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 "SVGParserUtilities.h"
+#include <wtf/MathExtras.h>
+
+static const float gOneOverThree = 1 / 3.f;
+
+namespace WebCore {
+
+SVGPathParser::SVGPathParser(SVGPathConsumer* consumer)
+    : m_consumer(consumer)
+{
+}
+
+SVGPathParser::~SVGPathParser()
+{
+}
+
+void SVGPathParser::parseClosePathSegment()
+{
+    // Reset m_currentPoint for the next path.
+    if (m_normalized)
+        m_currentPoint = m_subPathPoint;
+    m_pathClosed = true;
+    m_consumer->closePath();
+}
+
+bool SVGPathParser::parseMoveToSegment()
+{
+    float toX;
+    float toY;
+    if (!parseNumber(m_ptr, m_end, toX) || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    FloatPoint toPoint(toX, toY);
+    if (m_normalized) {
+        if (m_mode == RelativeCoordinates)
+            m_currentPoint += toPoint;
+        else
+            m_currentPoint = toPoint;
+        m_subPathPoint = m_currentPoint;
+        m_consumer->moveTo(m_currentPoint, m_pathClosed, AbsoluteCoordinates);
+    } else
+        m_consumer->moveTo(toPoint, m_pathClosed, m_mode);
+    m_pathClosed = false;
+    return true;
+}
+
+bool SVGPathParser::parseLineToSegment()
+{
+    float toX;
+    float toY;
+    if (!parseNumber(m_ptr, m_end, toX) || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    FloatPoint toPoint(toX, toY);
+    if (m_normalized) {
+        if (m_mode == RelativeCoordinates)
+            m_currentPoint += toPoint;
+        else
+            m_currentPoint = toPoint;
+        m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+    } else
+        m_consumer->lineTo(toPoint, m_mode);
+    return true;
+}
+
+bool SVGPathParser::parseLineToHorizontalSegment()
+{
+    float toX;
+    if (!parseNumber(m_ptr, m_end, toX))
+        return false;
+
+    if (m_normalized) {
+        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 (!parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    if (m_normalized) {
+        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()
+{
+    float x1;
+    float y1;
+    float x2;
+    float y2;
+    float toX;
+    float toY; 
+    if (!parseNumber(m_ptr, m_end, x1)
+        || !parseNumber(m_ptr, m_end, y1)
+        || !parseNumber(m_ptr, m_end, x2)
+        || !parseNumber(m_ptr, m_end, y2)
+        || !parseNumber(m_ptr, m_end, toX)
+        || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    FloatPoint point1(x1, y1);
+    FloatPoint point2(x2, y2);
+    FloatPoint point3(toX, toY);
+    if (m_normalized) {
+        if (m_mode == RelativeCoordinates) {
+            point1 += m_currentPoint;
+            point2 += m_currentPoint;
+            point3 += m_currentPoint;
+        }
+        m_consumer->curveToCubic(point1, point2, point3, AbsoluteCoordinates);
+
+        m_controlPoint = point2;
+        m_currentPoint = point3;
+    } else
+        m_consumer->curveToCubic(point1, point2, point3, m_mode);
+    return true;
+}
+
+bool SVGPathParser::parseCurveToCubicSmoothSegment()
+{
+    float x2;
+    float y2;
+    float toX;
+    float toY; 
+    if (!parseNumber(m_ptr, m_end, x2)
+        || !parseNumber(m_ptr, m_end, y2)
+        || !parseNumber(m_ptr, m_end, toX)
+        || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    if (m_lastCommand != 'c'
+        && m_lastCommand != 'C'
+        && m_lastCommand != 's'
+        && m_lastCommand != 'S')
+        m_controlPoint = m_currentPoint;
+
+    FloatPoint point2(x2, y2);
+    FloatPoint point3(toX, toY);
+    if (m_normalized) {
+        FloatPoint point1 = m_currentPoint;
+        point1.scale(2, 2);
+        point1.move(-m_controlPoint.x(), -m_controlPoint.y());
+        if (m_mode == RelativeCoordinates) {
+            point2 += m_currentPoint;
+            point3 += m_currentPoint;
+        }
+
+        m_consumer->curveToCubic(point1, point2, point3, AbsoluteCoordinates);
+
+        m_controlPoint = point2;
+        m_currentPoint = point3;
+    } else
+        m_consumer->curveToCubicSmooth(point2, point3, m_mode);
+    return true;
+}
+
+bool SVGPathParser::parseCurveToQuadraticSegment()
+{
+    float x1;
+    float y1;
+    float toX;
+    float toY;
+    if (!parseNumber(m_ptr, m_end, x1)
+        || !parseNumber(m_ptr, m_end, y1)
+        || !parseNumber(m_ptr, m_end, toX)
+        || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    FloatPoint point3(toX, toY);
+    if (m_normalized) {
+        FloatPoint point1 = m_currentPoint;
+        point1.move(2 * x1, 2 * y1);
+        FloatPoint point2(toX + 2 * x1, toY + 2 * y1);
+        if (m_mode == RelativeCoordinates) {
+            point1.move(2 * m_currentPoint.x(), 2 * m_currentPoint.y());
+            point2.move(3 * m_currentPoint.x(), 3 * m_currentPoint.y());
+            point3 += m_currentPoint;
+        }
+        point1.scale(gOneOverThree, gOneOverThree);
+        point2.scale(gOneOverThree, gOneOverThree);
+
+        m_consumer->curveToCubic(point1, point2, point3, AbsoluteCoordinates);
+
+        m_controlPoint = FloatPoint(x1, y1);
+        if (m_mode == RelativeCoordinates)
+            m_controlPoint += m_currentPoint;
+        m_currentPoint = point3;
+    } else
+        m_consumer->curveToQuadratic(FloatPoint(x1, y1), point3, m_mode);
+    return true;
+}
+
+bool SVGPathParser::parseCurveToQuadraticSmoothSegment()
+{
+    float toX;
+    float toY;
+    if (!parseNumber(m_ptr, m_end, toX) || !parseNumber(m_ptr, m_end, toY))
+        return false;
+    if (m_lastCommand != 'q'
+        && m_lastCommand != 'Q'
+        && m_lastCommand != 't'
+        && m_lastCommand != 'T')
+        m_controlPoint = m_currentPoint;
+
+    if (m_normalized) {
+        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(toX + 2 * cubicPoint.x(), toY + 2 * cubicPoint.y());
+        FloatPoint point3(toX, toY);
+        if (m_mode == RelativeCoordinates) {
+            point2 += m_currentPoint;
+            point3 += m_currentPoint;
+        }
+        point1.scale(gOneOverThree, gOneOverThree);
+        point2.scale(gOneOverThree, gOneOverThree);
+
+        m_consumer->curveToCubic(point1, point2, point3, AbsoluteCoordinates);
+
+        m_controlPoint = cubicPoint;
+        m_currentPoint = point3;
+    } else
+        m_consumer->curveToQuadraticSmooth(FloatPoint(toX, toY), m_mode);
+    return true;
+}
+
+bool SVGPathParser::parseArcToSegment()
+{
+    bool largeArc;
+    bool sweep;
+    float angle;
+    float rx;
+    float ry;
+    float toX;
+    float toY;
+    if (!parseNumber(m_ptr, m_end, rx)
+        || !parseNumber(m_ptr, m_end, ry)
+        || !parseNumber(m_ptr, m_end, angle)
+        || !parseArcFlag(m_ptr, m_end, largeArc)
+        || !parseArcFlag(m_ptr, m_end, sweep)
+        || !parseNumber(m_ptr, m_end, toX)
+        || !parseNumber(m_ptr, m_end, toY))
+        return false;
+
+    FloatPoint point2 = FloatPoint(toX, toY);
+    // 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_normalized) {
+            if (m_mode == RelativeCoordinates)
+                m_currentPoint += point2;
+            else
+                m_currentPoint = point2;
+            m_consumer->lineTo(m_currentPoint, AbsoluteCoordinates);
+        } else
+            m_consumer->lineTo(point2, m_mode);
+        return true;
+    }
+
+    if (m_normalized) {
+        FloatPoint point1 = m_currentPoint;
+        if (m_mode == RelativeCoordinates)
+            point2 += m_currentPoint;
+        m_currentPoint = point2;
+        return decomposeArcToCubic(angle, rx, ry, point1, point2, largeArc, sweep);
+    }
+    m_consumer->arcTo(point2, rx, ry, angle, largeArc, sweep, m_mode);
+    return true;
+}
+
+bool SVGPathParser::parsePathDataString(const String& s, bool normalized)
+{
+    m_ptr = s.characters();
+    m_end = m_ptr + s.length();
+    m_normalized = normalized;
+
+    m_controlPoint = FloatPoint();
+    m_currentPoint = FloatPoint();
+    m_subPathPoint = FloatPoint();
+    m_pathClosed = true;
+
+    // Skip any leading spaces.
+    if (!skipOptionalSpaces(m_ptr, m_end))
+        return false;
+
+    char command = *(m_ptr++);
+    m_lastCommand = ' ';
+    // Path must start with moveto.
+    if (command != 'm' && command != 'M')
+        return false;
+
+    while (true) {
+        // Skip spaces between command and first coordinate.
+        skipOptionalSpaces(m_ptr, m_end);
+        m_mode = command >= 'a' && command <= 'z' ? RelativeCoordinates : AbsoluteCoordinates;
+        switch (command) {
+        case 'm':
+        case 'M':
+            if (!parseMoveToSegment())
+                return false;
+            break;
+        case 'l':
+        case 'L':
+            if (!parseLineToSegment())
+                return false;
+            break;
+        case 'h':
+        case 'H':
+            if (!parseLineToHorizontalSegment())
+                return false;
+            break;
+        case 'v':
+        case 'V':
+            if (!parseLineToVerticalSegment())
+                return false;
+            break;
+        case 'z':
+        case 'Z':
+            parseClosePathSegment();
+            break;
+        case 'c':
+        case 'C':
+            if (!parseCurveToCubicSegment())
+                return false;
+            break;
+        case 's':
+        case 'S':
+            if (!parseCurveToCubicSmoothSegment())
+                return false;
+            break;
+        case 'q':
+        case 'Q':
+            if (!parseCurveToQuadraticSegment())
+                return false;
+            break;
+        case 't':
+        case 'T':
+            if (!parseCurveToQuadraticSmoothSegment())
+                return false;
+            break;
+        case 'a':
+        case 'A':
+            if (!parseArcToSegment())
+                return false;
+            break;
+        default:
+            return false;
+        }
+        m_lastCommand = command;
+
+        if (m_ptr >= m_end)
+            return true;
+
+        // Check for remaining coordinates in the current command.
+        if ((*m_ptr == '+' || *m_ptr == '-' || *m_ptr == '.' || (*m_ptr >= '0' && *m_ptr <= '9'))
+            && command != 'z' && command != 'Z') {
+            if (command == 'M')
+                command = 'L';
+            else if (command == 'm')
+                command = 'l';
+        } else
+            command = *(m_ptr++);
+
+        if (m_lastCommand != 'C' && m_lastCommand != 'c'
+            && m_lastCommand != 'S' && m_lastCommand != 's'
+            && m_lastCommand != 'Q' && m_lastCommand != 'q'
+            && m_lastCommand != 'T' && m_lastCommand != 't')
+            m_controlPoint = m_currentPoint;
+    }
+
+    return false;
+}
+
+// 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 point3 = FloatPoint(cosEndTheta, sinEndTheta);
+        point3.move(centerPoint.x(), centerPoint.y());
+        point2 = point3;
+        point2.move(t * sinEndTheta, -t * cosEndTheta);
+
+        m_consumer->curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2),
+                                 pointTransform.mapPoint(point3), AbsoluteCoordinates);
+    }
+    return true;
+}
+
+}
+
+#endif // ENABLE(SVG)