I'm developing a web application which use JFreeChart to render chart. However, when server dose not have any Chinese font installed, JFreeChart dose not display Chinese character even if I have set the font.
Then I write a small testing code and find out that add this line of code before drawing the chart can solve the problem.
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
So my questions are -
Why do I have to register font into JVM even if I create my font from File? Does that mean JFreeChart do not use the font I set directly?
When I deploy my program into server, even if I add this line of code, it dose not display Chinese characters. How do I make it always use the font I set in order to display character properly in all environment?
I know I can make a fallback
directory in $JAVA_HOME/jre/lib
and put my font into it. But that dose not explain why JFreeChart cannot display with the font I set.
I'm pretty sure the font were load correctly, and so does registerFont()
return true when I deploy the program into Tomcat.
According to JAVA 2D FAQ, now I realize I have to call registerFont()
in order to make my own font "installed" into JVM, and my font will be available through Font
constructor.
As of Java SE 6, there is a method : GraphicsEnvironment.registerFont() which gives you the ability to make a "created" font available to Font constructors and to be listed via Font enumeration APIs. Font.createFont() and this method combine to provide a way to "install" a Font into the running JRE so it is available just as O/S installed fonts are. However this Font does not persist across JRE invocations.
But, since I already have Font
instances created/derived from createFont()
, why doesn't my program still need to create other Font
?
Following is the code I used, it simply output a chart in PNG format. If you want to run the code, you should change the output location and font to fit your need, and here is the SourceForge link for Chinese font I use in code.
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.File;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
public class Problem {
public static void main(String[] args) throws Exception {
setJFreeChartTheme();
PieDataset dataset = createDataSet();
JFreeChart chart = ChartFactory.createPieChart(
"Chinese Testing", dataset, true, true, false);
ChartUtilities.saveChartAsJPEG(new File("/tmp/output.png"),
chart, 800, 600);
System.out.println("Done");
}
private static void setJFreeChartTheme() throws Exception {
Font font = loadFont();
//==================================================================
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
//==================================================================
StandardChartTheme theme = new StandardChartTheme("Chinese font", true);
theme.setExtraLargeFont(font.deriveFont(Font.BOLD, 20));
theme.setLargeFont(font.deriveFont(Font.BOLD, 16));
theme.setRegularFont(font.deriveFont(Font.PLAIN, 14));
theme.setSmallFont(font.deriveFont(Font.PLAIN, 12));
ChartFactory.setChartTheme(theme);
}
private static Font loadFont() throws Exception {
File file = new File("/tmp/wqy-zenhei.ttc");
return Font.createFont(Font.TRUETYPE_FONT, file);
}
private static PieDataset createDataSet() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("種類1", Integer.valueOf(1));
dataset.setValue("種類2", Integer.valueOf(2));
dataset.setValue("種類3", Integer.valueOf(3));
return dataset;
}
}
When you create a Font
directly from the TTF, Java obviously knows where to get a copy of the font file itself from within that single Font object. So why does the font also need to be registered in order to use it? The answer is that it does not always have to be registered, or at least not so long as the entire chain of control uses the original Font
object directly.
The nuance is in how JFreeChart asks to render the text. Rendering of text is performed in the TextUtilities#drawRotatedString
method from jcommon. On JDK7, this method will, by default:
AttributedString
based on the "attributes" of the font that you have passed in,Graphics2D#drawString
on the attributed string, which thenTextLayout
object.TextLayout
is the class that selects the actual Font object to be provided to Graphics2D
. TextLayout
is designed to support the rendering of multilingual text using various fonts (even if the same single source String needs to be rendered in multiple fonts) by using automatic font selection to find an appropriate font for each piece of the string.
The "attributes" mentioned above are simple facts about the font (like font family name, size, etc) that are derived from the Font
that you provide. In the event that the font you've provided is unable to render all of the characters in the input string, the attributes are used to select similar fonts to use for those runs of text that need to use a different font.
When JFreeChart invokes TextLayout, it always functions like this:
Font
object you supplied,Font#getFont
to get a font that matches the supplied attributes (see TextLayout#singleFont
), andIf you have not statically registered your font somewhere (like with GraphicsEnvironment#registerFont
), then all that the static Font#getFont
method will have to go on is the attribute string containing the font family name. It won't know where to access the Font object that contains the reference to the TTF, let alone any of the data needed to actually render the font.
If you don't want to register the font, then the trick is in making sure that your text is rendered only with the Font
object that you provide. It so happens that there is another constructor for TextLayout
that accepts a Font
object directly, rather than a set of attributes that are used to look up a font.
Helpfully, JFreeChart even provides a way to force it to use this constructor instead. In TextUtilities#drawRotatedString
, a special configuration parameter can be used to force JFreeChart to construct the TextLayout
object itself using the exact Font
object you provide.
To do this, you can either set up a jcommon.properties file like this:
jcommon.properties
(which should end up in the root level of your classpath/JAR), andorg.jfree.text.UseDrawRotatedStringWorkaround=true
or else simply make a call to the static function:
TextUtilities.setUseDrawRotatedStringWorkaround(true)
This will ask JFreeChart to render the text using your font directly, and...voila! It works even without having registered the font. This was tested in the context of the question above, which is that of using JFreeChart to render the text directly to a raster image. Your mileage might vary if you are trying to render to a display device (which I did not try).
I can't say for sure. One of my applications runs inside an OSGi container and I was wary of creating PermGen classloader leaks by statically registering a Font that can never be unregistered. Using the Font object directly avoids that problem, which is why I wanted to go this route. I suppose it's always possible that a specific Java platform might have trouble if you did this, but this works fine in my tests on at least Windows, Linux, and OS X hosts with Oracle JDK 7.