Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set QGraphicsTextItem text contents of exact height and width

I am required to create text items with exact width and height of text contents.
The height of the text is the most important requirement.
The position of the text should be relative to the text itself.

I also have to be able to place it on canvas in an exact spot.

Assuming a (printable) canvas (on a larger QGraphicsScene), say 5 inch width and 1 inch height, my text should be able to stretch top-bottom-left-right - and be placed on canvas, not part in part out.


I am sub-classing QGraphicsTextItem for my item type. I am resizing it, using QTransform(), to required size - in inches or mm or pixels (72*in).
Also setting the document() margin to 0, and anything inside (like QTextBlockFormat margins) also to 0.

I have implemented a setItemSize(QSizeF sz) (with sz in pixels), that resizes the QGraphicsTextItem as required.
The sz is initialized using the item bounding rect.
Assuming no wrap, single line text (multi-line could be solved separately once this issue is resolved).

When adding the item to canvas, I still see a top and bottom margin - and this varies based on font choice.
I drew a rectangle around the item to see it.
The top/bottom distances depend on font choices.


I have tried to use font metrics to determine these distances (in paint() I have been drawing lines to try to determine the position and rectangle in which the text fits).

I would be happy to at least be able to determine correct size to use for upper case, no accents or special characters fonts (it would be a start, though naturally I would need to be able to use any characters).
But at least some way to determine the size and position (relative to the (0,0) of item) of the text content even in the simplest case.....

The font metrics tightBoundingRect() seems the most accurate for size, but it seems impossible to determine its position so that I can somehow create my items correctly, and maybe resize/shift them correctly to fit on canvas.


Here are some examples of my struggle to determine at least exact size and position of text, relative to the (0,0) of the item (assuming that once I do that, I am able to expose that info to outside or include the shift in the item transform on resize).
Notice that the size of the text advertised by font metrics does not always cover the text, and for different fonts I am not able to position the tight bounding rect (magenta) around the text itself. (I did multiple guesses, the code below is just one - the lines are trying to show different font metrics sizes).

enter image description here

The above were experiments in paint function of the text item inheriting QGraphicsTextItem:

void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
    // draw text
    QGraphicsTextItem::paint(painter, option, widget);

    QPen p;
    p.setWidthF(0);
    QFontMetricsF fm(this->font());
    qreal ascent = fm.ascent(),
          descent = fm.descent(),
          hheight = fm.height();
    QRectF r = QGraphicsTextItem::boundingRect();
    QRectF rFont= fm.tightBoundingRect(toPlainText());

    qreal xmax = r.right();

    painter->save();
    painter->setBrush(Qt::NoBrush);

    // where is "ascent + descent"
    p.setColor(Qt::green);
    painter->setPen(p);
    painter->drawLine(QPointF(2, ascent), QPointF(2, ascent + descent));
    painter->drawLine(QPointF(2, ascent + descent), QPointF(xmax/2, ascent + descent));

    // where is "height"
    p.setColor(Qt::red);
    painter->setPen(p);
    painter->drawLine(QPointF(xmax/2, 0), QPointF(xmax/2, hheight));
    painter->drawLine(QPointF(xmax/2, ascent + descent), QPointF(xmax, ascent + descent));

    // where is "ascent"
    p.setColor(Qt::yellow);
    painter->setPen(p);
    painter->drawLine(QPointF(6, 0), QPointF(6, ascent));
    painter->drawLine(QPointF(6, ascent), QPointF(xmax, ascent));

    // something that may look like top of the text
    p.setColor(Qt::blue);
    painter->setPen(p);
    qreal yyy = ascent + rFont.y() + 1;
    painter->drawLine(QPointF(5, yyy), QPointF(xmax, yyy));

    // this should be useful... should be the natural offset
    qreal yoffset = (r.height() - rFont.height()) / 2;
//    qDebug() << yoffset << r << rFont;
    //qreal y0 = (r.height() - fm.height())/2;

    p.setColor(Qt::darkGreen);
    painter->drawEllipse(10, yoffset, 1, 1);

    // where is the font rect
    p.setColor(Qt::magenta);
    painter->setPen(p);
    yoffset = (r.height() + rFont.height()) / 2;
    painter->translate(0, yoffset);
    painter->drawRect(rFont);
    painter->restore();
}

I have also tried not using QGraphicsTextItem, but paint text inside a rectangle. The same thing happens.

(Qt 4.7 - 5.x)

like image 472
Thalia Avatar asked Jun 09 '16 21:06

Thalia


1 Answers

This is not a good solution. This is an attempt to solve my own problem - in a first iteration - of setting text with given width and height, using font metrics.

Reasons for not being good -

  • Even after resize, text is smaller than desired, I don't understand why

  • The position is incorrect, based on font style the text can be above or below the canvas, meaning it gets clipped.


I resize it using a factor calculated from the item bounding rect size and the font metrics bounding rect (I used the tight bounding rect for more accurate size).

myText->setItemFontSize(12);   // If I use font metrics I need to reset text size on every change, because resizing loses font info 
QFontMetricsF fm(myText->font());
QRectF fmRect = fm.tightBoundingRect(myText.toPlainText().toUpper());
// without toUpper() the size is too small - even so it is a bit small
// I read tightBoundingRect is slow - but boundingRect and height and ascent all give values that result in even smaller size
//qreal absH = fm.ascent();
qreal absH = fmRect.height();
qreal absW = fmRect.width();
qreal absHeightRatio = myText->getItemSize().height() / absH;
qreal absWidthRatio = myText->getItemSize().width() / absW;

Then setting size:

myText->setItemSize(QSizeF(absWidthRatio * textLength, absHeightRatio * fontHeight));
// This function scales the `QTransform` on item
// but since I request a final position dependent on item size 
// (including blank space around it) - it has no chance of being accurate..... 
// This is where my next effort will go, figuring out how to get rid of the fluff 
// around the item inside the scaling

The function for setting position: trying to center text:

// leftShift  = 10, rightShift = 10 in example
myText->setPos(0,0);
QRectF r = myText->mapToScene(myText->boundingRect()).boundingRect();
QSizeF sz = r.size();
qreal w = sz.width();
qreal h = sz.height();
qreal cx = (m_docLength - w + leftShift - rightShift)/2 - r.left();
qreal cy = (m_docHeight - h)/2 - r.top();
myText->setPos(cx, cy);

The images below are for fontHeight = m_docHeight -

Desirable:
- either the entire size of text (ascent + descent) equals doc height, and text is centered vertically based on content
- or the upper case size text equals doc height and the descent is below document (text centered based on only upper case) - this would seem easier based on how QGraphicsTextItem seems to position it

Actual:
- the text is smaller no matter which parameters I use to scale, and centered based on upper case text

enter image description here

As shown above - I have no idea how I could center vertically based on content (so for edge-to-edge text the descent would fit in) - and in lieu of that, all I really want is edge-to-edge uppercase letters, but I can't seem able to achieve that.

Oh and these are for Arial type font - one of the best-behaved. Other fonts jump all over the place, either above or below the canvas. And for some fonts, the resulting text is actually smaller - which is inexplicable to me, because how can the tight bounding rectangle be smaller than the item bounding rectangle...

Still this is as far as I got to getting my text as close to "true" size and placed on a canvas that matches its size.

like image 169
Thalia Avatar answered Oct 01 '22 07:10

Thalia