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