Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qsvganimatedproperty_p.h"
++ #include <QtCore/qpoint.h>
++ #include <QtGui/qcolor.h>
++ #include <QtGui/qtransform.h>
++
++ #include <QtCore/qlatin1stringview.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qstring.h>
++
++ #include <optional>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ Q_STATIC_LOGGING_CATEGORY(lcSvgAnimatedProperty, "qt.svg.animation.properties")
++
++ static std::optional<QSvgAbstractAnimatedProperty::Type> name2type(const QString &name)
++ {
++ // Perfect hashing:
++ //
++ // the length of the string uniquely identifies the property name
++ // (compiler guarantees, otherwise it would complain about duplicate case
++ // labels):
++ constexpr auto hash = [](QStringView s) {
++ return s.size();
++ };
++
++ switch (hash(name)) {
++ #define CASE(str, type) \
++ case hash(u"" #str): \
++ if (name == #str ## _L1) \
++ return QSvgAbstractAnimatedProperty:: type ; \
++ break; \
++ /* end */
++
++ CASE(fill, Color)
++ CASE(fill-opacity, Float)
++ CASE(stroke-opacity, Float)
++ CASE(stroke, Color)
++ CASE(opacity, Float)
++ CASE(transform, Transform)
++ CASE(offset-distance, Float)
++ #undef CASE
++ };
++ return std::nullopt;
++ }
++
++ static qreal q_lerp(qreal a, qreal b, qreal t)
++ {
++ return a + (b - a) * t;
++ }
++
++ static QPointF pointInterpolator(QPointF v1, QPointF v2, qreal t)
++ {
++ qreal x = q_lerp(v1.x(), v2.x(), t);
++ qreal y = q_lerp(v1.y(), v2.y(), t);
++
++ return QPointF(x, y);
++ }
++
++
++ QSvgAbstractAnimatedProperty::QSvgAbstractAnimatedProperty(const QString &name, Type type)
++ : m_propertyName(name)
++ , m_type(type)
++ {
++ }
++
++ QSvgAbstractAnimatedProperty::~QSvgAbstractAnimatedProperty()
++ {
++ }
++
++ void QSvgAbstractAnimatedProperty::setKeyFrames(const QList<qreal> &keyFrames)
++ {
++ m_keyFrames = keyFrames;
++ }
++
++ void QSvgAbstractAnimatedProperty::appendKeyFrame(qreal keyFrame)
++ {
++ m_keyFrames.append(keyFrame);
++ }
++
++ QList<qreal> QSvgAbstractAnimatedProperty::keyFrames() const
++ {
++ return m_keyFrames;
++ }
++
++ void QSvgAbstractAnimatedProperty::appendEasing(QSvgEasingInterfacePtr easing)
++ {
++ m_easings.push_back(std::move(easing));
++ }
++
++ const QSvgEasingInterface *QSvgAbstractAnimatedProperty::easingAt(unsigned int i) const
++ {
++ return i < m_easings.size() ? m_easings[i].get() : nullptr;
++ }
++
++ void QSvgAbstractAnimatedProperty::setPropertyName(const QString &name)
++ {
++ m_propertyName = name;
++ }
++
++ QStringView QSvgAbstractAnimatedProperty::propertyName() const
++ {
++ return m_propertyName;
++ }
++
++ QSvgAbstractAnimatedProperty::Type QSvgAbstractAnimatedProperty::type() const
++ {
++ return m_type;
++ }
++
++ QVariant QSvgAbstractAnimatedProperty::interpolatedValue() const
++ {
++ return m_interpolatedValue;
++ }
++
++ QSvgAbstractAnimatedProperty *QSvgAbstractAnimatedProperty::createAnimatedProperty(const QString &name)
++ {
++ const std::optional<Type> type = name2type(name);
++
++ if (!type) {
++ qCDebug(lcSvgAnimatedProperty) << "Property : " << name << " is not animatable";
++ return nullptr;
++ }
++
++ QSvgAbstractAnimatedProperty *prop = nullptr;
++
++ switch (*type) {
++ case QSvgAbstractAnimatedProperty::Color:
++ prop = new QSvgAnimatedPropertyColor(name);
++ break;
++ case QSvgAbstractAnimatedProperty::Transform:
++ prop = new QSvgAnimatedPropertyTransform(name);
++ break;
++ case QSvgAbstractAnimatedProperty::Float:
++ prop = new QSvgAnimatedPropertyFloat(name);
++ default:
++ break;
++ }
++
++ return prop;
++ }
++
++ QSvgAnimatedPropertyColor::QSvgAnimatedPropertyColor(const QString &name)
++ : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Color)
++ {
++ }
++
++ QSvgAnimatedPropertyColor::~QSvgAnimatedPropertyColor()
++ = default;
++
++ void QSvgAnimatedPropertyColor::setColors(const QList<QColor> &colors)
++ {
++ m_colors = colors;
++ }
++
++ void QSvgAnimatedPropertyColor::appendColor(const QColor &color)
++ {
++ m_colors.append(color);
++ }
++
++ QList<QColor> QSvgAnimatedPropertyColor::colors() const
++ {
++ return m_colors;
++ }
++
++ void QSvgAnimatedPropertyColor::interpolate(uint index, qreal t) const
++ {
++ QColor c1 = m_colors.at(index - 1);
++ QColor c2 = m_colors.at(index);
++
++ int alpha = q_lerp(c1.alpha(), c2.alpha(), t);
++ int red = q_lerp(c1.red(), c2.red(), t);
++ int green = q_lerp(c1.green(), c2.green(), t);
++ int blue = q_lerp(c1.blue(), c2.blue(), t);
++
++ m_interpolatedValue = QColor(red, green, blue, alpha);
++ }
++
++ QSvgAnimatedPropertyFloat::QSvgAnimatedPropertyFloat(const QString &name)
++ : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Float)
++ {
++ }
++
++ QSvgAnimatedPropertyFloat::~QSvgAnimatedPropertyFloat()
++ = default;
++
++ void QSvgAnimatedPropertyFloat::setValues(const QList<qreal> &values)
++ {
++ m_values = values;
++ }
++
++ void QSvgAnimatedPropertyFloat::appendValue(const qreal value)
++ {
++ m_values.append(value);
++ }
++
++ QList<qreal> QSvgAnimatedPropertyFloat::values() const
++ {
++ return m_values;
++ }
++
++ void QSvgAnimatedPropertyFloat::interpolate(uint index, qreal t) const
++ {
++ if (index >= (uint)m_keyFrames.size()) {
++ qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
++ return;
++ }
++
++ qreal float1 = m_values.at(index - 1);
++ qreal float2 = m_values.at(index);
++
++ m_interpolatedValue = q_lerp(float1, float2, t);
++ }
++
++ QSvgAnimatedPropertyTransform::QSvgAnimatedPropertyTransform(const QString &name)
++ : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Transform)
++ {
++
++ }
++
++ QSvgAnimatedPropertyTransform::~QSvgAnimatedPropertyTransform()
++ = default;
++
++ void QSvgAnimatedPropertyTransform::setTransformCount(quint32 count)
++ {
++ m_transformCount = count;
++ }
++
++ quint32 QSvgAnimatedPropertyTransform::transformCount() const
++ {
++ return m_transformCount;
++ }
++
++ void QSvgAnimatedPropertyTransform::appendComponents(const QList<TransformComponent> &components)
++ {
++ m_components.append(components);
++ }
++
++ QList<QSvgAnimatedPropertyTransform::TransformComponent> QSvgAnimatedPropertyTransform::components() const
++ {
++ return m_components;
++ }
++
++ // this function iterates over all TransformComponents in two consecutive
++ // key frames and interpolate between all TransformComponents. Moreover,
++ // it requires all key frames to have the same number of TransformComponents.
++ // This must be ensured by the parser itself, and it is handled in validateTransform
++ // function in qsvgcsshandler.cpp and in createAnimateTransformNode function
++ // in qsvghandler.cpp.
++ void QSvgAnimatedPropertyTransform::interpolate(uint index, qreal t) const
++ {
++ if (index >= (uint)m_keyFrames.size()) {
++ qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
++ return;
++ }
++
++ if (!m_transformCount ||
++ ((m_components.size() / qsizetype(m_transformCount)) != m_keyFrames.size())) {
++ return;
++ }
++
++ QTransform transform = QTransform();
++
++ qsizetype startIndex = (index - 1) * qsizetype(m_transformCount);
++ qsizetype endIndex = index * qsizetype(m_transformCount);
++
++ for (quint32 i = 0; i < m_transformCount; i++) {
++ TransformComponent tc1 = m_components.at(startIndex + i);
++ TransformComponent tc2 = m_components.at(endIndex + i);
++ if (tc1.type == tc2.type) {
++ if (tc1.type == TransformComponent::Translate) {
++ QPointF t1 = QPointF(tc1.values.at(0), tc1.values.at(1));
++ QPointF t2 = QPointF(tc2.values.at(0), tc2.values.at(1));
++ QPointF tr = pointInterpolator(t1, t2, t);
++ transform.translate(tr.x(), tr.y());
++ } else if (tc1.type == TransformComponent::Scale) {
++ QPointF s1 = QPointF(tc1.values.at(0), tc1.values.at(1));
++ QPointF s2 = QPointF(tc2.values.at(0), tc2.values.at(1));
++ QPointF sr = pointInterpolator(s1, s2, t);
++ transform.scale(sr.x(), sr.y());
++ } else if (tc1.type == TransformComponent::Rotate) {
++ QPointF cor1 = QPointF(tc1.values.at(1), tc1.values.at(2));
++ QPointF cor2 = QPointF(tc2.values.at(1), tc2.values.at(2));
++ QPointF corResult = pointInterpolator(cor1, cor2, t);
++ qreal angle1 = tc1.values.at(0);
++ qreal angle2 = tc2.values.at(0);
++ qreal angleResult = q_lerp(angle1, angle2, t);
++ transform.translate(corResult.x(), corResult.y());
++ transform.rotate(angleResult);
++ transform.translate(-corResult.x(), -corResult.y());
++ } else if (tc1.type == TransformComponent::Skew) {
++ QPointF skew1 = QPointF(tc1.values.at(0), tc1.values.at(1));
++ QPointF skew2 = QPointF(tc2.values.at(0), tc2.values.at(1));
++ QPointF skewResult = pointInterpolator(skew1, skew2, t);
++ transform.shear(qTan(qDegreesToRadians(skewResult.x())),
++ qTan(qDegreesToRadians(skewResult.y())));
++ }
++ }
++ }
++
++ m_interpolatedValue = transform;
++ }
++
++ QT_END_NAMESPACE