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