Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Impossible to get true text hight with QFontMetrics/QFontMetricsF
Forum Updated to NodeBB v4.3 + New Features

Impossible to get true text hight with QFontMetrics/QFontMetricsF

Scheduled Pinned Locked Moved Unsolved General and Desktop
17 Posts 4 Posters 3.0k Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • V Offline
    V Offline
    Violet Giraffe
    wrote on last edited by Violet Giraffe
    #1

    I have spent almost a full working day on a thing that should be simple and straightforward: aligning the text vertically centered inside a widget.
    The problem is that QFontMetrics/QFontMetricsF return height values that are 1) not correct, and 2) don't depend on the actual text.

    Some examples:
    QFontMetricsF{ font() }.boundingRect("a").height() gives 13.26
    QFontMetricsF{ font() }.boundingRect(")").height() gives 13.26

    QFontMetricsF{ font() }.size(0, "a"); gives 13.26
    QFontMetricsF{ font() }.size(0, ")"); gives 13.26

    But the height of a brace ) in my font is much larger than that of a.

    This is a bug, but before I report it, I wanted to consult with you. Perhaps, there is another obscure way to get the actual height of a symbol in pixels as rendered on the screen by the widget?

    Here's a more complete repro:

    #include <QApplication>
    #include <QDebug>
    #include <QFontMetricsF>
    
    int main(int argc, char *argv[])
    {
      QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
      QApplication a(argc, argv);
    
      QFontMetricsF fm(a.font());
      qInfo() << a.font();
      qInfo() << fm.boundingRect(")").height();
      qInfo() << fm.boundingRect("a").height();
      qInfo() << fm.size(0, ")").height();
      qInfo() << fm.size(0, "a").height();
    
      return 0;
    }
    

    Output:

    QFont(MS Shell Dlg 2,8.14286,-1,5,50,0,0,0,0,0)
    13.2656
    13.2656
    13.2656
    13.2656

    1 Reply Last reply
    0
    • Chris KawaC Online
      Chris KawaC Online
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by
      #2

      See QFontMetricsF::tightBoundingRect().

      V 1 Reply Last reply
      0
      • Chris KawaC Chris Kawa

        See QFontMetricsF::tightBoundingRect().

        V Offline
        V Offline
        Violet Giraffe
        wrote on last edited by
        #3

        @Chris-Kawa said in Impossible to get true text hight with QFontMetrics/QFontMetricsF:

        See QFontMetricsF::tightBoundingRect().

        tightBoundingRect also does not yield the correct value, but it does depend on the string contents so it's a step in the right direction.

        Meanwhile, I traced the actual problem why I can't align the text vertically properly: p.drawText(tightTextRect, 0, myText) renders the text with vertical offset towards the bottom. This code:

        auto tightTextRect = QFontMetricsF{font()}.boundingRect(myText);
        tightTextRect.moveCenter(QRectF(rect()).center());
        p.fillRect(tightTextRect, Qt::darkBlue);
        p.drawText(tightTextRect, 0, myText);
        

        Results in this:

        alt text

        1 Reply Last reply
        0
        • Chris KawaC Online
          Chris KawaC Online
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Works for me. Which Qt version and OS are you using? Do you have resolution scaling enabled in the OS?

          Btw. if all you want to do is center text you don't need to calculate precise rectangle for that manually. Just pass rect() and appropriate alignment flags to drawText, e.g.

          p.drawText(rect(), Qt::AlignCenter, myText);
          
          V 1 Reply Last reply
          2
          • Chris KawaC Chris Kawa

            Works for me. Which Qt version and OS are you using? Do you have resolution scaling enabled in the OS?

            Btw. if all you want to do is center text you don't need to calculate precise rectangle for that manually. Just pass rect() and appropriate alignment flags to drawText, e.g.

            p.drawText(rect(), Qt::AlignCenter, myText);
            
            V Offline
            V Offline
            Violet Giraffe
            wrote on last edited by Violet Giraffe
            #5

            @Chris-Kawa said in Impossible to get true text hight with QFontMetrics/QFontMetricsF:

            Just pass rect() and appropriate alignment flags to drawText

            That doesn't work either. As I said, there is an offset to the right and to the bottom. Compensating for the offset seems to solve the problem. So the problem is not with the height of the font, although I have doubts in that as well (after measuring the pixel sizes on the screen), but with the unexpected offset at which the text is drawn.

            Tested only on Windows so far, but on different PCs. Qt 5.14.1 and 5.15, no change between them. I do have a 175% and a 100% monitor in this system, AA_EnableHighDpiScaling enabled, but I have also tried on a pure 100% PC (single monitor, no scaling). I have a repro and I think I should report a bug.
            Here's the repro:

            #include <QApplication>
            #include <QFontMetricsF>
            #include <QMainWindow>
            #include <QPainter>
            
            struct TestWidget : QWidget
            {
            protected:
            	void paintEvent(QPaintEvent*) override
            	{
            		const QString text = "Hello!";
            		QPainter p(this);
            		p.fillRect(rect(), Qt::darkBlue);
            
            		auto textRect = QFontMetricsF(font()).tightBoundingRect(text);
            		textRect.moveTopLeft(QPointF(0.0, 0.0));
            		//textRect.setTopLeft(QPointF(textRect.left() - 2.0, textRect.top() - 6.0));
            
            		p.fillRect(textRect, Qt::green);
            		p.drawText(textRect, 0, 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();
            }
            

            Here's its output:

            alt text

            1 Reply Last reply
            0
            • V Offline
              V Offline
              Violet Giraffe
              wrote on last edited by Violet Giraffe
              #6

              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.

              A 1 Reply Last reply
              0
              • Chris KawaC Online
                Chris KawaC Online
                Chris Kawa
                Lifetime Qt Champion
                wrote on last edited by
                #7

                Well you still need to center text in that calculated rectangle:

                p.drawText(textRect, Qt::AlignCenter, text);
                

                With that change I get
                text alignment 1

                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

                text alignment 2

                And if I change textRect to

                QRectF textRect = QFontMetricsF(font()).tightBoundingRect(text);
                textRect.moveCenter(QRectF(rect()).center());
                

                I get this

                text alignment 3

                So everything seems as it should.
                I don't know. Seems to be something specific to your setup.

                V 1 Reply Last reply
                1
                • V Violet Giraffe

                  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.

                  A Offline
                  A Offline
                  Asperamanca
                  wrote on last edited by
                  #8

                  @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.

                  V 1 Reply Last reply
                  3
                  • Chris KawaC Chris Kawa

                    Well you still need to center text in that calculated rectangle:

                    p.drawText(textRect, Qt::AlignCenter, text);
                    

                    With that change I get
                    text alignment 1

                    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

                    text alignment 2

                    And if I change textRect to

                    QRectF textRect = QFontMetricsF(font()).tightBoundingRect(text);
                    textRect.moveCenter(QRectF(rect()).center());
                    

                    I get this

                    text alignment 3

                    So everything seems as it should.
                    I don't know. Seems to be something specific to your setup.

                    V Offline
                    V Offline
                    Violet Giraffe
                    wrote on last edited by Violet Giraffe
                    #9

                    @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.

                    Chris KawaC 1 Reply Last reply
                    0
                    • A Asperamanca

                      @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.

                      V Offline
                      V Offline
                      Violet Giraffe
                      wrote on last edited by
                      #10

                      @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.

                      1 Reply Last reply
                      0
                      • V Violet Giraffe

                        @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.

                        Chris KawaC Online
                        Chris KawaC Online
                        Chris Kawa
                        Lifetime Qt Champion
                        wrote on last edited by
                        #11

                        @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 with

                        p.setRenderHint(QPainter::RenderHint::Antialiasing);
                        

                        and this will give you blurry 10.5 pixel i.e. sharp 10 and alphablended 0.5.

                        V 1 Reply Last reply
                        2
                        • Chris KawaC Chris Kawa

                          @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 with

                          p.setRenderHint(QPainter::RenderHint::Antialiasing);
                          

                          and this will give you blurry 10.5 pixel i.e. sharp 10 and alphablended 0.5.

                          V Offline
                          V Offline
                          Violet Giraffe
                          wrote on last edited by Violet Giraffe
                          #12

                          @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();
                          }
                          

                          e1f0daae-7ff9-4b10-9f58-826a77ecdfa4-image.png

                          Also, could you please explain the meaning of negative top left coordinates that boundingRect() / tightRect() often have? That's why moveTo(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:

                          569f3b56-38d8-4089-9206-06ff0b7eae4b-image.png

                          1 Reply Last reply
                          0
                          • V Offline
                            V Offline
                            Violet Giraffe
                            wrote on last edited by
                            #13

                            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.

                            JonBJ 1 Reply Last reply
                            0
                            • V Violet Giraffe

                              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.

                              JonBJ Online
                              JonBJ Online
                              JonB
                              wrote on last edited by JonB
                              #14

                              @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 to textRect.left() too? leftBearing()? rightBearing()?
                              tightBoundingRect:

                              Note that the bounding rectangle may extend to the left of (0, 0), e.g. for italicized fonts

                              V 1 Reply Last reply
                              2
                              • JonBJ JonB

                                @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 to textRect.left() too? leftBearing()? rightBearing()?
                                tightBoundingRect:

                                Note that the bounding rectangle may extend to the left of (0, 0), e.g. for italicized fonts

                                V Offline
                                V Offline
                                Violet Giraffe
                                wrote on last edited by
                                #15

                                @JonB, you are correct, thanks for this tip!

                                JonBJ 1 Reply Last reply
                                0
                                • V Violet Giraffe

                                  @JonB, you are correct, thanks for this tip!

                                  JonBJ Online
                                  JonBJ Online
                                  JonB
                                  wrote on last edited by JonB
                                  #16

                                  @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. :)

                                  V 1 Reply Last reply
                                  0
                                  • JonBJ JonB

                                    @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. :)

                                    V Offline
                                    V Offline
                                    Violet Giraffe
                                    wrote on last edited by
                                    #17

                                    @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).

                                    1 Reply Last reply
                                    0

                                    • Login

                                    • Login or register to search.
                                    • First post
                                      Last post
                                    0
                                    • Categories
                                    • Recent
                                    • Tags
                                    • Popular
                                    • Users
                                    • Groups
                                    • Search
                                    • Get Qt Extensions
                                    • Unsolved