When rendering a chart with JFreeChart, I noticed a layout problem when the chart's category labels included Japanese characters. Although the text is rendered with the correct glyphs, the text was positioned in the wrong location, presumably because the font metrics were wrong.
The chart was originally configured to use the Source Sans Pro Regular font for that text, which supports only Latin character sets. The obvious solution is to bundle an actual Japanese .TTF font and ask JFreeChart to use it. This works fine, in that the output text uses the correct glyphs and it is also laid out correctly.
My questions
How did java.awt end up rendering the Japanese characters correctly in the first scenario, when using a source font that doesn't actually support anything except Latin characters? If it matters, I am testing on OS X 10.9 with JDK 1.7u45.
Is there any way to render the Japanese characters without bundling a separate Japanese font? (This is my end goal!) Although the bundling solution works, I don't want to add 6 Mb of bloat to my application if it can be avoided. Java clearly knows how to render the Japanese glyphs somehow even without the font (at least in my local environment)--it's seemingly just the metrics that are busted. I am wondering if this is related to the "frankenfont" issue below.
After the JRE performs an internal transformation, why does the Source Sans Pro font tell the caller (via canDisplayUpTo()) that it can display Japanese characters even though it cannot? (See below.)
Edited to clarify:
This is a server app, and the text we are rendering will show up in the client's browser and/or in PDF exports. The charts are always rasterized to PNGs on the server.
I have no control over the server OS or environment, and as nice as it would be to use the Java-standard platform fonts, many platforms have poor font choices that are unacceptable in my use case, so I need to bundle my own (at least for the Latin fonts). Using a platform font for the Japanese text is acceptable.
The app can potentially be asked to display a mix of Japanese and Latin text, without no a priori knowledge of the text type. I am ambivalent about what fonts get used if a string contains mixed languages, so long as the glyphs are rendered correctly.
Details
I understand that java.awt.Font#TextLayout is smart, and that when trying to lay out text, it first asks the underlying fonts whether they can actually render the supplied characters. If not, it presumably swaps in a different font that knows how to render those characters, but this is not happening here, based on my debugging pretty far into the JRE classes. TextLayout#singleFont
always returns a non-null value for the font and it proceeds through the fastInit()
part of the constructor.
One very curious note is that the Source Sans Pro font somehow gets coerced into telling the caller that it does know how to render Japanese characters after the JRE performs a transformation on the font.
For example:
// We load our font here (download from the first link above in the question)
File fontFile = new File("/tmp/source-sans-pro.regular.ttf");
Font font = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(fontFile));
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
// Here is some Japanese text that we want to display
String str = "クローズ";
// Should say that the font cannot display any of these characters (return code = 0)
System.out.println("Font " + font.getName() + " can display up to: " + font.canDisplayUpTo(str));
// But after doing this magic manipulation, the font claims that it can display the
// entire string (return code = -1)
AttributedString as = new AttributedString(str, font.getAttributes());
Map<AttributedCharacterIterator.Attribute,Object> attributes = as.getIterator().getAttributes();
Font newFont = Font.getFont(attributes);
// Eeek, -1!
System.out.println("Font " + newFont.getName() + " can display up to: " + newFont.canDisplayUpTo(str));
The output of this is:
Font Source Sans Pro can display up to: 0
Font Source Sans Pro can display up to: -1
Note that the three lines of "magic manipulation" mentioned above are not something of my own doing; we pass in the true source font object to JFreeChart, but it gets munged by the JRE when drawing the glyphs, which is what the three lines of "magic manipulation" code above replicates. The manipulation shown above is the functional equivalent of what happens in the following sequence of calls:
- org.jfree.text.TextUtilities#drawRotatedString
- sun.java2d.SunGraphics2D#drawString
- java.awt.font.TextLayout#(constructor)
- java.awt.font.TextLayout#singleFont
When we call Font.getFont() in the last line of the "magic" manipulation, we still get a Source Sans Pro font back, but the underlying font's font2D
field is different than the original font, and this single font now claims that it knows how to render the entire string. Why? It appears that Java is giving us back some sort of "frankenfont" that knows how to render all kinds of glyphs, even though it only understands the metrics for the glyphs that are supplied in the underlying source font.
A more complete example showing the JFreeChart rendering example is here, based off one of the JFreeChart examples: https://gist.github.com/sdudley/b710fd384e495e7f1439 The output from this example is shown below.