Improve underline drawing for fonts in QPainter

In the underline drawing code in QPainter there were
some issues causing

First of all, if an underline was very thick, then we would
still just offset the drawing by 0.5 (as if it were 1 pixel
wide) causing part of the underline to appear on top of the
text. The shift here is intended to move the top edge of the
underline to the top edge of the pixel where we want to
draw it, so we need to offset it by half of the actual line
thickness.

In addition, fractional line widths look bad when drawn with
aliased painting, which is the default. Depending on where
the text would be, the thickness of a line might vary. Even
if it's very close to an integer, e.g. 1.04 as in one case,
then it will still sometimes fill two pixels if it happens to
be placed directly between them. To ensure that we always fill
the same number of pixels, we round the line thickness to
nearest integer when antialiasing is not enabled.

Pick-to: 6.8
Fixes: QTBUG-134626
Change-Id: I95bae3ac16010fc133995e5f9b6c2e5d022e3743
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
(cherry picked from commit 2b5533af920726723479557356f56d0778bcabd3)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2025-03-18 15:11:17 +01:00 committed by Qt Cherry-pick Bot
parent 13aac36f69
commit af31f1f2c3
6 changed files with 50 additions and 3 deletions

View File

@ -6039,11 +6039,16 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
painter->fillRect(pos.x(), 0, qCeil(width), qMin(wave.height(), descent), wave);
painter->restore();
} else if (underlineStyle != QTextCharFormat::NoUnderline) {
const bool isAntialiasing = painter->renderHints().testFlag(QPainter::Antialiasing);
if (!isAntialiasing)
pen.setWidthF(qMax(fe->lineThickness().round(), QFixed(1)).toReal());
const qreal lineThicknessOffset = pen.widthF() / 2.0;
// Deliberately ceil the offset to avoid the underline coming too close to
// the text above it, but limit it to stay within descent.
qreal adjustedUnderlineOffset = std::ceil(underlineOffset) + 0.5;
qreal adjustedUnderlineOffset = std::ceil(underlineOffset) + lineThicknessOffset;
if (underlineOffset <= fe->descent().toReal())
adjustedUnderlineOffset = qMin(adjustedUnderlineOffset, fe->descent().toReal() - qreal(0.5));
adjustedUnderlineOffset = qMin(adjustedUnderlineOffset, fe->descent().toReal() - lineThicknessOffset);
const qreal underlinePos = pos.y() + adjustedUnderlineOffset;
QColor uc = charFormat.underlineColor();
if (uc.isValid())
@ -6056,6 +6061,9 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
textEngine->addUnderline(painter, underline);
else
painter->drawLine(underline);
if (!isAntialiasing)
pen.setWidthF(fe->lineThickness().toReal());
}
pen.setStyle(Qt::SolidLine);

View File

@ -8,7 +8,8 @@
# Collect test data
file(GLOB_RECURSE test_data_glob
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
scripts/*)
scripts/*
fonts/* )
list(APPEND test_data ${test_data_glob})
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)

View File

@ -196,4 +196,30 @@ save
drawText 0 20 "All the effects text drawing"
restore
translate 0 35
save
setPen black
setFont "QtUnderlineTest" 20 normal normal default underline
drawText 0 20 "1234"
restore
translate 0 35.5
save
setPen black
setFont "QtUnderlineTest2" 18 normal normal default underline
drawText 0 20 "1234"
restore
translate 0 35.5
save
setPen black
setFont "QtUnderlineTest2" 12 normal normal default underline
drawText 0 20 "1234"
restore
translate 0 35.5
save
setPen black
setFont "QtUnderlineTest2" 12 normal normal default underline
drawText 0 20 "1234"
restore

View File

@ -19,6 +19,8 @@
#include <QOpenGLPaintDevice>
#endif
#include <QFontDatabase>
#include <algorithm>
#ifndef GL_RGB10
@ -129,6 +131,16 @@ void tst_Lancelot::initTestCase()
scriptChecksums.insert(fileName, qChecksum(cont));
}
QString underlineTestFont1 = QFINDTESTDATA("fonts/QtUnderlineTest-Regular.ttf");
QVERIFY(!underlineTestFont1.isEmpty());
int id = QFontDatabase::addApplicationFont(underlineTestFont1);
QVERIFY(id >= 0);
QString underlineTestFont2 = QFINDTESTDATA("fonts/QtUnderlineTest2-Regular.ttf");
QVERIFY(!underlineTestFont2.isEmpty());
id = QFontDatabase::addApplicationFont(underlineTestFont2);
QVERIFY(id >= 0);
#ifndef QT_NO_OPENGL
initOpenGL();
#endif