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