Revision control
Copy as Markdown
// Copyright (C) 2025 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:critical reason:data-parser
#include "qsvgcssproperties_p.h"
#include <QtGui/private/qguisvg_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::Literals::StringLiterals;
using namespace QSvgCssValues;
namespace {
int parseCssClockValue(QStringView str, bool *ok)
{
int res = 0;
int ms = 1000;
str = str.trimmed();
if (str.endsWith("ms"_L1)) {
str.chop(2);
ms = 1;
} else if (str.endsWith("s"_L1)) {
str.chop(1);
} else {
if (ok)
*ok = false;
return res;
}
double val = ms * str.toDouble(ok);
if (ok) {
if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
res = static_cast<int>(val);
else
*ok = false;
}
return res;
}
bool isTimeValue(QStringView str) {
if (str.endsWith("ms"_L1))
str.chop(2);
else if (str.endsWith("s"_L1))
str.chop(1);
else
return false;
bool ok;
Q_UNUSED(str.toDouble(&ok))
return ok;
}
bool isIteration(QStringView str) {
if (str == "infinite"_L1)
return true;
bool ok;
Q_UNUSED(str.toDouble(&ok))
return ok;
}
bool isDirection(QStringView str) {
return str == "normal"_L1 || str == "reverse"_L1 || str.startsWith("alternate"_L1);
}
bool isTimingFunction(QStringView str) {
return str.startsWith("linear"_L1) || str.startsWith("ease"_L1)
|| str.startsWith("step"_L1) || str.startsWith("cubic-bezier"_L1);
}
bool isFillMode(QStringView str) {
return str == "none"_L1 || str == "forwards"_L1 || str == "backwards"_L1 || str == "both"_L1;
}
bool isPlayState(QStringView str) {
return str == "paused"_L1 || str =="running"_L1;
}
}
QSvgCssProperties::QSvgCssProperties(const QXmlStreamAttributes &attributes)
{
QRegularExpression re(";| "_L1);
for (int i = 0; i < attributes.size(); ++i) {
const QXmlStreamAttribute &attribute = attributes.at(i);
QStringView name = attribute.qualifiedName();
if (name.isEmpty())
continue;
QStringView value = attribute.value();
switch (name.at(0).unicode()) {
case 'a':
if (name == "animation"_L1)
shortHandtoLonghandForm(value);
if (name == "animation-name"_L1)
m_names = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-duration"_L1)
m_durations = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-delay"_L1)
m_delays = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-iteration-count"_L1)
m_iterationCounts = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-direction"_L1)
m_directions = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-timing-function"_L1)
m_timingFunctions = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-fill-mode"_L1)
m_fillModes = value.split(re, Qt::SkipEmptyParts);
if (name == "animation-play-state"_L1)
m_playStates = value.split(re, Qt::SkipEmptyParts);
break;
case 'o':
if (name == "offset-path"_L1)
m_offsetPath = value;
if (name == "offset-distance"_L1)
m_offsetDistance = value;
if (name == "offset-rotate"_L1)
m_offsetRotate = value;
break;
default:
break;
}
}
}
QList<QSvgAnimationProperty> QSvgCssProperties::animations() const
{
bool ok;
QList<QSvgAnimationProperty> parsedProperties;
for (int i = 0; i < m_names.size(); i++) {
QSvgAnimationProperty property;
property.name = m_names.at(i);
if (!m_durations.isEmpty()) {
QStringView durationStr = m_durations.at(i % m_durations.size());
int duration = parseCssClockValue(durationStr, &ok);
property.duration = ok ? duration : 0;
}
if (!m_delays.isEmpty()) {
QStringView delayStr = m_delays.at(i % m_delays.size());
int delay = parseCssClockValue(delayStr, &ok);
property.delay = ok ? delay : 0;
}
if (!m_iterationCounts.isEmpty()) {
QStringView iterationStr = m_iterationCounts.at(i % m_iterationCounts.size());
int iteration;
if (iterationStr == "infinite"_L1) {
iteration = -1;
} else {
qreal count = iterationStr.toDouble(&ok);
iteration = ok ? qMax(1.0, count) : 0;
}
property.iteration = iteration;
}
if (!m_timingFunctions.isEmpty()) {
QStringView timingFunctionStr = m_timingFunctions.at(i % m_timingFunctions.size());
if (timingFunctionStr == "linear"_L1) {
property.easingFunction = EasingFunction::Linear;
} else if (timingFunctionStr == "ease-in"_L1) {
property.easingFunction = EasingFunction::EaseIn;
} else if (timingFunctionStr == "ease-out"_L1) {
property.easingFunction = EasingFunction::EaseOut;
} else if (timingFunctionStr == "ease-in-out"_L1) {
property.easingFunction = EasingFunction::EaseInOut;
} else if (timingFunctionStr == "step-end"_L1) {
property.easingFunction = EasingFunction::Steps;
property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::End};
} else if (timingFunctionStr == "step-start"_L1) {
property.easingFunction = EasingFunction::Steps;
property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::Start};
}
}
parsedProperties.append(property);
}
return parsedProperties;
}
QSvgOffsetProperty QSvgCssProperties::offset() const
{
QSvgOffsetProperty offset;
static const QRegularExpression rx(uR"-(^\s*path\((['"]{1})([\s\+\-,.\w]*)\1{1}\))-"_s);
QRegularExpressionMatch m = rx.matchView(m_offsetPath);
if (m.hasMatch()) {
QStringView offsetPath = m.capturedView(2);
offset.path = QGuiSvg::parsePath(offsetPath);
}
qsizetype index;
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
if ((index = m_offsetRotate.indexOf("auto"_L1, 0, cs)) >= 0) {
std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 4));
offset.rotateType = angle ? QtSvg::OffsetRotateType::AutoAngle :
QtSvg::OffsetRotateType::Auto;
offset.angle = angle.value_or(0);
} else if ((index = m_offsetRotate.indexOf("reverse"_L1, 0, cs)) >= 0) {
std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 7));
offset.rotateType = angle ? QtSvg::OffsetRotateType::ReverseAngle :
QtSvg::OffsetRotateType::Reverse;
offset.angle = angle.value_or(0);
} else {
std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate);
offset.rotateType = angle ? QtSvg::OffsetRotateType::Angle :
QtSvg::OffsetRotateType::Auto;
offset.angle = angle.value_or(0);
}
QGuiSvg::LengthType type;
bool *ok = nullptr;
offset.distance= QGuiSvg::parseLength(m_offsetDistance, &type, ok);
if (type == QGuiSvg::LengthType::LT_PERCENT) {
offset.distance = offset.distance / 100.0;
}
return offset;
}
void QSvgCssProperties::shortHandtoLonghandForm(QStringView value)
{
m_names.clear();
m_durations.clear();
m_delays.clear();
m_iterationCounts.clear();
m_directions.clear();
m_timingFunctions.clear();
m_fillModes.clear();
m_playStates.clear();
enum Property : uchar {
Duration = 1,
Delay = 1 << 1,
IterationCount = 1 << 2,
Direction = 1 << 3,
TimingFunction = 1 << 4,
FillMode = 1 << 5,
PlayState = 1 << 6,
Name = 1 << 7
};
const QList<QStringView> animations = value.split(QLatin1Char(';'), Qt::SkipEmptyParts);
for (const QStringView &animation : animations) {
uchar propertyFlag = 0;
const QList<QStringView> animationProperties = animation.split(QLatin1Char(' '), Qt::SkipEmptyParts);
for (const QStringView &property : animationProperties) {
if (!(propertyFlag & Property::Duration) && isTimeValue(property)) {
m_durations.append(property);
propertyFlag |= Property::Duration;
} else if (!(propertyFlag & Property::Delay) && isTimeValue(property)) {
m_delays.append(property);
propertyFlag |= Property::Delay;
} else if (!(propertyFlag & Property::IterationCount) && isIteration(property)) {
m_iterationCounts.append(property);
propertyFlag |= Property::IterationCount;
} else if (!(propertyFlag & Property::Direction) && isDirection(property)) {
m_directions.append(property);
propertyFlag |= Property::Direction;
} else if (!(propertyFlag & Property::TimingFunction) && isTimingFunction(property)) {
m_timingFunctions.append(property);
propertyFlag |= Property::TimingFunction;
} else if (!(propertyFlag & Property::FillMode) && isFillMode(property)) {
m_fillModes.append(property);
propertyFlag |= Property::FillMode;
} else if (!(propertyFlag & Property::PlayState)&& isPlayState(property)) {
m_playStates.append(property);
propertyFlag |= Property::PlayState;
} else {
m_names.append(property);
propertyFlag |= Property::Name;
}
}
}
}
QT_END_NAMESPACE