Home | History | Annotate | Download | only in svg
      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