How to calculate font size for a string to make it fit into a rectangle of a fixed size.

C

carterweb

This is how I do it now.

1. Determine the dimensions of the rectangle.

2. Set a my font size to a fixed maximum size.

3. Apply the font my string and measure the string using the graphics
object.

4. If string size is less than the size of the rectangle, we are done.

5. Loop, subtracting one from the size of the font until the sting size
measures less than the size of the rectangle.

6. Draw the string in the rectangle.

The result is that the font scales the string to fit inside of the
rectangle. The trouble with this method is that it is rather slow due
to the loop. I would rather determine the size of the font needed for
the string based on the area of the rectangle and simply apply that
font size to the string. This has proven to be more difficult than I
expected. The trial and error used in the method above, however crude,
works. It's just slow.
 
P

Peter Duniho

[...]
The result is that the font scales the string to fit inside of the
rectangle. The trouble with this method is that it is rather slow due
to the loop. I would rather determine the size of the font needed for
the string based on the area of the rectangle and simply apply that
font size to the string.

Unfortunately, other than trial-and-error, I don't think there's a way to do
what you want. You can make some reasonably good guesses about the size of
a rendered string based on the size of that string at a given font size, but
it won't scale perfectly.

That said, you can certainly improve upon the method you're using now. One
way would be to use a binary search technique. Pick a size (perhaps your
"fixed maximum size", for example), and then do a binary search on the font
size until it just barely fits. In pseudocode:

sizeMac = sizeMax;
sizeMin = 1; // or whatever suitable minimum size applies in your case
sizeCur = (sizeMin + sizeMac) / 2;
while (sizeMin != sizeMac)
{
if (FTextFits(sizeCur))
{
sizeMin = sizeCur;
}
else
{
sizeMac = sizeCur - 1;
}
sizeCur = (sizeMin + sizeMac) / 2;
}

I might have an off-by-one error in there somewhere, but hopefully you get
the idea.

Another method would be to calculate a scale based on the difference between
the desired text size and the actual text size. For example, if you have a
rectangle 100 pixels wide, and the text at the maximum size is 150 pixels
wide, you might scale the maximum font size by the 2/3rds that is the ratio
of those two sizes (100/150). From there, you should be within a few points
of the correct size, which should be quicker to test.

Finally, you could combine the two methods. While there is no exact way to
predict the size of text drawn at one size based on how large it is at
another size, it will always be close. You could use the scaling method to
determine a good guess, then start your binary search with the minimum and
maximum some fixed percentage away from that guess. Not that the binary
search will ever take that many iterations anyway, but doing this will
reduce that number even further.

Of course, it should go without saying that you should perform this
calculation as infrequently as possible. Only when the destination drawing
rectangle actually does change in size should you do it. As long as the
destination remains the same size, you should cache the correct font size
and use that instead of recalculating each time.

Pete
 
L

Lucian Wischik

Peter Duniho said:
Another method would be to calculate a scale based on the difference between
the desired text size and the actual text size.

This is what I've done. If I remember correctly, it pretty much works
first time so I never even bothered with subsequent refinement.
 
P

Peter Duniho

Lucian Wischik said:
This is what I've done. If I remember correctly, it pretty much works
first time so I never even bothered with subsequent refinement.

