Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting string size in java (without having a Graphics object available)

I'm trying to write application which need to draw many strings using Graphics2D class in Java. I need to get sizes of each String object (to calculate exact position of each string). There are so many strings that it should be done before the paint() method is called and only once at the beginning of my program (so then I don't have Graphics2D object yet). I know that there is a method Font.getStringBounds() but it needs a FontRenderContext object as a parameter.

When i tried to create my own object:

FontRenderContext frc = new FontRenderContext(MyFont.getTransform(), true, true)

and then obtain the strings bounds I've always get different sizes than when I obtain FontRenderContext using Graphics2D.getFontRenderContext() method inside paint(). The differences are not big (about 1E-3) but I wonder why there is any difference at all?

However, is there any better and secure way to obtain sizes of a string?

Thnx for any help in advance!

like image 214
Lukasz Spas Avatar asked Feb 06 '11 15:02

Lukasz Spas


2 Answers

Nev-ah. Gon-na. Happen.

The reason is the rendering and computation you're looking for from FRC is specific to a Graphics context, i.e. a specific Graphics2D object. The one you're interested in is one you're handed at runtime- it's like no other (you have to assume).

You can compute as much as you want using an FRC from some other Graphics2D, but your computations all all for naught when you try to use them at runtime with the Graphics2D paintComponent is handed, which is the Graphics2D you're going to use, no matter what.

So, yes, this would be nice but it's entirely theoretical. All that nice information is effectively locked away inside that FRC because without the exact Graphics2D the AttributedString is actually going to be drawn to, that FRC is worse than useless- it's an illusion you might actually try to embrace.

It makes sense, since everything really IS dependent on the Graphics2D you get handed at runtime. So the best thing to do is just accept it and write your code to call out from within paintComponent out to whatever objects and whatever specialized computation you have to do and build your design around the fact that THIS is the way things are.

I's a good question and a good thing to wish you could do, just, you can't. You see other people asking for this elsewhere on the web, in other forums. Notice the lack of useful answers and / or deafening silence.

like image 68
Swing God Avatar answered Oct 13 '22 19:10

Swing God


Besides using FontMetrics, a JLabel can be used to determine the size of both unformatted and (basic HTML) rendered text. Here is an example.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import java.awt.image.BufferedImage;

import javax.swing.JOptionPane;
import javax.swing.JLabel;
import javax.swing.ImageIcon;

/** Sample code obtained from a thread on the Oracle forums that I cannot
locate at this instant.  My question was related to an unexpected rendering of
JLabel.  It was resolved by the 'added this' line courtesy of Darryl Burke. */
public class LabelRenderTest {

  String title = "<html><body style='width: 160px; padding: 8px'>"
          + "<h1>Do U C Me?</h1>"
          + "Here is a long string that will wrap.  "
          + "The effect we want is a multi-line label.";

  LabelRenderTest() {
    BufferedImage image = new BufferedImage(
            640,
            480,
            BufferedImage.TYPE_INT_RGB);
    Graphics2D imageGraphics = image.createGraphics();
    GradientPaint gp = new GradientPaint(
            20f, 20f, Color.blue,
            620f, 460f, Color.white);
    imageGraphics.setPaint(gp);
    imageGraphics.fillRect(0, 0, 800, 600);

    JLabel textLabel = new JLabel(title);
    textLabel.setSize(textLabel.getPreferredSize()); // <==== added this

    Dimension d = textLabel.getPreferredSize();
    BufferedImage bi = new BufferedImage(
            d.width,
            d.height,
            BufferedImage.TYPE_INT_ARGB);
    Graphics g = bi.createGraphics();
    g.setColor(new Color(255, 255, 255, 128));
    g.fillRoundRect(
            0,
            0,
            bi.getWidth(null),
            bi.getHeight(null),
            15,
            10);
    g.setColor(Color.black);
    textLabel.paint(g);
    Graphics g2 = image.getGraphics();
    g2.drawImage(bi, 20, 20, null);

    ImageIcon ii = new ImageIcon(image);
    JLabel imageLabel = new JLabel(ii);

    JOptionPane.showMessageDialog(null, imageLabel);
  }

  public static void main(String[] args) {
    LabelRenderTest ist = new LabelRenderTest();
  }
}

Edit 1: As to your "many strings" comment. Paint the strings to a BufferedImage that is only regenerated if needed. Use the buffered image each time paintComponent() is called.

like image 29
Andrew Thompson Avatar answered Oct 13 '22 19:10

Andrew Thompson