Implement missing support for 'em' and 'ex' lengths in style sheet

The Qt style sheet reference claimed that Length properties can be
specified in 'em' or 'ex' units, but that was never implemented.

Add the missing implementation. Since the sizes depend on the size of
the font of the current element, we cannot convert the units in the CSS
parser, but have to do so in the QRenderRule constructor, where we can
make a decision about which font to use if the style sheet itself doesn't
specify a font. Fall back to the widget font if possible; otherwise it
will be the application default font.

The implementation translates em into QFontMetrics.height, identical to
what is already done in the QCssParser. This is in line with the CSS
specification, but contradicts our previous documentation which stated
that 'em' means "width of M". Fix the documentation.

Fixes: QTBUG-8096
Pick-to: 6.2
Change-Id: I145e2504ae3b19101a0d0dd63653466b6c2cec1d
Done-with: Cristian Maureira-Fredes <Cristian.Maureira-Fredes@qt.io>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Volker Hilsheimer 2021-07-16 13:44:19 +02:00
parent 1a8de144b6
commit 33f9591e37
3 changed files with 80 additions and 23 deletions

View File

@ -3027,7 +3027,8 @@
\list
\li \c px: pixels
\li \c pt: the size of one point (i.e., 1/72 of an inch)
\li \c em: the em width of the font (i.e., the width of 'M')
\li \c em: the size relative to the font size of the element
(e.g., 2em means 2 times the size of the font)
\li \c ex: the x-height of the font (i.e., the height of 'x')
\endlist

View File

@ -1051,31 +1051,59 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject *
for (int i = 0; i < numKnownStyleHints; i++) {
QLatin1String styleHint(knownStyleHints[i]);
if (decl.d->property.compare(styleHint) == 0) {
QString hintName = QString(styleHint);
QVariant hintValue;
if (hintName.endsWith(QLatin1String("alignment"))) {
hintValue = (int) decl.alignmentValue();
} else if (hintName.endsWith(QLatin1String("color"))) {
hintValue = (int) decl.colorValue().rgba();
} else if (hintName.endsWith(QLatin1String("size"))) {
hintValue = decl.sizeValue();
} else if (hintName.endsWith(QLatin1String("icon"))) {
hintValue = decl.iconValue();
} else if (hintName == QLatin1String("button-layout")
&& decl.d->values.count() != 0 && decl.d->values.at(0).type == Value::String) {
hintValue = subControlLayout(decl.d->values.at(0).variant.toString());
} else {
int integer;
decl.intValue(&integer);
hintValue = integer;
}
styleHints[decl.d->property] = hintValue;
knownStyleHint = true;
break;
QString hintName = QString(styleHint);
QVariant hintValue;
if (hintName.endsWith(QLatin1String("alignment"))) {
hintValue = (int) decl.alignmentValue();
} else if (hintName.endsWith(QLatin1String("color"))) {
hintValue = (int) decl.colorValue().rgba();
} else if (hintName.endsWith(QLatin1String("size"))) {
// Check only for the 'em' case
const QString valueString = decl.d->values.at(0).variant.toString();
const bool isEmSize = valueString.endsWith(u"em", Qt::CaseInsensitive);
if (isEmSize || valueString.endsWith(u"ex", Qt::CaseInsensitive)) {
// 1em == size of font; 1ex == xHeight of font
// See lengthValueFromData helper in qcssparser.cpp
QFont fontForSize(font);
// if no font is specified, then use the widget font if possible
if (const QWidget *widget; !hasFont && (widget = qobject_cast<const QWidget*>(object)))
fontForSize = widget->font();
const QFontMetrics fontMetrics(fontForSize);
qreal pixelSize = isEmSize ? fontMetrics.height() : fontMetrics.xHeight();
// Transform size according to the 'em'/'ex' value
qreal emexSize = {};
if (decl.realValue(&emexSize, isEmSize ? "em" : "ex") && emexSize > 0) {
pixelSize *= emexSize;
const QSizeF newSize(pixelSize, pixelSize);
decl.d->parsed = QVariant::fromValue<QSizeF>(newSize);
hintValue = newSize;
} else {
qWarning("Invalid '%s' size for %s. Skipping.",
isEmSize ? "em" : "ex", qPrintable(valueString));
}
} else {
// Normal case where we receive a 'px' or 'pt' unit
hintValue = decl.sizeValue();
}
} else if (hintName.endsWith(QLatin1String("icon"))) {
hintValue = decl.iconValue();
} else if (hintName == QLatin1String("button-layout")
&& decl.d->values.count() != 0 && decl.d->values.at(0).type == Value::String) {
hintValue = subControlLayout(decl.d->values.at(0).variant.toString());
} else {
int integer;
decl.intValue(&integer);
hintValue = integer;
}
styleHints[decl.d->property] = hintValue;
knownStyleHint = true;
break;
}
}
if (!knownStyleHint)
qDebug("Unknown property %s", qPrintable(decl.d->property));
qWarning("Unknown property %s", qPrintable(decl.d->property));
}
}

