/src/qtsvg/src/svg/css/qsvgcsshandler.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 "qsvgcsshandler_p.h" |
6 | | #include <QtSvg/private/qsvgstyleselector_p.h> |
7 | | #include <QtSvg/private/qsvganimatedproperty_p.h> |
8 | | #include <QtSvg/private/qsvgutils_p.h> |
9 | | #include <QtGui/private/qmath_p.h> |
10 | | #include <QtCore/qlist.h> |
11 | | |
12 | | QT_BEGIN_NAMESPACE |
13 | | |
14 | | namespace { |
15 | | |
16 | | struct CssKeyFrameValue{ |
17 | | qreal keyFrame; |
18 | | QList<QCss::Value> values; |
19 | | QCss::Declaration timingFunction; |
20 | | }; |
21 | | |
22 | | bool fillPropertyEasing(const QList<CssKeyFrameValue> &keyFrames, QSvgAbstractAnimatedProperty *prop) |
23 | 0 | { |
24 | 0 | for (const CssKeyFrameValue &keyFrameValue : keyFrames) { |
25 | 0 | QStringView timingFunctionStr = QSvgCssHandler::parseDecltoString(keyFrameValue.timingFunction); |
26 | 0 | QSvgEasingInterfacePtr easing; |
27 | 0 | if (timingFunctionStr == QStringLiteral("linear")) { |
28 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::Linear); |
29 | 0 | } else if (timingFunctionStr == QStringLiteral("ease-in")) { |
30 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::EaseIn); |
31 | 0 | } else if (timingFunctionStr == QStringLiteral("ease-out")) { |
32 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::EaseOut); |
33 | 0 | } else if (timingFunctionStr == QStringLiteral("ease-in-out")) { |
34 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::EaseInOut); |
35 | 0 | } else if (timingFunctionStr == QStringLiteral("step-end")) { |
36 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::Steps, |
37 | 0 | QSvgCssValues::StepValues{quint32(1), |
38 | 0 | QSvgCssValues::StepPosition::End}); |
39 | 0 | } else if (timingFunctionStr == QStringLiteral("step-start")) { |
40 | 0 | easing = QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction::Steps, |
41 | 0 | QSvgCssValues::StepValues{quint32(1), |
42 | 0 | QSvgCssValues::StepPosition::Start}); |
43 | 0 | } |
44 | |
|
45 | 0 | prop->appendEasing(std::move(easing)); |
46 | 0 | } |
47 | | |
48 | | |
49 | |
|
50 | 0 | return true; |
51 | 0 | } |
52 | | |
53 | | bool fillColorProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyColor *prop) |
54 | 0 | { |
55 | 0 | for (const CssKeyFrameValue &keyFrame : keyFrames) { |
56 | 0 | if (keyFrame.values.size() != 1) |
57 | 0 | return false; |
58 | | |
59 | 0 | QString colorStr = keyFrame.values.first().toString(); |
60 | 0 | QColor color = QColor::fromString(colorStr); |
61 | 0 | prop->appendColor(color); |
62 | 0 | prop->appendKeyFrame(keyFrame.keyFrame); |
63 | 0 | } |
64 | | |
65 | 0 | return true; |
66 | 0 | } |
67 | | |
68 | | bool fillOpacityProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyFloat *prop) |
69 | 0 | { |
70 | 0 | for (const CssKeyFrameValue &keyFrame : keyFrames) { |
71 | 0 | if (keyFrame.values.size() != 1) |
72 | 0 | return false; |
73 | | |
74 | 0 | QString opacity = keyFrame.values.first().toString(); |
75 | 0 | prop->appendValue(opacity.toDouble()); |
76 | 0 | prop->appendKeyFrame(keyFrame.keyFrame); |
77 | 0 | } |
78 | | |
79 | 0 | return true; |
80 | 0 | } |
81 | | |
82 | 0 | bool validateTransform(QList<QList<QSvgAnimatedPropertyTransform::TransformComponent>> &keyFrameComponents) { |
83 | |
|
84 | 0 | if (keyFrameComponents.size() < 2) |
85 | 0 | return false; |
86 | | |
87 | 0 | qsizetype maxIndex = 0; |
88 | 0 | qsizetype maxSize = 0; |
89 | 0 | for (int i = 1; i < keyFrameComponents.size(); i++) { |
90 | 0 | auto &listA = keyFrameComponents[i - 1]; |
91 | 0 | auto &listB = keyFrameComponents[i]; |
92 | 0 | for (int j = 0; j < qMin(listA.size(), listB.size()); j++) { |
93 | 0 | auto typeA = listA.at(j).type; |
94 | 0 | auto typeB = listB.at(j).type; |
95 | | // TODO: Handle type mismatch as mentioned in CSS Transform Module specs. |
96 | 0 | if (typeA != typeB) |
97 | 0 | return false; |
98 | 0 | } |
99 | | |
100 | 0 | if (listA.size() > maxSize) { |
101 | 0 | maxIndex = i - 1; |
102 | 0 | maxSize = listA.size(); |
103 | 0 | } |
104 | |
|
105 | 0 | if (listB.size() > maxSize) { |
106 | 0 | maxIndex = i; |
107 | 0 | maxSize = listB.size(); |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | 0 | const auto &longList = keyFrameComponents.at(maxIndex); |
112 | | // pad the shorter list with identical transforms set to default values. |
113 | 0 | for (auto &list : keyFrameComponents) { |
114 | 0 | qsizetype size = list.size(); |
115 | |
|
116 | 0 | for (int j = size; j < maxSize; j++) { |
117 | 0 | QSvgAnimatedPropertyTransform::TransformComponent comp = longList.value(j); |
118 | 0 | switch (comp.type) { |
119 | 0 | case QSvgAnimatedPropertyTransform::TransformComponent::Translate: |
120 | 0 | case QSvgAnimatedPropertyTransform::TransformComponent::Skew: |
121 | 0 | comp.values[0] = 0; |
122 | 0 | comp.values[1] = 0; |
123 | 0 | break; |
124 | 0 | case QSvgAnimatedPropertyTransform::TransformComponent::Rotate: |
125 | 0 | comp.values[0] = 0; |
126 | 0 | comp.values[1] = 0; |
127 | 0 | comp.values[2] = 0; |
128 | 0 | break; |
129 | 0 | case QSvgAnimatedPropertyTransform::TransformComponent::Scale: |
130 | 0 | comp.values[0] = 1; |
131 | 0 | comp.values[1] = 1; |
132 | 0 | break; |
133 | 0 | default: |
134 | 0 | break; |
135 | 0 | } |
136 | 0 | list.append(comp); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | 0 | return true; |
141 | 0 | } |
142 | | |
143 | | bool fillTransformProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyTransform *prop) |
144 | 0 | { |
145 | | // Stores each key frame's list of components |
146 | 0 | QList<QList<QSvgAnimatedPropertyTransform::TransformComponent>> keyFramesComponents; |
147 | |
|
148 | 0 | for (const CssKeyFrameValue &keyFrame : keyFrames) { |
149 | 0 | QList<QSvgAnimatedPropertyTransform::TransformComponent> components; |
150 | 0 | for (const QCss::Value &val : keyFrame.values) { |
151 | 0 | if (val.type == QCss::Value::Function) { |
152 | 0 | QStringList lst = val.variant.toStringList(); |
153 | 0 | QStringView transformType = lst.value(0); |
154 | 0 | QStringList args = lst.value(1).split(QStringLiteral(","), Qt::SkipEmptyParts); |
155 | 0 | if (transformType == QStringLiteral("scale")) { |
156 | 0 | QSvgAnimatedPropertyTransform::TransformComponent component; |
157 | 0 | qreal scale0 = QSvgUtils::toDouble(args.value(0).trimmed()); |
158 | 0 | qreal scale1 = QSvgUtils::toDouble(args.value(1).trimmed()); |
159 | 0 | component.type = QSvgAnimatedPropertyTransform::TransformComponent::Scale; |
160 | 0 | component.values.append(scale0); |
161 | 0 | component.values.append(scale1); |
162 | 0 | components.append(component); |
163 | 0 | } else if (transformType == QStringLiteral("translate")) { |
164 | 0 | QSvgAnimatedPropertyTransform::TransformComponent component; |
165 | 0 | QSvgUtils::LengthType type; |
166 | 0 | qreal translate0 = QSvgUtils::parseLength(args.value(0), &type); |
167 | 0 | translate0 = QSvgUtils::convertToPixels(translate0, false, type); |
168 | 0 | qreal translate1 = QSvgUtils::parseLength(args.value(1), &type); |
169 | 0 | translate1 = QSvgUtils::convertToPixels(translate1, false, type); |
170 | 0 | component.type = QSvgAnimatedPropertyTransform::TransformComponent::Translate; |
171 | 0 | component.values.append(translate0); |
172 | 0 | component.values.append(translate1); |
173 | 0 | components.append(component); |
174 | 0 | } else if (transformType == QStringLiteral("rotate")) { |
175 | 0 | QSvgAnimatedPropertyTransform::TransformComponent component; |
176 | 0 | qreal rotationAngle = QSvgUtils::parseAngle(args.value(0)).value_or(0); |
177 | 0 | component.type = QSvgAnimatedPropertyTransform::TransformComponent::Rotate; |
178 | 0 | component.values.append(rotationAngle); |
179 | 0 | component.values.append(0); |
180 | 0 | component.values.append(0); |
181 | 0 | components.append(component); |
182 | 0 | } else if (transformType == QStringLiteral("skew")) { |
183 | 0 | QSvgAnimatedPropertyTransform::TransformComponent component; |
184 | 0 | qreal skew0 = QSvgUtils::parseAngle(args.value(0)).value_or(0); |
185 | 0 | qreal skew1 = QSvgUtils::parseAngle(args.value(1)).value_or(0); |
186 | 0 | component.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew; |
187 | 0 | component.values.append(skew0); |
188 | 0 | component.values.append(skew1); |
189 | 0 | components.append(component); |
190 | 0 | } else if (transformType == QStringLiteral("matrix")) { |
191 | 0 | QSvgAnimatedPropertyTransform::TransformComponent component1, component2, component3; |
192 | 0 | QSvgUtils::LengthType type; |
193 | 0 | qreal translate0 = QSvgUtils::parseLength(args.value(4), &type); |
194 | 0 | translate0 = QSvgUtils::convertToPixels(translate0, false, type); |
195 | 0 | qreal translate1 = QSvgUtils::parseLength(args.value(5), &type); |
196 | 0 | translate1 = QSvgUtils::convertToPixels(translate1, false, type); |
197 | 0 | qreal scale0 = QSvgUtils::toDouble(args.value(0).trimmed()); |
198 | 0 | qreal scale1 = QSvgUtils::toDouble(args.value(3).trimmed()); |
199 | 0 | qreal skew0 = QSvgUtils::toDouble((args.value(1).trimmed())); |
200 | 0 | qreal skew1 = QSvgUtils::toDouble((args.value(2).trimmed())); |
201 | 0 | component1.type = QSvgAnimatedPropertyTransform::TransformComponent::Translate; |
202 | 0 | component1.values.append(translate0); |
203 | 0 | component1.values.append(translate1); |
204 | 0 | component2.type = QSvgAnimatedPropertyTransform::TransformComponent::Scale; |
205 | 0 | component2.values.append(scale0); |
206 | 0 | component2.values.append(scale1); |
207 | 0 | component3.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew; |
208 | 0 | component3.values.append(skew0); |
209 | 0 | component3.values.append(skew1); |
210 | 0 | components.append(component1); |
211 | 0 | components.append(component2); |
212 | 0 | components.append(component3); |
213 | 0 | } |
214 | 0 | } |
215 | 0 | } |
216 | 0 | keyFramesComponents.append(components); |
217 | 0 | prop->appendKeyFrame(keyFrame.keyFrame); |
218 | 0 | } |
219 | |
|
220 | 0 | if (!validateTransform(keyFramesComponents)) |
221 | 0 | return false; |
222 | | |
223 | 0 | for (const auto &comp : std::as_const(keyFramesComponents)) { |
224 | 0 | prop->appendComponents(comp); |
225 | 0 | } |
226 | 0 | prop->setTransformCount(keyFramesComponents.first().size()); |
227 | |
|
228 | 0 | return true; |
229 | 0 | } |
230 | | |
231 | | bool fillOffsetDistanceProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyFloat *prop) |
232 | 0 | { |
233 | 0 | for (const CssKeyFrameValue &keyFrame : keyFrames) { |
234 | 0 | if (keyFrame.values.size() != 1) |
235 | 0 | return false; |
236 | | |
237 | 0 | QString offsetDistance = keyFrame.values.first().toString(); |
238 | |
|
239 | 0 | bool ok = false; |
240 | 0 | qreal distance = offsetDistance.toDouble(&ok); |
241 | 0 | if (!ok) |
242 | 0 | return false; |
243 | | |
244 | 0 | QCss::Value::Type type = keyFrame.values.first().type; |
245 | 0 | if (type != QCss::Value::Percentage && !qFuzzyCompare(distance, 0.)) |
246 | 0 | return false; |
247 | 0 | distance /= 100; |
248 | 0 | prop->appendValue(distance); |
249 | 0 | prop->appendKeyFrame(keyFrame.keyFrame); |
250 | 0 | } |
251 | | |
252 | 0 | return true; |
253 | 0 | } |
254 | | |
255 | | } |
256 | | |
257 | | QSvgCssHandler::QSvgCssHandler() |
258 | 41.7k | : m_selector(new QSvgStyleSelector) |
259 | 41.7k | { |
260 | | |
261 | 41.7k | } |
262 | | |
263 | | QSvgCssHandler::~QSvgCssHandler() |
264 | 41.7k | { |
265 | 41.7k | delete m_selector; |
266 | 41.7k | m_selector = nullptr; |
267 | 41.7k | } |
268 | | |
269 | | QSvgCssAnimation *QSvgCssHandler::createAnimation(QStringView name) |
270 | 0 | { |
271 | 0 | if (!m_animations.contains(name)) |
272 | 0 | return nullptr; |
273 | | |
274 | 0 | QCss::AnimationRule animationRule = m_animations[name]; |
275 | 0 | QHash<QString, QSvgAbstractAnimatedProperty*> animatedProperies; |
276 | 0 | QSvgCssAnimation *animation = new QSvgCssAnimation; |
277 | | |
278 | | |
279 | | // Css Parser returns a list of all properties for each key frame. Here, |
280 | | // we store the key frames and values for each property for easier parsing. |
281 | 0 | QHash<QString, QList<CssKeyFrameValue>> keyFrameValues; |
282 | 0 | for (const auto &ruleSet : std::as_const(animationRule.ruleSets)) { |
283 | 0 | for (const QCss::Declaration &decl : ruleSet.declarations) { |
284 | 0 | QCss::Value timingFunction; |
285 | 0 | CssKeyFrameValue keyFrameValue = {ruleSet.keyFrame, decl.d->values, ruleSet.timingFunction}; |
286 | 0 | QList<CssKeyFrameValue> &value = keyFrameValues[decl.d->property]; |
287 | 0 | value.append(keyFrameValue); |
288 | 0 | } |
289 | 0 | } |
290 | |
|
291 | 0 | for (auto it = keyFrameValues.begin(); it != keyFrameValues.end(); it++) { |
292 | 0 | QStringView property = it.key(); |
293 | 0 | const QList<CssKeyFrameValue> &keyFrames = it.value(); |
294 | 0 | auto *prop = QSvgAbstractAnimatedProperty::createAnimatedProperty(property.toString()); |
295 | 0 | if (!prop) |
296 | 0 | continue; |
297 | | |
298 | 0 | bool result = false; |
299 | 0 | if (property == QLatin1StringView("fill") || property == QLatin1StringView("stroke")) |
300 | 0 | result = fillColorProperty(keyFrames, static_cast<QSvgAnimatedPropertyColor*>(prop)); |
301 | 0 | else if (property == QLatin1StringView("transform")) |
302 | 0 | result = fillTransformProperty(keyFrames, static_cast<QSvgAnimatedPropertyTransform*>(prop)); |
303 | 0 | else if (property == QLatin1StringView("fill-opacity") || property == QLatin1StringView("stroke-opacity") |
304 | 0 | || property == QLatin1StringView("opacity")) |
305 | 0 | result = fillOpacityProperty(keyFrames, static_cast<QSvgAnimatedPropertyFloat*>(prop)); |
306 | 0 | else if (property == QLatin1StringView("offset-distance")) |
307 | 0 | result = fillOffsetDistanceProperty(keyFrames, static_cast<QSvgAnimatedPropertyFloat*>(prop)); |
308 | |
|
309 | 0 | result &= fillPropertyEasing(keyFrames, prop); |
310 | 0 | if (!result) { |
311 | 0 | delete prop; |
312 | 0 | continue; |
313 | 0 | } |
314 | | |
315 | 0 | animatedProperies[property] = prop; |
316 | 0 | } |
317 | |
|
318 | 0 | for (auto it = animatedProperies.begin(); it != animatedProperies.end(); it++) |
319 | 0 | animation->appendProperty(it.value()); |
320 | |
|
321 | 0 | return animation; |
322 | 0 | } |
323 | | |
324 | | QSvgCssEasingPtr QSvgCssHandler::createEasing(QSvgCssValues::EasingFunction easingFunction, |
325 | | const QSvgCssValues::EasingValues &values) |
326 | 0 | { |
327 | 0 | QSvgCssEasingPtr easing; |
328 | |
|
329 | 0 | switch (easingFunction) { |
330 | 0 | case QSvgCssValues::EasingFunction::Ease: |
331 | 0 | case QSvgCssValues::EasingFunction::EaseIn: |
332 | 0 | case QSvgCssValues::EasingFunction::EaseOut: |
333 | 0 | case QSvgCssValues::EasingFunction::EaseInOut: |
334 | 0 | case QSvgCssValues::EasingFunction::Linear: |
335 | 0 | easing = createEasingFromKeyword(easingFunction); |
336 | 0 | break; |
337 | 0 | case QSvgCssValues::EasingFunction::Steps: |
338 | 0 | easing = createStepsEasing(std::get<QSvgCssValues::StepValues>(values)); |
339 | 0 | break; |
340 | 0 | default: |
341 | 0 | easing = createEasingFromKeyword(QSvgCssValues::EasingFunction::Ease); |
342 | 0 | break; |
343 | 0 | } |
344 | | |
345 | 0 | return easing; |
346 | 0 | } |
347 | | |
348 | | void QSvgCssHandler::collectAnimations(const QCss::StyleSheet &sheet) |
349 | 1.08M | { |
350 | 1.08M | auto sortFunction = [](QCss::AnimationRule::AnimationRuleSet r1, QCss::AnimationRule::AnimationRuleSet r2) { |
351 | 0 | return r1.keyFrame < r2.keyFrame; |
352 | 0 | }; |
353 | | |
354 | 1.08M | QList<QCss::AnimationRule> animationRules = sheet.animationRules; |
355 | 1.08M | for (QCss::AnimationRule &rule : animationRules) { |
356 | 0 | std::sort(rule.ruleSets.begin(), rule.ruleSets.end(), sortFunction); |
357 | 0 | m_animations[rule.animName] = rule; |
358 | 0 | } |
359 | 1.08M | } |
360 | | |
361 | | void QSvgCssHandler::parseStyleSheet(const QStringView str) |
362 | 1.08M | { |
363 | 1.08M | QString css = str.toString(); |
364 | 1.08M | QCss::StyleSheet sheet; |
365 | 1.08M | QCss::Parser(css).parse(&sheet); |
366 | 1.08M | m_selector->styleSheets.append(sheet); |
367 | | |
368 | 1.08M | collectAnimations(sheet); |
369 | 1.08M | } |
370 | | |
371 | | QString QSvgCssHandler::parseDecltoString(const QCss::Declaration &decl) |
372 | 3.81M | { |
373 | 3.81M | if (decl.d->property.isEmpty()) |
374 | 0 | return QString(); |
375 | | |
376 | 3.81M | QString valueStr; |
377 | 3.81M | const int valCount = decl.d->values.size(); |
378 | 9.90M | for (int i = 0; i < valCount; ++i) { |
379 | 6.09M | QCss::Value val = decl.d->values.at(i); |
380 | 6.09M | switch (val.type) { |
381 | 245k | case QCss::Value::TermOperatorComma: |
382 | 245k | valueStr += QLatin1Char(';'); |
383 | 245k | break; |
384 | 364 | case QCss::Value::Uri: |
385 | 364 | { |
386 | 364 | QString temp = val.toString(); |
387 | 364 | temp.prepend(QLatin1String("url(")); |
388 | 364 | temp.append(QLatin1Char(')')); |
389 | 364 | valueStr += temp; |
390 | 364 | break; |
391 | 0 | } |
392 | 285 | case QCss::Value::Function: |
393 | 285 | { |
394 | 285 | QStringList lst = val.variant.toStringList(); |
395 | 285 | valueStr.append(lst.at(0)); |
396 | 285 | valueStr.append(QLatin1Char('(')); |
397 | 570 | for (int i = 1; i < lst.size(); ++i) { |
398 | 285 | valueStr.append(lst.at(i)); |
399 | 285 | if ((i +1) < lst.size()) |
400 | 0 | valueStr.append(QLatin1Char(',')); |
401 | 285 | } |
402 | 285 | valueStr.append(QLatin1Char(')')); |
403 | 285 | break; |
404 | 0 | } |
405 | 9.74k | case QCss::Value::KnownIdentifier: |
406 | 9.74k | switch (val.variant.toInt()) { |
407 | 1.92k | case QCss::Value_None: |
408 | 1.92k | valueStr += QLatin1String("none"); |
409 | 1.92k | break; |
410 | 0 | case QCss::Value_Auto: |
411 | 0 | valueStr += QLatin1String("auto"); |
412 | 0 | break; |
413 | 7.82k | default: |
414 | 7.82k | valueStr += val.toString(); |
415 | 7.82k | break; |
416 | 9.74k | } |
417 | 9.74k | break; |
418 | 59.9k | case QCss::Value::Percentage: |
419 | 59.9k | valueStr += val.toString() + QLatin1Char('%'); |
420 | 59.9k | break; |
421 | 5.77M | default: |
422 | 5.77M | valueStr += val.toString(); |
423 | 5.77M | break; |
424 | 6.09M | } |
425 | | |
426 | 6.09M | if (i + 1 < valCount) |
427 | 2.28M | valueStr += QLatin1Char(' '); |
428 | 6.09M | } |
429 | | |
430 | 3.81M | return valueStr; |
431 | 3.81M | } |
432 | | |
433 | | void QSvgCssHandler::parseCSStoXMLAttrs(const QList<QCss::Declaration> &declarations, QXmlStreamAttributes &attributes) const |
434 | 804k | { |
435 | 4.61M | for (int i = 0; i < declarations.size(); ++i) { |
436 | 3.81M | const QCss::Declaration &decl = declarations.at(i); |
437 | 3.81M | QString valueStr = parseDecltoString(decl); |
438 | 3.81M | if (!valueStr.isEmpty()) |
439 | 3.81M | attributes.append(QString(), decl.d->property, valueStr); |
440 | 3.81M | } |
441 | 804k | } |
442 | | |
443 | | void QSvgCssHandler::parseCSStoXMLAttrs(const QString &css, QXmlStreamAttributes &attributes) const |
444 | 80.3k | { |
445 | | // preprocess (for unicode escapes), tokenize and remove comments |
446 | 80.3k | QCss::Parser parser(css); |
447 | 80.3k | QString key; |
448 | | |
449 | 384k | while (parser.hasNext()) { |
450 | 320k | parser.skipSpace(); |
451 | | |
452 | 320k | if (!parser.hasNext()) |
453 | 0 | break; |
454 | 320k | parser.next(); |
455 | | |
456 | 320k | QString name; |
457 | 320k | QString value; |
458 | | |
459 | 320k | if (parser.hasEscapeSequences) { |
460 | 2.48k | key = parser.lexem(); |
461 | 2.48k | name = key; |
462 | 317k | } else { |
463 | 317k | const QCss::Symbol &sym = parser.symbol(); |
464 | 317k | name = sym.text.mid(sym.start, sym.len); |
465 | 317k | } |
466 | | |
467 | 320k | parser.skipSpace(); |
468 | 320k | if (!parser.test(QCss::COLON)) |
469 | 15.9k | break; |
470 | | |
471 | 304k | parser.skipSpace(); |
472 | 304k | if (!parser.hasNext()) |
473 | 293 | break; |
474 | | |
475 | 303k | const int firstSymbol = parser.index; |
476 | 303k | int symbolCount = 0; |
477 | 6.31M | do { |
478 | 6.31M | parser.next(); |
479 | 6.31M | ++symbolCount; |
480 | 6.31M | } while (parser.hasNext() && !parser.test(QCss::SEMICOLON)); |
481 | | |
482 | 303k | bool canExtractValueByRef = !parser.hasEscapeSequences; |
483 | 303k | if (canExtractValueByRef) { |
484 | 301k | int len = parser.symbols.at(firstSymbol).len; |
485 | 4.36M | for (int i = firstSymbol + 1; i < firstSymbol + symbolCount; ++i) { |
486 | 4.06M | len += parser.symbols.at(i).len; |
487 | | |
488 | 4.06M | if (parser.symbols.at(i - 1).start + parser.symbols.at(i - 1).len |
489 | 4.06M | != parser.symbols.at(i).start) { |
490 | 0 | canExtractValueByRef = false; |
491 | 0 | break; |
492 | 0 | } |
493 | 4.06M | } |
494 | 301k | if (canExtractValueByRef) { |
495 | 301k | const QCss::Symbol &sym = parser.symbols.at(firstSymbol); |
496 | 301k | value = sym.text.mid(sym.start, len); |
497 | 301k | } |
498 | 301k | } |
499 | 303k | if (!canExtractValueByRef) { |
500 | | |
501 | 1.95M | for (int i = firstSymbol; i < parser.index - 1; ++i) |
502 | 1.95M | value += parser.symbols.at(i).lexem(); |
503 | 2.09k | } |
504 | | |
505 | 303k | attributes.append(QString(), name, value); |
506 | | |
507 | 303k | parser.skipSpace(); |
508 | 303k | } |
509 | 80.3k | } |
510 | | |
511 | | void QSvgCssHandler::styleLookup(QSvgNode *node, QXmlStreamAttributes &attributes) const |
512 | 804k | { |
513 | 804k | QCss::StyleSelector::NodePtr cssNode; |
514 | 804k | cssNode.ptr = node; |
515 | 804k | QList<QCss::Declaration> decls = m_selector->declarationsForNode(cssNode); |
516 | | |
517 | 804k | parseCSStoXMLAttrs(decls, attributes); |
518 | 804k | } |
519 | | |
520 | | QSvgCssEasingPtr QSvgCssHandler::createEasingFromKeyword(QSvgCssValues::EasingFunction easingFunction) |
521 | 0 | { |
522 | 0 | constexpr QPointF easeC1(0.25, 0.1); |
523 | 0 | constexpr QPointF easeC2(0.25, 1); |
524 | 0 | constexpr QPointF easeInC1(0.42, 0); |
525 | 0 | constexpr QPointF easeInC2(1, 1); |
526 | 0 | constexpr QPointF easeOutC1(0, 0); |
527 | 0 | constexpr QPointF easeOutC2(0.58, 1); |
528 | 0 | constexpr QPointF linearC1(0, 0); |
529 | 0 | constexpr QPointF linearC2(1, 1); |
530 | |
|
531 | 0 | QSvgCssEasingPtr easing; |
532 | |
|
533 | 0 | switch (easingFunction) { |
534 | 0 | case QSvgCssValues::EasingFunction::Ease: |
535 | 0 | easing = std::make_unique<QSvgCssCubicBezierEasing>(easingFunction, easeC1, easeC2); |
536 | 0 | break; |
537 | 0 | case QSvgCssValues::EasingFunction::EaseIn: |
538 | 0 | easing = std::make_unique<QSvgCssCubicBezierEasing>(easingFunction, easeInC1, easeInC2); |
539 | 0 | break; |
540 | 0 | case QSvgCssValues::EasingFunction::EaseOut: |
541 | 0 | easing = std::make_unique<QSvgCssCubicBezierEasing>(easingFunction, easeOutC1, easeOutC2); |
542 | 0 | break; |
543 | 0 | case QSvgCssValues::EasingFunction::EaseInOut: |
544 | 0 | easing = std::make_unique<QSvgCssCubicBezierEasing>(easingFunction, easeInC1, easeOutC2); |
545 | 0 | break; |
546 | 0 | case QSvgCssValues::EasingFunction::Linear: |
547 | 0 | easing = std::make_unique<QSvgCssCubicBezierEasing>(easingFunction, linearC1, linearC2); |
548 | 0 | break; |
549 | 0 | default: |
550 | 0 | Q_UNREACHABLE(); |
551 | 0 | break; |
552 | 0 | } |
553 | | |
554 | 0 | return easing; |
555 | 0 | } |
556 | | |
557 | | QSvgCssEasingPtr QSvgCssHandler::createStepsEasing(const QSvgCssValues::StepValues &values) |
558 | 0 | { |
559 | 0 | quint32 steps = values.steps; |
560 | 0 | QSvgCssValues::StepPosition position = values.stepPosition; |
561 | |
|
562 | 0 | return std::make_unique<QSvgCssStepsEasing>(steps, position); |
563 | 0 | } |
564 | | |
565 | | QT_END_NAMESPACE |