PyQt5 QGraphicsSimpleTextItem Y position

Here is the simple code that draws letters using PyQt5. setPos is 0, 0 but letters not at the top of the window. Horizontally letters not at the window edge too. What is wrong?

Here is the image

Thank you

from PyQt5 import QtWidgets, QtGui, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtGui import QBrush, QColor
import sys


    class MainWindow(QMainWindow):

        def __init__(self):
            super(MainWindow, self).__init__()
            self.initWindow()

        def initWindow(self):
            self.setGeometry(200,200,1000,400)
            self.show()
            self.scene = QtWidgets.QGraphicsScene()
            self.graphics = QtWidgets.QGraphicsView(self.scene, self)
            self.graphics.setGeometry(0, 0, 1000, 400)
            self.scene.setSceneRect(0, 0, 1000, 400)
            self.graphics.setHorizontalScrollBarPolicy(1)
            self.graphics.setVerticalScrollBarPolicy(1)
            self.graphics.setFrameStyle(0)

            text = QtWidgets.QGraphicsSimpleTextItem()
            _font = QtGui.QFont()
            _font.setPixelSize(200)
            _font.setBold(False)

            text.setFont(_font)
            text.setText('DDD')
            text.setBrush(QBrush(QColor(0,0,0)))
            text.setPos(0, 0)
            self.scene.addItem(text)
            self.graphics.show()

    app = QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec())

Upvotes: 0

Views: 1111

Answers (1)

musicamante
musicamante

Reputation: 48231

As the QGraphicsSimpleTextItem documentation explains, the positioning of the text is based on the font "bounding rectangle":

coordinate system for simple text item

Each character symbol of a font uses a "baseline" for reference, an "ascent" (how much the font goes from the baseline to the top) and a descent (how much it goes down for letters like lowercase "g" or "q"). Also, most characters do not start exactly on the "left" 0-point, but there is always some margin of "horizontal margin".

If you want to position it exactly at the top-left, you'll need to use QFontMetrics, which provides specific metrics information about a font; also, since we're dealing with a QGraphicsScene, QFontMetricsF is more indicated, as it returns floating point precision. The most important function for this is tightBoundingRect().

If I add the following to your code, just after adding the text item:

    outRect = self.scene.addRect(text.boundingRect())
    outRect.setPen(QtCore.Qt.red)

    fm = QtGui.QFontMetricsF(_font)
    boundingRect = fm.tightBoundingRect(text.text())
    self.scene.addRect(boundingRect.translated(0, text.boundingRect().bottom() - fm.descent()))

The result is clear (I used a different string to better show the differences):

bounding rectangles of simple text item

The red rectangle indicates the actual bounding rectangle of the item (which has the same size of QFontMetrics(_font).boundingRect(QtCore.QRect(), QtCore.Qt.AlignCenter, text.text()) would return. The black rectangle shows the "tight" bounding rectangle, which is the smallest possible rectangle for that string. The tight rectangle (as the one provided by QFontMetrics.boundingRect) uses the baseline as the 0-point for the coordinates, so it will always have a negative y position and probably (but it depends on the font and the style) an x greater than 0.

Finally, to get your text item placed with the characters aligned on the top left corner of the scene, you'll need to compute its position based on that tight rectangle:

    text.setPos(-boundingRect.left(), -(fm.ascent() + boundingRect.top()))

The left has to be negative to compensate the horizontal text positioning, while the negative vertical position is computed by adding the ascent to the boundingRect top (which is negative in turn).
Keep in mind, though, that the bounding rectangle will still be the bigger red rectangle shown before (obviously translated to the new position):

translated item and bounding rectangle

So, while the text appears aligned on the top left, the item bounding rect top-left corner is actually outside the scene rectangle, and its bottom-left corner exceedes its visual representation. This is important, as it has to do with item collision detection and mouse button interaction.

To avoid that, you'd need to subclass QGraphicsSimpleTextItem and override boundingRect(), but keep in mind that positioning (expecially vertical) should always be based on the baseline (the ascent), otherwise you'll probably get unexpected or odd behavior if the text changes.

Upvotes: 3

Related Questions