blob: da72fddb61cdaf7bfe254b7c9348e62576d76f91 [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 <kdom/core/AttrImpl.h>
#include <kcanvas/KCanvasItem.h>
#include <kcanvas/KCanvasMatrix.h>
#include "svgattrs.h"
#include "SVGAngleImpl.h"
#include "SVGMatrixImpl.h"
#include "SVGDocumentImpl.h"
#include "SVGTransformImpl.h"
#include "SVGTransformableImpl.h"
#include "SVGTransformListImpl.h"
#include "SVGAnimatedTransformListImpl.h"
#include "SVGAnimateTransformElementImpl.h"
#include <kdebug.h>
#include <cmath>
using namespace KSVG;
using namespace std;
SVGAnimateTransformElementImpl::SVGAnimateTransformElementImpl(KDOM::DocumentPtr *doc, KDOM::NodeImpl::Id id, KDOM::DOMStringImpl *prefix)
: SVGAnimationElementImpl(doc, id, prefix)
{
m_type = SVG_TRANSFORM_UNKNOWN;
m_toTransform = 0;
m_fromTransform = 0;
m_initialTransform = 0;
m_currentItem = -1;
m_lastMatrix = 0;
m_transformMatrix = 0;
m_rotateSpecialCase = false;
m_toRotateSpecialCase = false;
m_fromRotateSpecialCase = false;
}
SVGAnimateTransformElementImpl::~SVGAnimateTransformElementImpl()
{
if(m_toTransform)
m_toTransform->deref();
if(m_fromTransform)
m_fromTransform->deref();
if(m_initialTransform)
m_initialTransform->deref();
if(m_lastMatrix)
m_lastMatrix->deref();
if(m_transformMatrix)
m_transformMatrix->deref();
}
void SVGAnimateTransformElementImpl::parseAttribute(KDOM::AttributeImpl *attr)
{
int id = (attr->id() & NodeImpl_IdLocalMask);
KDOM::DOMString value(attr->value());
switch(id)
{
case ATTR_TYPE:
{
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;
break;
}
default:
SVGAnimationElementImpl::parseAttribute(attr);
};
}
void SVGAnimateTransformElementImpl::handleTimerEvent(double timePercentage)
{
// Start condition.
if(!m_connected)
{
if(m_initialTransform)
m_initialTransform->deref();
m_initialTransform = 0;
// Save initial transform... (needed for fill="remove" or additve="sum")
SVGTransformableImpl *transform = dynamic_cast<SVGTransformableImpl *>(targetElement());
SVGTransformListImpl *transformList = (transform ? transform->transform()->baseVal() : 0);
if(transformList)
{
transformList->ref();
for(unsigned long i = 0; i < transformList->numberOfItems(); i++)
{
SVGTransformImpl *value = transformList->getItem(i);
if(!value)
continue;
if(value->type() == m_type)
{
value->ref();
m_initialTransform = value;
break;
}
}
transformList->deref();
}
// Animation mode handling
switch(detectAnimationMode())
{
case TO_ANIMATION:
case FROM_TO_ANIMATION:
{
if(m_toTransform)
m_toTransform->deref();
m_toTransform = parseTransformValue(m_to);
m_toRotateSpecialCase = m_rotateSpecialCase;
if(m_fromTransform)
m_fromTransform->deref();
if(!m_from.isEmpty()) // from-to animation
{
m_fromTransform = parseTransformValue(m_from);
m_fromRotateSpecialCase = m_rotateSpecialCase;
}
else // to animation
{
m_fromTransform = m_initialTransform;
m_fromTransform->ref();
m_fromRotateSpecialCase = false;
}
if(!m_fromTransform)
{
m_fromTransform = new SVGTransformImpl();
m_fromTransform->ref();
}
break;
}
case BY_ANIMATION:
case FROM_BY_ANIMATION:
{
if(m_toTransform)
m_toTransform->deref();
m_toTransform = parseTransformValue(m_by);
m_toRotateSpecialCase = m_rotateSpecialCase;
if(m_fromTransform)
m_fromTransform->deref();
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 SVGTransformImpl();
m_fromTransform->ref();
}
SVGMatrixImpl *byMatrix = m_toTransform->matrix();
SVGMatrixImpl *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;
}
}
SVGDocumentImpl *document = static_cast<SVGDocumentImpl *>(ownerDocument());
if(document)
{
document->timeScheduler()->connectIntervalTimer(this);
m_connected = true;
}
return;
}
// Calculations...
if(timePercentage >= 1.0)
timePercentage = 1.0;
QWMatrix 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
KDOM::DOMString value1 = KDOM::DOMString(m_values->getItem(itemByPercentage));
KDOM::DOMString value2 = KDOM::DOMString(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()->qmatrix();
qFromMatrix = m_fromTransform->matrix()->qmatrix();
apply = true;
useTimePercentage = 1.0;
}
if(m_toTransform)
m_toTransform->deref();
m_toTransform = parseTransformValue(value2.string());
m_toRotateSpecialCase = m_rotateSpecialCase;
if(m_fromTransform)
m_fromTransform->deref();
m_fromTransform = parseTransformValue(value1.string());
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()->qmatrix();
if(m_fromTransform && m_fromTransform->matrix() && qFromMatrix.isIdentity())
qFromMatrix = m_fromTransform->matrix()->qmatrix();
if(!m_transformMatrix)
{
m_transformMatrix = new SVGMatrixImpl();
m_transformMatrix->ref();
}
else
{
m_transformMatrix->reset();
if(isAccumulated() && repeations() != 0.0 && m_lastMatrix)
m_transformMatrix->multiply(m_lastMatrix);
}
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(qRound(toAngle) == 1)
toAngle = 0.0;
else
toAngle = 360.0;
}
if(m_fromRotateSpecialCase)
{
if(qRound(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 = (SVGAngleImpl::todeg(atan(qToMatrix.m21()) - atan(qFromMatrix.m21())) *
useTimePercentage) + SVGAngleImpl::todeg(atan(qFromMatrix.m21()));
m_transformMatrix->skewX(sx);
break;
}
case SVG_TRANSFORM_SKEWY:
{
double sy = (SVGAngleImpl::todeg(atan(qToMatrix.m12()) - atan(qFromMatrix.m12())) *
useTimePercentage) + SVGAngleImpl::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))
{
if(m_lastMatrix)
m_lastMatrix->deref();
m_lastMatrix = new SVGMatrixImpl();
m_lastMatrix->ref();
if(m_transformMatrix)
m_lastMatrix->copy(m_transformMatrix);
m_repeations++;
return;
}
SVGDocumentImpl *document = static_cast<SVGDocumentImpl *>(ownerDocument());
if(document)
{
document->timeScheduler()->disconnectIntervalTimer(this);
m_connected = false;
}
// Reset...
m_currentItem = -1;
if(m_toTransform)
m_toTransform->deref();
m_toTransform = 0;
if(m_fromTransform)
m_fromTransform->deref();
m_fromTransform = 0;
if(m_initialTransform)
m_initialTransform->deref();
m_initialTransform = 0;
if(!isFrozen())
{
if(m_transformMatrix)
m_transformMatrix->deref();
SVGMatrixImpl *initial = initialMatrix();
if(initial)
m_transformMatrix = initial;
else
{
m_transformMatrix = new SVGMatrixImpl();
m_transformMatrix->ref();
}
}
}
}
SVGTransformImpl *SVGAnimateTransformElementImpl::parseTransformValue(const QString &data) const
{
SVGTransformImpl *ret = 0;
QString parse = data.stripWhiteSpace();
if(parse.isEmpty())
return ret;
int commaPos = parse.find(','); // In case two values are passed...
switch(m_type)
{
case SVG_TRANSFORM_TRANSLATE:
{
ret = new SVGTransformImpl();
ret->ref();
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();
ret->setTranslate(tx, ty);
break;
}
case SVG_TRANSFORM_SCALE:
{
ret = new SVGTransformImpl();
ret->ref();
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;
}
ret->setScale(sx, sy);
break;
}
case SVG_TRANSFORM_ROTATE:
{
ret = new SVGTransformImpl();
ret->ref();
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;
}
ret->setRotate(angle, cx, cy);
break;
}
case SVG_TRANSFORM_SKEWX:
case SVG_TRANSFORM_SKEWY:
{
ret = new SVGTransformImpl();
ret->ref();
double angle = parse.toDouble(); // TODO: probably needs it's own 'angle' parser
if(m_type == SVG_TRANSFORM_SKEWX)
ret->setSkewX(angle);
else
ret->setSkewY(angle);
break;
}
default:
break;
}
if(ret)
ret->ref();
return ret;
}
void SVGAnimateTransformElementImpl::calculateRotationFromMatrix(const QWMatrix &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 = SVGAngleImpl::todeg(asin(sina));
angle = SVGAngleImpl::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;
}
SVGMatrixImpl *SVGAnimateTransformElementImpl::initialMatrix() const
{
SVGTransformableImpl *transform = dynamic_cast<SVGTransformableImpl *>(targetElement());
SVGTransformListImpl *transformList = (transform ? transform->transform()->baseVal() : 0);
if(!transformList)
return 0;
SVGTransformImpl *result = transformList->concatenate();
if(!result)
return 0;
result->ref();
SVGMatrixImpl *ret = result->matrix();
ret->ref();
result->deref();
return ret;
}
SVGMatrixImpl *SVGAnimateTransformElementImpl::transformMatrix() const
{
return m_transformMatrix;
}
// vim:ts=4:noet