Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvgcssproperties.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6#include <QtGui/private/qguisvg_p.h>
7
9
10using namespace Qt::Literals::StringLiterals;
11using namespace QSvgCssValues;
12
13namespace {
14
15int parseCssClockValue(QStringView str, bool *ok)
16{
17 int res = 0;
18 int ms = 1000;
19 str = str.trimmed();
20 if (str.endsWith("ms"_L1)) {
21 str.chop(2);
22 ms = 1;
23 } else if (str.endsWith("s"_L1)) {
24 str.chop(1);
25 } else {
26 if (ok)
27 *ok = false;
28 return res;
29 }
30 double val = ms * str.toDouble(ok);
31 if (ok) {
32 if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
33 res = static_cast<int>(val);
34 else
35 *ok = false;
36 }
37 return res;
38}
39
40bool isTimeValue(QStringView str) {
41 if (str.endsWith("ms"_L1))
42 str.chop(2);
43 else if (str.endsWith("s"_L1))
44 str.chop(1);
45 else
46 return false;
47
48 bool ok;
49 Q_UNUSED(str.toDouble(&ok))
50 return ok;
51}
52
53bool isIteration(QStringView str) {
54 if (str == "infinite"_L1)
55 return true;
56 bool ok;
57 Q_UNUSED(str.toDouble(&ok))
58 return ok;
59}
60
61bool isDirection(QStringView str) {
62 return str == "normal"_L1 || str == "reverse"_L1 || str.startsWith("alternate"_L1);
63}
64
65bool isTimingFunction(QStringView str) {
66 return str.startsWith("linear"_L1) || str.startsWith("ease"_L1)
67 || str.startsWith("step"_L1) || str.startsWith("cubic-bezier"_L1);
68}
69
70bool isFillMode(QStringView str) {
71 return str == "none"_L1 || str == "forwards"_L1 || str == "backwards"_L1 || str == "both"_L1;
72}
73
74bool isPlayState(QStringView str) {
75 return str == "paused"_L1 || str =="running"_L1;
76}
77
78}
79
80QSvgCssProperties::QSvgCssProperties(const QXmlStreamAttributes &attributes)
81{
82 QRegularExpression re(";| "_L1);
83 for (int i = 0; i < attributes.size(); ++i) {
84 const QXmlStreamAttribute &attribute = attributes.at(i);
85 QStringView name = attribute.qualifiedName();
86 if (name.isEmpty())
87 continue;
88 QStringView value = attribute.value();
89 switch (name.at(0).unicode()) {
90
91 case 'a':
92 if (name == "animation"_L1)
93 shortHandtoLonghandForm(value);
94 if (name == "animation-name"_L1)
95 m_names = value.split(re, Qt::SkipEmptyParts);
96 if (name == "animation-duration"_L1)
97 m_durations = value.split(re, Qt::SkipEmptyParts);
98 if (name == "animation-delay"_L1)
99 m_delays = value.split(re, Qt::SkipEmptyParts);
100 if (name == "animation-iteration-count"_L1)
101 m_iterationCounts = value.split(re, Qt::SkipEmptyParts);
102 if (name == "animation-direction"_L1)
103 m_directions = value.split(re, Qt::SkipEmptyParts);
104 if (name == "animation-timing-function"_L1)
105 m_timingFunctions = value.split(re, Qt::SkipEmptyParts);
106 if (name == "animation-fill-mode"_L1)
107 m_fillModes = value.split(re, Qt::SkipEmptyParts);
108 if (name == "animation-play-state"_L1)
109 m_playStates = value.split(re, Qt::SkipEmptyParts);
110 break;
111
112 case 'o':
113 if (name == "offset-path"_L1)
114 m_offsetPath = value;
115 if (name == "offset-distance"_L1)
116 m_offsetDistance = value;
117 if (name == "offset-rotate"_L1)
118 m_offsetRotate = value;
119 break;
120
121 default:
122 break;
123 }
124 }
125}
126
128{
129 bool ok;
130 QList<QSvgAnimationProperty> parsedProperties;
131 for (int i = 0; i < m_names.size(); i++) {
132 QSvgAnimationProperty property;
133 property.name = m_names.at(i);
134
135 if (!m_durations.isEmpty()) {
136 QStringView durationStr = m_durations.at(i % m_durations.size());
137 int duration = parseCssClockValue(durationStr, &ok);
138 property.duration = ok ? duration : 0;
139 }
140
141 if (!m_delays.isEmpty()) {
142 QStringView delayStr = m_delays.at(i % m_delays.size());
143 int delay = parseCssClockValue(delayStr, &ok);
144 property.delay = ok ? delay : 0;
145 }
146
147 if (!m_iterationCounts.isEmpty()) {
148 QStringView iterationStr = m_iterationCounts.at(i % m_iterationCounts.size());
149 int iteration;
150 if (iterationStr == "infinite"_L1) {
151 iteration = -1;
152 } else {
153 qreal count = iterationStr.toDouble(&ok);
154 iteration = ok ? qMax(1.0, count) : 0;
155 }
156 property.iteration = iteration;
157 }
158
159 if (!m_timingFunctions.isEmpty()) {
160 QStringView timingFunctionStr = m_timingFunctions.at(i % m_timingFunctions.size());
161 if (timingFunctionStr == "linear"_L1) {
162 property.easingFunction = EasingFunction::Linear;
163 } else if (timingFunctionStr == "ease-in"_L1) {
164 property.easingFunction = EasingFunction::EaseIn;
165 } else if (timingFunctionStr == "ease-out"_L1) {
166 property.easingFunction = EasingFunction::EaseOut;
167 } else if (timingFunctionStr == "ease-in-out"_L1) {
168 property.easingFunction = EasingFunction::EaseInOut;
169 } else if (timingFunctionStr == "step-end"_L1) {
170 property.easingFunction = EasingFunction::Steps;
171 property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::End};
172 } else if (timingFunctionStr == "step-start"_L1) {
173 property.easingFunction = EasingFunction::Steps;
174 property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::Start};
175 }
176 }
177 parsedProperties.append(property);
178 }
179
180 return parsedProperties;
181}
182
184{
185 QSvgOffsetProperty offset;
186
187 static const QRegularExpression rx(uR"-(^\s*path\‍((['"]{1})([\s\+\-,.\w]*)\1{1}\‍))-"_s);
188 QRegularExpressionMatch m = rx.matchView(m_offsetPath);
189 if (m.hasMatch()) {
190 QStringView offsetPath = m.capturedView(2);
191 offset.path = QGuiSvg::parsePath(offsetPath);
192 }
193
194 qsizetype index;
195 Qt::CaseSensitivity cs = Qt::CaseInsensitive;
196 if ((index = m_offsetRotate.indexOf("auto"_L1, 0, cs)) >= 0) {
197 std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 4));
198 offset.rotateType = angle ? QtSvg::OffsetRotateType::AutoAngle :
199 QtSvg::OffsetRotateType::Auto;
200 offset.angle = angle.value_or(0);
201 } else if ((index = m_offsetRotate.indexOf("reverse"_L1, 0, cs)) >= 0) {
202 std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 7));
203 offset.rotateType = angle ? QtSvg::OffsetRotateType::ReverseAngle :
204 QtSvg::OffsetRotateType::Reverse;
205 offset.angle = angle.value_or(0);
206 } else {
207 std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate);
208 offset.rotateType = angle ? QtSvg::OffsetRotateType::Angle :
209 QtSvg::OffsetRotateType::Auto;
210 offset.angle = angle.value_or(0);
211 }
212
213 QGuiSvg::LengthType type;
214 bool *ok = nullptr;
215 offset.distance= QGuiSvg::parseLength(m_offsetDistance, &type, ok);
216 if (type == QGuiSvg::LengthType::LT_PERCENT) {
217 offset.distance = offset.distance / 100.0;
218 }
219
220 return offset;
221}
222
223void QSvgCssProperties::shortHandtoLonghandForm(QStringView value)
224{
225 m_names.clear();
226 m_durations.clear();
227 m_delays.clear();
228 m_iterationCounts.clear();
229 m_directions.clear();
230 m_timingFunctions.clear();
231 m_fillModes.clear();
232 m_playStates.clear();
233
234 enum Property : uchar {
235 Duration = 1,
236 Delay = 1 << 1,
237 IterationCount = 1 << 2,
238 Direction = 1 << 3,
239 TimingFunction = 1 << 4,
240 FillMode = 1 << 5,
241 PlayState = 1 << 6,
242 Name = 1 << 7
243 };
244
245 const QList<QStringView> animations = value.split(QLatin1Char(';'), Qt::SkipEmptyParts);
246 for (const QStringView &animation : animations) {
247 uchar propertyFlag = 0;
248 const QList<QStringView> animationProperties = animation.split(QLatin1Char(' '), Qt::SkipEmptyParts);
249 for (const QStringView &property : animationProperties) {
250 if (!(propertyFlag & Property::Duration) && isTimeValue(property)) {
251 m_durations.append(property);
252 propertyFlag |= Property::Duration;
253 } else if (!(propertyFlag & Property::Delay) && isTimeValue(property)) {
254 m_delays.append(property);
255 propertyFlag |= Property::Delay;
256 } else if (!(propertyFlag & Property::IterationCount) && isIteration(property)) {
257 m_iterationCounts.append(property);
258 propertyFlag |= Property::IterationCount;
259 } else if (!(propertyFlag & Property::Direction) && isDirection(property)) {
260 m_directions.append(property);
261 propertyFlag |= Property::Direction;
262 } else if (!(propertyFlag & Property::TimingFunction) && isTimingFunction(property)) {
263 m_timingFunctions.append(property);
264 propertyFlag |= Property::TimingFunction;
265 } else if (!(propertyFlag & Property::FillMode) && isFillMode(property)) {
266 m_fillModes.append(property);
267 propertyFlag |= Property::FillMode;
268 } else if (!(propertyFlag & Property::PlayState)&& isPlayState(property)) {
269 m_playStates.append(property);
270 propertyFlag |= Property::PlayState;
271 } else {
272 m_names.append(property);
273 propertyFlag |= Property::Name;
274 }
275 }
276 }
277}
278
279QT_END_NAMESPACE
QSvgOffsetProperty offset() const
QSvgCssProperties(const QXmlStreamAttributes &attributes)
QList< QSvgAnimationProperty > animations() const
Combined button and popup list for selecting options.