blob: d9e3835e27b411b25bb40b07d28b8f6b3d88673c [file] [log] [blame]
/*
* 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 "core/svg/SVGPathParser.h"
#include "core/svg/SVGPathConsumer.h"
#include "platform/transforms/AffineTransform.h"
#include "wtf/MathExtras.h"
namespace blink {
static FloatPoint reflectedPoint(const FloatPoint& reflectIn, const FloatPoint& pointToReflect)
{
return FloatPoint(2 * reflectIn.x() - pointToReflect.x(), 2 * reflectIn.y() - pointToReflect.y());
}
// Blend the points with a ratio (1/3):(2/3).
static FloatPoint blendPoints(const FloatPoint& p1, const FloatPoint& p2)
{
const float oneOverThree = 1 / 3.f;
return FloatPoint((p1.x() + 2 * p2.x()) * oneOverThree, (p1.y() + 2 * p2.y()) * oneOverThree);
}
static inline bool isCubicCommand(SVGPathSegType command)
{
return command == PathSegCurveToCubicAbs
|| command == PathSegCurveToCubicRel
|| command == PathSegCurveToCubicSmoothAbs
|| command == PathSegCurveToCubicSmoothRel;
}
static inline bool isQuadraticCommand(SVGPathSegType command)
{
return command == PathSegCurveToQuadraticAbs
|| command == PathSegCurveToQuadraticRel
|| command == PathSegCurveToQuadraticSmoothAbs
|| command == PathSegCurveToQuadraticSmoothRel;
}
void SVGPathNormalizer::emitSegment(const PathSegmentData& segment)
{
PathSegmentData normSeg = segment;
// Convert relative points to absolute points.
switch (segment.command) {
case PathSegCurveToQuadraticRel:
normSeg.point1 += m_currentPoint;
normSeg.targetPoint += m_currentPoint;
break;
case PathSegCurveToCubicRel:
normSeg.point1 += m_currentPoint;
/* fall through */
case PathSegCurveToCubicSmoothRel:
normSeg.point2 += m_currentPoint;
/* fall through */
case PathSegMoveToRel:
case PathSegLineToRel:
case PathSegLineToHorizontalRel:
case PathSegLineToVerticalRel:
case PathSegCurveToQuadraticSmoothRel:
case PathSegArcRel:
normSeg.targetPoint += m_currentPoint;
break;
case PathSegLineToHorizontalAbs:
normSeg.targetPoint.setY(m_currentPoint.y());
break;
case PathSegLineToVerticalAbs:
normSeg.targetPoint.setX(m_currentPoint.x());
break;
case PathSegClosePath:
// Reset m_currentPoint for the next path.
normSeg.targetPoint = m_subPathPoint;
break;
default:
break;
}
// Update command verb, handle smooth segments and convert quadratic curve
// segments to cubics.
switch (segment.command) {
case PathSegMoveToRel:
case PathSegMoveToAbs:
m_subPathPoint = normSeg.targetPoint;
normSeg.command = PathSegMoveToAbs;
break;
case PathSegLineToRel:
case PathSegLineToAbs:
case PathSegLineToHorizontalRel:
case PathSegLineToHorizontalAbs:
case PathSegLineToVerticalRel:
case PathSegLineToVerticalAbs:
normSeg.command = PathSegLineToAbs;
break;
case PathSegClosePath:
normSeg.command = PathSegClosePath;
break;
case PathSegCurveToCubicSmoothRel:
case PathSegCurveToCubicSmoothAbs:
if (!isCubicCommand(m_lastCommand))
normSeg.point1 = m_currentPoint;
else
normSeg.point1 = reflectedPoint(m_currentPoint, m_controlPoint);
/* fall through */
case PathSegCurveToCubicRel:
case PathSegCurveToCubicAbs:
m_controlPoint = normSeg.point2;
normSeg.command = PathSegCurveToCubicAbs;
break;
case PathSegCurveToQuadraticSmoothRel:
case PathSegCurveToQuadraticSmoothAbs:
if (!isQuadraticCommand(m_lastCommand))
normSeg.point1 = m_currentPoint;
else
normSeg.point1 = reflectedPoint(m_currentPoint, m_controlPoint);
/* fall through */
case PathSegCurveToQuadraticRel:
case PathSegCurveToQuadraticAbs:
// Save the unmodified control point.
m_controlPoint = normSeg.point1;
normSeg.point1 = blendPoints(m_currentPoint, m_controlPoint);
normSeg.point2 = blendPoints(normSeg.targetPoint, m_controlPoint);
normSeg.command = PathSegCurveToCubicAbs;
break;
case PathSegArcRel:
case PathSegArcAbs:
if (!decomposeArcToCubic(m_currentPoint, normSeg)) {
// On failure, emit a line segment to the target point.
normSeg.command = PathSegLineToAbs;
} else {
// decomposeArcToCubic() has already emitted the normalized
// segments, so set command to PathSegArcAbs, to skip any further
// emit.
normSeg.command = PathSegArcAbs;
}
break;
default:
ASSERT_NOT_REACHED();
}
if (normSeg.command != PathSegArcAbs)
m_consumer->emitSegment(normSeg);
m_currentPoint = normSeg.targetPoint;
if (!isCubicCommand(segment.command) && !isQuadraticCommand(segment.command))
m_controlPoint = m_currentPoint;
m_lastCommand = segment.command;
}
// 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 SVGPathNormalizer::decomposeArcToCubic(const FloatPoint& currentPoint, const PathSegmentData& arcSegment)
{
// 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
float rx = fabsf(arcSegment.arcRadii().x());
float ry = fabsf(arcSegment.arcRadii().y());
if (!rx || !ry)
return false;
// 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.
if (arcSegment.targetPoint == currentPoint)
return false;
float angle = arcSegment.arcAngle();
FloatSize midPointDistance = currentPoint - arcSegment.targetPoint;
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);
FloatPoint point1 = pointTransform.mapPoint(currentPoint);
FloatPoint point2 = pointTransform.mapPoint(arcSegment.targetPoint);
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 (arcSegment.arcSweep == arcSegment.arcLarge)
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 && arcSegment.arcSweep)
thetaArc += twoPiFloat;
else if (thetaArc > 0 && !arcSegment.arcSweep)
thetaArc -= twoPiFloat;
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);
PathSegmentData cubicSegment;
cubicSegment.command = PathSegCurveToCubicAbs;
cubicSegment.point1 = pointTransform.mapPoint(point1);
cubicSegment.point2 = pointTransform.mapPoint(point2);
cubicSegment.targetPoint = pointTransform.mapPoint(targetPoint);
m_consumer->emitSegment(cubicSegment);
}
return true;
}
} // namespace blink