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