}
}
+void QQuickTextPrivate::updateBaseline(qreal baseline, qreal dy)
+{
+ Q_Q(QQuickText);
+
+ qreal yoff = 0;
+
+ if (q->heightValid()) {
+ if (vAlign == QQuickText::AlignBottom)
+ yoff = dy;
+ else if (vAlign == QQuickText::AlignVCenter)
+ yoff = dy/2;
+ }
+
+ q->setBaselineOffset(baseline + yoff);
+}
+
void QQuickTextPrivate::updateSize()
{
Q_Q(QQuickText);
return;
}
- QFontMetricsF fm(font);
- if (text.isEmpty()) {
- qreal fontHeight = fm.height();
+ if (text.isEmpty() && !isLineLaidOutConnected() && fontSizeMode() == QQuickText::FixedSize) {
+ // How much more expensive is it to just do a full layout on an empty string here?
+ // There may be subtle differences in the height and baseline calculations between
+ // QTextLayout and QFontMetrics and the number of variables that can affect the size
+ // and position of a line is increasing.
+ QFontMetricsF fm(font);
+ qreal fontHeight = qCeil(fm.height()); // QScriptLine and therefore QTextLine rounds up
+ if (!richText) { // line height, so we will as well.
+ fontHeight = lineHeightMode() == QQuickText::FixedHeight
+ ? lineHeight()
+ : fontHeight * lineHeight();
+ }
+ updateBaseline(fm.ascent(), q->height() - fontHeight);
q->setImplicitSize(0, fontHeight);
layedOutTextRect = QRectF(0, 0, 0, fontHeight);
emit q->contentSizeChanged();
qreal naturalWidth = 0;
- qreal dy = q->height();
QSizeF size(0, 0);
QSizeF previousSize = layedOutTextRect.size();
#if defined(Q_OS_MAC)
//setup instance of QTextLayout for all cases other than richtext
if (!richText) {
- QRectF textRect = setupTextLayout(&naturalWidth);
+ qreal baseline = 0;
+ QRectF textRect = setupTextLayout(&naturalWidth, &baseline);
if (internalWidthUpdate) // probably the result of a binding loop, but by letting it
return; // get this far we'll get a warning to that effect if it is.
layedOutTextRect = textRect;
size = textRect.size();
- dy -= size.height();
+ updateBaseline(baseline, q->height() - size.height());
} else {
singleline = false; // richtext can't elide or be optimized for single-line case
ensureDoc();
extra->doc->setTextWidth(q->width());
else
extra->doc->setTextWidth(extra->doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
- dy -= extra->doc->size().height();
QSizeF dsize = extra->doc->size();
layedOutTextRect = QRectF(QPointF(0,0), dsize);
size = QSizeF(extra->doc->idealWidth(),dsize.height());
- }
- qreal yoff = 0;
- if (q->heightValid()) {
- if (vAlign == QQuickText::AlignBottom)
- yoff = dy;
- else if (vAlign == QQuickText::AlignVCenter)
- yoff = dy/2;
+ QFontMetricsF fm(font);
+ updateBaseline(fm.ascent(), q->height() - size.height());
}
- q->setBaselineOffset(fm.ascent() + yoff);
//### need to comfirm cost of always setting these for richText
internalWidthUpdate = true;
already absolutely positioned horizontally).
*/
-QRectF QQuickTextPrivate::setupTextLayout(qreal *const naturalWidth)
+QRectF QQuickTextPrivate::setupTextLayout(qreal *const naturalWidth, qreal *const baseline)
{
Q_Q(QQuickText);
layout.setCacheEnabled(true);
internalWidthUpdate = wasInLayout;
}
- QFontMetrics fm(font);
+ QFontMetricsF fm(font);
qreal height = (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : fm.height() * lineHeight();
- return QRect(0, 0, 0, height);
+ *baseline = fm.ascent();
+ return QRectF(0, 0, 0, height);
}
qreal lineWidth = q->widthValid() && q->width() > 0 ? q->width() : FLT_MAX;
elideLayout = 0;
}
+ QTextLine firstLine = visibleCount == 1 && elideLayout
+ ? elideLayout->lineAt(0)
+ : layout.lineAt(0);
+ Q_ASSERT(firstLine.isValid());
+ *baseline = firstLine.y() + firstLine.ascent();
+
if (!customLayout)
br.setHeight(height);
void fontFormatSizes_data();
void fontFormatSizes();
+ void baselineOffset_data();
+ void baselineOffset();
+
private:
QStringList standard;
QStringList richText;
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), qreal(0));
- QCOMPARE(text->boundingRect().height(), QFontMetricsF(text->font()).height());
+ QCOMPARE(text->boundingRect().height(), qreal(qCeil(QFontMetricsF(text->font()).height())));
text->setText("Hello World");
delete view;
}
+typedef qreal (*ExpectedBaseline)(QQuickText *item);
+Q_DECLARE_METATYPE(ExpectedBaseline)
+
+static qreal expectedBaselineTop(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ return fm.ascent();
+}
+
+static qreal expectedBaselineBottom(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ return item->height() - item->contentHeight() + fm.ascent();
+}
+
+static qreal expectedBaselineCenter(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ return ((item->height() - item->contentHeight()) / 2) + fm.ascent();
+}
+
+static qreal expectedBaselineBold(QQuickText *item)
+{
+ QFont font = item->font();
+ font.setBold(true);
+ QFontMetricsF fm(font);
+ return fm.ascent();
+}
+
+static qreal expectedBaselineImage(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ // The line is positioned so the bottom of the line is aligned with the bottom of the image,
+ // or image height - line height and the baseline is line position + ascent. Because
+ // QTextLine's height is rounded up this can give slightly different results to image height
+ // - descent.
+ return 181 - qCeil(fm.height()) + fm.ascent();
+}
+
+static qreal expectedBaselineCustom(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ return 16 + fm.ascent();
+}
+
+static qreal expectedBaselineScaled(QQuickText *item)
+{
+ QFont font = item->font();
+ QTextLayout layout(item->text().replace(QLatin1Char('\n'), QChar::LineSeparator));
+ do {
+ layout.setFont(font);
+ qreal width = 0;
+ layout.beginLayout();
+ for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) {
+ line.setLineWidth(FLT_MAX);
+ width = qMax(line.naturalTextWidth(), width);
+ }
+ layout.endLayout();
+
+ if (width < item->width()) {
+ QFontMetricsF fm(layout.font());
+ return fm.ascent();
+ }
+ font.setPointSize(font.pointSize() - 1);
+ } while (font.pointSize() > 0);
+ return 0;
+}
+
+static qreal expectedBaselineFixedBottom(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ qreal dy = item->text().contains(QLatin1Char('\n'))
+ ? 160
+ : 180;
+ return dy + fm.ascent();
+}
+
+static qreal expectedBaselineProportionalBottom(QQuickText *item)
+{
+ QFontMetricsF fm(item->font());
+ qreal dy = item->text().contains(QLatin1Char('\n'))
+ ? 200 - (qCeil(fm.height()) * 3)
+ : 200 - (qCeil(fm.height()) * 1.5);
+ return dy + fm.ascent();
+}
+
+void tst_qquicktext::baselineOffset_data()
+{
+ qRegisterMetaType<ExpectedBaseline>();
+ QTest::addColumn<QString>("text");
+ QTest::addColumn<QString>("wrappedText");
+ QTest::addColumn<QByteArray>("bindings");
+ QTest::addColumn<ExpectedBaseline>("expectedBaseline");
+ QTest::addColumn<ExpectedBaseline>("expectedBaselineEmpty");
+
+ QTest::newRow("top align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; verticalAlignment: Text.AlignTop")
+ << &expectedBaselineTop
+ << &expectedBaselineTop;
+ QTest::newRow("bottom align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; verticalAlignment: Text.AlignBottom")
+ << &expectedBaselineBottom
+ << &expectedBaselineBottom;
+ QTest::newRow("center align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter")
+ << &expectedBaselineCenter
+ << &expectedBaselineCenter;
+
+ QTest::newRow("bold")
+ << "<b>hello world</b>"
+ << "<b>hello<br/>world</b>"
+ << QByteArray("height: 200")
+ << &expectedBaselineTop
+ << &expectedBaselineBold;
+
+ QTest::newRow("richText")
+ << "<b>hello world</b>"
+ << "<b>hello<br/>world</b>"
+ << QByteArray("height: 200; textFormat: Text.RichText")
+ << &expectedBaselineTop
+ << &expectedBaselineTop;
+
+ QTest::newRow("elided")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("width: 20; height: 8; elide: Text.ElideRight")
+ << &expectedBaselineTop
+ << &expectedBaselineTop;
+
+ QTest::newRow("elided bottom align")
+ << "hello world"
+ << "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+ << QByteArray("width: 200; height: 200; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom")
+ << &expectedBaselineBottom
+ << &expectedBaselineBottom;
+
+ QTest::newRow("image")
+ << "hello <img src=\"images/heart200.png\" /> world"
+ << "hello <img src=\"images/heart200.png\" /><br/>world"
+ << QByteArray("height: 200\n; baseUrl: \"") + testFileUrl("reference").toEncoded() + QByteArray("\"")
+ << &expectedBaselineImage
+ << &expectedBaselineTop;
+
+ QTest::newRow("customLine")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; onLineLaidOut: line.y += 16")
+ << &expectedBaselineCustom
+ << &expectedBaselineCustom;
+
+ QTest::newRow("scaled font")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit")
+ << &expectedBaselineScaled
+ << &expectedBaselineTop;
+
+ QTest::newRow("fixed line height top align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop")
+ << &expectedBaselineTop
+ << &expectedBaselineTop;
+
+ QTest::newRow("fixed line height bottom align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom")
+ << &expectedBaselineFixedBottom
+ << &expectedBaselineFixedBottom;
+
+ QTest::newRow("proportional line height top align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop")
+ << &expectedBaselineTop
+ << &expectedBaselineTop;
+
+ QTest::newRow("proportional line height bottom align")
+ << "hello world"
+ << "hello\nworld"
+ << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom")
+ << &expectedBaselineProportionalBottom
+ << &expectedBaselineProportionalBottom;
+}
+
+void tst_qquicktext::baselineOffset()
+{
+ QFETCH(QString, text);
+ QFETCH(QString, wrappedText);
+ QFETCH(QByteArray, bindings);
+ QFETCH(ExpectedBaseline, expectedBaseline);
+ QFETCH(ExpectedBaseline, expectedBaselineEmpty);
+
+ QQmlComponent component(&engine);
+ component.setData(
+ "import QtQuick 2.0\n"
+ "Text {\n"
+ + bindings + "\n"
+ "}", QUrl());
+
+ QScopedPointer<QObject> object(component.create());
+
+ QQuickText *item = qobject_cast<QQuickText *>(object.data());
+ QVERIFY(item);
+
+ {
+ qreal baseline = expectedBaselineEmpty(item);
+
+ QCOMPARE(item->baselineOffset(), baseline);
+
+ item->setText(text);
+ if (expectedBaseline != expectedBaselineEmpty)
+ baseline = expectedBaseline(item);
+
+ QCOMPARE(item->baselineOffset(), baseline);
+
+ item->setText(wrappedText);
+ QCOMPARE(item->baselineOffset(), expectedBaseline(item));
+ }
+
+ QFont font = item->font();
+ font.setPointSize(font.pointSize() + 8);
+
+ {
+ QCOMPARE(item->baselineOffset(), expectedBaseline(item));
+
+ item->setText(text);
+ qreal baseline = expectedBaseline(item);
+ QCOMPARE(item->baselineOffset(), baseline);
+
+ item->setText(QString());
+ if (expectedBaselineEmpty != expectedBaseline)
+ baseline = expectedBaselineEmpty(item);
+
+ QCOMPARE(item->baselineOffset(), baseline);
+ }
+}
+
QTEST_MAIN(tst_qquicktext)
#include "tst_qquicktext.moc"