5

I'm looking for a way to infer a Java AWT Font size from a width. For example, I know I want to write 'hello world' within 100 pixels. I know I'm using the font "Times", in style Font.PLAIN, and I want to get the font size that fits the best with my given width of 100 pixels.

I know I could calculate it in a loop (something like while(font.getSize() < panel.getWidth()), but to be honest I don't find it very elegant.

jules
  • 73
  • 2
  • 8
  • 1
    And what is "the font size that fits the best with the width of the panel"? – jarnbjo Jan 17 '13 at 13:21
  • The ideal size depends on the text you are trying to write, unless you have a fixed width font. – Peter Lawrey Jan 17 '13 at 13:22
  • Yes sorry I forgot to mention that I also know the string I want to use ! I edit my post... – jules Jan 17 '13 at 13:23
  • Ok my post can be misleading I edit it better – jules Jan 17 '13 at 13:27
  • 1
    *"..I don't find it very elegant."* Sometimes it pays to use what works, irrespective of elegance. Having said that, you might simply use an `AffineTransform` to shrink & stretch the text as in [this answer](http://stackoverflow.com/a/13440543/418556). – Andrew Thompson Jan 17 '13 at 13:39
  • 1
    Thank you for your comment. When I said "not very elegant", I actually meant "too expansive for what it is" (as I have to create a new Font object at every iteration...) But yes I'll take a look at the AffineTransform object it seems to answer to my needs :) – jules Jan 17 '13 at 13:59

2 Answers2

5

You can get the rendered width and height of a string using the FontMetrics class (be sure to enable fractional font metrics in the Graphics2D instance to avoid rounding errors):

Graphics2D g = ...;
g.setRenderingHint(
    RenderingHints.KEY_FRACTIONALMETRICS, 
    RenderingHints.VALUE_FRACTIONALMETRICS_ON);

Font font = Font.decode("Times New Roman");

String text = "Foo";

Rectangle2D r2d = g.getFontMetrics(font).getStringBounds(text, g);

Now, when you have the width of the text using a font with the default (or actually any) size, you can scale the font, so that the text will fit within a specified width, e.g. 100px:

font = font.deriveFont((float)(font.getSize2D() * 100/r2d.getWidth()));

Similarly, you may have to limit the font size, so that you don't exceed the available panel height.

To improve the appearance of the rendered text, you should also consider enabling antialiasing for text rendering and/or kerning support in the font:

g.setRenderingHint(
    RenderingHints.KEY_TEXT_ANTIALIASING, 
    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

Map<TextAttribute, Object> atts = new HashMap<TextAttribute, Object>();
atts.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
font = font.deriveFont(atts);
jarnbjo
  • 33,923
  • 7
  • 70
  • 94
  • Thank you for your response. Actually I made some tests thinking about this method, and I found out that the font size is not linear regarding the text width! For instance, given a font and a text, if a font size of 1 corresponds to a text width of 10 pixels, a font size of 100 will not necessarily give a text width of 1000 pixels... – jules Jan 17 '13 at 13:52
  • You are right that there are some rounding errors using a relatively small font (e.g. 12pt) to calculate the target size. You can workaround this by enabling "fractional" font metrics in the graphics instance. I've update my answer. – jarnbjo Jan 17 '13 at 14:13
1

Have a look at these two methods I am using. It is not elegant as you say, but it works.

private static int pickOptimalFontSize (Graphics2D g, String title, int width, int height) {
    Rectangle2D rect = null;

    int fontSize = 30; //initial value
    do {
        fontSize--;
        Font font = Font("Arial", Font.PLAIN, fontSize);
        rect = getStringBoundsRectangle2D(g, title, font);
    } while (rect.getWidth() >= width || rect.getHeight() >= height);

    return fontSize;
}

public static Rectangle2D getStringBoundsRectangle2D (Graphics g, String title, Font font) {
    g.setFont(font);
    FontMetrics fm = g.getFontMetrics();
    Rectangle2D rect = fm.getStringBounds(title, g);
    return rect;
}
Nikolay Kuznetsov
  • 9,467
  • 12
  • 55
  • 101
  • Yes this is the kind of method I was thinking at first. I'll try to take a look at the AffineTransform given by Adrew Thompson in the upper comments, and if I don't find a satisfaying way of using it I will go back to your answer. Thanks anyway! – jules Jan 17 '13 at 13:54