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
qsvgcsshandler.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 <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
13
14namespace {
15
16// Parses the angle from a string and convert it to degrees.
17qreal qsvg_parseAngle(QStringView str, bool *ok = nullptr)
18{
19 QStringView numStr = str.trimmed();
20
21 if (numStr.isEmpty()) {
22 if (ok)
23 *ok = false;
24 return false;
25 }
26
27 qreal unitFactor;
28 if (numStr.endsWith(QLatin1String("deg"))) {
29 numStr.chop(3);
30 unitFactor = 1.0;
31 } else if (numStr.endsWith(QLatin1String("grad"))) {
32 numStr.chop(4);
33 // deg = grad * 0.9;
34 unitFactor = 0.9;
35 } else if (numStr.endsWith(QLatin1String("rad"))) {
36 numStr.chop(3);
37 unitFactor = 180.0 / Q_PI;
38 } else if (numStr.endsWith(QLatin1String("turn"))) {
39 numStr.chop(4);
40 // one circle = one turn
41 unitFactor = 360.0;
42 } else {
43 unitFactor = 0.0;
44 }
45
46 return QSvgUtils::toDouble(numStr, ok) * unitFactor;
47}
48
53
54bool fillColorProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyColor *prop)
55{
56 for (CssKeyFrameValue keyFrame : keyFrames) {
57 if (keyFrame.values.size() != 1)
58 return false;
59
60 QString colorStr = keyFrame.values.first().toString();
61 QColor color = QColor::fromString(colorStr);
62 prop->appendColor(color);
63 prop->appendKeyFrame(keyFrame.keyFrame);
64 }
65
66 return true;
67}
68
69bool fillOpacityProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyFloat *prop)
70{
71 for (CssKeyFrameValue keyFrame : keyFrames) {
72 if (keyFrame.values.size() != 1)
73 return false;
74
75 QString opacity = keyFrame.values.first().toString();
76 prop->appendValue(opacity.toDouble());
77 prop->appendKeyFrame(keyFrame.keyFrame);
78 }
79
80 return true;
81}
82
83bool validateTransform(QList<QList<QSvgAnimatedPropertyTransform::TransformComponent>> &keyFrameComponents) {
84
85 if (keyFrameComponents.size() < 2)
86 return false;
87
88 qsizetype maxIndex = 0;
89 qsizetype maxSize = 0;
90 for (int i = 1; i < keyFrameComponents.size(); i++) {
91 auto &listA = keyFrameComponents[i - 1];
92 auto &listB = keyFrameComponents[i];
93 for (int j = 0; j < qMin(listA.size(), listB.size()); j++) {
94 auto typeA = listA.at(j).type;
95 auto typeB = listB.at(j).type;
96 // TODO: Handle type mismatch as mentioned in CSS Transform Module specs.
97 if (typeA != typeB)
98 return false;
99 }
100
101 if (listA.size() > maxSize) {
102 maxIndex = i - 1;
103 maxSize = listA.size();
104 }
105
106 if (listB.size() > maxSize) {
107 maxIndex = i;
108 maxSize = listB.size();
109 }
110 }
111
112 const auto &longList = keyFrameComponents.at(maxIndex);
113 // pad the shorter list with identical transforms set to default values.
114 for (auto &list : keyFrameComponents) {
115 qsizetype size = list.size();
116
117 for (int j = size; j < maxSize; j++) {
118 QSvgAnimatedPropertyTransform::TransformComponent comp = longList.value(j);
119 switch (comp.type) {
120 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
121 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
122 comp.values[0] = 0;
123 comp.values[1] = 0;
124 break;
125 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
126 comp.values[0] = 0;
127 comp.values[1] = 0;
128 comp.values[2] = 0;
129 break;
130 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
131 comp.values[0] = 1;
132 comp.values[1] = 1;
133 break;
134 default:
135 break;
136 }
137 list.append(comp);
138 }
139 }
140
141 return true;
142}
143
144bool fillTransformProperty(const QList<CssKeyFrameValue> &keyFrames, QSvgAnimatedPropertyTransform *prop)
145{
146 // Stores each key frame's list of components
147 QList<QList<QSvgAnimatedPropertyTransform::TransformComponent>> keyFramesComponents;
148
149 for (CssKeyFrameValue keyFrame : keyFrames) {
150 QList<QSvgAnimatedPropertyTransform::TransformComponent> components;
151 for (QCss::Value val : keyFrame.values) {
152 if (val.type == QCss::Value::Function) {
153 QStringList lst = val.variant.toStringList();
154 QStringView transformType = lst.value(0);
155 QStringList args = lst.value(1).split(QStringLiteral(","), Qt::SkipEmptyParts);
156 if (transformType == QStringLiteral("scale")) {
157 QSvgAnimatedPropertyTransform::TransformComponent component;
158 qreal scale0 = QSvgUtils::toDouble(args.value(0).trimmed());
159 qreal scale1 = QSvgUtils::toDouble(args.value(1).trimmed());
160 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Scale;
161 component.values.append(scale0);
162 component.values.append(scale1);
163 components.append(component);
164 } else if (transformType == QStringLiteral("translate")) {
165 QSvgAnimatedPropertyTransform::TransformComponent component;
166 QSvgUtils::LengthType type;
167 qreal translate0 = QSvgUtils::parseLength(args.value(0), &type);
168 translate0 = QSvgUtils::convertToPixels(translate0, false, type);
169 qreal translate1 = QSvgUtils::parseLength(args.value(1), &type);
170 translate1 = QSvgUtils::convertToPixels(translate1, false, type);
171 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Translate;
172 component.values.append(translate0);
173 component.values.append(translate1);
174 components.append(component);
175 } else if (transformType == QStringLiteral("rotate")) {
176 QSvgAnimatedPropertyTransform::TransformComponent component;
177 qreal rotationAngle = qsvg_parseAngle(args.value(0));
178 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Rotate;
179 component.values.append(rotationAngle);
180 component.values.append(0);
181 component.values.append(0);
182 components.append(component);
183 } else if (transformType == QStringLiteral("skew")) {
184 QSvgAnimatedPropertyTransform::TransformComponent component;
185 qreal skew0 = qsvg_parseAngle(args.value(0));
186 qreal skew1 = qsvg_parseAngle(args.value(1));
187 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew;
188 component.values.append(skew0);
189 component.values.append(skew1);
190 components.append(component);
191 } else if (transformType == QStringLiteral("matrix")) {
192 QSvgAnimatedPropertyTransform::TransformComponent component1, component2, component3;
193 QSvgUtils::LengthType type;
194 qreal translate0 = QSvgUtils::parseLength(args.value(4), &type);
195 translate0 = QSvgUtils::convertToPixels(translate0, false, type);
196 qreal translate1 = QSvgUtils::parseLength(args.value(5), &type);
197 translate1 = QSvgUtils::convertToPixels(translate1, false, type);
198 qreal scale0 = QSvgUtils::toDouble(args.value(0).trimmed());
199 qreal scale1 = QSvgUtils::toDouble(args.value(3).trimmed());
200 qreal skew0 = QSvgUtils::toDouble((args.value(1).trimmed()));
201 qreal skew1 = QSvgUtils::toDouble((args.value(2).trimmed()));
202 component1.type = QSvgAnimatedPropertyTransform::TransformComponent::Translate;
203 component1.values.append(translate0);
204 component1.values.append(translate1);
205 component2.type = QSvgAnimatedPropertyTransform::TransformComponent::Scale;
206 component2.values.append(scale0);
207 component2.values.append(scale1);
208 component3.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew;
209 component3.values.append(skew0);
210 component3.values.append(skew1);
211 components.append(component1);
212 components.append(component2);
213 components.append(component3);
214 }
215 }
216 }
217 keyFramesComponents.append(components);
218 prop->appendKeyFrame(keyFrame.keyFrame);
219 }
220
221 if (!validateTransform(keyFramesComponents))
222 return false;
223
224 for (auto comp : keyFramesComponents) {
225 prop->appendComponents(comp);
226 }
227 prop->setTransformCount(keyFramesComponents.first().size());
228
229 return true;
230}
231
232}
233
239
241{
242 delete m_selector;
243 m_selector = nullptr;
244}
245
247{
248 if (!m_animations.contains(name))
249 return nullptr;
250
251 QCss::AnimationRule animationRule = m_animations[name];
252 QHash<QString, QSvgAbstractAnimatedProperty*> animatedProperies;
253 QSvgCssAnimation *animation = new QSvgCssAnimation;
254
255
256 // Css Parser returns a list of all properties for each key frame. Here,
257 // we store the key frames and values for each property for easier parsing.
258 QHash<QString, QList<CssKeyFrameValue>> keyFrameValues;
259 for (const auto &ruleSet : std::as_const(animationRule.ruleSets)) {
260 for (QCss::Declaration decl : ruleSet.declarations) {
261 CssKeyFrameValue keyFrameValue = {ruleSet.keyFrame, decl.d->values};
262 QList<CssKeyFrameValue> &value = keyFrameValues[decl.d->property];
263 value.append(keyFrameValue);
264 }
265 }
266
267 for (auto it = keyFrameValues.begin(); it != keyFrameValues.end(); it++) {
268 QStringView property = it.key();
269 const QList<CssKeyFrameValue> &keyFrames = it.value();
270 auto *prop = QSvgAbstractAnimatedProperty::createAnimatedProperty(property.toString());
271 if (!prop)
272 continue;
273
274 bool result = false;
275 if (property == QLatin1StringView("fill") || property == QLatin1StringView("stroke"))
276 result = fillColorProperty(keyFrames, static_cast<QSvgAnimatedPropertyColor*>(prop));
277 else if (property == QLatin1StringView("transform"))
278 result = fillTransformProperty(keyFrames, static_cast<QSvgAnimatedPropertyTransform*>(prop));
279 else if (property == QLatin1StringView("fill-opacity") || property == QLatin1StringView("stroke-opacity")
280 || property == QLatin1StringView("opacity"))
281 result = fillOpacityProperty(keyFrames, static_cast<QSvgAnimatedPropertyFloat*>(prop));
282
283 if (!result) {
284 delete prop;
285 continue;
286 }
287
288 animatedProperies[property] = prop;
289 }
290
291 for (auto it = animatedProperies.begin(); it != animatedProperies.end(); it++)
292 animation->appendProperty(it.value());
293
294 return animation;
295}
296
297void QSvgCssHandler::collectAnimations(const QCss::StyleSheet &sheet)
298{
299 auto sortFunction = [](QCss::AnimationRule::AnimationRuleSet r1, QCss::AnimationRule::AnimationRuleSet r2) {
300 return r1.keyFrame < r2.keyFrame;
301 };
302
303 QList<QCss::AnimationRule> animationRules = sheet.animationRules;
304 for (QCss::AnimationRule rule : animationRules) {
305 std::sort(rule.ruleSets.begin(), rule.ruleSets.end(), sortFunction);
306 m_animations[rule.animName] = rule;
307 }
308}
309
310void QSvgCssHandler::parseStyleSheet(const QStringView str)
311{
312 QString css = str.toString();
313 QCss::StyleSheet sheet;
314 QCss::Parser(css).parse(&sheet);
315 m_selector->styleSheets.append(sheet);
316
317 collectAnimations(sheet);
318}
319
320void QSvgCssHandler::parseCSStoXMLAttrs(const QList<QCss::Declaration> &declarations, QXmlStreamAttributes &attributes) const
321{
322 for (int i = 0; i < declarations.size(); ++i) {
323 const QCss::Declaration &decl = declarations.at(i);
324 if (decl.d->property.isEmpty())
325 continue;
326 QString valueStr;
327 const int valCount = decl.d->values.size();
328 for (int i = 0; i < valCount; ++i) {
329 QCss::Value val = decl.d->values.at(i);
330 if (val.type == QCss::Value::TermOperatorComma) {
331 valueStr += QLatin1Char(';');
332 } else if (val.type == QCss::Value::Uri) {
333 valueStr.prepend(QLatin1String("url("));
334 valueStr.append(QLatin1Char(')'));
335 } else if (val.type == QCss::Value::Function) {
336 QStringList lst = val.variant.toStringList();
337 valueStr.append(lst.at(0));
338 valueStr.append(QLatin1Char('('));
339 for (int i = 1; i < lst.size(); ++i) {
340 valueStr.append(lst.at(i));
341 if ((i +1) < lst.size())
342 valueStr.append(QLatin1Char(','));
343 }
344 valueStr.append(QLatin1Char(')'));
345 } else if (val.type == QCss::Value::KnownIdentifier) {
346 switch (val.variant.toInt()) {
347 case QCss::Value_None:
348 valueStr = QLatin1String("none");
349 break;
350 default:
351 break;
352 }
353 } else
354 valueStr += val.toString();
355
356 if (i + 1 < valCount)
357 valueStr += QLatin1Char(' ');
358 }
359
360 attributes.append(QString(), decl.d->property, valueStr);
361 }
362}
363
364void QSvgCssHandler::parseCSStoXMLAttrs(const QString &css, QXmlStreamAttributes &attributes) const
365{
366 // preprocess (for unicode escapes), tokenize and remove comments
367 QCss::Parser parser(css);
368 QString key;
369
370 while (parser.hasNext()) {
371 parser.skipSpace();
372
373 if (!parser.hasNext())
374 break;
375 parser.next();
376
377 QString name;
378 QString value;
379
380 if (parser.hasEscapeSequences) {
381 key = parser.lexem();
382 name = key;
383 } else {
384 const QCss::Symbol &sym = parser.symbol();
385 name = sym.text.mid(sym.start, sym.len);
386 }
387
388 parser.skipSpace();
389 if (!parser.test(QCss::COLON))
390 break;
391
392 parser.skipSpace();
393 if (!parser.hasNext())
394 break;
395
396 const int firstSymbol = parser.index;
397 int symbolCount = 0;
398 do {
399 parser.next();
400 ++symbolCount;
401 } while (parser.hasNext() && !parser.test(QCss::SEMICOLON));
402
403 bool canExtractValueByRef = !parser.hasEscapeSequences;
404 if (canExtractValueByRef) {
405 int len = parser.symbols.at(firstSymbol).len;
406 for (int i = firstSymbol + 1; i < firstSymbol + symbolCount; ++i) {
407 len += parser.symbols.at(i).len;
408
409 if (parser.symbols.at(i - 1).start + parser.symbols.at(i - 1).len
410 != parser.symbols.at(i).start) {
411 canExtractValueByRef = false;
412 break;
413 }
414 }
415 if (canExtractValueByRef) {
416 const QCss::Symbol &sym = parser.symbols.at(firstSymbol);
417 value = sym.text.mid(sym.start, len);
418 }
419 }
420 if (!canExtractValueByRef) {
421
422 for (int i = firstSymbol; i < parser.index - 1; ++i)
423 value += parser.symbols.at(i).lexem();
424 }
425
426 attributes.append(QString(), name, value);
427
428 parser.skipSpace();
429 }
430}
431
432void QSvgCssHandler::styleLookup(QSvgNode *node, QXmlStreamAttributes &attributes) const
433{
434 QCss::StyleSelector::NodePtr cssNode;
435 cssNode.ptr = node;
436 QList<QCss::Declaration> decls = m_selector->declarationsForNode(cssNode);
437
438 parseCSStoXMLAttrs(decls, attributes);
439}
440
441QT_END_NAMESPACE
void collectAnimations(const QCss::StyleSheet &sheet)
void styleLookup(QSvgNode *node, QXmlStreamAttributes &attributes) const
void parseCSStoXMLAttrs(const QList< QCss::Declaration > &declarations, QXmlStreamAttributes &attributes) const
void parseStyleSheet(const QStringView str)
QSvgCssAnimation * createAnimation(QStringView name)
bool fillTransformProperty(const QList< CssKeyFrameValue > &keyFrames, QSvgAnimatedPropertyTransform *prop)
bool fillOpacityProperty(const QList< CssKeyFrameValue > &keyFrames, QSvgAnimatedPropertyFloat *prop)
qreal qsvg_parseAngle(QStringView str, bool *ok=nullptr)
bool fillColorProperty(const QList< CssKeyFrameValue > &keyFrames, QSvgAnimatedPropertyColor *prop)
bool validateTransform(QList< QList< QSvgAnimatedPropertyTransform::TransformComponent > > &keyFrameComponents)