Java: Friendlier way to get an instance of FontMetrics - java

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.

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:
If you call c.getGraphics() it will return null (thus there is no graphics object)
This (canvas) will also work in headless mode.
Now you can easily get height and width...

The really correct answer is to use Toolkit.
Font font = new Font("Courier New", Font.PLAIN, 14);
FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);

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.

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.

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;
}

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.

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.

Related

Is there any standard Java Component with a notification count

I am wondering if there is a Standard Java Component (Icon or JLabel) that offers the abillity to show a notification count like the example below. I am not doing Android Development. I just want to show something similar in a Java desktop client application
The following code in a JLabel subclass would get the same result, but I am more interested in a standard solution. Which automatically derives the correct font size and automatically adjust to the visible space.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int w = getWidth();
Font orgFont = g.getFont();
Font deriveFont = orgFont.deriveFont(8.5f);
g2d.setPaint(Color.RED);
g2d.fillOval(w - 12, 0, 12, 12);
g2d.setPaint(Color.BLACK);
g2d.setFont(deriveFont);
g2d.drawString("99", w-10, 10);
g2d.setFont(orgFont);
}
if there is a Standard Java Component (Icon or JLabel)
No there is no standard component.
You might be able to:
Use the Compound Icon. It allows you to combine multiple icons into one.
Add a second JLabel with the notification Icon to the first JLabel. By default a JLabel doesn't have a layout manager but there is no reason you can't use one.
So for example to add the label in the top/right corner you could do:
JLabel main = new JLabel(…);
main.setLayout( new FlowLayout(FlowLayout.FlowLayout.RIGHT, 5, 5) );
JLabel notify = new JLabel(…);
main.add( notify );

Is it possible to highlight a character partially?

