Reputation: 3282
So the problem I have is that I start my application by displaying a simple menu. To size and align the text correctly I need to obtain font metrics and I cannot find a way to do it quickly. I tested my program and it looks like whatever method I use to obtain font metrics the first call takes over 500 milliseconds!? Because of it the time it takes to start-up my application is much longer than necessary.
I don't know if it is platform specific or not, but just in case, I'm using Mac OS 10.6.2 on MacBook Pro (hardware isn't an issue here).
If you know a way of obtaining font metrics quicker please help.
I tried these 3 methods for obtaining the font metrics and the first call is always very slow, no matter which method I choose.
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import javax.swing.JFrame;
public class FontMetricsTest extends JFrame {
public FontMetricsTest() {
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Font font = new Font("Dialog", Font.BOLD, 10);
long start = System.currentTimeMillis();
FontMetrics fontMetrics = g2.getFontMetrics(font);
// LineMetrics fontMetrics1 =
// font.getLineMetrics("X", new FontRenderContext(null, false, false));
// FontMetrics fontMetrics2 = g.getFontMetrics();
long end = System.currentTimeMillis();
System.out.println(end - start);
g2.setFont(font);
}
public static void main(String[] args) {
new FontMetricsTest();
}
}
Upvotes: 5
Views: 1879
Reputation: 42223
While not exactly the same code I also noticed that drawString
was slow as well, could took around ~300ms on an Apple Macbook M1.
Profiling revealed that a call drawString
had to load OS fonts (sun.font.CFontManager.loadNativeFonts()
) which took in this run ~203ms sometimes it's slower.
And as a matter of fact Graphics2d::getFontMetrics
also has to load font to get its metadata, this is done along the way by a sun.font.FontUtilities.getFont2D(Font)
. You can notice in this flamegraph fragment that it is also present.
So what happens there is that little programs like we have here are likely to pay the price of the first use of the font. In regular programs it's likely other Swing components, like a JLabel
will have paid the price for us. E.g.
So the suggestion there would be to make a regular Swing UI with a JLabel
, and actually draw on a JPanel
.
If getting the font involves heavy computation, another suggestion would be to resolve the font / font metrics at a different time than paint, leveraging the updateUI
to refresh the font when needed (e.g. a LaF change). In pseudo code :
class MyCanvas extends JPanel {
private Font myFont; // always set by updateUI
@Override
public void updatedUI() {
myFont = slowFontAccess();
}
public void paintComponent(Graphics g) {
...
g2.getFontMetrics(myFont)
...
}
}
Upvotes: 0
Reputation: 1877
While I can't tell you how to get around the issue itself, you can use this method to decide exactly when to init it:
new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY).createGraphics().getFontMetrics();
This is useful because you can put it anywhere - so, for example, you could do it while you display a loading screen or something. If you use a Graphics
object during paint()
, then you're restricted to only initializing while rendering.
EDIT:
In fact, this can be reduced to:
FontUtilities.getFont2D(new Font("Dialog", 0, 12));
(The slow part is the getFont2D
call, not the Font
constructor.)
EDIT 2:
And, finally, this can be reduced to:
sun.font.FontManagerFactory.getInstance();
The issue is that this singleton class needs a long time to start up, as it enumerates all of the system fonts.
EDIT 3:
There is no good way around this issue if you want to use the standard Graphics system.
Upvotes: 3
Reputation: 17422
No real clue as for why it's so slow, but for method 3, shouldn't you be calling 'setFont' first?
public void paint(Graphics g) {
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
}
It doesn't make a difference speed-wise, though :-(
Also, it's a bit uneconomic to create a new Font
each time paint()
is called (which happens a lot), you could move that to your constructor. But that cannot be the problem here, as you start measuring the time only after the Font has been created.
Upvotes: 1