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