fractorium/Source/Fractorium/qcssparser.cpp
Person 1dfbd4eff2 --User changes
-Add new preset dimensions to the right click menu of the width and height fields in the editor.
-Change QSS stylesheets to properly handle tabs.
-Make tabs rectangular by default. For some reason, they had always been triangular.

--Bug fixes
 -Incremental rendering times in the editor were wrong.

--Code changes
 -Migrate to Qt6. There is probably more work to be done here.
-Migrate to VS2022.
-Migrate to Wix 4 installer.
-Change installer to install to program files for all users.
-Fix many VS2022 code analysis warnings.
-No longer use byte typedef, because std::byte is now a type. Revert all back to unsigned char.
-Upgrade OpenCL headers to version 3.0 and keep locally now rather than trying to look for system files.
-No longer link to Nvidia or AMD specific OpenCL libraries. Use the generic installer located at OCL_ROOT too.
-Add the ability to change OpenCL grid dimensions. This was attempted for investigating possible performance improvments, but made no difference.

This has not been verified on Linux or Mac yet.
2023-04-25 17:59:54 -06:00

3696 lines
85 KiB
C++

/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "FractoriumPch.h"
#include "qcssscanner.h"
/// <summary>
/// The code in this file did not originate in Fractorium.
/// It was taken either in whole or in part from the source code
/// of Qt Creator. Their license applies.
/// </summary>
using namespace QCss;
struct QCssKnownValue
{
const char* name;
quint64 id;
};
static const QCssKnownValue properties[NumProperties - 1] =
{
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
{ "-qt-list-indent", QtListIndent },
{ "-qt-list-number-prefix", QtListNumberPrefix },
{ "-qt-list-number-suffix", QtListNumberSuffix },
{ "-qt-paragraph-type", QtParagraphType },
{ "-qt-style-features", QtStyleFeatures },
{ "-qt-table-type", QtTableType },
{ "-qt-user-state", QtUserState },
{ "alternate-background-color", QtAlternateBackground },
{ "background", Background },
{ "background-attachment", BackgroundAttachment },
{ "background-clip", BackgroundClip },
{ "background-color", BackgroundColor },
{ "background-image", BackgroundImage },
{ "background-origin", BackgroundOrigin },
{ "background-position", BackgroundPosition },
{ "background-repeat", BackgroundRepeat },
{ "border", Border },
{ "border-bottom", BorderBottom },
{ "border-bottom-color", BorderBottomColor },
{ "border-bottom-left-radius", BorderBottomLeftRadius },
{ "border-bottom-right-radius", BorderBottomRightRadius },
{ "border-bottom-style", BorderBottomStyle },
{ "border-bottom-width", BorderBottomWidth },
{ "border-color", BorderColor },
{ "border-image", BorderImage },
{ "border-left", BorderLeft },
{ "border-left-color", BorderLeftColor },
{ "border-left-style", BorderLeftStyle },
{ "border-left-width", BorderLeftWidth },
{ "border-radius", BorderRadius },
{ "border-right", BorderRight },
{ "border-right-color", BorderRightColor },
{ "border-right-style", BorderRightStyle },
{ "border-right-width", BorderRightWidth },
{ "border-style", BorderStyles },
{ "border-top", BorderTop },
{ "border-top-color", BorderTopColor },
{ "border-top-left-radius", BorderTopLeftRadius },
{ "border-top-right-radius", BorderTopRightRadius },
{ "border-top-style", BorderTopStyle },
{ "border-top-width", BorderTopWidth },
{ "border-width", BorderWidth },
{ "bottom", Bottom },
{ "color", Property::Color },
{ "float", Float },
{ "font", QCss::Font },
{ "font-family", FontFamily },
{ "font-size", FontSize },
{ "font-style", FontStyle },
{ "font-variant", FontVariant },
{ "font-weight", FontWeight },
{ "height", Height },
{ "image", QtImage },
{ "image-position", QtImageAlignment },
{ "left", Left },
{ "line-height", LineHeight },
{ "list-style", ListStyle },
{ "list-style-type", ListStyleType },
{ "margin", Margin },
{ "margin-bottom", MarginBottom },
{ "margin-left", MarginLeft },
{ "margin-right", MarginRight },
{ "margin-top", MarginTop },
{ "max-height", MaximumHeight },
{ "max-width", MaximumWidth },
{ "min-height", MinimumHeight },
{ "min-width", MinimumWidth },
{ "outline", Outline },
{ "outline-bottom-left-radius", OutlineBottomLeftRadius },
{ "outline-bottom-right-radius", OutlineBottomRightRadius },
{ "outline-color", OutlineColor },
{ "outline-offset", OutlineOffset },
{ "outline-radius", OutlineRadius },
{ "outline-style", OutlineStyle },
{ "outline-top-left-radius", OutlineTopLeftRadius },
{ "outline-top-right-radius", OutlineTopRightRadius },
{ "outline-width", OutlineWidth },
{ "padding", Padding },
{ "padding-bottom", PaddingBottom },
{ "padding-left", PaddingLeft },
{ "padding-right", PaddingRight },
{ "padding-top", PaddingTop },
{ "page-break-after", PageBreakAfter },
{ "page-break-before", PageBreakBefore },
{ "position", Position },
{ "right", Right },
{ "selection-background-color", QtSelectionBackground },
{ "selection-color", QtSelectionForeground },
{ "spacing", QtSpacing },
{ "subcontrol-origin", QtOrigin },
{ "subcontrol-position", QtPosition },
{ "text-align", TextAlignment },
{ "text-decoration", TextDecoration },
{ "text-indent", TextIndent },
{ "text-transform", TextTransform },
{ "text-underline-style", TextUnderlineStyle },
{ "top", Top },
{ "vertical-align", VerticalAlignment },
{ "white-space", Whitespace },
{ "width", Width }
};
static const QCssKnownValue values[NumKnownValues - 1] =
{
{ "active", Value_Active },
{ "alternate-base", Value_AlternateBase },
{ "always", Value_Always },
{ "auto", Value_Auto },
{ "base", Value_Base },
{ "bold", Value_Bold },
{ "bottom", Value_Bottom },
{ "bright-text", Value_BrightText },
{ "button", Value_Button },
{ "button-text", Value_ButtonText },
{ "center", Value_Center },
{ "circle", Value_Circle },
{ "dark", Value_Dark },
{ "dashed", Value_Dashed },
{ "decimal", Value_Decimal },
{ "disabled", Value_Disabled },
{ "disc", Value_Disc },
{ "dot-dash", Value_DotDash },
{ "dot-dot-dash", Value_DotDotDash },
{ "dotted", Value_Dotted },
{ "double", Value_Double },
{ "groove", Value_Groove },
{ "highlight", Value_Highlight },
{ "highlighted-text", Value_HighlightedText },
{ "inset", Value_Inset },
{ "italic", Value_Italic },
{ "large", Value_Large },
{ "left", Value_Left },
{ "light", Value_Light },
{ "line-through", Value_LineThrough },
{ "link", Value_Link },
{ "link-visited", Value_LinkVisited },
{ "lower-alpha", Value_LowerAlpha },
{ "lower-roman", Value_LowerRoman },
{ "lowercase", Value_Lowercase },
{ "medium", Value_Medium },
{ "mid", Value_Mid },
{ "middle", Value_Middle },
{ "midlight", Value_Midlight },
{ "native", Value_Native },
{ "none", Value_None },
{ "normal", Value_Normal },
{ "nowrap", Value_NoWrap },
{ "oblique", Value_Oblique },
{ "off", Value_Off },
{ "on", Value_On },
{ "outset", Value_Outset },
{ "overline", Value_Overline },
{ "pre", Value_Pre },
{ "pre-wrap", Value_PreWrap },
{ "ridge", Value_Ridge },
{ "right", Value_Right },
{ "selected", Value_Selected },
{ "shadow", Value_Shadow },
{ "small", Value_Small },
{ "small-caps", Value_SmallCaps },
{ "solid", Value_Solid },
{ "square", Value_Square },
{ "sub", Value_Sub },
{ "super", Value_Super },
{ "text", Value_Text },
{ "top", Value_Top },
{ "transparent", Value_Transparent },
{ "underline", Value_Underline },
{ "upper-alpha", Value_UpperAlpha },
{ "upper-roman", Value_UpperRoman },
{ "uppercase", Value_Uppercase },
{ "wave", Value_Wave },
{ "window", Value_Window },
{ "window-text", Value_WindowText },
{ "x-large", Value_XLarge },
{ "xx-large", Value_XXLarge }
};
//Map id to strings as they appears in the 'values' array above
static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47,
29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16,
11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31,
1, 15, 0, 52, 45, 44
};
QString Value::toString() const
{
if (type == KnownIdentifier)
{
return QLatin1String(values[indexOfId[variant.toInt()]].name);
}
else
{
return variant.toString();
}
}
static const QCssKnownValue pseudos[NumPseudos - 1] =
{
{ "active", PseudoClass_Active },
{ "adjoins-item", PseudoClass_Item },
{ "alternate", PseudoClass_Alternate },
{ "bottom", PseudoClass_Bottom },
{ "checked", PseudoClass_Checked },
{ "closable", PseudoClass_Closable },
{ "closed", PseudoClass_Closed },
{ "default", PseudoClass_Default },
{ "disabled", PseudoClass_Disabled },
{ "edit-focus", PseudoClass_EditFocus },
{ "editable", PseudoClass_Editable },
{ "enabled", PseudoClass_Enabled },
{ "exclusive", PseudoClass_Exclusive },
{ "first", PseudoClass_First },
{ "flat", PseudoClass_Flat },
{ "floatable", PseudoClass_Floatable },
{ "focus", PseudoClass_Focus },
{ "has-children", PseudoClass_Children },
{ "has-siblings", PseudoClass_Sibling },
{ "horizontal", PseudoClass_Horizontal },
{ "hover", PseudoClass_Hover },
{ "indeterminate", PseudoClass_Indeterminate },
{ "last", PseudoClass_Last },
{ "left", PseudoClass_Left },
{ "maximized", PseudoClass_Maximized },
{ "middle", PseudoClass_Middle },
{ "minimized", PseudoClass_Minimized },
{ "movable", PseudoClass_Movable },
{ "next-selected", PseudoClass_NextSelected },
{ "no-frame", PseudoClass_Frameless },
{ "non-exclusive", PseudoClass_NonExclusive },
{ "off", PseudoClass_Unchecked },
{ "on", PseudoClass_Checked },
{ "only-one", PseudoClass_OnlyOne },
{ "open", PseudoClass_Open },
{ "pressed", PseudoClass_Pressed },
{ "previous-selected", PseudoClass_PreviousSelected },
{ "read-only", PseudoClass_ReadOnly },
{ "right", PseudoClass_Right },
{ "selected", PseudoClass_Selected },
{ "top", PseudoClass_Top },
{ "unchecked", PseudoClass_Unchecked },
{ "vertical", PseudoClass_Vertical },
{ "window", PseudoClass_Window }
};
static const QCssKnownValue origins[NumKnownOrigins - 1] =
{
{ "border", Origin_Border },
{ "content", Origin_Content },
{ "margin", Origin_Margin }, // not in css
{ "padding", Origin_Padding }
};
static const QCssKnownValue repeats[NumKnownRepeats - 1] =
{
{ "no-repeat", Repeat_None },
{ "repeat-x", Repeat_X },
{ "repeat-xy", Repeat_XY },
{ "repeat-y", Repeat_Y }
};
static const QCssKnownValue tileModes[NumKnownTileModes - 1] =
{
{ "repeat", TileMode_Repeat },
{ "round", TileMode_Round },
{ "stretch", TileMode_Stretch },
};
static const QCssKnownValue positions[NumKnownPositionModes - 1] =
{
{ "absolute", PositionMode_Absolute },
{ "fixed", PositionMode_Fixed },
{ "relative", PositionMode_Relative },
{ "static", PositionMode_Static }
};
static const QCssKnownValue attachments[NumKnownAttachments - 1] =
{
{ "fixed", Attachment_Fixed },
{ "scroll", Attachment_Scroll }
};
static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] =
{
{ "background-color", StyleFeature_BackgroundColor },
{ "background-gradient", StyleFeature_BackgroundGradient },
{ "none", StyleFeature_None }
};
#if defined(Q_CC_MSVC) && _MSC_VER < 1600
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue& prop1, const QCssKnownValue& prop2)
{
return QString::compare(QString::fromLatin1(prop1.name), QLatin1String(prop2.name), Qt::CaseInsensitive) < 0;
}
#endif
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString& name, const QCssKnownValue& prop) noexcept
{
return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0;
}
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue& prop, const QString& name) noexcept
{
return QString::compare(QLatin1String(prop.name), name, Qt::CaseInsensitive) < 0;
}
static quint64 findKnownValue(const QString& name, const QCssKnownValue* start, int numValues)
{
if (start != nullptr)
{
const QCssKnownValue* end = &start[numValues - 1];
const QCssKnownValue* prop = std::lower_bound(start, end, name);
if (prop != nullptr)
if ((prop == end) || (name < *prop))
return 0;
return prop->id;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Value Extractor
ValueExtractor::ValueExtractor(const QVector<Declaration>& decls, const QPalette& pal)
: declarations(decls), adjustment(0), fontExtracted(false), pal(pal)
{
}
LengthData ValueExtractor::lengthValue(const Value& v)
{
QString s = v.variant.toString();
s.reserve(s.length());
LengthData data;
data.unit = LengthData::NONE;
if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive))
data.unit = LengthData::Px;
else if (s.endsWith(QLatin1String("ex"), Qt::CaseInsensitive))
data.unit = LengthData::Ex;
else if (s.endsWith(QLatin1String("em"), Qt::CaseInsensitive))
data.unit = LengthData::Em;
if (data.unit != LengthData::NONE)
s.chop(2);
data.number = s.toDouble();
return data;
}
static int lengthValueFromData(const LengthData& data, const QFont& f)
{
if (data.unit == LengthData::Ex)
return qRound(QFontMetrics(f).xHeight() * data.number);
else if (data.unit == LengthData::Em)
return qRound(QFontMetrics(f).height() * data.number);
return qRound(data.number);
}
int ValueExtractor::lengthValue(const Declaration& decl)
{
if (decl.d->parsed.isValid())
return lengthValueFromData(qvariant_cast<LengthData>(decl.d->parsed), f);
if (decl.d->values.count() < 1)
return 0;
LengthData const data = lengthValue(decl.d->values.at(0));
decl.d->parsed = QVariant::fromValue<LengthData>(data);
return lengthValueFromData(data, f);
}
void ValueExtractor::lengthValues(const Declaration& decl, int* m)
{
if (m != nullptr)
{
if (decl.d->parsed.isValid())
{
QList<QVariant> v = decl.d->parsed.toList();
for (int i = 0; i < 4; i++)
m[i] = lengthValueFromData(qvariant_cast<LengthData>(v.at(i)), f);
return;
}
LengthData datas[4];
int i;
for (i = 0; i < qMin(decl.d->values.count(), 4); i++)
datas[i] = lengthValue(decl.d->values[i]);
if (i == 0)
{
LengthData zero = { 0.0, LengthData::NONE };
datas[0] = datas[1] = datas[2] = datas[3] = zero;
}
else if (i == 1)
{
datas[3] = datas[2] = datas[1] = datas[0];
}
else if (i == 2)
{
datas[2] = datas[0];
datas[3] = datas[1];
}
else if (i == 3)
{
datas[3] = datas[1];
}
QList<QVariant> v;
for (i = 0; i < 4; i++)
{
v += QVariant::fromValue<LengthData>(datas[i]);
m[i] = lengthValueFromData(datas[i], f);
}
decl.d->parsed = v;
}
}
bool ValueExtractor::extractGeometry(int* w, int* h, int* minw, int* minh, int* maxw, int* maxh)
{
extractFont();
bool hit = false;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case Width: if (w != nullptr) *w = lengthValue(decl); break;
case Height: if (h != nullptr) *h = lengthValue(decl); break;
case MinimumWidth: if (minw != nullptr) *minw = lengthValue(decl); break;
case MinimumHeight: if (minh != nullptr) *minh = lengthValue(decl); break;
case MaximumWidth: if (maxw != nullptr) *maxw = lengthValue(decl); break;
case MaximumHeight: if (maxh != nullptr) *maxh = lengthValue(decl); break;
default: continue;
}
hit = true;
}
return hit;
}
bool ValueExtractor::extractPosition(int* left, int* top, int* right, int* bottom, QCss::Origin* origin,
Qt::Alignment* position, QCss::PositionMode* mode, Qt::Alignment* textAlignment)
{
extractFont();
bool hit = false;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case Left: if (left != nullptr) *left = lengthValue(decl); break;
case Top: if (top != nullptr) *top = lengthValue(decl); break;
case Right: if (right != nullptr) *right = lengthValue(decl); break;
case Bottom: if (bottom != nullptr) *bottom = lengthValue(decl); break;
case QtOrigin: if (origin != nullptr) *origin = decl.originValue(); break;
case QtPosition: if (position != nullptr) *position = decl.alignmentValue(); break;
case TextAlignment: if (textAlignment != nullptr) *textAlignment = decl.alignmentValue(); break;
case Position: if (mode != nullptr) *mode = decl.positionValue(); break;
default: continue;
}
hit = true;
}
return hit;
}
bool ValueExtractor::extractBox(int* margins, int* paddings, int* spacing)
{
extractFont();
bool hit = false;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case PaddingLeft: if (paddings != nullptr) paddings[LeftEdge] = lengthValue(decl); break;
case PaddingRight: if (paddings != nullptr) paddings[RightEdge] = lengthValue(decl); break;
case PaddingTop: if (paddings != nullptr) paddings[TopEdge] = lengthValue(decl); break;
case PaddingBottom: if (paddings != nullptr) paddings[BottomEdge] = lengthValue(decl); break;
case Padding: if (paddings != nullptr) lengthValues(decl, paddings); break;
case MarginLeft: if (margins != nullptr) margins[LeftEdge] = lengthValue(decl); break;
case MarginRight: if (margins != nullptr) margins[RightEdge] = lengthValue(decl); break;
case MarginTop: if (margins != nullptr) margins[TopEdge] = lengthValue(decl); break;
case MarginBottom: if (margins != nullptr) margins[BottomEdge] = lengthValue(decl); break;
case Margin: if (margins != nullptr) lengthValues(decl, margins); break;
case QtSpacing: if (spacing) *spacing = lengthValue(decl); break;
default: continue;
}
hit = true;
}
return hit;
}
int ValueExtractor::extractStyleFeatures()
{
int features = StyleFeature_None;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
if (decl.d->propertyId == QtStyleFeatures)
features = decl.styleFeaturesValue();
}
return features;
}
QSize ValueExtractor::sizeValue(const Declaration& decl)
{
if (decl.d->parsed.isValid())
{
QList<QVariant> v = decl.d->parsed.toList();
return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f),
lengthValueFromData(qvariant_cast<LengthData>(v.at(1)), f));
}
LengthData x[2] = { {0, LengthData::NONE }, {0, LengthData::NONE} };
if (decl.d->values.count() > 0)
x[0] = lengthValue(decl.d->values.at(0));
if (decl.d->values.count() > 1)
x[1] = lengthValue(decl.d->values.at(1));
else
x[1] = x[0];
QList<QVariant> v;
v << QVariant::fromValue<LengthData>(x[0]) << QVariant::fromValue<LengthData>(x[1]);
decl.d->parsed = v;
return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f));
}
void ValueExtractor::sizeValues(const Declaration& decl, QSize* radii)
{
if (radii != nullptr)
{
radii[0] = sizeValue(decl);
for (int i = 1; i < 4; i++)
radii[i] = radii[0];
}
}
bool ValueExtractor::extractBorder(int* borders, QBrush* colors, BorderStyle* styles,
QSize* radii)
{
extractFont();
bool hit = false;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case BorderLeftWidth: if (borders != nullptr) borders[LeftEdge] = lengthValue(decl); break;
case BorderRightWidth: if (borders != nullptr) borders[RightEdge] = lengthValue(decl); break;
case BorderTopWidth: if (borders != nullptr) borders[TopEdge] = lengthValue(decl); break;
case BorderBottomWidth: if (borders != nullptr) borders[BottomEdge] = lengthValue(decl); break;
case BorderWidth: if (borders != nullptr) lengthValues(decl, borders); break;
case BorderLeftColor: if (colors != nullptr) colors[LeftEdge] = decl.brushValue(pal); break;
case BorderRightColor: if (colors != nullptr) colors[RightEdge] = decl.brushValue(pal); break;
case BorderTopColor: if (colors != nullptr) colors[TopEdge] = decl.brushValue(pal); break;
case BorderBottomColor: if (colors != nullptr) colors[BottomEdge] = decl.brushValue(pal); break;
case BorderColor: if (colors != nullptr) decl.brushValues(colors, pal); break;
case BorderTopStyle: if (styles != nullptr) styles[TopEdge] = decl.styleValue(); break;
case BorderBottomStyle: if (styles != nullptr) styles[BottomEdge] = decl.styleValue(); break;
case BorderLeftStyle: if (styles != nullptr) styles[LeftEdge] = decl.styleValue(); break;
case BorderRightStyle: if (styles != nullptr) styles[RightEdge] = decl.styleValue(); break;
case BorderStyles: if (styles != nullptr) decl.styleValues(styles); break;
#ifndef QT_OS_ANDROID_GCC_48_WORKAROUND
case BorderTopLeftRadius: if (radii != nullptr) radii[0] = sizeValue(decl); break;
#else
case BorderTopLeftRadius: new (radii)QSize(sizeValue(decl)); break;
#endif
case BorderTopRightRadius: if (radii != nullptr) radii[1] = sizeValue(decl); break;
case BorderBottomLeftRadius: if (radii != nullptr) radii[2] = sizeValue(decl); break;
case BorderBottomRightRadius: if (radii != nullptr) radii[3] = sizeValue(decl); break;
case BorderRadius: if (radii != nullptr) sizeValues(decl, radii); break;
case BorderLeft:
if (borders != nullptr && styles != nullptr && colors != nullptr)
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
break;
case BorderTop:
if (borders != nullptr && styles != nullptr && colors != nullptr)
borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]);
break;
case BorderRight:
if (borders != nullptr && styles != nullptr && colors != nullptr)
borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]);
break;
case BorderBottom:
if (borders != nullptr && styles != nullptr && colors != nullptr)
borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]);
break;
case Border:
if (borders != nullptr && styles != nullptr && colors != nullptr)
{
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
}
break;
default: continue;
}
hit = true;
}
return hit;
}
bool ValueExtractor::extractOutline(int* borders, QBrush* colors, BorderStyle* styles,
QSize* radii, int* offsets)
{
extractFont();
bool hit = false;
for (int i = 0; i < declarations.count(); i++)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case OutlineWidth: if (borders != nullptr) lengthValues(decl, borders); break;
case OutlineColor: if (colors != nullptr) decl.brushValues(colors, pal); break;
case OutlineStyle: if (styles != nullptr) decl.styleValues(styles); break;
case OutlineTopLeftRadius: if (radii != nullptr) radii[0] = sizeValue(decl); break;
case OutlineTopRightRadius: if (radii != nullptr) radii[1] = sizeValue(decl); break;
case OutlineBottomLeftRadius: if (radii != nullptr) radii[2] = sizeValue(decl); break;
case OutlineBottomRightRadius: if (radii != nullptr) radii[3] = sizeValue(decl); break;
case OutlineRadius: if (radii != nullptr) sizeValues(decl, radii); break;
case OutlineOffset: if (offsets != nullptr) lengthValues(decl, offsets); break;
case Outline:
if (borders != nullptr && styles != nullptr && colors != nullptr)
{
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
}
break;
default: continue;
}
hit = true;
}
return hit;
}
static Qt::Alignment parseAlignment(const QCss::Value* values, int count)
{
Qt::Alignment a[2] = { Qt::AlignLeft, Qt::AlignLeft };
if (values != nullptr)
{
for (int i = 0; i < qMin(2, count); i++)
{
if (values[i].type != Value::KnownIdentifier)
break;
switch (values[i].variant.toInt())
{
case Value_Left: a[i] = Qt::AlignLeft; break;
case Value_Right: a[i] = Qt::AlignRight; break;
case Value_Top: a[i] = Qt::AlignTop; break;
case Value_Bottom: a[i] = Qt::AlignBottom; break;
case Value_Center: a[i] = Qt::AlignCenter; break;
default: break;
}
}
}
if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter)
a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter)
a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
return a[0] | a[1];
}
static ColorData parseColorValue(QCss::Value v)
{
if (v.type == Value::Identifier || v.type == Value::String)
{
v.variant.convert(QMetaType(QMetaType::QColor));
v.type = Value::Color;
}
if (v.type == Value::Color)
return qvariant_cast<QColor>(v.variant);
if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
return QColor(Qt::transparent);
if (v.type != Value::Function)
return ColorData();
QStringList lst = v.variant.toStringList();
if (lst.count() != 2)
return ColorData();
if ((lst.at(0).compare(QLatin1String("palette"), Qt::CaseInsensitive)) == 0)
{
const auto role = findKnownValue(lst.at(1).trimmed(), values, NumKnownValues);
if (role >= Value_FirstColorRole && role <= Value_LastColorRole)
return (QPalette::ColorRole)(role - Value_FirstColorRole);
return ColorData();
}
const auto rgb = lst.at(0).startsWith(QLatin1String("rgb"));
const auto rgba = lst.at(0).startsWith(QLatin1String("rgba"));
Parser p(lst.at(1));
if (!p.testExpr())
return ColorData();
QVector<QCss::Value> colorDigits;
if (!p.parseExpr(&colorDigits))
return ColorData();
for (int i = 0; i < qMin(colorDigits.count(), 7); i += 2)
{
if (colorDigits.at(i).type == Value::Percentage)
{
colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (255. / 100.);
colorDigits[i].type = Value::Number;
}
else if (colorDigits.at(i).type != Value::Number)
{
return ColorData();
}
}
const auto v1 = colorDigits.at(0).variant.toInt();
const auto v2 = colorDigits.at(2).variant.toInt();
const auto v3 = colorDigits.at(4).variant.toInt();
auto alpha = 255;
if (colorDigits.count() >= 7)
{
int alphaValue = colorDigits.at(6).variant.toInt();
if (rgba && alphaValue <= 1)
alpha = colorDigits.at(6).variant.toReal() * 255.;
else
alpha = alphaValue;
}
return rgb ? QColor::fromRgb(v1, v2, v3, alpha)
: QColor::fromHsv(v1, v2, v3, alpha);
}
static QColor colorFromData(const ColorData& c, const QPalette& pal)
{
if (c.type == ColorData::Color)
{
return c.color;
}
else if (c.type == ColorData::Role)
{
return pal.color(c.role);
}
return QColor();
}
static BrushData parseBrushValue(const QCss::Value& v, const QPalette& pal)
{
const auto c = parseColorValue(v);
if (c.type == ColorData::Color)
{
return QBrush(c.color);
}
else if (c.type == ColorData::Role)
{
return c.role;
}
if (v.type != Value::Function)
return BrushData();
QStringList lst = v.variant.toStringList();
if (lst.count() != 2)
return BrushData();
QStringList gradFuncs;
gradFuncs << QLatin1String("qlineargradient") << QLatin1String("qradialgradient") << QLatin1String("qconicalgradient") << QLatin1String("qgradient");
int gradType = -1;
if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1)
return BrushData();
QHash<QString, qreal> vars;
QVector<QGradientStop> stops;
int spread = -1;
QStringList spreads;
spreads << QLatin1String("pad") << QLatin1String("reflect") << QLatin1String("repeat");
bool dependsOnThePalette = false;
Parser parser(lst.at(1));
while (parser.hasNext())
{
parser.skipSpace();
if (!parser.test(IDENT))
return BrushData();
QString attr = parser.lexem();
parser.skipSpace();
if (!parser.test(COLON))
return BrushData();
parser.skipSpace();
if (attr.compare(QLatin1String("stop"), Qt::CaseInsensitive) == 0)
{
QCss::Value stop, color;
parser.next();
if (!parser.parseTerm(&stop)) return BrushData();
parser.skipSpace();
parser.next();
if (!parser.parseTerm(&color)) return BrushData();
const auto cd = parseColorValue(color);
if (cd.type == ColorData::Role)
dependsOnThePalette = true;
stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal)));
}
else
{
parser.next();
QCss::Value value;
std::ignore = parser.parseTerm(&value);
if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0)
{
spread = spreads.indexOf(value.variant.toString());
}
else
{
vars[attr] = value.variant.toReal();
}
}
parser.skipSpace();
std::ignore = parser.test(COMMA);
}
if (gradType == 0)
{
QLinearGradient lg(vars.value(QLatin1String("x1")), vars.value(QLatin1String("y1")),
vars.value(QLatin1String("x2")), vars.value(QLatin1String("y2")));
lg.setCoordinateMode(QGradient::ObjectBoundingMode);
lg.setStops(stops);
if (spread != -1)
lg.setSpread(QGradient::Spread(spread));
BrushData bd = QBrush(lg);
if (dependsOnThePalette)
bd.type = BrushData::DependsOnThePalette;
return bd;
}
if (gradType == 1)
{
QRadialGradient rg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")),
vars.value(QLatin1String("radius")), vars.value(QLatin1String("fx")),
vars.value(QLatin1String("fy")));
rg.setCoordinateMode(QGradient::ObjectBoundingMode);
rg.setStops(stops);
if (spread != -1)
rg.setSpread(QGradient::Spread(spread));
BrushData bd = QBrush(rg);
if (dependsOnThePalette)
bd.type = BrushData::DependsOnThePalette;
return bd;
}
if (gradType == 2)
{
QConicalGradient cg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")),
vars.value(QLatin1String("angle")));
cg.setCoordinateMode(QGradient::ObjectBoundingMode);
cg.setStops(stops);
if (spread != -1)
cg.setSpread(QGradient::Spread(spread));
BrushData bd = QBrush(cg);
if (dependsOnThePalette)
bd.type = BrushData::DependsOnThePalette;
return bd;
}
return BrushData();
}
static QBrush brushFromData(const BrushData& c, const QPalette& pal)
{
if (c.type == BrushData::Role)
{
return pal.color(c.role);
}
else
{
return c.brush;
}
}
static BorderStyle parseStyleValue(QCss::Value v)
{
if (v.type == Value::KnownIdentifier)
{
switch (v.variant.toInt())
{
case Value_None:
return BorderStyle_None;
case Value_Dotted:
return BorderStyle_Dotted;
case Value_Dashed:
return BorderStyle_Dashed;
case Value_Solid:
return BorderStyle_Solid;
case Value_Double:
return BorderStyle_Double;
case Value_DotDash:
return BorderStyle_DotDash;
case Value_DotDotDash:
return BorderStyle_DotDotDash;
case Value_Groove:
return BorderStyle_Groove;
case Value_Ridge:
return BorderStyle_Ridge;
case Value_Inset:
return BorderStyle_Inset;
case Value_Outset:
return BorderStyle_Outset;
case Value_Native:
return BorderStyle_Native;
default:
break;
}
}
return BorderStyle_Unknown;
}
void ValueExtractor::borderValue(const Declaration& decl, int* width, QCss::BorderStyle* style, QBrush* color)
{
if (width != nullptr && style != nullptr && color != nullptr)
{
if (decl.d->parsed.isValid())
{
BorderData data = qvariant_cast<BorderData>(decl.d->parsed);
*width = lengthValueFromData(data.width, f);
*style = data.style;
*color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor());
return;
}
*width = 0;
*style = BorderStyle_None;
*color = QColor();
if (decl.d->values.isEmpty())
return;
BorderData data;
data.width.number = 0;
data.width.unit = LengthData::NONE;
data.style = BorderStyle_None;
int i = 0;
if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number)
{
data.width = lengthValue(decl.d->values.at(i));
*width = lengthValueFromData(data.width, f);
if (++i >= decl.d->values.count())
{
decl.d->parsed = QVariant::fromValue<BorderData>(data);
return;
}
}
data.style = parseStyleValue(decl.d->values.at(i));
if (data.style != BorderStyle_Unknown)
{
*style = data.style;
if (++i >= decl.d->values.count())
{
decl.d->parsed = QVariant::fromValue<BorderData>(data);
return;
}
}
else
{
data.style = BorderStyle_None;
}
data.color = parseBrushValue(decl.d->values.at(i), pal);
*color = brushFromData(data.color, pal);
if (data.color.type != BrushData::DependsOnThePalette)
decl.d->parsed = QVariant::fromValue<BorderData>(data);
}
}
static void parseShorthandBackgroundProperty(const QVector<QCss::Value>& values, BrushData* brush, QString* image, Repeat* repeat, Qt::Alignment* alignment, const QPalette& pal)
{
if (brush != nullptr && image != nullptr && repeat != nullptr && alignment != nullptr)
{
*brush = BrushData();
*image = QString();
*repeat = Repeat_XY;
*alignment = Qt::AlignTop | Qt::AlignLeft;
for (int i = 0; i < values.count(); ++i)
{
const QCss::Value& v = values.at(i);
if (v.type == Value::Uri)
{
*image = v.variant.toString();
continue;
}
else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None)
{
*image = QString();
continue;
}
else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
{
*brush = QBrush(Qt::transparent);
}
Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(v.variant.toString(),
repeats, NumKnownRepeats));
if (repeatAttempt != Repeat_Unknown)
{
*repeat = repeatAttempt;
continue;
}
if (v.type == Value::KnownIdentifier)
{
const int start = i;
int count = 1;
if (i < values.count() - 1
&& values.at(i + 1).type == Value::KnownIdentifier)
{
++i;
++count;
}
Qt::Alignment a = parseAlignment(values.constData() + start, count);
if (int(a) != 0)
{
*alignment = a;
continue;
}
i -= count - 1;
}
*brush = parseBrushValue(v, pal);
}
}
}
bool ValueExtractor::extractBackground(QBrush* brush, QString* image, Repeat* repeat,
Qt::Alignment* alignment, Origin* origin, Attachment* attachment,
Origin* clip)
{
bool hit = false;
if (brush != nullptr && image != nullptr && repeat != nullptr && alignment != nullptr && origin != nullptr && attachment != nullptr && clip != nullptr)
{
for (int i = 0; i < declarations.count(); ++i)
{
const Declaration& decl = declarations.at(i);
if (decl.d->values.isEmpty())
continue;
const QCss::Value& val = decl.d->values.at(0);
switch (decl.d->propertyId)
{
case BackgroundColor:
*brush = decl.brushValue();
break;
case BackgroundImage:
if (val.type == Value::Uri)
*image = val.variant.toString();
break;
case BackgroundRepeat:
if (decl.d->parsed.isValid())
{
*repeat = static_cast<Repeat>(decl.d->parsed.toInt());
}
else
{
*repeat = static_cast<Repeat>(findKnownValue(val.variant.toString(),
repeats, NumKnownRepeats));
decl.d->parsed = *repeat;
}
break;
case BackgroundPosition:
*alignment = decl.alignmentValue();
break;
case BackgroundOrigin:
*origin = decl.originValue();
break;
case BackgroundClip:
*clip = decl.originValue();
break;
case Background:
if (decl.d->parsed.isValid())
{
BackgroundData data = qvariant_cast<BackgroundData>(decl.d->parsed);
*brush = brushFromData(data.brush, pal);
*image = data.image;
*repeat = data.repeat;
*alignment = data.alignment;
}
else
{
BrushData brushData;
parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal);
*brush = brushFromData(brushData, pal);
if (brushData.type != BrushData::DependsOnThePalette)
{
BackgroundData data = { brushData, *image, *repeat, *alignment };
decl.d->parsed = QVariant::fromValue<BackgroundData>(data);
}
}
break;
case BackgroundAttachment:
*attachment = decl.attachmentValue();
break;
default: continue;
}
hit = true;
}
}
return hit;
}
static bool setFontSizeFromValue(QCss::Value value, QFont* font, int* fontSizeAdjustment)
{
if (font != nullptr && fontSizeAdjustment != nullptr)
{
if (value.type == Value::KnownIdentifier)
{
bool valid = true;
switch (value.variant.toInt())
{
case Value_Small: *fontSizeAdjustment = -1; break;
case Value_Medium: *fontSizeAdjustment = 0; break;
case Value_Large: *fontSizeAdjustment = 1; break;
case Value_XLarge: *fontSizeAdjustment = 2; break;
case Value_XXLarge: *fontSizeAdjustment = 3; break;
default: valid = false; break;
}
return valid;
}
if (value.type != Value::Length)
return false;
bool valid = false;
QString s = value.variant.toString();
if (s.endsWith(QLatin1String("pt"), Qt::CaseInsensitive))
{
s.chop(2);
value.variant = s;
if (value.variant.convert(QMetaType((QMetaType::Type)qMetaTypeId<qreal>())))
{
font->setPointSizeF(value.variant.toReal());
valid = true;
}
}
else if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive))
{
s.chop(2);
value.variant = s;
if (value.variant.convert(QMetaType(QMetaType::Int)))
{
font->setPixelSize(value.variant.toInt());
valid = true;
}
}
return valid;
}
return false;
}
static bool setFontStyleFromValue(const QCss::Value& value, QFont* font)
{
if (value.type != Value::KnownIdentifier)
return false ;
if (font != nullptr)
{
switch (value.variant.toInt())
{
case Value_Normal: font->setStyle(QFont::StyleNormal); return true;
case Value_Italic: font->setStyle(QFont::StyleItalic); return true;
case Value_Oblique: font->setStyle(QFont::StyleOblique); return true;
default: break;
}
}
return false;
}
static QFont::Weight IntToFontWeight(int i) noexcept
{
if (i <= QFont::Weight::Thin)
return QFont::Weight::Thin;
else if (i <= QFont::Weight::ExtraLight)
return QFont::Weight::ExtraLight;
else if (i <= QFont::Weight::Light)
return QFont::Weight::Light;
else if (i <= QFont::Weight::Normal)
return QFont::Weight::Normal;
else if (i <= QFont::Weight::Medium)
return QFont::Weight::Medium;
else if (i <= QFont::Weight::DemiBold)
return QFont::Weight::DemiBold;
else if (i <= QFont::Weight::Bold)
return QFont::Weight::Bold;
else if (i <= QFont::Weight::ExtraBold)
return QFont::Weight::ExtraBold;
return QFont::Weight::Black;
}
static bool setFontWeightFromValue(const QCss::Value& value, QFont* font)
{
if (font != nullptr)
{
if (value.type == Value::KnownIdentifier)
{
switch (value.variant.toInt())
{
case Value_Normal: font->setWeight(QFont::Normal); return true;
case Value_Bold: font->setWeight(QFont::Bold); return true;
default: break;
}
return false;
}
if (value.type != Value::Number)
return false;
font->setWeight(IntToFontWeight(value.variant.toInt()));
}
return true;
}
/** \internal
parse the font family from the values (starting from index \a start)
and set it the \a font
The function returns \c true if a family was extracted.
*/
static bool setFontFamilyFromValues(const QVector<QCss::Value>& values, QFont* font, int start = 0)
{
QString family;
bool shouldAddSpace = false;
for (int i = start; i < values.count(); ++i)
{
const QCss::Value& v = values.at(i);
if (v.type == Value::TermOperatorComma)
{
family += QLatin1Char(',');
shouldAddSpace = false;
continue;
}
const QString str = v.variant.toString();
if (str.isEmpty())
break;
if (shouldAddSpace)
family += QLatin1Char(' ');
family += str;
shouldAddSpace = true;
}
if (family.isEmpty())
return false;
if (font != nullptr)
font->setFamily(family);
return true;
}
static void setTextDecorationFromValues(const QVector<QCss::Value>& values, QFont* font)
{
if (font != nullptr)
{
for (int i = 0; i < values.count(); ++i)
{
if (values.at(i).type != Value::KnownIdentifier)
continue;
switch (values.at(i).variant.toInt())
{
case Value_Underline: font->setUnderline(true); break;
case Value_Overline: font->setOverline(true); break;
case Value_LineThrough: font->setStrikeOut(true); break;
case Value_None:
font->setUnderline(false);
font->setOverline(false);
font->setStrikeOut(false);
break;
default: break;
}
}
}
}
static void parseShorthandFontProperty(const QVector<QCss::Value>& values, QFont* font, int* fontSizeAdjustment)
{
if (font != nullptr)
{
font->setStyle(QFont::StyleNormal);
font->setWeight(QFont::Normal);
*fontSizeAdjustment = -255;
int i = 0;
while (i < values.count())
{
if (setFontStyleFromValue(values.at(i), font)
|| setFontWeightFromValue(values.at(i), font))
++i;
else
break;
}
if (i < values.count())
{
setFontSizeFromValue(values.at(i), font, fontSizeAdjustment);
++i;
}
if (i < values.count())
{
setFontFamilyFromValues(values, font, i);
}
}
}
static void setFontVariantFromValue(const QCss::Value& value, QFont* font)
{
if (font != nullptr)
{
if (value.type == Value::KnownIdentifier)
{
switch (value.variant.toInt())
{
case Value_Normal: font->setCapitalization(QFont::MixedCase); break;
case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break;
default: break;
}
}
}
}
static void setTextTransformFromValue(const QCss::Value& value, QFont* font)
{
if (font != nullptr)
{
if (value.type == Value::KnownIdentifier)
{
switch (value.variant.toInt())
{
case Value_None: font->setCapitalization(QFont::MixedCase); break;
case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break;
case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break;
default: break;
}
}
}
}
bool ValueExtractor::extractFont(QFont* font, int* fontSizeAdjustment)
{
if (font != nullptr)
{
if (fontExtracted)
{
*font = f;
*fontSizeAdjustment = adjustment;
return fontExtracted == 1;
}
bool hit = false;
for (int i = 0; i < declarations.count(); ++i)
{
const Declaration& decl = declarations.at(i);
if (decl.d->values.isEmpty())
continue;
const QCss::Value& val = decl.d->values.at(0);
switch (decl.d->propertyId)
{
case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break;
case FontStyle: setFontStyleFromValue(val, font); break;
case FontWeight: setFontWeightFromValue(val, font); break;
case FontFamily: setFontFamilyFromValues(decl.d->values, font); break;
case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break;
case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break;
case FontVariant: setFontVariantFromValue(val, font); break;
case TextTransform: setTextTransformFromValue(val, font); break;
default: continue;
}
hit = true;
}
f = *font;
adjustment = *fontSizeAdjustment;
fontExtracted = hit ? 1 : 2;
return hit;
}
return false;
}
bool ValueExtractor::extractPalette(QBrush* fg, QBrush* sfg, QBrush* sbg, QBrush* abg)
{
bool hit = false;
if (fg != nullptr && sfg != nullptr && sbg != nullptr && abg != nullptr)
{
for (int i = 0; i < declarations.count(); ++i)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case Color: *fg = decl.brushValue(pal); break;
case QtSelectionForeground: *sfg = decl.brushValue(pal); break;
case QtSelectionBackground: *sbg = decl.brushValue(pal); break;
case QtAlternateBackground: *abg = decl.brushValue(pal); break;
default: continue;
}
hit = true;
}
}
return hit;
}
void ValueExtractor::extractFont()
{
if (fontExtracted)
return;
int dummy = -255;
extractFont(&f, &dummy);
}
bool ValueExtractor::extractImage(QIcon* icon, Qt::Alignment* a, QSize* size)
{
bool hit = false;
if (icon != nullptr && a != nullptr && size != nullptr)
{
for (int i = 0; i < declarations.count(); ++i)
{
const Declaration& decl = declarations.at(i);
switch (decl.d->propertyId)
{
case QtImage:
*icon = decl.iconValue();
if (decl.d->values.count() > 0 && decl.d->values.at(0).type == Value::Uri)
{
// try to pull just the size from the image...
QImageReader imageReader(decl.d->values.at(0).variant.toString());
if ((*size = imageReader.size()).isNull())
{
// but we'll have to load the whole image if the
// format doesn't support just reading the size
*size = imageReader.read().size();
}
}
break;
case QtImageAlignment: *a = decl.alignmentValue(); break;
default: continue;
}
hit = true;
}
}
return hit;
}
///////////////////////////////////////////////////////////////////////////////
// Declaration
QColor Declaration::colorValue(const QPalette& pal) const
{
if (d->values.count() != 1)
return QColor();
if (d->parsed.isValid())
{
if (d->parsed.typeId() == QMetaType::QColor)
return qvariant_cast<QColor>(d->parsed);
if (d->parsed.typeId() == QMetaType::Int)
return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
}
ColorData color = parseColorValue(d->values.at(0));
if (color.type == ColorData::Role)
{
d->parsed = QVariant::fromValue<int>(color.role);
return pal.color((QPalette::ColorRole)(color.role));
}
else
{
d->parsed = QVariant::fromValue<QColor>(color.color);
return color.color;
}
}
QBrush Declaration::brushValue(const QPalette& pal) const
{
if (d->values.count() != 1)
return QBrush();
if (d->parsed.isValid())
{
if (d->parsed.typeId() == QMetaType::QBrush)
return qvariant_cast<QBrush>(d->parsed);
if (d->parsed.typeId() == QMetaType::Int)
return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
}
BrushData data = parseBrushValue(d->values.at(0), pal);
if (data.type == BrushData::Role)
{
d->parsed = QVariant::fromValue<int>(data.role);
return pal.color((QPalette::ColorRole)(data.role));
}
else
{
if (data.type != BrushData::DependsOnThePalette)
d->parsed = QVariant::fromValue<QBrush>(data.brush);
return data.brush;
}
}
void Declaration::brushValues(QBrush* c, const QPalette& pal) const
{
int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value.
// the bit 4 say we need to update d->parsed
int i = 0;
if (c != nullptr)
{
if (d->parsed.isValid())
{
needParse = 0;
QList<QVariant> v = d->parsed.toList();
for (i = 0; i < qMin(v.count(), 4); i++)
{
if (v.at(i).typeId() == QMetaType::QBrush)
{
c[i] = qvariant_cast<QBrush>(v.at(i));
}
else if (v.at(i).typeId() == QMetaType::Int)
{
c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
}
else
{
needParse |= (1 << i);
}
}
}
if (needParse != 0)
{
QList<QVariant> v;
for (i = 0; i < qMin(d->values.count(), 4); i++)
{
if (!(needParse & (1 << i)))
continue;
BrushData data = parseBrushValue(d->values.at(i), pal);
if (data.type == BrushData::Role)
{
v += QVariant::fromValue<int>(data.role);
c[i] = pal.color((QPalette::ColorRole)(data.role));
}
else
{
if (data.type != BrushData::DependsOnThePalette)
{
v += QVariant::fromValue<QBrush>(data.brush);
}
else
{
v += QVariant();
}
c[i] = data.brush;
}
}
if (needParse & 0x10)
d->parsed = v;
}
if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush();
else if (i == 1) c[3] = c[2] = c[1] = c[0];
else if (i == 2) c[2] = c[0], c[3] = c[1];
else if (i == 3) c[3] = c[1];
}
}
bool Declaration::realValue(qreal* real, const char* unit) const
{
if (d->values.count() != 1)
return false;
const Value& v = d->values.at(0);
if (unit != nullptr && v.type != Value::Length)
return false;
QString s = v.variant.toString();
if (unit != nullptr)
{
if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive))
return false;
s.chop(qstrlen(unit));
}
bool ok = false;
qreal val = s.toDouble(&ok);
if (ok && real != nullptr)
*real = val;
return ok;
}
static bool intValueHelper(const QCss::Value& v, int* i, const char* unit)
{
if (unit && v.type != Value::Length)
return false;
QString s = v.variant.toString();
if (unit)
{
if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive))
return false;
s.chop(qstrlen(unit));
}
bool ok = false;
int val = s.toInt(&ok);
if (ok && i != nullptr)
*i = val;
return ok;
}
bool Declaration::intValue(int* i, const char* unit) const
{
if (d->values.count() != 1)
return false;
return intValueHelper(d->values.at(0), i, unit);
}
QSize Declaration::sizeValue() const
{
if (d->parsed.isValid())
return qvariant_cast<QSize>(d->parsed);
int x[2] = { 0, 0 };
if (d->values.count() > 0)
intValueHelper(d->values.at(0), &x[0], "px");
if (d->values.count() > 1)
intValueHelper(d->values.at(1), &x[1], "px");
else
x[1] = x[0];
QSize size(x[0], x[1]);
d->parsed = QVariant::fromValue<QSize>(size);
return size;
}
QRect Declaration::rectValue() const
{
if (d->values.count() != 1)
return QRect();
if (d->parsed.isValid())
return qvariant_cast<QRect>(d->parsed);
const QCss::Value& v = d->values.at(0);
if (v.type != Value::Function)
return QRect();
QStringList func = v.variant.toStringList();
if (func.count() != 2 || func.at(0).compare(QLatin1String("rect")) != 0)
return QRect();
QStringList args = func[1].split(QLatin1Char(' '), Qt::SplitBehaviorFlags::SkipEmptyParts);
if (args.count() != 4)
return QRect();
QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt());
d->parsed = QVariant::fromValue<QRect>(rect);
return rect;
}
void Declaration::colorValues(QColor* c, const QPalette& pal) const
{
int i;
if (c != nullptr)
{
if (d->parsed.isValid())
{
QList<QVariant> v = d->parsed.toList();
for (i = 0; i < qMin(d->values.count(), 4); i++)
{
if (v.at(i).typeId() == QMetaType::QColor)
{
c[i] = qvariant_cast<QColor>(v.at(i));
}
else
{
c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
}
}
}
else
{
QList<QVariant> v;
for (i = 0; i < qMin(d->values.count(), 4); i++)
{
ColorData color = parseColorValue(d->values.at(i));
if (color.type == ColorData::Role)
{
v += QVariant::fromValue<int>(color.role);
c[i] = pal.color((QPalette::ColorRole)(color.role));
}
else
{
v += QVariant::fromValue<QColor>(color.color);
c[i] = color.color;
}
}
d->parsed = v;
}
if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor();
else if (i == 1) c[3] = c[2] = c[1] = c[0];
else if (i == 2) c[2] = c[0], c[3] = c[1];
else if (i == 3) c[3] = c[1];
}
}
BorderStyle Declaration::styleValue() const
{
if (d->values.count() != 1)
return BorderStyle_None;
return parseStyleValue(d->values.at(0));
}
void Declaration::styleValues(BorderStyle* s) const
{
if (s != nullptr)
{
int i;
for (i = 0; i < qMin(d->values.count(), 4); i++)
s[i] = parseStyleValue(d->values.at(i));
if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None;
else if (i == 1) s[3] = s[2] = s[1] = s[0];
else if (i == 2) s[2] = s[0], s[3] = s[1];
else if (i == 3) s[3] = s[1];
}
}
Repeat Declaration::repeatValue() const
{
if (d->parsed.isValid())
return static_cast<Repeat>(d->parsed.toInt());
if (d->values.count() != 1)
return Repeat_Unknown;
const auto v = findKnownValue(d->values.at(0).variant.toString(),
repeats, NumKnownRepeats);
d->parsed = v;
return static_cast<Repeat>(v);
}
Origin Declaration::originValue() const
{
if (d->parsed.isValid())
return static_cast<Origin>(d->parsed.toInt());
if (d->values.count() != 1)
return Origin_Unknown;
const auto v = findKnownValue(d->values.at(0).variant.toString(),
origins, NumKnownOrigins);
d->parsed = v;
return static_cast<Origin>(v);
}
PositionMode Declaration::positionValue() const
{
if (d->parsed.isValid())
return static_cast<PositionMode>(d->parsed.toInt());
if (d->values.count() != 1)
return PositionMode_Unknown;
const auto v = findKnownValue(d->values.at(0).variant.toString(),
positions, NumKnownPositionModes);
d->parsed = v;
return static_cast<PositionMode>(v);
}
Attachment Declaration::attachmentValue() const
{
if (d->parsed.isValid())
return static_cast<Attachment>(d->parsed.toInt());
if (d->values.count() != 1)
return Attachment_Unknown;
const auto v = findKnownValue(d->values.at(0).variant.toString(),
attachments, NumKnownAttachments);
d->parsed = v;
return static_cast<Attachment>(v);
}
int Declaration::styleFeaturesValue() const
{
Q_ASSERT(d->propertyId == QtStyleFeatures);
if (d->parsed.isValid())
return d->parsed.toInt();
int features = StyleFeature_None;
for (int i = 0; i < d->values.count(); i++)
{
features |= static_cast<int>(findKnownValue(d->values.value(i).variant.toString(),
styleFeatures, NumKnownStyleFeatures));
}
d->parsed = features;
return features;
}
QString Declaration::uriValue() const
{
if (d->values.isEmpty() || d->values.at(0).type != Value::Uri)
return QString();
return d->values.at(0).variant.toString();
}
Qt::Alignment Declaration::alignmentValue() const
{
if (d->parsed.isValid())
return Qt::Alignment(d->parsed.toInt());
if (d->values.isEmpty() || d->values.count() > 2)
return Qt::AlignLeft | Qt::AlignTop;
Qt::Alignment v = parseAlignment(d->values.constData(), d->values.count());
d->parsed = int(v);
return v;
}
void Declaration::borderImageValue(QString* image, int* cuts,
TileMode* h, TileMode* v) const
{
if (image != nullptr && cuts != nullptr && h != nullptr && v != nullptr)
{
*image = uriValue();
for (int i = 0; i < 4; i++)
cuts[i] = -1;
*h = *v = TileMode_Stretch;
if (d->values.count() < 2)
return;
if (d->values.at(1).type == Value::Number) // cuts!
{
int i;
for (i = 0; i < qMin(d->values.count() - 1, 4); i++)
{
const Value& v = d->values.at(i + 1);
if (v.type != Value::Number)
break;
cuts[i] = v.variant.toString().toInt();
}
if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0;
else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0];
else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1];
else if (i == 3) cuts[3] = cuts[1];
}
if (d->values.last().type == Value::Identifier)
{
*v = static_cast<TileMode>(findKnownValue(d->values.last().variant.toString(),
tileModes, NumKnownTileModes));
}
if (d->values[d->values.count() - 2].type == Value::Identifier)
{
*h = static_cast<TileMode>
(findKnownValue(d->values[d->values.count() - 2].variant.toString(),
tileModes, NumKnownTileModes));
}
else
*h = *v;
}
}
QIcon Declaration::iconValue() const
{
if (d->parsed.isValid())
return qvariant_cast<QIcon>(d->parsed);
QIcon icon;
for (int i = 0; i < d->values.count();)
{
const Value& value = d->values.at(i++);
if (value.type != Value::Uri)
break;
QString uri = value.variant.toString();
QIcon::Mode mode = QIcon::Normal;
QIcon::State state = QIcon::Off;
for (int j = 0; j < 2; j++)
{
if (i != d->values.count() && d->values.at(i).type == Value::KnownIdentifier)
{
switch (d->values.at(i).variant.toInt())
{
case Value_Disabled: mode = QIcon::Disabled; break;
case Value_Active: mode = QIcon::Active; break;
case Value_Selected: mode = QIcon::Selected; break;
case Value_Normal: mode = QIcon::Normal; break;
case Value_On: state = QIcon::On; break;
case Value_Off: state = QIcon::Off; break;
default: break;
}
++i;
}
else
{
break;
}
}
// QIcon is soo broken
if (icon.isNull())
icon = QIcon(uri);
else
icon.addPixmap(uri, mode, state);
if (i == d->values.count())
break;
if (d->values.at(i).type == Value::TermOperatorComma)
i++;
}
d->parsed = QVariant::fromValue<QIcon>(icon);
return icon;
}
///////////////////////////////////////////////////////////////////////////////
// Selector
int Selector::specificity() const
{
int val = 0;
for (int i = 0; i < basicSelectors.count(); ++i)
{
const BasicSelector& sel = basicSelectors.at(i);
if (!sel.elementName.isEmpty())
val += 1;
val += (sel.pseudos.count() + sel.attributeSelectors.count()) * 0x10;
val += sel.ids.count() * 0x100;
}
return val;
}
QString Selector::pseudoElement() const
{
const BasicSelector& bs = basicSelectors.last();
if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown)
return bs.pseudos.at(0).name;
return QString();
}
quint64 Selector::pseudoClass(quint64* negated) const
{
const BasicSelector& bs = basicSelectors.last();
if (bs.pseudos.isEmpty())
return PseudoClass_Unspecified;
quint64 pc = PseudoClass_Unknown;
for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.count(); i++)
{
const Pseudo& pseudo = bs.pseudos.at(i);
if (pseudo.type == PseudoClass_Unknown)
return PseudoClass_Unknown;
if (!pseudo.negated)
pc |= pseudo.type;
else if (negated)
*negated |= pseudo.type;
}
return pc;
}
///////////////////////////////////////////////////////////////////////////////
// StyleSheet
void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity)
{
QVector<StyleRule> universals;
for (int i = 0; i < styleRules.count(); ++i)
{
const StyleRule& rule = styleRules.at(i);
QVector<Selector> universalsSelectors;
for (int j = 0; j < rule.selectors.count(); ++j)
{
const Selector& selector = rule.selectors.at(j);
if (selector.basicSelectors.isEmpty())
continue;
if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation)
{
if (selector.basicSelectors.count() != 1)
continue;
}
else if (selector.basicSelectors.count() <= 1)
{
continue;
}
const BasicSelector& sel = selector.basicSelectors.at(selector.basicSelectors.count() - 1);
if (!sel.ids.isEmpty())
{
StyleRule nr;
nr.selectors += selector;
nr.declarations = rule.declarations;
nr.order = i;
idIndex.insert(sel.ids.at(0), nr);
}
else if (!sel.elementName.isEmpty())
{
StyleRule nr;
nr.selectors += selector;
nr.declarations = rule.declarations;
nr.order = i;
QString name = sel.elementName;
if (nameCaseSensitivity == Qt::CaseInsensitive)
name = name.toLower();
nameIndex.insert(name, nr);
}
else
{
universalsSelectors += selector;
}
}
if (!universalsSelectors.isEmpty())
{
StyleRule nr;
nr.selectors = universalsSelectors;
nr.declarations = rule.declarations;
nr.order = i;
universals << nr;
}
}
styleRules = universals;
}
///////////////////////////////////////////////////////////////////////////////
// StyleSelector
StyleSelector::~StyleSelector()
{
}
bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
{
return nodeNames(node).contains(nodeName, nameCaseSensitivity);
}
QStringList StyleSelector::nodeIds(NodePtr node) const
{
return QStringList(attribute(node, QLatin1String("id")));
}
bool StyleSelector::selectorMatches(const Selector& selector, NodePtr node)
{
if (selector.basicSelectors.isEmpty())
return false;
if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation)
{
if (selector.basicSelectors.count() != 1)
return false;
return basicSelectorMatches(selector.basicSelectors.at(0), node);
}
if (selector.basicSelectors.count() <= 1)
return false;
int i = selector.basicSelectors.count() - 1;
node = duplicateNode(node);
bool match = true;
BasicSelector sel = selector.basicSelectors.at(i);
do
{
match = basicSelectorMatches(sel, node);
if (!match)
{
if (sel.relationToNext == BasicSelector::MatchNextSelectorIfParent
|| i == selector.basicSelectors.count() - 1) // first element must always match!
break;
}
if (match || sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor)
--i;
if (i < 0)
break;
sel = selector.basicSelectors.at(i);
if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
|| sel.relationToNext == BasicSelector::MatchNextSelectorIfParent)
{
const auto nextParent = parentNode(node);
freeNode(node);
node = nextParent;
}
else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfPreceeds)
{
const auto previousSibling = previousSiblingNode(node);
freeNode(node);
node = previousSibling;
}
if (isNullNode(node))
{
match = false;
break;
}
}
while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor));
freeNode(node);
return match;
}
bool StyleSelector::basicSelectorMatches(const BasicSelector& sel, NodePtr node)
{
if (!sel.attributeSelectors.isEmpty())
{
if (!hasAttributes(node))
return false;
for (int i = 0; i < sel.attributeSelectors.count(); ++i)
{
const QCss::AttributeSelector& a = sel.attributeSelectors.at(i);
const QString attrValue = attribute(node, a.name);
if (attrValue.isNull())
return false;
if (a.valueMatchCriterium == QCss::AttributeSelector::MatchContains)
{
QStringList lst = attrValue.split(QLatin1Char(' '));
if (!lst.contains(a.value))
return false;
}
else if (
(a.valueMatchCriterium == QCss::AttributeSelector::MatchEqual
&& attrValue != a.value)
||
(a.valueMatchCriterium == QCss::AttributeSelector::MatchBeginsWith
&& !attrValue.startsWith(a.value))
)
return false;
}
}
if (!sel.elementName.isEmpty()
&& !nodeNameEquals(node, sel.elementName))
return false;
if (!sel.ids.isEmpty()
&& sel.ids != nodeIds(node))
return false;
return true;
}
void StyleSelector::matchRule(NodePtr node, const StyleRule& rule, StyleSheetOrigin origin,
int depth, QMap<uint, StyleRule>* weightedRules)
{
for (int j = 0; j < rule.selectors.count(); ++j)
{
const Selector& selector = rule.selectors.at(j);
if (selectorMatches(selector, node))
{
uint const weight = rule.order
+ selector.specificity() * 0x100
+ (uint(origin) + depth) * 0x100000;
StyleRule newRule = rule;
if (rule.selectors.count() > 1)
{
newRule.selectors.resize(1);
newRule.selectors[0] = selector;
}
//We might have rules with the same weight if they came from a rule with several selectors
if (weightedRules != nullptr)
weightedRules->insert(weight, newRule);
}
}
}
// Returns style rules that are in ascending order of specificity
// Each of the StyleRule returned will contain exactly one Selector
QVector<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
{
QVector<StyleRule> rules;
if (styleSheets.isEmpty())
return rules;
QMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below
//prune using indexed stylesheet
for (int sheetIdx = 0; sheetIdx < styleSheets.count(); ++sheetIdx)
{
const StyleSheet& styleSheet = styleSheets.at(sheetIdx);
for (int i = 0; i < styleSheet.styleRules.count(); ++i)
{
matchRule(node, styleSheet.styleRules.at(i), styleSheet.origin, styleSheet.depth, &weightedRules);
}
if (!styleSheet.idIndex.isEmpty())
{
QStringList ids = nodeIds(node);
for (int i = 0; i < ids.count(); i++)
{
const QString& key = ids.at(i);
QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.idIndex.constFind(key);
while (it != styleSheet.idIndex.constEnd() && it.key() == key)
{
matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
++it;
}
}
}
if (!styleSheet.nameIndex.isEmpty())
{
QStringList names = nodeNames(node);
for (int i = 0; i < names.count(); i++)
{
QString name = names.at(i);
if (nameCaseSensitivity == Qt::CaseInsensitive)
name = name.toLower();
QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.nameIndex.constFind(name);
while (it != styleSheet.nameIndex.constEnd() && it.key() == name)
{
matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
++it;
}
}
}
if (!medium.isEmpty())
{
for (int i = 0; i < styleSheet.mediaRules.count(); ++i)
{
if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive))
{
for (int j = 0; j < styleSheet.mediaRules.at(i).styleRules.count(); ++j)
{
matchRule(node, styleSheet.mediaRules.at(i).styleRules.at(j), styleSheet.origin,
styleSheet.depth, &weightedRules);
}
}
}
}
}
rules.reserve(weightedRules.count());
QMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin();
for ( ; it != weightedRules.constEnd() ; ++it)
rules += *it;
return rules;
}
// for qtexthtmlparser which requires just the declarations with Enabled state
// and without pseudo elements
QVector<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char* extraPseudo)
{
QVector<Declaration> decls;
QVector<StyleRule> rules = styleRulesForNode(node);
for (int i = 0; i < rules.count(); i++)
{
const Selector& selector = rules.at(i).selectors.at(0);
const QString pseudoElement = selector.pseudoElement();
if (extraPseudo && pseudoElement == QLatin1String(extraPseudo))
{
decls += rules.at(i).declarations;
continue;
}
if (!pseudoElement.isEmpty()) // skip rules with pseudo elements
continue;
quint64 const pseudoClass = selector.pseudoClass();
if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified)
decls += rules.at(i).declarations;
}
return decls;
}
QString Symbol::lexem() const
{
QString result;
if (len > 0)
result.reserve(len);
for (int i = 0; i < len; ++i)
{
if (text.at(start + i) == QLatin1Char('\\') && i < len - 1)
++i;
result += text.at(start + i);
}
return result;
}
Parser::Parser(const QString& css, bool isFile)
{
init(css, isFile);
}
Parser::Parser() noexcept
{
index = 0;
errorIndex = -1;
hasEscapeSequences = false;
}
void Parser::init(const QString& css, bool isFile)
{
QString styleSheet = css;
if (isFile)
{
QFile file(css);
if (file.open(QFile::ReadOnly))
{
sourcePath = QFileInfo(styleSheet).absolutePath() + QLatin1Char('/');
QTextStream stream(&file);
styleSheet = stream.readAll();
}
else
{
qWarning() << "QCss::Parser - Failed to load file " << css;
styleSheet.clear();
}
}
else
{
sourcePath.clear();
}
hasEscapeSequences = false;
symbols.resize(0);
symbols.reserve(8);
Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols);
index = 0;
errorIndex = -1;
}
bool Parser::parse(StyleSheet* styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
{
if (testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("charset")))
{
if (!next(STRING)) return false;
if (!next(SEMICOLON)) return false;
}
while (test(S) || test(CDO) || test(CDC)) {}
if (styleSheet != nullptr)
{
while (testImport())
{
ImportRule rule;
if (!parseImport(&rule)) return false;
styleSheet->importRules.append(rule);
while (test(S) || test(CDO) || test(CDC)) {}
}
do
{
if (testMedia())
{
MediaRule rule;
if (!parseMedia(&rule)) return false;
styleSheet->mediaRules.append(rule);
}
else if (testPage())
{
PageRule rule;
if (!parsePage(&rule)) return false;
styleSheet->pageRules.append(rule);
}
else if (testRuleset())
{
StyleRule rule;
if (!parseRuleset(&rule)) return false;
styleSheet->styleRules.append(rule);
}
else if (test(ATKEYWORD_SYM))
{
if (!until(RBRACE)) return false;
}
else if (hasNext())
{
return false;
}
while (test(S) || test(CDO) || test(CDC)) {}
}
while (hasNext());
styleSheet->buildIndexes(nameCaseSensitivity);
}
return true;
}
Symbol Parser::errorSymbol()
{
if (errorIndex == -1) return Symbol();
return symbols.at(errorIndex);
}
static inline void removeOptionalQuotes(QString* str)
{
if (str != nullptr)
{
if (!str->startsWith(QLatin1Char('\''))
&& !str->startsWith(QLatin1Char('\"')))
return;
str->remove(0, 1);
str->chop(1);
}
}
bool Parser::parseImport(ImportRule* importRule)
{
if (importRule != nullptr)
{
skipSpace();
if (test(STRING))
{
importRule->href = lexem();
}
else
{
if (!testAndParseUri(&importRule->href)) return false;
}
removeOptionalQuotes(&importRule->href);
skipSpace();
if (testMedium())
{
if (!parseMedium(&importRule->media)) return false;
while (test(COMMA))
{
skipSpace();
if (!parseNextMedium(&importRule->media)) return false;
}
}
if (!next(SEMICOLON)) return false;
skipSpace();
}
return true;
}
bool Parser::parseMedia(MediaRule* mediaRule)
{
if (mediaRule != nullptr)
{
do
{
skipSpace();
if (!parseNextMedium(&mediaRule->media)) return false;
}
while (test(COMMA));
if (!next(LBRACE)) return false;
skipSpace();
while (testRuleset())
{
StyleRule rule;
if (!parseRuleset(&rule)) return false;
mediaRule->styleRules.append(rule);
}
if (!next(RBRACE)) return false;
skipSpace();
}
return true;
}
bool Parser::parseMedium(QStringList* media)
{
if (media != nullptr)
{
media->append(lexem());
skipSpace();
}
return true;
}
bool Parser::parsePage(PageRule* pageRule)
{
if (pageRule != nullptr)
{
skipSpace();
if (testPseudoPage())
if (!parsePseudoPage(&pageRule->selector)) return false;
skipSpace();
if (!next(LBRACE)) return false;
do
{
skipSpace();
Declaration decl;
if (!parseNextDeclaration(&decl)) return false;
if (!decl.isEmpty())
pageRule->declarations.append(decl);
}
while (test(SEMICOLON));
if (!next(RBRACE)) return false;
skipSpace();
}
return true;
}
bool Parser::parsePseudoPage(QString* selector)
{
if (!next(IDENT)) return false;
*selector = lexem();
return true;
}
bool Parser::parseNextOperator(Value* value)
{
if (!hasNext()) return true;
if (value != nullptr)
{
switch (next())
{
case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break;
case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break;
default: prev(); break;
}
}
return true;
}
bool Parser::parseCombinator(BasicSelector::Relation* relation)
{
if (relation != nullptr)
{
*relation = BasicSelector::NoRelation;
if (lookup() == S)
{
*relation = BasicSelector::MatchNextSelectorIfAncestor;
skipSpace();
}
else
{
prev();
}
if (test(PLUS))
{
*relation = BasicSelector::MatchNextSelectorIfPreceeds;
}
else if (test(GREATER))
{
*relation = BasicSelector::MatchNextSelectorIfParent;
}
skipSpace();
}
return true;
}
bool Parser::parseProperty(Declaration* decl)
{
if (decl != nullptr)
{
decl->d->property = lexem();
decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties, NumProperties));
skipSpace();
}
return true;
}
bool Parser::parseRuleset(StyleRule* styleRule)
{
if (styleRule != nullptr)
{
Selector sel;
if (!parseSelector(&sel)) return false;
styleRule->selectors.append(sel);
while (test(COMMA))
{
skipSpace();
Selector sel;
if (!parseNextSelector(&sel)) return false;
styleRule->selectors.append(sel);
}
skipSpace();
if (!next(LBRACE)) return false;
const int declarationStart = index;
do
{
skipSpace();
Declaration decl;
const int rewind = index;
if (!parseNextDeclaration(&decl))
{
index = rewind;
const bool foundSemicolon = until(SEMICOLON);
const int semicolonIndex = index;
index = declarationStart;
const bool foundRBrace = until(RBRACE);
if (foundSemicolon && semicolonIndex < index)
{
decl = Declaration();
index = semicolonIndex - 1;
}
else
{
skipSpace();
return foundRBrace;
}
}
if (!decl.isEmpty())
styleRule->declarations.append(decl);
}
while (test(SEMICOLON));
if (!next(RBRACE)) return false;
skipSpace();
}
return true;
}
bool Parser::parseSelector(Selector* sel)
{
if (sel != nullptr)
{
BasicSelector basicSel;
if (!parseSimpleSelector(&basicSel)) return false;
while (testCombinator())
{
if (!parseCombinator(&basicSel.relationToNext)) return false;
if (!testSimpleSelector()) break;
sel->basicSelectors.append(basicSel);
basicSel = BasicSelector();
if (!parseSimpleSelector(&basicSel)) return false;
}
sel->basicSelectors.append(basicSel);
}
return true;
}
bool Parser::parseSimpleSelector(BasicSelector* basicSel)
{
if (basicSel == nullptr)
return false;
int minCount = 0;
if (lookupElementName())
{
if (!parseElementName(&basicSel->elementName)) return false;
}
else
{
prev();
minCount = 1;
}
bool onceMore;
int count = 0;
do
{
onceMore = false;
if (test(HASH))
{
QString theid = lexem();
// chop off leading #
theid.remove(0, 1);
basicSel->ids.append(theid);
onceMore = true;
}
else if (testClass())
{
onceMore = true;
AttributeSelector a;
a.name = QLatin1String("class");
a.valueMatchCriterium = AttributeSelector::MatchContains;
if (!parseClass(&a.value)) return false;
basicSel->attributeSelectors.append(a);
}
else if (testAttrib())
{
onceMore = true;
AttributeSelector a;
if (!parseAttrib(&a)) return false;
basicSel->attributeSelectors.append(a);
}
else if (testPseudo())
{
onceMore = true;
Pseudo ps;
if (!parsePseudo(&ps)) return false;
basicSel->pseudos.append(ps);
}
if (onceMore) ++count;
}
while (onceMore);
return count >= minCount;
}
bool Parser::parseClass(QString* name)
{
if (!next(IDENT)) return false;
if (name != nullptr)
*name = lexem();
return true;
}
bool Parser::parseElementName(QString* name)
{
if (name != nullptr)
{
switch (lookup())
{
case STAR: name->clear(); break;
case IDENT: *name = lexem(); break;
default: return false;
}
}
return true;
}
bool Parser::parseAttrib(AttributeSelector* attr)
{
skipSpace();
if (!next(IDENT)) return false;
if (attr != nullptr)
{
attr->name = lexem();
skipSpace();
if (test(EQUAL))
{
attr->valueMatchCriterium = AttributeSelector::MatchEqual;
}
else if (test(INCLUDES))
{
attr->valueMatchCriterium = AttributeSelector::MatchContains;
}
else if (test(DASHMATCH))
{
attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
}
else
{
return next(RBRACKET);
}
skipSpace();
if (!test(IDENT) && !test(STRING)) return false;
attr->value = unquotedLexem();
skipSpace();
}
return next(RBRACKET);
}
bool Parser::parsePseudo(Pseudo* pseudo)
{
std::ignore = test(COLON);
if (pseudo != nullptr)
{
pseudo->negated = test(EXCLAMATION_SYM);
if (test(IDENT))
{
pseudo->name = lexem();
pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos, NumPseudos));
return true;
}
if (!next(FUNCTION)) return false;
pseudo->function = lexem();
// chop off trailing parenthesis
pseudo->function.chop(1);
skipSpace();
if (!test(IDENT)) return false;
pseudo->name = lexem();
skipSpace();
}
return next(RPAREN);
}
bool Parser::parseNextDeclaration(Declaration* decl)
{
if (!testProperty())
return true; // not an error!
if (decl != nullptr)
{
if (!parseProperty(decl)) return false;
if (!next(COLON)) return false;
skipSpace();
if (!parseNextExpr(&decl->d->values)) return false;
if (testPrio())
if (!parsePrio(decl)) return false;
}
return true;
}
bool Parser::testPrio()
{
const int rewind = index;
if (!test(EXCLAMATION_SYM)) return false;
skipSpace();
if (!test(IDENT))
{
index = rewind;
return false;
}
if (lexem().compare(QLatin1String("important"), Qt::CaseInsensitive) != 0)
{
index = rewind;
return false;
}
return true;
}
bool Parser::parsePrio(Declaration* declaration)
{
if (declaration != nullptr)
declaration->d->important = true;
skipSpace();
return true;
}
bool Parser::parseExpr(QVector<Value>* values)
{
Value val;
if (!parseTerm(&val)) return false;
if (values != nullptr)
{
values->append(val);
bool onceMore;
do
{
onceMore = false;
val = Value();
if (!parseNextOperator(&val)) return false;
if (val.type != QCss::Value::Unknown)
values->append(val);
if (testTerm())
{
onceMore = true;
val = Value();
if (!parseTerm(&val)) return false;
values->append(val);
}
}
while (onceMore);
}
return true;
}
bool Parser::testTerm()
{
return test(PLUS) || test(MINUS)
|| test(NUMBER)
|| test(PERCENTAGE)
|| test(LENGTH)
|| test(STRING)
|| test(IDENT)
|| testHexColor()
|| testFunction();
}
bool Parser::parseTerm(Value* value)
{
QString str = lexem();
bool haveUnary = false;
if (lookup() == PLUS || lookup() == MINUS)
{
haveUnary = true;
if (!hasNext()) return false;
next();
str += lexem();
}
if (value != nullptr)
{
value->variant = str;
value->type = QCss::Value::String;
switch (lookup())
{
case NUMBER:
value->type = Value::Number;
value->variant.convert(QMetaType(QMetaType::Double));
break;
case PERCENTAGE:
value->type = Value::Percentage;
str.chop(1); // strip off %
value->variant = str;
break;
case LENGTH:
value->type = Value::Length;
break;
case STRING:
if (haveUnary) return false;
value->type = Value::String;
str.chop(1);
str.remove(0, 1);
value->variant = str;
break;
case IDENT:
{
if (haveUnary) return false;
value->type = Value::Identifier;
const int theid = findKnownValue(str, values, NumKnownValues);
if (theid != 0)
{
value->type = Value::KnownIdentifier;
value->variant = theid;
}
break;
}
default:
{
if (haveUnary) return false;
prev();
if (testHexColor())
{
QColor col;
if (!parseHexColor(&col)) return false;
value->type = Value::Color;
value->variant = col;
}
else if (testFunction())
{
QString name, args;
if (!parseFunction(&name, &args)) return false;
if (name == QLatin1String("url"))
{
value->type = Value::Uri;
removeOptionalQuotes(&args);
if (QFileInfo(args).isRelative() && !sourcePath.isEmpty())
{
args.prepend(sourcePath);
}
value->variant = args;
}
else
{
value->type = Value::Function;
value->variant = QStringList() << name << args;
}
}
else
{
return recordError();
}
return true;
}
}
skipSpace();
}
return true;
}
bool Parser::parseFunction(QString* name, QString* args)
{
if (name != nullptr && args != nullptr)
{
*name = lexem();
name->chop(1);
skipSpace();
const int start = index;
if (!until(RPAREN)) return false;
for (int i = start; i < index - 1; ++i)
args->append(symbols.at(i).lexem());
/*
if (!nextExpr(&arguments)) return false;
if (!next(RPAREN)) return false;
*/
skipSpace();
}
return true;
}
bool Parser::parseHexColor(QColor* col)
{
if (col != nullptr)
{
col->setNamedColor(lexem());
if (!col->isValid())
{
qWarning("QCssParser::parseHexColor: Unknown color name '%s'", lexem().toLatin1().constData());
return false;
}
skipSpace();
}
return true;
}
bool Parser::testAndParseUri(QString* uri)
{
const int rewind = index;
if (!testFunction()) return false;
QString name, args;
if (!parseFunction(&name, &args))
{
index = rewind;
return false;
}
if (name.toLower() != QLatin1String("url"))
{
index = rewind;
return false;
}
if (uri != nullptr)
{
*uri = args;
removeOptionalQuotes(uri);
}
return true;
}
bool Parser::testSimpleSelector()
{
return testElementName()
|| (test(HASH))
|| testClass()
|| testAttrib()
|| testPseudo();
}
bool Parser::next(QCss::TokenType t)
{
if (hasNext() && next() == t)
return true;
return recordError();
}
bool Parser::test(QCss::TokenType t) noexcept
{
if (index >= symbols.count())
return false;
if (symbols.at(index).token == t)
{
++index;
return true;
}
return false;
}
QString Parser::unquotedLexem() const
{
QString s = lexem();
if (lookup() == STRING)
{
s.chop(1);
s.remove(0, 1);
}
return s;
}
QString Parser::lexemUntil(QCss::TokenType t)
{
QString lexem;
while (hasNext() && next() != t)
lexem += symbol().lexem();
return lexem;
}
bool Parser::until(QCss::TokenType target, QCss::TokenType target2) noexcept
{
int braceCount = 0;
int brackCount = 0;
int parenCount = 0;
if (index)
{
switch (symbols.at(index - 1).token)
{
case LBRACE: ++braceCount; break;
case LBRACKET: ++brackCount; break;
case FUNCTION:
case LPAREN: ++parenCount; break;
default: ;
}
}
while (index < symbols.size())
{
QCss::TokenType const t = symbols.at(index++).token;
switch (t)
{
case LBRACE: ++braceCount; break;
case RBRACE: --braceCount; break;
case LBRACKET: ++brackCount; break;
case RBRACKET: --brackCount; break;
case FUNCTION:
case LPAREN: ++parenCount; break;
case RPAREN: --parenCount; break;
default: break;
}
if ((t == target || (target2 != NONE && t == target2))
&& braceCount <= 0
&& brackCount <= 0
&& parenCount <= 0)
return true;
if (braceCount < 0 || brackCount < 0 || parenCount < 0)
{
--index;
break;
}
}
return false;
}
bool Parser::testTokenAndEndsWith(QCss::TokenType t, QLatin1String str)
{
if (!test(t)) return false;
if (!lexem().endsWith(str, Qt::CaseInsensitive))
{
prev();
return false;
}
return true;
}