views:

563

answers:

4

I just wrote some code to scale a font to fit within (the length of) a rectangle. It starts at 18 width and iterates down until it fits.

This seems horribly inefficient, but I can't find a non-looping way to do it. This line is for labels in a game grid that scales, so I can't see a work-around solution (wrapping, cutting off and extending past the rectangle are all unacceptable).

It's actually pretty quick, I'm doing this for hundreds of rectangles and it's fast enough to just slow it down a touch.

If nobody comes up with anything better, I'll just load the starting guess from a table (so that it's much closer than 18) and use this--except for the lag it works great.

public Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float try=18.0f;
    Font font=pFont;

    while(x > 4) {                             
            font=g.getFont().deriveFont(try);
            FontMetrics fm=g.getFontMetrics(font);
            int width=fm.stringWidth(text);
            if(width <= rect.width)
                return font;
            try*=.9;            
    }
    return font;
}
A: 

A different, obvious way would be to have the text pre-drawn on a bitmap, and then shrink the bitmap to fit the rectangle; but, because of hand-crafted font design and hinting etc., finding the right font size produces the best-looking (although perhaps not the quickest) result.

ChrisW
+1  A: 

You can improve the efficiency using a binary search pattern - high/low with some granularity - either 1, 0.5 or 0.25 points.

For example, guess at 18, too high? Move to 9, too Low? 13.5, too low? 15.75, too high? 14!

Software Monkey
+2  A: 

semi-psuedo code:

public Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float fontSize = 20.0f;
    Font font = pFont;

    font = g.getFont().deriveFont(fontSize);
    int width = g.getFontMetrics(font).stringWidth(text);
    fontSize = (rect.width / width ) * fontSize;
    return g.getFont().deriveFont(fontSize);;
}

i'm not sure why you pass in pFont as it's not used...

Luke Schafer
Good answer. If I'm understanding your code, it reminds me of a Newton-Raphson approximation. Maybe do that twice instead of once, to be more accurate, as there may be some rounding error.
ChrisW
Good catch with the pFont--threw that in at the last second after scooping this code out of another method and didn't give it much thought.This looks tempting! I'm not totally sure I'd get the right size (not sure font sizes grow linearly) but if nothing else it will make an AWESOME first guess.
Bill K
Yeah, I thought about the fact that it mightn't scale like that. I figured that if you wanted it to be the size of a rectangle without it being within a bounding box then this would be fine. Otherwise, your testing would show if it's good enough and you could then add a short iteration loop to fix it.
Luke Schafer
A: 

You could use interpolation search:

public static Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float min=0.1f;
    float max=72f;
    float size=18.0f;
    Font font=pFont;

    while(max - min <= 0.1) {
        font = g.getFont().deriveFont(size);
        FontMetrics fm = g.getFontMetrics(font);
        int width = fm.stringWidth(text);
        if (width == rect.width) {
            return font;
        } else {
            if (width < rect.width) {
                min = size;
            } else {
                max = size;
            }
            size = Math.min(max, Math.max(min, size * (float)rect.width / (float)width));
        }
    }
    return font;
}
Laurence Gonsalves