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, QMultiMap<uint, StyleRule> *weightedRules) const
2203{
2204 for (const Selector &selector : rule.selectors) {
2205 if (selectorMatches(selector, node)) {
2206 uint weight = rule.order
2207 + selector.specificity() *0x100
2208 + (uint(origin) + depth)*0x100000;
2209 StyleRule newRule = rule;
2210 if (rule.selectors.size() > 1) {
2211 newRule.selectors.resize(1);
2212 newRule.selectors[0] = selector;
2213 }
2214 //We might have rules with the same weight if they came from a rule with several selectors
2215 weightedRules->insert(weight, newRule);
2216 }
2217 }
2218}
2219
2220// Returns style rules that are in ascending order of specificity
2221// Each of the StyleRule returned will contain exactly one Selector
2222QList<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
2223{
2224 QList<StyleRule> rules;
2225 if (styleSheets.isEmpty())
2226 return rules;
2227
2228 QMultiMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below
2229
2230 //prune using indexed stylesheet
2231 for (const StyleSheet &styleSheet : std::as_const(styleSheets)) {
2232 for (const StyleRule &styleRule : styleSheet.styleRules)
2233 matchRule(node, styleRule, styleSheet.origin, styleSheet.depth, &weightedRules);
2234
2235 if (!styleSheet.idIndex.isEmpty()) {
2236 const QStringList ids = nodeIds(node);
2237 for (const QString &key : ids) {
2238 auto it = styleSheet.idIndex.constFind(key);
2239 while (it != styleSheet.idIndex.constEnd() && it.key() == key) {
2240 matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
2241 ++it;
2242 }
2243 }
2244 }
2245 if (!styleSheet.nameIndex.isEmpty()) {
2246 const QStringList names = nodeNames(node);
2247 for (QString name : names) {
2248 if (nameCaseSensitivity == Qt::CaseInsensitive)
2249 name = std::move(name).toLower();
2250 auto it = styleSheet.nameIndex.constFind(name);
2251 while (it != styleSheet.nameIndex.constEnd() && it.key() == name) {
2252 matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
2253 ++it;
2254 }
2255 }
2256 }
2257 if (!medium.isEmpty()) {
2258 for (const MediaRule &mediaRule : styleSheet.mediaRules) {
2259 if (mediaRule.media.contains(medium, Qt::CaseInsensitive)) {
2260 for (const StyleRule &styleRule : mediaRule.styleRules) {
2261 matchRule(node, styleRule, styleSheet.origin,
2262 styleSheet.depth, &weightedRules);
2263 }
2264 }
2265 }
2266 }
2267 }
2268
2269 rules.reserve(weightedRules.size());
2270 QMultiMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin();
2271 for ( ; it != weightedRules.constEnd() ; ++it)
2272 rules += *it;
2273
2274 return rules;
2275}
2276
2277// for qtexthtmlparser which requires just the declarations with Enabled state
2278// and without pseudo elements
2279QList<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo)
2280{
2281 QList<Declaration> decls;
2282 const QList<StyleRule> rules = styleRulesForNode(node);
2283 for (const StyleRule &styleRule : rules) {
2284 const Selector &selector = styleRule.selectors.at(0);
2285 const QString pseudoElement = selector.pseudoElement();
2286
2287 if (extraPseudo && pseudoElement == QLatin1StringView(extraPseudo)) {
2288 decls += styleRule.declarations;
2289 continue;
2290 }
2291
2292 if (!pseudoElement.isEmpty()) // skip rules with pseudo elements
2293 continue;
2294 quint64 pseudoClass = selector.pseudoClass();
2295 if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified)
2296 decls += styleRule.declarations;
2297 }
2298 return decls;
2299}
2300
2301static inline bool isHexDigit(const char c)
2302{
2303 return (c >= '0' && c <= '9')
2304 || (c >= 'a' && c <= 'f')
2305 || (c >= 'A' && c <= 'F')
2306 ;
2307}
2308
2309QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences)
2310{
2311 QString output = input;
2312
2313 if (hasEscapeSequences)
2314 *hasEscapeSequences = false;
2315
2316 int i = 0;
2317 while (i < output.size()) {
2318 if (output.at(i) == u'\\') {
2319
2320 ++i;
2321 // test for unicode hex escape
2322 int hexCount = 0;
2323 const int hexStart = i;
2324 while (i < output.size()
2325 && isHexDigit(output.at(i).toLatin1())
2326 && hexCount < 7) {
2327 ++hexCount;
2328 ++i;
2329 }
2330 if (hexCount == 0) {
2331 if (hasEscapeSequences)
2332 *hasEscapeSequences = true;
2333 continue;
2334 }
2335
2336 hexCount = qMin(hexCount, 6);
2337 bool ok = false;
2338 const char16_t code = QStringView{output}.mid(hexStart, hexCount).toUShort(&ok, 16);
2339 if (ok) {
2340 output.replace(hexStart - 1, hexCount + 1, code);
2341 i = hexStart;
2342 } else {
2343 i = hexStart;
2344 }
2345 } else {
2346 ++i;
2347 }
2348 }
2349 return output;
2350}
2351
2352int QCssScanner_Generated::handleCommentStart()
2353{
2354 while (pos < input.size() - 1) {
2355 if (input.at(pos) == u'*' && input.at(pos + 1) == u'/') {
2356 pos += 2;
2357 break;
2358 }
2359 ++pos;
2360 }
2361 return S;
2362}
2363
2364void Scanner::scan(const QString &preprocessedInput, QList<Symbol> *symbols)
2365{
2366 QCssScanner_Generated scanner(preprocessedInput);
2367 Symbol sym;
2368 int tok = scanner.lex();
2369 while (tok != -1) {
2370 sym.token = static_cast<QCss::TokenType>(tok);
2371 sym.text = scanner.input;
2372 sym.start = scanner.lexemStart;
2373 sym.len = scanner.lexemLength;
2374 symbols->append(sym);
2375 tok = scanner.lex();
2376 }
2377}
2378
2379QString Symbol::lexem() const
2380{
2381 QString result;
2382 if (len > 0)
2383 result.reserve(len);
2384 for (int i = 0; i < len; ++i) {
2385 if (text.at(start + i) == u'\\' && i < len - 1)
2386 ++i;
2387 result += text.at(start + i);
2388 }
2389 return result;
2390}
2391
2392Parser::Parser(const QString &css, bool isFile)
2393{
2394 init(css, isFile);
2395}
2396
2397Parser::Parser()
2398{
2399 index = 0;
2400 errorIndex = -1;
2401 hasEscapeSequences = false;
2402}
2403
2404void Parser::init(const QString &css, bool isFile)
2405{
2406 QString styleSheet = css;
2407 if (isFile) {
2408 QFile file(css);
2409 if (file.open(QFile::ReadOnly)) {
2410 sourcePath = QFileInfo(styleSheet).absolutePath() + u'/';
2411 QTextStream stream(&file);
2412 styleSheet = stream.readAll();
2413 } else {
2414 qWarning() << "QCss::Parser - Failed to load file " << css;
2415 styleSheet.clear();
2416 }
2417 } else {
2418 sourcePath.clear();
2419 }
2420
2421 hasEscapeSequences = false;
2422 symbols.clear();
2423 symbols.reserve(8);
2424 Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols);
2425 index = 0;
2426 errorIndex = -1;
2427}
2428
2429bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
2430{
2431 if (testTokenAndEndsWith(ATKEYWORD_SYM, "charset"_L1)) {
2432 while (test(S) || test(CDO) || test(CDC)) {}
2433 if (!next(STRING)) return false;
2434 if (!next(SEMICOLON)) return false;
2435 }
2436
2437 while (test(S) || test(CDO) || test(CDC)) {}
2438
2439 while (testImport()) {
2440 ImportRule rule;
2441 if (!parseImport(&rule)) return false;
2442 styleSheet->importRules.append(rule);
2443 while (test(S) || test(CDO) || test(CDC)) {}
2444 }
2445
2446 do {
2447 if (testMedia()) {
2448 MediaRule rule;
2449 if (!parseMedia(&rule)) return false;
2450 styleSheet->mediaRules.append(rule);
2451 } else if (testPage()) {
2452 PageRule rule;
2453 if (!parsePage(&rule)) return false;
2454 styleSheet->pageRules.append(rule);
2455 } else if (testAnimation()) {
2456 AnimationRule rule;
2457 if (!parseAnimation(&rule)) return false;
2458 styleSheet->animationRules.append(rule);
2459 } else if (testRuleset()) {
2460 StyleRule rule;
2461 if (!parseRuleset(&rule)) return false;
2462 styleSheet->styleRules.append(rule);
2463 } else if (test(ATKEYWORD_SYM)) {
2464 if (!until(RBRACE)) return false;
2465 } else if (hasNext()) {
2466 return false;
2467 }
2468 while (test(S) || test(CDO) || test(CDC)) {}
2469 } while (hasNext());
2470 styleSheet->buildIndexes(nameCaseSensitivity);
2471 return true;
2472}
2473
2474Symbol Parser::errorSymbol()
2475{
2476 if (errorIndex == -1) return Symbol();
2477 return symbols.at(errorIndex);
2478}
2479
2480static inline void removeOptionalQuotes(QString *str)
2481{
2482 if (!str->startsWith(u'\'') && !str->startsWith(u'\"'))
2483 return;
2484 str->remove(0, 1);
2485 str->chop(1);
2486}
2487
2488bool Parser::parseImport(ImportRule *importRule)
2489{
2490 skipSpace();
2491
2492 if (test(STRING)) {
2493 importRule->href = lexem();
2494 } else {
2495 if (!testAndParseUri(&importRule->href)) return false;
2496 }
2497 removeOptionalQuotes(&importRule->href);
2498
2499 skipSpace();
2500
2501 if (testMedium()) {
2502 if (!parseMedium(&importRule->media)) return false;
2503
2504 while (test(COMMA)) {
2505 skipSpace();
2506 if (!parseNextMedium(&importRule->media)) return false;
2507 }
2508 }
2509
2510 if (!next(SEMICOLON)) return false;
2511
2512 skipSpace();
2513 return true;
2514}
2515
2516bool Parser::parseMedia(MediaRule *mediaRule)
2517{
2518 do {
2519 skipSpace();
2520 if (!parseNextMedium(&mediaRule->media)) return false;
2521 } while (test(COMMA));
2522
2523 if (!next(LBRACE)) return false;
2524 skipSpace();
2525
2526 while (testRuleset()) {
2527 StyleRule rule;
2528 if (!parseRuleset(&rule)) return false;
2529 mediaRule->styleRules.append(rule);
2530 }
2531
2532 if (!next(RBRACE)) return false;
2533 skipSpace();
2534 return true;
2535}
2536
2537bool Parser::parseMedium(QStringList *media)
2538{
2539 media->append(lexem());
2540 skipSpace();
2541 return true;
2542}
2543
2544bool Parser::parsePage(PageRule *pageRule)
2545{
2546 skipSpace();
2547
2548 if (testPseudoPage())
2549 if (!parsePseudoPage(&pageRule->selector)) return false;
2550
2551 skipSpace();
2552 if (!next(LBRACE)) return false;
2553
2554 do {
2555 skipSpace();
2556 Declaration decl;
2557 if (!parseNextDeclaration(&decl)) return false;
2558 if (!decl.isEmpty())
2559 pageRule->declarations.append(decl);
2560 } while (test(SEMICOLON));
2561
2562 if (!next(RBRACE)) return false;
2563 skipSpace();
2564 return true;
2565}
2566
2567bool Parser::parsePseudoPage(QString *selector)
2568{
2569 if (!next(IDENT)) return false;
2570 *selector = lexem();
2571 return true;
2572}
2573
2574bool Parser::parseNextOperator(Value *value)
2575{
2576 if (!hasNext()) return true;
2577 switch (next()) {
2578 case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break;
2579 case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break;
2580 default: prev(); break;
2581 }
2582 return true;
2583}
2584
2585bool Parser::parseAnimation(AnimationRule *animationRule)
2586{
2587 skipSpace();
2588 if (!test(IDENT)) return false;
2589
2590 animationRule->animName = lexem();
2591
2592 if (!next(LBRACE)) return false;
2593 skipSpace();
2594
2595 while (test(PERCENTAGE) || test(IDENT)) {
2596 AnimationRule::AnimationRuleSet set;
2597 if (lookup() == PERCENTAGE) {
2598 QString name = lexem();
2599 name.removeLast();
2600 float keyFrame = name.toFloat() / 100;
2601 set.keyFrame = keyFrame;
2602 } else if (lookup() == IDENT) {
2603 QString name;
2604 if (parseElementName(&name)) {
2605 if (name == QStringLiteral("from"))
2606 set.keyFrame = 0;
2607 else if (name == QStringLiteral("to"))
2608 set.keyFrame = 1;
2609 }
2610 }
2611
2612 skipSpace();
2613 if (!next(LBRACE)) return false;
2614 const int declarationStart = index;
2615
2616 do {
2617 skipSpace();
2618 Declaration decl;
2619 const int rewind = index;
2620 if (!parseNextDeclaration(&decl)) {
2621 index = rewind;
2622 const bool foundSemicolon = until(SEMICOLON);
2623 const int semicolonIndex = index;
2624
2625 index = declarationStart;
2626 const bool foundRBrace = until(RBRACE);
2627
2628 if (foundSemicolon && semicolonIndex < index) {
2629 decl = Declaration();
2630 index = semicolonIndex - 1;
2631 } else {
2632 skipSpace();
2633 return foundRBrace;
2634 }
2635 }
2636 if (!decl.isEmpty())
2637 set.declarations.append(decl);
2638 } while (test(SEMICOLON));
2639
2640 if (!next(RBRACE)) return false;
2641 skipSpace();
2642 animationRule->ruleSets.append(set);
2643 }
2644
2645 if (!next(RBRACE)) return false;
2646 skipSpace();
2647
2648 return true;
2649}
2650
2651bool Parser::parseCombinator(BasicSelector::Relation *relation)
2652{
2653 *relation = BasicSelector::NoRelation;
2654 if (lookup() == S) {
2655 *relation = BasicSelector::MatchNextSelectorIfAncestor;
2656 skipSpace();
2657 } else {
2658 prev();
2659 }
2660 if (test(PLUS)) {
2661 *relation = BasicSelector::MatchNextSelectorIfDirectAdjecent;
2662 } else if (test(GREATER)) {
2663 *relation = BasicSelector::MatchNextSelectorIfParent;
2664 } else if (test(TILDE)) {
2665 *relation = BasicSelector::MatchNextSelectorIfIndirectAdjecent;
2666 }
2667 skipSpace();
2668 return true;
2669}
2670
2671bool Parser::parseProperty(Declaration *decl)
2672{
2673 decl->d->property = lexem();
2674 decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties, NumProperties));
2675 decl->d->inheritable = isInheritable(decl->d->propertyId);
2676 skipSpace();
2677 return true;
2678}
2679
2680bool Parser::parseRuleset(StyleRule *styleRule)
2681{
2682 Selector sel;
2683 if (!parseSelector(&sel)) return false;
2684 styleRule->selectors.append(sel);
2685
2686 while (test(COMMA)) {
2687 skipSpace();
2688 Selector sel;
2689 if (!parseNextSelector(&sel)) return false;
2690 styleRule->selectors.append(sel);
2691 }
2692
2693 skipSpace();
2694 if (!next(LBRACE)) return false;
2695 const int declarationStart = index;
2696
2697 do {
2698 skipSpace();
2699 Declaration decl;
2700 const int rewind = index;
2701 if (!parseNextDeclaration(&decl)) {
2702 index = rewind;
2703 const bool foundSemicolon = until(SEMICOLON);
2704 const int semicolonIndex = index;
2705
2706 index = declarationStart;
2707 const bool foundRBrace = until(RBRACE);
2708
2709 if (foundSemicolon && semicolonIndex < index) {
2710 decl = Declaration();
2711 index = semicolonIndex - 1;
2712 } else {
2713 skipSpace();
2714 return foundRBrace;
2715 }
2716 }
2717 if (!decl.isEmpty())
2718 styleRule->declarations.append(decl);
2719 } while (test(SEMICOLON));
2720
2721 if (!next(RBRACE)) return false;
2722 skipSpace();
2723 return true;
2724}
2725
2726bool Parser::parseSelector(Selector *sel)
2727{
2728 BasicSelector basicSel;
2729 if (!parseSimpleSelector(&basicSel)) return false;
2730 while (testCombinator()) {
2731 if (!parseCombinator(&basicSel.relationToNext)) return false;
2732
2733 if (!testSimpleSelector()) break;
2734 sel->basicSelectors.append(basicSel);
2735
2736 basicSel = BasicSelector();
2737 if (!parseSimpleSelector(&basicSel)) return false;
2738 }
2739 sel->basicSelectors.append(basicSel);
2740 return true;
2741}
2742
2743bool Parser::parseSimpleSelector(BasicSelector *basicSel)
2744{
2745 int minCount = 0;
2746 if (lookupElementName()) {
2747 if (!parseElementName(&basicSel->elementName)) return false;
2748 } else {
2749 prev();
2750 minCount = 1;
2751 }
2752 bool onceMore;
2753 int count = 0;
2754 do {
2755 onceMore = false;
2756 if (test(HASH)) {
2757 QString theid = lexem();
2758 // chop off leading #
2759 theid.remove(0, 1);
2760 basicSel->ids.append(theid);
2761 onceMore = true;
2762 } else if (testClass()) {
2763 onceMore = true;
2764 AttributeSelector a;
2765 a.name = "class"_L1;
2766 a.valueMatchCriterium = AttributeSelector::MatchIncludes;
2767 if (!parseClass(&a.value)) return false;
2768 basicSel->attributeSelectors.append(a);
2769 } else if (testAttrib()) {
2770 onceMore = true;
2771 AttributeSelector a;
2772 if (!parseAttrib(&a)) return false;
2773 basicSel->attributeSelectors.append(a);
2774 } else if (testPseudo()) {
2775 onceMore = true;
2776 Pseudo ps;
2777 if (!parsePseudo(&ps)) return false;
2778 basicSel->pseudos.append(ps);
2779 }
2780 if (onceMore) ++count;
2781 } while (onceMore);
2782 return count >= minCount;
2783}
2784
2785bool Parser::parseClass(QString *name)
2786{
2787 if (!next(IDENT)) return false;
2788 *name = lexem();
2789 return true;
2790}
2791
2792bool Parser::parseElementName(QString *name)
2793{
2794 switch (lookup()) {
2795 case STAR: name->clear(); break;
2796 case IDENT: *name = lexem(); break;
2797 default: return false;
2798 }
2799 return true;
2800}
2801
2802bool Parser::parseAttrib(AttributeSelector *attr)
2803{
2804 skipSpace();
2805 if (!next(IDENT)) return false;
2806 attr->name = lexem();
2807 skipSpace();
2808
2809 if (test(EQUAL)) {
2810 attr->valueMatchCriterium = AttributeSelector::MatchEqual;
2811 } else if (test(INCLUDES)) {
2812 attr->valueMatchCriterium = AttributeSelector::MatchIncludes;
2813 } else if (test(DASHMATCH)) {
2814 attr->valueMatchCriterium = AttributeSelector::MatchDashMatch;
2815 } else if (test(BEGINSWITH)) {
2816 attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
2817 } else if (test(ENDSWITH)) {
2818 attr->valueMatchCriterium = AttributeSelector::MatchEndsWith;
2819 } else if (test(CONTAINS)) {
2820 attr->valueMatchCriterium = AttributeSelector::MatchContains;
2821 } else {
2822 return next(RBRACKET);
2823 }
2824
2825 skipSpace();
2826
2827 if (!test(IDENT) && !test(STRING)) return false;
2828 attr->value = unquotedLexem();
2829
2830 skipSpace();
2831 return next(RBRACKET);
2832}
2833
2834bool Parser::parsePseudo(Pseudo *pseudo)
2835{
2836 (void)test(COLON);
2837 pseudo->negated = test(EXCLAMATION_SYM);
2838 if (test(IDENT)) {
2839 pseudo->name = lexem();
2840 pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos, NumPseudos));
2841 return true;
2842 }
2843 if (!next(FUNCTION)) return false;
2844 pseudo->function = lexem();
2845 // chop off trailing parenthesis
2846 pseudo->function.chop(1);
2847 skipSpace();
2848 if (!test(IDENT)) return false;
2849 pseudo->name = lexem();
2850 skipSpace();
2851 return next(RPAREN);
2852}
2853
2854bool Parser::parseNextDeclaration(Declaration *decl)
2855{
2856 if (!testProperty())
2857 return true; // not an error!
2858 if (!parseProperty(decl)) return false;
2859 if (!next(COLON)) return false;
2860 skipSpace();
2861 if (!parseNextExpr(&decl->d->values)) return false;
2862 if (testPrio())
2863 if (!parsePrio(decl)) return false;
2864 return true;
2865}
2866
2867bool Parser::testPrio()
2868{
2869 const int rewind = index;
2870 if (!test(EXCLAMATION_SYM)) return false;
2871 skipSpace();
2872 if (!test(IDENT)) {
2873 index = rewind;
2874 return false;
2875 }
2876 if (lexem().compare("important"_L1, Qt::CaseInsensitive) != 0) {
2877 index = rewind;
2878 return false;
2879 }
2880 return true;
2881}
2882
2883bool Parser::parsePrio(Declaration *declaration)
2884{
2885 declaration->d->important = true;
2886 skipSpace();
2887 return true;
2888}
2889
2890bool Parser::parseExpr(QList<Value> *values)
2891{
2892 Value val;
2893 if (!parseTerm(&val)) return false;
2894 values->append(val);
2895 bool onceMore;
2896 do {
2897 onceMore = false;
2898 val = Value();
2899 if (!parseNextOperator(&val)) return false;
2900 if (val.type != QCss::Value::Unknown)
2901 values->append(val);
2902 if (testTerm()) {
2903 onceMore = true;
2904 val = Value();
2905 if (!parseTerm(&val)) return false;
2906 values->append(val);
2907 }
2908 } while (onceMore);
2909 return true;
2910}
2911
2912bool Parser::testTerm()
2913{
2914 return test(PLUS) || test(MINUS)
2915 || test(NUMBER)
2916 || test(PERCENTAGE)
2917 || test(LENGTH)
2918 || test(STRING)
2919 || test(IDENT)
2920 || testHexColor()
2921 || testFunction();
2922}
2923
2924bool Parser::parseTerm(Value *value)
2925{
2926 QString str = lexem();
2927 bool haveUnary = false;
2928 if (lookup() == PLUS || lookup() == MINUS) {
2929 haveUnary = true;
2930 if (!hasNext()) return false;
2931 next();
2932 str += lexem();
2933 }
2934
2935 value->variant = str;
2936 value->type = QCss::Value::String;
2937 switch (lookup()) {
2938 case NUMBER:
2939 value->type = Value::Number;
2940 value->variant.convert(QMetaType::fromType<double>());
2941 break;
2942 case PERCENTAGE:
2943 value->type = Value::Percentage;
2944 str.chop(1); // strip off %
2945 value->variant = str;
2946 break;
2947 case LENGTH:
2948 value->type = Value::Length;
2949 break;
2950
2951 case STRING:
2952 if (haveUnary) return false;
2953 value->type = Value::String;
2954 str.chop(1);
2955 str.remove(0, 1);
2956 value->variant = str;
2957 break;
2958 case IDENT: {
2959 if (haveUnary) return false;
2960 value->type = Value::Identifier;
2961 const int theid = findKnownValue(str, values, NumKnownValues);
2962 if (theid != 0) {
2963 value->type = Value::KnownIdentifier;
2964 value->variant = theid;
2965 }
2966 break;
2967 }
2968 default: {
2969 if (haveUnary) return false;
2970 prev();
2971 if (testHexColor()) {
2972 QColor col;
2973 if (!parseHexColor(&col)) return false;
2974 value->type = Value::Color;
2975 value->variant = col;
2976 } else if (testFunction()) {
2977 QString name, args;
2978 if (!parseFunction(&name, &args)) return false;
2979 if (name == "url"_L1) {
2980 value->type = Value::Uri;
2981 removeOptionalQuotes(&args);
2982 if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) {
2983 args.prepend(sourcePath);
2984 }
2985 value->variant = args;
2986 } else {
2987 value->type = Value::Function;
2988 value->variant = QStringList() << name << args;
2989 }
2990 } else {
2991 return recordError();
2992 }
2993 return true;
2994 }
2995 }
2996 skipSpace();
2997 return true;
2998}
2999
3000bool Parser::parseFunction(QString *name, QString *args)
3001{
3002 *name = lexem();
3003 name->chop(1);
3004 // until(RPAREN) needs FUNCTION token at index-1 to work properly
3005 int start = index;
3006 skipSpace();
3007 std::swap(start, index);
3008 if (!until(RPAREN)) return false;
3009 for (int i = start; i < index - 1; ++i)
3010 args->append(symbols.at(i).lexem());
3011 /*
3012 if (!nextExpr(&arguments)) return false;
3013 if (!next(RPAREN)) return false;
3014 */
3015 skipSpace();
3016 return true;
3017}
3018
3019bool Parser::parseHexColor(QColor *col)
3020{
3021 *col = QColor::fromString(lexem());
3022 if (!col->isValid()) {
3023 qWarning("QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData());
3024 return false;
3025 }
3026 skipSpace();
3027 return true;
3028}
3029
3030bool Parser::testAndParseUri(QString *uri)
3031{
3032 const int rewind = index;
3033 if (!testFunction()) return false;
3034
3035 QString name, args;
3036 if (!parseFunction(&name, &args)) {
3037 index = rewind;
3038 return false;
3039 }
3040 if (name.compare("url"_L1, Qt::CaseInsensitive) != 0) {
3041 index = rewind;
3042 return false;
3043 }
3044 *uri = args;
3045 removeOptionalQuotes(uri);
3046 return true;
3047}
3048
3049bool Parser::testSimpleSelector()
3050{
3051 return testElementName()
3052 || (test(HASH))
3053 || testClass()
3054 || testAttrib()
3055 || testPseudo();
3056}
3057
3058bool Parser::next(QCss::TokenType t)
3059{
3060 if (hasNext() && next() == t)
3061 return true;
3062 return recordError();
3063}
3064
3065bool Parser::test(QCss::TokenType t)
3066{
3067 if (index >= symbols.size())
3068 return false;
3069 if (symbols.at(index).token == t) {
3070 ++index;
3071 return true;
3072 }
3073 return false;
3074}
3075
3076QString Parser::unquotedLexem() const
3077{
3078 QString s = lexem();
3079 if (lookup() == STRING) {
3080 s.chop(1);
3081 s.remove(0, 1);
3082 }
3083 return s;
3084}
3085
3086QString Parser::lexemUntil(QCss::TokenType t)
3087{
3088 QString lexem;
3089 while (hasNext() && next() != t)
3090 lexem += symbol().lexem();
3091 return lexem;
3092}
3093
3094bool Parser::until(QCss::TokenType target, QCss::TokenType target2)
3095{
3096 int braceCount = 0;
3097 int brackCount = 0;
3098 int parenCount = 0;
3099 if (index) {
3100 switch(symbols.at(index-1).token) {
3101 case LBRACE: ++braceCount; break;
3102 case LBRACKET: ++brackCount; break;
3103 case FUNCTION:
3104 case LPAREN: ++parenCount; break;
3105 default: ;
3106 }
3107 }
3108 while (index < symbols.size()) {
3109 QCss::TokenType t = symbols.at(index++).token;
3110 switch (t) {
3111 case LBRACE: ++braceCount; break;
3112 case RBRACE: --braceCount; break;
3113 case LBRACKET: ++brackCount; break;
3114 case RBRACKET: --brackCount; break;
3115 case FUNCTION:
3116 case LPAREN: ++parenCount; break;
3117 case RPAREN: --parenCount; break;
3118 default: break;
3119 }
3120 if ((t == target || (target2 != NONE && t == target2))
3121 && braceCount <= 0
3122 && brackCount <= 0
3123 && parenCount <= 0)
3124 return true;
3125
3126 if (braceCount < 0 || brackCount < 0 || parenCount < 0) {
3127 --index;
3128 break;
3129 }
3130 }
3131 return false;
3132}
3133
3134bool Parser::testTokenAndEndsWith(QCss::TokenType t, QLatin1StringView str)
3135{
3136 if (!test(t)) return false;
3137 if (!lexem().endsWith(str, Qt::CaseInsensitive)) {
3138 prev();
3139 return false;
3140 }
3141 return true;
3142}
3143
3144QT_END_NAMESPACE
3145#endif // QT_NO_CSSPARSER
\inmodule QtGui
Definition qbrush.h:468
\inmodule QtGui
Definition qbrush.h:416
\inmodule QtGui
Definition qbrush.h:434
@ 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)