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
qcssparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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 "qcssparser_p.h"
6
7#include <QtCore/qmap.h>
8#include <qdebug.h>
9#include <qicon.h>
10#include <qcolor.h>
11#include <qfont.h>
12#include <qfileinfo.h>
13#include <qfontmetrics.h>
14#include <qbrush.h>
15#include <qimagereader.h>
16#include <qtextformat.h>
17
18#include <QtCore/q20algorithm.h>
19
20#ifndef QT_NO_CSSPARSER
21
23
24using namespace Qt::StringLiterals;
25
26QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::BackgroundData, QCss__BackgroundData)
27QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::LengthData, QCss__LengthData)
28QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::BorderData, QCss__BorderData)
29
30#include "qcssscanner.cpp"
31
32using namespace QCss;
33
35{
36 const char name[28];
38
39 struct ByName;
40};
41
43{
44 constexpr bool operator()(const QCssKnownValue &lhs, const QCssKnownValue &rhs) const noexcept
45 { return std::string_view{lhs.name} < std::string_view{rhs.name}; }
46};
47#if !defined(Q_CC_GNU_ONLY) || Q_CC_GNU >= 1000
48# define NOT_OLD_GCCs(...) __VA_ARGS__
49#else
50# define NOT_OLD_GCCs(...) /* nothing */
51#endif
52#define CHECK_ARRAY_IS_SORTED(array)
53 NOT_OLD_GCCs(
54 static_assert(q20::is_sorted(std::begin(array), std::end(array),
55 QCssKnownValue::ByName{}));
56 ) /* NOT_OLD_GCCs */
57 /* end */
58
59// This array is sorted alphabetically.
60static constexpr QCssKnownValue properties[] = {
61 { "-qt-background-role", QtBackgroundRole },
62 { "-qt-block-indent", QtBlockIndent },
63 { "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey },
64 { "-qt-foreground", QtForeground },
65 { "-qt-line-height-type", QtLineHeightType },
66 { "-qt-list-indent", QtListIndent },
67 { "-qt-list-number-prefix", QtListNumberPrefix },
68 { "-qt-list-number-suffix", QtListNumberSuffix },
69 { "-qt-paragraph-type", QtParagraphType },
70 { "-qt-stroke-color", QtStrokeColor },
71 { "-qt-stroke-dasharray", QtStrokeDashArray },
72 { "-qt-stroke-dashoffset", QtStrokeDashOffset },
73 { "-qt-stroke-linecap", QtStrokeLineCap },
74 { "-qt-stroke-linejoin", QtStrokeLineJoin },
75 { "-qt-stroke-miterlimit", QtStrokeMiterLimit },
76 { "-qt-stroke-width", QtStrokeWidth },
77 { "-qt-style-features", QtStyleFeatures },
78 { "-qt-table-type", QtTableType },
79 { "-qt-user-state", QtUserState },
80 { "accent-color", QtAccent },
81 { "alternate-background-color", QtAlternateBackground },
82 { "background", Background },
83 { "background-attachment", BackgroundAttachment },
84 { "background-clip", BackgroundClip },
85 { "background-color", BackgroundColor },
86 { "background-image", BackgroundImage },
87 { "background-origin", BackgroundOrigin },
88 { "background-position", BackgroundPosition },
89 { "background-repeat", BackgroundRepeat },
90 { "border", Border },
91 { "border-bottom", BorderBottom },
92 { "border-bottom-color", BorderBottomColor },
93 { "border-bottom-left-radius", BorderBottomLeftRadius },
94 { "border-bottom-right-radius", BorderBottomRightRadius },
95 { "border-bottom-style", BorderBottomStyle },
96 { "border-bottom-width", BorderBottomWidth },
97 { "border-collapse", BorderCollapse },
98 { "border-color", BorderColor },
99 { "border-image", BorderImage },
100 { "border-left", BorderLeft },
101 { "border-left-color", BorderLeftColor },
102 { "border-left-style", BorderLeftStyle },
103 { "border-left-width", BorderLeftWidth },
104 { "border-radius", BorderRadius },
105 { "border-right", BorderRight },
106 { "border-right-color", BorderRightColor },
107 { "border-right-style", BorderRightStyle },
108 { "border-right-width", BorderRightWidth },
109 { "border-style", BorderStyles },
110 { "border-top", BorderTop },
111 { "border-top-color", BorderTopColor },
112 { "border-top-left-radius", BorderTopLeftRadius },
113 { "border-top-right-radius", BorderTopRightRadius },
114 { "border-top-style", BorderTopStyle },
115 { "border-top-width", BorderTopWidth },
116 { "border-width", BorderWidth },
117 { "bottom", Bottom },
118 { "color", Color },
119 { "float", Float },
120 { "font", Font },
121 { "font-family", FontFamily },
122 { "font-kerning", FontKerning },
123 { "font-size", FontSize },
124 { "font-style", FontStyle },
125 { "font-variant", FontVariant },
126 { "font-weight", FontWeight },
127 { "height", Height },
128 { "icon", QtIcon },
129 { "image", QtImage },
130 { "image-position", QtImageAlignment },
131 { "left", Left },
132 { "letter-spacing", LetterSpacing },
133 { "line-height", LineHeight },
134 { "list-style", ListStyle },
135 { "list-style-type", ListStyleType },
136 { "margin" , Margin },
137 { "margin-bottom", MarginBottom },
138 { "margin-left", MarginLeft },
139 { "margin-right", MarginRight },
140 { "margin-top", MarginTop },
141 { "max-height", MaximumHeight },
142 { "max-width", MaximumWidth },
143 { "min-height", MinimumHeight },
144 { "min-width", MinimumWidth },
145 { "outline", Outline },
146 { "outline-bottom-left-radius", OutlineBottomLeftRadius },
147 { "outline-bottom-right-radius", OutlineBottomRightRadius },
148 { "outline-color", OutlineColor },
149 { "outline-offset", OutlineOffset },
150 { "outline-radius", OutlineRadius },
151 { "outline-style", OutlineStyle },
152 { "outline-top-left-radius", OutlineTopLeftRadius },
153 { "outline-top-right-radius", OutlineTopRightRadius },
154 { "outline-width", OutlineWidth },
155 { "padding", Padding },
156 { "padding-bottom", PaddingBottom },
157 { "padding-left", PaddingLeft },
158 { "padding-right", PaddingRight },
159 { "padding-top", PaddingTop },
160 { "page-break-after", PageBreakAfter },
161 { "page-break-before", PageBreakBefore },
162 { "placeholder-text-color", QtPlaceHolderTextColor },
163 { "position", Position },
164 { "right", Right },
165 { "selection-background-color", QtSelectionBackground },
166 { "selection-color", QtSelectionForeground },
167 { "spacing", QtSpacing },
168 { "subcontrol-origin", QtOrigin },
169 { "subcontrol-position", QtPosition },
170 { "text-align", TextAlignment },
171 { "text-decoration", TextDecoration },
172 { "text-decoration-color", TextDecorationColor },
173 { "text-indent", TextIndent },
174 { "text-transform", TextTransform },
175 { "text-underline-style", TextUnderlineStyle },
176 { "top", Top },
177 { "vertical-align", VerticalAlignment },
178 { "white-space", Whitespace },
179 { "width", Width },
180 { "word-spacing", WordSpacing }
181};
182CHECK_ARRAY_IS_SORTED(properties)
183
184static constexpr QCssKnownValue values[] = {
185 { "accent", Value_Accent },
186 { "active", Value_Active },
187 { "alternate-base", Value_AlternateBase },
188 { "always", Value_Always },
189 { "auto", Value_Auto },
190 { "base", Value_Base },
191 { "beveljoin", Value_BevelJoin},
192 { "bold", Value_Bold },
193 { "bottom", Value_Bottom },
194 { "bright-text", Value_BrightText },
195 { "button", Value_Button },
196 { "button-text", Value_ButtonText },
197 { "center", Value_Center },
198 { "circle", Value_Circle },
199 { "dark", Value_Dark },
200 { "dashed", Value_Dashed },
201 { "decimal", Value_Decimal },
202 { "disabled", Value_Disabled },
203 { "disc", Value_Disc },
204 { "dot-dash", Value_DotDash },
205 { "dot-dot-dash", Value_DotDotDash },
206 { "dotted", Value_Dotted },
207 { "double", Value_Double },
208 { "flatcap", Value_FlatCap},
209 { "groove", Value_Groove },
210 { "highlight", Value_Highlight },
211 { "highlighted-text", Value_HighlightedText },
212 { "inset", Value_Inset },
213 { "italic", Value_Italic },
214 { "large", Value_Large },
215 { "left", Value_Left },
216 { "light", Value_Light },
217 { "line-through", Value_LineThrough },
218 { "link", Value_Link },
219 { "link-visited", Value_LinkVisited },
220 { "lower-alpha", Value_LowerAlpha },
221 { "lower-roman", Value_LowerRoman },
222 { "lowercase", Value_Lowercase },
223 { "medium", Value_Medium },
224 { "mid", Value_Mid },
225 { "middle", Value_Middle },
226 { "midlight", Value_Midlight },
227 { "miterjoin", Value_MiterJoin},
228 { "native", Value_Native },
229 { "no-role", Value_NoRole },
230 { "none", Value_None },
231 { "normal", Value_Normal },
232 { "nowrap", Value_NoWrap },
233 { "oblique", Value_Oblique },
234 { "off", Value_Off },
235 { "on", Value_On },
236 { "outset", Value_Outset },
237 { "overline", Value_Overline },
238 { "placeholder-text", Value_PlaceholderText },
239 { "pre", Value_Pre },
240 { "pre-line", Value_PreLine },
241 { "pre-wrap", Value_PreWrap },
242 { "ridge", Value_Ridge },
243 { "right", Value_Right },
244 { "roundcap", Value_RoundCap},
245 { "roundjoin", Value_RoundJoin},
246 { "selected", Value_Selected },
247 { "shadow", Value_Shadow },
248 { "small" , Value_Small },
249 { "small-caps", Value_SmallCaps },
250 { "solid", Value_Solid },
251 { "square", Value_Square },
252 { "squarecap", Value_SquareCap},
253 { "sub", Value_Sub },
254 { "super", Value_Super },
255 { "svgmiterjoin", Value_SvgMiterJoin},
256 { "text", Value_Text },
257 { "tooltip-base", Value_ToolTipBase },
258 { "tooltip-text", Value_ToolTipText },
259 { "top", Value_Top },
260 { "transparent", Value_Transparent },
261 { "underline", Value_Underline },
262 { "upper-alpha", Value_UpperAlpha },
263 { "upper-roman", Value_UpperRoman },
264 { "uppercase", Value_Uppercase },
265 { "wave", Value_Wave },
266 { "window", Value_Window },
267 { "window-text", Value_WindowText },
268 { "x-large", Value_XLarge },
269 { "xx-large", Value_XXLarge }
270};
272
273//Map id to strings as they appears in the 'values' array above
274static constexpr uchar indexOfId[] = {
275 0, 46, 54, 47, 55, 56, 63, 38, 29, 83, 84, 28, 48, 7, 76, 52,
276 32, 68, 69, 30, 58, 74, 8, 12, 43, 65, 21, 15, 19, 20, 22, 24, 57, 27, 51, 80, 40, 4, 3, 45, 75, 18, 13,
277 66, 16, 35, 77, 36, 78, 64, 79, 37, 67, 23, 59, 42, 6, 60, 70, 82, 10, 31, 41, 14, 39, 71, 9, 11, 5, 81,
278 62, 25, 26, 33, 34, 2, 44, 72, 73, 53, 0, 17, 1, 61, 50, 49 };
279
280static_assert(std::size(indexOfId) == std::size(values) + 1); // UnknownValue not in values array but in indexOfId
281
282QString Value::toString() const
283{
284 if (type == KnownIdentifier) {
285 return QLatin1StringView(values[indexOfId[variant.toInt()]].name);
286 } else {
287 return variant.toString();
288 }
289}
290
291static constexpr QCssKnownValue pseudos[] = {
292 { "active", PseudoClass_Active },
293 { "adjoins-item", PseudoClass_Item },
294 { "alternate", PseudoClass_Alternate },
295 { "bottom", PseudoClass_Bottom },
296 { "checked", PseudoClass_Checked },
297 { "closable", PseudoClass_Closable },
298 { "closed", PseudoClass_Closed },
299 { "default", PseudoClass_Default },
300 { "disabled", PseudoClass_Disabled },
301 { "edit-focus", PseudoClass_EditFocus },
302 { "editable", PseudoClass_Editable },
303 { "enabled", PseudoClass_Enabled },
304 { "exclusive", PseudoClass_Exclusive },
305 { "first", PseudoClass_First },
306 { "flat", PseudoClass_Flat },
307 { "floatable", PseudoClass_Floatable },
308 { "focus", PseudoClass_Focus },
309 { "has-children", PseudoClass_Children },
310 { "has-siblings", PseudoClass_Sibling },
311 { "horizontal", PseudoClass_Horizontal },
312 { "hover", PseudoClass_Hover },
313 { "indeterminate" , PseudoClass_Indeterminate },
314 { "last", PseudoClass_Last },
315 { "left", PseudoClass_Left },
316 { "maximized", PseudoClass_Maximized },
317 { "middle", PseudoClass_Middle },
318 { "minimized", PseudoClass_Minimized },
319 { "movable", PseudoClass_Movable },
320 { "next-selected", PseudoClass_NextSelected },
321 { "no-frame", PseudoClass_Frameless },
322 { "non-exclusive", PseudoClass_NonExclusive },
323 { "off", PseudoClass_Unchecked },
324 { "on", PseudoClass_Checked },
325 { "only-one", PseudoClass_OnlyOne },
326 { "open", PseudoClass_Open },
327 { "pressed", PseudoClass_Pressed },
328 { "previous-selected", PseudoClass_PreviousSelected },
329 { "read-only", PseudoClass_ReadOnly },
330 { "right", PseudoClass_Right },
331 { "selected", PseudoClass_Selected },
332 { "top", PseudoClass_Top },
333 { "unchecked" , PseudoClass_Unchecked },
334 { "vertical", PseudoClass_Vertical },
335 { "window", PseudoClass_Window }
336};
338
339static constexpr QCssKnownValue origins[] = {
340 { "border", Origin_Border },
341 { "content", Origin_Content },
342 { "margin", Origin_Margin }, // not in css
343 { "padding", Origin_Padding }
344};
346
347static constexpr QCssKnownValue repeats[] = {
348 { "no-repeat", Repeat_None },
349 { "repeat-x", Repeat_X },
350 { "repeat-xy", Repeat_XY },
351 { "repeat-y", Repeat_Y }
352};
354
355static constexpr QCssKnownValue tileModes[] = {
356 { "repeat", TileMode_Repeat },
357 { "round", TileMode_Round },
358 { "stretch", TileMode_Stretch },
359};
360CHECK_ARRAY_IS_SORTED(tileModes)
361
362static constexpr QCssKnownValue positions[] = {
363 { "absolute", PositionMode_Absolute },
364 { "fixed", PositionMode_Fixed },
365 { "relative", PositionMode_Relative },
366 { "static", PositionMode_Static }
367};
368CHECK_ARRAY_IS_SORTED(positions)
369
370static constexpr QCssKnownValue attachments[] = {
371 { "fixed", Attachment_Fixed },
372 { "scroll", Attachment_Scroll }
373};
374CHECK_ARRAY_IS_SORTED(attachments)
375
376static constexpr QCssKnownValue styleFeatures[] = {
377 { "background-color", StyleFeature_BackgroundColor },
378 { "background-gradient", StyleFeature_BackgroundGradient },
379 { "none", StyleFeature_None }
380};
381CHECK_ARRAY_IS_SORTED(styleFeatures)
382
383static bool operator<(const QString &name, const QCssKnownValue &prop)
384{
385 return QString::compare(name, QLatin1StringView(prop.name), Qt::CaseInsensitive) < 0;
386}
387
388static bool operator<(const QCssKnownValue &prop, const QString &name)
389{
390 return QString::compare(QLatin1StringView(prop.name), name, Qt::CaseInsensitive) < 0;
391}
392
393#undef CHECK_ARRAY_IS_SORTED
394#undef NOT_OLD_GCCs
395
396template <typename C>
397static quint64 findKnownValue(const QString &name, const C &arr)
398{
399 const QCssKnownValue *prop = std::lower_bound(std::begin(arr), std::end(arr), name);
400 if ((prop == std::end(arr)) || (name < *prop))
401 return 0;
402 return prop->id;
403}
404
405static inline bool isInheritable(Property propertyId)
406{
407 switch (propertyId) {
408 case Font:
409 case FontKerning:
410 case FontFamily:
411 case FontSize:
412 case FontStyle:
413 case FontWeight:
414 case TextIndent:
415 case Whitespace:
416 case ListStyleType:
417 case ListStyle:
418 case TextAlignment:
419 case FontVariant:
420 case TextTransform:
421 case LineHeight:
422 case LetterSpacing:
423 case WordSpacing:
424 return true;
425 default:
426 break;
427 }
428 return false;
429}
430
431///////////////////////////////////////////////////////////////////////////////
432// Value Extractor
433ValueExtractor::ValueExtractor(const QList<Declaration> &decls, const QPalette &pal)
434: declarations(decls), adjustment(0), fontExtracted(false), pal(pal)
435{
436}
437
438LengthData ValueExtractor::lengthValue(const Value& v)
439{
440 const QString str = v.variant.toString();
441 QStringView s(str);
442 LengthData data;
443 data.unit = LengthData::None;
444 if (s.endsWith(u"px", Qt::CaseInsensitive))
445 data.unit = LengthData::Px;
446 else if (s.endsWith(u"ex", Qt::CaseInsensitive))
447 data.unit = LengthData::Ex;
448 else if (s.endsWith(u"em", Qt::CaseInsensitive))
449 data.unit = LengthData::Em;
450
451 if (data.unit != LengthData::None)
452 s.chop(2);
453 else if (v.type == Value::Percentage)
454 data.unit = LengthData::Percent;
455
456 data.number = s.toDouble();
457 return data;
458}
459
460static int lengthValueFromData(const LengthData& data, const QFont& f)
461{
462 const int scale = (data.unit == LengthData::Ex ? QFontMetrics(f).xHeight()
463 : data.unit == LengthData::Em ? QFontMetrics(f).height() : 1);
464 // raised lower limit due to the implementation of qRound()
465 return qRound(qBound(double(INT_MIN) + 0.1, scale * data.number, double(INT_MAX)));
466}
467
468QTextLength ValueExtractor::textLength(const Declaration &decl)
469{
470 const LengthData data = lengthValue(decl.d->values.at(0));
471 if (data.unit == LengthData::Percent)
472 return QTextLength(QTextLength::PercentageLength, data.number);
473
474 return QTextLength(QTextLength::FixedLength, lengthValueFromData(data, f));
475}
476
477int ValueExtractor::lengthValue(const Declaration &decl)
478{
479 if (decl.d->parsed.isValid())
480 return lengthValueFromData(qvariant_cast<LengthData>(decl.d->parsed), f);
481 if (decl.d->values.size() < 1)
482 return 0;
483 LengthData data = lengthValue(decl.d->values.at(0));
484 decl.d->parsed = QVariant::fromValue<LengthData>(data);
485 return lengthValueFromData(data,f);
486}
487
488void ValueExtractor::lengthValues(const Declaration &decl, int *m)
489{
490 if (decl.d->parsed.isValid()) {
491 QList<QVariant> v = decl.d->parsed.toList();
492 Q_ASSERT(v.size() == 4);
493 for (int i = 0; i < 4; i++)
494 m[i] = lengthValueFromData(qvariant_cast<LengthData>(v.at(i)), f);
495 return;
496 }
497
498 LengthData datas[4];
499 int i;
500 for (i = 0; i < qMin(decl.d->values.size(), 4); i++)
501 datas[i] = lengthValue(decl.d->values[i]);
502
503 if (i == 0) {
504 LengthData zero = {0.0, LengthData::None};
505 datas[0] = datas[1] = datas[2] = datas[3] = zero;
506 } else if (i == 1) {
507 datas[3] = datas[2] = datas[1] = datas[0];
508 } else if (i == 2) {
509 datas[2] = datas[0];
510 datas[3] = datas[1];
511 } else if (i == 3) {
512 datas[3] = datas[1];
513 }
514
515 QList<QVariant> v;
516 v.reserve(4);
517 for (i = 0; i < 4; i++) {
518 v += QVariant::fromValue<LengthData>(datas[i]);
519 m[i] = lengthValueFromData(datas[i], f);
520 }
521 decl.d->parsed = std::move(v);
522}
523
524bool ValueExtractor::extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh)
525{
526 extractFont();
527 bool hit = false;
528 for (const Declaration &decl : std::as_const(declarations)) {
529 switch (decl.d->propertyId) {
530 case Width: *w = lengthValue(decl); break;
531 case Height: *h = lengthValue(decl); break;
532 case MinimumWidth: *minw = lengthValue(decl); break;
533 case MinimumHeight: *minh = lengthValue(decl); break;
534 case MaximumWidth: *maxw = lengthValue(decl); break;
535 case MaximumHeight: *maxh = lengthValue(decl); break;
536 default: continue;
537 }
538 hit = true;
539 }
540
541 return hit;
542}
543
544bool ValueExtractor::extractPosition(int *left, int *top, int *right, int *bottom, QCss::Origin *origin,
545 Qt::Alignment *position, QCss::PositionMode *mode, Qt::Alignment *textAlignment)
546{
547 extractFont();
548 bool hit = false;
549 for (const Declaration &decl : std::as_const(declarations)) {
550 switch (decl.d->propertyId) {
551 case Left: *left = lengthValue(decl); break;
552 case Top: *top = lengthValue(decl); break;
553 case Right: *right = lengthValue(decl); break;
554 case Bottom: *bottom = lengthValue(decl); break;
555 case QtOrigin: *origin = decl.originValue(); break;
556 case QtPosition: *position = decl.alignmentValue(); break;
557 case TextAlignment: *textAlignment = decl.alignmentValue(); break;
558 case Position: *mode = decl.positionValue(); break;
559 default: continue;
560 }
561 hit = true;
562 }
563
564 return hit;
565}
566
567bool ValueExtractor::extractBox(int *margins, int *paddings, int *spacing)
568{
569 extractFont();
570 bool hit = false;
571 for (const Declaration &decl : std::as_const(declarations)) {
572 switch (decl.d->propertyId) {
573 case PaddingLeft: paddings[LeftEdge] = lengthValue(decl); break;
574 case PaddingRight: paddings[RightEdge] = lengthValue(decl); break;
575 case PaddingTop: paddings[TopEdge] = lengthValue(decl); break;
576 case PaddingBottom: paddings[BottomEdge] = lengthValue(decl); break;
577 case Padding: lengthValues(decl, paddings); break;
578
579 case MarginLeft: margins[LeftEdge] = lengthValue(decl); break;
580 case MarginRight: margins[RightEdge] = lengthValue(decl); break;
581 case MarginTop: margins[TopEdge] = lengthValue(decl); break;
582 case MarginBottom: margins[BottomEdge] = lengthValue(decl); break;
583 case Margin: lengthValues(decl, margins); break;
584 case QtSpacing: if (spacing) *spacing = lengthValue(decl); break;
585
586 default: continue;
587 }
588 hit = true;
589 }
590
591 return hit;
592}
593
594int ValueExtractor::extractStyleFeatures() const
595{
596 int features = StyleFeature_None;
597 for (const Declaration &decl : declarations) {
598 if (decl.d->propertyId == QtStyleFeatures)
599 features = decl.styleFeaturesValue();
600 }
601 return features;
602}
603
604QSize ValueExtractor::sizeValue(const Declaration &decl)
605{
606 if (decl.d->parsed.isValid()) {
607 QList<QVariant> v = decl.d->parsed.toList();
608 return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f),
609 lengthValueFromData(qvariant_cast<LengthData>(v.at(1)), f));
610 }
611
612 LengthData x[2] = { {0, LengthData::None }, {0, LengthData::None} };
613 if (decl.d->values.size() > 0)
614 x[0] = lengthValue(decl.d->values.at(0));
615 if (decl.d->values.size() > 1)
616 x[1] = lengthValue(decl.d->values.at(1));
617 else
618 x[1] = x[0];
619 QList<QVariant> v;
620 v << QVariant::fromValue<LengthData>(x[0]) << QVariant::fromValue<LengthData>(x[1]);
621 decl.d->parsed = std::move(v);
622 return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f));
623}
624
625void ValueExtractor::sizeValues(const Declaration &decl, QSize *radii)
626{
627 radii[0] = sizeValue(decl);
628 for (int i = 1; i < 4; i++)
629 radii[i] = radii[0];
630}
631
632bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *styles,
633 QSize *radii)
634{
635 extractFont();
636 bool hit = false;
637 for (const Declaration &decl : std::as_const(declarations)) {
638 switch (decl.d->propertyId) {
639 case BorderLeftWidth: borders[LeftEdge] = lengthValue(decl); break;
640 case BorderRightWidth: borders[RightEdge] = lengthValue(decl); break;
641 case BorderTopWidth: borders[TopEdge] = lengthValue(decl); break;
642 case BorderBottomWidth: borders[BottomEdge] = lengthValue(decl); break;
643 case BorderWidth: lengthValues(decl, borders); break;
644
645 case BorderLeftColor: colors[LeftEdge] = decl.brushValue(pal); break;
646 case BorderRightColor: colors[RightEdge] = decl.brushValue(pal); break;
647 case BorderTopColor: colors[TopEdge] = decl.brushValue(pal); break;
648 case BorderBottomColor: colors[BottomEdge] = decl.brushValue(pal); break;
649 case BorderColor: decl.brushValues(colors, pal); break;
650
651 case BorderTopStyle: styles[TopEdge] = decl.styleValue(); break;
652 case BorderBottomStyle: styles[BottomEdge] = decl.styleValue(); break;
653 case BorderLeftStyle: styles[LeftEdge] = decl.styleValue(); break;
654 case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break;
655 case BorderStyles: decl.styleValues(styles); break;
656
657 case BorderTopLeftRadius: radii[0] = sizeValue(decl); break;
658 case BorderTopRightRadius: radii[1] = sizeValue(decl); break;
659 case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break;
660 case BorderBottomRightRadius: radii[3] = sizeValue(decl); break;
661 case BorderRadius: sizeValues(decl, radii); break;
662
663 case BorderLeft:
664 borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
665 break;
666 case BorderTop:
667 borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]);
668 break;
669 case BorderRight:
670 borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]);
671 break;
672 case BorderBottom:
673 borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]);
674 break;
675 case Border:
676 borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
677 borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
678 styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
679 colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
680 break;
681
682 default: continue;
683 }
684 hit = true;
685 }
686
687 return hit;
688}
689
690bool ValueExtractor::extractOutline(int *borders, QBrush *colors, BorderStyle *styles,
691 QSize *radii, int *offsets)
692{
693 extractFont();
694 bool hit = false;
695 for (const Declaration &decl : std::as_const(declarations)) {
696 switch (decl.d->propertyId) {
697 case OutlineWidth: lengthValues(decl, borders); break;
698 case OutlineColor: decl.brushValues(colors, pal); break;
699 case OutlineStyle: decl.styleValues(styles); break;
700
701 case OutlineTopLeftRadius: radii[0] = sizeValue(decl); break;
702 case OutlineTopRightRadius: radii[1] = sizeValue(decl); break;
703 case OutlineBottomLeftRadius: radii[2] = sizeValue(decl); break;
704 case OutlineBottomRightRadius: radii[3] = sizeValue(decl); break;
705 case OutlineRadius: sizeValues(decl, radii); break;
706 case OutlineOffset: lengthValues(decl, offsets); break;
707
708 case Outline:
709 borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
710 borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
711 styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
712 colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
713 break;
714
715 default: continue;
716 }
717 hit = true;
718 }
719
720 return hit;
721}
722
723static Qt::Alignment parseAlignment(const QCss::Value *values, int count)
724{
725 Qt::Alignment a[2] = { { }, { } };
726 for (int i = 0; i < qMin(2, count); i++) {
727 if (values[i].type != Value::KnownIdentifier)
728 break;
729 switch (values[i].variant.toInt()) {
730 case Value_Left: a[i] = Qt::AlignLeft; break;
731 case Value_Right: a[i] = Qt::AlignRight; break;
732 case Value_Top: a[i] = Qt::AlignTop; break;
733 case Value_Bottom: a[i] = Qt::AlignBottom; break;
734 case Value_Center: a[i] = Qt::AlignCenter; break;
735 default: break;
736 }
737 }
738
739 if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter)
740 a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
741 if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter)
742 a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
743 return a[0] | a[1];
744}
745
747{
749 v.variant.convert(QMetaType::fromType<QColor>());
751 }
752
753 if (v.type == Value::Color)
754 return qvariant_cast<QColor>(v.variant);
755
756 if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
757 return QColor(Qt::transparent);
758
759 if (v.type != Value::Function)
760 return ColorData();
761
762 QStringList lst = v.variant.toStringList();
763 if (lst.size() != 2)
764 return ColorData();
765
766 const QString &identifier = lst.at(0);
767 if ((identifier.compare("palette"_L1, Qt::CaseInsensitive)) == 0) {
768 static_assert((Value_LastColorRole - Value_FirstColorRole + 1) == QPalette::ColorRole::NColorRoles);
769
770 int role = findKnownValue(lst.at(1).trimmed(), values);
771 if (role >= Value_FirstColorRole && role <= Value_LastColorRole)
772 return (QPalette::ColorRole)(role-Value_FirstColorRole);
773
774 return ColorData();
775 }
776
777 const bool rgb = identifier.startsWith("rgb"_L1);
778 const bool hsv = !rgb && identifier.startsWith("hsv"_L1);
779 const bool hsl = !rgb && !hsv && identifier.startsWith("hsl"_L1);
780
781 if (!rgb && !hsv && !hsl)
782 return ColorData();
783
784 const bool hasAlpha = identifier.size() == 4 && identifier.at(3) == u'a';
785 if (identifier.size() > 3 && !hasAlpha)
786 return ColorData();
787
788 Parser p(lst.at(1));
789 if (!p.testExpr())
790 return ColorData();
791
792 QList<QCss::Value> colorDigits;
793 if (!p.parseExpr(&colorDigits))
794 return ColorData();
795 const int tokenCount = colorDigits.size();
796
797 for (int i = 0; i < qMin(tokenCount, 7); i += 2) {
798 if (colorDigits.at(i).type == Value::Percentage) {
799 const qreal maxRange = (rgb || i != 0) ? 255. : 359.;
800 colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (maxRange / 100.);
801 colorDigits[i].type = Value::Number;
802 } else if (colorDigits.at(i).type != Value::Number) {
803 return ColorData();
804 }
805 }
806
807
808 if (tokenCount < 5)
809 return ColorData();
810
811 if (hasAlpha && tokenCount != 7) {
812 qWarning("QCssParser::parseColorValue: Specified color with alpha value but no alpha given: '%s'", qPrintable(lst.join(u' ')));
813 return ColorData();
814 }
815 if (!hasAlpha && tokenCount != 5) {
816 qWarning("QCssParser::parseColorValue: Specified color without alpha value but alpha given: '%s'", qPrintable(lst.join(u' ')));
817 return ColorData();
818 }
819
820 int v1 = colorDigits.at(0).variant.toInt();
821 int v2 = colorDigits.at(2).variant.toInt();
822 int v3 = colorDigits.at(4).variant.toInt();
823 int alpha = 255;
824 if (tokenCount == 7) {
825 int alphaValue = colorDigits.at(6).variant.toInt();
826 if (alphaValue <= 1)
827 alpha = colorDigits.at(6).variant.toReal() * 255.;
828 else
829 alpha = alphaValue;
830 }
831
832 if (rgb)
833 return QColor::fromRgb(v1, v2, v3, alpha);
834 if (hsv)
835 return QColor::fromHsv(v1, v2, v3, alpha);
836 return QColor::fromHsl(v1, v2, v3, alpha);
837}
838
839static QColor colorFromData(const ColorData& c, const QPalette &pal)
840{
841 if (c.type == ColorData::Color) {
842 return c.color;
843 } else if (c.type == ColorData::Role) {
844 return pal.color(c.role);
845 }
846 return QColor();
847}
848
849static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
850{
852 if (c.type == ColorData::Color) {
853 return QBrush(c.color);
854 } else if (c.type == ColorData::Role) {
855 return c.role;
856 }
857
858 if (v.type != Value::Function)
859 return BrushData();
860
861 QStringList lst = v.variant.toStringList();
862 if (lst.size() != 2)
863 return BrushData();
864
865 QStringList gradFuncs;
866 gradFuncs << "qlineargradient"_L1 << "qradialgradient"_L1 << "qconicalgradient"_L1 << "qgradient"_L1;
867 int gradType = -1;
868
869 if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1)
870 return BrushData();
871
872 QHash<QString, qreal> vars;
873 QList<QGradientStop> stops;
874
875 int spread = -1;
876 QStringList spreads;
877 spreads << "pad"_L1 << "reflect"_L1 << "repeat"_L1;
878
879 int coordinateMode = -1;
880 QStringList coordinateModes;
881 coordinateModes << "logical"_L1 << "stretchtodevice"_L1 << "objectbounding"_L1 << "object"_L1;
882
883 bool dependsOnThePalette = false;
884 Parser parser(lst.at(1));
885 while (parser.hasNext()) {
886 parser.skipSpace();
887 if (!parser.test(IDENT))
888 return BrushData();
889 QString attr = parser.lexem();
890 parser.skipSpace();
891 if (!parser.test(COLON))
892 return BrushData();
893 parser.skipSpace();
894 if (attr.compare("stop"_L1, Qt::CaseInsensitive) == 0) {
895 QCss::Value stop, color;
896 parser.next();
897 if (!parser.parseTerm(&stop)) return BrushData();
898 parser.skipSpace();
899 parser.next();
900 if (!parser.parseTerm(&color)) return BrushData();
902 if (cd.type == ColorData::Role)
903 dependsOnThePalette = true;
904 stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal)));
905 } else {
906 parser.next();
907 QCss::Value value;
908 (void)parser.parseTerm(&value);
909 if (attr.compare("spread"_L1, Qt::CaseInsensitive) == 0)
910 spread = spreads.indexOf(value.variant.toString());
911 else if (attr.compare("coordinatemode"_L1, Qt::CaseInsensitive) == 0)
912 coordinateMode = coordinateModes.indexOf(value.variant.toString());
913 else
914 vars[attr] = value.variant.toReal();
915 }
916 parser.skipSpace();
917 (void)parser.test(COMMA);
918 }
919
920 if (gradType == 0) {
921 QLinearGradient lg(vars.value("x1"_L1), vars.value("y1"_L1),
922 vars.value("x2"_L1), vars.value("y2"_L1));
923 lg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
924 lg.setStops(stops);
925 if (spread != -1)
926 lg.setSpread(QGradient::Spread(spread));
927 BrushData bd = QBrush(lg);
928 if (dependsOnThePalette)
930 return bd;
931 }
932
933 if (gradType == 1) {
934 QRadialGradient rg(vars.value("cx"_L1), vars.value("cy"_L1),
935 vars.value("radius"_L1), vars.value("fx"_L1),
936 vars.value("fy"_L1));
937 rg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
938 rg.setStops(stops);
939 if (spread != -1)
940 rg.setSpread(QGradient::Spread(spread));
941 BrushData bd = QBrush(rg);
942 if (dependsOnThePalette)
944 return bd;
945 }
946
947 if (gradType == 2) {
948 QConicalGradient cg(vars.value("cx"_L1), vars.value("cy"_L1), vars.value("angle"_L1));
949 cg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
950 cg.setStops(stops);
951 if (spread != -1)
952 cg.setSpread(QGradient::Spread(spread));
953 BrushData bd = QBrush(cg);
954 if (dependsOnThePalette)
956 return bd;
957 }
958
959 return BrushData();
960}
961
962static QBrush brushFromData(const BrushData& c, const QPalette &pal)
963{
964 if (c.type == BrushData::Role) {
965 return pal.color(c.role);
966 } else {
967 return c.brush;
968 }
969}
970
972{
974 switch (v.variant.toInt()) {
975 case Value_None:
976 return BorderStyle_None;
977 case Value_Dotted:
978 return BorderStyle_Dotted;
979 case Value_Dashed:
980 return BorderStyle_Dashed;
981 case Value_Solid:
982 return BorderStyle_Solid;
983 case Value_Double:
984 return BorderStyle_Double;
985 case Value_DotDash:
986 return BorderStyle_DotDash;
987 case Value_DotDotDash:
989 case Value_Groove:
990 return BorderStyle_Groove;
991 case Value_Ridge:
992 return BorderStyle_Ridge;
993 case Value_Inset:
994 return BorderStyle_Inset;
995 case Value_Outset:
996 return BorderStyle_Outset;
997 case Value_Native:
998 return BorderStyle_Native;
999 default:
1000 break;
1001 }
1002 }
1003
1004 return BorderStyle_Unknown;
1005}
1006
1007void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color)
1008{
1009 if (decl.d->parsed.isValid()) {
1010 BorderData data = qvariant_cast<BorderData>(decl.d->parsed);
1011 *width = lengthValueFromData(data.width, f);
1012 *style = data.style;
1013 *color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor());
1014 return;
1015 }
1016
1017 *width = 0;
1018 *style = BorderStyle_None;
1019 *color = QColor();
1020
1021 if (decl.d->values.isEmpty())
1022 return;
1023
1024 BorderData data;
1025 data.width.number = 0;
1026 data.width.unit = LengthData::None;
1027 data.style = BorderStyle_None;
1028
1029 int i = 0;
1030 if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number) {
1031 data.width = lengthValue(decl.d->values.at(i));
1032 *width = lengthValueFromData(data.width, f);
1033 if (++i >= decl.d->values.size()) {
1034 decl.d->parsed = QVariant::fromValue<BorderData>(data);
1035 return;
1036 }
1037 }
1038
1039 data.style = parseStyleValue(decl.d->values.at(i));
1040 if (data.style != BorderStyle_Unknown) {
1041 *style = data.style;
1042 if (++i >= decl.d->values.size()) {
1043 decl.d->parsed = QVariant::fromValue<BorderData>(data);
1044 return;
1045 }
1046 } else {
1047 data.style = BorderStyle_None;
1048 }
1049
1050 data.color = parseBrushValue(decl.d->values.at(i), pal);
1051 if (data.color.type != BrushData::Invalid) {
1052 *color = brushFromData(data.color, pal);
1053 if (data.color.type != BrushData::DependsOnThePalette)
1054 decl.d->parsed = QVariant::fromValue<BorderData>(data);
1055 }
1056}
1057
1058static void parseShorthandBackgroundProperty(const QList<QCss::Value> &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal)
1059{
1060 *brush = BrushData();
1061 *image = QString();
1062 *repeat = Repeat_XY;
1063 *alignment = Qt::AlignTop | Qt::AlignLeft;
1064
1065 for (qsizetype i = 0; i < values.size(); ++i) {
1066 const QCss::Value &v = values.at(i);
1067 if (v.type == Value::Uri) {
1068 *image = v.variant.toString();
1069 continue;
1070 } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None) {
1071 *image = QString();
1072 continue;
1073 } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) {
1074 *brush = QBrush(Qt::transparent);
1075 }
1076
1077 Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(v.variant.toString(),
1078 repeats));
1079 if (repeatAttempt != Repeat_Unknown) {
1080 *repeat = repeatAttempt;
1081 continue;
1082 }
1083
1084 if (v.type == Value::KnownIdentifier) {
1085 const int start = i;
1086 int count = 1;
1087 if (i < values.size() - 1
1088 && values.at(i + 1).type == Value::KnownIdentifier) {
1089 ++i;
1090 ++count;
1091 }
1092 Qt::Alignment a = parseAlignment(values.constData() + start, count);
1093 if (int(a) != 0) {
1094 *alignment = a;
1095 continue;
1096 }
1097 i -= count - 1;
1098 }
1099
1100 *brush = parseBrushValue(v, pal);
1101 }
1102}
1103
1104bool ValueExtractor::extractBackground(QBrush *brush, QString *image, Repeat *repeat,
1105 Qt::Alignment *alignment, Origin *origin, Attachment *attachment,
1106 Origin *clip) const
1107{
1108 bool hit = false;
1109 for (const Declaration &decl : declarations) {
1110 if (decl.d->values.isEmpty())
1111 continue;
1112 const QCss::Value &val = decl.d->values.at(0);
1113 switch (decl.d->propertyId) {
1114 case BackgroundColor:
1115 *brush = decl.brushValue();
1116 break;
1117 case BackgroundImage:
1118 if (val.type == Value::Uri)
1119 *image = val.variant.toString();
1120 break;
1121 case BackgroundRepeat:
1122 if (decl.d->parsed.isValid()) {
1123 *repeat = static_cast<Repeat>(decl.d->parsed.toInt());
1124 } else {
1125 *repeat = static_cast<Repeat>(findKnownValue(val.variant.toString(),
1126 repeats));
1127 decl.d->parsed = *repeat;
1128 }
1129 break;
1130 case BackgroundPosition:
1131 *alignment = decl.alignmentValue();
1132 break;
1133 case BackgroundOrigin:
1134 *origin = decl.originValue();
1135 break;
1136 case BackgroundClip:
1137 *clip = decl.originValue();
1138 break;
1139 case Background:
1140 if (decl.d->parsed.isValid()) {
1141 BackgroundData data = qvariant_cast<BackgroundData>(decl.d->parsed);
1142 *brush = brushFromData(data.brush, pal);
1143 *image = data.image;
1144 *repeat = data.repeat;
1145 *alignment = data.alignment;
1146 } else {
1147 BrushData brushData;
1148 parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal);
1149 *brush = brushFromData(brushData, pal);
1150 if (brushData.type != BrushData::DependsOnThePalette) {
1151 BackgroundData data = { brushData, *image, *repeat, *alignment };
1152 decl.d->parsed = QVariant::fromValue<BackgroundData>(data);
1153 }
1154 }
1155 break;
1156 case BackgroundAttachment:
1157 *attachment = decl.attachmentValue();
1158 break;
1159 default: continue;
1160 }
1161 hit = true;
1162 }
1163 return hit;
1164}
1165
1166static bool setFontSizeFromValue(QCss::Value value, QFont *font, int *fontSizeAdjustment)
1167{
1168 if (value.type == Value::KnownIdentifier) {
1169 bool valid = true;
1170 switch (value.variant.toInt()) {
1171 case Value_Small: *fontSizeAdjustment = -1; break;
1172 case Value_Medium: *fontSizeAdjustment = 0; break;
1173 case Value_Large: *fontSizeAdjustment = 1; break;
1174 case Value_XLarge: *fontSizeAdjustment = 2; break;
1175 case Value_XXLarge: *fontSizeAdjustment = 3; break;
1176 default: valid = false; break;
1177 }
1178 return valid;
1179 }
1180 if (value.type != Value::Length)
1181 return false;
1182
1183 bool valid = false;
1184 QString s = value.variant.toString();
1185 if (s.endsWith("pt"_L1, Qt::CaseInsensitive)) {
1186 s.chop(2);
1187 value.variant = std::move(s);
1188 if (value.variant.convert(QMetaType::fromType<qreal>())) {
1189 font->setPointSizeF(qBound(qreal(0), value.variant.toReal(), qreal(1 << 24) - 1));
1190 valid = true;
1191 }
1192 } else if (s.endsWith("px"_L1, Qt::CaseInsensitive)) {
1193 s.chop(2);
1194 value.variant = std::move(s);
1195 if (value.variant.convert(QMetaType::fromType<qreal>())) {
1196 font->setPixelSize(qBound(0, value.variant.toInt(), (1 << 24) - 1));
1197 valid = true;
1198 }
1199 }
1200 return valid;
1201}
1202
1203static bool setFontStyleFromValue(const QCss::Value &value, QFont *font)
1204{
1205 if (value.type != Value::KnownIdentifier)
1206 return false ;
1207 switch (value.variant.toInt()) {
1208 case Value_Normal: font->setStyle(QFont::StyleNormal); return true;
1209 case Value_Italic: font->setStyle(QFont::StyleItalic); return true;
1210 case Value_Oblique: font->setStyle(QFont::StyleOblique); return true;
1211 default: break;
1212 }
1213 return false;
1214}
1215
1216static bool setFontKerningFromValue(const QCss::Value &value, QFont *font)
1217{
1218 if (value.type != Value::KnownIdentifier)
1219 return false ;
1220 switch (value.variant.toInt()) {
1221 case Value_Normal: font->setKerning(true); return true;
1222 case Value_None: font->setKerning(false); return true;
1223 case Value_Auto: return true;
1224 default: break;
1225 }
1226 return false;
1227}
1228
1229static bool setFontWeightFromValue(const QCss::Value &value, QFont *font)
1230{
1231 if (value.type == Value::KnownIdentifier) {
1232 switch (value.variant.toInt()) {
1233 case Value_Normal: font->setWeight(QFont::Normal); return true;
1234 case Value_Bold: font->setWeight(QFont::Bold); return true;
1235 default: break;
1236 }
1237 return false;
1238 }
1239 if (value.type != Value::Number)
1240 return false;
1241 // .toInt() would call qRound64() and might overflow the long long there
1242 font->setWeight(QFont::Weight(qRound(qBound(0.0, value.variant.toDouble(), 1001.0))));
1243 return true;
1244}
1245
1246/** \internal
1247 * parse the font family from the values (starting from index \a start)
1248 * and set it the \a font
1249 * The function returns \c true if a family was extracted.
1250 */
1251static bool setFontFamilyFromValues(const QList<QCss::Value> &values, QFont *font, int start = 0)
1252{
1253 QString family;
1254 QStringList families;
1255 bool shouldAddSpace = false;
1256 for (qsizetype i = start; i < values.size(); ++i) {
1257 const QCss::Value &v = values.at(i);
1259 families << family;
1260 family.clear();
1261 shouldAddSpace = false;
1262 continue;
1263 }
1264 const QString str = v.variant.toString();
1265 if (str.isEmpty())
1266 break;
1267 if (shouldAddSpace)
1268 family += u' ';
1269 family += str;
1270 shouldAddSpace = true;
1271 }
1272 if (!family.isEmpty())
1273 families << family;
1274 if (families.isEmpty())
1275 return false;
1276 font->setFamilies(families);
1277 return true;
1278}
1279
1280static void setTextDecorationFromValues(const QList<QCss::Value> &values, QFont *font)
1281{
1282 for (const QCss::Value &value : values) {
1283 if (value.type != Value::KnownIdentifier)
1284 continue;
1285 switch (value.variant.toInt()) {
1286 case Value_Underline: font->setUnderline(true); break;
1287 case Value_Overline: font->setOverline(true); break;
1288 case Value_LineThrough: font->setStrikeOut(true); break;
1289 case Value_None:
1290 font->setUnderline(false);
1291 font->setOverline(false);
1292 font->setStrikeOut(false);
1293 break;
1294 default: break;
1295 }
1296 }
1297}
1298
1299static void setLetterSpacingFromValue(const QCss::Value &value, QFont *font)
1300{
1301 QString s = value.variant.toString();
1302 qreal val;
1303 bool ok = false;
1304 if (s.endsWith("em"_L1, Qt::CaseInsensitive)) {
1305 s.chop(2);
1306 val = s.toDouble(&ok);
1307 if (ok)
1308 font->setLetterSpacing(QFont::PercentageSpacing, (val + 1.0) * 100);
1309 } else if (s.endsWith("px"_L1, Qt::CaseInsensitive)) {
1310 s.chop(2);
1311 val = s.toDouble(&ok);
1312 if (ok)
1313 font->setLetterSpacing(QFont::AbsoluteSpacing, val);
1314 }
1315}
1316
1317static void setWordSpacingFromValue(const QCss::Value &value, QFont *font)
1318{
1319 QString s = value.variant.toString();
1320 if (s.endsWith("px"_L1, Qt::CaseInsensitive)) {
1321 s.chop(2);
1322 qreal val;
1323 bool ok = false;
1324 val = s.toDouble(&ok);
1325 if (ok)
1326 font->setWordSpacing(val);
1327 }
1328}
1329
1330static void parseShorthandFontProperty(const QList<QCss::Value> &values, QFont *font, int *fontSizeAdjustment)
1331{
1332 font->setStyle(QFont::StyleNormal);
1333 font->setWeight(QFont::Normal);
1334 *fontSizeAdjustment = -255;
1335
1336 int i = 0;
1337 while (i < values.size()) {
1338 if (setFontStyleFromValue(values.at(i), font)
1339 || setFontWeightFromValue(values.at(i), font))
1340 ++i;
1341 else
1342 break;
1343 }
1344
1345 if (i < values.size()) {
1346 setFontSizeFromValue(values.at(i), font, fontSizeAdjustment);
1347 ++i;
1348 }
1349
1350 if (i < values.size()) {
1351 setFontFamilyFromValues(values, font, i);
1352 }
1353}
1354
1355static void setFontVariantFromValue(const QCss::Value &value, QFont *font)
1356{
1357 if (value.type == Value::KnownIdentifier) {
1358 switch (value.variant.toInt()) {
1359 case Value_Normal: font->setCapitalization(QFont::MixedCase); break;
1360 case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break;
1361 default: break;
1362 }
1363 }
1364}
1365
1366static void setTextTransformFromValue(const QCss::Value &value, QFont *font)
1367{
1368 if (value.type == Value::KnownIdentifier) {
1369 switch (value.variant.toInt()) {
1370 case Value_None: font->setCapitalization(QFont::MixedCase); break;
1371 case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break;
1372 case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break;
1373 default: break;
1374 }
1375 }
1376}
1377
1378bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment)
1379{
1380 if (fontExtracted) {
1381 *font = f;
1382 *fontSizeAdjustment = adjustment;
1383 return fontExtracted == 1;
1384 }
1385
1386 bool hit = false;
1387 for (const Declaration &decl : std::as_const(declarations)) {
1388 if (decl.d->values.isEmpty())
1389 continue;
1390 const QCss::Value &val = decl.d->values.at(0);
1391 switch (decl.d->propertyId) {
1392 case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break;
1393 case FontStyle: setFontStyleFromValue(val, font); break;
1394 case FontWeight: setFontWeightFromValue(val, font); break;
1395 case FontFamily: setFontFamilyFromValues(decl.d->values, font); break;
1396 case FontKerning: setFontKerningFromValue(val, font); break;
1397 case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break;
1398 case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break;
1399 case FontVariant: setFontVariantFromValue(val, font); break;
1400 case TextTransform: setTextTransformFromValue(val, font); break;
1401 case LetterSpacing: setLetterSpacingFromValue(val, font); break;
1402 case WordSpacing: setWordSpacingFromValue(val, font); break;
1403 default: continue;
1404 }
1405 hit = true;
1406 }
1407
1408 f = *font;
1409 adjustment = *fontSizeAdjustment;
1410 fontExtracted = hit ? 1 : 2;
1411 return hit;
1412}
1413
1414bool ValueExtractor::extractPalette(QBrush *foreground,
1415 QBrush *selectedForeground,
1416 QBrush *selectedBackground,
1417 QBrush *alternateBackground,
1418 QBrush *placeHolderTextForeground,
1419 QBrush *accent) const
1420{
1421 bool hit = false;
1422 for (const Declaration &decl : declarations) {
1423 switch (decl.d->propertyId) {
1424 case Color: *foreground = decl.brushValue(pal); break;
1425 case QtSelectionForeground: *selectedForeground = decl.brushValue(pal); break;
1426 case QtSelectionBackground: *selectedBackground = decl.brushValue(pal); break;
1427 case QtAlternateBackground: *alternateBackground = decl.brushValue(pal); break;
1428 case QtPlaceHolderTextColor: *placeHolderTextForeground = decl.brushValue(pal); break;
1429 case QtAccent: *accent = decl.brushValue(pal); break;
1430 default: continue;
1431 }
1432 hit = true;
1433 }
1434 return hit;
1435}
1436
1437void ValueExtractor::extractFont()
1438{
1439 if (fontExtracted)
1440 return;
1441 int dummy = -255;
1442 extractFont(&f, &dummy);
1443}
1444
1445bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size) const
1446{
1447 bool hit = false;
1448 for (const Declaration &decl : declarations) {
1449 switch (decl.d->propertyId) {
1450 case QtImage:
1451 *icon = decl.iconValue();
1452 if (decl.d->values.size() > 0 && decl.d->values.at(0).type == Value::Uri) {
1453 // try to pull just the size from the image...
1454 QImageReader imageReader(decl.d->values.at(0).variant.toString());
1455 if ((*size = imageReader.size()).isNull()) {
1456 // but we'll have to load the whole image if the
1457 // format doesn't support just reading the size
1458 *size = imageReader.read().size();
1459 }
1460 }
1461 break;
1462 case QtImageAlignment: *a = decl.alignmentValue(); break;
1463 default: continue;
1464 }
1465 hit = true;
1466 }
1467 return hit;
1468}
1469
1470bool ValueExtractor::extractIcon(QIcon *icon, QSize *size) const
1471{
1472 // Find last declaration that specifies an icon
1473 const auto declaration = std::find_if(
1474 declarations.rbegin(), declarations.rend(),
1475 [](const Declaration &decl) { return decl.d->propertyId == QtIcon; });
1476 if (declaration == declarations.rend())
1477 return false;
1478
1479 *icon = declaration->iconValue();
1480
1481 // If the value contains a URI, try to get the size of the icon
1482 if (declaration->d->values.isEmpty())
1483 return true;
1484
1485 const auto &propertyValue = declaration->d->values.constFirst();
1486 if (propertyValue.type != Value::Uri)
1487 return true;
1488
1489 // First try to read just the size from the image without loading it
1490 const QString url(propertyValue.variant.toString());
1491 QImageReader imageReader(url);
1492 *size = imageReader.size();
1493 if (!size->isNull())
1494 return true;
1495
1496 // Get the size by loading the image instead
1497 *size = imageReader.read().size();
1498 return true;
1499}
1500
1501///////////////////////////////////////////////////////////////////////////////
1502// Declaration
1503QColor Declaration::colorValue(const QPalette &pal) const
1504{
1505 if (d->values.size() != 1)
1506 return QColor();
1507
1508 if (d->parsed.isValid()) {
1509 switch (d->parsed.typeId()) {
1510 case qMetaTypeId<QColor>():
1511 return qvariant_cast<QColor>(d->parsed);
1512 case qMetaTypeId<int>():
1513 return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
1514 case qMetaTypeId<QList<QVariant>>():
1515 if (d->parsed.toList().size() == 1) {
1516 auto parsedList = d->parsed.toList();
1517 const auto &value = parsedList.at(0);
1518 return qvariant_cast<QColor>(value);
1519 }
1520 break;
1521 }
1522 }
1523
1524 ColorData color = parseColorValue(d->values.at(0));
1525 if (color.type == ColorData::Role) {
1526 d->parsed = QVariant::fromValue<int>(color.role);
1527 return pal.color((QPalette::ColorRole)(color.role));
1528 } else {
1529 d->parsed = QVariant::fromValue<QColor>(color.color);
1530 return color.color;
1531 }
1532}
1533
1534QBrush Declaration::brushValue(const QPalette &pal) const
1535{
1536 if (d->values.size() != 1)
1537 return QBrush();
1538
1539 if (d->parsed.isValid()) {
1540 if (d->parsed.userType() == QMetaType::QBrush)
1541 return qvariant_cast<QBrush>(d->parsed);
1542 if (d->parsed.userType() == QMetaType::Int)
1543 return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
1544 }
1545
1546 BrushData data = parseBrushValue(d->values.at(0), pal);
1547
1548 if (data.type == BrushData::Role) {
1549 d->parsed = QVariant::fromValue<int>(data.role);
1550 return pal.color((QPalette::ColorRole)(data.role));
1551 } else {
1552 if (data.type != BrushData::DependsOnThePalette)
1553 d->parsed = QVariant::fromValue<QBrush>(data.brush);
1554 return data.brush;
1555 }
1556}
1557
1558void Declaration::brushValues(QBrush *c, const QPalette &pal) const
1559{
1560 int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value.
1561 // the bit 4 say we need to update d->parsed
1562 int i = 0;
1563 if (d->parsed.isValid()) {
1564 needParse = 0;
1565 Q_ASSERT(d->parsed.metaType() == QMetaType::fromType<QList<QVariant>>());
1566 QList<QVariant> v = d->parsed.toList();
1567 for (i = 0; i < qMin(v.size(), 4); i++) {
1568 if (v.at(i).userType() == QMetaType::QBrush) {
1569 c[i] = qvariant_cast<QBrush>(v.at(i));
1570 } else if (v.at(i).userType() == QMetaType::Int) {
1571 c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
1572 } else {
1573 needParse |= (1<<i);
1574 }
1575 }
1576 }
1577 if (needParse != 0) {
1578 QList<QVariant> v;
1579 for (i = 0; i < qMin(d->values.size(), 4); i++) {
1580 if (!(needParse & (1<<i)))
1581 continue;
1582 BrushData data = parseBrushValue(d->values.at(i), pal);
1583 if (data.type == BrushData::Role) {
1584 v += QVariant::fromValue<int>(data.role);
1585 c[i] = pal.color((QPalette::ColorRole)(data.role));
1586 } else {
1587 if (data.type != BrushData::DependsOnThePalette) {
1588 v += QVariant::fromValue<QBrush>(data.brush);
1589 } else {
1590 v += QVariant();
1591 }
1592 c[i] = data.brush;
1593 }
1594 }
1595 if (needParse & 0x10)
1596 d->parsed = std::move(v);
1597 }
1598 if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush();
1599 else if (i == 1) c[3] = c[2] = c[1] = c[0];
1600 else if (i == 2) c[2] = c[0], c[3] = c[1];
1601 else if (i == 3) c[3] = c[1];
1602}
1603
1604bool Declaration::realValue(qreal *real, const char *unit) const
1605{
1606 if (d->values.size() != 1)
1607 return false;
1608 const Value &v = d->values.at(0);
1609 if (unit && v.type != Value::Length)
1610 return false;
1611 const QString str = v.variant.toString();
1612 QStringView s(str);
1613 if (unit) {
1614 const QLatin1StringView unitStr(unit);
1615 if (!s.endsWith(unitStr, Qt::CaseInsensitive))
1616 return false;
1617 s.chop(unitStr.size());
1618 }
1619 bool ok = false;
1620 qreal val = s.toDouble(&ok);
1621 if (ok)
1622 *real = val;
1623 return ok;
1624}
1625
1626static bool intValueHelper(const QCss::Value &v, int *i, const char *unit)
1627{
1628 if (unit && v.type != Value::Length)
1629 return false;
1630 const QString str = v.variant.toString();
1631 QStringView s(str);
1632 if (unit) {
1633 const QLatin1StringView unitStr(unit);
1634 if (!s.endsWith(unitStr, Qt::CaseInsensitive))
1635 return false;
1636 s.chop(unitStr.size());
1637 }
1638 bool ok = false;
1639 int val = s.toInt(&ok);
1640 if (ok)
1641 *i = val;
1642 return ok;
1643}
1644
1645bool Declaration::intValue(int *i, const char *unit) const
1646{
1647 if (d->values.size() != 1)
1648 return false;
1649 return intValueHelper(d->values.at(0), i, unit);
1650}
1651
1652QSize Declaration::sizeValue() const
1653{
1654 if (d->parsed.isValid())
1655 return qvariant_cast<QSize>(d->parsed);
1656
1657 int x[2] = { 0, 0 };
1658 const qsizetype count = d->values.size();
1659 for (qsizetype i = 0; i < count; ++i) {
1660 if (i > 1) {
1661 qWarning("QCssParser::sizeValue: Too many values provided");
1662 break;
1663 }
1664 const auto &value = d->values.at(i);
1665 const QString valueString = value.variant.toString();
1666 if (valueString.endsWith(u"pt", Qt::CaseInsensitive)) {
1667 intValueHelper(value, &x[i], "pt");
1668 // according to https://www.w3.org/TR/css3-values/#absolute-lengths
1669 // 1pt = 1/72th of 1 inch, and 1px = 1/96th of 1 inch
1670 x[i] = (x[i] * 72) / 96;
1671 } else {
1672 // by default we use 'px'
1673 intValueHelper(value, &x[i], "px");
1674 }
1675 }
1676 if (count == 1)
1677 x[1] = x[0];
1678 QSize size(x[0], x[1]);
1679 d->parsed = QVariant::fromValue<QSize>(size);
1680 return size;
1681}
1682
1683QRect Declaration::rectValue() const
1684{
1685 if (d->values.size() != 1)
1686 return QRect();
1687
1688 if (d->parsed.isValid())
1689 return qvariant_cast<QRect>(d->parsed);
1690
1691 const QCss::Value &v = d->values.at(0);
1692 if (v.type != Value::Function)
1693 return QRect();
1694 const QStringList func = v.variant.toStringList();
1695 if (func.size() != 2 || func.at(0).compare("rect"_L1) != 0)
1696 return QRect();
1697 const auto args = QStringView{func[1]}.split(u' ', Qt::SkipEmptyParts);
1698 if (args.size() != 4)
1699 return QRect();
1700 QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt());
1701 d->parsed = QVariant::fromValue<QRect>(rect);
1702 return rect;
1703}
1704
1705void Declaration::colorValues(QColor *c, const QPalette &pal) const
1706{
1707 int i;
1708 if (d->parsed.isValid()) {
1709 QList<QVariant> v = d->parsed.toList();
1710 for (i = 0; i < qMin(d->values.size(), 4); i++) {
1711 if (v.at(i).userType() == QMetaType::QColor) {
1712 c[i] = qvariant_cast<QColor>(v.at(i));
1713 } else {
1714 c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
1715 }
1716 }
1717 } else {
1718 QList<QVariant> v;
1719 for (i = 0; i < qMin(d->values.size(), 4); i++) {
1720 ColorData color = parseColorValue(d->values.at(i));
1721 if (color.type == ColorData::Role) {
1722 v += QVariant::fromValue<int>(color.role);
1723 c[i] = pal.color((QPalette::ColorRole)(color.role));
1724 } else {
1725 v += QVariant::fromValue<QColor>(color.color);
1726 c[i] = color.color;
1727 }
1728 }
1729 d->parsed = std::move(v);
1730 }
1731
1732 if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor();
1733 else if (i == 1) c[3] = c[2] = c[1] = c[0];
1734 else if (i == 2) c[2] = c[0], c[3] = c[1];
1735 else if (i == 3) c[3] = c[1];
1736}
1737
1738BorderStyle Declaration::styleValue() const
1739{
1740 if (d->values.size() != 1)
1741 return BorderStyle_None;
1742 return parseStyleValue(d->values.at(0));
1743}
1744
1745void Declaration::styleValues(BorderStyle *s) const
1746{
1747 int i;
1748 for (i = 0; i < qMin(d->values.size(), 4); i++)
1749 s[i] = parseStyleValue(d->values.at(i));
1750 if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None;
1751 else if (i == 1) s[3] = s[2] = s[1] = s[0];
1752 else if (i == 2) s[2] = s[0], s[3] = s[1];
1753 else if (i == 3) s[3] = s[1];
1754}
1755
1756Repeat Declaration::repeatValue() const
1757{
1758 if (d->parsed.isValid())
1759 return static_cast<Repeat>(d->parsed.toInt());
1760 if (d->values.size() != 1)
1761 return Repeat_Unknown;
1762 int v = findKnownValue(d->values.at(0).variant.toString(),
1763 repeats);
1764 d->parsed = v;
1765 return static_cast<Repeat>(v);
1766}
1767
1768Origin Declaration::originValue() const
1769{
1770 if (d->parsed.isValid())
1771 return static_cast<Origin>(d->parsed.toInt());
1772 if (d->values.size() != 1)
1773 return Origin_Unknown;
1774 int v = findKnownValue(d->values.at(0).variant.toString(),
1775 origins);
1776 d->parsed = v;
1777 return static_cast<Origin>(v);
1778}
1779
1780PositionMode Declaration::positionValue() const
1781{
1782 if (d->parsed.isValid())
1783 return static_cast<PositionMode>(d->parsed.toInt());
1784 if (d->values.size() != 1)
1785 return PositionMode_Unknown;
1786 int v = findKnownValue(d->values.at(0).variant.toString(),
1787 positions);
1788 d->parsed = v;
1789 return static_cast<PositionMode>(v);
1790}
1791
1792Attachment Declaration::attachmentValue() const
1793{
1794 if (d->parsed.isValid())
1795 return static_cast<Attachment>(d->parsed.toInt());
1796 if (d->values.size() != 1)
1797 return Attachment_Unknown;
1798 int v = findKnownValue(d->values.at(0).variant.toString(),
1799 attachments);
1800 d->parsed = v;
1801 return static_cast<Attachment>(v);
1802}
1803
1804int Declaration::styleFeaturesValue() const
1805{
1806 Q_ASSERT(d->propertyId == QtStyleFeatures);
1807 if (d->parsed.isValid())
1808 return d->parsed.toInt();
1809 int features = StyleFeature_None;
1810 for (const Value &value : std::as_const(d->values)) {
1811 features |= static_cast<int>(findKnownValue(value.variant.toString(),
1812 styleFeatures));
1813 }
1814 d->parsed = features;
1815 return features;
1816}
1817
1818QString Declaration::uriValue() const
1819{
1820 if (d->values.isEmpty() || d->values.at(0).type != Value::Uri)
1821 return QString();
1822 return d->values.at(0).variant.toString();
1823}
1824
1825Qt::Alignment Declaration::alignmentValue() const
1826{
1827 if (d->parsed.isValid())
1828 return Qt::Alignment(d->parsed.toInt());
1829 if (d->values.isEmpty() || d->values.size() > 2)
1830 return Qt::AlignLeft | Qt::AlignTop;
1831
1832 Qt::Alignment v = parseAlignment(d->values.constData(), d->values.size());
1833 d->parsed = int(v);
1834 return v;
1835}
1836
1837void Declaration::borderImageValue(QString *image, int *cuts,
1838 TileMode *h, TileMode *v) const
1839{
1840 const DeclarationData *d = this->d.data(); // make it const and shadow d
1841 *image = uriValue();
1842 for (int i = 0; i < 4; i++)
1843 cuts[i] = -1;
1844 *h = *v = TileMode_Stretch;
1845
1846 if (d->values.size() < 2)
1847 return;
1848
1849 if (d->values.at(1).type == Value::Number) { // cuts!
1850 int i;
1851 for (i = 0; i < qMin(d->values.size()-1, 4); i++) {
1852 const Value& v = d->values.at(i+1);
1853 if (v.type != Value::Number)
1854 break;
1855 cuts[i] = v.variant.toString().toInt();
1856 if (cuts[i] < 0) {
1857 qWarning("Declaration::borderImageValue: Invalid cut value %d at position %d",
1858 cuts[i], i);
1859 cuts[0] = cuts[1] = cuts[2] = cuts[3] = -1;
1860 i = 4;
1861 break;
1862 }
1863 }
1864 if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0;
1865 else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0];
1866 else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1];
1867 else if (i == 3) cuts[3] = cuts[1];
1868 }
1869
1870 if (d->values.last().type == Value::Identifier) {
1871 *v = static_cast<TileMode>(findKnownValue(d->values.last().variant.toString(),
1872 tileModes));
1873 }
1874 if (d->values[d->values.size() - 2].type == Value::Identifier) {
1875 *h = static_cast<TileMode>
1876 (findKnownValue(d->values[d->values.size()-2].variant.toString(),
1877 tileModes));
1878 } else
1879 *h = *v;
1880}
1881
1882bool Declaration::borderCollapseValue() const
1883{
1884 if (d->values.size() != 1)
1885 return false;
1886 else
1887 return d->values.at(0).toString() == "collapse"_L1;
1888}
1889
1890QList<qreal> Declaration::dashArray() const
1891{
1892 if (d->propertyId != Property::QtStrokeDashArray || d->values.empty())
1893 return QList<qreal>();
1894
1895 bool isValid = true;
1896 QList<qreal> dashes;
1897 for (qsizetype i = 0; i < d->values.size(); i++) {
1898 const Value &v = d->values[i];
1899 // Separators must be at odd indices and Numbers at even indices.
1900 bool isValidSeparator = (i & 1) && v.type == Value::TermOperatorComma;
1901 bool isValidNumber = !(i & 1) && v.type == Value::Number;
1902 if (!isValidNumber && !isValidSeparator) {
1903 isValid = false;
1904 break;
1905 } else if (isValidNumber) {
1906 bool ok;
1907 dashes.append(v.variant.toReal(&ok));
1908 if (!ok) {
1909 isValid = false;
1910 break;
1911 }
1912 }
1913 }
1914
1915 isValid &= !(dashes.size() & 1);
1916 return isValid ? dashes : QList<qreal>();
1917}
1918
1919QIcon Declaration::iconValue() const
1920{
1921 if (d->parsed.isValid())
1922 return qvariant_cast<QIcon>(d->parsed);
1923
1924 QIcon icon;
1925 for (qsizetype i = 0; i < d->values.size();) {
1926 const Value &value = d->values.at(i++);
1927 if (value.type != Value::Uri)
1928 break;
1929 QString uri = value.variant.toString();
1930 QIcon::Mode mode = QIcon::Normal;
1931 QIcon::State state = QIcon::Off;
1932 for (int j = 0; j < 2; j++) {
1933 if (i != d->values.size() && d->values.at(i).type == Value::KnownIdentifier) {
1934 switch (d->values.at(i).variant.toInt()) {
1935 case Value_Disabled: mode = QIcon::Disabled; break;
1936 case Value_Active: mode = QIcon::Active; break;
1937 case Value_Selected: mode = QIcon::Selected; break;
1938 case Value_Normal: mode = QIcon::Normal; break;
1939 case Value_On: state = QIcon::On; break;
1940 case Value_Off: state = QIcon::Off; break;
1941 default: break;
1942 }
1943 ++i;
1944 } else {
1945 break;
1946 }
1947 }
1948
1949 // QIcon is soo broken
1950 if (icon.isNull())
1951 icon = QIcon(uri);
1952 else
1953 icon.addPixmap(uri, mode, state);
1954
1955 if (i == d->values.size())
1956 break;
1957
1958 if (d->values.at(i).type == Value::TermOperatorComma)
1959 i++;
1960 }
1961
1962 d->parsed = QVariant::fromValue<QIcon>(icon);
1963 return icon;
1964}
1965
1966///////////////////////////////////////////////////////////////////////////////
1967// Selector
1968int Selector::specificity() const
1969{
1970 int val = 0;
1971 for (const BasicSelector &sel : basicSelectors) {
1972 if (!sel.elementName.isEmpty())
1973 val += 1;
1974
1975 val += (sel.pseudos.size() + sel.attributeSelectors.size()) * 0x10;
1976 val += sel.ids.size() * 0x100;
1977 }
1978 return val;
1979}
1980
1981QString Selector::pseudoElement() const
1982{
1983 const BasicSelector& bs = basicSelectors.last();
1984 if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown)
1985 return bs.pseudos.at(0).name;
1986 return QString();
1987}
1988
1989quint64 Selector::pseudoClass(quint64 *negated) const
1990{
1991 const BasicSelector& bs = basicSelectors.last();
1992 if (bs.pseudos.isEmpty())
1993 return PseudoClass_Unspecified;
1994 quint64 pc = PseudoClass_Unknown;
1995 for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.size(); i++) {
1996 const Pseudo &pseudo = bs.pseudos.at(i);
1997 if (pseudo.type == PseudoClass_Unknown)
1998 return PseudoClass_Unknown;
1999 if (!pseudo.negated)
2000 pc |= pseudo.type;
2001 else if (negated)
2002 *negated |= pseudo.type;
2003 }
2004 return pc;
2005}
2006
2007///////////////////////////////////////////////////////////////////////////////
2008// StyleSheet
2009void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity)
2010{
2011 QList<StyleRule> universals;
2012 for (qsizetype i = 0; i < styleRules.size(); ++i) {
2013 const StyleRule &rule = styleRules.at(i);
2014 QList<Selector> universalsSelectors;
2015 for (const Selector &selector : rule.selectors) {
2016
2017 if (selector.basicSelectors.isEmpty())
2018 continue;
2019
2020 if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) {
2021 if (selector.basicSelectors.size() != 1)
2022 continue;
2023 } else if (selector.basicSelectors.size() <= 1) {
2024 continue;
2025 }
2026
2027 const BasicSelector &sel = selector.basicSelectors.at(selector.basicSelectors.size() - 1);
2028
2029 if (!sel.ids.isEmpty()) {
2030 StyleRule nr;
2031 nr.selectors += selector;
2032 nr.declarations = rule.declarations;
2033 nr.order = i;
2034 idIndex.insert(sel.ids.at(0), nr);
2035 } else if (!sel.elementName.isEmpty()) {
2036 StyleRule nr;
2037 nr.selectors += selector;
2038 nr.declarations = rule.declarations;
2039 nr.order = i;
2040 QString name = sel.elementName;
2041 if (nameCaseSensitivity == Qt::CaseInsensitive)
2042 name = std::move(name).toLower();
2043 nameIndex.insert(name, nr);
2044 } else {
2045 universalsSelectors += selector;
2046 }
2047 }
2048 if (!universalsSelectors.isEmpty()) {
2049 StyleRule nr;
2050 nr.selectors = universalsSelectors;
2051 nr.declarations = rule.declarations;
2052 nr.order = i;
2053 universals << nr;
2054 }
2055 }
2056 styleRules = universals;
2057}
2058
2059///////////////////////////////////////////////////////////////////////////////
2060// StyleSelector
2061StyleSelector::~StyleSelector()
2062{
2063}
2064
2065bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
2066{
2067 return nodeNames(node).contains(nodeName, nameCaseSensitivity);
2068}
2069
2070QStringList StyleSelector::nodeIds(NodePtr node) const
2071{
2072 return QStringList(attributeValue(node, QCss::AttributeSelector{"id"_L1, {}, AttributeSelector::NoMatch}));
2073}
2074
2075bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node) const
2076{
2077 if (selector.basicSelectors.isEmpty())
2078 return false;
2079
2080 if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) {
2081 if (selector.basicSelectors.size() != 1)
2082 return false;
2083 return basicSelectorMatches(selector.basicSelectors.at(0), node);
2084 }
2085 if (selector.basicSelectors.size() <= 1)
2086 return false;
2087
2088 qsizetype i = selector.basicSelectors.size() - 1;
2089 node = duplicateNode(node);
2090 bool match = true;
2091
2092 BasicSelector sel = selector.basicSelectors.at(i);
2093 do {
2094 match = basicSelectorMatches(sel, node);
2095 if (!match) {
2096 if (i == selector.basicSelectors.size() - 1) // first element must always match!
2097 break;
2098 if (sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor &&
2099 sel.relationToNext != BasicSelector::MatchNextSelectorIfIndirectAdjecent)
2100 break;
2101 }
2102
2103 if (match || (sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor &&
2104 sel.relationToNext != BasicSelector::MatchNextSelectorIfIndirectAdjecent))
2105 --i;
2106
2107 if (i < 0)
2108 break;
2109
2110 sel = selector.basicSelectors.at(i);
2111 if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
2112 || sel.relationToNext == BasicSelector::MatchNextSelectorIfParent) {
2113
2114 NodePtr nextParent = parentNode(node);
2115 freeNode(node);
2116 node = nextParent;
2117 } else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfDirectAdjecent
2118 || sel.relationToNext == BasicSelector::MatchNextSelectorIfIndirectAdjecent) {
2119 NodePtr previousSibling = previousSiblingNode(node);
2120 freeNode(node);
2121 node = previousSibling;
2122 }
2123 if (isNullNode(node)) {
2124 match = false;
2125 break;
2126 }
2127 } while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
2128 || sel.relationToNext == BasicSelector::MatchNextSelectorIfIndirectAdjecent));
2129
2130 freeNode(node);
2131
2132 return match;
2133}
2134
2135bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node) const
2136{
2137 if (!sel.attributeSelectors.isEmpty()) {
2138 if (!hasAttributes(node))
2139 return false;
2140
2141 for (const QCss::AttributeSelector &a : sel.attributeSelectors) {
2142
2143 const QString attrValue = attributeValue(node, a);
2144 if (attrValue.isNull())
2145 return false;
2146
2147 switch (a.valueMatchCriterium) {
2148 case QCss::AttributeSelector::NoMatch:
2149 break;
2150 case QCss::AttributeSelector::MatchEqual:
2151 if (attrValue != a.value)
2152 return false;
2153 break;
2154 case QCss::AttributeSelector::MatchIncludes: {
2155 const auto lst = QStringView{attrValue}.tokenize(u' ');
2156 bool found = false;
2157 for (auto s : lst) {
2158 if (s == a.value) {
2159 found = true;
2160 break;
2161 }
2162 }
2163 if (!found)
2164 return false;
2165 break;
2166 }
2167 case QCss::AttributeSelector::MatchDashMatch: {
2168 const QString dashPrefix = a.value + u'-';
2169 if (attrValue != a.value && !attrValue.startsWith(dashPrefix))
2170 return false;
2171 break;
2172 }
2173 case QCss::AttributeSelector::MatchBeginsWith:
2174 if (!attrValue.startsWith(a.value))
2175 return false;
2176 break;
2177 case QCss::AttributeSelector::MatchEndsWith:
2178 if (!attrValue.endsWith(a.value))
2179 return false;
2180 break;
2181 case QCss::AttributeSelector::MatchContains:
2182 if (!attrValue.contains(a.value))
2183 return false;
2184 break;
2185 }
2186 }
2187 }
2188
2189 if (!sel.elementName.isEmpty()
2190 && !nodeNameEquals(node, sel.elementName))
2191 return false;
2192
2193 if (!sel.ids.isEmpty()
2194 && sel.ids != nodeIds(node))
2195 return false;
2196
2197 return true;
2198}
2199
2200void StyleSelector::matchRule(NodePtr node, const StyleRule &rule, StyleSheetOrigin origin,
2201 int depth, std::multimap<uint64_t, StyleRule> *weightedRules) const
2202{
2203 for (const Selector &selector : rule.selectors) {
2204 if (selectorMatches(selector, node)) {
2205 if (uint32_t(rule.order) >= 0x100000ULL)
2206 qWarning("StyleSelector: rule order is to large - style sheet"
2207 "must not contain more than 0x100000 entries");
2208 if (uint32_t(selector.specificity()) >= 0x100000ULL)
2209 qWarning("StyleSelector: selector specifity is to large - "
2210 "style sheet must not contain more than 0x100000 selectors "
2211 "per widget type");
2212 const uint64_t weight = rule.order
2213 + selector.specificity() * 0x100000ULL
2214 + (uint(origin) + depth) * 0x10000000000ULL;
2215 StyleRule newRule = rule;
2216 if (rule.selectors.size() > 1)
2217 newRule.selectors.push_back(selector);
2218
2219 //We might have rules with the same weight if they came from a rule with several selectors
2220 weightedRules->emplace(weight, std::move(newRule));
2221 }
2222 }
2223}
2224
2225// Returns style rules that are in ascending order of specificity
2226// Each of the StyleRule returned will contain exactly one Selector
2227QList<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
2228{
2229 QList<StyleRule> rules;
2230 if (styleSheets.isEmpty())
2231 return rules;
2232
2233 std::multimap<uint64_t, StyleRule> weightedRules; // (spec, rule) that will be sorted below
2234
2235 //prune using indexed stylesheet
2236 for (const StyleSheet &styleSheet : std::as_const(styleSheets)) {
2237 for (const StyleRule &styleRule : styleSheet.styleRules)
2238 matchRule(node, styleRule, styleSheet.origin, styleSheet.depth, &weightedRules);
2239
2240 if (!styleSheet.idIndex.isEmpty()) {
2241 const QStringList ids = nodeIds(node);
2242 for (const QString &key : ids) {
2243 auto it = styleSheet.idIndex.constFind(key);
2244 while (it != styleSheet.idIndex.constEnd() && it.key() == key) {
2245 matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
2246 ++it;
2247 }
2248 }
2249 }
2250 if (!styleSheet.nameIndex.isEmpty()) {
2251 const QStringList names = nodeNames(node);
2252 for (QString name : names) {
2253 if (nameCaseSensitivity == Qt::CaseInsensitive)
2254 name = std::move(name).toLower();
2255 auto it = styleSheet.nameIndex.constFind(name);
2256 while (it != styleSheet.nameIndex.constEnd() && it.key() == name) {
2257 matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
2258 ++it;
2259 }
2260 }
2261 }
2262 if (!medium.isEmpty()) {
2263 for (const MediaRule &mediaRule : styleSheet.mediaRules) {
2264 if (mediaRule.media.contains(medium, Qt::CaseInsensitive)) {
2265 for (const StyleRule &styleRule : mediaRule.styleRules) {
2266 matchRule(node, styleRule, styleSheet.origin,
2267 styleSheet.depth, &weightedRules);
2268 }
2269 }
2270 }
2271 }
2272 }
2273
2274 rules.reserve(weightedRules.size());
2275 for (const auto &weigthedRule : weightedRules)
2276 rules.push_back(std::move(weigthedRule.second));
2277
2278 return rules;
2279}
2280
2281// for qtexthtmlparser which requires just the declarations with Enabled state
2282// and without pseudo elements
2283QList<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo)
2284{
2285 QList<Declaration> decls;
2286 const QList<StyleRule> rules = styleRulesForNode(node);
2287 for (const StyleRule &styleRule : rules) {
2288 const Selector &selector = styleRule.selectors.at(0);
2289 const QString pseudoElement = selector.pseudoElement();
2290
2291 if (extraPseudo && pseudoElement == QLatin1StringView(extraPseudo)) {
2292 decls += styleRule.declarations;
2293 continue;
2294 }
2295
2296 if (!pseudoElement.isEmpty()) // skip rules with pseudo elements
2297 continue;
2298 quint64 pseudoClass = selector.pseudoClass();
2299 if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified)
2300 decls += styleRule.declarations;
2301 }
2302 return decls;
2303}
2304
2305static inline bool isHexDigit(const char c)
2306{
2307 return (c >= '0' && c <= '9')
2308 || (c >= 'a' && c <= 'f')
2309 || (c >= 'A' && c <= 'F')
2310 ;
2311}
2312
2313QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences)
2314{
2315 QString output = input;
2316
2317 if (hasEscapeSequences)
2318 *hasEscapeSequences = false;
2319
2320 int i = 0;
2321 while (i < output.size()) {
2322 if (output.at(i) == u'\\') {
2323
2324 ++i;
2325 // test for unicode hex escape
2326 int hexCount = 0;
2327 const int hexStart = i;
2328 while (i < output.size()
2329 && isHexDigit(output.at(i).toLatin1())
2330 && hexCount < 7) {
2331 ++hexCount;
2332 ++i;
2333 }
2334 if (hexCount == 0) {
2335 if (hasEscapeSequences)
2336 *hasEscapeSequences = true;
2337 continue;
2338 }
2339
2340 hexCount = qMin(hexCount, 6);
2341 bool ok = false;
2342 const char16_t code = QStringView{output}.mid(hexStart, hexCount).toUShort(&ok, 16);
2343 if (ok) {
2344 output.replace(hexStart - 1, hexCount + 1, code);
2345 i = hexStart;
2346 } else {
2347 i = hexStart;
2348 }
2349 } else {
2350 ++i;
2351 }
2352 }
2353 return output;
2354}
2355
2356int QCssScanner_Generated::handleCommentStart()
2357{
2358 while (pos < input.size() - 1) {
2359 if (input.at(pos) == u'*' && input.at(pos + 1) == u'/') {
2360 pos += 2;
2361 break;
2362 }
2363 ++pos;
2364 }
2365 return S;
2366}
2367
2368void Scanner::scan(const QString &preprocessedInput, QList<Symbol> *symbols)
2369{
2370 QCssScanner_Generated scanner(preprocessedInput);
2371 Symbol sym;
2372 int tok = scanner.lex();
2373 while (tok != -1) {
2374 sym.token = static_cast<QCss::TokenType>(tok);
2375 sym.text = scanner.input;
2376 sym.start = scanner.lexemStart;
2377 sym.len = scanner.lexemLength;
2378 symbols->append(sym);
2379 tok = scanner.lex();
2380 }
2381}
2382
2383QString Symbol::lexem() const
2384{
2385 QString result;
2386 if (len > 0)
2387 result.reserve(len);
2388 for (int i = 0; i < len; ++i) {
2389 if (text.at(start + i) == u'\\' && i < len - 1)
2390 ++i;
2391 result += text.at(start + i);
2392 }
2393 return result;
2394}
2395
2396Parser::Parser(const QString &css, bool isFile)
2397{
2398 init(css, isFile);
2399}
2400
2401Parser::Parser()
2402{
2403 index = 0;
2404 errorIndex = -1;
2405 hasEscapeSequences = false;
2406}
2407
2408void Parser::init(const QString &css, bool isFile)
2409{
2410 QString styleSheet = css;
2411 if (isFile) {
2412 QFile file(css);
2413 if (file.open(QFile::ReadOnly)) {
2414 sourcePath = QFileInfo(styleSheet).absolutePath() + u'/';
2415 QTextStream stream(&file);
2416 styleSheet = stream.readAll();
2417 } else {
2418 qWarning() << "QCss::Parser - Failed to load file " << css;
2419 styleSheet.clear();
2420 }
2421 } else {
2422 sourcePath.clear();
2423 }
2424
2425 hasEscapeSequences = false;
2426 symbols.clear();
2427 symbols.reserve(8);
2428 Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols);
2429 index = 0;
2430 errorIndex = -1;
2431}
2432
2433bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
2434{
2435 if (testTokenAndEndsWith(ATKEYWORD_SYM, "charset"_L1)) {
2436 while (test(S) || test(CDO) || test(CDC)) {}
2437 if (!next(STRING)) return false;
2438 if (!next(SEMICOLON)) return false;
2439 }
2440
2441 while (test(S) || test(CDO) || test(CDC)) {}
2442
2443 while (testImport()) {
2444 ImportRule rule;
2445 if (!parseImport(&rule)) return false;
2446 styleSheet->importRules.append(rule);
2447 while (test(S) || test(CDO) || test(CDC)) {}
2448 }
2449
2450 do {
2451 if (testMedia()) {
2452 MediaRule rule;
2453 if (!parseMedia(&rule)) return false;
2454 styleSheet->mediaRules.append(rule);
2455 } else if (testPage()) {
2456 PageRule rule;
2457 if (!parsePage(&rule)) return false;
2458 styleSheet->pageRules.append(rule);
2459 } else if (testAnimation()) {
2460 AnimationRule rule;
2461 if (!parseAnimation(&rule)) return false;
2462 styleSheet->animationRules.append(rule);
2463 } else if (testRuleset()) {
2464 StyleRule rule;
2465 if (!parseRuleset(&rule)) return false;
2466 styleSheet->styleRules.append(rule);
2467 } else if (test(ATKEYWORD_SYM)) {
2468 if (!until(RBRACE)) return false;
2469 } else if (hasNext()) {
2470 return false;
2471 }
2472 while (test(S) || test(CDO) || test(CDC)) {}
2473 } while (hasNext());
2474 styleSheet->buildIndexes(nameCaseSensitivity);
2475 return true;
2476}
2477
2478Symbol Parser::errorSymbol()
2479{
2480 if (errorIndex == -1) return Symbol();
2481 return symbols.at(errorIndex);
2482}
2483
2484static inline void removeOptionalQuotes(QString *str)
2485{
2486 if (!str->startsWith(u'\'') && !str->startsWith(u'\"'))
2487 return;
2488 str->remove(0, 1);
2489 str->chop(1);
2490}
2491
2492bool Parser::parseImport(ImportRule *importRule)
2493{
2494 skipSpace();
2495
2496 if (test(STRING)) {
2497 importRule->href = lexem();
2498 } else {
2499 if (!testAndParseUri(&importRule->href)) return false;
2500 }
2501 removeOptionalQuotes(&importRule->href);
2502
2503 skipSpace();
2504
2505 if (testMedium()) {
2506 if (!parseMedium(&importRule->media)) return false;
2507
2508 while (test(COMMA)) {
2509 skipSpace();
2510 if (!parseNextMedium(&importRule->media)) return false;
2511 }
2512 }
2513
2514 if (!next(SEMICOLON)) return false;
2515
2516 skipSpace();
2517 return true;
2518}
2519
2520bool Parser::parseMedia(MediaRule *mediaRule)
2521{
2522 do {
2523 skipSpace();
2524 if (!parseNextMedium(&mediaRule->media)) return false;
2525 } while (test(COMMA));
2526
2527 if (!next(LBRACE)) return false;
2528 skipSpace();
2529
2530 while (testRuleset()) {
2531 StyleRule rule;
2532 if (!parseRuleset(&rule)) return false;
2533 mediaRule->styleRules.append(rule);
2534 }
2535
2536 if (!next(RBRACE)) return false;
2537 skipSpace();
2538 return true;
2539}
2540
2541bool Parser::parseMedium(QStringList *media)
2542{
2543 media->append(lexem());
2544 skipSpace();
2545 return true;
2546}
2547
2548bool Parser::parsePage(PageRule *pageRule)
2549{
2550 skipSpace();
2551
2552 if (testPseudoPage())
2553 if (!parsePseudoPage(&pageRule->selector)) return false;
2554
2555 skipSpace();
2556 if (!next(LBRACE)) return false;
2557
2558 do {
2559 skipSpace();
2560 Declaration decl;
2561 if (!parseNextDeclaration(&decl)) return false;
2562 if (!decl.isEmpty())
2563 pageRule->declarations.append(decl);
2564 } while (test(SEMICOLON));
2565
2566 if (!next(RBRACE)) return false;
2567 skipSpace();
2568 return true;
2569}
2570
2571bool Parser::parsePseudoPage(QString *selector)
2572{
2573 if (!next(IDENT)) return false;
2574 *selector = lexem();
2575 return true;
2576}
2577
2578bool Parser::parseNextOperator(Value *value)
2579{
2580 if (!hasNext()) return true;
2581 switch (next()) {
2582 case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break;
2583 case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break;
2584 default: prev(); break;
2585 }
2586 return true;
2587}
2588
2589bool Parser::parseAnimation(AnimationRule *animationRule)
2590{
2591 skipSpace();
2592 if (!test(IDENT)) return false;
2593
2594 animationRule->animName = lexem();
2595
2596 if (!next(LBRACE)) return false;
2597 skipSpace();
2598
2599 while (test(PERCENTAGE) || test(IDENT)) {
2600 AnimationRule::AnimationRuleSet set;
2601 if (lookup() == PERCENTAGE) {
2602 QString name = lexem();
2603 name.removeLast();
2604 float keyFrame = name.toFloat() / 100;
2605 set.keyFrame = keyFrame;
2606 } else if (lookup() == IDENT) {
2607 QString name;
2608 if (parseElementName(&name)) {
2609 if (name == QStringLiteral("from"))
2610 set.keyFrame = 0;
2611 else if (name == QStringLiteral("to"))
2612 set.keyFrame = 1;
2613 }
2614 }
2615
2616 skipSpace();
2617 if (!next(LBRACE)) return false;
2618 const int declarationStart = index;
2619
2620 do {
2621 skipSpace();
2622 Declaration decl;
2623 const int rewind = index;
2624 if (!parseNextDeclaration(&decl)) {
2625 index = rewind;
2626 const bool foundSemicolon = until(SEMICOLON);
2627 const int semicolonIndex = index;
2628
2629 index = declarationStart;
2630 const bool foundRBrace = until(RBRACE);
2631
2632 if (foundSemicolon && semicolonIndex < index) {
2633 decl = Declaration();
2634 index = semicolonIndex - 1;
2635 } else {
2636 skipSpace();
2637 return foundRBrace;
2638 }
2639 }
2640 if (!decl.isEmpty()) {
2641 if (decl.d->property == "animation-timing-function"_L1)
2642 set.timingFunction = std::move(decl);
2643 else
2644 set.declarations.push_back(std::move(decl));
2645 }
2646 } while (test(SEMICOLON));
2647
2648 if (!next(RBRACE)) return false;
2649 skipSpace();
2650 animationRule->ruleSets.append(set);
2651 }
2652
2653 if (!next(RBRACE)) return false;
2654 skipSpace();
2655
2656 return true;
2657}
2658
2659bool Parser::parseCombinator(BasicSelector::Relation *relation)
2660{
2661 *relation = BasicSelector::NoRelation;
2662 if (lookup() == S) {
2663 *relation = BasicSelector::MatchNextSelectorIfAncestor;
2664 skipSpace();
2665 } else {
2666 prev();
2667 }
2668 if (test(PLUS)) {
2669 *relation = BasicSelector::MatchNextSelectorIfDirectAdjecent;
2670 } else if (test(GREATER)) {
2671 *relation = BasicSelector::MatchNextSelectorIfParent;
2672 } else if (test(TILDE)) {
2673 *relation = BasicSelector::MatchNextSelectorIfIndirectAdjecent;
2674 }
2675 skipSpace();
2676 return true;
2677}
2678
2679bool Parser::parseProperty(Declaration *decl)
2680{
2681 decl->d->property = lexem();
2682 decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties));
2683 decl->d->inheritable = isInheritable(decl->d->propertyId);
2684 skipSpace();
2685 return true;
2686}
2687
2688bool Parser::parseRuleset(StyleRule *styleRule)
2689{
2690 Selector sel;
2691 if (!parseSelector(&sel)) return false;
2692 styleRule->selectors.append(sel);
2693
2694 while (test(COMMA)) {
2695 skipSpace();
2696 Selector sel;
2697 if (!parseNextSelector(&sel)) return false;
2698 styleRule->selectors.append(sel);
2699 }
2700
2701 skipSpace();
2702 if (!next(LBRACE)) return false;
2703 const int declarationStart = index;
2704
2705 do {
2706 skipSpace();
2707 Declaration decl;
2708 const int rewind = index;
2709 if (!parseNextDeclaration(&decl)) {
2710 index = rewind;
2711 const bool foundSemicolon = until(SEMICOLON);
2712 const int semicolonIndex = index;
2713
2714 index = declarationStart;
2715 const bool foundRBrace = until(RBRACE);
2716
2717 if (foundSemicolon && semicolonIndex < index) {
2718 decl = Declaration();
2719 index = semicolonIndex - 1;
2720 } else {
2721 skipSpace();
2722 return foundRBrace;
2723 }
2724 }
2725 if (!decl.isEmpty())
2726 styleRule->declarations.append(decl);
2727 } while (test(SEMICOLON));
2728
2729 if (!next(RBRACE)) return false;
2730 skipSpace();
2731 return true;
2732}
2733
2734bool Parser::parseSelector(Selector *sel)
2735{
2736 BasicSelector basicSel;
2737 if (!parseSimpleSelector(&basicSel)) return false;
2738 while (testCombinator()) {
2739 if (!parseCombinator(&basicSel.relationToNext)) return false;
2740
2741 if (!testSimpleSelector()) break;
2742 sel->basicSelectors.append(basicSel);
2743
2744 basicSel = BasicSelector();
2745 if (!parseSimpleSelector(&basicSel)) return false;
2746 }
2747 sel->basicSelectors.append(basicSel);
2748 return true;
2749}
2750
2751bool Parser::parseSimpleSelector(BasicSelector *basicSel)
2752{
2753 int minCount = 0;
2754 if (lookupElementName()) {
2755 if (!parseElementName(&basicSel->elementName)) return false;
2756 } else {
2757 prev();
2758 minCount = 1;
2759 }
2760 bool onceMore;
2761 int count = 0;
2762 do {
2763 onceMore = false;
2764 if (test(HASH)) {
2765 QString theid = lexem();
2766 // chop off leading #
2767 theid.remove(0, 1);
2768 basicSel->ids.append(theid);
2769 onceMore = true;
2770 } else if (testClass()) {
2771 onceMore = true;
2772 AttributeSelector a;
2773 a.name = "class"_L1;
2774 a.valueMatchCriterium = AttributeSelector::MatchIncludes;
2775 if (!parseClass(&a.value)) return false;
2776 basicSel->attributeSelectors.append(a);
2777 } else if (testAttrib()) {
2778 onceMore = true;
2779 AttributeSelector a;
2780 if (!parseAttrib(&a)) return false;
2781 basicSel->attributeSelectors.append(a);
2782 } else if (testPseudo()) {
2783 onceMore = true;
2784 Pseudo ps;
2785 if (!parsePseudo(&ps)) return false;
2786 basicSel->pseudos.append(ps);
2787 }
2788 if (onceMore) ++count;
2789 } while (onceMore);
2790 return count >= minCount;
2791}
2792
2793bool Parser::parseClass(QString *name)
2794{
2795 if (!next(IDENT)) return false;
2796 *name = lexem();
2797 return true;
2798}
2799
2800bool Parser::parseElementName(QString *name)
2801{
2802 switch (lookup()) {
2803 case STAR: name->clear(); break;
2804 case IDENT: *name = lexem(); break;
2805 default: return false;
2806 }
2807 return true;
2808}
2809
2810bool Parser::parseAttrib(AttributeSelector *attr)
2811{
2812 skipSpace();
2813 if (!next(IDENT)) return false;
2814 attr->name = lexem();
2815 skipSpace();
2816
2817 if (test(EQUAL)) {
2818 attr->valueMatchCriterium = AttributeSelector::MatchEqual;
2819 } else if (test(INCLUDES)) {
2820 attr->valueMatchCriterium = AttributeSelector::MatchIncludes;
2821 } else if (test(DASHMATCH)) {
2822 attr->valueMatchCriterium = AttributeSelector::MatchDashMatch;
2823 } else if (test(BEGINSWITH)) {
2824 attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
2825 } else if (test(ENDSWITH)) {
2826 attr->valueMatchCriterium = AttributeSelector::MatchEndsWith;
2827 } else if (test(CONTAINS)) {
2828 attr->valueMatchCriterium = AttributeSelector::MatchContains;
2829 } else {
2830 return next(RBRACKET);
2831 }
2832
2833 skipSpace();
2834
2835 if (!test(IDENT) && !test(STRING)) return false;
2836 attr->value = unquotedLexem();
2837
2838 skipSpace();
2839 return next(RBRACKET);
2840}
2841
2842bool Parser::parsePseudo(Pseudo *pseudo)
2843{
2844 (void)test(COLON);
2845 pseudo->negated = test(EXCLAMATION_SYM);
2846 if (test(IDENT)) {
2847 pseudo->name = lexem();
2848 pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos));
2849 return true;
2850 }
2851 if (!next(FUNCTION)) return false;
2852 pseudo->function = lexem();
2853 // chop off trailing parenthesis
2854 pseudo->function.chop(1);
2855 skipSpace();
2856 if (!test(IDENT)) return false;
2857 pseudo->name = lexem();
2858 skipSpace();
2859 return next(RPAREN);
2860}
2861
2862bool Parser::parseNextDeclaration(Declaration *decl)
2863{
2864 if (!testProperty())
2865 return true; // not an error!
2866 if (!parseProperty(decl)) return false;
2867 if (!next(COLON)) return false;
2868 skipSpace();
2869 if (!parseNextExpr(&decl->d->values)) return false;
2870 if (testPrio())
2871 if (!parsePrio(decl)) return false;
2872 return true;
2873}
2874
2875bool Parser::testPrio()
2876{
2877 const int rewind = index;
2878 if (!test(EXCLAMATION_SYM)) return false;
2879 skipSpace();
2880 if (!test(IDENT)) {
2881 index = rewind;
2882 return false;
2883 }
2884 if (lexem().compare("important"_L1, Qt::CaseInsensitive) != 0) {
2885 index = rewind;
2886 return false;
2887 }
2888 return true;
2889}
2890
2891bool Parser::parsePrio(Declaration *declaration)
2892{
2893 declaration->d->important = true;
2894 skipSpace();
2895 return true;
2896}
2897
2898bool Parser::parseExpr(QList<Value> *values)
2899{
2900 Value val;
2901 if (!parseTerm(&val)) return false;
2902 values->append(val);
2903 bool onceMore;
2904 do {
2905 onceMore = false;
2906 val = Value();
2907 if (!parseNextOperator(&val)) return false;
2908 if (val.type != QCss::Value::Unknown)
2909 values->append(val);
2910 if (testTerm()) {
2911 onceMore = true;
2912 val = Value();
2913 if (!parseTerm(&val)) return false;
2914 values->append(val);
2915 }
2916 } while (onceMore);
2917 return true;
2918}
2919
2920bool Parser::testTerm()
2921{
2922 return test(PLUS) || test(MINUS)
2923 || test(NUMBER)
2924 || test(PERCENTAGE)
2925 || test(LENGTH)
2926 || test(STRING)
2927 || test(IDENT)
2928 || testHexColor()
2929 || testFunction();
2930}
2931
2932bool Parser::parseTerm(Value *value)
2933{
2934 QString str = lexem();
2935 bool haveUnary = false;
2936 if (lookup() == PLUS || lookup() == MINUS) {
2937 haveUnary = true;
2938 if (!hasNext()) return false;
2939 next();
2940 str += lexem();
2941 }
2942
2943 value->variant = str;
2944 value->type = QCss::Value::String;
2945 switch (lookup()) {
2946 case NUMBER:
2947 value->type = Value::Number;
2948 value->variant.convert(QMetaType::fromType<double>());
2949 break;
2950 case PERCENTAGE:
2951 value->type = Value::Percentage;
2952 str.chop(1); // strip off %
2953 value->variant = str;
2954 break;
2955 case LENGTH:
2956 value->type = Value::Length;
2957 break;
2958
2959 case STRING:
2960 if (haveUnary) return false;
2961 value->type = Value::String;
2962 str.chop(1);
2963 str.remove(0, 1);
2964 value->variant = str;
2965 break;
2966 case IDENT: {
2967 if (haveUnary) return false;
2968 value->type = Value::Identifier;
2969 const int theid = findKnownValue(str, values);
2970 if (theid != 0) {
2971 value->type = Value::KnownIdentifier;
2972 value->variant = theid;
2973 }
2974 break;
2975 }
2976 default: {
2977 if (haveUnary) return false;
2978 prev();
2979 if (testHexColor()) {
2980 QColor col;
2981 if (!parseHexColor(&col)) return false;
2982 value->type = Value::Color;
2983 value->variant = col;
2984 } else if (testFunction()) {
2985 QString name, args;
2986 if (!parseFunction(&name, &args)) return false;
2987 if (name == "url"_L1) {
2988 value->type = Value::Uri;
2989 removeOptionalQuotes(&args);
2990 if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) {
2991 args.prepend(sourcePath);
2992 }
2993 value->variant = std::move(args);
2994 } else {
2995 value->type = Value::Function;
2996 value->variant = QStringList() << name << args;
2997 }
2998 } else {
2999 return recordError();
3000 }
3001 return true;
3002 }
3003 }
3004 skipSpace();
3005 return true;
3006}
3007
3008bool Parser::parseFunction(QString *name, QString *args)
3009{
3010 *name = lexem();
3011 name->chop(1);
3012 // until(RPAREN) needs FUNCTION token at index-1 to work properly
3013 int start = index;
3014 skipSpace();
3015 std::swap(start, index);
3016 if (!until(RPAREN)) return false;
3017 for (int i = start; i < index - 1; ++i)
3018 args->append(symbols.at(i).lexem());
3019 /*
3020 if (!nextExpr(&arguments)) return false;
3021 if (!next(RPAREN)) return false;
3022 */
3023 skipSpace();
3024 return true;
3025}
3026
3027bool Parser::parseHexColor(QColor *col)
3028{
3029 *col = QColor::fromString(lexem());
3030 if (!col->isValid()) {
3031 qWarning("QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData());
3032 return false;
3033 }
3034 skipSpace();
3035 return true;
3036}
3037
3038bool Parser::testAndParseUri(QString *uri)
3039{
3040 const int rewind = index;
3041 if (!testFunction()) return false;
3042
3043 QString name, args;
3044 if (!parseFunction(&name, &args)) {
3045 index = rewind;
3046 return false;
3047 }
3048 if (name.compare("url"_L1, Qt::CaseInsensitive) != 0) {
3049 index = rewind;
3050 return false;
3051 }
3052 *uri = std::move(args);
3053 removeOptionalQuotes(uri);
3054 return true;
3055}
3056
3057bool Parser::testSimpleSelector()
3058{
3059 return testElementName()
3060 || (test(HASH))
3061 || testClass()
3062 || testAttrib()
3063 || testPseudo();
3064}
3065
3066bool Parser::next(QCss::TokenType t)
3067{
3068 if (hasNext() && next() == t)
3069 return true;
3070 return recordError();
3071}
3072
3073bool Parser::test(QCss::TokenType t)
3074{
3075 if (index >= symbols.size())
3076 return false;
3077 if (symbols.at(index).token == t) {
3078 ++index;
3079 return true;
3080 }
3081 return false;
3082}
3083
3084QString Parser::unquotedLexem() const
3085{
3086 QString s = lexem();
3087 if (lookup() == STRING) {
3088 s.chop(1);
3089 s.remove(0, 1);
3090 }
3091 return s;
3092}
3093
3094QString Parser::lexemUntil(QCss::TokenType t)
3095{
3096 QString lexem;
3097 while (hasNext() && next() != t)
3098 lexem += symbol().lexem();
3099 return lexem;
3100}
3101
3102bool Parser::until(QCss::TokenType target, QCss::TokenType target2)
3103{
3104 int braceCount = 0;
3105 int brackCount = 0;
3106 int parenCount = 0;
3107 if (index) {
3108 switch(symbols.at(index-1).token) {
3109 case LBRACE: ++braceCount; break;
3110 case LBRACKET: ++brackCount; break;
3111 case FUNCTION:
3112 case LPAREN: ++parenCount; break;
3113 default: ;
3114 }
3115 }
3116 while (index < symbols.size()) {
3117 QCss::TokenType t = symbols.at(index++).token;
3118 switch (t) {
3119 case LBRACE: ++braceCount; break;
3120 case RBRACE: --braceCount; break;
3121 case LBRACKET: ++brackCount; break;
3122 case RBRACKET: --brackCount; break;
3123 case FUNCTION:
3124 case LPAREN: ++parenCount; break;
3125 case RPAREN: --parenCount; break;
3126 default: break;
3127 }
3128 if ((t == target || (target2 != NONE && t == target2))
3129 && braceCount <= 0
3130 && brackCount <= 0
3131 && parenCount <= 0)
3132 return true;
3133
3134 if (braceCount < 0 || brackCount < 0 || parenCount < 0) {
3135 --index;
3136 break;
3137 }
3138 }
3139 return false;
3140}
3141
3142bool Parser::testTokenAndEndsWith(QCss::TokenType t, QLatin1StringView str)
3143{
3144 if (!test(t)) return false;
3145 if (!lexem().endsWith(str, Qt::CaseInsensitive)) {
3146 prev();
3147 return false;
3148 }
3149 return true;
3150}
3151
3152QT_END_NAMESPACE
3153#endif // QT_NO_CSSPARSER
\inmodule QtGui
Definition qbrush.h:469
\inmodule QtGui
Definition qbrush.h:417
\inmodule QtGui
Definition qbrush.h:435
@ Repeat_XY
@ Repeat_Unknown
@ Repeat_None
@ Origin_Padding
@ Origin_Content
@ Origin_Margin
@ Origin_Border
@ StyleFeature_BackgroundGradient
@ StyleFeature_BackgroundColor
@ StyleFeature_None
@ Attachment_Fixed
@ Attachment_Scroll
@ Value_Disc
@ Value_Bottom
@ Value_Wave
@ Value_UpperRoman
@ Value_Oblique
@ Value_Selected
@ Value_XXLarge
@ Value_Decimal
@ Value_PreWrap
@ Value_Link
@ Value_Active
@ Value_Button
@ Value_Left
@ Value_Circle
@ Value_Medium
@ Value_Midlight
@ Value_SvgMiterJoin
@ Value_Pre
@ Value_SquareCap
@ Value_Normal
@ Value_LineThrough
@ Value_DotDotDash
@ Value_Square
@ Value_Small
@ Value_Groove
@ Value_Transparent
@ Value_LowerAlpha
@ Value_Off
@ Value_Solid
@ Value_Disabled
@ Value_Light
@ Value_Super
@ Value_NoWrap
@ Value_Base
@ Value_Outset
@ Value_Accent
@ Value_Shadow
@ Value_Native
@ Value_ButtonText
@ Value_RoundJoin
@ Value_LowerRoman
@ Value_Top
@ Value_None
@ Value_Highlight
@ Value_Bold
@ Value_ToolTipText
@ Value_Dark
@ Value_Center
@ Value_Right
@ Value_Sub
@ Value_Text
@ Value_PreLine
@ Value_HighlightedText
@ Value_WindowText
@ Value_FlatCap
@ Value_Underline
@ Value_ToolTipBase
@ Value_DotDash
@ Value_Uppercase
@ Value_Auto
@ Value_Inset
@ Value_Large
@ Value_XLarge
@ Value_MiterJoin
@ Value_SmallCaps
@ Value_RoundCap
@ Value_Double
@ Value_AlternateBase
@ Value_BevelJoin
@ Value_Mid
@ Value_Overline
@ Value_Ridge
@ Value_UpperAlpha
@ Value_PlaceholderText
@ Value_Dashed
@ Value_BrightText
@ Value_Always
@ Value_LinkVisited
@ Value_Dotted
@ Value_Middle
@ Value_Window
@ Value_NoRole
@ Value_Lowercase
@ Value_Italic
@ BorderImage
@ BorderTopLeftRadius
@ BackgroundColor
@ Whitespace
@ OutlineTopLeftRadius
@ MarginLeft
@ QtAlternateBackground
@ QtStrokeMiterLimit
@ BorderTopColor
@ QtPlaceHolderTextColor
@ MarginRight
@ BorderBottomLeftRadius
@ QtStrokeDashArray
@ QtTableType
@ BorderBottom
@ Padding
@ QtStyleFeatures
@ BorderTopStyle
@ OutlineBottomRightRadius
@ FontKerning
@ BorderRightWidth
@ QtStrokeColor
@ PaddingRight
@ QtStrokeDashOffset
@ BackgroundOrigin
@ BorderBottomColor
@ MarginTop
@ TextUnderlineStyle
@ QtListIndent
@ PaddingLeft
@ PaddingTop
@ QtForeground
@ QtSpacing
@ BorderLeftColor
@ OutlineTopRightRadius
@ BorderRightColor
@ QtSelectionForeground
@ PaddingBottom
@ LetterSpacing
@ BorderBottomRightRadius
@ QtListNumberPrefix
@ OutlineWidth
@ PageBreakAfter
@ BorderRight
@ OutlineColor
@ OutlineBottomLeftRadius
@ FontWeight
@ QtBackgroundRole
@ QtForegroundTextureCacheKey
@ TextIndent
@ QtPosition
@ BorderColor
@ MarginBottom
@ MaximumWidth
@ QtUserState
@ OutlineStyle
@ BorderLeftWidth
@ BorderCollapse
@ QtBlockIndent
@ BackgroundPosition
@ WordSpacing
@ QtStrokeWidth
@ BackgroundImage
@ BorderRightStyle
@ FontStyle
@ TextTransform
@ FontFamily
@ FontVariant
@ BorderWidth
@ VerticalAlignment
@ LineHeight
@ QtParagraphType
@ QtSelectionBackground
@ BorderStyles
@ BorderLeft
@ TextDecoration
@ BorderTopRightRadius
@ ListStyleType
@ OutlineOffset
@ BorderRadius
@ ListStyle
@ QtStrokeLineJoin
@ BorderBottomWidth
@ Background
@ BorderTopWidth
@ OutlineRadius
@ MinimumWidth
@ BackgroundAttachment
@ TextAlignment
@ BackgroundClip
@ TextDecorationColor
@ QtListNumberSuffix
@ QtImageAlignment
@ BorderBottomStyle
@ BorderTop
@ FontSize
@ PageBreakBefore
@ QtLineHeightType
@ BackgroundRepeat
@ BorderLeftStyle
@ MinimumHeight
@ MaximumHeight
@ QtStrokeLineCap
@ TileMode_Repeat
@ TileMode_Round
@ TileMode_Stretch
@ PositionMode_Absolute
@ PositionMode_Relative
@ PositionMode_Static
@ PositionMode_Fixed
@ BorderStyle_Dotted
@ BorderStyle_Solid
@ BorderStyle_Double
@ BorderStyle_DotDash
@ BorderStyle_Ridge
@ BorderStyle_Unknown
@ BorderStyle_Dashed
@ BorderStyle_Outset
@ BorderStyle_Groove
@ BorderStyle_Native
@ BorderStyle_None
@ BorderStyle_DotDotDash
@ BorderStyle_Inset
Combined button and popup list for selecting options.
static void setTextDecorationFromValues(const QList< QCss::Value > &values, QFont *font)
static bool operator<(const QString &name, const QCssKnownValue &prop)
static constexpr QCssKnownValue positions[]
static void removeOptionalQuotes(QString *str)
static constexpr QCssKnownValue repeats[]
static bool setFontFamilyFromValues(const QList< QCss::Value > &values, QFont *font, int start=0)
static bool setFontWeightFromValue(const QCss::Value &value, QFont *font)
static int lengthValueFromData(const LengthData &data, const QFont &f)
static bool operator<(const QCssKnownValue &prop, const QString &name)
#define CHECK_ARRAY_IS_SORTED(array)
static void setTextTransformFromValue(const QCss::Value &value, QFont *font)
static bool setFontSizeFromValue(QCss::Value value, QFont *font, int *fontSizeAdjustment)
static constexpr QCssKnownValue tileModes[]
static bool setFontStyleFromValue(const QCss::Value &value, QFont *font)
static ColorData parseColorValue(QCss::Value v)
static BorderStyle parseStyleValue(const QCss::Value &v)
static void setFontVariantFromValue(const QCss::Value &value, QFont *font)
static bool intValueHelper(const QCss::Value &v, int *i, const char *unit)
static Qt::Alignment parseAlignment(const QCss::Value *values, int count)
static constexpr QCssKnownValue styleFeatures[]
static constexpr QCssKnownValue properties[]
static void setLetterSpacingFromValue(const QCss::Value &value, QFont *font)
static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
static quint64 findKnownValue(const QString &name, const C &arr)
static bool isHexDigit(const char c)
static bool isInheritable(Property propertyId)
static constexpr uchar indexOfId[]
static constexpr QCssKnownValue pseudos[]
static bool setFontKerningFromValue(const QCss::Value &value, QFont *font)
static void parseShorthandBackgroundProperty(const QList< QCss::Value > &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal)
static constexpr QCssKnownValue attachments[]
static void parseShorthandFontProperty(const QList< QCss::Value > &values, QFont *font, int *fontSizeAdjustment)
static QBrush brushFromData(const BrushData &c, const QPalette &pal)
static void setWordSpacingFromValue(const QCss::Value &value, QFont *font)
static constexpr QCssKnownValue values[]
static constexpr QCssKnownValue origins[]
static QColor colorFromData(const ColorData &c, const QPalette &pal)
constexpr bool operator()(const QCssKnownValue &lhs, const QCssKnownValue &rhs) const noexcept
const char name[28]
Q_GUI_EXPORT void buildIndexes(Qt::CaseSensitivity nameCaseSensitivity=Qt::CaseSensitive)