Martijn Courteaux
Martijn Courteaux

Reputation: 68907

Java: Friendlier way to get an instance of FontMetrics

Is there a friendlier way to get an instance of FontMetrics than

FontMetrics fm = Graphics.getFontMetrics(Font);

I hate this way because of the following example:

If you want to create in a game a menu and you want all the menuitems in the center of the screen you need fontmetrics. But, mostly, menuitems are clickable. So I create an array of Rectangles and all the rectangles fits around the items, so when the mouse is pressed, I can simply use

for (int i = 0; i < rects.length; i++)
if (rects[i].contains(mouseX, mouseY)) { ... }

But to create the rects I also need FontMetrics for their coordinates. So this mean that I have to construct all my rectangles in the paint-method of my menu.

So I want a way to get the FontMetrics so I can construct the Rectangles in a method called by the constructor.

Upvotes: 4

Views: 9008

Answers (7)

WJS
WJS

Reputation: 40057

Updated recommendation. FontMetrics is deprecated. Use LineMetrics instead.

String text = "some string";
FontRenderContext frc = new FontRenderContext(font.getTransform(), true, true);
LineMetrics lm = font.getLineMetrics(text, frc);

However, some methods such as SwingUtilities.computeStringWidth require a FontMetrics instance. Another option is to compute the bounds of the String.

Rectangle2D bounds = font.getStringBounds(text, frc);

Then the width and height may be obtained from the bounds.

Upvotes: 0

ghostNet
ghostNet

Reputation: 31

Adding to what Lonzak said, how about this:

public static FontMetrics getFontMetrics(Font font){
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    GraphicsConfiguration config = gd.getDefaultConfiguration();

    Canvas c = new Canvas(config);
    return c.getFontMetrics(font);
}

You could store the 'config' variable as a static variable so it is constructed once in some utility font class that contains other font related information for your game/development environment. I guess you could also do this with the canvas variable.

Upvotes: 0

Lonzak
Lonzak

Reputation: 9816

For me the easiest way was to:

Font font = new Font("Helvetica",Font.PLAIN,12);
Canvas c = new Canvas();
FontMetrics fm = c.getFontMetrics(font);

Benefits:

  1. If you call c.getGraphics() it will return null (thus there is no graphics object)
  2. This (canvas) will also work in headless mode.

Now you can easily get height and width...

Upvotes: 12

Martijn Courteaux
Martijn Courteaux

Reputation: 68907

The really correct answer is to use Toolkit.

Font font = new Font("Courier New", Font.PLAIN, 14);
FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);

Upvotes: 7

Martijn Courteaux
Martijn Courteaux

Reputation: 68907

I think this is a good solution

private static HashMap<Font, FontMetrics> fontmetrics = new HashMap<Font, FontMetrics>();


public static FontMetrics getFontMetrics(Font font)
{
    if (fontmetrics.containsKey(font))
    {
        return fontmetrics.get(font);
    }
    FontMetrics fm = createFontMetrics(font);
    fontmetrics.put(font, fm);
    return fm;
}

private static FontMetrics createFontMetrics(Font font)
{
    BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
    Graphics g = bi.getGraphics();
    FontMetrics fm = g.getFontMetrics(font);
    g.dispose();
    bi = null;
    return fm;
}

Upvotes: 1

McDowell
McDowell

Reputation: 108979

Assuming the menu text is fixed, you could pre-draw the text to a BufferedImage with alpha transparency and make your calculations then. Then, when you need the menu text, just draw the image.

You'll still have to do some offset calculations to centre the image (assuming the panel size can change), but these should be relatively lightweight.

Upvotes: 1

Carl Smotricz
Carl Smotricz

Reputation: 67820

Once the background component, i.e. whatever is behind your menu, has been rendered, it has a Graphics object that you can use to get the metrics for a given font, just once.

You certainly don't want to be doing this in the paint method, which should be as lightweight as possible. I'd hang this code on a listener that gets called when the component is first rendered. It can store the resulting FontMetrics object somewhere where you can later access it, either in a paint method for drawing those menu item boxes.

Rather than determining the measurements of your menu graphics at the last moment, i.e. when painting, it might be a good idea instead to create some components to represent your menu. You can place those components on the Glass Pane more info here so they'll float above everything else, and you'll have the added bonus that those components are all capable of accepting mouse clicks and firing listener events on them, and since they only capture events on their own geometry you don't even have to figure out which part of menu was hit by the click, if at all.

Another advantage of using components here is that you may entirely get around the requirement for fiddling with font metrics. There are ready-made menu items, or you could just use JLabels, and you can specify their alignment, you can use a LayoutManager to size the boxes to the width of the biggest label, and so forth.

Upvotes: 2

Related Questions