From 7094466f7d0c2176eb3080021a4ea5d220555df9 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 30 Mar 2016 13:15:02 +0200 Subject: [PATCH] Interpret fixed CSS line-height as minimum rather than absolute The QTextBlockFormat::FixedHeight overrides the line height regardless of its calculated height. If the line contains objects or text which is higher than the specified line height, using FixedHeight will cause them to overlap with the previous line. This is not what happens in normal web browsers. The expected behavior is that the line height given in CSS is the minimum height, but that we still reserve space needed to display everything without overlaps. To make it possible for people to retain the old behavior, we introduce the -qt-line-height-type property, which allows them to override the default. This also fixes output from toHtml() to use the new property rather than set the minimum height of the paragraph or the "line-spacing" property, which does not exist in either CSS nor in Qt. [ChangeLog][QtGui][Important Behavior Changes] When line height is specified in pixels, this is now interpreted as the minimum line height rather than an absolute line height to avoid overlaps. To get the old behavior, use the -qt-line-height-type property in CSS and set it to "fixed". Task-number: QTBUG-51962 Change-Id: Ic2dde649b69209672170dad4c2de1e1c432a1078 Reviewed-by: Lars Knoll --- src/gui/text/qcssparser.cpp | 1 + src/gui/text/qcssparser_p.h | 1 + src/gui/text/qtextdocument.cpp | 17 ++-- src/gui/text/qtexthtmlparser.cpp | 44 ++++++++-- src/gui/text/qtexthtmlparser_p.h | 1 + .../text/qtextdocument/tst_qtextdocument.cpp | 88 ++++++++++++++++++- 6 files changed, 134 insertions(+), 18 deletions(-) diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index b9e05e726e0..53d4e3fa362 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -67,6 +67,7 @@ struct QCssKnownValue static const QCssKnownValue properties[NumProperties - 1] = { { "-qt-background-role", QtBackgroundRole }, { "-qt-block-indent", QtBlockIndent }, + { "-qt-line-height-type", QtLineHeightType }, { "-qt-list-indent", QtListIndent }, { "-qt-list-number-prefix", QtListNumberPrefix }, { "-qt-list-number-suffix", QtListNumberSuffix }, diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h index b7692d32aaa..4da7b62dbaa 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -189,6 +189,7 @@ enum Property { QtListNumberPrefix, QtListNumberSuffix, LineHeight, + QtLineHeightType, NumProperties }; diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index f50f771c07a..febd5da488c 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2763,26 +2763,25 @@ void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) } if (format.lineHeightType() != QTextBlockFormat::SingleHeight) { + html += QLatin1String(" line-height:") + + QString::number(format.lineHeight()); switch (format.lineHeightType()) { case QTextBlockFormat::ProportionalHeight: + html += QLatin1String("%;"); + break; case QTextBlockFormat::FixedHeight: - html += QLatin1String(" line-height:"); + html += QLatin1String("; -qt-line-height-type: fixed;"); break; case QTextBlockFormat::MinimumHeight: - html += QLatin1String(" min-height:"); + html += QLatin1String("px;"); break; case QTextBlockFormat::LineDistanceHeight: - html += QLatin1String(" line-spacing:"); + html += QLatin1String("; -qt-line-height-type: line-distance;"); break; - case QTextBlockFormat::SingleHeight: default: + html += QLatin1String(";"); break; // Should never reach here } - html += QString::number(format.lineHeight()); - if (format.lineHeightType() == QTextBlockFormat::ProportionalHeight) - html += QLatin1String("%;"); - else - html += QLatin1String("px;"); } emitPageBreakPolicy(format.pageBreakPolicy()); diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index d8e12f70241..576ff7d9350 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -492,7 +492,7 @@ static QString quoteNewline(const QString &s) QTextHtmlParserNode::QTextHtmlParserNode() : parent(0), id(Html_unknown), - cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false), + cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false), hasOwnLineHeightType(false), hasCssListIndent(false), isEmptyParagraph(false), isTextFrame(false), isRootFrame(false), displayMode(QTextHtmlElement::DisplayInline), hasHref(false), listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0), @@ -1198,20 +1198,48 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector case QCss::QtBlockIndent: blockFormat.setIndent(decl.d->values.first().variant.toInt()); break; - case QCss::LineHeight: { + case QCss::QtLineHeightType: { + QString lineHeightTypeName = decl.d->values.first().variant.toString(); + QTextBlockFormat::LineHeightTypes lineHeightType; + if (lineHeightTypeName.compare(QLatin1String("proportional"), Qt::CaseInsensitive) == 0) + lineHeightType = QTextBlockFormat::ProportionalHeight; + else if (lineHeightTypeName.compare(QLatin1String("fixed"), Qt::CaseInsensitive) == 0) + lineHeightType = QTextBlockFormat::FixedHeight; + else if (lineHeightTypeName.compare(QLatin1String("minimum"), Qt::CaseInsensitive) == 0) + lineHeightType = QTextBlockFormat::MinimumHeight; + else if (lineHeightTypeName.compare(QLatin1String("line-distance"), Qt::CaseInsensitive) == 0) + lineHeightType = QTextBlockFormat::LineDistanceHeight; + else + lineHeightType = QTextBlockFormat::SingleHeight; + + blockFormat.setProperty(QTextBlockFormat::LineHeightType, lineHeightType); + hasOwnLineHeightType = true; + } + break; + case QCss::LineHeight: { qreal lineHeight; + QTextBlockFormat::LineHeightTypes lineHeightType; if (decl.realValue(&lineHeight, "px")) { - blockFormat.setLineHeight(lineHeight, QTextBlockFormat::FixedHeight); + lineHeightType = QTextBlockFormat::MinimumHeight; } else { bool ok; QString value = decl.d->values.first().toString(); lineHeight = value.toDouble(&ok); - if (ok) - blockFormat.setLineHeight(lineHeight, QTextBlockFormat::ProportionalHeight); - else - blockFormat.setLineHeight(0, QTextBlockFormat::SingleHeight); + if (ok) { + lineHeightType = QTextBlockFormat::ProportionalHeight; + } else { + lineHeight = 0.0; + lineHeightType = QTextBlockFormat::SingleHeight; + } } - break; } + + // Only override line height type if specified in same node + if (hasOwnLineHeightType) + lineHeightType = QTextBlockFormat::LineHeightTypes(blockFormat.lineHeightType()); + + blockFormat.setLineHeight(lineHeight, lineHeightType); + break; + } case QCss::TextIndent: { qreal indent = 0; if (decl.realValue(&indent, "px")) diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h index 8e5a90be0b9..4c0dd967f94 100644 --- a/src/gui/text/qtexthtmlparser_p.h +++ b/src/gui/text/qtexthtmlparser_p.h @@ -171,6 +171,7 @@ struct QTextHtmlParserNode { QTextBlockFormat blockFormat; uint cssFloat : 2; uint hasOwnListStyle : 1; + uint hasOwnLineHeightType : 1; uint hasCssListIndent : 1; uint isEmptyParagraph : 1; uint isTextFrame : 1; diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp index 04e6bba91e7..1869139a986 100644 --- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp +++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp @@ -183,6 +183,8 @@ private slots: void textCursorUsageWithinContentsChange(); void cssInheritance(); + + void lineHeightType(); private: void backgroundImage_checkExpectedHtml(const QTextDocument &doc); void buildRegExpData(); @@ -3191,7 +3193,7 @@ void tst_QTextDocument::cssInheritance() "

Foo

Bar

Baz

"); QTextBlock block = td.begin(); QTextBlockFormat fmt = block.blockFormat(); - QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::FixedHeight)); + QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::MinimumHeight)); QCOMPARE(fmt.lineHeight(), qreal(40)); block = block.next(); fmt = block.blockFormat(); @@ -3276,5 +3278,89 @@ void tst_QTextDocument::cssInheritance() } } +void tst_QTextDocument::lineHeightType() +{ + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight)); + QCOMPARE(format.lineHeight(), 0.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight)); + QCOMPARE(format.lineHeight(), 40.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); + QCOMPARE(format.lineHeight(), 200.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight)); + QCOMPARE(format.lineHeight(), 200.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); + QCOMPARE(format.lineHeight(), 40.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); + QCOMPARE(format.lineHeight(), 10.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight)); + QCOMPARE(format.lineHeight(), 33.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); + QCOMPARE(format.lineHeight(), 200.0); + } + + { + QTextDocument td; + td.setHtml("Foobar"); + QTextBlock block = td.begin(); + QTextBlockFormat format = block.blockFormat(); + QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); + QCOMPARE(format.lineHeight(), 200.0); + } +} + QTEST_MAIN(tst_QTextDocument) #include "tst_qtextdocument.moc"