As I mentioned, it will always get you close. But you have no guarantee
that the resulting value will be exactly the largest size that would fit,
nor even that it will fit (a few pixels may be truncated, or the text might
wrap if you've selected that formatting option).
 
P

Peter Duniho

Peter Duniho said:
As I mentioned, it will always get you close. But you have no guarantee
that the resulting value will be exactly the largest size that would fit,
nor even that it will fit (a few pixels may be truncated, or the text
might wrap if you've selected that formatting option).

One more follow-up...

In the interest of quantifying the statement I made, I wrote a small program
that tests all of the fonts on the computer for all calculated widths and
font sizes from the smallest to largest.

The basic algorithm:

For every font family installed:
Find the width of the font at 128 points
For every possible width between 1/128th of the max width and the
max width:
Calculate a new font size based on the ratio of the target width
to the max width
Find the actual width of the font for that calculated font size

Note: where I write "font size" what I actually mean is the width of a
sample string, arbitrarily chosen, at a given font size.

I divided the results into three categories:

* calculated font size definitely fit
* calculated font size might not fit
* calculated font size definitely does not fit

The "might not" comes about because for fractional widths, I don't know what
the defined Windows behavior is, and there may not in fact be a defined
behavior when the necessary width for a string is some fractional amount
larger than the desired size. For the sake of discussion, I assumed that
half of the "might nots" actually do result in display truncation or
unwanted wrapping, adding that half to the "definitely does not" for the
purpose of calculating error rates.

I tried a variety of methods, each variation involving truncating one or
more parts of the calculation (there were three places that the supporting
calculations could be truncated: the max width of the string, the calculated
font size, and the end result of the width for the string at the calculated
size).

Interestingly, the *best* results occurred when I truncated all of the
results of the calculations, including the target font size. One of the
worst outcomes was when the widths were truncated, but the font size was
not. This isn't entirely unexpected, since truncation of the font size and
the final width makes the string more likely to fit by making it slightly
smaller.

Generally speaking, the string fails to fit into the target width roughly
somewhere between 5% and 35% of the time, depending on how the calculations
are done (yes, I realize that's a huge variance).

For extra credit, I also added a histogram display to show me roughly what
range of scaling causes the most error. Not surprisingly, the results are
heavily weighted toward the larger scales (that is, a target font size
closer to the maximum). I say "not surprisingly", because it stands to
reason that when the absolute sizes are larger, small variations in
percentage are more likely going to result in multi-pixel errors.
Interestingly, the outcome here wasn't perfectly smooth, and in fact had
peaks and valleys for some calculation variations (for example, truncating
everything resulted in the bin for 88-91% scaling having more occurences
than either of the two higher bins).

Note that this weighting towards the sizes closer to the maximum means that
the error rate may be significantly higher than the stated "5% to 35%" when
considering scenarios in which the font size needs to be reduced only a
small amount.

Finally, I also tried calculating the target font size based on the Pixels
unit rather than Point unit. This did result in a significant improvement
in speed, but otherwise the basic results were very similar (slightly
"smoother" as far as the histogram goes, but otherwise basically the same).

Anyway, I hope that this helps illustrate the underlying problem. IMHO, the
main lessons to take away are:

* A straight calculation _can_ result in the incorrect answer
* You are more likely to get the incorrect answer when the string only
has to shrink a little bit and/or when the maximum size is large

I wish that there was a more reliable way to make a string fit in a specific
area, but at some point one really does need to do some trial and error
calculations to ensure that the font size chosen really does result in the
displayed string fitting entirely within the target rectangle. Otherwise,
your code is not assured of working 100% of the time.

Pete
 
F

ferociousturtle

A better way would be to use the MeasureString or equivalent GDI method
to find the width of a string using your current font. Then, use that
width to calculate the ratio by which you need to scale your font.

The fomula is:

CurrentWidth = MeasureString(myString, myFont).Width
newFontSize = CurrentFontSize * (DesiredWidth / CurrentWidth)


The following is an example in VB.NET.

Using g As Graphics = Me.CreateGraphics()
Dim w As Integer = g.MeasureString("HELLO WORLD",
Me.Font).Width
Me.Font = New Font(Me.Font.FontFamily, Me.Font.Size *
CSng(Me.Width / w), Me.Font.Style, Me.Font.Unit)
End Using
 
P

Peter Duniho

A better way would be to use the MeasureString or equivalent GDI method
to find the width of a string using your current font. Then, use that
width to calculate the ratio by which you need to scale your font.

The fomula is:

CurrentWidth = MeasureString(myString, myFont).Width
newFontSize = CurrentFontSize * (DesiredWidth / CurrentWidth)

If you had bothered to read the discussion in this thread, you would
understand that that's not a reliable way to do it. You're not even
guaranteed to be within one pixel (as "JR" implies).
The following is an example in VB.NET. [...]

Why are you posting VB code in a C# newsgroup?

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top