PDA

View Full Version : Find maximum size of a given font that will fit a given size



Skylark
22nd July 2010, 19:49
Hi all,

I want to render some text with Qt that will be used in a texture in OpenGL. I have the basic setup done using QGraphicsView::render(QPainter...) and QGLWidget::convertToGLFormat(image), and that works. I use a QWidget with a QLabel with setWordWrap(true) inside my QGraphicsScene.

The problem is that I'm using a fixed font size. What I'd like is that given a string of text and a font family (say Arial), find the maximum size that will fit the texture. This has to consider word wrapping when possible. For example:


QFont getMaximumFontSize(const QString& text, const QString& fontFamily, int width, int height)
{
//...
}
(width and height are the dimensions of the texture)

I have an iterative approach that kind of works, using QFontMetrics::boundingRect() starting at a large size and going down when it doesn't fit well enough. The problem is that there is no clear indication that the text doesn't fit.


QFont getMaxFontSize(const QString& text, const QString& fontFamily, int width, int height)
{
int size = 1000;
QFont font("Arial", size);
QRect max(0,0,width,height);
int widthThreshold = int(float(max.width()) * 0.1);
int heightThreshold = int(float(max.height()) * 0.1);

while (size > 1)
{
QFontMetrics metrics(font);
QRect bb = metrics.boundingRect(max, Qt::TextWordWrap, text);
if (bb.width() > (max.width() - widthThreshold) ||
bb.height() > (max.height() - heightThreshold))
{
--size;
font = QFont("Arial", size);
}
else
{
break;
}
}

return font;
}
In addition to being potentially slow (going from 1000 in steps of 1, creating a new QFont and QFontMetrics each iteration), sometimes putting all the text on one line is a better "fit" according to the criteria in my while() loop above. But in most cases it works well.

Would anyone have some ideas as to how to get the information I want, to improve upon what I have? What I would really like is to get that info in one shot instead of iterating over font sizes. Thanks in advance,

Skylark

Lykurg
22nd July 2010, 20:15
One simple approach could be to create a more efficient algorithm for you font decrease. If it is too big use as a new font size the half of the last one. E.g. you start with 100, use 50. If then it is too small increase with the half of your last step, which would be 25. etc. With that you can speed up things a lot.
Also don't create a new QFont each time. Better alter the old object by using setPointSize().

Skylark
22nd July 2010, 20:43
Sure I can improve the algorithm to reduce the number of iterations. But I was wondering if there was a way to get exactly the information I need. If not, then I guess I'll do it like that. It's the next best thing.

Thanks for the suggestion Lykurg.

Lykurg
22nd July 2010, 21:19
Once I had the same problem, but I don't have found a build in function. Another solution could be that you paint the text with a default size (with a few checks if you want the text to be drawn in 1, 2, 3, ... lines) and then simply scale the image. If you can live with the lower quality that could be also an option. I had chosen the solution with finding the best font size.

Skylark
22nd July 2010, 21:23
OK thanks, good to know someone has been in the same boat as me in the past.

eean
27th July 2010, 18:19
My requirements are a bit different, but here's my code.


void
TextItem::setGeometry(const QRectF& rect)
{
QFont theFont = font();

int size = qMin(static_cast<int>(rect.height()), 40);

theFont.setPixelSize(size);
QFontMetricsF fm(theFont);
while(fm.width(toPlainText()) > rect.width())
{
size -= 5;
theFont.setPixelSize(size);
fm = QFontMetricsF(theFont);
}

setFont(theFont);
setPos(rect.topLeft());
}

SixDegrees
27th July 2010, 21:07
I agree with Lykburg; binary searching will be very fast, and will plow through a 1000-point range in less than eight steps on average.

Another approach: create a string, measure it, divide it into the available space and simply scale the font by that factor. For a fixed-width font, this should work nicely.