blob: bf75c1ca9a067ab2557ac7050fc2d06f06fcf4c4 [file] [log] [blame]
/*
Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
2004, 2005 Rob Buis <buis@kde.org>
This file is part of the KDE project
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., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "config.h"
#ifdef SVG_SUPPORT
#include "Attr.h"
#include <kcanvas/RenderPath.h>
#include <kcanvas/KCanvasMatrix.h>
#include "SVGAngle.h"
#include "SVGMatrix.h"
#include "SVGTransform.h"
#include "SVGStyledTransformableElement.h"
#include "SVGTransformList.h"
#include "SVGAnimatedTransformList.h"
#include "SVGAnimateTransformElement.h"
#include "KSVGTimeScheduler.h"
#include "Document.h"
#include "SVGDocumentExtensions.h"
#include "SVGSVGElement.h"
#include <math.h>
using namespace std;
namespace WebCore {
SVGAnimateTransformElement::SVGAnimateTransformElement(const QualifiedName& tagName, Document *doc)
: SVGAnimationElement(tagName, doc)
, m_currentItem(-1)
, m_type(SVG_TRANSFORM_UNKNOWN)
, m_rotateSpecialCase(false)
, m_toRotateSpecialCase(false)
, m_fromRotateSpecialCase(false)
{
}
SVGAnimateTransformElement::~SVGAnimateTransformElement()
{
}
void SVGAnimateTransformElement::parseMappedAttribute(MappedAttribute *attr)
{
if (attr->name() == SVGNames::typeAttr) {
const String& value = attr->value();
if (value == "translate")
m_type = SVG_TRANSFORM_TRANSLATE;
else if (value == "scale")
m_type = SVG_TRANSFORM_SCALE;
else if (value == "rotate")
m_type = SVG_TRANSFORM_ROTATE;
else if (value == "skewX")
m_type = SVG_TRANSFORM_SKEWX;
else if (value == "skewY")
m_type = SVG_TRANSFORM_SKEWY;
} else
SVGAnimationElement::parseMappedAttribute(attr);
}
void SVGAnimateTransformElement::handleTimerEvent(double timePercentage)
{
// Start condition.
if (!m_connected) {
m_initialTransform = 0;
// Save initial transform... (needed for fill="remove" or additve="sum")
if (targetElement()->isStyledTransformable()) {
SVGStyledTransformableElement *transform = static_cast<SVGStyledTransformableElement *>(targetElement());
RefPtr<SVGTransformList> transformList = transform->transform()->baseVal();
if (transformList) {
for (unsigned long i = 0; i < transformList->numberOfItems(); i++) {
SVGTransform *value = transformList->getItem(i);
if (!value)
continue;
if (value->type() == m_type) {
m_initialTransform = value;
break;
}
}
}
}
// Animation mode handling
switch(detectAnimationMode())
{
case TO_ANIMATION:
case FROM_TO_ANIMATION:
{
m_toTransform = parseTransformValue(m_to);
m_toRotateSpecialCase = m_rotateSpecialCase;
if (!m_from.isEmpty()) // from-to animation
{
m_fromTransform = parseTransformValue(m_from);
m_fromRotateSpecialCase = m_rotateSpecialCase;
}
else // to animation
{
m_fromTransform = m_initialTransform;
m_fromRotateSpecialCase = false;
}
if (!m_fromTransform)
m_fromTransform = new SVGTransform();
break;
}
case BY_ANIMATION:
case FROM_BY_ANIMATION:
{
m_toTransform = parseTransformValue(m_by);
m_toRotateSpecialCase = m_rotateSpecialCase;
if (!m_from.isEmpty()) // from-by animation
{
m_fromTransform = parseTransformValue(m_from);
m_fromRotateSpecialCase = m_rotateSpecialCase;
}
else // by animation
{
m_fromTransform = m_initialTransform;
m_fromRotateSpecialCase = false;
}
if (!m_fromTransform)
m_fromTransform = new SVGTransform();
SVGMatrix *byMatrix = m_toTransform->matrix();
SVGMatrix *fromMatrix = m_fromTransform->matrix();
byMatrix->multiply(fromMatrix);
break;
}
case VALUES_ANIMATION:
break;
default:
{
//kdError() << k_funcinfo << " Unable to detect animation mode! Aborting creation!" << endl;
return;
}
}
ownerSVGElement()->timeScheduler()->connectIntervalTimer(this);
m_connected = true;
return;
}
// Calculations...
if (timePercentage >= 1.0)
timePercentage = 1.0;
AffineTransform qToMatrix, qFromMatrix;
double useTimePercentage = timePercentage;
if (m_values) {
int itemByPercentage = calculateCurrentValueItem(timePercentage);
if (itemByPercentage == -1)
return;
if (m_currentItem != itemByPercentage) // Item changed...
{
// Extract current 'from' / 'to' values
String value1 = String(m_values->getItem(itemByPercentage));
String value2 = String(m_values->getItem(itemByPercentage + 1));
// Calculate new from/to transform values...
if (!value1.isEmpty() && !value2.isEmpty()) {
bool apply = false;
if (m_toTransform && m_fromTransform) {
qToMatrix = m_toTransform->matrix()->matrix();
qFromMatrix = m_fromTransform->matrix()->matrix();
apply = true;
useTimePercentage = 1.0;
}
m_toTransform = parseTransformValue(value2.deprecatedString());
m_toRotateSpecialCase = m_rotateSpecialCase;
m_fromTransform = parseTransformValue(value1.deprecatedString());
m_fromRotateSpecialCase = m_rotateSpecialCase;
m_currentItem = itemByPercentage;
if (!apply)
return;
}
}
else if (m_toTransform && m_fromTransform)
useTimePercentage = calculateRelativeTimePercentage(timePercentage, m_currentItem);
}
if (m_toTransform && m_toTransform->matrix() && qToMatrix.isIdentity())
qToMatrix = m_toTransform->matrix()->matrix();
if (m_fromTransform && m_fromTransform->matrix() && qFromMatrix.isIdentity())
qFromMatrix = m_fromTransform->matrix()->matrix();
if (!m_transformMatrix)
m_transformMatrix = new SVGMatrix();
else {
m_transformMatrix->reset();
if (isAccumulated() && repeations() != 0.0 && m_lastMatrix)
m_transformMatrix->multiply(m_lastMatrix.get());
}
switch(m_type)
{
case SVG_TRANSFORM_TRANSLATE:
{
double dx = ((qToMatrix.dx() - qFromMatrix.dx()) * useTimePercentage) + qFromMatrix.dx();
double dy = ((qToMatrix.dy() - qFromMatrix.dy()) * useTimePercentage) + qFromMatrix.dy();
m_transformMatrix->translate(dx, dy);
break;
}
case SVG_TRANSFORM_SCALE:
{
double sx = ((qToMatrix.m11() - qFromMatrix.m11()) * useTimePercentage) + qFromMatrix.m11();
double sy = ((qToMatrix.m22() - qFromMatrix.m22()) * useTimePercentage) + qFromMatrix.m22();
m_transformMatrix->scaleNonUniform(sx, sy);
break;
}
case SVG_TRANSFORM_ROTATE:
{
double toAngle, toCx, toCy, fromAngle, fromCx, fromCy;
calculateRotationFromMatrix(qToMatrix, toAngle, toCx, toCy);
calculateRotationFromMatrix(qFromMatrix, fromAngle, fromCx, fromCy);
if (m_toRotateSpecialCase) {
if (lround(toAngle) == 1)
toAngle = 0.0;
else
toAngle = 360.0;
}
if (m_fromRotateSpecialCase) {
if (lround(fromAngle) == 1)
fromAngle = 0.0;
else
fromAngle = 360.0;
}
double angle = ((toAngle - fromAngle) * useTimePercentage) + fromAngle;
double cx = (toCx - fromCx) * useTimePercentage + fromCx;
double cy = (toCy - fromCy) * useTimePercentage + fromCy;
m_transformMatrix->translate(cx, cy);
m_transformMatrix->rotate(angle);
m_transformMatrix->translate(-cx, -cy);
break;
}
case SVG_TRANSFORM_SKEWX:
{
double sx = (SVGAngle::todeg(atan(qToMatrix.m21()) - atan(qFromMatrix.m21())) *
useTimePercentage) + SVGAngle::todeg(atan(qFromMatrix.m21()));
m_transformMatrix->skewX(sx);
break;
}
case SVG_TRANSFORM_SKEWY:
{
double sy = (SVGAngle::todeg(atan(qToMatrix.m12()) - atan(qFromMatrix.m12())) *
useTimePercentage) + SVGAngle::todeg(atan(qFromMatrix.m12()));
m_transformMatrix->skewY(sy);
break;
}
default:
break;
}
// End condition.
if (timePercentage == 1.0)
{
if ((m_repeatCount > 0 && m_repeations < m_repeatCount - 1) || isIndefinite(m_repeatCount))
{
m_lastMatrix = new SVGMatrix();
if (m_transformMatrix)
m_lastMatrix->copy(m_transformMatrix.get());
m_repeations++;
return;
}
ownerSVGElement()->timeScheduler()->disconnectIntervalTimer(this);
m_connected = false;
// Reset...
m_currentItem = -1;
m_toTransform = 0;
m_fromTransform = 0;
m_initialTransform = 0;
if (!isFrozen()) {
SVGMatrix *initial = initialMatrix();
if (initial)
m_transformMatrix = initial;
else
m_transformMatrix = new SVGMatrix();
}
}
}
RefPtr<SVGTransform> SVGAnimateTransformElement::parseTransformValue(const DeprecatedString &data) const
{
DeprecatedString parse = data.stripWhiteSpace();
if (parse.isEmpty())
return 0;
int commaPos = parse.find(','); // In case two values are passed...
RefPtr<SVGTransform> parsedTransform = new SVGTransform();
switch(m_type)
{
case SVG_TRANSFORM_TRANSLATE:
{
double tx = 0.0, ty = 0.0;
if (commaPos != - 1) {
tx = parse.mid(0, commaPos).toDouble();
ty = parse.mid(commaPos + 1).toDouble();
} else
tx = parse.toDouble();
parsedTransform->setTranslate(tx, ty);
break;
}
case SVG_TRANSFORM_SCALE:
{
double sx = 1.0, sy = 1.0;
if (commaPos != - 1) {
sx = parse.mid(0, commaPos).toDouble();
sy = parse.mid(commaPos + 1).toDouble();
} else {
sx = parse.toDouble();
sy = sx;
}
parsedTransform->setScale(sx, sy);
break;
}
case SVG_TRANSFORM_ROTATE:
{
double angle = 0, cx = 0, cy = 0;
if (commaPos != - 1) {
angle = parse.mid(0, commaPos).toDouble(); // TODO: probably needs it's own 'angle' parser
int commaPosTwo = parse.find(',', commaPos + 1); // In case three values are passed...
if (commaPosTwo != -1) {
cx = parse.mid(commaPos + 1, commaPosTwo - commaPos - 1).toDouble();
cy = parse.mid(commaPosTwo + 1).toDouble();
}
}
else
angle = parse.toDouble();
// Ok now here's a hack to make it possible to calculate cx/cy values, if angle = 0
// or angle=360 -> for angle=0 our matrix is m11: 1 m12: 0 m21: 0 m22: 1 dx: 0 dy: 0
// As you can see there is no way to retrieve the cx/cy values for these angles.
// -> set 'm_rotateSpecialCase' to true, and save angle = 1/359 -> this way we can calculate
// the cx/cy values, while keeping this uber-optimized way of handling <animateTransform>!
m_rotateSpecialCase = false;
if (angle == 0.0) {
angle = angle + 1.0;
m_rotateSpecialCase = true;
} else if (angle == 360.0) {
angle = angle - 1.0;
m_rotateSpecialCase = true;
}
parsedTransform->setRotate(angle, cx, cy);
break;
}
case SVG_TRANSFORM_SKEWX:
case SVG_TRANSFORM_SKEWY:
{
double angle = parse.toDouble(); // TODO: probably needs it's own 'angle' parser
if (m_type == SVG_TRANSFORM_SKEWX)
parsedTransform->setSkewX(angle);
else
parsedTransform->setSkewY(angle);
break;
}
default:
return 0;
}
return parsedTransform;
}
void SVGAnimateTransformElement::calculateRotationFromMatrix(const AffineTransform &matrix, double &angle, double &cx, double &cy) const
{
double cosa = matrix.m11();
double sina = -matrix.m21();
if (cosa != 1.0)
{
// Calculate the angle via magic ;)
double temp = SVGAngle::todeg(asin(sina));
angle = SVGAngle::todeg(acos(cosa));
if (temp < 0)
angle = 360.0 - angle;
double res = (1 - cosa) + ((sina * sina) / (1 - cosa));
cx = (matrix.dx() - ((sina * matrix.dy()) / (1 - cosa))) / res;
cy = (matrix.dy() + ((sina * matrix.dx()) / (1 - cosa))) / res;
return;
}
cx = 0.0;
cy = 0.0;
angle = 0.0;
}
SVGMatrix *SVGAnimateTransformElement::initialMatrix() const
{
if (!targetElement()->isStyledTransformable())
return 0;
SVGStyledTransformableElement *transform = static_cast<SVGStyledTransformableElement *>(targetElement());
SVGTransformList *transformList = (transform ? transform->transform()->baseVal() : 0);
if (!transformList)
return 0;
RefPtr<SVGTransform> result = transformList->concatenate();
if (!result)
return 0;
SVGMatrix *ret = result->matrix();
ret->ref();
return ret;
}
SVGMatrix *SVGAnimateTransformElement::transformMatrix() const
{
return m_transformMatrix.get();
}
}
// vim:ts=4:noet
#endif // SVG_SUPPORT