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