/* * Copyright (C) 2002, 2003 The Karbon Developers * Copyright (C) 2006 Alexander Kellett * Copyright (C) 2006, 2007 Rob Buis * Copyright (C) 2007, 2009, 2015 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" #include "SVGPathParser.h" #include "AffineTransform.h" #include "SVGPathByteStreamBuilder.h" #include "SVGPathSource.h" #include "SVGPathStringBuilder.h" #include static const float gOneOverThree = 1 / 3.f; namespace WebCore { bool SVGPathParser::parse(SVGPathSource& source, SVGPathConsumer& consumer, PathParsingMode mode, bool checkForInitialMoveTo) { SVGPathParser parser(consumer, source, mode); return parser.parsePathData(checkForInitialMoveTo); } bool SVGPathParser::parseToByteStream(SVGPathSource& source, SVGPathByteStream& byteStream, PathParsingMode mode, bool checkForInitialMoveTo) { SVGPathByteStreamBuilder builder(byteStream); return parse(source, builder, mode, checkForInitialMoveTo); } bool SVGPathParser::parseToString(SVGPathSource& source, String& result, PathParsingMode mode, bool checkForInitialMoveTo) { SVGPathStringBuilder builder; SVGPathParser parser(builder, source, mode); bool ok = parser.parsePathData(checkForInitialMoveTo); result = builder.result(); return ok; } SVGPathParser::SVGPathParser(SVGPathConsumer& consumer, SVGPathSource& source, PathParsingMode parsingMode) : m_source(source) , m_consumer(consumer) , m_pathParsingMode(parsingMode) { } 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 // If the current point and target point for the arc are identical, it should be treated as a zero length // path. This ensures continuity in animations. rx = fabsf(rx); ry = fabsf(ry); bool arcIsZeroLength = false; if (m_pathParsingMode == NormalizedParsing) { if (m_mode == RelativeCoordinates) arcIsZeroLength = targetPoint == FloatPoint::zero(); else arcIsZeroLength = targetPoint == m_currentPoint; } if (!rx || !ry || arcIsZeroLength) { 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::parsePathData(bool checkForInitialMoveTo) { // Skip any leading spaces. if (!m_source.moveToNextToken()) return true; SVGPathSegType command; m_source.parseSVGSegmentType(command); // Path must start with moveto. if (checkForInitialMoveTo && 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; FALLTHROUGH; case PathSegMoveToAbs: if (!parseMoveToSegment()) return false; break; case PathSegLineToRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegLineToAbs: if (!parseLineToSegment()) return false; break; case PathSegLineToHorizontalRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegLineToHorizontalAbs: if (!parseLineToHorizontalSegment()) return false; break; case PathSegLineToVerticalRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegLineToVerticalAbs: if (!parseLineToVerticalSegment()) return false; break; case PathSegClosePath: parseClosePathSegment(); break; case PathSegCurveToCubicRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegCurveToCubicAbs: if (!parseCurveToCubicSegment()) return false; break; case PathSegCurveToCubicSmoothRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegCurveToCubicSmoothAbs: if (!parseCurveToCubicSmoothSegment()) return false; break; case PathSegCurveToQuadraticRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegCurveToQuadraticAbs: if (!parseCurveToQuadraticSegment()) return false; break; case PathSegCurveToQuadraticSmoothRel: m_mode = RelativeCoordinates; FALLTHROUGH; case PathSegCurveToQuadraticSmoothAbs: if (!parseCurveToQuadraticSmoothSegment()) return false; break; case PathSegArcRel: m_mode = RelativeCoordinates; FALLTHROUGH; 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; } // 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 = point1 + point2; centerPoint.scale(0.5f, 0.5f); centerPoint.move(-delta.height(), delta.width()); float theta1 = FloatPoint(point1 - centerPoint).slopeAngleRadians(); float theta2 = FloatPoint(point2 - centerPoint).slopeAngleRadians(); 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 (!std::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; } }