Get QPainter drawn text dimensions - qt

I want to draw some text using QPainter::drawText command.
In order to set its position properly I need to know the dimension of drawn string in pixels.
Is there a way to know pixel dimension of the string? Possibly without drawing it before?

The QFontMetrics class has a method just for this purpose: boundingRect().
From the Qt docs (http://doc.qt.io/qt-5/qfontmetrics.html#boundingRect-2):
Returns the bounding rectangle of the characters in the string specified by text.
Note that the bounding rect only provides the width of drawn text, the width() function provides the width of trailing spaces as well. Also from the Qt docs:
boundingRect() returns a rectangle describing the pixels this string will cover whereas width() returns the distance to where the next string should be drawn.

Related

Qt FontMetrics boundingrect vs Geometry rect

QString sText1 = "Sample Text890\nSample Text 890";
QString sText2 = "Sample Text890 Sample Text 890";
label1_->setText(sText1);
label2_->setText(sText2);
label1_->setWordWrap(false);
label2_->setWordWrap(false);
label1_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
label2_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
Numbered boxes show metrics/information of corresponding labels just above them.
case 1: BoundingRect width is NOT equal to label widthx2
BoundingRectHeight is equal to label widthx2
case 2: BoundingRect width and height matches with label's width and height
case 3: No clue how boundingrect and label geometry are related!!
case 4: No clue how boundingrect and label geometry are related!!
case 5:
QString sText1 = "Sample Text890\nSample Text 890";
QString sText2 = "Sample Text890 Sample Text 890";
label1_->setWordWrap(true);
label2_->setWordWrap(true);
Question: I'm confused how font's bounding rect and label's geometry are related.
EDIT: I have updated case 5 and case 6 with label word wrap TRUE.
From the boundingRect(text) documentation:
Newline characters are processed as normal characters, not as linebreaks.
So, considering the above, case 1 and 2 have the same height because, since the new line character does not create a new line. The width is different because a new line character has a different width.
Case 3 and 4 have the same bounding rect, which is the smallest possible width based on the given rect and text option. Since the base rect has no width and the option is to wrap words, you'll get a rectangle that is wide as the "longest" word (based on the font), while the height depends on the widths of the other words in the given text: you'll get a line for each word, unless two or more words can fit the above maximum width; the height depends on the line height multiplied the final line count.
Consider the original strings:
sText1 = 'Sample Text890\nSample Text 890'
sText1 = 'Sample Text890 Sample Text 890'
Case 1
The processed string is actually:
Sample Text890*Sample Text 890
Note: " * " refers to the newline character, it's not actually the * character.
The width is that of the whole string.
The negative y is because the drawing considers the origin (0, 0) for the baseline: y is the negative fontMetrics.ascent().
In this case, the QLabel has a different height because it does consider the newline character.
Case 2
The processed string is used as it is (one line), so the label size matches the font metrics. The result is probably as using a QRect at (0, 0), sized with horizontalAdvance() and height().
Case 3 and 4
Since the source rectangle has no width and word wrapping is enabled, the text will be laid out in order to fit it by extending the width until wrapping is possible; the final processed text becomes the following:
Sample
Text890
Sample
Text
890
Which makes sense, since the height for case 1 and 2 is 41 and the height of case 3 and 4 is about 5 times that value.
Note that results may change depending on the font, and do not depend on the length of the string. For instance, consider a peculiar font that has numbers that are extremely wide (about 3 times a normal character); the resulting text could be this:
Sample
Text890
Sample Text
890
That would be because 890 is very wide with that peculiar font, and the line Text890 becomes wider than Sample Text, which then will fit in a single line:
Relations with the QLabel size
By default, QLabel does not wrap text (see the wordWrap property), so case 3 and 4 are not related because you explicitly specified that option for the font metrics. That said, you can get consistent result if you understand how text layout works.
For instance, to get a consistent bounding rect, use a very big rectangle as source:
QRect rect1 = fontMetrics.boundingRect(QRect(0, 0, 2000, 2000), Qt::TextWordWrap, sText1);
Which will return a size equal to the label basic sizeHint(). Obviously, since the source rectangle is that big, the word wrap option is useless, and you'll get the same result using 0 instead of Qt::TextWordWrap.
On the other hand, you can have the same result of the bounding rect that you got with the empty QRect, using the minimumSizeHint() and with wrapping enabled:
label.setWordWrap(true)
QSize minWidth = label.minimumSizeHint().width()
QRect boundingRect(0, 0, minWidth, label.heightForWidth(minWidth))
Remember, though, that using word wrapping in QLabel can have counter intuitive and unwanted results; while those results might seem unexpected, they actually are expected (see the note about layout issues: Qt layout management is not the same as a webpage, and the priority is always for all widgets, even if it's for the sake of a label. If the label must support wrapping but it's also placed in a complex layout, you need to explicitly set a reasonable minimum size (width, height, or both) whenever the layout also contains widgets that can adapt their size based on the overall available size; see the related note below.
Final considerations
QLabel uses parts of the Qt rich text processing framework, specifically QTextDocument, its document layout and the basic QTextLayout; unfortunately, probably due to performance reasons, all those components are private for QLabel; the only way to reliably compute a QLabel size (in the rare case for which this should be really needed) is to have deep knowledge of the above aspects;
QFontMetrics and QTextLayout are closely related: the former uses the latter to compute the size of laid out text (boundingRect(QRect, flags, text)), and vice versa for computing basic glyph sizes;
Qt layout managers will try their best to fit a word-wrapped label in the layout, but, as explained above, results may vary;
word-wrapped text should not be part of a layout: while this might be considered a Qt limitation, it's almost always a bad choice from the UX perspective (remember, a program is not a webpage, which is scrollable by nature); those texts should probably be put in a scroll area, so eventually consider using a QTextEdit or QPlainTextEdit set as readOnly and eventually a transparent background to make it look "like a label";
any padding/border/margin set with setContentsMargins() or QSS (Qt style sheets) must be added manually when trying to use font metrics;

Dimensioning in Qt

I have graphicsView in which different items are drawn in the scene.I want to have an option for dimensioning i.e to dimension.
There are different items like Line, Circle, Ellipse, Arc, Point, Text,
I want whenver the the two points are clicked on any item,or the anywhere on the scene it should tell me its dimension.
The items are drawn with the mouseclicks. Can I get help to proceed?
The items are inherited from QGraphicsItem, LineItem.
Like:http://imgur.com/kBOjfmw
You can use something like this to get the length in inches:
qreal distance(QPointF & p1, QPointF & p2) {
return QLineF(p1, p2).length() / QApplication::screens().at(0)->physicalDotsPerInch();
}
Multiply by 2.54 if you want centimeters. Then create a QGraphicsItem which draws the line between the two points and text with the distance.
You can use void QWidget::mousePress/Release/Event(QMouseEvent * event) and get the click position from the event->pos(), and you should map to the scene coords when you are actually drawing the dimension.
If you don't want the actual line length but the horizontal or vertical length as in that image you posted as example, you can calculate that from the absolute difference between the two points x or y components.

Getting QGraphicsTextItem length?

Is there anyway to calculate the text's length when TextWidth = -1?.
I have a rectangle that has a QGraphicsTextItem in it, and I want to change the rectangle's width when characters exceed the rectangle.
I found this post by stopping on the same problem.
i'm using text->boundingRect().width()to get the width.
Perhaps it helps anybody
textWidth = -1 means, that
"[...] the text will not be broken into
multiple lines unless it is enforced
through an explicit line break or a
new paragraph."
(QTextDocument::textWidth())
So, if you want to get the length of your QGraphicsTextItem you can't use textWidth, but instead you need the actual length of the String within this QGraphicsTextItem. Have a look at QGraphicsTextItem::toPlainText(), which returns a QString. Call size() on that string.
int length = my_graphics_text_item.toPlainText().size()
Now you have the number of characters in this string and can implement a resize function to make your rectangle grow, when there are too many characters. It's a kind of workaround, but I hope it helps solving your problem.
You could also create a QFontMetrics([font of your QGraphicsTextItem]) instance and call its width(QString) function to obtain the width of the passed string in pixels, were it drawn in the specified fontfamily/-size/-weight.
Just obtaining the character count is only reasonable when using a monospaced font. In all other cases it's not a good idea.

Use window/viewport to flip QPainter y-axis

I'm using Qt 4.7 QPainter to draw some polygons, etc into a widget. I am hoping to alter the coordinate system so that (0,0) is at the center of my widget, and the x/y axis behave in a standard "Cartesian" way (ie. y increases going "up" and decreases going "down"). In other words, I want the coordinates to be "math"-like not "computer graphics"-like, if you know what I mean. :-)
I'm trying to do this using setViewport() and setWindow() rather than do the math myself, as it would be nice to be able to just call the draw methods directly with my coordinates.
Here's what I've got so far:
// Setup coordinates
double screenWidth = width();
double screenHeight = height();
double windowWidth = 100.0;
double windowHeight = (screenHeight / screenWidth) * windowWidth;
painter.setViewport(0, 0, screenWidth, screenHeight);
painter.setWindow(-(windowWidth / 2.0), -(windowHeight / 2.0), windowWidth, windowHeight);
// Draw stuff
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::blue);
painter.drawRect(-10, -10, 20, 20);
Now this works just fine, in that it draws a nice blue square in the middle of the screen. The problem is, I have to say that the upper left corner is (-10, -10). I'd like to be able to make it (-10, 10), as that is what it would be in Cartesian coords.
I tried messing with setWindow/setViewport to get this "y-axis flip", but to no avail. This seems like a really easy/basic thing to do, but after scouring the Qt docs and the web, I can't figure it out!
Thanks,
Chris
Use class QMatrix. It specifies 2D transformations. QMatrix is set to QPainter.
But remember, in your case, if you convert your widget's coords to Cartesian coords, you will have to put first point at (-10,-10) (not at (-10,10) as you did mentioned) to draw a rect, which has center at (0,0), because Y-axis now grows up and X-Axis now grows right.
All you need is to transform your coord system this way:
translate origin from (0,0) to the middle of the widget:
scale Y-axis by -1 factor:
Here is the code, typed in paintEvent() function of a widget:
QPainter pn( this );
int w_2 = width() / 2;
int h_2 = height() / 2;
{ // X- and Y-Axis drawing
pn.setPen( Qt::blue );
pn.drawLine( 0, h_2, width(), h_2); // X-Axis
pn.drawLine( w_2, 0 , w_2, height() ); // Y-Axis
}
QMatrix m;
m.translate( w_2, h_2 );
m.scale( 1, -1 );
pn.setMatrix( m );
pn.setPen( Qt::NoPen );
pn.setBrush( QBrush( Qt::blue, Qt::Dense4Pattern ) );
pn.drawRect( -10, -10, 20, 20 );
result:
update apr 07, 2014
This question was asked a long time ago and many things have changed since. For those asking themselves the same question today (beginnings of 2014) then my personal answer is that since Qt 4.3 it is possible to solve problem with text flipping more easier.
You are right. Text also gets filpped because it is drawn with the same painter. You can draw text at the end, when all flipped drawings are done, if it is possible. This method is not convinient because of new calculations of texts position. Also you will need to drop settings for painter.
Now I would recommend you to use QGraphicsView, because of huge support of 2D painting. Also for each QGraphicsItem ItemIgnoresTransformations flag can be set, which allows it to ignore inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the graphics view is transformed
The above answer will also flip text, "p" will be "b". To avoid that you have to flip back the y-axis before text is drawn, and you have to change sign on y-coord for the text position when you draw it. This is a little bit ugly I think, or is there a better way?
As stated above, drawing text also appears flipped upside down. There is an easy solution to it, see below. We will temporary disable the world transform for the text drawing. Note that text is not scaled anymore.
in your painting code we want to draw text on coordinate QPointF P;
Painter pn( this );
// calculate the point with the transform
QPointF p = pm.transform().map(P);
// Disable Transform temporary
pn.setWorldMatrixEnabled(false);
// draw it ordinary, no scaling etc
pn.drawText(p, QString("HI FRIENDS!"));
// Enable the transform again
pn.setWorldMatrixEnabled(true);
I needed to flip the y-axis in order to paint lines and polygons using Qt from points defined in Java coordinates. I imagine others will need to do this in porting from Java to Qt coordinate systems. The discussion above was helpful. My solution was:
painter.translate(0,height());
painter.scale(1.0, -1.0);
and then proceed to draw the lines and polygons.

Is there a way to make drawText() update a QPicture's bounding rect?

Drawing on a QPicture should update its bounding rect. Like this:
>>> picture = QPicture()
>>> painter = QPainter(picture)
>>> picture.boundingRect()
QRect(0,0,0,0)
>>> painter.drawRect(20,20,50,50)
>>> picture.boundingRect()
QRect(20,20,50,50)
But if I draw text on it, the bounding rect isn't updated:
>>> picture = QPicture()
>>> painter = QPainter(picture)
>>> picture.boundingRect()
QRect(0,0,0,0)
>>> painter.drawText(10,10, "Hello, World!")
>>> picture.boundingRect()
QRect(0,0,0,0)
Obviously, it doesn't update the bounding rect.
Is there a way to make it repsect drawn text or do I have to do it manually? (Not too hard, but I hope that Qt can assist me here.)
Take a look at these overload methods, where you must specify the Bounding Rectangle after the text parameter (which is apparently different than the rectangle in the first argument's position):
Draws the given text within the
provided rectangle according to the
specified flags. The boundingRect (if
not null) is set to the what the
bounding rectangle should be in order
to enclose the whole text.
QPainter.drawText (1), QPainter.drawText (2)
Update:
It appears if you want to generate a bounding rectangle for the drawText() method in advance, you just call the boundingRect() method on QPainter, which does the following:
Returns the bounding rectangle of the
text as it will appear when drawn
inside the given rectangle with the
specified flags using the currently
set font(); i.e the function tells you
where the drawText() function will
draw when given the same arguments.
If the text does not fit within the
given rectangle using the specified
flags, the function returns the
required rectangle.
QPainter.boundingRect
I linked to BoundingRect with QRectF output, but the information applies to the other versions as well.
So basically, pass the result of QPainter.boundingRect() into the boundingRect parameter of the QPainter.drawText() method (the second QRect argument).
Update 2:
I APOLOGIZE PROFUSELY for being so damn dense. I forgot that drawText works differently in PyQt than in Qt. The bounding rectangle is RETURNED by the drawText function (not passed in like Qt) and in addition, you have to specify alignment flags before you get a bounding rectangle given back to you. (I even included the p.end() as per Aaron Digulla's comment):
pic = Qt.QPicture()
p = QtGui.QPainter(pic)
brect = p.drawText(10,10,200,200, QtCore.Qt.AlignCenter, "blah")
p.end()
print brect
print pic.boundingRect()
Here is the output:
PyQt4.QtCore.QRect(100, 103, 20, 14)
PyQt4.QtCore.QRect(0, 0, 0, 0)
So it appears you will have to set the bounding rectangle yourself, though at least it is returned to you by the output of the drawText() method when passing in flags.
This does not seem like ideal behaviour, that you would have to set the bounding rectangle yourself. I hope someone else has the answer you're looking for, but I suspect you may want to report this bug.
Painting doesn't change the size of something in Qt. The main reason is this:
A component has to paint itself
The paint triggers a resize
The resize triggers painting -> endless loop
So the resize has to happen during the layout phase. After that, the bounds should not change.
To solve your problem, use QFontMetric to figure out how big your text is going to be during or close to the construction of your picture and then resize it accordingly.
[EDIT] Hm ... try to call end() before requesting the bounding rect. If that works, you've found a bug (can't see a reason why the bounding rect should not exist as you add elements...)

Resources