/* * Copyright (C) Research In Motion Limited 2010, 2011. All rights reserved. * Copyright (C) 2015 Apple Inc. 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 "SVGPathBlender.h" #include "AnimationUtilities.h" #include "SVGPathSeg.h" #include "SVGPathSource.h" #include #include namespace WebCore { bool SVGPathBlender::addAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, unsigned repeatCount) { SVGPathBlender blender(fromSource, toSource, &consumer); return blender.addAnimatedPath(repeatCount); } bool SVGPathBlender::blendAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, float progress) { SVGPathBlender blender(fromSource, toSource, &consumer); return blender.blendAnimatedPath(progress); } bool SVGPathBlender::canBlendPaths(SVGPathSource& fromSource, SVGPathSource& toSource) { SVGPathBlender blender(fromSource, toSource); return blender.canBlendPaths(); } SVGPathBlender::SVGPathBlender(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer* consumer) : m_fromSource(fromSource) , m_toSource(toSource) , m_consumer(consumer) { } // Helper functions static inline FloatPoint blendFloatPoint(const FloatPoint& a, const FloatPoint& b, float progress) { BlendingContext context { progress }; return FloatPoint(blend(a.x(), b.x(), context), blend(a.y(), b.y(), context)); } float SVGPathBlender::blendAnimatedDimensonalFloat(float from, float to, FloatBlendMode blendMode, float progress) { if (m_addTypesCount) { ASSERT(m_fromMode == m_toMode); return from + to * m_addTypesCount; } if (m_fromMode == m_toMode) return blend(from, to, { progress }); float fromValue = blendMode == BlendHorizontal ? m_fromCurrentPoint.x() : m_fromCurrentPoint.y(); float toValue = blendMode == BlendHorizontal ? m_toCurrentPoint.x() : m_toCurrentPoint.y(); // Transform toY to the coordinate mode of fromY float animValue = blend(from, m_fromMode == AbsoluteCoordinates ? to + toValue : to - toValue, { progress }); if (m_isInFirstHalfOfAnimation) return animValue; // Transform the animated point to the coordinate mode, needed for the current progress. float currentValue = blend(fromValue, toValue, { progress }); return m_toMode == AbsoluteCoordinates ? animValue + currentValue : animValue - currentValue; } FloatPoint SVGPathBlender::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint, float progress) { if (m_addTypesCount) { ASSERT(m_fromMode == m_toMode); FloatPoint repeatedToPoint = toPoint; repeatedToPoint.scale(m_addTypesCount); return fromPoint + repeatedToPoint; } if (m_fromMode == m_toMode) return blendFloatPoint(fromPoint, toPoint, progress); // Transform toPoint to the coordinate mode of fromPoint FloatPoint animatedPoint = toPoint; if (m_fromMode == AbsoluteCoordinates) animatedPoint += m_toCurrentPoint; else animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y()); animatedPoint = blendFloatPoint(fromPoint, animatedPoint, progress); if (m_isInFirstHalfOfAnimation) return animatedPoint; // Transform the animated point to the coordinate mode, needed for the current progress. FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, progress); if (m_toMode == AbsoluteCoordinates) return animatedPoint + currentPoint; animatedPoint.move(-currentPoint.x(), -currentPoint.y()); return animatedPoint; } template using InvokeResult = typename std::invoke_result_t::value_type; template using ResultPair = std::pair, InvokeResult>; template static std::optional> pullFromSources(SVGPathSource& fromSource, SVGPathSource& toSource, Function&& function) { InvokeResult fromResult; if (fromSource.hasMoreData()) { auto parsedFrom = std::invoke(function, fromSource); if (!parsedFrom) return std::nullopt; fromResult = WTFMove(*parsedFrom); } auto parsedTo = std::invoke(std::forward(function), toSource); if (!parsedTo) return std::nullopt; return ResultPair { WTFMove(fromResult), WTFMove(*parsedTo) }; } bool SVGPathBlender::blendMoveToSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseMoveToSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->moveTo(blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendLineToSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseLineToSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->lineTo(blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendLineToHorizontalSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseLineToHorizontalSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->lineToHorizontal(blendAnimatedDimensonalFloat(from.x, to.x, BlendHorizontal, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint.setX(m_fromMode == AbsoluteCoordinates ? from.x : m_fromCurrentPoint.x() + from.x); m_toCurrentPoint.setX(m_toMode == AbsoluteCoordinates ? to.x : m_toCurrentPoint.x() + to.x); return true; } bool SVGPathBlender::blendLineToVerticalSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseLineToVerticalSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->lineToVertical(blendAnimatedDimensonalFloat(from.y, to.y, BlendVertical, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint.setY(m_fromMode == AbsoluteCoordinates ? from.y : m_fromCurrentPoint.y() + from.y); m_toCurrentPoint.setY(m_toMode == AbsoluteCoordinates ? to.y : m_toCurrentPoint.y() + to.y); return true; } bool SVGPathBlender::blendCurveToCubicSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseCurveToCubicSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->curveToCubic(blendAnimatedFloatPoint(from.point1, to.point1, progress), blendAnimatedFloatPoint(from.point2, to.point2, progress), blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendCurveToCubicSmoothSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseCurveToCubicSmoothSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->curveToCubicSmooth(blendAnimatedFloatPoint(from.point2, to.point2, progress), blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendCurveToQuadraticSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseCurveToQuadraticSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->curveToQuadratic(blendAnimatedFloatPoint(from.point1, to.point1, progress), blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendCurveToQuadraticSmoothSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseCurveToQuadraticSmoothSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; m_consumer->curveToQuadraticSmooth(blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } bool SVGPathBlender::blendArcToSegment(float progress) { auto result = pullFromSources(m_fromSource, m_toSource, &SVGPathSource::parseArcToSegment); if (!result) return false; if (!m_consumer) return true; auto [from, to] = *result; if (m_addTypesCount) { ASSERT(m_fromMode == m_toMode); auto scaledToTargetPoint = to.targetPoint; scaledToTargetPoint.scale(m_addTypesCount); m_consumer->arcTo(from.rx + to.rx * m_addTypesCount, from.ry + to.ry * m_addTypesCount, from.angle + to.angle * m_addTypesCount, from.largeArc || to.largeArc, from.sweep || to.sweep, from.targetPoint + scaledToTargetPoint, m_fromMode); } else { BlendingContext context { progress }; m_consumer->arcTo(blend(from.rx, to.rx, context), blend(from.ry, to.ry, context), blend(from.angle, to.angle, context), m_isInFirstHalfOfAnimation ? from.largeArc : to.largeArc, m_isInFirstHalfOfAnimation ? from.sweep : to.sweep, blendAnimatedFloatPoint(from.targetPoint, to.targetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); } m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? from.targetPoint : m_fromCurrentPoint + from.targetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? to.targetPoint : m_toCurrentPoint + to.targetPoint; return true; } static inline PathCoordinateMode coordinateModeOfCommand(const SVGPathSegType& type) { if (type < PathSegMoveToAbs) return AbsoluteCoordinates; // Odd number = relative command if (type % 2) return RelativeCoordinates; return AbsoluteCoordinates; } static inline bool isSegmentEqual(const SVGPathSegType& fromType, const SVGPathSegType& toType, const PathCoordinateMode& fromMode, const PathCoordinateMode& toMode) { if (fromType == toType && (fromType == PathSegUnknown || fromType == PathSegClosePath)) return true; unsigned short from = fromType; unsigned short to = toType; if (fromMode == toMode) return from == to; if (fromMode == AbsoluteCoordinates) return from == to - 1; return to == from - 1; } bool SVGPathBlender::addAnimatedPath(unsigned repeatCount) { SetForScope change(m_addTypesCount, repeatCount); return blendAnimatedPath(0); } bool SVGPathBlender::canBlendPaths() { float progress = 0.5; bool fromSourceHadData = m_fromSource.hasMoreData(); while (m_toSource.hasMoreData()) { SVGPathSegType fromCommand; if (fromSourceHadData) { auto parsedFromCommand = m_fromSource.parseSVGSegmentType(); if (!parsedFromCommand) return false; fromCommand = *parsedFromCommand; } auto parsedtoCommand = m_toSource.parseSVGSegmentType(); if (!parsedtoCommand) return false; SVGPathSegType toCommand = *parsedtoCommand; m_toMode = coordinateModeOfCommand(toCommand); m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode; if (m_fromMode != m_toMode && m_addTypesCount) return false; if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode)) return false; switch (toCommand) { case PathSegMoveToRel: case PathSegMoveToAbs: if (!blendMoveToSegment(progress)) return false; break; case PathSegLineToRel: case PathSegLineToAbs: if (!blendLineToSegment(progress)) return false; break; case PathSegLineToHorizontalRel: case PathSegLineToHorizontalAbs: if (!blendLineToHorizontalSegment(progress)) return false; break; case PathSegLineToVerticalRel: case PathSegLineToVerticalAbs: if (!blendLineToVerticalSegment(progress)) return false; break; case PathSegClosePath: break; case PathSegCurveToCubicRel: case PathSegCurveToCubicAbs: if (!blendCurveToCubicSegment(progress)) return false; break; case PathSegCurveToCubicSmoothRel: case PathSegCurveToCubicSmoothAbs: if (!blendCurveToCubicSmoothSegment(progress)) return false; break; case PathSegCurveToQuadraticRel: case PathSegCurveToQuadraticAbs: if (!blendCurveToQuadraticSegment(progress)) return false; break; case PathSegCurveToQuadraticSmoothRel: case PathSegCurveToQuadraticSmoothAbs: if (!blendCurveToQuadraticSmoothSegment(progress)) return false; break; case PathSegArcRel: case PathSegArcAbs: if (!blendArcToSegment(progress)) return false; break; case PathSegUnknown: return false; } if (!fromSourceHadData) continue; if (m_fromSource.hasMoreData() != m_toSource.hasMoreData()) return false; if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData()) return true; } return true; } bool SVGPathBlender::blendAnimatedPath(float progress) { m_isInFirstHalfOfAnimation = progress < 0.5f; bool fromSourceHadData = m_fromSource.hasMoreData(); while (m_toSource.hasMoreData()) { SVGPathSegType fromCommand; if (fromSourceHadData) { auto parsedFromCommand = m_fromSource.parseSVGSegmentType(); if (!parsedFromCommand) return false; fromCommand = *parsedFromCommand; } auto parsedToCommand = m_toSource.parseSVGSegmentType(); if (!parsedToCommand) return false; SVGPathSegType toCommand = *parsedToCommand; m_toMode = coordinateModeOfCommand(toCommand); m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode; if (m_fromMode != m_toMode && m_addTypesCount) return false; if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode)) return false; switch (toCommand) { case PathSegMoveToRel: case PathSegMoveToAbs: if (!blendMoveToSegment(progress)) return false; break; case PathSegLineToRel: case PathSegLineToAbs: if (!blendLineToSegment(progress)) return false; break; case PathSegLineToHorizontalRel: case PathSegLineToHorizontalAbs: if (!blendLineToHorizontalSegment(progress)) return false; break; case PathSegLineToVerticalRel: case PathSegLineToVerticalAbs: if (!blendLineToVerticalSegment(progress)) return false; break; case PathSegClosePath: m_consumer->closePath(); break; case PathSegCurveToCubicRel: case PathSegCurveToCubicAbs: if (!blendCurveToCubicSegment(progress)) return false; break; case PathSegCurveToCubicSmoothRel: case PathSegCurveToCubicSmoothAbs: if (!blendCurveToCubicSmoothSegment(progress)) return false; break; case PathSegCurveToQuadraticRel: case PathSegCurveToQuadraticAbs: if (!blendCurveToQuadraticSegment(progress)) return false; break; case PathSegCurveToQuadraticSmoothRel: case PathSegCurveToQuadraticSmoothAbs: if (!blendCurveToQuadraticSmoothSegment(progress)) return false; break; case PathSegArcRel: case PathSegArcAbs: if (!blendArcToSegment(progress)) return false; break; case PathSegUnknown: return false; } if (!fromSourceHadData) continue; if (m_fromSource.hasMoreData() != m_toSource.hasMoreData()) return false; if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData()) return true; } return true; } }