Impossible to get true text hight with QFontMetrics/QFontMetricsF
-
Same problem on macOS 10.15, even the amount of the offset is about the same.
To clarify:
p.drawText(rect(), Qt::AlignCenter, myText);
also exhibits the same offset, so when total height of the widget is only a few pixels larger than the text height it becomes obvious that the text is not centered. -
Well you still need to center text in that calculated rectangle:
p.drawText(textRect, Qt::AlignCenter, text);
With that change I get
And here's a slightly modified version:
void paintEvent(QPaintEvent*) override { const QString text = "Hello!"; QPainter p(this); const QRectF textRect = QFontMetricsF(font()).boundingRect(rect(), Qt::AlignCenter, text); p.fillRect(rect(), Qt::darkBlue); p.fillRect(textRect, Qt::green); p.drawText(rect(), Qt::AlignCenter, text); }
This gives me
And if I change
textRect
toQRectF textRect = QFontMetricsF(font()).tightBoundingRect(text); textRect.moveCenter(QRectF(rect()).center());
I get this
So everything seems as it should.
I don't know. Seems to be something specific to your setup. -
Same problem on macOS 10.15, even the amount of the offset is about the same.
To clarify:
p.drawText(rect(), Qt::AlignCenter, myText);
also exhibits the same offset, so when total height of the widget is only a few pixels larger than the text height it becomes obvious that the text is not centered.@Violet-Giraffe
Normally, when you call drawText, it will render the text including any spacing that should go with it. The tightBoundingRect gives you the minimum space necessary to render the characters itself, but the drawText will still try to put the default spacing on top. You have to calculate the necessary offsets, and you get this information from the QFontMetricsF class: ascent and descent.Take a look at this stackoverflow question and the highest rated answer. It should help.
-
Well you still need to center text in that calculated rectangle:
p.drawText(textRect, Qt::AlignCenter, text);
With that change I get
And here's a slightly modified version:
void paintEvent(QPaintEvent*) override { const QString text = "Hello!"; QPainter p(this); const QRectF textRect = QFontMetricsF(font()).boundingRect(rect(), Qt::AlignCenter, text); p.fillRect(rect(), Qt::darkBlue); p.fillRect(textRect, Qt::green); p.drawText(rect(), Qt::AlignCenter, text); }
This gives me
And if I change
textRect
toQRectF textRect = QFontMetricsF(font()).tightBoundingRect(text); textRect.moveCenter(QRectF(rect()).center());
I get this
So everything seems as it should.
I don't know. Seems to be something specific to your setup.@Chris-Kawa said in Impossible to get true text hight with QFontMetrics/QFontMetricsF:
Well you still need to center text in that calculated rectangle
Why? Since when is it a requirement such that if you don't center the text it's rendered at a wrong position?
In other words, in which reality does the output of my original example make any sense?I agree that the second two of your results are interesting, but they require
AlignCenter
for the same reason. Thanks for the tip, though, I didn't realize such combination of manual alignment (by specifying the pre-aligned rect) + automatic alignment (AlignCenter
) improves the situation.Note that I say "improves", not "fixes". Even on your last screenshot the offset from the top is 11px and from the bottom it's 10px! But I can live with that. Probably due to integer rounding and high DPI re-calculations.
-
@Violet-Giraffe
Normally, when you call drawText, it will render the text including any spacing that should go with it. The tightBoundingRect gives you the minimum space necessary to render the characters itself, but the drawText will still try to put the default spacing on top. You have to calculate the necessary offsets, and you get this information from the QFontMetricsF class: ascent and descent.Take a look at this stackoverflow question and the highest rated answer. It should help.
@Asperamanca said in Impossible to get true text hight with QFontMetrics/QFontMetricsF:
Take a look at this stackoverflow question and the highest rated answer. It should help.
Thanks! This is a great description, but it matches what I already understood intuitively thus far.
-
@Chris-Kawa said in Impossible to get true text hight with QFontMetrics/QFontMetricsF:
Well you still need to center text in that calculated rectangle
Why? Since when is it a requirement such that if you don't center the text it's rendered at a wrong position?
In other words, in which reality does the output of my original example make any sense?I agree that the second two of your results are interesting, but they require
AlignCenter
for the same reason. Thanks for the tip, though, I didn't realize such combination of manual alignment (by specifying the pre-aligned rect) + automatic alignment (AlignCenter
) improves the situation.Note that I say "improves", not "fixes". Even on your last screenshot the offset from the top is 11px and from the bottom it's 10px! But I can live with that. Probably due to integer rounding and high DPI re-calculations.
@Violet-Giraffe said:
In other words, in which reality does the output of my original example make any sense?
In this one :) As @Asperamanca said there's more to text layout than a simple rectangle. I'm guessing offset is the ascent you also see in my second picture. Query it from the font metrics to see if that matches.
Even on your last screenshot the offset from the top is 11px and from the bottom it's 10px!
How would you draw sharp half a pixel?
If you really want to you can enable antialiasing on the painter withp.setRenderHint(QPainter::RenderHint::Antialiasing);
and this will give you blurry 10.5 pixel i.e. sharp 10 and alphablended 0.5.
-
@Violet-Giraffe said:
In other words, in which reality does the output of my original example make any sense?
In this one :) As @Asperamanca said there's more to text layout than a simple rectangle. I'm guessing offset is the ascent you also see in my second picture. Query it from the font metrics to see if that matches.
Even on your last screenshot the offset from the top is 11px and from the bottom it's 10px!
How would you draw sharp half a pixel?
If you really want to you can enable antialiasing on the painter withp.setRenderHint(QPainter::RenderHint::Antialiasing);
and this will give you blurry 10.5 pixel i.e. sharp 10 and alphablended 0.5.
@Chris-Kawa, you're of course right about half a pixel, I realized 1px difference is just rounding just as I submitted the comment, and I should have deleted that part.
But how about this code? As you can see, there's still a bug, and I don't mean the string :)
struct TestWidget : QWidget { protected: void paintEvent(QPaintEvent*) override { const QString text = "bug()"; QPainter p(this); p.fillRect(rect(), Qt::darkBlue); auto textRect = QFontMetricsF(font()).tightBoundingRect(text); textRect.moveTo(0.0, 0.0); p.fillRect(textRect, Qt::green); p.drawText(textRect, Qt::AlignCenter, text); } }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication a(argc, argv); QMainWindow mw; auto* widget = new TestWidget; auto f = widget->font(); f.setPixelSize(30); widget->setFont(f); mw.setCentralWidget(widget); mw.show(); return a.exec(); }
Also, could you please explain the meaning of negative top left coordinates that
boundingRect()
/tightRect()
often have? That's whymoveTo(0.0, 0.0)
is needed.P. S. I have established that the following magic manipulation solves the problem, but these numbers depend on the font and its size. Any idea how to calculate them, what font properties should be factored in to get this
7
?textRect.setTopLeft(QPointF(textRect.left(), textRect.top() - 7.0));
This is what it looks like:
-
I think the magic offset is
-fm.descent() - fm.underlinePos()
. Note that it's NOT translation, it is expansion of the original rect towards the top. -
I think the magic offset is
-fm.descent() - fm.underlinePos()
. Note that it's NOT translation, it is expansion of the original rect towards the top.@Violet-Giraffe
Try putting your font into italic and see whether it does not fit, horizontally?
I'm thinking there might need to be a modification totextRect.left()
too?leftBearing()
?rightBearing()
?
tightBoundingRect:Note that the bounding rectangle may extend to the left of (0, 0), e.g. for italicized fonts
-
@Violet-Giraffe
Try putting your font into italic and see whether it does not fit, horizontally?
I'm thinking there might need to be a modification totextRect.left()
too?leftBearing()
?rightBearing()
?
tightBoundingRect:Note that the bounding rectangle may extend to the left of (0, 0), e.g. for italicized fonts
@JonB, you are correct, thanks for this tip!
-
@JonB, you are correct, thanks for this tip!
@Violet-Giraffe
And that's where your "magic"- fm.underlinePos()
comes from: you have removed the underline position from your area, for right or for wrong. If the text were to be underlined, you would currently exclude it, I'm thinking. :) -
@Violet-Giraffe
And that's where your "magic"- fm.underlinePos()
comes from: you have removed the underline position from your area, for right or for wrong. If the text were to be underlined, you would currently exclude it, I'm thinking. :)@JonB, I don't think so, because as I said, I did not just translate the rect, and certainly didn't shrink it. I expanded it towards the top (possibly into negative coordinates).