Is it possible to achieve something like this in Swing :
Notice here A is colored partially.
I know may be its not possible with DefaultHighlighter class alone.
(Text displayed on JTextArea)
Any solutions to achieve this in Swing's alone or have to apply CSS?
EDIT:
If that is not possible with swing, any solution with the below tags ?
Something like this is possible with Java 2D but not with JTextArea. So you can create code which renders this in a frame but I don't think you'll find an easy way to add support for this to the text editors (well, you could always embed an image in a text editor but that's probably not what you want).
[EDIT] For a text display for a karaoke player, Java 2D is the way to go. The Java editor APIs will just get in your way.
Here is an example for rendering text with a gradient:
public void sayWorld(Graphics2D g2D, int x, int y, boolean shear) {
final String txt = "Hello World!";
// gradient color from blue to red
GradientPaint gp = new GradientPaint((float)x, (float)y, Color.blue,
x+100, y+20, Color.red);
g2D.setPaint(gp);
if (shear) g2D.shear(-0.5,0.0);
else g2D.shear(+0.5, 0);
g2D.drawString(txt, x, y);
FontRenderContext frc = new FontRenderContext(null,false,false);
TextLayout tl = new TextLayout(txt, font, frc);
AffineTransform textAt = new AffineTransform();
//textAt.translate(0, (float)tl.getBounds().getHeight());
textAt.translate(x,y);
//textAt.shear(-0.5,0.0);
Shape outline = tl.getOutline(textAt);
g2D.setColor(Color.yellow);
BasicStroke wideStroke = new BasicStroke(2);
g2D.setStroke(wideStroke);
g2D.draw(outline);
}
(source)
Basically, you render get the text layout from a single line of the lyrics, then convert that into a shape (= the outline of each letter). You can then use this shape as a clip area to render the gradient.
In your case, you need an abrupt gradient which is twice as wide as the longest text line that you want to render. Shift it appropriately to get the gradient change in the place of the text where you need it.

How to change the color of the background in libgdx labels?

I can change the color of the font like this
LabelStyle style1 = new LabelStyle(..some font...,
Color.valueOf("FF4500")
);
label.setStyle(style1);
but how do I change the background?
right now the background is the same as the background of whole screen which is set in
render method lke this
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
Gdx.gl.glClearColor(
1.000f, 0.980f, 0.941f
,1);
Label label = new Label(labelText, skin);
Pixmap labelColor = new Pixmap(labelWidth, labelHeight, Pixmap.Format.RGB888);
labelColor.setColor(<your-color-goes-here>);
labelColor.fill();
label.getStyle().background = new Image(new Texture(labelColor)).getDrawable();
Basically, use the getDrawable() function of the Image class to assign the color of your Label's LabelStyles' background Drawable.
This is the simplest workaround I've been able to come up with and, frankly, it's just silly that there is no setBackground() in the Label class.
Actually, maybe the easiest fix is to hack the Label class and add a setBackground() method to it.
[Edit] Be sure to dispose of Pixmaps when you are done with them; i.e. labelColor.dispose();
[Update] #Mitrakov Artem made a good point: The above solution will affect all instances of this LabelStyle. If that's not what you want you can create a new LabelStyle, use the above method on it, then save it to the Label. Quoting Artem: "So I would recommend to create a new style (LabelStyle style = new LabelStyle(label.getStyle());), change its background and then apply it to the label (label.setStyle(style);)"
Actually you do not change the background of the Lable like that. You did just change the clearcolour. Guess you know that.
To change the background you need to change the background at the style of the label. To do so i'd recommend to use a simple NinePatch as background, (can be a square! if its white you can change the colour of the ninepatch and the background colour changes!)
NinePatch temp = new NinePatch(new Texture(....), 10, 10, 10, 10); //edges
For more information about ninepatch take a look here libgdx wiki ninepatch
You need to add that ninepatch to an Skin objekt. For example like this
Skin skin = new Skin();
skin.add("background",temp)
After that you can get a drawable from the skin that you can set as background of the LabelStyle.
style1.background = skin.getDrawable("background");
see libgdx API LabelStyle
You can also use a simple bitmap but that does get scaled to the label size which causes in most of the cases deformation. A Ninepatch can be scaled without having deformation.
If you need a quick and easy solution, you can use the snippet below. It doesn't work well with multiline text because it doesn't take the text width per line into account.
Anyway, the background is automatically adjusted to the width and height of the label widget (i.e. if your text changes).
private Label label = new Label("text", createLabelStyleWithBackground());
private LabelStyle createLabelStyleWithBackground() {
LabelStyle labelStyle = new LabelStyle();
labelStyle.font = new BitmapFont();
labelStyle.fontColor = Color.WHITE;
labelStyle.background = createBackground();
return labelStyle;
}
private Drawable createBackground() {
Pixmap labelColor = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
Color color = new Color(Color.GRAY);
color.a = 0.75f;
labelColor.setColor(color);
labelColor.fill();
Texture texture = new Texture(labelColor);
return new BaseDrawable() {
#Override
public void draw(Batch batch, float x, float y, float width, float height) {
GlyphLayout layout = label.getGlyphLayout();
x = label.getX();
y = label.getY() - (layout.height + 15) / 2; // +15 is some space
batch.draw(texture, x, y, layout.width, layout.height + 15);
}
};
}
here is an example with a multiline label

String length in pixels in Java

Is there a way to calculate the length of a string in pixels, given a certain java.awt.Font object, that does not use any GUI components?
that does not use any GUI components?
It depends on what you mean here. I'm assuming you mean you want to do it without receiving a HeadlessException.
The best way is with a BufferedImage. AFAIK, this won't throw a HeadlessException:
Font font = ... ;
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
FontMetrics fm = img.getGraphics().getFontMetrics(font);
int width = fm.stringWidth("Your string");
Other than using something like this, I don't think you can. You need a graphics context in order to create a FontMetrics and give you font size information.
You can use the Graphics2D object to get the font bounds (including the width):
Graphics2D g2d = ...
Font font = ...
Rectangle2D f = font.getStringBounds("hello world!", g2d.getFontRenderContext());
But that depends on how you will get the Graphics2D object (for example from an Image).
This gives the output of (137.0, 15.09375) for me. I have no idea what the units are, but it certainly looks proportionally correct and doesn't use Graphics2D directly.
Font f = new Font("Ariel", Font.PLAIN, 12);
Rectangle2D r = f.getStringBounds("Hello World! Hello World!", new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT));
System.out.println("(" + r.getWidth() + ", " + r.getHeight() + ")");
I needed to get length and width of a string before paintComponent was called so I could size the enclosing panel to the text dimensions. None of these techniques provided a sufficiently width and I did not have a Graphics object available. I resolved the issue by setting my font to "Monospaced".

Java - FontMetrics without Graphics

How to get FontMetrics without use Graphics ? I want to get FontMetrics in constructor, now I do this way:
BufferedImage bi = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB);
FontMetrics fm = bi.getGraphics().getFontMetrics(font);
int width = fm.stringWidth(pattern);
int height = fm.getHeight();
No you do not necessarily need to get/use the graphics object:
Font font = new Font("Helvetica",Font.PLAIN,12);
Canvas c = new Canvas();
FontMetrics fm = c.getFontMetrics(font);
If you would now call c.getGraphics() it would return null. The canvas solution on the other hand will also work in headless mode.
Hmm... It is quite logical that you need graphics to get FontMetrics. Font height, width etc. can differ on various displays.
If you have some Component, you can use it for getting FontMetrics:
component.getFontMetrics(font);

Categories