View File

@ -2352,12 +2352,40 @@ void tst_QStyleSheetStyle::iconSizes_data()
smallFont.setPointSizeF(9.0);
QFont largeFont;
largeFont.setPointSizeF(24.0);
QFont hugeFont;
hugeFont.setPointSizeF(40.0);
QTest::addRow("default") << QString() << QFont() << QSize(defaultSize, defaultSize);
QTest::addRow("pixels") << "icon-size: 50px" << QFont() << QSize(50, 50);
QTest::addRow("points") << "icon-size: 20pt" << QFont() << QSize(15, 15);
QTest::addRow("pixels with font") << "icon-size: 50px" << smallFont << QSize(50, 50);
QTest::addRow("points with font") << "icon-size: 20pt" << largeFont << QSize(15, 15);
const QFontMetrics defaultMetrics{QFont()};
const QFontMetrics smallMetrics(smallFont);
const QFontMetrics largeMetrics(largeFont);
const QFontMetrics hugeMetrics(hugeFont);
QTest::addRow("1em, default font") << "icon-size: 1em"
<< QFont() << QSize(defaultMetrics.height(), defaultMetrics.height());
QTest::addRow("1em, small font") << "icon-size: 1em"
<< smallFont << QSize(smallMetrics.height(), smallMetrics.height());
QTest::addRow("1em, large font") << "icon-size: 1em"
<< largeFont << QSize(largeMetrics.height(), largeMetrics.height());
QTest::addRow("1.5em, lage font") << "icon-size: 1.5em"
<< largeFont << QSize(largeMetrics.height(), largeMetrics.height()) * 1.5;
QTest::addRow("2em with styled font") << "font-size: 40pt; icon-size: 2em"
<< QFont() << QSize(hugeMetrics.height(), hugeMetrics.height()) * 2;
QTest::addRow("1ex, default font") << "icon-size: 1ex"
<< QFont() << QSize(defaultMetrics.xHeight(), defaultMetrics.xHeight());
QTest::addRow("1ex, small font") << "icon-size: 1ex"
<< smallFont << QSize(smallMetrics.xHeight(), smallMetrics.xHeight());
QTest::addRow("1ex, large font") << "icon-size: 1ex"
<< largeFont << QSize(largeMetrics.xHeight(), largeMetrics.xHeight());
QTest::addRow("1.5ex, lage font") << "icon-size: 1.5ex"
<< largeFont << QSize(largeMetrics.xHeight(), largeMetrics.xHeight()) * 1.5;
QTest::addRow("2ex with styled font") << "font-size: 40pt; icon-size: 2ex"
<< QFont() << QSize(hugeMetrics.xHeight(), hugeMetrics.xHeight()) * 2;
}
void tst_QStyleSheetStyle::iconSizes()